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 Sun Microsystems, Inc. 025 * Portions copyright 2015 ForgeRock AS 026 */ 027 028package org.forgerock.opendj.ldap.schema; 029 030import static java.util.Arrays.*; 031 032import static org.forgerock.opendj.ldap.schema.SchemaUtils.*; 033 034import static com.forgerock.opendj.ldap.CoreMessages.*; 035 036import java.util.Collection; 037import java.util.Collections; 038import java.util.HashSet; 039import java.util.Iterator; 040import java.util.LinkedHashSet; 041import java.util.LinkedList; 042import java.util.List; 043import java.util.Map; 044import java.util.Set; 045 046import org.forgerock.i18n.LocalizableMessage; 047import org.forgerock.util.Reject; 048 049/** 050 * This class defines a data structure for storing and interacting with a 051 * matching rule use definition, which may be used to restrict the set of 052 * attribute types that may be used for a given matching rule. 053 */ 054public final class MatchingRuleUse extends SchemaElement { 055 056 /** A fluent API for incrementally constructing matching rule uses. */ 057 public static final class Builder extends SchemaElementBuilder<Builder> { 058 private String oid; 059 private final List<String> names = new LinkedList<>(); 060 private boolean isObsolete; 061 private final Set<String> attributeOIDs = new LinkedHashSet<>(); 062 063 Builder(MatchingRuleUse mru, SchemaBuilder builder) { 064 super(builder, mru); 065 this.oid = mru.oid; 066 this.names.addAll(mru.names); 067 this.isObsolete = mru.isObsolete; 068 this.attributeOIDs.addAll(mru.attributeOIDs); 069 } 070 071 Builder(final String oid, final SchemaBuilder builder) { 072 super(builder); 073 this.oid = oid; 074 } 075 076 /** 077 * Adds this matching rule use definition to the schema, throwing a 078 * {@code ConflictingSchemaElementException} if there is an existing 079 * matching rule definition with the same numeric OID. 080 * 081 * @return The parent schema builder. 082 * @throws ConflictingSchemaElementException 083 * If there is an existing matching rule use definition with 084 * the same numeric OID. 085 */ 086 public SchemaBuilder addToSchema() { 087 return getSchemaBuilder().addMatchingRuleUse(new MatchingRuleUse(this), false); 088 } 089 090 /** 091 * Adds this matching rule use definition to the schema overwriting any 092 * existing matching rule use definition with the same numeric OID. 093 * 094 * @return The parent schema builder. 095 */ 096 public SchemaBuilder addToSchemaOverwrite() { 097 return getSchemaBuilder().addMatchingRuleUse(new MatchingRuleUse(this), true); 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 251 /** 252 * The OID of the matching rule associated with this matching rule 253 * use definition. 254 */ 255 private final String oid; 256 257 /** The set of user defined names for this definition. */ 258 private final List<String> names; 259 260 /** Indicates whether this definition is declared "obsolete". */ 261 private final boolean isObsolete; 262 263 /** 264 * The set of attribute types with which this matching rule use is 265 * associated. 266 */ 267 private final Set<String> attributeOIDs; 268 269 private MatchingRule matchingRule; 270 private Set<AttributeType> attributes = Collections.emptySet(); 271 272 private MatchingRuleUse(final Builder builder) { 273 super(builder); 274 Reject.ifNull(builder.oid); 275 276 this.oid = builder.oid; 277 this.names = unmodifiableCopyOfList(builder.names); 278 this.isObsolete = builder.isObsolete; 279 this.attributeOIDs = unmodifiableCopyOfSet(builder.attributeOIDs); 280 } 281 282 /** 283 * Returns {@code true} if the provided object is a matching rule use having 284 * the same numeric OID as this matching rule use. 285 * 286 * @param o 287 * The object to be compared. 288 * @return {@code true} if the provided object is a matching rule use having 289 * the same numeric OID as this matching rule use. 290 */ 291 @Override 292 public boolean equals(final Object o) { 293 if (this == o) { 294 return true; 295 } else if (o instanceof MatchingRuleUse) { 296 final MatchingRuleUse other = (MatchingRuleUse) o; 297 return oid.equals(other.oid); 298 } else { 299 return false; 300 } 301 } 302 303 /** 304 * Returns an unmodifiable set containing the attributes associated with 305 * this matching rule use. 306 * 307 * @return An unmodifiable set containing the attributes associated with 308 * this matching rule use. 309 */ 310 public Set<AttributeType> getAttributes() { 311 return attributes; 312 } 313 314 /** 315 * Returns the matching rule for this matching rule use. 316 * 317 * @return The matching rule for this matching rule use. 318 */ 319 public MatchingRule getMatchingRule() { 320 return matchingRule; 321 } 322 323 /** 324 * Returns the matching rule OID for this schema definition. 325 * 326 * @return The OID for this schema definition. 327 */ 328 public String getMatchingRuleOID() { 329 return oid; 330 } 331 332 /** 333 * Returns the name or matching rule OID for this schema definition. If it 334 * has one or more names, then the primary name will be returned. If it does 335 * not have any names, then the OID will be returned. 336 * 337 * @return The name or OID for this schema definition. 338 */ 339 public String getNameOrOID() { 340 if (names.isEmpty()) { 341 return oid; 342 } 343 return names.get(0); 344 } 345 346 /** 347 * Returns an unmodifiable list containing the user-defined names that may 348 * be used to reference this schema definition. 349 * 350 * @return Returns an unmodifiable list containing the user-defined names 351 * that may be used to reference this schema definition. 352 */ 353 public List<String> getNames() { 354 return names; 355 } 356 357 /** 358 * Indicates whether the provided attribute type is referenced by this 359 * matching rule use. 360 * 361 * @param attributeType 362 * The attribute type for which to make the determination. 363 * @return {@code true} if the provided attribute type is referenced by this 364 * matching rule use, or {@code false} if it is not. 365 */ 366 public boolean hasAttribute(final AttributeType attributeType) { 367 return attributes.contains(attributeType); 368 } 369 370 /** 371 * Returns the hash code for this matching rule use. It will be calculated 372 * as the hash code of the numeric OID. 373 * 374 * @return The hash code for this matching rule use. 375 */ 376 @Override 377 public int hashCode() { 378 return oid.hashCode(); 379 } 380 381 /** 382 * Indicates whether this schema definition has the specified name. 383 * 384 * @param name 385 * The name for which to make the determination. 386 * @return <code>true</code> if the specified name is assigned to this 387 * schema definition, or <code>false</code> if not. 388 */ 389 public boolean hasName(final String name) { 390 for (final String n : names) { 391 if (n.equalsIgnoreCase(name)) { 392 return true; 393 } 394 } 395 return false; 396 } 397 398 /** 399 * Indicates whether this schema definition has the specified name or 400 * matching rule OID. 401 * 402 * @param value 403 * The value for which to make the determination. 404 * @return <code>true</code> if the provided value matches the OID or one of 405 * the names assigned to this schema definition, or 406 * <code>false</code> if not. 407 */ 408 public boolean hasNameOrOID(final String value) { 409 return hasName(value) || oid.equals(value); 410 } 411 412 /** 413 * Indicates whether this schema definition is declared "obsolete". 414 * 415 * @return <code>true</code> if this schema definition is declared 416 * "obsolete", or <code>false</code> if not. 417 */ 418 public boolean isObsolete() { 419 return isObsolete; 420 } 421 422 @Override 423 void toStringContent(final StringBuilder buffer) { 424 buffer.append(oid); 425 426 if (!names.isEmpty()) { 427 final Iterator<String> iterator = names.iterator(); 428 429 final String firstName = iterator.next(); 430 if (iterator.hasNext()) { 431 buffer.append(" NAME ( '"); 432 buffer.append(firstName); 433 434 while (iterator.hasNext()) { 435 buffer.append("' '"); 436 buffer.append(iterator.next()); 437 } 438 439 buffer.append("' )"); 440 } else { 441 buffer.append(" NAME '"); 442 buffer.append(firstName); 443 buffer.append("'"); 444 } 445 } 446 447 appendDescription(buffer); 448 449 if (isObsolete) { 450 buffer.append(" OBSOLETE"); 451 } 452 453 if (!attributeOIDs.isEmpty()) { 454 final Iterator<String> iterator = attributeOIDs.iterator(); 455 456 final String firstName = iterator.next(); 457 if (iterator.hasNext()) { 458 buffer.append(" APPLIES ( "); 459 buffer.append(firstName); 460 461 while (iterator.hasNext()) { 462 buffer.append(" $ "); 463 buffer.append(iterator.next()); 464 } 465 466 buffer.append(" )"); 467 } else { 468 buffer.append(" APPLIES "); 469 buffer.append(firstName); 470 } 471 } 472 } 473 474 void validate(final Schema schema, final List<LocalizableMessage> warnings) 475 throws SchemaException { 476 try { 477 matchingRule = schema.getMatchingRule(oid); 478 } catch (final UnknownSchemaElementException e) { 479 // This is bad because the matching rule use is associated with a 480 // matching rule that we don't know anything about. 481 final LocalizableMessage message = 482 ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_MATCHING_RULE1.get(getNameOrOID(), oid); 483 throw new SchemaException(message, e); 484 } 485 486 attributes = new HashSet<>(attributeOIDs.size()); 487 for (final String attribute : attributeOIDs) { 488 try { 489 attributes.add(schema.getAttributeType(attribute)); 490 } catch (final UnknownSchemaElementException e) { 491 final LocalizableMessage message = 492 ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_ATTR1.get(getNameOrOID(), attribute); 493 throw new SchemaException(message, e); 494 } 495 } 496 attributes = Collections.unmodifiableSet(attributes); 497 } 498}