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