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 2014 Manuel Gaupp 026 * Portions Copyright 2011-2015 ForgeRock AS 027 */ 028package org.forgerock.opendj.ldap.schema; 029 030import static java.util.Collections.*; 031 032import static org.forgerock.opendj.ldap.LdapException.*; 033import static org.forgerock.opendj.ldap.schema.ObjectClass.*; 034import static org.forgerock.opendj.ldap.schema.ObjectClassType.*; 035import static org.forgerock.opendj.ldap.schema.Schema.*; 036import static org.forgerock.opendj.ldap.schema.SchemaConstants.*; 037import static org.forgerock.opendj.ldap.schema.SchemaOptions.*; 038import static org.forgerock.opendj.ldap.schema.SchemaUtils.*; 039 040import static com.forgerock.opendj.ldap.CoreMessages.*; 041import static com.forgerock.opendj.util.StaticUtils.*; 042 043import java.util.ArrayList; 044import java.util.Arrays; 045import java.util.Collection; 046import java.util.Collections; 047import java.util.HashMap; 048import java.util.LinkedHashMap; 049import java.util.LinkedList; 050import java.util.List; 051import java.util.Map; 052import java.util.Set; 053import java.util.concurrent.atomic.AtomicInteger; 054import java.util.regex.Pattern; 055 056import org.forgerock.i18n.LocalizableMessage; 057import org.forgerock.i18n.LocalizedIllegalArgumentException; 058import org.forgerock.opendj.ldap.Attribute; 059import org.forgerock.opendj.ldap.ByteString; 060import org.forgerock.opendj.ldap.Connection; 061import org.forgerock.opendj.ldap.DN; 062import org.forgerock.opendj.ldap.DecodeException; 063import org.forgerock.opendj.ldap.Entry; 064import org.forgerock.opendj.ldap.EntryNotFoundException; 065import org.forgerock.opendj.ldap.Filter; 066import org.forgerock.opendj.ldap.LdapException; 067import org.forgerock.opendj.ldap.LdapPromise; 068import org.forgerock.opendj.ldap.Option; 069import org.forgerock.opendj.ldap.ResultCode; 070import org.forgerock.opendj.ldap.SearchScope; 071import org.forgerock.opendj.ldap.requests.Requests; 072import org.forgerock.opendj.ldap.requests.SearchRequest; 073import org.forgerock.opendj.ldap.responses.SearchResultEntry; 074import org.forgerock.opendj.ldap.schema.DITContentRule.Builder; 075import org.forgerock.util.Reject; 076import org.forgerock.util.AsyncFunction; 077import org.forgerock.util.Function; 078import org.forgerock.util.promise.Promise; 079 080import com.forgerock.opendj.util.StaticUtils; 081import com.forgerock.opendj.util.SubstringReader; 082 083/** 084 * Schema builders should be used for incremental construction of new schemas. 085 */ 086public final class SchemaBuilder { 087 088 /** Constant used for name to oid mapping when one name actually maps to multiple numerical OID. */ 089 public static final String AMBIGUOUS_OID = "<ambiguous-oid>"; 090 091 private static final String ATTR_SUBSCHEMA_SUBENTRY = "subschemaSubentry"; 092 093 private static final String[] SUBSCHEMA_ATTRS = { ATTR_LDAP_SYNTAXES, 094 ATTR_ATTRIBUTE_TYPES, ATTR_DIT_CONTENT_RULES, ATTR_DIT_STRUCTURE_RULES, 095 ATTR_MATCHING_RULE_USE, ATTR_MATCHING_RULES, ATTR_NAME_FORMS, ATTR_OBJECT_CLASSES }; 096 097 private static final Filter SUBSCHEMA_FILTER = Filter.valueOf("(objectClass=subschema)"); 098 099 private static final String[] SUBSCHEMA_SUBENTRY_ATTRS = { ATTR_SUBSCHEMA_SUBENTRY }; 100 101 /** 102 * Constructs a search request for retrieving the subschemaSubentry 103 * attribute from the named entry. 104 */ 105 private static SearchRequest getReadSchemaForEntrySearchRequest(final DN dn) { 106 return Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT, Filter.objectClassPresent(), 107 SUBSCHEMA_SUBENTRY_ATTRS); 108 } 109 110 /** 111 * Constructs a search request for retrieving the named subschema 112 * sub-entry. 113 */ 114 private static SearchRequest getReadSchemaSearchRequest(final DN dn) { 115 return Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT, SUBSCHEMA_FILTER, 116 SUBSCHEMA_ATTRS); 117 } 118 119 private static DN getSubschemaSubentryDN(final DN name, final Entry entry) throws LdapException { 120 final Attribute subentryAttr = entry.getAttribute(ATTR_SUBSCHEMA_SUBENTRY); 121 122 if (subentryAttr == null || subentryAttr.isEmpty()) { 123 // Did not get the subschema sub-entry attribute. 124 throw newLdapException(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED, 125 ERR_NO_SUBSCHEMA_SUBENTRY_ATTR.get(name.toString()).toString()); 126 } 127 128 final String dnString = subentryAttr.iterator().next().toString(); 129 DN subschemaDN; 130 try { 131 subschemaDN = DN.valueOf(dnString); 132 } catch (final LocalizedIllegalArgumentException e) { 133 throw newLdapException(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED, 134 ERR_INVALID_SUBSCHEMA_SUBENTRY_ATTR.get(name.toString(), dnString, 135 e.getMessageObject()).toString()); 136 } 137 return subschemaDN; 138 } 139 140 private Map<Integer, DITStructureRule> id2StructureRules; 141 private Map<String, List<AttributeType>> name2AttributeTypes; 142 private Map<String, List<DITContentRule>> name2ContentRules; 143 private Map<String, List<MatchingRule>> name2MatchingRules; 144 private Map<String, List<MatchingRuleUse>> name2MatchingRuleUses; 145 private Map<String, List<NameForm>> name2NameForms; 146 private Map<String, List<ObjectClass>> name2ObjectClasses; 147 private Map<String, List<DITStructureRule>> name2StructureRules; 148 private Map<String, List<DITStructureRule>> nameForm2StructureRules; 149 private Map<String, AttributeType> numericOID2AttributeTypes; 150 private Map<String, DITContentRule> numericOID2ContentRules; 151 private Map<String, MatchingRule> numericOID2MatchingRules; 152 private Map<String, MatchingRuleUse> numericOID2MatchingRuleUses; 153 private Map<String, NameForm> numericOID2NameForms; 154 private Map<String, ObjectClass> numericOID2ObjectClasses; 155 private Map<String, Syntax> numericOID2Syntaxes; 156 private Map<String, List<NameForm>> objectClass2NameForms; 157 private Map<String, String> name2OIDs; 158 private String schemaName; 159 private List<LocalizableMessage> warnings; 160 private SchemaOptions options; 161 162 163 /** A schema which should be copied into this builder on any mutation. */ 164 private Schema copyOnWriteSchema; 165 166 /** 167 * A unique ID which can be used to uniquely identify schemas 168 * constructed without a name. 169 */ 170 private static final AtomicInteger NEXT_SCHEMA_ID = new AtomicInteger(); 171 172 /** 173 * Creates a new schema builder with no schema elements and default 174 * compatibility options. 175 */ 176 public SchemaBuilder() { 177 preLazyInitBuilder(null, null); 178 } 179 180 /** 181 * Creates a new schema builder containing all of the schema elements 182 * contained in the provided subschema subentry. Any problems encountered 183 * while parsing the entry can be retrieved using the returned schema's 184 * {@link Schema#getWarnings()} method. 185 * 186 * @param entry 187 * The subschema subentry to be parsed. 188 * @throws NullPointerException 189 * If {@code entry} was {@code null}. 190 */ 191 public SchemaBuilder(final Entry entry) { 192 preLazyInitBuilder(entry.getName().toString(), null); 193 addSchema(entry, true); 194 } 195 196 /** 197 * Creates a new schema builder containing all of the schema elements from 198 * the provided schema and its compatibility options. 199 * 200 * @param schema 201 * The initial contents of the schema builder. 202 * @throws NullPointerException 203 * If {@code schema} was {@code null}. 204 */ 205 public SchemaBuilder(final Schema schema) { 206 preLazyInitBuilder(schema.getSchemaName(), schema); 207 } 208 209 /** 210 * Creates a new schema builder with no schema elements and default 211 * compatibility options. 212 * 213 * @param schemaName 214 * The user-friendly name of this schema which may be used for 215 * debugging purposes. 216 */ 217 public SchemaBuilder(final String schemaName) { 218 preLazyInitBuilder(schemaName, null); 219 } 220 221 private Boolean allowsMalformedNamesAndOptions() { 222 return options.get(ALLOW_MALFORMED_NAMES_AND_OPTIONS); 223 } 224 225 /** 226 * Adds the provided attribute type definition to this schema builder. 227 * 228 * @param definition 229 * The attribute type definition. 230 * @param overwrite 231 * {@code true} if any existing attribute type with the same OID 232 * should be overwritten. 233 * @return A reference to this schema builder. 234 * @throws ConflictingSchemaElementException 235 * If {@code overwrite} was {@code false} and a conflicting 236 * schema element was found. 237 * @throws LocalizedIllegalArgumentException 238 * If the provided attribute type definition could not be 239 * parsed. 240 * @throws NullPointerException 241 * If {@code definition} was {@code null}. 242 */ 243 public SchemaBuilder addAttributeType(final String definition, final boolean overwrite) { 244 Reject.ifNull(definition); 245 246 lazyInitBuilder(); 247 248 try { 249 final SubstringReader reader = new SubstringReader(definition); 250 251 // We'll do this a character at a time. First, skip over any 252 // leading whitespace. 253 reader.skipWhitespaces(); 254 255 if (reader.remaining() <= 0) { 256 // This means that the definition was empty or contained only 257 // whitespace. That is illegal. 258 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE1.get(definition)); 259 } 260 261 // The next character must be an open parenthesis. If it is not, 262 // then that is an error. 263 final char c = reader.read(); 264 if (c != '(') { 265 final LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get( 266 definition, reader.pos() - 1, String.valueOf(c)); 267 throw new LocalizedIllegalArgumentException(message); 268 } 269 270 // Skip over any spaces immediately following the opening 271 // parenthesis. 272 reader.skipWhitespaces(); 273 274 // The next set of characters must be the OID. 275 final String oid = readOID(reader, allowsMalformedNamesAndOptions()); 276 AttributeType.Builder atBuilder = new AttributeType.Builder(oid, this); 277 atBuilder.definition(definition); 278 String superiorType = null; 279 String syntax = null; 280 // At this point, we should have a pretty specific syntax that 281 // describes what may come next, but some of the components are 282 // optional and it would be pretty easy to put something in the 283 // wrong order, so we will be very flexible about what we can 284 // accept. Just look at the next token, figure out what it is and 285 // how to treat what comes after it, then repeat until we get to 286 // the end of the definition. But before we start, set default 287 // values for everything else we might need to know. 288 while (true) { 289 final String tokenName = readTokenName(reader); 290 291 if (tokenName == null) { 292 // No more tokens. 293 break; 294 } else if ("name".equalsIgnoreCase(tokenName)) { 295 atBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions())); 296 } else if ("desc".equalsIgnoreCase(tokenName)) { 297 // This specifies the description for the attribute type. It 298 // is an arbitrary string of characters enclosed in single 299 // quotes. 300 atBuilder.description(readQuotedString(reader)); 301 } else if ("obsolete".equalsIgnoreCase(tokenName)) { 302 // This indicates whether the attribute type should be 303 // considered obsolete. 304 atBuilder.obsolete(true); 305 } else if ("sup".equalsIgnoreCase(tokenName)) { 306 // This specifies the name or OID of the superior attribute 307 // type from which this attribute type should inherit its 308 // properties. 309 superiorType = readOID(reader, allowsMalformedNamesAndOptions()); 310 } else if ("equality".equalsIgnoreCase(tokenName)) { 311 // This specifies the name or OID of the equality matching 312 // rule to use for this attribute type. 313 atBuilder.equalityMatchingRule(readOID(reader, allowsMalformedNamesAndOptions())); 314 } else if ("ordering".equalsIgnoreCase(tokenName)) { 315 // This specifies the name or OID of the ordering matching 316 // rule to use for this attribute type. 317 atBuilder.orderingMatchingRule(readOID(reader, allowsMalformedNamesAndOptions())); 318 } else if ("substr".equalsIgnoreCase(tokenName)) { 319 // This specifies the name or OID of the substring matching 320 // rule to use for this attribute type. 321 atBuilder.substringMatchingRule(readOID(reader, allowsMalformedNamesAndOptions())); 322 } else if ("syntax".equalsIgnoreCase(tokenName)) { 323 // This specifies the numeric OID of the syntax for this 324 // matching rule. It may optionally be immediately followed 325 // by an open curly brace, an integer definition, and a close 326 // curly brace to suggest the minimum number of characters 327 // that should be allowed in values of that type. This 328 // implementation will ignore any such length because it 329 // does not impose any practical limit on the length of attribute 330 // values. 331 syntax = readOIDLen(reader, allowsMalformedNamesAndOptions()); 332 } else if ("single-value".equalsIgnoreCase(tokenName)) { 333 // This indicates that attributes of this type are allowed 334 // to have at most one value. 335 atBuilder.singleValue(true); 336 } else if ("collective".equalsIgnoreCase(tokenName)) { 337 // This indicates that attributes of this type are collective 338 // (i.e., have their values generated dynamically in some way). 339 atBuilder.collective(true); 340 } else if ("no-user-modification".equalsIgnoreCase(tokenName)) { 341 // This indicates that the values of attributes of this type 342 // are not to be modified by end users. 343 atBuilder.noUserModification(true); 344 } else if ("usage".equalsIgnoreCase(tokenName)) { 345 // This specifies the usage string for this attribute type. 346 // It should be followed by one of the strings 347 // "userApplications", "directoryOperation", 348 // "distributedOperation", or "dSAOperation". 349 int length = 0; 350 351 reader.skipWhitespaces(); 352 reader.mark(); 353 354 while (" )".indexOf(reader.read()) == -1) { 355 length++; 356 } 357 reader.reset(); 358 final String usageStr = reader.read(length); 359 if ("userapplications".equalsIgnoreCase(usageStr)) { 360 atBuilder.usage(AttributeUsage.USER_APPLICATIONS); 361 } else if ("directoryoperation".equalsIgnoreCase(usageStr)) { 362 atBuilder.usage(AttributeUsage.DIRECTORY_OPERATION); 363 } else if ("distributedoperation".equalsIgnoreCase(usageStr)) { 364 atBuilder.usage(AttributeUsage.DISTRIBUTED_OPERATION); 365 } else if ("dsaoperation".equalsIgnoreCase(usageStr)) { 366 atBuilder.usage(AttributeUsage.DSA_OPERATION); 367 } else { 368 throw new LocalizedIllegalArgumentException( 369 WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE1.get(definition, usageStr)); 370 } 371 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { 372 // This must be a non-standard property and it must be 373 // followed by either a single definition in single quotes 374 // or an open parenthesis followed by one or more values in 375 // single quotes separated by spaces followed by a close 376 // parenthesis. 377 atBuilder.extraProperties(tokenName, readExtensions(reader)); 378 } else { 379 throw new LocalizedIllegalArgumentException( 380 ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_TOKEN1.get(definition, tokenName)); 381 } 382 } 383 384 final List<String> approxRules = atBuilder.getExtraProperties().get(SCHEMA_PROPERTY_APPROX_RULE); 385 if (approxRules != null && !approxRules.isEmpty()) { 386 atBuilder.approximateMatchingRule(approxRules.get(0)); 387 } 388 389 if (superiorType == null && syntax == null) { 390 throw new LocalizedIllegalArgumentException( 391 WARN_ATTR_SYNTAX_ATTRTYPE_MISSING_SYNTAX_AND_SUPERIOR.get(definition)); 392 } 393 394 atBuilder.superiorType(superiorType) 395 .syntax(syntax); 396 397 return overwrite ? atBuilder.addToSchemaOverwrite() : atBuilder.addToSchema(); 398 } catch (final DecodeException e) { 399 final LocalizableMessage msg = ERR_ATTR_SYNTAX_ATTRTYPE_INVALID1.get(definition, e.getMessageObject()); 400 throw new LocalizedIllegalArgumentException(msg, e.getCause()); 401 } 402 } 403 404 /** 405 * Adds the provided DIT content rule definition to this schema builder. 406 * 407 * @param definition 408 * The DIT content rule definition. 409 * @param overwrite 410 * {@code true} if any existing DIT content rule with the same 411 * OID should be overwritten. 412 * @return A reference to this schema builder. 413 * @throws ConflictingSchemaElementException 414 * If {@code overwrite} was {@code false} and a conflicting 415 * schema element was found. 416 * @throws LocalizedIllegalArgumentException 417 * If the provided DIT content rule definition could not be 418 * parsed. 419 * @throws NullPointerException 420 * If {@code definition} was {@code null}. 421 */ 422 public SchemaBuilder addDITContentRule(final String definition, final boolean overwrite) { 423 Reject.ifNull(definition); 424 425 lazyInitBuilder(); 426 427 try { 428 final SubstringReader reader = new SubstringReader(definition); 429 430 // We'll do this a character at a time. First, skip over any 431 // leading whitespace. 432 reader.skipWhitespaces(); 433 434 if (reader.remaining() <= 0) { 435 // This means that the value was empty or contained only 436 // whitespace. That is illegal. 437 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_DCR_EMPTY_VALUE1.get(definition)); 438 } 439 440 // The next character must be an open parenthesis. If it is not, 441 // then that is an error. 442 final char c = reader.read(); 443 if (c != '(') { 444 final LocalizableMessage message = ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS.get( 445 definition, reader.pos() - 1, String.valueOf(c)); 446 throw new LocalizedIllegalArgumentException(message); 447 } 448 449 // Skip over any spaces immediately following the opening 450 // parenthesis. 451 reader.skipWhitespaces(); 452 453 // The next set of characters must be the OID. 454 final DITContentRule.Builder contentRuleBuilder = 455 buildDITContentRule(readOID(reader, allowsMalformedNamesAndOptions())); 456 contentRuleBuilder.definition(definition); 457 458 // At this point, we should have a pretty specific syntax that 459 // describes what may come next, but some of the components are 460 // optional and it would be pretty easy to put something in the 461 // wrong order, so we will be very flexible about what we can 462 // accept. Just look at the next token, figure out what it is and 463 // how to treat what comes after it, then repeat until we get to 464 // the end of the value. But before we start, set default values 465 // for everything else we might need to know. 466 while (true) { 467 final String tokenName = readTokenName(reader); 468 469 if (tokenName == null) { 470 // No more tokens. 471 break; 472 } else if ("name".equalsIgnoreCase(tokenName)) { 473 contentRuleBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions())); 474 } else if ("desc".equalsIgnoreCase(tokenName)) { 475 // This specifies the description for the attribute type. It 476 // is an arbitrary string of characters enclosed in single 477 // quotes. 478 contentRuleBuilder.description(readQuotedString(reader)); 479 } else if ("obsolete".equalsIgnoreCase(tokenName)) { 480 // This indicates whether the attribute type should be 481 // considered obsolete. 482 contentRuleBuilder.obsolete(true); 483 } else if ("aux".equalsIgnoreCase(tokenName)) { 484 contentRuleBuilder.auxiliaryObjectClasses(readOIDs(reader, allowsMalformedNamesAndOptions())); 485 } else if ("must".equalsIgnoreCase(tokenName)) { 486 contentRuleBuilder.requiredAttributes(readOIDs(reader, allowsMalformedNamesAndOptions())); 487 } else if ("may".equalsIgnoreCase(tokenName)) { 488 contentRuleBuilder.optionalAttributes(readOIDs(reader, allowsMalformedNamesAndOptions())); 489 } else if ("not".equalsIgnoreCase(tokenName)) { 490 contentRuleBuilder.prohibitedAttributes(readOIDs(reader, allowsMalformedNamesAndOptions())); 491 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { 492 // This must be a non-standard property and it must be 493 // followed by either a single definition in single quotes 494 // or an open parenthesis followed by one or more values in 495 // single quotes separated by spaces followed by a close 496 // parenthesis. 497 contentRuleBuilder.extraProperties(tokenName, readExtensions(reader)); 498 } else { 499 throw new LocalizedIllegalArgumentException( 500 ERR_ATTR_SYNTAX_DCR_ILLEGAL_TOKEN1.get(definition, tokenName)); 501 } 502 } 503 504 return overwrite ? contentRuleBuilder.addToSchemaOverwrite() : contentRuleBuilder.addToSchema(); 505 } catch (final DecodeException e) { 506 final LocalizableMessage msg = ERR_ATTR_SYNTAX_DCR_INVALID1.get(definition, e.getMessageObject()); 507 throw new LocalizedIllegalArgumentException(msg, e.getCause()); 508 } 509 } 510 511 /** 512 * Adds the provided DIT structure rule definition to this schema builder. 513 * 514 * @param definition 515 * The DIT structure rule definition. 516 * @param overwrite 517 * {@code true} if any existing DIT structure rule with the same 518 * OID should be overwritten. 519 * @return A reference to this schema builder. 520 * @throws ConflictingSchemaElementException 521 * If {@code overwrite} was {@code false} and a conflicting 522 * schema element was found. 523 * @throws LocalizedIllegalArgumentException 524 * If the provided DIT structure rule definition could not be 525 * parsed. 526 * @throws NullPointerException 527 * If {@code definition} was {@code null}. 528 */ 529 public SchemaBuilder addDITStructureRule(final String definition, final boolean overwrite) { 530 Reject.ifNull(definition); 531 532 lazyInitBuilder(); 533 534 try { 535 final SubstringReader reader = new SubstringReader(definition); 536 537 // We'll do this a character at a time. First, skip over any 538 // leading whitespace. 539 reader.skipWhitespaces(); 540 541 if (reader.remaining() <= 0) { 542 // This means that the value was empty or contained only 543 // whitespace. That is illegal. 544 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE1.get(definition)); 545 } 546 547 // The next character must be an open parenthesis. If it is not, 548 // then that is an error. 549 final char c = reader.read(); 550 if (c != '(') { 551 final LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get( 552 definition, reader.pos() - 1, String.valueOf(c)); 553 throw new LocalizedIllegalArgumentException(message); 554 } 555 556 // Skip over any spaces immediately following the opening 557 // parenthesis. 558 reader.skipWhitespaces(); 559 560 // The next set of characters must be the OID. 561 final DITStructureRule.Builder ruleBuilder = new DITStructureRule.Builder(readRuleID(reader), this); 562 String nameForm = null; 563 564 // At this point, we should have a pretty specific syntax that 565 // describes what may come next, but some of the components are 566 // optional and it would be pretty easy to put something in the 567 // wrong order, so we will be very flexible about what we can 568 // accept. Just look at the next token, figure out what it is and 569 // how to treat what comes after it, then repeat until we get to 570 // the end of the value. But before we start, set default values 571 // for everything else we might need to know. 572 while (true) { 573 final String tokenName = readTokenName(reader); 574 575 if (tokenName == null) { 576 // No more tokens. 577 break; 578 } else if ("name".equalsIgnoreCase(tokenName)) { 579 ruleBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions())); 580 } else if ("desc".equalsIgnoreCase(tokenName)) { 581 // This specifies the description for the attribute type. It 582 // is an arbitrary string of characters enclosed in single 583 // quotes. 584 ruleBuilder.description(readQuotedString(reader)); 585 } else if ("obsolete".equalsIgnoreCase(tokenName)) { 586 // This indicates whether the attribute type should be 587 // considered obsolete. 588 ruleBuilder.obsolete(true); 589 } else if ("form".equalsIgnoreCase(tokenName)) { 590 nameForm = readOID(reader, allowsMalformedNamesAndOptions()); 591 } else if ("sup".equalsIgnoreCase(tokenName)) { 592 ruleBuilder.superiorRules(readRuleIDs(reader)); 593 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { 594 // This must be a non-standard property and it must be 595 // followed by either a single definition in single quotes 596 // or an open parenthesis followed by one or more values in 597 // single quotes separated by spaces followed by a close 598 // parenthesis. 599 ruleBuilder.extraProperties(tokenName, readExtensions(reader)); 600 } else { 601 throw new LocalizedIllegalArgumentException( 602 ERR_ATTR_SYNTAX_DSR_ILLEGAL_TOKEN1.get(definition, tokenName)); 603 } 604 } 605 606 if (nameForm == null) { 607 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM.get(definition)); 608 } 609 ruleBuilder.nameForm(nameForm); 610 611 return overwrite ? ruleBuilder.addToSchemaOverwrite() : ruleBuilder.addToSchema(); 612 } catch (final DecodeException e) { 613 final LocalizableMessage msg = ERR_ATTR_SYNTAX_DSR_INVALID1.get(definition, e.getMessageObject()); 614 throw new LocalizedIllegalArgumentException(msg, e.getCause()); 615 } 616 } 617 618 /** 619 * Adds the provided enumeration syntax definition to this schema builder. 620 * 621 * @param oid 622 * The OID of the enumeration syntax definition. 623 * @param description 624 * The description of the enumeration syntax definition. 625 * @param overwrite 626 * {@code true} if any existing syntax with the same OID should 627 * be overwritten. 628 * @param enumerations 629 * The range of values which attribute values must match in order 630 * to be valid. 631 * @return A reference to this schema builder. 632 * @throws ConflictingSchemaElementException 633 * If {@code overwrite} was {@code false} and a conflicting 634 * schema element was found. 635 */ 636 public SchemaBuilder addEnumerationSyntax(final String oid, final String description, 637 final boolean overwrite, final String... enumerations) { 638 Reject.ifNull((Object) enumerations); 639 640 lazyInitBuilder(); 641 642 final EnumSyntaxImpl enumImpl = new EnumSyntaxImpl(oid, Arrays.asList(enumerations)); 643 644 final Syntax.Builder syntaxBuilder = buildSyntax(oid).description(description) 645 .extraProperties(Collections.singletonMap("X-ENUM", Arrays.asList(enumerations))) 646 .implementation(enumImpl); 647 648 syntaxBuilder.addToSchema(overwrite); 649 650 try { 651 buildMatchingRule(enumImpl.getOrderingMatchingRule()) 652 .names(OMR_GENERIC_ENUM_NAME + oid) 653 .syntaxOID(oid) 654 .extraProperties(CoreSchemaImpl.OPENDS_ORIGIN) 655 .implementation(new EnumOrderingMatchingRule(enumImpl)) 656 .addToSchemaOverwrite(); 657 } catch (final ConflictingSchemaElementException e) { 658 removeSyntax(oid); 659 } 660 return this; 661 } 662 663 /** 664 * Adds the provided matching rule definition to this schema builder. 665 * 666 * @param definition 667 * The matching rule definition. 668 * @param overwrite 669 * {@code true} if any existing matching rule with the same OID 670 * should be overwritten. 671 * @return A reference to this schema builder. 672 * @throws ConflictingSchemaElementException 673 * If {@code overwrite} was {@code false} and a conflicting 674 * schema element was found. 675 * @throws LocalizedIllegalArgumentException 676 * If the provided matching rule definition could not be parsed. 677 * @throws NullPointerException 678 * If {@code definition} was {@code null}. 679 */ 680 public SchemaBuilder addMatchingRule(final String definition, final boolean overwrite) { 681 Reject.ifNull(definition); 682 683 lazyInitBuilder(); 684 685 try { 686 final SubstringReader reader = new SubstringReader(definition); 687 688 // We'll do this a character at a time. First, skip over any 689 // leading whitespace. 690 reader.skipWhitespaces(); 691 692 if (reader.remaining() <= 0) { 693 // This means that the value was empty or contained only 694 // whitespace. That is illegal. 695 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_MR_EMPTY_VALUE1.get(definition)); 696 } 697 698 // The next character must be an open parenthesis. If it is not, 699 // then that is an error. 700 final char c = reader.read(); 701 if (c != '(') { 702 final LocalizableMessage message = ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS.get( 703 definition, reader.pos() - 1, String.valueOf(c)); 704 throw new LocalizedIllegalArgumentException(message); 705 } 706 707 // Skip over any spaces immediately following the opening 708 // parenthesis. 709 reader.skipWhitespaces(); 710 711 // The next set of characters must be the OID. 712 final MatchingRule.Builder matchingRuleBuilder = new MatchingRule.Builder( 713 readOID(reader, allowsMalformedNamesAndOptions()), this); 714 matchingRuleBuilder.definition(definition); 715 716 String syntax = null; 717 // At this point, we should have a pretty specific syntax that 718 // describes what may come next, but some of the components are 719 // optional and it would be pretty easy to put something in the 720 // wrong order, so we will be very flexible about what we can 721 // accept. Just look at the next token, figure out what it is and 722 // how to treat what comes after it, then repeat until we get to 723 // the end of the value. But before we start, set default values 724 // for everything else we might need to know. 725 while (true) { 726 final String tokenName = readTokenName(reader); 727 728 if (tokenName == null) { 729 // No more tokens. 730 break; 731 } else if ("name".equalsIgnoreCase(tokenName)) { 732 matchingRuleBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions())); 733 } else if ("desc".equalsIgnoreCase(tokenName)) { 734 // This specifies the description for the matching rule. It 735 // is an arbitrary string of characters enclosed in single 736 // quotes. 737 matchingRuleBuilder.description(readQuotedString(reader)); 738 } else if ("obsolete".equalsIgnoreCase(tokenName)) { 739 // This indicates whether the matching rule should be 740 // considered obsolete. We do not need to do any more 741 // parsing for this token. 742 matchingRuleBuilder.obsolete(true); 743 } else if ("syntax".equalsIgnoreCase(tokenName)) { 744 syntax = readOID(reader, allowsMalformedNamesAndOptions()); 745 matchingRuleBuilder.syntaxOID(syntax); 746 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { 747 // This must be a non-standard property and it must be 748 // followed by either a single definition in single quotes 749 // or an open parenthesis followed by one or more values in 750 // single quotes separated by spaces followed by a close 751 // parenthesis. 752 final List<String> extensions = readExtensions(reader); 753 matchingRuleBuilder.extraProperties(tokenName, extensions.toArray(new String[extensions.size()])); 754 } else { 755 throw new LocalizedIllegalArgumentException( 756 ERR_ATTR_SYNTAX_MR_ILLEGAL_TOKEN1.get(definition, tokenName)); 757 } 758 } 759 760 // Make sure that a syntax was specified. 761 if (syntax == null) { 762 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_MR_NO_SYNTAX.get(definition)); 763 } 764 if (overwrite) { 765 matchingRuleBuilder.addToSchemaOverwrite(); 766 } else { 767 matchingRuleBuilder.addToSchema(); 768 } 769 } catch (final DecodeException e) { 770 final LocalizableMessage msg = 771 ERR_ATTR_SYNTAX_MR_INVALID1.get(definition, e.getMessageObject()); 772 throw new LocalizedIllegalArgumentException(msg, e.getCause()); 773 } 774 return this; 775 } 776 777 /** 778 * Adds the provided matching rule use definition to this schema builder. 779 * 780 * @param definition 781 * The matching rule use definition. 782 * @param overwrite 783 * {@code true} if any existing matching rule use with the same 784 * OID should be overwritten. 785 * @return A reference to this schema builder. 786 * @throws ConflictingSchemaElementException 787 * If {@code overwrite} was {@code false} and a conflicting 788 * schema element was found. 789 * @throws LocalizedIllegalArgumentException 790 * If the provided matching rule use definition could not be 791 * parsed. 792 * @throws NullPointerException 793 * If {@code definition} was {@code null}. 794 */ 795 public SchemaBuilder addMatchingRuleUse(final String definition, final boolean overwrite) { 796 Reject.ifNull(definition); 797 798 lazyInitBuilder(); 799 800 try { 801 final SubstringReader reader = new SubstringReader(definition); 802 803 // We'll do this a character at a time. First, skip over any 804 // leading whitespace. 805 reader.skipWhitespaces(); 806 807 if (reader.remaining() <= 0) { 808 // This means that the value was empty or contained only 809 // whitespace. That is illegal. 810 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_MRUSE_EMPTY_VALUE1.get(definition)); 811 } 812 813 // The next character must be an open parenthesis. If it is not, 814 // then that is an error. 815 final char c = reader.read(); 816 if (c != '(') { 817 final LocalizableMessage message = ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS.get( 818 definition, reader.pos() - 1, String.valueOf(c)); 819 throw new LocalizedIllegalArgumentException(message); 820 } 821 822 // Skip over any spaces immediately following the opening 823 // parenthesis. 824 reader.skipWhitespaces(); 825 826 // The next set of characters must be the OID. 827 final MatchingRuleUse.Builder useBuilder = 828 buildMatchingRuleUse(readOID(reader, allowsMalformedNamesAndOptions())); 829 Set<String> attributes = null; 830 831 // At this point, we should have a pretty specific syntax that 832 // describes what may come next, but some of the components are 833 // optional and it would be pretty easy to put something in the 834 // wrong order, so we will be very flexible about what we can 835 // accept. Just look at the next token, figure out what it is and 836 // how to treat what comes after it, then repeat until we get to 837 // the end of the value. But before we start, set default values 838 // for everything else we might need to know. 839 while (true) { 840 final String tokenName = readTokenName(reader); 841 842 if (tokenName == null) { 843 // No more tokens. 844 break; 845 } else if ("name".equalsIgnoreCase(tokenName)) { 846 useBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions())); 847 } else if ("desc".equalsIgnoreCase(tokenName)) { 848 // This specifies the description for the attribute type. It 849 // is an arbitrary string of characters enclosed in single 850 // quotes. 851 useBuilder.description(readQuotedString(reader)); 852 } else if ("obsolete".equalsIgnoreCase(tokenName)) { 853 // This indicates whether the attribute type should be 854 // considered obsolete. 855 useBuilder.obsolete(true); 856 } else if ("applies".equalsIgnoreCase(tokenName)) { 857 attributes = readOIDs(reader, allowsMalformedNamesAndOptions()); 858 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { 859 // This must be a non-standard property and it must be 860 // followed by either a single definition in single quotes 861 // or an open parenthesis followed by one or more values in 862 // single quotes separated by spaces followed by a close 863 // parenthesis. 864 useBuilder.extraProperties(tokenName, readExtensions(reader)); 865 } else { 866 throw new LocalizedIllegalArgumentException( 867 ERR_ATTR_SYNTAX_MRUSE_ILLEGAL_TOKEN1.get(definition, tokenName)); 868 } 869 } 870 871 // Make sure that the set of attributes was defined. 872 if (attributes == null || attributes.size() == 0) { 873 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_MRUSE_NO_ATTR.get(definition)); 874 } 875 useBuilder.attributes(attributes); 876 877 return overwrite ? useBuilder.addToSchemaOverwrite() : useBuilder.addToSchema(); 878 } catch (final DecodeException e) { 879 final LocalizableMessage msg = ERR_ATTR_SYNTAX_MRUSE_INVALID1.get(definition, e.getMessageObject()); 880 throw new LocalizedIllegalArgumentException(msg, e.getCause()); 881 } 882 } 883 884 /** 885 * Returns a builder which can be used for incrementally constructing a new 886 * attribute type before adding it to the schema. Example usage: 887 * 888 * <pre> 889 * SchemaBuilder builder = ...; 890 * builder.buildAttributeType("attributetype-oid").name("attribute type name").addToSchema(); 891 * </pre> 892 * 893 * @param oid 894 * The OID of the attribute type definition. 895 * @return A builder to continue building the AttributeType. 896 */ 897 public AttributeType.Builder buildAttributeType(final String oid) { 898 lazyInitBuilder(); 899 return new AttributeType.Builder(oid, this); 900 } 901 902 /** 903 * Returns a builder which can be used for incrementally constructing a new 904 * DIT structure rule before adding it to the schema. Example usage: 905 * 906 * <pre> 907 * SchemaBuilder builder = ...; 908 * final int myRuleID = ...; 909 * builder.buildDITStructureRule(myRuleID).name("DIT structure rule name").addToSchema(); 910 * </pre> 911 * 912 * @param ruleID 913 * The ID of the DIT structure rule. 914 * @return A builder to continue building the DITStructureRule. 915 */ 916 public DITStructureRule.Builder buildDITStructureRule(final int ruleID) { 917 lazyInitBuilder(); 918 return new DITStructureRule.Builder(ruleID, this); 919 } 920 921 /** 922 * Returns a builder which can be used for incrementally constructing a new matching rule before adding it to the 923 * schema. Example usage: 924 * 925 * <pre> 926 * SchemaBuilder builder = ...; 927 * builder.buildMatchingRule("matchingrule-oid").name("matching rule name").addToSchema(); 928 * </pre> 929 * 930 * @param oid 931 * The OID of the matching rule definition. 932 * @return A builder to continue building the MatchingRule. 933 */ 934 public MatchingRule.Builder buildMatchingRule(final String oid) { 935 lazyInitBuilder(); 936 return new MatchingRule.Builder(oid, this); 937 } 938 939 /** 940 * Returns a builder which can be used for incrementally constructing a new 941 * matching rule use before adding it to the schema. Example usage: 942 * 943 * <pre> 944 * SchemaBuilder builder = ...; 945 * builder.buildMatchingRuleUse("matchingrule-oid") 946 * .name("matching rule use name") 947 * .addToSchema(); 948 * </pre> 949 * 950 * @param oid 951 * The OID of the matching rule definition. 952 * @return A builder to continue building the MatchingRuleUse. 953 */ 954 public MatchingRuleUse.Builder buildMatchingRuleUse(final String oid) { 955 lazyInitBuilder(); 956 return new MatchingRuleUse.Builder(oid, this); 957 } 958 959 /** 960 * Adds the provided name form definition to this schema builder. 961 * 962 * @param definition 963 * The name form definition. 964 * @param overwrite 965 * {@code true} if any existing name form with the same OID 966 * should be overwritten. 967 * @return A reference to this schema builder. 968 * @throws ConflictingSchemaElementException 969 * If {@code overwrite} was {@code false} and a conflicting 970 * schema element was found. 971 * @throws LocalizedIllegalArgumentException 972 * If the provided name form definition could not be parsed. 973 * @throws NullPointerException 974 * If {@code definition} was {@code null}. 975 */ 976 public SchemaBuilder addNameForm(final String definition, final boolean overwrite) { 977 Reject.ifNull(definition); 978 979 lazyInitBuilder(); 980 981 try { 982 final SubstringReader reader = new SubstringReader(definition); 983 984 // We'll do this a character at a time. First, skip over any 985 // leading whitespace. 986 reader.skipWhitespaces(); 987 988 if (reader.remaining() <= 0) { 989 // This means that the value was empty or contained only 990 // whitespace. That is illegal. 991 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_NAME_FORM_EMPTY_VALUE1.get(definition)); 992 } 993 994 // The next character must be an open parenthesis. If it is not, 995 // then that is an error. 996 final char c = reader.read(); 997 if (c != '(') { 998 final LocalizableMessage message = ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get( 999 definition, reader.pos() - 1, c); 1000 throw new LocalizedIllegalArgumentException(message); 1001 } 1002 1003 // Skip over any spaces immediately following the opening 1004 // parenthesis. 1005 reader.skipWhitespaces(); 1006 1007 // The next set of characters must be the OID. 1008 final NameForm.Builder nameFormBuilder = new NameForm.Builder( 1009 readOID(reader, allowsMalformedNamesAndOptions()), this); 1010 nameFormBuilder.definition(definition); 1011 1012 // Required properties : 1013 String structuralOID = null; 1014 Collection<String> requiredAttributes = Collections.emptyList(); 1015 1016 // At this point, we should have a pretty specific syntax that 1017 // describes what may come next, but some of the components are 1018 // optional and it would be pretty easy to put something in the 1019 // wrong order, so we will be very flexible about what we can 1020 // accept. Just look at the next token, figure out what it is and 1021 // how to treat what comes after it, then repeat until we get to 1022 // the end of the value. But before we start, set default values 1023 // for everything else we might need to know. 1024 while (true) { 1025 final String tokenName = readTokenName(reader); 1026 1027 if (tokenName == null) { 1028 // No more tokens. 1029 break; 1030 } else if ("name".equalsIgnoreCase(tokenName)) { 1031 nameFormBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions())); 1032 } else if ("desc".equalsIgnoreCase(tokenName)) { 1033 // This specifies the description for the attribute type. It 1034 // is an arbitrary string of characters enclosed in single 1035 // quotes. 1036 nameFormBuilder.description(readQuotedString(reader)); 1037 } else if ("obsolete".equalsIgnoreCase(tokenName)) { 1038 // This indicates whether the attribute type should be 1039 // considered obsolete. We do not need to do any more 1040 // parsing for this token. 1041 nameFormBuilder.obsolete(true); 1042 } else if ("oc".equalsIgnoreCase(tokenName)) { 1043 structuralOID = readOID(reader, allowsMalformedNamesAndOptions()); 1044 nameFormBuilder.structuralObjectClassOID(structuralOID); 1045 } else if ("must".equalsIgnoreCase(tokenName)) { 1046 requiredAttributes = readOIDs(reader, allowsMalformedNamesAndOptions()); 1047 nameFormBuilder.requiredAttributes(requiredAttributes); 1048 } else if ("may".equalsIgnoreCase(tokenName)) { 1049 nameFormBuilder.optionalAttributes(readOIDs(reader, allowsMalformedNamesAndOptions())); 1050 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { 1051 // This must be a non-standard property and it must be 1052 // followed by either a single definition in single quotes 1053 // or an open parenthesis followed by one or more values in 1054 // single quotes separated by spaces followed by a close 1055 // parenthesis. 1056 final List<String> extensions = readExtensions(reader); 1057 nameFormBuilder.extraProperties(tokenName, extensions.toArray(new String[extensions.size()])); 1058 } else { 1059 throw new LocalizedIllegalArgumentException( 1060 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_TOKEN1.get(definition, tokenName)); 1061 } 1062 } 1063 1064 // Make sure that a structural class was specified. If not, then 1065 // it cannot be valid and the name form cannot be build. 1066 if (structuralOID == null) { 1067 throw new LocalizedIllegalArgumentException( 1068 ERR_ATTR_SYNTAX_NAME_FORM_NO_STRUCTURAL_CLASS1.get(definition)); 1069 } 1070 1071 if (requiredAttributes.isEmpty()) { 1072 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_NAME_FORM_NO_REQUIRED_ATTR.get(definition)); 1073 } 1074 1075 if (overwrite) { 1076 nameFormBuilder.addToSchemaOverwrite(); 1077 } else { 1078 nameFormBuilder.addToSchema(); 1079 } 1080 } catch (final DecodeException e) { 1081 final LocalizableMessage msg = 1082 ERR_ATTR_SYNTAX_NAME_FORM_INVALID1.get(definition, e.getMessageObject()); 1083 throw new LocalizedIllegalArgumentException(msg, e.getCause()); 1084 } 1085 return this; 1086 } 1087 1088 /** 1089 * Returns a builder which can be used for incrementally constructing a new 1090 * DIT content rule before adding it to the schema. Example usage: 1091 * 1092 * <pre> 1093 * SchemaBuilder builder = ...; 1094 * builder.buildDITContentRule("structuralobjectclass-oid").name("DIT content rule name").addToSchema(); 1095 * </pre> 1096 * 1097 * @param structuralClassOID 1098 * The OID of the structural objectclass for the DIT content rule to build. 1099 * @return A builder to continue building the DITContentRule. 1100 */ 1101 public Builder buildDITContentRule(String structuralClassOID) { 1102 lazyInitBuilder(); 1103 return new DITContentRule.Builder(structuralClassOID, this); 1104 } 1105 1106 /** 1107 * Returns a builder which can be used for incrementally constructing a new 1108 * name form before adding it to the schema. Example usage: 1109 * 1110 * <pre> 1111 * SchemaBuilder builder = ...; 1112 * builder.buildNameForm("1.2.3.4").name("myNameform").addToSchema(); 1113 * </pre> 1114 * 1115 * @param oid 1116 * The OID of the name form definition. 1117 * @return A builder to continue building the NameForm. 1118 */ 1119 public NameForm.Builder buildNameForm(final String oid) { 1120 lazyInitBuilder(); 1121 return new NameForm.Builder(oid, this); 1122 } 1123 1124 /** 1125 * Returns a builder which can be used for incrementally constructing a new 1126 * object class before adding it to the schema. Example usage: 1127 * 1128 * <pre> 1129 * SchemaBuilder builder = ...; 1130 * builder.buildObjectClass("objectclass-oid").name("object class name").addToSchema(); 1131 * </pre> 1132 * 1133 * @param oid 1134 * The OID of the object class definition. 1135 * @return A builder to continue building the ObjectClass. 1136 */ 1137 public ObjectClass.Builder buildObjectClass(final String oid) { 1138 lazyInitBuilder(); 1139 return new ObjectClass.Builder(oid, this); 1140 } 1141 1142 /** 1143 * Returns a builder which can be used for incrementally constructing a new 1144 * syntax before adding it to the schema. Example usage: 1145 * 1146 * <pre> 1147 * SchemaBuilder builder = ...; 1148 * builder.buildSyntax("1.2.3.4").addToSchema(); 1149 * </pre> 1150 * 1151 * @param oid 1152 * The OID of the syntax definition. 1153 * @return A builder to continue building the syntax. 1154 */ 1155 public Syntax.Builder buildSyntax(final String oid) { 1156 lazyInitBuilder(); 1157 return new Syntax.Builder(oid, this); 1158 } 1159 1160 /** 1161 * Returns an attribute type builder whose fields are initialized to the 1162 * values of the provided attribute type. This method should be used when 1163 * duplicating attribute types from external schemas or when modifying 1164 * existing attribute types. 1165 * 1166 * @param attributeType 1167 * The attribute type source. 1168 * @return A builder to continue building the AttributeType. 1169 */ 1170 public AttributeType.Builder buildAttributeType(final AttributeType attributeType) { 1171 lazyInitBuilder(); 1172 return new AttributeType.Builder(attributeType, this); 1173 } 1174 1175 /** 1176 * Returns a DIT content rule builder whose fields are initialized to the 1177 * values of the provided DIT content rule. This method should be used when 1178 * duplicating DIT content rules from external schemas or when modifying 1179 * existing DIT content rules. 1180 * 1181 * @param contentRule 1182 * The DIT content rule source. 1183 * @return A builder to continue building the DITContentRule. 1184 */ 1185 public DITContentRule.Builder buildDITContentRule(DITContentRule contentRule) { 1186 lazyInitBuilder(); 1187 return new DITContentRule.Builder(contentRule, this); 1188 } 1189 1190 /** 1191 * Returns an DIT structure rule builder whose fields are initialized to the 1192 * values of the provided rule. This method should be used when duplicating 1193 * structure rules from external schemas or when modifying existing 1194 * structure rules. 1195 * 1196 * @param structureRule 1197 * The DIT structure rule source. 1198 * @return A builder to continue building the DITStructureRule. 1199 */ 1200 public DITStructureRule.Builder buildDITStructureRule(final DITStructureRule structureRule) { 1201 lazyInitBuilder(); 1202 return new DITStructureRule.Builder(structureRule, this); 1203 } 1204 1205 /** 1206 * Returns a matching rule builder whose fields are initialized to the 1207 * values of the provided matching rule. This method should be used when 1208 * duplicating matching rules from external schemas or when modifying 1209 * existing matching rules. 1210 * 1211 * @param matchingRule 1212 * The matching rule source. 1213 * @return A builder to continue building the MatchingRule. 1214 */ 1215 public MatchingRule.Builder buildMatchingRule(final MatchingRule matchingRule) { 1216 return buildMatchingRule(matchingRule, true); 1217 } 1218 1219 private MatchingRule.Builder buildMatchingRule(final MatchingRule matchingRule, final boolean initialize) { 1220 if (initialize) { 1221 lazyInitBuilder(); 1222 } 1223 return new MatchingRule.Builder(matchingRule, this); 1224 } 1225 1226 /** 1227 * Returns a matching rule use builder whose fields are initialized to the 1228 * values of the provided matching rule use object. This method should be used when 1229 * duplicating matching rule uses from external schemas or when modifying 1230 * existing matching rule uses. 1231 * 1232 * @param matchingRuleUse 1233 * The matching rule use source. 1234 * @return A builder to continue building the MatchingRuleUse. 1235 */ 1236 public MatchingRuleUse.Builder buildMatchingRuleUse(final MatchingRuleUse matchingRuleUse) { 1237 lazyInitBuilder(); 1238 return new MatchingRuleUse.Builder(matchingRuleUse, this); 1239 } 1240 1241 /** 1242 * Returns a name form builder whose fields are initialized to the 1243 * values of the provided name form. This method should be used when 1244 * duplicating name forms from external schemas or when modifying 1245 * existing names forms. 1246 * 1247 * @param nameForm 1248 * The name form source. 1249 * @return A builder to continue building the NameForm. 1250 */ 1251 public NameForm.Builder buildNameForm(final NameForm nameForm) { 1252 lazyInitBuilder(); 1253 return new NameForm.Builder(nameForm, this); 1254 } 1255 1256 /** 1257 * Returns an object class builder whose fields are initialized to the 1258 * values of the provided object class. This method should be used when 1259 * duplicating object classes from external schemas or when modifying 1260 * existing object classes. 1261 * 1262 * @param objectClass 1263 * The object class source. 1264 * @return A builder to continue building the ObjectClass. 1265 */ 1266 public ObjectClass.Builder buildObjectClass(final ObjectClass objectClass) { 1267 lazyInitBuilder(); 1268 return new ObjectClass.Builder(objectClass, this); 1269 } 1270 1271 /** 1272 * Returns a syntax builder whose fields are initialized to the 1273 * values of the provided syntax. This method should be used when 1274 * duplicating syntaxes from external schemas or when modifying 1275 * existing syntaxes. 1276 * 1277 * @param syntax 1278 * The syntax source. 1279 * @return A builder to continue building the Syntax. 1280 */ 1281 public Syntax.Builder buildSyntax(final Syntax syntax) { 1282 return buildSyntax(syntax, true); 1283 } 1284 1285 private Syntax.Builder buildSyntax(final Syntax syntax, final boolean initialize) { 1286 if (initialize) { 1287 lazyInitBuilder(); 1288 } 1289 return new Syntax.Builder(syntax, this); 1290 } 1291 1292 /** 1293 * Adds the provided object class definition to this schema builder. 1294 * 1295 * @param definition 1296 * The object class definition. 1297 * @param overwrite 1298 * {@code true} if any existing object class with the same OID 1299 * should be overwritten. 1300 * @return A reference to this schema builder. 1301 * @throws ConflictingSchemaElementException 1302 * If {@code overwrite} was {@code false} and a conflicting 1303 * schema element was found. 1304 * @throws LocalizedIllegalArgumentException 1305 * If the provided object class definition could not be parsed. 1306 * @throws NullPointerException 1307 * If {@code definition} was {@code null}. 1308 */ 1309 public SchemaBuilder addObjectClass(final String definition, final boolean overwrite) { 1310 Reject.ifNull(definition); 1311 1312 lazyInitBuilder(); 1313 1314 try { 1315 final SubstringReader reader = new SubstringReader(definition); 1316 1317 // We'll do this a character at a time. First, skip over any 1318 // leading whitespace. 1319 reader.skipWhitespaces(); 1320 1321 if (reader.remaining() <= 0) { 1322 // This means that the value was empty or contained only 1323 // whitespace. That is illegal. 1324 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_OBJECTCLASS_EMPTY_VALUE1.get(definition)); 1325 } 1326 1327 // The next character must be an open parenthesis. If it is not, 1328 // then that is an error. 1329 final char c = reader.read(); 1330 if (c != '(') { 1331 final LocalizableMessage message = ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS1.get( 1332 definition, reader.pos() - 1, String.valueOf(c)); 1333 throw new LocalizedIllegalArgumentException(message); 1334 } 1335 1336 // Skip over any spaces immediately following the opening 1337 // parenthesis. 1338 reader.skipWhitespaces(); 1339 1340 // The next set of characters is the OID. 1341 final String oid = readOID(reader, allowsMalformedNamesAndOptions()); 1342 Set<String> superiorClasses = emptySet(); 1343 ObjectClassType ocType = null; 1344 ObjectClass.Builder ocBuilder = new ObjectClass.Builder(oid, this).definition(definition); 1345 1346 // At this point, we should have a pretty specific syntax that 1347 // describes what may come next, but some of the components are 1348 // optional and it would be pretty easy to put something in the 1349 // wrong order, so we will be very flexible about what we can 1350 // accept. Just look at the next token, figure out what it is and 1351 // how to treat what comes after it, then repeat until we get to 1352 // the end of the value. But before we start, set default values 1353 // for everything else we might need to know. 1354 while (true) { 1355 final String tokenName = readTokenName(reader); 1356 1357 if (tokenName == null) { 1358 // No more tokens. 1359 break; 1360 } else if ("name".equalsIgnoreCase(tokenName)) { 1361 ocBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions())); 1362 } else if ("desc".equalsIgnoreCase(tokenName)) { 1363 // This specifies the description for the attribute type. It 1364 // is an arbitrary string of characters enclosed in single 1365 // quotes. 1366 ocBuilder.description(readQuotedString(reader)); 1367 } else if ("obsolete".equalsIgnoreCase(tokenName)) { 1368 // This indicates whether the attribute type should be 1369 // considered obsolete. 1370 ocBuilder.obsolete(true); 1371 } else if ("sup".equalsIgnoreCase(tokenName)) { 1372 superiorClasses = readOIDs(reader, allowsMalformedNamesAndOptions()); 1373 } else if ("abstract".equalsIgnoreCase(tokenName)) { 1374 // This indicates that entries must not include this 1375 // objectclass unless they also include a non-abstract 1376 // objectclass that inherits from this class. 1377 ocType = ABSTRACT; 1378 } else if ("structural".equalsIgnoreCase(tokenName)) { 1379 ocType = STRUCTURAL; 1380 } else if ("auxiliary".equalsIgnoreCase(tokenName)) { 1381 ocType = AUXILIARY; 1382 } else if ("must".equalsIgnoreCase(tokenName)) { 1383 ocBuilder.requiredAttributes(readOIDs(reader, allowsMalformedNamesAndOptions())); 1384 } else if ("may".equalsIgnoreCase(tokenName)) { 1385 ocBuilder.optionalAttributes(readOIDs(reader, allowsMalformedNamesAndOptions())); 1386 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { 1387 // This must be a non-standard property and it must be 1388 // followed by either a single definition in single quotes 1389 // or an open parenthesis followed by one or more values in 1390 // single quotes separated by spaces followed by a close 1391 // parenthesis. 1392 final List<String> extensions = readExtensions(reader); 1393 ocBuilder.extraProperties(tokenName, extensions.toArray(new String[extensions.size()])); 1394 } else { 1395 throw new LocalizedIllegalArgumentException( 1396 ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_TOKEN1.get(definition, tokenName)); 1397 } 1398 } 1399 1400 if (EXTENSIBLE_OBJECT_OBJECTCLASS_OID.equals(oid)) { 1401 addObjectClass(newExtensibleObjectObjectClass( 1402 ocBuilder.getDescription(), ocBuilder.getExtraProperties(), this), overwrite); 1403 return this; 1404 } else { 1405 if (ocType == STRUCTURAL && superiorClasses.isEmpty()) { 1406 superiorClasses = singleton(TOP_OBJECTCLASS_NAME); 1407 } 1408 ocBuilder.superiorObjectClasses(superiorClasses) 1409 .type(ocType); 1410 return overwrite ? ocBuilder.addToSchemaOverwrite() : ocBuilder.addToSchema(); 1411 } 1412 } catch (final DecodeException e) { 1413 throw new LocalizedIllegalArgumentException( 1414 ERR_ATTR_SYNTAX_OBJECTCLASS_INVALID1.get(definition, e.getMessageObject()), e.getCause()); 1415 } 1416 } 1417 1418 /** 1419 * Adds the provided pattern syntax definition to this schema builder. 1420 * 1421 * @param oid 1422 * The OID of the pattern syntax definition. 1423 * @param description 1424 * The description of the pattern syntax definition. 1425 * @param pattern 1426 * The regular expression pattern which attribute values must 1427 * match in order to be valid. 1428 * @param overwrite 1429 * {@code true} if any existing syntax with the same OID should 1430 * be overwritten. 1431 * @return A reference to this schema builder. 1432 * @throws ConflictingSchemaElementException 1433 * If {@code overwrite} was {@code false} and a conflicting 1434 * schema element was found. 1435 */ 1436 public SchemaBuilder addPatternSyntax(final String oid, final String description, 1437 final Pattern pattern, final boolean overwrite) { 1438 Reject.ifNull(pattern); 1439 1440 lazyInitBuilder(); 1441 1442 final Syntax.Builder syntaxBuilder = buildSyntax(oid).description(description).extraProperties( 1443 Collections.singletonMap("X-PATTERN", Collections.singletonList(pattern.toString()))); 1444 1445 syntaxBuilder.addToSchema(overwrite); 1446 1447 return this; 1448 } 1449 1450 /** 1451 * Reads the schema elements contained in the named subschema sub-entry and 1452 * adds them to this schema builder. 1453 * <p> 1454 * If the requested schema is not returned by the Directory Server then the 1455 * request will fail with an {@link EntryNotFoundException}. 1456 * 1457 * @param connection 1458 * A connection to the Directory Server whose schema is to be 1459 * read. 1460 * @param name 1461 * The distinguished name of the subschema sub-entry. 1462 * @param overwrite 1463 * {@code true} if existing schema elements with the same 1464 * conflicting OIDs should be overwritten. 1465 * @return A reference to this schema builder. 1466 * @throws LdapException 1467 * If the result code indicates that the request failed for some 1468 * reason. 1469 * @throws UnsupportedOperationException 1470 * If the connection does not support search operations. 1471 * @throws IllegalStateException 1472 * If the connection has already been closed, i.e. if 1473 * {@code isClosed() == true}. 1474 * @throws NullPointerException 1475 * If the {@code connection} or {@code name} was {@code null}. 1476 */ 1477 public SchemaBuilder addSchema(final Connection connection, final DN name, 1478 final boolean overwrite) throws LdapException { 1479 // The call to addSchema will perform copyOnWrite. 1480 final SearchRequest request = getReadSchemaSearchRequest(name); 1481 final Entry entry = connection.searchSingleEntry(request); 1482 return addSchema(entry, overwrite); 1483 } 1484 1485 /** 1486 * Adds all of the schema elements contained in the provided subschema 1487 * subentry to this schema builder. Any problems encountered while parsing 1488 * the entry can be retrieved using the returned schema's 1489 * {@link Schema#getWarnings()} method. 1490 * 1491 * @param entry 1492 * The subschema subentry to be parsed. 1493 * @param overwrite 1494 * {@code true} if existing schema elements with the same 1495 * conflicting OIDs should be overwritten. 1496 * @return A reference to this schema builder. 1497 * @throws NullPointerException 1498 * If {@code entry} was {@code null}. 1499 */ 1500 public SchemaBuilder addSchema(final Entry entry, final boolean overwrite) { 1501 Reject.ifNull(entry); 1502 1503 lazyInitBuilder(); 1504 1505 Attribute attr = entry.getAttribute(Schema.ATTR_LDAP_SYNTAXES); 1506 if (attr != null) { 1507 for (final ByteString def : attr) { 1508 try { 1509 addSyntax(def.toString(), overwrite); 1510 } catch (final LocalizedIllegalArgumentException e) { 1511 warnings.add(e.getMessageObject()); 1512 } 1513 } 1514 } 1515 1516 attr = entry.getAttribute(Schema.ATTR_ATTRIBUTE_TYPES); 1517 if (attr != null) { 1518 for (final ByteString def : attr) { 1519 try { 1520 addAttributeType(def.toString(), overwrite); 1521 } catch (final LocalizedIllegalArgumentException e) { 1522 warnings.add(e.getMessageObject()); 1523 } 1524 } 1525 } 1526 1527 attr = entry.getAttribute(Schema.ATTR_OBJECT_CLASSES); 1528 if (attr != null) { 1529 for (final ByteString def : attr) { 1530 try { 1531 addObjectClass(def.toString(), overwrite); 1532 } catch (final LocalizedIllegalArgumentException e) { 1533 warnings.add(e.getMessageObject()); 1534 } 1535 } 1536 } 1537 1538 attr = entry.getAttribute(Schema.ATTR_MATCHING_RULE_USE); 1539 if (attr != null) { 1540 for (final ByteString def : attr) { 1541 try { 1542 addMatchingRuleUse(def.toString(), overwrite); 1543 } catch (final LocalizedIllegalArgumentException e) { 1544 warnings.add(e.getMessageObject()); 1545 } 1546 } 1547 } 1548 1549 attr = entry.getAttribute(Schema.ATTR_MATCHING_RULES); 1550 if (attr != null) { 1551 for (final ByteString def : attr) { 1552 try { 1553 addMatchingRule(def.toString(), overwrite); 1554 } catch (final LocalizedIllegalArgumentException e) { 1555 warnings.add(e.getMessageObject()); 1556 } 1557 } 1558 } 1559 1560 attr = entry.getAttribute(Schema.ATTR_DIT_CONTENT_RULES); 1561 if (attr != null) { 1562 for (final ByteString def : attr) { 1563 try { 1564 addDITContentRule(def.toString(), overwrite); 1565 } catch (final LocalizedIllegalArgumentException e) { 1566 warnings.add(e.getMessageObject()); 1567 } 1568 } 1569 } 1570 1571 attr = entry.getAttribute(Schema.ATTR_DIT_STRUCTURE_RULES); 1572 if (attr != null) { 1573 for (final ByteString def : attr) { 1574 try { 1575 addDITStructureRule(def.toString(), overwrite); 1576 } catch (final LocalizedIllegalArgumentException e) { 1577 warnings.add(e.getMessageObject()); 1578 } 1579 } 1580 } 1581 1582 attr = entry.getAttribute(Schema.ATTR_NAME_FORMS); 1583 if (attr != null) { 1584 for (final ByteString def : attr) { 1585 try { 1586 addNameForm(def.toString(), overwrite); 1587 } catch (final LocalizedIllegalArgumentException e) { 1588 warnings.add(e.getMessageObject()); 1589 } 1590 } 1591 } 1592 1593 return this; 1594 } 1595 1596 /** 1597 * Adds all of the schema elements in the provided schema to this schema 1598 * builder. 1599 * 1600 * @param schema 1601 * The schema to be copied into this schema builder. 1602 * @param overwrite 1603 * {@code true} if existing schema elements with the same 1604 * conflicting OIDs should be overwritten. 1605 * @return A reference to this schema builder. 1606 * @throws ConflictingSchemaElementException 1607 * If {@code overwrite} was {@code false} and conflicting schema 1608 * elements were found. 1609 * @throws NullPointerException 1610 * If {@code schema} was {@code null}. 1611 */ 1612 public SchemaBuilder addSchema(final Schema schema, final boolean overwrite) { 1613 Reject.ifNull(schema); 1614 1615 lazyInitBuilder(); 1616 1617 addSchema0(schema, overwrite); 1618 return this; 1619 } 1620 1621 /** 1622 * Asynchronously reads the schema elements contained in the named subschema 1623 * sub-entry and adds them to this schema builder. 1624 * <p> 1625 * If the requested schema is not returned by the Directory Server then the 1626 * request will fail with an {@link EntryNotFoundException}. 1627 * 1628 * @param connection 1629 * A connection to the Directory Server whose schema is to be 1630 * read. 1631 * @param name 1632 * The distinguished name of the subschema sub-entry. 1633 * the operation result when it is received, may be {@code null}. 1634 * @param overwrite 1635 * {@code true} if existing schema elements with the same 1636 * conflicting OIDs should be overwritten. 1637 * @return A promise representing the updated schema builder. 1638 * @throws UnsupportedOperationException 1639 * If the connection does not support search operations. 1640 * @throws IllegalStateException 1641 * If the connection has already been closed, i.e. if 1642 * {@code connection.isClosed() == true}. 1643 * @throws NullPointerException 1644 * If the {@code connection} or {@code name} was {@code null}. 1645 */ 1646 public LdapPromise<SchemaBuilder> addSchemaAsync(final Connection connection, final DN name, 1647 final boolean overwrite) { 1648 // The call to addSchema will perform copyOnWrite. 1649 return connection.searchSingleEntryAsync(getReadSchemaSearchRequest(name)).then( 1650 new Function<SearchResultEntry, SchemaBuilder, LdapException>() { 1651 @Override 1652 public SchemaBuilder apply(SearchResultEntry result) throws LdapException { 1653 addSchema(result, overwrite); 1654 return SchemaBuilder.this; 1655 } 1656 }); 1657 } 1658 1659 /** 1660 * Reads the schema elements contained in the subschema sub-entry which 1661 * applies to the named entry and adds them to this schema builder. 1662 * <p> 1663 * If the requested entry or its associated schema are not returned by the 1664 * Directory Server then the request will fail with an 1665 * {@link EntryNotFoundException}. 1666 * <p> 1667 * This implementation first reads the {@code subschemaSubentry} attribute 1668 * of the entry in order to identify the schema and then invokes 1669 * {@link #addSchemaForEntry(Connection, DN, boolean)} to read the schema. 1670 * 1671 * @param connection 1672 * A connection to the Directory Server whose schema is to be 1673 * read. 1674 * @param name 1675 * The distinguished name of the entry whose schema is to be 1676 * located. 1677 * @param overwrite 1678 * {@code true} if existing schema elements with the same 1679 * conflicting OIDs should be overwritten. 1680 * @return A reference to this schema builder. 1681 * @throws LdapException 1682 * If the result code indicates that the request failed for some 1683 * reason. 1684 * @throws UnsupportedOperationException 1685 * If the connection does not support search operations. 1686 * @throws IllegalStateException 1687 * If the connection has already been closed, i.e. if 1688 * {@code connection.isClosed() == true}. 1689 * @throws NullPointerException 1690 * If the {@code connection} or {@code name} was {@code null}. 1691 */ 1692 public SchemaBuilder addSchemaForEntry(final Connection connection, final DN name, 1693 final boolean overwrite) throws LdapException { 1694 // The call to addSchema will perform copyOnWrite. 1695 final SearchRequest request = getReadSchemaForEntrySearchRequest(name); 1696 final Entry entry = connection.searchSingleEntry(request); 1697 final DN subschemaDN = getSubschemaSubentryDN(name, entry); 1698 return addSchema(connection, subschemaDN, overwrite); 1699 } 1700 1701 /** 1702 * Asynchronously reads the schema elements contained in the subschema 1703 * sub-entry which applies to the named entry and adds them to this schema 1704 * builder. 1705 * <p> 1706 * If the requested entry or its associated schema are not returned by the 1707 * Directory Server then the request will fail with an 1708 * {@link EntryNotFoundException}. 1709 * <p> 1710 * This implementation first reads the {@code subschemaSubentry} attribute 1711 * of the entry in order to identify the schema and then invokes 1712 * {@link #addSchemaAsync(Connection, DN, boolean)} to read the schema. 1713 * 1714 * @param connection 1715 * A connection to the Directory Server whose schema is to be 1716 * read. 1717 * @param name 1718 * The distinguished name of the entry whose schema is to be 1719 * located. 1720 * @param overwrite 1721 * {@code true} if existing schema elements with the same 1722 * conflicting OIDs should be overwritten. 1723 * @return A promise representing the updated schema builder. 1724 * @throws UnsupportedOperationException 1725 * If the connection does not support search operations. 1726 * @throws IllegalStateException 1727 * If the connection has already been closed, i.e. if 1728 * {@code connection.isClosed() == true}. 1729 * @throws NullPointerException 1730 * If the {@code connection} or {@code name} was {@code null}. 1731 */ 1732 public LdapPromise<SchemaBuilder> addSchemaForEntryAsync(final Connection connection, final DN name, 1733 final boolean overwrite) { 1734 return connection.searchSingleEntryAsync(getReadSchemaForEntrySearchRequest(name)).thenAsync( 1735 new AsyncFunction<SearchResultEntry, SchemaBuilder, LdapException>() { 1736 @Override 1737 public Promise<SchemaBuilder, LdapException> apply(SearchResultEntry result) throws LdapException { 1738 final DN subschemaDN = getSubschemaSubentryDN(name, result); 1739 return addSchemaAsync(connection, subschemaDN, overwrite); 1740 } 1741 }); 1742 } 1743 1744 /** 1745 * Adds the provided substitution syntax definition to this schema builder. 1746 * 1747 * @param oid 1748 * The OID of the substitution syntax definition. 1749 * @param description 1750 * The description of the substitution syntax definition. 1751 * @param substituteSyntax 1752 * The OID of the syntax whose implementation should be 1753 * substituted. 1754 * @param overwrite 1755 * {@code true} if any existing syntax with the same OID should 1756 * be overwritten. 1757 * @return A reference to this schema builder. 1758 * @throws ConflictingSchemaElementException 1759 * If {@code overwrite} was {@code false} and a conflicting 1760 * schema element was found. 1761 */ 1762 public SchemaBuilder addSubstitutionSyntax(final String oid, final String description, 1763 final String substituteSyntax, final boolean overwrite) { 1764 Reject.ifNull(substituteSyntax); 1765 1766 lazyInitBuilder(); 1767 1768 final Syntax.Builder syntaxBuilder = buildSyntax(oid).description(description).extraProperties( 1769 Collections.singletonMap("X-SUBST", Collections.singletonList(substituteSyntax))); 1770 1771 syntaxBuilder.addToSchema(overwrite); 1772 1773 return this; 1774 } 1775 1776 /** 1777 * Adds the provided syntax definition to this schema builder. 1778 * 1779 * @param definition 1780 * The syntax definition. 1781 * @param overwrite 1782 * {@code true} if any existing syntax with the same OID should 1783 * be overwritten. 1784 * @return A reference to this schema builder. 1785 * @throws ConflictingSchemaElementException 1786 * If {@code overwrite} was {@code false} and a conflicting 1787 * schema element was found. 1788 * @throws LocalizedIllegalArgumentException 1789 * If the provided syntax definition could not be parsed. 1790 * @throws NullPointerException 1791 * If {@code definition} was {@code null}. 1792 */ 1793 public SchemaBuilder addSyntax(final String definition, final boolean overwrite) { 1794 Reject.ifNull(definition); 1795 1796 lazyInitBuilder(); 1797 1798 try { 1799 final SubstringReader reader = new SubstringReader(definition); 1800 1801 // We'll do this a character at a time. First, skip over any 1802 // leading whitespace. 1803 reader.skipWhitespaces(); 1804 1805 if (reader.remaining() <= 0) { 1806 // This means that the value was empty or contained only 1807 // whitespace. That is illegal. 1808 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_ATTRSYNTAX_EMPTY_VALUE1.get(definition)); 1809 } 1810 1811 // The next character must be an open parenthesis. If it is not, 1812 // then that is an error. 1813 final char c = reader.read(); 1814 if (c != '(') { 1815 final LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get( 1816 definition, reader.pos() - 1, String.valueOf(c)); 1817 throw new LocalizedIllegalArgumentException(message); 1818 } 1819 1820 // Skip over any spaces immediately following the opening 1821 // parenthesis. 1822 reader.skipWhitespaces(); 1823 1824 // The next set of characters must be the OID. 1825 final String oid = readOID(reader, allowsMalformedNamesAndOptions()); 1826 final Syntax.Builder syntaxBuilder = new Syntax.Builder(oid, this).definition(definition); 1827 1828 // At this point, we should have a pretty specific syntax that 1829 // describes what may come next, but some of the components are 1830 // optional and it would be pretty easy to put something in the 1831 // wrong order, so we will be very flexible about what we can 1832 // accept. Just look at the next token, figure out what it is and 1833 // how to treat what comes after it, then repeat until we get to 1834 // the end of the value. But before we start, set default values 1835 // for everything else we might need to know. 1836 while (true) { 1837 final String tokenName = readTokenName(reader); 1838 1839 if (tokenName == null) { 1840 // No more tokens. 1841 break; 1842 } else if ("desc".equalsIgnoreCase(tokenName)) { 1843 // This specifies the description for the syntax. It is an 1844 // arbitrary string of characters enclosed in single quotes. 1845 syntaxBuilder.description(readQuotedString(reader)); 1846 } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { 1847 // This must be a non-standard property and it must be 1848 // followed by either a single definition in single quotes 1849 // or an open parenthesis followed by one or more values in 1850 // single quotes separated by spaces followed by a close 1851 // parenthesis. 1852 final List<String> extensions = readExtensions(reader); 1853 syntaxBuilder.extraProperties(tokenName, extensions.toArray(new String[extensions.size()])); 1854 } else { 1855 throw new LocalizedIllegalArgumentException( 1856 ERR_ATTR_SYNTAX_ATTRSYNTAX_ILLEGAL_TOKEN1.get(definition, tokenName)); 1857 } 1858 } 1859 1860 // See if it is a enum syntax 1861 for (final Map.Entry<String, List<String>> property : syntaxBuilder.getExtraProperties().entrySet()) { 1862 if ("x-enum".equalsIgnoreCase(property.getKey())) { 1863 final EnumSyntaxImpl enumImpl = new EnumSyntaxImpl(oid, property.getValue()); 1864 syntaxBuilder.implementation(enumImpl); 1865 syntaxBuilder.addToSchema(overwrite); 1866 1867 buildMatchingRule(enumImpl.getOrderingMatchingRule()) 1868 .names(OMR_GENERIC_ENUM_NAME + oid) 1869 .syntaxOID(oid) 1870 .extraProperties(CoreSchemaImpl.OPENDS_ORIGIN) 1871 .implementation(new EnumOrderingMatchingRule(enumImpl)) 1872 .addToSchemaOverwrite(); 1873 return this; 1874 } 1875 } 1876 1877 syntaxBuilder.addToSchema(overwrite); 1878 } catch (final DecodeException e) { 1879 final LocalizableMessage msg = 1880 ERR_ATTR_SYNTAX_ATTRSYNTAX_INVALID1.get(definition, e.getMessageObject()); 1881 throw new LocalizedIllegalArgumentException(msg, e.getCause()); 1882 } 1883 return this; 1884 } 1885 1886 SchemaOptions getOptions() { 1887 lazyInitBuilder(); 1888 1889 return options; 1890 } 1891 1892 /** 1893 * Removes the named attribute type from this schema builder. 1894 * 1895 * @param name 1896 * The name or OID of the attribute type to be removed. 1897 * @return {@code true} if the attribute type was found. 1898 */ 1899 public boolean removeAttributeType(final String name) { 1900 lazyInitBuilder(); 1901 1902 final AttributeType element = numericOID2AttributeTypes.get(name); 1903 if (element != null) { 1904 removeAttributeType(element); 1905 return true; 1906 } 1907 final List<AttributeType> elements = name2AttributeTypes.get(toLowerCase(name)); 1908 if (elements != null) { 1909 for (final AttributeType e : elements) { 1910 removeAttributeType(e); 1911 } 1912 return true; 1913 } 1914 return false; 1915 } 1916 1917 /** 1918 * Removes the named DIT content rule from this schema builder. 1919 * 1920 * @param name 1921 * The name or OID of the DIT content rule to be removed. 1922 * @return {@code true} if the DIT content rule was found. 1923 */ 1924 public boolean removeDITContentRule(final String name) { 1925 lazyInitBuilder(); 1926 1927 final DITContentRule element = numericOID2ContentRules.get(name); 1928 if (element != null) { 1929 removeDITContentRule(element); 1930 return true; 1931 } 1932 final List<DITContentRule> elements = name2ContentRules.get(toLowerCase(name)); 1933 if (elements != null) { 1934 for (final DITContentRule e : elements) { 1935 removeDITContentRule(e); 1936 } 1937 return true; 1938 } 1939 return false; 1940 } 1941 1942 /** 1943 * Removes the specified DIT structure rule from this schema builder. 1944 * 1945 * @param ruleID 1946 * The ID of the DIT structure rule to be removed. 1947 * @return {@code true} if the DIT structure rule was found. 1948 */ 1949 public boolean removeDITStructureRule(final int ruleID) { 1950 lazyInitBuilder(); 1951 1952 final DITStructureRule element = id2StructureRules.get(ruleID); 1953 if (element != null) { 1954 removeDITStructureRule(element); 1955 return true; 1956 } 1957 return false; 1958 } 1959 1960 /** 1961 * Removes the named matching rule from this schema builder. 1962 * 1963 * @param name 1964 * The name or OID of the matching rule to be removed. 1965 * @return {@code true} if the matching rule was found. 1966 */ 1967 public boolean removeMatchingRule(final String name) { 1968 lazyInitBuilder(); 1969 1970 final MatchingRule element = numericOID2MatchingRules.get(name); 1971 if (element != null) { 1972 removeMatchingRule(element); 1973 return true; 1974 } 1975 final List<MatchingRule> elements = name2MatchingRules.get(toLowerCase(name)); 1976 if (elements != null) { 1977 for (final MatchingRule e : elements) { 1978 removeMatchingRule(e); 1979 } 1980 return true; 1981 } 1982 return false; 1983 } 1984 1985 /** 1986 * Removes the named matching rule use from this schema builder. 1987 * 1988 * @param name 1989 * The name or OID of the matching rule use to be removed. 1990 * @return {@code true} if the matching rule use was found. 1991 */ 1992 public boolean removeMatchingRuleUse(final String name) { 1993 lazyInitBuilder(); 1994 1995 final MatchingRuleUse element = numericOID2MatchingRuleUses.get(name); 1996 if (element != null) { 1997 removeMatchingRuleUse(element); 1998 return true; 1999 } 2000 final List<MatchingRuleUse> elements = name2MatchingRuleUses.get(toLowerCase(name)); 2001 if (elements != null) { 2002 for (final MatchingRuleUse e : elements) { 2003 removeMatchingRuleUse(e); 2004 } 2005 return true; 2006 } 2007 return false; 2008 } 2009 2010 /** 2011 * Removes the named name form from this schema builder. 2012 * 2013 * @param name 2014 * The name or OID of the name form to be removed. 2015 * @return {@code true} if the name form was found. 2016 */ 2017 public boolean removeNameForm(final String name) { 2018 lazyInitBuilder(); 2019 2020 final NameForm element = numericOID2NameForms.get(name); 2021 if (element != null) { 2022 removeNameForm(element); 2023 return true; 2024 } 2025 final List<NameForm> elements = name2NameForms.get(toLowerCase(name)); 2026 if (elements != null) { 2027 for (final NameForm e : elements) { 2028 removeNameForm(e); 2029 } 2030 return true; 2031 } 2032 return false; 2033 } 2034 2035 /** 2036 * Removes the named object class from this schema builder. 2037 * 2038 * @param name 2039 * The name or OID of the object class to be removed. 2040 * @return {@code true} if the object class was found. 2041 */ 2042 public boolean removeObjectClass(final String name) { 2043 lazyInitBuilder(); 2044 2045 final ObjectClass element = numericOID2ObjectClasses.get(name); 2046 if (element != null) { 2047 removeObjectClass(element); 2048 return true; 2049 } 2050 final List<ObjectClass> elements = name2ObjectClasses.get(toLowerCase(name)); 2051 if (elements != null) { 2052 for (final ObjectClass e : elements) { 2053 removeObjectClass(e); 2054 } 2055 return true; 2056 } 2057 return false; 2058 } 2059 2060 /** 2061 * Removes the named syntax from this schema builder. 2062 * 2063 * @param numericOID 2064 * The name of the syntax to be removed. 2065 * @return {@code true} if the syntax was found. 2066 */ 2067 public boolean removeSyntax(final String numericOID) { 2068 lazyInitBuilder(); 2069 2070 final Syntax element = numericOID2Syntaxes.get(numericOID); 2071 if (element != null) { 2072 removeSyntax(element); 2073 return true; 2074 } 2075 return false; 2076 } 2077 2078 /** 2079 * Sets a schema option overriding any previous values for the option. 2080 * 2081 * @param <T> 2082 * The option type. 2083 * @param option 2084 * Option with which the specified value is to be associated. 2085 * @param value 2086 * Value to be associated with the specified option. 2087 * @return A reference to this schema builder. 2088 * @throws UnsupportedOperationException 2089 * If the schema builder options are read only. 2090 */ 2091 public <T> SchemaBuilder setOption(final Option<T> option, T value) { 2092 getOptions().set(option, value); 2093 return this; 2094 } 2095 2096 /** 2097 * Returns a strict {@code Schema} containing all of the schema elements 2098 * contained in this schema builder as well as the same set of schema 2099 * compatibility options. 2100 * <p> 2101 * This method does not alter the contents of this schema builder. 2102 * 2103 * @return A {@code Schema} containing all of the schema elements contained 2104 * in this schema builder as well as the same set of schema 2105 * compatibility options 2106 */ 2107 public Schema toSchema() { 2108 // If this schema builder was initialized from another schema and no 2109 // modifications have been made since then we can simply return the 2110 // original schema. 2111 if (copyOnWriteSchema != null) { 2112 return copyOnWriteSchema; 2113 } 2114 2115 // We still need to ensure that this builder has been initialized 2116 // (otherwise some fields may still be null). 2117 lazyInitBuilder(); 2118 2119 final String localSchemaName; 2120 if (schemaName != null) { 2121 localSchemaName = schemaName; 2122 } else { 2123 localSchemaName = String.format("Schema#%d", NEXT_SCHEMA_ID.getAndIncrement()); 2124 } 2125 2126 Syntax defaultSyntax = numericOID2Syntaxes.get(options.get(DEFAULT_SYNTAX_OID)); 2127 if (defaultSyntax == null) { 2128 defaultSyntax = Schema.getCoreSchema().getDefaultSyntax(); 2129 } 2130 2131 MatchingRule defaultMatchingRule = numericOID2MatchingRules.get(options.get(DEFAULT_MATCHING_RULE_OID)); 2132 if (defaultMatchingRule == null) { 2133 defaultMatchingRule = Schema.getCoreSchema().getDefaultMatchingRule(); 2134 } 2135 2136 final Schema schema = 2137 new Schema.StrictImpl(localSchemaName, options, 2138 defaultSyntax, defaultMatchingRule, numericOID2Syntaxes, 2139 numericOID2MatchingRules, numericOID2MatchingRuleUses, 2140 numericOID2AttributeTypes, numericOID2ObjectClasses, numericOID2NameForms, 2141 numericOID2ContentRules, id2StructureRules, name2MatchingRules, 2142 name2MatchingRuleUses, name2AttributeTypes, name2ObjectClasses, 2143 name2NameForms, name2ContentRules, name2StructureRules, 2144 objectClass2NameForms, nameForm2StructureRules, name2OIDs, warnings).asStrictSchema(); 2145 validate(schema); 2146 2147 // Re-init this builder so that it can continue to be used afterwards. 2148 preLazyInitBuilder(schemaName, schema); 2149 2150 return schema; 2151 } 2152 2153 private void registerNameToOIDMapping(String name, String anOID) { 2154 if (name2OIDs.put(name, anOID) != null) { 2155 name2OIDs.put(name, AMBIGUOUS_OID); 2156 } 2157 } 2158 2159 SchemaBuilder addAttributeType(final AttributeType attribute, final boolean overwrite) { 2160 AttributeType conflictingAttribute; 2161 if (numericOID2AttributeTypes.containsKey(attribute.getOID())) { 2162 conflictingAttribute = numericOID2AttributeTypes.get(attribute.getOID()); 2163 if (!overwrite) { 2164 final LocalizableMessage message = 2165 ERR_SCHEMA_CONFLICTING_ATTRIBUTE_OID.get(attribute.getNameOrOID(), 2166 attribute.getOID(), conflictingAttribute.getNameOrOID()); 2167 throw new ConflictingSchemaElementException(message); 2168 } 2169 removeAttributeType(conflictingAttribute); 2170 } 2171 2172 numericOID2AttributeTypes.put(attribute.getOID(), attribute); 2173 for (final String name : attribute.getNames()) { 2174 final String lowerName = StaticUtils.toLowerCase(name); 2175 List<AttributeType> attrs = name2AttributeTypes.get(lowerName); 2176 if (attrs == null) { 2177 name2AttributeTypes.put(lowerName, Collections.singletonList(attribute)); 2178 } else if (attrs.size() == 1) { 2179 attrs = new ArrayList<>(attrs); 2180 attrs.add(attribute); 2181 name2AttributeTypes.put(lowerName, attrs); 2182 } else { 2183 attrs.add(attribute); 2184 } 2185 } 2186 2187 return this; 2188 } 2189 2190 SchemaBuilder addDITContentRule(final DITContentRule rule, final boolean overwrite) { 2191 DITContentRule conflictingRule; 2192 if (numericOID2ContentRules.containsKey(rule.getStructuralClassOID())) { 2193 conflictingRule = numericOID2ContentRules.get(rule.getStructuralClassOID()); 2194 if (!overwrite) { 2195 final LocalizableMessage message = 2196 ERR_SCHEMA_CONFLICTING_DIT_CONTENT_RULE1.get(rule.getNameOrOID(), rule 2197 .getStructuralClassOID(), conflictingRule.getNameOrOID()); 2198 throw new ConflictingSchemaElementException(message); 2199 } 2200 removeDITContentRule(conflictingRule); 2201 } 2202 2203 numericOID2ContentRules.put(rule.getStructuralClassOID(), rule); 2204 for (final String name : rule.getNames()) { 2205 final String lowerName = StaticUtils.toLowerCase(name); 2206 List<DITContentRule> rules = name2ContentRules.get(lowerName); 2207 if (rules == null) { 2208 name2ContentRules.put(lowerName, Collections.singletonList(rule)); 2209 } else if (rules.size() == 1) { 2210 rules = new ArrayList<>(rules); 2211 rules.add(rule); 2212 name2ContentRules.put(lowerName, rules); 2213 } else { 2214 rules.add(rule); 2215 } 2216 } 2217 2218 return this; 2219 } 2220 2221 SchemaBuilder addDITStructureRule(final DITStructureRule rule, final boolean overwrite) { 2222 DITStructureRule conflictingRule; 2223 if (id2StructureRules.containsKey(rule.getRuleID())) { 2224 conflictingRule = id2StructureRules.get(rule.getRuleID()); 2225 if (!overwrite) { 2226 final LocalizableMessage message = 2227 ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_ID.get(rule.getNameOrRuleID(), 2228 rule.getRuleID(), conflictingRule.getNameOrRuleID()); 2229 throw new ConflictingSchemaElementException(message); 2230 } 2231 removeDITStructureRule(conflictingRule); 2232 } 2233 2234 id2StructureRules.put(rule.getRuleID(), rule); 2235 for (final String name : rule.getNames()) { 2236 final String lowerName = StaticUtils.toLowerCase(name); 2237 List<DITStructureRule> rules = name2StructureRules.get(lowerName); 2238 if (rules == null) { 2239 name2StructureRules.put(lowerName, Collections.singletonList(rule)); 2240 } else if (rules.size() == 1) { 2241 rules = new ArrayList<>(rules); 2242 rules.add(rule); 2243 name2StructureRules.put(lowerName, rules); 2244 } else { 2245 rules.add(rule); 2246 } 2247 } 2248 2249 return this; 2250 } 2251 2252 SchemaBuilder addMatchingRuleUse(final MatchingRuleUse use, final boolean overwrite) { 2253 MatchingRuleUse conflictingUse; 2254 if (numericOID2MatchingRuleUses.containsKey(use.getMatchingRuleOID())) { 2255 conflictingUse = numericOID2MatchingRuleUses.get(use.getMatchingRuleOID()); 2256 if (!overwrite) { 2257 final LocalizableMessage message = 2258 ERR_SCHEMA_CONFLICTING_MATCHING_RULE_USE.get(use.getNameOrOID(), use 2259 .getMatchingRuleOID(), conflictingUse.getNameOrOID()); 2260 throw new ConflictingSchemaElementException(message); 2261 } 2262 removeMatchingRuleUse(conflictingUse); 2263 } 2264 2265 numericOID2MatchingRuleUses.put(use.getMatchingRuleOID(), use); 2266 for (final String name : use.getNames()) { 2267 final String lowerName = StaticUtils.toLowerCase(name); 2268 List<MatchingRuleUse> uses = name2MatchingRuleUses.get(lowerName); 2269 if (uses == null) { 2270 name2MatchingRuleUses.put(lowerName, Collections.singletonList(use)); 2271 } else if (uses.size() == 1) { 2272 uses = new ArrayList<>(uses); 2273 uses.add(use); 2274 name2MatchingRuleUses.put(lowerName, uses); 2275 } else { 2276 uses.add(use); 2277 } 2278 } 2279 2280 return this; 2281 } 2282 2283 SchemaBuilder addMatchingRule(final MatchingRule rule, final boolean overwrite) { 2284 Reject.ifTrue(rule.isValidated(), 2285 "Matching rule has already been validated, it can't be added"); 2286 MatchingRule conflictingRule; 2287 if (numericOID2MatchingRules.containsKey(rule.getOID())) { 2288 conflictingRule = numericOID2MatchingRules.get(rule.getOID()); 2289 if (!overwrite) { 2290 final LocalizableMessage message = 2291 ERR_SCHEMA_CONFLICTING_MR_OID.get(rule.getNameOrOID(), rule.getOID(), 2292 conflictingRule.getNameOrOID()); 2293 throw new ConflictingSchemaElementException(message); 2294 } 2295 removeMatchingRule(conflictingRule); 2296 } 2297 2298 numericOID2MatchingRules.put(rule.getOID(), rule); 2299 for (final String name : rule.getNames()) { 2300 final String lowerName = StaticUtils.toLowerCase(name); 2301 List<MatchingRule> rules = name2MatchingRules.get(lowerName); 2302 if (rules == null) { 2303 name2MatchingRules.put(lowerName, Collections.singletonList(rule)); 2304 } else if (rules.size() == 1) { 2305 rules = new ArrayList<>(rules); 2306 rules.add(rule); 2307 name2MatchingRules.put(lowerName, rules); 2308 } else { 2309 rules.add(rule); 2310 } 2311 } 2312 return this; 2313 } 2314 2315 SchemaBuilder addNameForm(final NameForm form, final boolean overwrite) { 2316 NameForm conflictingForm; 2317 if (numericOID2NameForms.containsKey(form.getOID())) { 2318 conflictingForm = numericOID2NameForms.get(form.getOID()); 2319 if (!overwrite) { 2320 final LocalizableMessage message = 2321 ERR_SCHEMA_CONFLICTING_NAME_FORM_OID.get(form.getNameOrOID(), 2322 form.getOID(), conflictingForm.getNameOrOID()); 2323 throw new ConflictingSchemaElementException(message); 2324 } 2325 removeNameForm(conflictingForm); 2326 } 2327 2328 numericOID2NameForms.put(form.getOID(), form); 2329 for (final String name : form.getNames()) { 2330 final String lowerName = StaticUtils.toLowerCase(name); 2331 List<NameForm> forms = name2NameForms.get(lowerName); 2332 if (forms == null) { 2333 name2NameForms.put(lowerName, Collections.singletonList(form)); 2334 } else if (forms.size() == 1) { 2335 forms = new ArrayList<>(forms); 2336 forms.add(form); 2337 name2NameForms.put(lowerName, forms); 2338 } else { 2339 forms.add(form); 2340 } 2341 } 2342 return this; 2343 } 2344 2345 SchemaBuilder addObjectClass(final ObjectClass oc, final boolean overwrite) { 2346 ObjectClass conflictingOC; 2347 if (numericOID2ObjectClasses.containsKey(oc.getOID())) { 2348 conflictingOC = numericOID2ObjectClasses.get(oc.getOID()); 2349 if (!overwrite) { 2350 final LocalizableMessage message = 2351 ERR_SCHEMA_CONFLICTING_OBJECTCLASS_OID1.get(oc.getNameOrOID(), oc.getOID(), 2352 conflictingOC.getNameOrOID()); 2353 throw new ConflictingSchemaElementException(message); 2354 } 2355 removeObjectClass(conflictingOC); 2356 } 2357 2358 numericOID2ObjectClasses.put(oc.getOID(), oc); 2359 for (final String name : oc.getNames()) { 2360 final String lowerName = StaticUtils.toLowerCase(name); 2361 List<ObjectClass> classes = name2ObjectClasses.get(lowerName); 2362 if (classes == null) { 2363 name2ObjectClasses.put(lowerName, Collections.singletonList(oc)); 2364 } else if (classes.size() == 1) { 2365 classes = new ArrayList<>(classes); 2366 classes.add(oc); 2367 name2ObjectClasses.put(lowerName, classes); 2368 } else { 2369 classes.add(oc); 2370 } 2371 } 2372 2373 return this; 2374 } 2375 2376 private void addSchema0(final Schema schema, final boolean overwrite) { 2377 // All of the schema elements must be duplicated because validation will 2378 // cause them to update all their internal references which, although 2379 // unlikely, may be different in the new schema. 2380 2381 for (final Syntax syntax : schema.getSyntaxes()) { 2382 if (overwrite) { 2383 buildSyntax(syntax, false).addToSchemaOverwrite(); 2384 } else { 2385 buildSyntax(syntax, false).addToSchema(); 2386 } 2387 } 2388 2389 for (final MatchingRule matchingRule : schema.getMatchingRules()) { 2390 if (overwrite) { 2391 buildMatchingRule(matchingRule, false).addToSchemaOverwrite(); 2392 } else { 2393 buildMatchingRule(matchingRule, false).addToSchema(); 2394 } 2395 } 2396 2397 for (final MatchingRuleUse matchingRuleUse : schema.getMatchingRuleUses()) { 2398 addMatchingRuleUse(matchingRuleUse, overwrite); 2399 } 2400 2401 for (final AttributeType attributeType : schema.getAttributeTypes()) { 2402 addAttributeType(attributeType, overwrite); 2403 } 2404 2405 for (final ObjectClass objectClass : schema.getObjectClasses()) { 2406 addObjectClass(objectClass, overwrite); 2407 } 2408 2409 for (final NameForm nameForm : schema.getNameForms()) { 2410 addNameForm(nameForm, overwrite); 2411 } 2412 2413 for (final DITContentRule contentRule : schema.getDITContentRules()) { 2414 addDITContentRule(contentRule, overwrite); 2415 } 2416 2417 for (final DITStructureRule structureRule : schema.getDITStuctureRules()) { 2418 addDITStructureRule(structureRule, overwrite); 2419 } 2420 } 2421 2422 SchemaBuilder addSyntax(final Syntax syntax, final boolean overwrite) { 2423 Reject.ifTrue(syntax.isValidated(), "Syntax has already been validated, it can't be added"); 2424 Syntax conflictingSyntax; 2425 if (numericOID2Syntaxes.containsKey(syntax.getOID())) { 2426 conflictingSyntax = numericOID2Syntaxes.get(syntax.getOID()); 2427 if (!overwrite) { 2428 final LocalizableMessage message = ERR_SCHEMA_CONFLICTING_SYNTAX_OID.get(syntax.toString(), 2429 syntax.getOID(), conflictingSyntax.getOID()); 2430 throw new ConflictingSchemaElementException(message); 2431 } 2432 removeSyntax(conflictingSyntax); 2433 } 2434 2435 numericOID2Syntaxes.put(syntax.getOID(), syntax); 2436 return this; 2437 } 2438 2439 private void lazyInitBuilder() { 2440 // Lazy initialization. 2441 if (numericOID2Syntaxes == null) { 2442 options = defaultSchemaOptions(); 2443 2444 numericOID2Syntaxes = new LinkedHashMap<>(); 2445 numericOID2MatchingRules = new LinkedHashMap<>(); 2446 numericOID2MatchingRuleUses = new LinkedHashMap<>(); 2447 numericOID2AttributeTypes = new LinkedHashMap<>(); 2448 numericOID2ObjectClasses = new LinkedHashMap<>(); 2449 numericOID2NameForms = new LinkedHashMap<>(); 2450 numericOID2ContentRules = new LinkedHashMap<>(); 2451 id2StructureRules = new LinkedHashMap<>(); 2452 2453 name2MatchingRules = new LinkedHashMap<>(); 2454 name2MatchingRuleUses = new LinkedHashMap<>(); 2455 name2AttributeTypes = new LinkedHashMap<>(); 2456 name2ObjectClasses = new LinkedHashMap<>(); 2457 name2NameForms = new LinkedHashMap<>(); 2458 name2ContentRules = new LinkedHashMap<>(); 2459 name2StructureRules = new LinkedHashMap<>(); 2460 2461 objectClass2NameForms = new HashMap<>(); 2462 nameForm2StructureRules = new HashMap<>(); 2463 name2OIDs = new HashMap<>(); 2464 warnings = new LinkedList<>(); 2465 } 2466 2467 if (copyOnWriteSchema != null) { 2468 // Copy the schema. 2469 addSchema0(copyOnWriteSchema, true); 2470 options = SchemaOptions.copyOf(copyOnWriteSchema.getOptions()); 2471 copyOnWriteSchema = null; 2472 } 2473 } 2474 2475 private void preLazyInitBuilder(final String schemaName, final Schema copyOnWriteSchema) { 2476 this.schemaName = schemaName; 2477 this.copyOnWriteSchema = copyOnWriteSchema; 2478 2479 this.options = null; 2480 2481 this.numericOID2Syntaxes = null; 2482 this.numericOID2MatchingRules = null; 2483 this.numericOID2MatchingRuleUses = null; 2484 this.numericOID2AttributeTypes = null; 2485 this.numericOID2ObjectClasses = null; 2486 this.numericOID2NameForms = null; 2487 this.numericOID2ContentRules = null; 2488 this.id2StructureRules = null; 2489 2490 this.name2MatchingRules = null; 2491 this.name2MatchingRuleUses = null; 2492 this.name2AttributeTypes = null; 2493 this.name2ObjectClasses = null; 2494 this.name2NameForms = null; 2495 this.name2ContentRules = null; 2496 this.name2StructureRules = null; 2497 2498 this.objectClass2NameForms = null; 2499 this.nameForm2StructureRules = null; 2500 this.warnings = null; 2501 } 2502 2503 private void removeAttributeType(final AttributeType attributeType) { 2504 numericOID2AttributeTypes.remove(attributeType.getOID()); 2505 for (final String name : attributeType.getNames()) { 2506 final String lowerName = StaticUtils.toLowerCase(name); 2507 final List<AttributeType> attributes = name2AttributeTypes.get(lowerName); 2508 if (attributes != null && attributes.contains(attributeType)) { 2509 if (attributes.size() <= 1) { 2510 name2AttributeTypes.remove(lowerName); 2511 } else { 2512 attributes.remove(attributeType); 2513 } 2514 } 2515 } 2516 } 2517 2518 private void removeDITContentRule(final DITContentRule rule) { 2519 numericOID2ContentRules.remove(rule.getStructuralClassOID()); 2520 for (final String name : rule.getNames()) { 2521 final String lowerName = StaticUtils.toLowerCase(name); 2522 final List<DITContentRule> rules = name2ContentRules.get(lowerName); 2523 if (rules != null && rules.contains(rule)) { 2524 if (rules.size() <= 1) { 2525 name2ContentRules.remove(lowerName); 2526 } else { 2527 rules.remove(rule); 2528 } 2529 } 2530 } 2531 } 2532 2533 private void removeDITStructureRule(final DITStructureRule rule) { 2534 id2StructureRules.remove(rule.getRuleID()); 2535 for (final String name : rule.getNames()) { 2536 final String lowerName = StaticUtils.toLowerCase(name); 2537 final List<DITStructureRule> rules = name2StructureRules.get(lowerName); 2538 if (rules != null && rules.contains(rule)) { 2539 if (rules.size() <= 1) { 2540 name2StructureRules.remove(lowerName); 2541 } else { 2542 rules.remove(rule); 2543 } 2544 } 2545 } 2546 } 2547 2548 private void removeMatchingRule(final MatchingRule rule) { 2549 numericOID2MatchingRules.remove(rule.getOID()); 2550 for (final String name : rule.getNames()) { 2551 final String lowerName = StaticUtils.toLowerCase(name); 2552 final List<MatchingRule> rules = name2MatchingRules.get(lowerName); 2553 if (rules != null && rules.contains(rule)) { 2554 if (rules.size() <= 1) { 2555 name2MatchingRules.remove(lowerName); 2556 } else { 2557 rules.remove(rule); 2558 } 2559 } 2560 } 2561 } 2562 2563 private void removeMatchingRuleUse(final MatchingRuleUse use) { 2564 numericOID2MatchingRuleUses.remove(use.getMatchingRuleOID()); 2565 for (final String name : use.getNames()) { 2566 final String lowerName = StaticUtils.toLowerCase(name); 2567 final List<MatchingRuleUse> uses = name2MatchingRuleUses.get(lowerName); 2568 if (uses != null && uses.contains(use)) { 2569 if (uses.size() <= 1) { 2570 name2MatchingRuleUses.remove(lowerName); 2571 } else { 2572 uses.remove(use); 2573 } 2574 } 2575 } 2576 } 2577 2578 private void removeNameForm(final NameForm form) { 2579 numericOID2NameForms.remove(form.getOID()); 2580 name2NameForms.remove(form.getOID()); 2581 for (final String name : form.getNames()) { 2582 final String lowerName = StaticUtils.toLowerCase(name); 2583 final List<NameForm> forms = name2NameForms.get(lowerName); 2584 if (forms != null && forms.contains(form)) { 2585 if (forms.size() <= 1) { 2586 name2NameForms.remove(lowerName); 2587 } else { 2588 forms.remove(form); 2589 } 2590 } 2591 } 2592 } 2593 2594 private void removeObjectClass(final ObjectClass oc) { 2595 numericOID2ObjectClasses.remove(oc.getOID()); 2596 name2ObjectClasses.remove(oc.getOID()); 2597 for (final String name : oc.getNames()) { 2598 final String lowerName = StaticUtils.toLowerCase(name); 2599 final List<ObjectClass> classes = name2ObjectClasses.get(lowerName); 2600 if (classes != null && classes.contains(oc)) { 2601 if (classes.size() <= 1) { 2602 name2ObjectClasses.remove(lowerName); 2603 } else { 2604 classes.remove(oc); 2605 } 2606 } 2607 } 2608 } 2609 2610 private void removeSyntax(final Syntax syntax) { 2611 numericOID2Syntaxes.remove(syntax.getOID()); 2612 } 2613 2614 private void validate(final Schema schema) { 2615 // Verify all references in all elements 2616 for (final Syntax syntax : numericOID2Syntaxes.values().toArray( 2617 new Syntax[numericOID2Syntaxes.values().size()])) { 2618 try { 2619 syntax.validate(schema, warnings); 2620 registerNameToOIDMapping(syntax.getName(), syntax.getOID()); 2621 } catch (final SchemaException e) { 2622 removeSyntax(syntax); 2623 warnings.add(ERR_SYNTAX_VALIDATION_FAIL 2624 .get(syntax.toString(), e.getMessageObject())); 2625 } 2626 } 2627 2628 for (final MatchingRule rule : numericOID2MatchingRules.values().toArray( 2629 new MatchingRule[numericOID2MatchingRules.values().size()])) { 2630 try { 2631 rule.validate(schema, warnings); 2632 for (final String name : rule.getNames()) { 2633 registerNameToOIDMapping(StaticUtils.toLowerCase(name), rule.getOID()); 2634 } 2635 } catch (final SchemaException e) { 2636 removeMatchingRule(rule); 2637 warnings.add(ERR_MR_VALIDATION_FAIL.get(rule.toString(), e.getMessageObject())); 2638 } 2639 } 2640 2641 // Attribute types need special processing because they have 2642 // hierarchical dependencies. 2643 final List<AttributeType> invalidAttributeTypes = new LinkedList<>(); 2644 for (final AttributeType attributeType : numericOID2AttributeTypes.values()) { 2645 attributeType.validate(schema, invalidAttributeTypes, warnings); 2646 } 2647 2648 for (final AttributeType attributeType : invalidAttributeTypes) { 2649 removeAttributeType(attributeType); 2650 } 2651 2652 for (final AttributeType attributeType : numericOID2AttributeTypes.values()) { 2653 for (final String name : attributeType.getNames()) { 2654 registerNameToOIDMapping(StaticUtils.toLowerCase(name), attributeType.getOID()); 2655 } 2656 } 2657 2658 // Object classes need special processing because they have hierarchical 2659 // dependencies. 2660 final List<ObjectClass> invalidObjectClasses = new LinkedList<>(); 2661 for (final ObjectClass objectClass : numericOID2ObjectClasses.values()) { 2662 objectClass.validate(schema, invalidObjectClasses, warnings); 2663 } 2664 2665 for (final ObjectClass objectClass : invalidObjectClasses) { 2666 removeObjectClass(objectClass); 2667 } 2668 2669 for (final ObjectClass objectClass : numericOID2ObjectClasses.values()) { 2670 for (final String name : objectClass.getNames()) { 2671 registerNameToOIDMapping(StaticUtils.toLowerCase(name), objectClass.getOID()); 2672 } 2673 } 2674 for (final MatchingRuleUse use : numericOID2MatchingRuleUses.values().toArray( 2675 new MatchingRuleUse[numericOID2MatchingRuleUses.values().size()])) { 2676 try { 2677 use.validate(schema, warnings); 2678 for (final String name : use.getNames()) { 2679 registerNameToOIDMapping(StaticUtils.toLowerCase(name), use.getMatchingRuleOID()); 2680 } 2681 } catch (final SchemaException e) { 2682 removeMatchingRuleUse(use); 2683 warnings.add(ERR_MRU_VALIDATION_FAIL.get(use.toString(), e.getMessageObject())); 2684 } 2685 } 2686 2687 for (final NameForm form : numericOID2NameForms.values().toArray( 2688 new NameForm[numericOID2NameForms.values().size()])) { 2689 try { 2690 form.validate(schema, warnings); 2691 2692 // build the objectClass2NameForms map 2693 final String ocOID = form.getStructuralClass().getOID(); 2694 List<NameForm> forms = objectClass2NameForms.get(ocOID); 2695 if (forms == null) { 2696 objectClass2NameForms.put(ocOID, Collections.singletonList(form)); 2697 } else if (forms.size() == 1) { 2698 forms = new ArrayList<>(forms); 2699 forms.add(form); 2700 objectClass2NameForms.put(ocOID, forms); 2701 } else { 2702 forms.add(form); 2703 } 2704 for (final String name : form.getNames()) { 2705 registerNameToOIDMapping(StaticUtils.toLowerCase(name), form.getOID()); 2706 } 2707 } catch (final SchemaException e) { 2708 removeNameForm(form); 2709 warnings.add(ERR_NAMEFORM_VALIDATION_FAIL 2710 .get(form.toString(), e.getMessageObject())); 2711 } 2712 } 2713 2714 for (final DITContentRule rule : numericOID2ContentRules.values().toArray( 2715 new DITContentRule[numericOID2ContentRules.values().size()])) { 2716 try { 2717 rule.validate(schema, warnings); 2718 for (final String name : rule.getNames()) { 2719 registerNameToOIDMapping(StaticUtils.toLowerCase(name), rule.getStructuralClassOID()); 2720 } 2721 } catch (final SchemaException e) { 2722 removeDITContentRule(rule); 2723 warnings.add(ERR_DCR_VALIDATION_FAIL.get(rule.toString(), e.getMessageObject())); 2724 } 2725 } 2726 2727 // DIT structure rules need special processing because they have 2728 // hierarchical dependencies. 2729 final List<DITStructureRule> invalidStructureRules = new LinkedList<>(); 2730 for (final DITStructureRule rule : id2StructureRules.values()) { 2731 rule.validate(schema, invalidStructureRules, warnings); 2732 } 2733 2734 for (final DITStructureRule rule : invalidStructureRules) { 2735 removeDITStructureRule(rule); 2736 } 2737 2738 for (final DITStructureRule rule : id2StructureRules.values()) { 2739 // build the nameForm2StructureRules map 2740 final String ocOID = rule.getNameForm().getOID(); 2741 List<DITStructureRule> rules = nameForm2StructureRules.get(ocOID); 2742 if (rules == null) { 2743 nameForm2StructureRules.put(ocOID, Collections.singletonList(rule)); 2744 } else if (rules.size() == 1) { 2745 rules = new ArrayList<>(rules); 2746 rules.add(rule); 2747 nameForm2StructureRules.put(ocOID, rules); 2748 } else { 2749 rules.add(rule); 2750 } 2751 } 2752 } 2753}