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 Sun Microsystems, Inc. 015 * Portions copyright 2015-2016 ForgeRock AS. 016 */ 017package org.forgerock.opendj.ldap.schema; 018 019import static java.util.Arrays.*; 020 021import static org.forgerock.opendj.ldap.schema.SchemaUtils.*; 022 023import static com.forgerock.opendj.ldap.CoreMessages.*; 024 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.Iterator; 029import java.util.LinkedHashSet; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034 035import org.forgerock.i18n.LocalizableMessage; 036import org.forgerock.util.Reject; 037 038/** 039 * This class defines a data structure for storing and interacting with a 040 * matching rule use definition, which may be used to restrict the set of 041 * attribute types that may be used for a given matching rule. 042 */ 043public final class MatchingRuleUse extends AbstractSchemaElement { 044 /** A fluent API for incrementally constructing matching rule uses. */ 045 public static final class Builder extends SchemaElementBuilder<Builder> { 046 private String oid; 047 private final List<String> names = new LinkedList<>(); 048 private boolean isObsolete; 049 private final Set<String> attributeOIDs = new LinkedHashSet<>(); 050 051 Builder(MatchingRuleUse mru, SchemaBuilder builder) { 052 super(builder, mru); 053 this.oid = mru.oid; 054 this.names.addAll(mru.names); 055 this.isObsolete = mru.isObsolete; 056 this.attributeOIDs.addAll(mru.attributeOIDs); 057 } 058 059 Builder(final String oid, final SchemaBuilder builder) { 060 super(builder); 061 this.oid = oid; 062 } 063 064 /** 065 * Adds this matching rule use definition to the schema, throwing a 066 * {@code ConflictingSchemaElementException} if there is an existing 067 * matching rule definition with the same numeric OID. 068 * 069 * @return The parent schema builder. 070 * @throws ConflictingSchemaElementException 071 * If there is an existing matching rule use definition with 072 * the same numeric OID. 073 */ 074 public SchemaBuilder addToSchema() { 075 return getSchemaBuilder().addMatchingRuleUse(new MatchingRuleUse(this), false); 076 } 077 078 /** 079 * Adds this matching rule use definition to the schema overwriting any 080 * existing matching rule use definition with the same numeric OID. 081 * 082 * @return The parent schema builder. 083 */ 084 public SchemaBuilder addToSchemaOverwrite() { 085 return getSchemaBuilder().addMatchingRuleUse(new MatchingRuleUse(this), true); 086 } 087 088 /** 089 * Adds this matching rule use to the schema, overwriting any existing matching rule use 090 * with the same numeric OID if the overwrite parameter is set to {@code true}. 091 * 092 * @param overwrite 093 * {@code true} if any matching rule use with the same OID should be overwritten. 094 * @return The parent schema builder. 095 */ 096 SchemaBuilder addToSchema(final boolean overwrite) { 097 return overwrite ? addToSchemaOverwrite() : addToSchema(); 098 } 099 100 /** 101 * Adds the provided list of attribute types to the list of attribute 102 * type the matching rule applies to. 103 * 104 * @param attributeOIDs 105 * The list of attribute type numeric OIDs. 106 * @return This builder. 107 */ 108 public Builder attributes(Collection<String> attributeOIDs) { 109 this.attributeOIDs.addAll(attributeOIDs); 110 return this; 111 } 112 113 /** 114 * Adds the provided list of attribute types to the list of attribute 115 * type the matching rule applies to. 116 * 117 * @param attributeOIDs 118 * The list of attribute type numeric OIDs. 119 * @return This builder. 120 */ 121 public Builder attributes(String... attributeOIDs) { 122 this.attributeOIDs.addAll(asList(attributeOIDs)); 123 return this; 124 } 125 126 @Override 127 public Builder description(final String description) { 128 return description0(description); 129 } 130 131 @Override 132 public Builder extraProperties(final Map<String, List<String>> extraProperties) { 133 return extraProperties0(extraProperties); 134 } 135 136 @Override 137 public Builder extraProperties(final String extensionName, final String... extensionValues) { 138 return extraProperties0(extensionName, extensionValues); 139 } 140 141 @Override 142 Builder getThis() { 143 return this; 144 } 145 146 /** 147 * Adds the provided user friendly names. 148 * 149 * @param names 150 * The user friendly names. 151 * @return This builder. 152 */ 153 public Builder names(final Collection<String> names) { 154 this.names.addAll(names); 155 return this; 156 } 157 158 /** 159 * Adds the provided user friendly names. 160 * 161 * @param names 162 * The user friendly names. 163 * @return This builder. 164 */ 165 public Builder names(final String... names) { 166 return names(asList(names)); 167 } 168 169 /** 170 * Specifies whether this schema element is obsolete. 171 * 172 * @param isObsolete 173 * {@code true} if this schema element is obsolete 174 * (default is {@code false}). 175 * @return This builder. 176 */ 177 public Builder obsolete(final boolean isObsolete) { 178 this.isObsolete = isObsolete; 179 return this; 180 } 181 182 /** 183 * Sets the numeric OID which uniquely identifies this matching rule use 184 * definition. 185 * 186 * @param oid 187 * The numeric OID. 188 * @return This builder. 189 */ 190 public Builder oid(final String oid) { 191 this.oid = oid; 192 return this; 193 } 194 195 /** 196 * Removes all attribute types the matching rule applies to. 197 * 198 * @return This builder. 199 */ 200 public Builder removeAllAttributes() { 201 this.attributeOIDs.clear(); 202 return this; 203 } 204 205 @Override 206 public Builder removeAllExtraProperties() { 207 return removeAllExtraProperties0(); 208 } 209 210 /** 211 * Removes all user defined names. 212 * 213 * @return This builder. 214 */ 215 public Builder removeAllNames() { 216 this.names.clear(); 217 return this; 218 } 219 220 /** 221 * Removes the provided attribute type. 222 * 223 * @param attributeOID 224 * The attribute type OID to be removed. 225 * @return This builder. 226 */ 227 public Builder removeAttribute(String attributeOID) { 228 this.attributeOIDs.remove(attributeOID); 229 return this; 230 } 231 232 @Override 233 public Builder removeExtraProperty(String extensionName, String... extensionValues) { 234 return removeExtraProperty0(extensionName, extensionValues); 235 } 236 237 /** 238 * Removes the provided user defined name. 239 * 240 * @param name 241 * The user defined name to be removed. 242 * @return This builder. 243 */ 244 public Builder removeName(String name) { 245 this.names.remove(name); 246 return this; 247 } 248 } 249 250 /** The OID of the matching rule associated with this matching rule use definition. */ 251 private final String oid; 252 253 /** The set of user defined names for this definition. */ 254 private final List<String> names; 255 256 /** Indicates whether this definition is declared "obsolete". */ 257 private final boolean isObsolete; 258 259 /** The set of attribute types with which this matching rule use is associated. */ 260 private final Set<String> attributeOIDs; 261 262 private MatchingRule matchingRule; 263 private Set<AttributeType> attributes = Collections.emptySet(); 264 265 private MatchingRuleUse(final Builder builder) { 266 super(builder); 267 Reject.ifNull(builder.oid); 268 269 this.oid = builder.oid; 270 this.names = unmodifiableCopyOfList(builder.names); 271 this.isObsolete = builder.isObsolete; 272 this.attributeOIDs = unmodifiableCopyOfSet(builder.attributeOIDs); 273 } 274 275 /** 276 * Returns {@code true} if the provided object is a matching rule use having 277 * the same numeric OID as this matching rule use. 278 * 279 * @param o 280 * The object to be compared. 281 * @return {@code true} if the provided object is a matching rule use having 282 * the same numeric OID as this matching rule use. 283 */ 284 @Override 285 public boolean equals(final Object o) { 286 if (this == o) { 287 return true; 288 } else if (o instanceof MatchingRuleUse) { 289 final MatchingRuleUse other = (MatchingRuleUse) o; 290 return oid.equals(other.oid); 291 } else { 292 return false; 293 } 294 } 295 296 /** 297 * Returns an unmodifiable set containing the attributes associated with 298 * this matching rule use. 299 * 300 * @return An unmodifiable set containing the attributes associated with 301 * this matching rule use. 302 */ 303 public Set<AttributeType> getAttributes() { 304 return attributes; 305 } 306 307 /** 308 * Returns the matching rule for this matching rule use. 309 * 310 * @return The matching rule for this matching rule use. 311 */ 312 public MatchingRule getMatchingRule() { 313 return matchingRule; 314 } 315 316 /** 317 * Returns the matching rule OID for this schema definition. 318 * 319 * @return The OID for this schema definition. 320 */ 321 public String getMatchingRuleOID() { 322 return oid; 323 } 324 325 /** 326 * Returns the name or matching rule OID for this schema definition. If it 327 * has one or more names, then the primary name will be returned. If it does 328 * not have any names, then the OID will be returned. 329 * 330 * @return The name or OID for this schema definition. 331 */ 332 public String getNameOrOID() { 333 if (names.isEmpty()) { 334 return oid; 335 } 336 return names.get(0); 337 } 338 339 /** 340 * Returns an unmodifiable list containing the user-defined names that may 341 * be used to reference this schema definition. 342 * 343 * @return Returns an unmodifiable list containing the user-defined names 344 * that may be used to reference this schema definition. 345 */ 346 public List<String> getNames() { 347 return names; 348 } 349 350 /** 351 * Indicates whether the provided attribute type is referenced by this 352 * matching rule use. 353 * 354 * @param attributeType 355 * The attribute type for which to make the determination. 356 * @return {@code true} if the provided attribute type is referenced by this 357 * matching rule use, or {@code false} if it is not. 358 */ 359 public boolean hasAttribute(final AttributeType attributeType) { 360 return attributes.contains(attributeType); 361 } 362 363 /** 364 * Returns the hash code for this matching rule use. It will be calculated 365 * as the hash code of the numeric OID. 366 * 367 * @return The hash code for this matching rule use. 368 */ 369 @Override 370 public int hashCode() { 371 return oid.hashCode(); 372 } 373 374 /** 375 * Indicates whether this schema definition has the specified name. 376 * 377 * @param name 378 * The name for which to make the determination. 379 * @return <code>true</code> if the specified name is assigned to this 380 * schema definition, or <code>false</code> if not. 381 */ 382 public boolean hasName(final String name) { 383 for (final String n : names) { 384 if (n.equalsIgnoreCase(name)) { 385 return true; 386 } 387 } 388 return false; 389 } 390 391 /** 392 * Indicates whether this schema definition has the specified name or 393 * matching rule OID. 394 * 395 * @param value 396 * The value for which to make the determination. 397 * @return <code>true</code> if the provided value matches the OID or one of 398 * the names assigned to this schema definition, or 399 * <code>false</code> if not. 400 */ 401 public boolean hasNameOrOID(final String value) { 402 return hasName(value) || oid.equals(value); 403 } 404 405 /** 406 * Indicates whether this schema definition is declared "obsolete". 407 * 408 * @return <code>true</code> if this schema definition is declared 409 * "obsolete", or <code>false</code> if not. 410 */ 411 public boolean isObsolete() { 412 return isObsolete; 413 } 414 415 @Override 416 void toStringContent(final StringBuilder buffer) { 417 buffer.append(oid); 418 419 if (!names.isEmpty()) { 420 final Iterator<String> iterator = names.iterator(); 421 422 final String firstName = iterator.next(); 423 if (iterator.hasNext()) { 424 buffer.append(" NAME ( '"); 425 buffer.append(firstName); 426 427 while (iterator.hasNext()) { 428 buffer.append("' '"); 429 buffer.append(iterator.next()); 430 } 431 432 buffer.append("' )"); 433 } else { 434 buffer.append(" NAME '"); 435 buffer.append(firstName); 436 buffer.append("'"); 437 } 438 } 439 440 appendDescription(buffer); 441 442 if (isObsolete) { 443 buffer.append(" OBSOLETE"); 444 } 445 446 if (!attributeOIDs.isEmpty()) { 447 final Iterator<String> iterator = attributeOIDs.iterator(); 448 449 final String firstName = iterator.next(); 450 if (iterator.hasNext()) { 451 buffer.append(" APPLIES ( "); 452 buffer.append(firstName); 453 454 while (iterator.hasNext()) { 455 buffer.append(" $ "); 456 buffer.append(iterator.next()); 457 } 458 459 buffer.append(" )"); 460 } else { 461 buffer.append(" APPLIES "); 462 buffer.append(firstName); 463 } 464 } 465 } 466 467 void validate(final Schema schema) throws SchemaException { 468 try { 469 matchingRule = schema.getMatchingRule(oid); 470 } catch (final UnknownSchemaElementException e) { 471 // This is bad because the matching rule use is associated with a 472 // matching rule that we don't know anything about. 473 final LocalizableMessage message = 474 ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_MATCHING_RULE1.get(getNameOrOID(), oid); 475 throw new SchemaException(message, e); 476 } 477 478 attributes = new HashSet<>(attributeOIDs.size()); 479 for (final String attribute : attributeOIDs) { 480 try { 481 attributes.add(schema.getAttributeType(attribute)); 482 } catch (final UnknownSchemaElementException e) { 483 final LocalizableMessage message = 484 ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_ATTR1.get(getNameOrOID(), attribute); 485 throw new SchemaException(message, e); 486 } 487 } 488 attributes = Collections.unmodifiableSet(attributes); 489 } 490}