001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2009-2010 Sun Microsystems, Inc. 015 * Portions copyright 2013-2016 ForgeRock AS. 016 */ 017package org.forgerock.opendj.ldap.schema; 018 019import static com.forgerock.opendj.ldap.CoreMessages.*; 020 021import java.util.Arrays; 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027 028import org.forgerock.i18n.LocalizableMessage; 029import org.forgerock.opendj.ldap.Assertion; 030import org.forgerock.opendj.ldap.ByteSequence; 031import org.forgerock.opendj.ldap.ByteString; 032import org.forgerock.opendj.ldap.DecodeException; 033import org.forgerock.opendj.ldap.spi.Indexer; 034import org.forgerock.opendj.ldap.spi.IndexingOptions; 035 036/** 037 * This class defines a data structure for storing and interacting with matching 038 * rules, which are used by servers to compare attribute values against 039 * assertion values when performing Search and Compare operations. They are also 040 * used to identify the value to be added or deleted when modifying entries, and 041 * are used when comparing a purported distinguished name with the name of an 042 * entry. 043 * <p> 044 * Matching rule implementations must extend the 045 * <code>MatchingRuleImplementation</code> class so they can be used by OpenDJ. 046 * <p> 047 * Where ordered sets of names, or extra properties are provided, the ordering 048 * will be preserved when the associated fields are accessed via their getters 049 * or via the {@link #toString()} methods. 050 */ 051public final class MatchingRule extends AbstractSchemaElement { 052 053 /** A fluent API for incrementally constructing matching rules. */ 054 public static final class Builder extends SchemaElementBuilder<Builder> { 055 private String oid; 056 private final List<String> names = new LinkedList<>(); 057 private boolean isObsolete; 058 private String syntaxOID; 059 private MatchingRuleImpl impl; 060 061 Builder(final MatchingRule mr, final SchemaBuilder builder) { 062 super(builder, mr); 063 this.oid = mr.oid; 064 this.names.addAll(mr.names); 065 this.isObsolete = mr.isObsolete; 066 this.syntaxOID = mr.syntaxOID; 067 this.impl = mr.impl; 068 } 069 070 Builder(final String oid, final SchemaBuilder builder) { 071 super(builder); 072 oid(oid); 073 } 074 075 /** 076 * Adds this matching rule to the schema, throwing a 077 * {@code ConflictingSchemaElementException} if there is an existing 078 * matching rule with the same numeric OID. 079 * 080 * @return The parent schema builder. 081 * @throws ConflictingSchemaElementException 082 * If there is an existing matching rule with the same 083 * numeric OID. 084 */ 085 public SchemaBuilder addToSchema() { 086 return getSchemaBuilder().addMatchingRule(new MatchingRule(this), false); 087 } 088 089 /** 090 * Adds this matching rule to the schema overwriting any existing matching rule with the same numeric OID. 091 * 092 * @return The parent schema builder. 093 */ 094 public SchemaBuilder addToSchemaOverwrite() { 095 return getSchemaBuilder().addMatchingRule(new MatchingRule(this), true); 096 } 097 098 /** 099 * Adds this matching rule to the schema, overwriting any existing matching rule 100 * with the same numeric OID if the overwrite parameter is set to {@code true}. 101 * 102 * @param overwrite 103 * {@code true} if any matching rule with the same OID should be overwritten. 104 * @return The parent schema builder. 105 */ 106 SchemaBuilder addToSchema(final boolean overwrite) { 107 return overwrite ? addToSchemaOverwrite() : addToSchema(); 108 } 109 110 @Override 111 public Builder description(final String description) { 112 return description0(description); 113 } 114 115 @Override 116 public Builder extraProperties(final Map<String, List<String>> extraProperties) { 117 return extraProperties0(extraProperties); 118 } 119 120 @Override 121 public Builder extraProperties(final String extensionName, final String... extensionValues) { 122 return extraProperties0(extensionName, extensionValues); 123 } 124 125 /** 126 * Adds the provided user friendly names. 127 * 128 * @param names 129 * The user friendly names. 130 * @return This builder. 131 */ 132 public Builder names(final Collection<String> names) { 133 this.names.addAll(names); 134 return this; 135 } 136 137 /** 138 * Adds the provided user friendly names. 139 * 140 * @param names 141 * The user friendly names. 142 * @return This builder. 143 */ 144 public Builder names(final String... names) { 145 return names(Arrays.asList(names)); 146 } 147 148 /** 149 * Specifies whether this schema element is obsolete. 150 * 151 * @param isObsolete 152 * {@code true} if this schema element is obsolete (default is {@code false}). 153 * @return This builder. 154 */ 155 public Builder obsolete(final boolean isObsolete) { 156 this.isObsolete = isObsolete; 157 return this; 158 } 159 160 /** 161 * Sets the numeric OID which uniquely identifies this matching rule. 162 * 163 * @param oid 164 * The numeric OID. 165 * @return This builder. 166 */ 167 public Builder oid(final String oid) { 168 this.oid = oid; 169 return this; 170 } 171 172 /** 173 * Sets the syntax OID of this matching rule. 174 * 175 * @param syntax 176 * The syntax OID. 177 * @return This builder. 178 */ 179 public Builder syntaxOID(final String syntax) { 180 this.syntaxOID = syntax; 181 return this; 182 } 183 184 /** 185 * Sets the matching rule implementation. 186 * 187 * @param implementation 188 * The matching rule implementation. 189 * @return This builder. 190 */ 191 public Builder implementation(final MatchingRuleImpl implementation) { 192 this.impl = implementation; 193 return this; 194 } 195 196 @Override 197 public Builder removeAllExtraProperties() { 198 return removeAllExtraProperties0(); 199 } 200 201 /** 202 * Removes all user friendly names. 203 * 204 * @return This builder. 205 */ 206 public Builder removeAllNames() { 207 this.names.clear(); 208 return this; 209 } 210 211 @Override 212 public Builder removeExtraProperty(final String extensionName, final String... extensionValues) { 213 return removeExtraProperty0(extensionName, extensionValues); 214 } 215 216 /** 217 * Removes the provided user friendly name. 218 * 219 * @param name 220 * The user friendly name to be removed. 221 * @return This builder. 222 */ 223 public Builder removeName(final String name) { 224 names.remove(name); 225 return this; 226 } 227 228 @Override 229 Builder getThis() { 230 return this; 231 } 232 } 233 234 private final String oid; 235 private final List<String> names; 236 private final boolean isObsolete; 237 private final String syntaxOID; 238 private MatchingRuleImpl impl; 239 private Syntax syntax; 240 private Schema schema; 241 242 private MatchingRule(final Builder builder) { 243 super(builder); 244 245 // Checks for required attributes. 246 if (builder.oid == null || builder.oid.isEmpty()) { 247 throw new IllegalArgumentException("An OID must be specified."); 248 } 249 if (builder.syntaxOID == null || builder.syntaxOID.isEmpty()) { 250 throw new IllegalArgumentException("Required syntax OID must be specified."); 251 } 252 253 oid = builder.oid; 254 names = SchemaUtils.unmodifiableCopyOfList(builder.names); 255 isObsolete = builder.isObsolete; 256 syntaxOID = builder.syntaxOID; 257 impl = builder.impl; 258 } 259 260 /** 261 * Returns {@code true} if the provided object is a matching rule having the 262 * same numeric OID as this matching rule. 263 * 264 * @param o 265 * The object to be compared. 266 * @return {@code true} if the provided object is a matching rule having the 267 * same numeric OID as this matching rule. 268 */ 269 @Override 270 public boolean equals(final Object o) { 271 if (this == o) { 272 return true; 273 } else if (o instanceof MatchingRule) { 274 final MatchingRule other = (MatchingRule) o; 275 return oid.equals(other.oid); 276 } else { 277 return false; 278 } 279 } 280 281 /** 282 * Returns the normalized form of the provided assertion value, which is 283 * best suited for efficiently performing matching operations on that value. 284 * The assertion value is guaranteed to be valid against this matching 285 * rule's assertion syntax. 286 * 287 * @param value 288 * The syntax checked assertion value to be normalized. 289 * @return The normalized version of the provided assertion value. 290 * @throws DecodeException 291 * if the syntax of the value is not valid. 292 */ 293 public Assertion getAssertion(final ByteSequence value) throws DecodeException { 294 return impl.getAssertion(schema, value); 295 } 296 297 /** 298 * Returns the normalized form of the provided assertion substring values, 299 * which is best suited for efficiently performing matching operations on 300 * that value. 301 * 302 * @param subInitial 303 * The normalized substring value fragment that should appear at 304 * the beginning of the target value. 305 * @param subAnyElements 306 * The normalized substring value fragments that should appear in 307 * the middle of the target value. 308 * @param subFinal 309 * The normalized substring value fragment that should appear at 310 * the end of the target value. 311 * @return The normalized version of the provided assertion value. 312 * @throws DecodeException 313 * if the syntax of the value is not valid. 314 */ 315 public Assertion getSubstringAssertion(final ByteSequence subInitial, 316 final List<? extends ByteSequence> subAnyElements, final ByteSequence subFinal) 317 throws DecodeException { 318 return impl.getSubstringAssertion(schema, subInitial, subAnyElements, subFinal); 319 } 320 321 /** 322 * Returns the normalized form of the provided assertion value, which is 323 * best suited for efficiently performing greater than or equal ordering 324 * matching operations on that value. The assertion value is guaranteed to 325 * be valid against this matching rule's assertion syntax. 326 * 327 * @param value 328 * The syntax checked assertion value to be normalized. 329 * @return The normalized version of the provided assertion value. 330 * @throws DecodeException 331 * if the syntax of the value is not valid. 332 */ 333 public Assertion getGreaterOrEqualAssertion(final ByteSequence value) throws DecodeException { 334 return impl.getGreaterOrEqualAssertion(schema, value); 335 } 336 337 /** 338 * Returns the normalized form of the provided assertion value, which is 339 * best suited for efficiently performing greater than or equal ordering 340 * matching operations on that value. The assertion value is guaranteed to 341 * be valid against this matching rule's assertion syntax. 342 * 343 * @param value 344 * The syntax checked assertion value to be normalized. 345 * @return The normalized version of the provided assertion value. 346 * @throws DecodeException 347 * if the syntax of the value is not valid. 348 */ 349 public Assertion getLessOrEqualAssertion(final ByteSequence value) throws DecodeException { 350 return impl.getLessOrEqualAssertion(schema, value); 351 } 352 353 /** 354 * Returns the indexers for this matching rule configured using the provided indexing options. 355 * 356 * @param options 357 * The indexing options 358 * @return the collection of indexers for this matching rule. 359 */ 360 public Collection<? extends Indexer> createIndexers(IndexingOptions options) { 361 return impl.createIndexers(options); 362 } 363 364 /** 365 * Returns the name or OID for this schema definition. If it has one or more 366 * names, then the primary name will be returned. If it does not have any 367 * names, then the OID will be returned. 368 * 369 * @return The name or OID for this schema definition. 370 */ 371 public String getNameOrOID() { 372 if (names.isEmpty()) { 373 return oid; 374 } 375 return names.get(0); 376 } 377 378 /** 379 * Returns an unmodifiable list containing the user-defined names that may 380 * be used to reference this schema definition. 381 * 382 * @return Returns an unmodifiable list containing the user-defined names 383 * that may be used to reference this schema definition. 384 */ 385 public List<String> getNames() { 386 return names; 387 } 388 389 /** 390 * Returns the OID for this schema definition. 391 * 392 * @return The OID for this schema definition. 393 */ 394 public String getOID() { 395 return oid; 396 } 397 398 /** 399 * Returns the OID of the assertion value syntax with which this matching 400 * rule is associated. 401 * 402 * @return The OID of the assertion value syntax with which this matching 403 * rule is associated. 404 */ 405 public Syntax getSyntax() { 406 return syntax; 407 } 408 409 /** 410 * Returns the hash code for this matching rule. It will be calculated as 411 * the hash code of the numeric OID. 412 * 413 * @return The hash code for this matching rule. 414 */ 415 @Override 416 public int hashCode() { 417 return oid.hashCode(); 418 } 419 420 /** 421 * Indicates whether this schema definition has the specified name. 422 * 423 * @param name 424 * The name for which to make the determination. 425 * @return <code>true</code> if the specified name is assigned to this 426 * schema definition, or <code>false</code> if not. 427 */ 428 public boolean hasName(final String name) { 429 for (final String n : names) { 430 if (n.equalsIgnoreCase(name)) { 431 return true; 432 } 433 } 434 return false; 435 } 436 437 /** 438 * Indicates whether this schema definition has the specified name or OID. 439 * 440 * @param value 441 * The value for which to make the determination. 442 * @return <code>true</code> if the provided value matches the OID or one of 443 * the names assigned to this schema definition, or 444 * <code>false</code> if not. 445 */ 446 public boolean hasNameOrOID(final String value) { 447 return hasName(value) || getOID().equals(value); 448 } 449 450 /** 451 * Indicates whether this schema definition is declared "obsolete". 452 * 453 * @return <code>true</code> if this schema definition is declared 454 * "obsolete", or <code>false</code> if not. 455 */ 456 public boolean isObsolete() { 457 return isObsolete; 458 } 459 460 /** 461 * Returns the normalized form of the provided attribute value, which is 462 * best suited for efficiently performing matching operations on that value. 463 * The returned normalized representation can be compared for equality with 464 * other values normalized with this matching rule using 465 * {@link ByteSequence#equals(Object)}. In addition, normalized values can 466 * be compared using {@link ByteSequence#compareTo(ByteSequence)}, although 467 * the sort order is only defined for ordering matching rules. 468 * 469 * @param value 470 * The attribute value to be normalized. 471 * @return The normalized version of the provided attribute value. 472 * @throws DecodeException 473 * If the syntax of the value is not valid. 474 */ 475 public ByteString normalizeAttributeValue(final ByteSequence value) throws DecodeException { 476 return impl.normalizeAttributeValue(schema, value); 477 } 478 479 @Override 480 void toStringContent(final StringBuilder buffer) { 481 buffer.append(oid); 482 483 if (!names.isEmpty()) { 484 final Iterator<String> iterator = names.iterator(); 485 486 final String firstName = iterator.next(); 487 if (iterator.hasNext()) { 488 buffer.append(" NAME ( '"); 489 buffer.append(firstName); 490 491 while (iterator.hasNext()) { 492 buffer.append("' '"); 493 buffer.append(iterator.next()); 494 } 495 496 buffer.append("' )"); 497 } else { 498 buffer.append(" NAME '"); 499 buffer.append(firstName); 500 buffer.append("'"); 501 } 502 } 503 504 appendDescription(buffer); 505 506 if (isObsolete) { 507 buffer.append(" OBSOLETE"); 508 } 509 510 buffer.append(" SYNTAX "); 511 buffer.append(syntaxOID); 512 } 513 514 void validate(final Schema schema, final List<LocalizableMessage> warnings) 515 throws SchemaException { 516 // Try finding an implementation in the core schema 517 if (impl == null && Schema.getDefaultSchema().hasMatchingRule(oid)) { 518 impl = Schema.getDefaultSchema().getMatchingRule(oid).impl; 519 } 520 if (impl == null && Schema.getCoreSchema().hasMatchingRule(oid)) { 521 impl = Schema.getCoreSchema().getMatchingRule(oid).impl; 522 } 523 524 if (impl == null) { 525 final MatchingRule defaultMatchingRule = schema.getDefaultMatchingRule(); 526 if (defaultMatchingRule.impl == null) { 527 // The default matching rule was never validated. 528 defaultMatchingRule.validate(schema, warnings); 529 } 530 impl = defaultMatchingRule.impl; 531 final LocalizableMessage message = 532 WARN_MATCHING_RULE_NOT_IMPLEMENTED1.get(getNameOrOID(), schema 533 .getDefaultMatchingRule().getOID()); 534 warnings.add(message); 535 } 536 537 try { 538 // Make sure the specific syntax is defined in this schema. 539 syntax = schema.getSyntax(syntaxOID); 540 } catch (final UnknownSchemaElementException e) { 541 final LocalizableMessage message = 542 ERR_ATTR_SYNTAX_MR_UNKNOWN_SYNTAX1.get(getNameOrOID(), syntaxOID); 543 throw new SchemaException(message, e); 544 } 545 546 this.schema = schema; 547 } 548 549 /** 550 * Indicates if the matching rule has been validated, which means it has a 551 * non-null schema. 552 * 553 * @return {@code true} if and only if this matching rule has been validated 554 */ 555 boolean isValidated() { 556 return schema != null; 557 } 558}