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 DIT structure rule, which is used to indicate the types 051 * of children that entries may have. 052 */ 053public final class DITStructureRule extends SchemaElement { 054 055 /** A fluent API for incrementally constructing DIT structure rules. */ 056 public static final class Builder extends SchemaElementBuilder<Builder> { 057 private int ruleID; 058 private final List<String> names = new LinkedList<>(); 059 private boolean isObsolete; 060 private String nameFormOID; 061 private final Set<Integer> superiorRuleIDs = new LinkedHashSet<>(); 062 063 Builder(final DITStructureRule structureRule, final SchemaBuilder builder) { 064 super(builder); 065 this.ruleID = structureRule.ruleID; 066 this.names.addAll(structureRule.names); 067 this.isObsolete = structureRule.isObsolete; 068 this.nameFormOID = structureRule.nameFormOID; 069 this.superiorRuleIDs.addAll(structureRule.superiorRuleIDs); 070 } 071 072 Builder(final Integer ruleID, final SchemaBuilder schemaBuilder) { 073 super(schemaBuilder); 074 this.ruleID = ruleID; 075 } 076 077 /** 078 * Adds this DIT structure rule to the schema, throwing a 079 * {@code ConflictingSchemaElementException} if there is an existing DIT 080 * structure rule with the same numeric ID. 081 * 082 * @return The parent schema builder. 083 * @throws ConflictingSchemaElementException 084 * If there is an existing structure rule with the same 085 * numeric ID. 086 */ 087 public SchemaBuilder addToSchema() { 088 return getSchemaBuilder().addDITStructureRule(new DITStructureRule(this), false); 089 } 090 091 /** 092 * Adds this DIT structure rule to the schema overwriting any existing 093 * DIT structure rule with the same numeric ID. 094 * 095 * @return The parent schema builder. 096 */ 097 public SchemaBuilder addToSchemaOverwrite() { 098 return getSchemaBuilder().addDITStructureRule(new DITStructureRule(this), true); 099 } 100 101 @Override 102 public Builder description(final String description) { 103 return description0(description); 104 } 105 106 @Override 107 public Builder extraProperties(final Map<String, List<String>> extraProperties) { 108 return extraProperties0(extraProperties); 109 } 110 111 @Override 112 public Builder extraProperties(final String extensionName, final String... extensionValues) { 113 return extraProperties0(extensionName, extensionValues); 114 } 115 116 @Override 117 Builder getThis() { 118 return this; 119 } 120 121 /** 122 * Sets the name form associated with the DIT structure rule. 123 * 124 * @param nameFormOID 125 * The name form numeric OID. 126 * @return This builder. 127 */ 128 public Builder nameForm(final String nameFormOID) { 129 this.nameFormOID = nameFormOID; 130 return this; 131 } 132 133 /** 134 * Adds the provided user friendly names. 135 * 136 * @param names 137 * The user friendly names. 138 * @return This builder. 139 */ 140 public Builder names(final Collection<String> names) { 141 this.names.addAll(names); 142 return this; 143 } 144 145 /** 146 * Adds the provided user friendly names. 147 * 148 * @param names 149 * The user friendly names. 150 * @return This builder. 151 */ 152 public Builder names(final String... names) { 153 return names(asList(names)); 154 } 155 156 /** 157 * Specifies whether this schema element is obsolete. 158 * 159 * @param isObsolete 160 * {@code true} if this schema element is obsolete 161 * (default is {@code false}). 162 * @return This builder. 163 */ 164 public Builder obsolete(final boolean isObsolete) { 165 this.isObsolete = isObsolete; 166 return this; 167 } 168 169 @Override 170 public Builder removeAllExtraProperties() { 171 return removeAllExtraProperties0(); 172 } 173 174 /** 175 * Removes all user defined names. 176 * 177 * @return This builder. 178 */ 179 public Builder removeAllNames() { 180 this.names.clear(); 181 return this; 182 } 183 184 /** 185 * Removes all superior rules. 186 * 187 * @return This builder. 188 */ 189 public Builder removeAllSuperiorRules() { 190 this.superiorRuleIDs.clear(); 191 return this; 192 } 193 194 @Override 195 public Builder removeExtraProperty(final String extensionName, final String... extensionValues) { 196 return removeExtraProperty0(extensionName, extensionValues); 197 } 198 199 /** 200 * Removes the provided user defined name. 201 * 202 * @param name 203 * The user defined name to be removed. 204 * @return This builder. 205 */ 206 public Builder removeName(final String name) { 207 this.names.remove(name); 208 return this; 209 } 210 211 /** 212 * Removes the provided superior rule. 213 * 214 * @param superiorRuleID 215 * The superior rule ID to be removed. 216 * @return This builder. 217 */ 218 public Builder removeSuperiorRule(final int superiorRuleID) { 219 this.superiorRuleIDs.remove(superiorRuleID); 220 return this; 221 } 222 223 /** 224 * Sets the the numeric ID which uniquely identifies this structure rule. 225 * 226 * @param ruleID 227 * The numeric ID. 228 * @return This builder. 229 */ 230 public Builder ruleID(final int ruleID) { 231 this.ruleID = ruleID; 232 return this; 233 } 234 235 /** 236 * Adds the provided superior rule identifiers. 237 * 238 * @param superiorRuleIDs 239 * Structure rule identifiers. 240 * @return This builder. 241 */ 242 public Builder superiorRules(final int... superiorRuleIDs) { 243 for (int ruleID : superiorRuleIDs) { 244 this.superiorRuleIDs.add(ruleID); 245 } 246 return this; 247 } 248 249 Builder superiorRules(final Collection<Integer> superiorRuleIDs) { 250 this.superiorRuleIDs.addAll(superiorRuleIDs); 251 return this; 252 } 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 or not validation has been performed. */ 275 private boolean needsValidating = true; 276 277 /** The indicates whether or not 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. 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. This may occur 462 // if 463 // multiple rules specify the same superior. 464 if (!needsValidating) { 465 return isValid; 466 } 467 468 // Prevent re-validation. 469 needsValidating = false; 470 471 try { 472 nameForm = schema.getNameForm(nameFormOID); 473 } catch (final UnknownSchemaElementException e) { 474 final LocalizableMessage message = 475 ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM.get(getNameOrRuleID(), nameFormOID); 476 failValidation(invalidSchemaElements, warnings, message); 477 return false; 478 } 479 480 if (!superiorRuleIDs.isEmpty()) { 481 superiorRules = new HashSet<>(superiorRuleIDs.size()); 482 for (final Integer id : superiorRuleIDs) { 483 try { 484 superiorRules.add(schema.getDITStructureRule(id)); 485 } catch (final UnknownSchemaElementException e) { 486 final LocalizableMessage message = 487 ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(getNameOrRuleID(), id); 488 failValidation(invalidSchemaElements, warnings, message); 489 return false; 490 } 491 } 492 } 493 superiorRules = Collections.unmodifiableSet(superiorRules); 494 495 return isValid = true; 496 } 497 498 private void failValidation(final List<DITStructureRule> invalidSchemaElements, 499 final List<LocalizableMessage> warnings, final LocalizableMessage message) { 500 invalidSchemaElements.add(this); 501 warnings.add(ERR_DSR_VALIDATION_FAIL.get(toString(), message)); 502 } 503}