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 DIT structure rule, which is used to indicate the types 040 * of children that entries may have. 041 */ 042public final class DITStructureRule extends AbstractSchemaElement { 043 044 /** A fluent API for incrementally constructing DIT structure rules. */ 045 public static final class Builder extends SchemaElementBuilder<Builder> { 046 private int ruleID; 047 private final List<String> names = new LinkedList<>(); 048 private boolean isObsolete; 049 private String nameFormOID; 050 private final Set<Integer> superiorRuleIDs = new LinkedHashSet<>(); 051 052 Builder(final DITStructureRule structureRule, final SchemaBuilder builder) { 053 super(builder, structureRule); 054 this.ruleID = structureRule.ruleID; 055 this.names.addAll(structureRule.names); 056 this.isObsolete = structureRule.isObsolete; 057 this.nameFormOID = structureRule.nameFormOID; 058 this.superiorRuleIDs.addAll(structureRule.superiorRuleIDs); 059 } 060 061 Builder(final Integer ruleID, final SchemaBuilder schemaBuilder) { 062 super(schemaBuilder); 063 this.ruleID = ruleID; 064 } 065 066 /** 067 * Adds this DIT structure rule to the schema, throwing a 068 * {@code ConflictingSchemaElementException} if there is an existing DIT 069 * structure rule with the same numeric ID. 070 * 071 * @return The parent schema builder. 072 * @throws ConflictingSchemaElementException 073 * If there is an existing structure rule with the same 074 * numeric ID. 075 */ 076 public SchemaBuilder addToSchema() { 077 return getSchemaBuilder().addDITStructureRule(new DITStructureRule(this), false); 078 } 079 080 /** 081 * Adds this DIT structure rule to the schema overwriting any existing 082 * DIT structure rule with the same numeric ID. 083 * 084 * @return The parent schema builder. 085 */ 086 public SchemaBuilder addToSchemaOverwrite() { 087 return getSchemaBuilder().addDITStructureRule(new DITStructureRule(this), true); 088 } 089 090 /** 091 * Adds this DIT structure rule to the schema, overwriting any existing DIT structure rule 092 * with the same numeric OID if the overwrite parameter is set to {@code true}. 093 * 094 * @param overwrite 095 * {@code true} if any DIT structure rule with the same OID should be overwritten. 096 * @return The parent schema builder. 097 */ 098 SchemaBuilder addToSchema(final boolean overwrite) { 099 return overwrite ? addToSchemaOverwrite() : addToSchema(); 100 } 101 102 @Override 103 public Builder description(final String description) { 104 return description0(description); 105 } 106 107 @Override 108 public Builder extraProperties(final Map<String, List<String>> extraProperties) { 109 return extraProperties0(extraProperties); 110 } 111 112 @Override 113 public Builder extraProperties(final String extensionName, final String... extensionValues) { 114 return extraProperties0(extensionName, extensionValues); 115 } 116 117 @Override 118 Builder getThis() { 119 return this; 120 } 121 122 /** 123 * Sets the name form associated with the DIT structure rule. 124 * 125 * @param nameFormOID 126 * The name form numeric OID. 127 * @return This builder. 128 */ 129 public Builder nameForm(final String nameFormOID) { 130 this.nameFormOID = nameFormOID; 131 return this; 132 } 133 134 /** 135 * Adds the provided user friendly names. 136 * 137 * @param names 138 * The user friendly names. 139 * @return This builder. 140 */ 141 public Builder names(final Collection<String> names) { 142 this.names.addAll(names); 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 String... names) { 154 return names(asList(names)); 155 } 156 157 /** 158 * Specifies whether this schema element is obsolete. 159 * 160 * @param isObsolete 161 * {@code true} if this schema element is obsolete 162 * (default is {@code false}). 163 * @return This builder. 164 */ 165 public Builder obsolete(final boolean isObsolete) { 166 this.isObsolete = isObsolete; 167 return this; 168 } 169 170 @Override 171 public Builder removeAllExtraProperties() { 172 return removeAllExtraProperties0(); 173 } 174 175 /** 176 * Removes all user defined names. 177 * 178 * @return This builder. 179 */ 180 public Builder removeAllNames() { 181 this.names.clear(); 182 return this; 183 } 184 185 /** 186 * Removes all superior rules. 187 * 188 * @return This builder. 189 */ 190 public Builder removeAllSuperiorRules() { 191 this.superiorRuleIDs.clear(); 192 return this; 193 } 194 195 @Override 196 public Builder removeExtraProperty(final String extensionName, final String... extensionValues) { 197 return removeExtraProperty0(extensionName, extensionValues); 198 } 199 200 /** 201 * Removes the provided user defined name. 202 * 203 * @param name 204 * The user defined name to be removed. 205 * @return This builder. 206 */ 207 public Builder removeName(final String name) { 208 this.names.remove(name); 209 return this; 210 } 211 212 /** 213 * Removes the provided superior rule. 214 * 215 * @param superiorRuleID 216 * The superior rule ID to be removed. 217 * @return This builder. 218 */ 219 public Builder removeSuperiorRule(final int superiorRuleID) { 220 this.superiorRuleIDs.remove(superiorRuleID); 221 return this; 222 } 223 224 /** 225 * Sets the the numeric ID which uniquely identifies this structure rule. 226 * 227 * @param ruleID 228 * The numeric ID. 229 * @return This builder. 230 */ 231 public Builder ruleID(final int ruleID) { 232 this.ruleID = ruleID; 233 return this; 234 } 235 236 /** 237 * Adds the provided superior rule identifiers. 238 * 239 * @param superiorRuleIDs 240 * Structure rule identifiers. 241 * @return This builder. 242 */ 243 public Builder superiorRules(final int... superiorRuleIDs) { 244 for (int ruleID : superiorRuleIDs) { 245 this.superiorRuleIDs.add(ruleID); 246 } 247 return this; 248 } 249 250 Builder superiorRules(final Collection<Integer> superiorRuleIDs) { 251 this.superiorRuleIDs.addAll(superiorRuleIDs); 252 return this; 253 } 254 } 255 256 /** The rule ID for this DIT structure rule. */ 257 private final Integer ruleID; 258 259 /** The set of user defined names for this definition. */ 260 private final List<String> names; 261 262 /** Indicates whether this definition is declared "obsolete". */ 263 private final boolean isObsolete; 264 265 /** The name form for this DIT structure rule. */ 266 private final String nameFormOID; 267 268 /** The set of superior DIT structure rules. */ 269 private final Set<Integer> superiorRuleIDs; 270 271 private NameForm nameForm; 272 private Set<DITStructureRule> superiorRules = Collections.emptySet(); 273 274 /** Indicates whether validation has been performed. */ 275 private boolean needsValidating = true; 276 277 /** The indicates whether validation failed. */ 278 private boolean isValid; 279 280 DITStructureRule(final Builder builder) { 281 super(builder); 282 Reject.ifNull(builder.nameFormOID); 283 284 this.ruleID = builder.ruleID; 285 this.names = unmodifiableCopyOfList(builder.names); 286 this.isObsolete = builder.isObsolete; 287 this.nameFormOID = builder.nameFormOID; 288 this.superiorRuleIDs = unmodifiableCopyOfSet(builder.superiorRuleIDs); 289 } 290 291 /** 292 * Returns {@code true} if the provided object is a DIT structure rule 293 * having the same rule ID as this DIT structure rule. 294 * 295 * @param o 296 * The object to be compared. 297 * @return {@code true} if the provided object is a DIT structure rule 298 * having the same rule ID as this DIT structure rule. 299 */ 300 @Override 301 public boolean equals(final Object o) { 302 if (this == o) { 303 return true; 304 } else if (o instanceof DITStructureRule) { 305 final DITStructureRule other = (DITStructureRule) o; 306 return ruleID.equals(other.ruleID); 307 } else { 308 return false; 309 } 310 } 311 312 /** 313 * Retrieves the name form for this DIT structure rule. 314 * 315 * @return The name form for this DIT structure rule. 316 */ 317 public NameForm getNameForm() { 318 return nameForm; 319 } 320 321 /** 322 * Retrieves the name or rule ID for this schema definition. If it has one 323 * or more names, then the primary name will be returned. If it does not 324 * have any names, then the OID will be returned. 325 * 326 * @return The name or OID for this schema definition. 327 */ 328 public String getNameOrRuleID() { 329 if (names.isEmpty()) { 330 return ruleID.toString(); 331 } 332 return names.get(0); 333 } 334 335 /** 336 * Returns an unmodifiable list containing the user-defined names that may 337 * be used to reference this schema definition. 338 * 339 * @return Returns an unmodifiable list containing the user-defined names 340 * that may be used to reference this schema definition. 341 */ 342 public List<String> getNames() { 343 return names; 344 } 345 346 /** 347 * Retrieves the rule ID for this DIT structure rule. 348 * 349 * @return The rule ID for this DIT structure rule (never {@code null}). 350 */ 351 public Integer getRuleID() { 352 return ruleID; 353 } 354 355 /** 356 * Returns an unmodifiable set containing the superior rules for this DIT 357 * structure rule. 358 * 359 * @return An unmodifiable set containing the superior rules for this DIT 360 * structure rule. 361 */ 362 public Set<DITStructureRule> getSuperiorRules() { 363 return superiorRules; 364 } 365 366 /** 367 * Returns the hash code for this DIT structure rule. It will be calculated 368 * as the hash code of the rule ID. 369 * 370 * @return The hash code for this DIT structure rule. 371 */ 372 @Override 373 public int hashCode() { 374 return ruleID.hashCode(); 375 } 376 377 /** 378 * Indicates whether this schema definition has the specified name. 379 * 380 * @param name 381 * The name for which to make the determination. 382 * @return <code>true</code> if the specified name is assigned to this 383 * schema definition, or <code>false</code> if not. 384 */ 385 public boolean hasName(final String name) { 386 for (final String n : names) { 387 if (n.equalsIgnoreCase(name)) { 388 return true; 389 } 390 } 391 return false; 392 } 393 394 /** 395 * Indicates whether this schema definition is declared "obsolete". 396 * 397 * @return <code>true</code> if this schema definition is declared 398 * "obsolete", or <code>false</code> if not. 399 */ 400 public boolean isObsolete() { 401 return isObsolete; 402 } 403 404 @Override 405 void toStringContent(final StringBuilder buffer) { 406 buffer.append(ruleID); 407 408 if (!names.isEmpty()) { 409 final Iterator<String> iterator = names.iterator(); 410 411 final String firstName = iterator.next(); 412 if (iterator.hasNext()) { 413 buffer.append(" NAME ( '"); 414 buffer.append(firstName); 415 416 while (iterator.hasNext()) { 417 buffer.append("' '"); 418 buffer.append(iterator.next()); 419 } 420 421 buffer.append("' )"); 422 } else { 423 buffer.append(" NAME '"); 424 buffer.append(firstName); 425 buffer.append("'"); 426 } 427 } 428 429 appendDescription(buffer); 430 431 if (isObsolete) { 432 buffer.append(" OBSOLETE"); 433 } 434 435 buffer.append(" FORM "); 436 buffer.append(nameFormOID); 437 438 if (superiorRuleIDs != null && !superiorRuleIDs.isEmpty()) { 439 final Iterator<Integer> iterator = superiorRuleIDs.iterator(); 440 441 final Integer firstRule = iterator.next(); 442 if (iterator.hasNext()) { 443 buffer.append(" SUP ( "); 444 buffer.append(firstRule); 445 446 while (iterator.hasNext()) { 447 buffer.append(" "); 448 buffer.append(iterator.next()); 449 } 450 451 buffer.append(" )"); 452 } else { 453 buffer.append(" SUP "); 454 buffer.append(firstRule); 455 } 456 } 457 } 458 459 boolean validate(final Schema schema, final List<DITStructureRule> invalidSchemaElements, 460 final List<LocalizableMessage> warnings) { 461 // Avoid validating this schema element more than once. 462 // This may occur if multiple rules specify the same superior. 463 if (!needsValidating) { 464 return isValid; 465 } 466 467 // Prevent re-validation. 468 needsValidating = false; 469 470 try { 471 nameForm = schema.getNameForm(nameFormOID); 472 } catch (final UnknownSchemaElementException e) { 473 final LocalizableMessage message = 474 ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM.get(getNameOrRuleID(), nameFormOID); 475 failValidation(invalidSchemaElements, warnings, message); 476 return false; 477 } 478 479 if (!superiorRuleIDs.isEmpty()) { 480 superiorRules = new HashSet<>(superiorRuleIDs.size()); 481 for (final Integer id : superiorRuleIDs) { 482 try { 483 superiorRules.add(schema.getDITStructureRule(id)); 484 } catch (final UnknownSchemaElementException e) { 485 final LocalizableMessage message = 486 ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(getNameOrRuleID(), id); 487 failValidation(invalidSchemaElements, warnings, message); 488 return false; 489 } 490 } 491 } 492 superiorRules = Collections.unmodifiableSet(superiorRules); 493 494 return isValid = true; 495 } 496 497 private void failValidation(final List<DITStructureRule> invalidSchemaElements, 498 final List<LocalizableMessage> warnings, final LocalizableMessage message) { 499 invalidSchemaElements.add(this); 500 warnings.add(ERR_DSR_VALIDATION_FAIL.get(toString(), message)); 501 } 502}