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