001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.types; 018 019import static org.forgerock.opendj.ldap.ModificationType.*; 020import static org.forgerock.opendj.ldap.schema.CoreSchema.*; 021import static org.forgerock.opendj.ldap.schema.SchemaOptions.*; 022import static org.opends.messages.BackendMessages.*; 023import static org.opends.messages.SchemaMessages.*; 024import static org.opends.server.config.ConfigConstants.*; 025import static org.opends.server.util.ServerConstants.*; 026import static org.opends.server.util.StaticUtils.*; 027 028import java.io.BufferedReader; 029import java.io.BufferedWriter; 030import java.io.File; 031import java.io.FileNotFoundException; 032import java.io.FileReader; 033import java.io.FileWriter; 034import java.io.FilenameFilter; 035import java.io.IOException; 036import java.text.ParseException; 037import java.util.Collection; 038import java.util.HashMap; 039import java.util.LinkedHashSet; 040import java.util.LinkedList; 041import java.util.List; 042import java.util.Map; 043import java.util.Set; 044import java.util.TreeSet; 045import java.util.concurrent.locks.Lock; 046import java.util.concurrent.locks.ReentrantLock; 047 048import org.forgerock.i18n.LocalizableMessage; 049import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1; 050import org.forgerock.i18n.LocalizedIllegalArgumentException; 051import org.forgerock.i18n.slf4j.LocalizedLogger; 052import org.forgerock.opendj.ldap.AttributeDescription; 053import org.forgerock.opendj.ldap.ByteString; 054import org.forgerock.opendj.ldap.ModificationType; 055import org.forgerock.opendj.ldap.ResultCode; 056import org.forgerock.opendj.ldap.schema.AttributeType; 057import org.forgerock.opendj.ldap.schema.ConflictingSchemaElementException; 058import org.forgerock.opendj.ldap.schema.DITContentRule; 059import org.forgerock.opendj.ldap.schema.DITStructureRule; 060import org.forgerock.opendj.ldap.schema.MatchingRule; 061import org.forgerock.opendj.ldap.schema.MatchingRuleUse; 062import org.forgerock.opendj.ldap.schema.MatchingRuleUse.Builder; 063import org.forgerock.opendj.ldap.schema.NameForm; 064import org.forgerock.opendj.ldap.schema.ObjectClass; 065import org.forgerock.opendj.ldap.schema.SchemaBuilder; 066import org.forgerock.opendj.ldap.schema.Syntax; 067import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException; 068import org.forgerock.util.Option; 069import org.forgerock.util.Utils; 070import org.opends.server.core.DirectoryServer; 071import org.opends.server.core.SchemaConfigManager; 072import org.opends.server.util.Base64; 073 074/** 075 * This class defines a data structure that holds information about the components of the Directory 076 * Server schema. It includes the following kinds of elements: 077 * <UL> 078 * <LI>Attribute type definitions</LI> 079 * <LI>Objectclass definitions</LI> 080 * <LI>syntax definitions</LI> 081 * <LI>Matching rule definitions</LI> 082 * <LI>Matching rule use definitions</LI> 083 * <LI>DIT content rule definitions</LI> 084 * <LI>DIT structure rule definitions</LI> 085 * <LI>Name form definitions</LI> 086 * </UL> 087 * It always uses non-strict {@link org.forgerock.opendj.ldap.schema.Schema} under the hood. 088 */ 089@org.opends.server.types.PublicAPI( 090 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 091 mayInstantiate=false, 092 mayExtend=false, 093 mayInvoke=true) 094public final class Schema 095{ 096 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 097 098 /** The oldest modification timestamp for any schema configuration file. */ 099 private long oldestModificationTime; 100 /** The youngest modification timestamp for any schema configuration file. */ 101 private long youngestModificationTime; 102 103 /** 104 * A set of extra attributes that are not used directly by the schema but may 105 * be used by other component to store information in the schema. 106 * <p> 107 * ex : Replication uses this to store its state and GenerationID. 108 */ 109 private Map<String, Attribute> extraAttributes = new HashMap<>(); 110 111 /** 112 * The SDK schema. 113 * <p> 114 * It will progressively take over server implementation of the schema. 115 * <p> 116 * @GuardedBy("exclusiveLock") 117 */ 118 private volatile org.forgerock.opendj.ldap.schema.Schema schemaNG; 119 120 /** Guards updates to the schema. */ 121 private final Lock exclusiveLock = new ReentrantLock(); 122 123 /** 124 * Creates a new schema structure with all elements initialized but empty. 125 * 126 * @param schemaNG 127 * The SDK schema 128 * @throws DirectoryException 129 * if the schema has warnings 130 */ 131 public Schema(org.forgerock.opendj.ldap.schema.Schema schemaNG) throws DirectoryException 132 { 133 final org.forgerock.opendj.ldap.schema.Schema newSchemaNG = 134 new SchemaBuilder(schemaNG) 135 .setOption(DEFAULT_SYNTAX_OID, getDirectoryStringSyntax().getOID()) 136 .toSchema(); 137 switchSchema(newSchemaNG); 138 139 oldestModificationTime = System.currentTimeMillis(); 140 youngestModificationTime = oldestModificationTime; 141 } 142 143 /** 144 * Returns the SDK schema. 145 * 146 * @return the SDK schema 147 */ 148 public org.forgerock.opendj.ldap.schema.Schema getSchemaNG() 149 { 150 return schemaNG; 151 } 152 153 /** 154 * Retrieves the attribute type definitions for this schema. 155 * 156 * @return The attribute type definitions for this schema. 157 */ 158 public Collection<AttributeType> getAttributeTypes() 159 { 160 return schemaNG.getAttributeTypes(); 161 } 162 163 /** 164 * Indicates whether this schema definition includes an attribute type with the provided name or 165 * OID. 166 * 167 * @param nameOrOid 168 * The name or OID for which to make the determination 169 * @return {@code true} if this schema contains an attribute type with the provided name or OID, 170 * or {@code false} if not. 171 */ 172 public boolean hasAttributeType(String nameOrOid) 173 { 174 return schemaNG.hasAttributeType(nameOrOid); 175 } 176 177 /** 178 * Retrieves the attribute type definition with the specified name or OID. 179 * 180 * @param nameOrOid 181 * The name or OID of the attribute type to retrieve 182 * @return The requested attribute type 183 */ 184 public AttributeType getAttributeType(String nameOrOid) 185 { 186 try 187 { 188 return schemaNG.getAttributeType(nameOrOid); 189 } 190 catch (UnknownSchemaElementException e) 191 { 192 // It should never happen because we only use non-strict schemas 193 throw new RuntimeException(e); 194 } 195 } 196 197 /** 198 * Retrieves the attribute type definition with the specified name or OID. 199 * 200 * @param nameOrOid 201 * The name or OID of the attribute type to retrieve 202 * @param syntax 203 * The syntax to use when creating the temporary "place-holder" attribute type. 204 * @return The requested attribute type 205 */ 206 public AttributeType getAttributeType(String nameOrOid, Syntax syntax) 207 { 208 try 209 { 210 return schemaNG.getAttributeType(nameOrOid, syntax); 211 } 212 catch (UnknownSchemaElementException e) 213 { 214 // It should never happen because we only use non-strict schemas 215 throw new RuntimeException(e); 216 } 217 } 218 219 /** 220 * Parses an object class from its provided definition. 221 * 222 * @param definition 223 * The definition of the object class 224 * @return the object class 225 * @throws DirectoryException 226 * If an error occurs 227 */ 228 public ObjectClass parseObjectClass(final String definition) throws DirectoryException 229 { 230 try 231 { 232 SchemaBuilder builder = new SchemaBuilder(schemaNG); 233 builder.addObjectClass(definition, true); 234 org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema(); 235 rejectSchemaWithWarnings(newSchema); 236 return newSchema.getObjectClass(parseObjectClassOID(definition)); 237 } 238 catch (UnknownSchemaElementException e) 239 { 240 // this should never happen 241 LocalizableMessage msg = ERR_OBJECT_CLASS_CANNOT_REGISTER.get(definition); 242 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e); 243 } 244 catch (LocalizedIllegalArgumentException e) 245 { 246 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 247 } 248 } 249 250 /** 251 * Parses an attribute type from its provided definition. 252 * 253 * @param definition 254 * The definition of the attribute type 255 * @return the attribute type 256 * @throws DirectoryException 257 * If an error occurs 258 */ 259 public AttributeType parseAttributeType(final String definition) throws DirectoryException 260 { 261 try 262 { 263 SchemaBuilder builder = new SchemaBuilder(schemaNG); 264 builder.addAttributeType(definition, true); 265 org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema(); 266 rejectSchemaWithWarnings(newSchema); 267 return newSchema.getAttributeType(parseAttributeTypeOID(definition)); 268 } 269 catch (UnknownSchemaElementException e) 270 { 271 // this should never happen 272 LocalizableMessage msg = ERR_ATTR_TYPE_CANNOT_REGISTER.get(definition); 273 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e); 274 } 275 catch (LocalizedIllegalArgumentException e) 276 { 277 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 278 } 279 } 280 281 /** 282 * Parses a matching rule use from its provided definition. 283 * 284 * @param definition 285 * The definition of the matching rule use 286 * @return the matching rule use 287 * @throws DirectoryException 288 * If an error occurs 289 */ 290 public MatchingRuleUse parseMatchingRuleUse(final String definition) throws DirectoryException 291 { 292 try 293 { 294 SchemaBuilder builder = new SchemaBuilder(schemaNG); 295 builder.addMatchingRuleUse(definition, true); 296 org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema(); 297 rejectSchemaWithWarnings(newSchema); 298 return newSchema.getMatchingRuleUse(parseMatchingRuleUseOID(definition)); 299 } 300 catch (UnknownSchemaElementException e) 301 { 302 LocalizableMessage msg = ERR_MATCHING_RULE_USE_CANNOT_REGISTER.get(definition); 303 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e); 304 } 305 catch (LocalizedIllegalArgumentException e) 306 { 307 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 308 } 309 } 310 311 /** 312 * Parses a name form from its provided definition. 313 * 314 * @param definition 315 * The definition of the name form 316 * @return the name form 317 * @throws DirectoryException 318 * If an error occurs 319 */ 320 public NameForm parseNameForm(final String definition) throws DirectoryException 321 { 322 try 323 { 324 SchemaBuilder builder = new SchemaBuilder(schemaNG); 325 builder.addNameForm(definition, true); 326 org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema(); 327 rejectSchemaWithWarnings(newSchema); 328 return newSchema.getNameForm(parseNameFormOID(definition)); 329 } 330 catch (UnknownSchemaElementException e) 331 { 332 LocalizableMessage msg = ERR_NAME_FORM_CANNOT_REGISTER.get(definition); 333 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e); 334 } 335 catch (LocalizedIllegalArgumentException e) 336 { 337 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 338 } 339 } 340 341 /** 342 * Parses a a DIT content rule from its provided definition. 343 * 344 * @param definition 345 * The definition of the matching rule use 346 * @return the DIT content rule 347 * @throws DirectoryException 348 * If an error occurs 349 */ 350 public DITContentRule parseDITContentRule(final String definition) throws DirectoryException 351 { 352 try 353 { 354 SchemaBuilder builder = new SchemaBuilder(schemaNG); 355 builder.addDITContentRule(definition, true); 356 org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema(); 357 rejectSchemaWithWarnings(newSchema); 358 return newSchema.getDITContentRule(parseDITContentRuleOID(definition)); 359 } 360 catch (UnknownSchemaElementException e) 361 { 362 LocalizableMessage msg = ERR_DIT_CONTENT_RULE_CANNOT_REGISTER.get(definition); 363 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e); 364 } 365 catch (LocalizedIllegalArgumentException e) 366 { 367 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 368 } 369 } 370 371 /** 372 * Parses a DIT structure rule from its provided definition. 373 * 374 * @param definition 375 * The definition of the DIT structure rule 376 * @return the DIT structure rule 377 * @throws DirectoryException 378 * If an error occurs 379 */ 380 public DITStructureRule parseDITStructureRule(String definition) throws DirectoryException 381 { 382 try 383 { 384 SchemaBuilder builder = new SchemaBuilder(schemaNG); 385 builder.addDITStructureRule(definition, true); 386 org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema(); 387 rejectSchemaWithWarnings(newSchema); 388 return newSchema.getDITStructureRule(parseRuleID(definition)); 389 } 390 catch (UnknownSchemaElementException e) 391 { 392 LocalizableMessage msg = ERR_NAME_FORM_CANNOT_REGISTER.get(definition); 393 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e); 394 } 395 catch (LocalizedIllegalArgumentException e) 396 { 397 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 398 } 399 } 400 401 /** 402 * Registers an attribute type from its provided definition. 403 * 404 * @param definition 405 * The definition of the attribute type 406 * @param schemaFile 407 * The schema file where this definition belongs, 408 * maybe {@code null} 409 * @param overwrite 410 * Indicates whether to overwrite the attribute 411 * type if it already exists based on OID or name 412 * @throws DirectoryException 413 * If an error occurs 414 */ 415 public void registerAttributeType(final String definition, final String schemaFile, final boolean overwrite) 416 throws DirectoryException 417 { 418 exclusiveLock.lock(); 419 try 420 { 421 String defWithFile = getDefinitionWithSchemaFile(definition, schemaFile); 422 switchSchema(new SchemaBuilder(schemaNG) 423 .addAttributeType(defWithFile, overwrite) 424 .toSchema()); 425 } 426 catch (ConflictingSchemaElementException | UnknownSchemaElementException e) 427 { 428 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 429 } 430 catch (LocalizedIllegalArgumentException e) 431 { 432 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 433 } 434 finally 435 { 436 exclusiveLock.unlock(); 437 } 438 } 439 440 /** 441 * Registers the provided attribute type definition with this schema. 442 * 443 * @param attributeType 444 * The attribute type to register with this schema. 445 * @param schemaFile 446 * The schema file where this definition belongs, maybe {@code null} 447 * @param overwriteExisting 448 * Indicates whether to overwrite an existing mapping if there are any conflicts (i.e., 449 * another attribute type with the same OID or name). 450 * @throws DirectoryException 451 * If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to 452 * {@code false} 453 */ 454 public void registerAttributeType(final AttributeType attributeType, final String schemaFile, 455 final boolean overwriteExisting) throws DirectoryException 456 { 457 exclusiveLock.lock(); 458 try 459 { 460 SchemaBuilder builder = new SchemaBuilder(schemaNG); 461 registerAttributeType0(builder, attributeType, schemaFile, overwriteExisting); 462 switchSchema(builder.toSchema()); 463 } 464 catch (LocalizedIllegalArgumentException e) 465 { 466 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 467 } 468 finally 469 { 470 exclusiveLock.unlock(); 471 } 472 } 473 474 private void registerAttributeType0(SchemaBuilder builder, final AttributeType attributeType, 475 final String schemaFile, final boolean overwriteExisting) 476 { 477 AttributeType.Builder b = builder.buildAttributeType(attributeType); 478 if (schemaFile != null) 479 { 480 b.removeExtraProperty(SCHEMA_PROPERTY_FILENAME) 481 .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile); 482 } 483 if (overwriteExisting) 484 { 485 b.addToSchemaOverwrite(); 486 } 487 else 488 { 489 b.addToSchema(); 490 } 491 } 492 493 /** 494 * Replaces an existing attribute type by the provided new attribute type. 495 * 496 * @param newAttributeType 497 * Attribute type to register to the schema. 498 * @param existingAttributeType 499 * Attribute type to remove from the schema. 500 * @param schemaFile 501 * The schema file which the new object class belongs to. 502 * @throws DirectoryException 503 * If an errors occurs. 504 */ 505 public void replaceAttributeType(AttributeType newAttributeType, AttributeType existingAttributeType, 506 String schemaFile) throws DirectoryException 507 { 508 exclusiveLock.lock(); 509 try 510 { 511 SchemaBuilder builder = new SchemaBuilder(schemaNG); 512 builder.removeAttributeType(existingAttributeType.getNameOrOID()); 513 registerAttributeType0(builder, newAttributeType, schemaFile, false); 514 switchSchema(builder.toSchema()); 515 } 516 finally 517 { 518 exclusiveLock.unlock(); 519 } 520 } 521 522 /** 523 * Returns the OID from the provided object class definition, assuming the definition is valid. 524 * <p> 525 * This method does not perform any check. 526 * 527 * @param definition 528 * The definition of a object class, assumed to be valid 529 * @return the OID, which is never {@code null} 530 * @throws DirectoryException 531 * If a problem occurs while parsing the definition 532 */ 533 public static String parseObjectClassOID(String definition) throws DirectoryException 534 { 535 return parseOID(definition, ERR_PARSING_OBJECTCLASS_OID); 536 } 537 538 /** 539 * Returns the OID from the provided attribute type definition, assuming the definition is valid. 540 * <p> 541 * This method does not perform any check. 542 * 543 * @param definition 544 * The definition of an attribute type, assumed to be valid 545 * @return the OID, which is never {@code null} 546 * @throws DirectoryException 547 * If a problem occurs while parsing the definition 548 */ 549 public static String parseAttributeTypeOID(String definition) throws DirectoryException 550 { 551 return parseOID(definition, ERR_PARSING_ATTRIBUTE_TYPE_OID); 552 } 553 554 private static String parseMatchingRuleUseOID(String definition) throws DirectoryException 555 { 556 return parseOID(definition, ERR_PARSING_MATCHING_RULE_USE_OID); 557 } 558 559 /** 560 * Returns the OID from the provided name form definition, assuming the definition is valid. 561 * <p> 562 * This method does not perform any check. 563 * 564 * @param definition 565 * The definition of a name form, assumed to be valid 566 * @return the OID, which is never {@code null} 567 * @throws DirectoryException 568 * If a problem occurs while parsing the definition 569 */ 570 public static String parseNameFormOID(String definition) throws DirectoryException 571 { 572 return parseOID(definition, ERR_PARSING_NAME_FORM_OID); 573 } 574 575 private static String parseDITContentRuleOID(String definition) throws DirectoryException 576 { 577 return parseOID(definition, ERR_PARSING_DIT_CONTENT_RULE_OID); 578 } 579 580 /** 581 * Returns the ruleID from the provided dit structure rule definition, assuming the definition is 582 * valid. 583 * <p> 584 * This method does not perform any check. 585 * 586 * @param definition 587 * The definition of a dit structure rule, assumed to be valid 588 * @return the OID, which is never {@code null} 589 * @throws DirectoryException 590 * If a problem occurs while parsing the definition 591 */ 592 public static int parseRuleID(String definition) throws DirectoryException 593 { 594 // Reuse code of parseOID, even though this is not an OID 595 return Integer.parseInt(parseOID(definition, ERR_PARSING_DIT_STRUCTURE_RULE_RULEID)); 596 } 597 598 /** 599 * Returns the OID from the provided syntax definition, assuming the definition is valid. 600 * <p> 601 * This method does not perform any check. 602 * 603 * @param definition 604 * The definition of a syntax, assumed to be valid 605 * @return the OID, which is never {@code null} 606 * @throws DirectoryException 607 * If a problem occurs while parsing the definition 608 */ 609 public static String parseSyntaxOID(String definition) throws DirectoryException 610 { 611 return parseOID(definition, ERR_PARSING_LDAP_SYNTAX_OID); 612 } 613 614 private static String parseOID(String definition, Arg1<Object> parsingErrorMsg) throws DirectoryException 615 { 616 try 617 { 618 int pos = 0; 619 int length = definition.length(); 620 // Skip over any leading whitespace. 621 while (pos < length && (definition.charAt(pos) == ' ')) 622 { 623 pos++; 624 } 625 // Skip the open parenthesis. 626 pos++; 627 // Skip over any spaces immediately following the opening parenthesis. 628 while (pos < length && definition.charAt(pos) == ' ') 629 { 630 pos++; 631 } 632 // The next set of characters must be the OID. 633 int oidStartPos = pos; 634 while (pos < length && definition.charAt(pos) != ' ' && definition.charAt(pos) != ')') 635 { 636 pos++; 637 } 638 return definition.substring(oidStartPos, pos); 639 } 640 catch (IndexOutOfBoundsException e) 641 { 642 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, parsingErrorMsg.get(definition), e); 643 } 644 } 645 646 /** 647 * Deregisters the provided attribute type definition with this schema. 648 * 649 * @param attributeType The attribute type to deregister with this schema. 650 * @throws DirectoryException 651 * If the attribute type is referenced by another schema element. 652 */ 653 public void deregisterAttributeType(final AttributeType attributeType) throws DirectoryException 654 { 655 exclusiveLock.lock(); 656 try 657 { 658 SchemaBuilder builder = new SchemaBuilder(schemaNG); 659 if (builder.removeAttributeType(attributeType.getNameOrOID())) 660 { 661 switchSchema(builder.toSchema()); 662 } 663 } 664 finally 665 { 666 exclusiveLock.unlock(); 667 } 668 } 669 670 /** 671 * Retrieves the objectclass definitions for this schema. 672 * 673 * @return The objectclass definitions for this schema. 674 */ 675 public Collection<ObjectClass> getObjectClasses() 676 { 677 return schemaNG.getObjectClasses(); 678 } 679 680 /** 681 * Indicates whether this schema definition includes an objectclass with the provided name or OID. 682 * 683 * @param nameOrOid 684 * The name or OID for which to make the determination. 685 * @return {@code true} if this schema contains an objectclass with the provided name or OID, or 686 * {@code false} if not. 687 */ 688 public boolean hasObjectClass(String nameOrOid) 689 { 690 return schemaNG.hasObjectClass(nameOrOid); 691 } 692 693 694 695 /** 696 * Retrieves the objectclass definition with the specified name or OID. 697 * 698 * @param nameOrOid 699 * The name or OID of the objectclass to retrieve. 700 * @return The requested objectclass, or {@code null} if no class is registered with the provided 701 * name or OID. 702 */ 703 public ObjectClass getObjectClass(String nameOrOid) 704 { 705 return schemaNG.getObjectClass(nameOrOid); 706 } 707 708 /** 709 * Registers the provided objectclass definition with this schema. 710 * 711 * @param objectClass 712 * The objectclass to register with this schema. 713 * @param schemaFile 714 * The schema file where this definition belongs, maybe {@code null} 715 * @param overwriteExisting 716 * Indicates whether to overwrite an existing mapping if there are any conflicts (i.e., 717 * another objectclass with the same OID or name). 718 * @throws DirectoryException 719 * If a conflict is encountered and the {@code overwriteExisting} flag is set to 720 * {@code false}. 721 */ 722 public void registerObjectClass(ObjectClass objectClass, String schemaFile, boolean overwriteExisting) 723 throws DirectoryException 724 { 725 exclusiveLock.lock(); 726 try 727 { 728 SchemaBuilder builder = new SchemaBuilder(schemaNG); 729 registerObjectClass0(builder, objectClass, schemaFile, overwriteExisting); 730 switchSchema(builder.toSchema()); 731 } 732 finally 733 { 734 exclusiveLock.unlock(); 735 } 736 } 737 738 private void registerObjectClass0(SchemaBuilder builder, ObjectClass objectClass, String schemaFile, 739 boolean overwriteExisting) 740 { 741 ObjectClass.Builder b = builder.buildObjectClass(objectClass); 742 if (schemaFile != null) 743 { 744 b.removeExtraProperty(SCHEMA_PROPERTY_FILENAME) 745 .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile); 746 } 747 if (overwriteExisting) 748 { 749 b.addToSchemaOverwrite(); 750 } 751 else 752 { 753 b.addToSchema(); 754 } 755 } 756 757 /** 758 * Registers an object class from its provided definition. 759 * 760 * @param definition 761 * The definition of the object class 762 * @param schemaFile 763 * The schema file where this definition belongs, may be {@code null} 764 * @param overwriteExisting 765 * Indicates whether to overwrite the object class 766 * if it already exists based on OID or name 767 * @throws DirectoryException 768 * If an error occurs 769 */ 770 public void registerObjectClass(String definition, String schemaFile, boolean overwriteExisting) 771 throws DirectoryException 772 { 773 exclusiveLock.lock(); 774 try 775 { 776 String defWithFile = getDefinitionWithSchemaFile(definition, schemaFile); 777 switchSchema(new SchemaBuilder(schemaNG) 778 .addObjectClass(defWithFile, overwriteExisting) 779 .toSchema()); 780 } 781 catch (ConflictingSchemaElementException | UnknownSchemaElementException e) 782 { 783 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 784 } 785 catch (LocalizedIllegalArgumentException e) 786 { 787 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 788 } 789 finally 790 { 791 exclusiveLock.unlock(); 792 } 793 } 794 795 /** 796 * Deregisters the provided objectclass definition with this schema. 797 * 798 * @param objectClass The objectclass to deregister with this schema. 799 * @throws DirectoryException 800 * If the object class is referenced by another schema element. 801 */ 802 public void deregisterObjectClass(ObjectClass objectClass) throws DirectoryException 803 { 804 exclusiveLock.lock(); 805 try 806 { 807 SchemaBuilder builder = new SchemaBuilder(schemaNG); 808 if (builder.removeObjectClass(objectClass.getNameOrOID())) 809 { 810 switchSchema(builder.toSchema()); 811 } 812 } 813 finally 814 { 815 exclusiveLock.unlock(); 816 } 817 } 818 819 820 821 /** 822 * Retrieves the syntax definitions for this schema. 823 * 824 * @return The syntax definitions for this schema. 825 */ 826 public Collection<Syntax> getSyntaxes() 827 { 828 return schemaNG.getSyntaxes(); 829 } 830 831 832 833 /** 834 * Indicates whether this schema definition includes an attribute syntax with the provided OID. 835 * 836 * @param oid 837 * The OID for which to make the determination 838 * @return {@code true} if this schema contains an syntax with the provided OID, or {@code false} 839 * if not. 840 */ 841 public boolean hasSyntax(String oid) 842 { 843 return schemaNG.hasSyntax(oid); 844 } 845 846 /** 847 * Retrieves the syntax definition with the OID. 848 * 849 * @param numericOid 850 * The OID of the syntax to retrieve. 851 * @return The requested syntax, or {@code null} if no syntax is registered with the provided OID. 852 */ 853 public Syntax getSyntax(String numericOid) 854 { 855 return schemaNG.getSyntax(numericOid); 856 } 857 858 /** 859 * Registers the provided syntax definition with this schema. 860 * 861 * @param syntax 862 * The syntax to register with this schema. 863 * @param overwriteExisting 864 * Indicates whether to overwrite an existing mapping if there are any conflicts (i.e., 865 * another attribute syntax with the same OID). 866 * @throws DirectoryException 867 * If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to 868 * {@code false} 869 */ 870 public void registerSyntax(final Syntax syntax, final boolean overwriteExisting) throws DirectoryException 871 { 872 exclusiveLock.lock(); 873 try 874 { 875 SchemaBuilder builder = new SchemaBuilder(schemaNG); 876 Syntax.Builder b = builder.buildSyntax(syntax); 877 if (overwriteExisting) 878 { 879 b.addToSchemaOverwrite(); 880 } 881 else 882 { 883 b.addToSchema(); 884 } 885 switchSchema(builder.toSchema()); 886 } 887 catch (LocalizedIllegalArgumentException e) 888 { 889 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 890 } 891 finally 892 { 893 exclusiveLock.unlock(); 894 } 895 } 896 897 /** 898 * Registers the provided syntax definition with this schema. 899 * 900 * @param definition 901 * The definition to register with this schema. 902 * @param overwriteExisting 903 * Indicates whether to overwrite an existing mapping if there are any conflicts (i.e., 904 * another attribute syntax with the same OID). 905 * @throws DirectoryException 906 * If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to 907 * {@code false} 908 */ 909 public void registerSyntax(final String definition, final boolean overwriteExisting) throws DirectoryException 910 { 911 exclusiveLock.lock(); 912 try 913 { 914 org.forgerock.opendj.ldap.schema.Schema newSchema = 915 new SchemaBuilder(schemaNG) 916 .addSyntax(definition, overwriteExisting) 917 .toSchema(); 918 switchSchema(newSchema); 919 } 920 catch (ConflictingSchemaElementException | UnknownSchemaElementException e) 921 { 922 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 923 } 924 catch (LocalizedIllegalArgumentException e) 925 { 926 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 927 } 928 finally 929 { 930 exclusiveLock.unlock(); 931 } 932 } 933 934 /** 935 * Deregisters the provided syntax definition with this schema. 936 * 937 * @param syntax 938 * The syntax to deregister with this schema. 939 * @throws DirectoryException 940 * If the LDAP syntax is referenced by another schema element. 941 */ 942 public void deregisterSyntax(final Syntax syntax) throws DirectoryException 943 { 944 exclusiveLock.lock(); 945 try 946 { 947 SchemaBuilder builder = new SchemaBuilder(schemaNG); 948 builder.removeSyntax(syntax.getOID()); 949 switchSchema(builder.toSchema()); 950 } 951 finally 952 { 953 exclusiveLock.unlock(); 954 } 955 } 956 957 /** 958 * Retrieves all matching rule definitions for this schema. 959 * 960 * @return The matching rule definitions for this schema 961 */ 962 public Collection<MatchingRule> getMatchingRules() 963 { 964 return schemaNG.getMatchingRules(); 965 } 966 967 /** 968 * Indicates whether this schema definition includes a matching rule with the provided name or 969 * OID. 970 * 971 * @param nameOrOid 972 * The name or OID for which to make the determination 973 * @return {@code true} if this schema contains a matching rule with the provided name or OID, or 974 * {@code false} if not. 975 */ 976 public boolean hasMatchingRule(String nameOrOid) 977 { 978 return schemaNG.hasMatchingRule(nameOrOid); 979 } 980 981 /** 982 * Retrieves the matching rule definition with the specified name or OID. 983 * 984 * @param nameOrOid 985 * The name or OID of the matching rule to retrieve 986 * @return The requested matching rule, or {@code null} if no rule is registered with the provided 987 * name or OID. 988 * @throws UnknownSchemaElementException 989 * If the requested matching rule was not found or if the provided name is ambiguous. 990 */ 991 public MatchingRule getMatchingRule(String nameOrOid) 992 { 993 return schemaNG.getMatchingRule(nameOrOid); 994 } 995 996 /** 997 * Registers the provided matching rule definitions with this schema. 998 * 999 * @param matchingRules 1000 * The matching rules to register with this schema. 1001 * @param overwriteExisting 1002 * Indicates whether to overwrite an existing mapping if there are 1003 * any conflicts (i.e., another matching rule with the same OID or 1004 * name). 1005 * @throws DirectoryException 1006 * If a conflict is encountered and the 1007 * {@code overwriteExisting} flag is set to {@code false} 1008 */ 1009 public void registerMatchingRules(Collection<MatchingRule> matchingRules, boolean overwriteExisting) 1010 throws DirectoryException 1011 { 1012 exclusiveLock.lock(); 1013 try 1014 { 1015 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1016 for (MatchingRule matchingRule : matchingRules) 1017 { 1018 MatchingRule.Builder b = builder.buildMatchingRule(matchingRule); 1019 if (overwriteExisting) 1020 { 1021 b.addToSchemaOverwrite(); 1022 } 1023 else 1024 { 1025 b.addToSchema(); 1026 } 1027 } 1028 switchSchema(builder.toSchema()); 1029 } 1030 catch (LocalizedIllegalArgumentException e) 1031 { 1032 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 1033 } 1034 finally 1035 { 1036 exclusiveLock.unlock(); 1037 } 1038 } 1039 1040 /** 1041 * Deregisters the provided matching rule definition with this schema. 1042 * 1043 * @param matchingRule 1044 * The matching rule to deregister with this schema. 1045 * @throws DirectoryException 1046 * If the schema has constraints violations. 1047 */ 1048 public void deregisterMatchingRule(final MatchingRule matchingRule) throws DirectoryException 1049 { 1050 exclusiveLock.lock(); 1051 try 1052 { 1053 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1054 builder.removeMatchingRule(matchingRule.getNameOrOID()); 1055 switchSchema(builder.toSchema()); 1056 } 1057 finally 1058 { 1059 exclusiveLock.unlock(); 1060 } 1061 } 1062 1063 1064 /** 1065 * Retrieves the matching rule use definitions for this schema, as a 1066 * mapping between the matching rule for the matching rule use 1067 * definition and the matching rule use itself. Each matching rule 1068 * use should only be present once, since its only key is its 1069 * matching rule. The contents of the returned mapping must not be 1070 * altered. 1071 * 1072 * @return The matching rule use definitions for this schema. 1073 */ 1074 public Collection<MatchingRuleUse> getMatchingRuleUses() 1075 { 1076 return schemaNG.getMatchingRuleUses(); 1077 } 1078 1079 1080 1081 /** 1082 * Retrieves the matching rule use definition for the specified matching rule. 1083 * 1084 * @param matchingRule 1085 * The matching rule for which to retrieve the matching rule use definition. 1086 * @return The matching rule use definition, or {@code null} if none exists for the specified 1087 * matching rule. 1088 */ 1089 public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule) 1090 { 1091 return schemaNG.getMatchingRuleUse(matchingRule); 1092 } 1093 1094 /** 1095 * Registers the provided matching rule use definition with this schema. 1096 * 1097 * @param matchingRuleUse 1098 * The matching rule use definition to register. 1099 * @param schemaFile 1100 * The schema file where this definition belongs, maybe {@code null} 1101 * @param overwriteExisting 1102 * Indicates whether to overwrite an existing mapping if there are any conflicts (i.e., 1103 * another matching rule use with the same matching rule). 1104 * @throws DirectoryException 1105 * If a conflict is encountered and the {@code overwriteExisting} flag is set to 1106 * {@code false} 1107 */ 1108 public void registerMatchingRuleUse(MatchingRuleUse matchingRuleUse, String schemaFile, boolean overwriteExisting) 1109 throws DirectoryException 1110 { 1111 exclusiveLock.lock(); 1112 try 1113 { 1114 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1115 Builder mruBuilder = builder.buildMatchingRuleUse(matchingRuleUse); 1116 if (schemaFile != null) 1117 { 1118 mruBuilder.removeExtraProperty(SCHEMA_PROPERTY_FILENAME) 1119 .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile); 1120 } 1121 if (overwriteExisting) 1122 { 1123 mruBuilder.addToSchemaOverwrite(); 1124 } 1125 else 1126 { 1127 mruBuilder.addToSchema(); 1128 } 1129 switchSchema(builder.toSchema()); 1130 } 1131 finally 1132 { 1133 exclusiveLock.unlock(); 1134 } 1135 } 1136 1137 private String getDefinitionWithSchemaFile(String definition, String schemaFile) 1138 { 1139 return schemaFile != null ? addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile) : definition; 1140 } 1141 1142 /** 1143 * Deregisters the provided matching rule use definition with this schema. 1144 * 1145 * @param matchingRuleUse 1146 * The matching rule use to deregister with this schema. 1147 * @throws DirectoryException 1148 * If the schema has constraints violations. 1149 */ 1150 public void deregisterMatchingRuleUse(org.forgerock.opendj.ldap.schema.MatchingRuleUse matchingRuleUse) 1151 throws DirectoryException 1152 { 1153 exclusiveLock.lock(); 1154 try 1155 { 1156 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1157 builder.removeMatchingRuleUse(matchingRuleUse.getNameOrOID()); 1158 switchSchema(builder.toSchema()); 1159 } 1160 finally 1161 { 1162 exclusiveLock.unlock(); 1163 } 1164 } 1165 1166 1167 1168 /** 1169 * Retrieves the DIT content rule definitions for this schema. 1170 * 1171 * @return The DIT content rule definitions for this schema. 1172 */ 1173 public Collection<DITContentRule> getDITContentRules() 1174 { 1175 return schemaNG.getDITContentRules(); 1176 } 1177 1178 1179 /** 1180 * Retrieves the DIT content rule definition for the specified objectclass. 1181 * 1182 * @param objectClass 1183 * The objectclass for the DIT content rule to retrieve. 1184 * @return The requested DIT content rule, or {@code null} if no DIT content rule is registered 1185 * with the provided objectclass. 1186 */ 1187 public DITContentRule getDITContentRule(ObjectClass objectClass) 1188 { 1189 return schemaNG.getDITContentRule(objectClass); 1190 } 1191 1192 1193 1194 /** 1195 * Registers the provided DIT content rule definition with this schema. 1196 * 1197 * @param ditContentRule 1198 * The DIT content rule to register. 1199 * @param schemaFile 1200 * The schema file where this definition belongs, maybe {@code null} 1201 * @param overwriteExisting 1202 * Indicates whether to overwrite an existing mapping if there are any conflicts (i.e., 1203 * another DIT content rule with the same objectclass). 1204 * @throws DirectoryException 1205 * If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to 1206 * {@code false} 1207 */ 1208 public void registerDITContentRule(DITContentRule ditContentRule, String schemaFile, boolean overwriteExisting) 1209 throws DirectoryException 1210 { 1211 exclusiveLock.lock(); 1212 try 1213 { 1214 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1215 DITContentRule.Builder dcrBuilder = builder.buildDITContentRule(ditContentRule); 1216 if (schemaFile != null) 1217 { 1218 dcrBuilder.removeExtraProperty(SCHEMA_PROPERTY_FILENAME) 1219 .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile); 1220 } 1221 if (overwriteExisting) 1222 { 1223 dcrBuilder.addToSchemaOverwrite(); 1224 } 1225 else 1226 { 1227 dcrBuilder.addToSchema(); 1228 } 1229 switchSchema(builder.toSchema()); 1230 } 1231 finally 1232 { 1233 exclusiveLock.unlock(); 1234 } 1235 } 1236 1237 /** 1238 * Deregisters the provided DIT content rule definition with this 1239 * schema. 1240 * 1241 * @param ditContentRule The DIT content rule to deregister with 1242 * this schema. 1243 * @throws DirectoryException 1244 * May be thrown if the schema has constraint violations, although 1245 * deregistering a DIT content rule should not break any constraint. 1246 */ 1247 public void deregisterDITContentRule(DITContentRule ditContentRule) throws DirectoryException 1248 { 1249 exclusiveLock.lock(); 1250 try 1251 { 1252 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1253 builder.removeDITContentRule(ditContentRule.getNameOrOID()); 1254 switchSchema(builder.toSchema()); 1255 } 1256 finally 1257 { 1258 exclusiveLock.unlock(); 1259 } 1260 } 1261 1262 1263 1264 /** 1265 * Retrieves the DIT structure rule definitions for this schema. 1266 * The contents of the returned mapping must not be altered. 1267 * 1268 * @return The DIT structure rule definitions for this schema. 1269 */ 1270 public Collection<DITStructureRule> getDITStructureRules() 1271 { 1272 return schemaNG.getDITStuctureRules(); 1273 } 1274 1275 /** 1276 * Retrieves the DIT structure rule definition with the provided rule ID. 1277 * 1278 * @param ruleID 1279 * The rule ID for the DIT structure rule to retrieve. 1280 * @return The requested DIT structure rule, or {@code null} if no DIT structure rule is 1281 * registered with the provided rule ID. 1282 */ 1283 public DITStructureRule getDITStructureRule(int ruleID) 1284 { 1285 return schemaNG.getDITStructureRule(ruleID); 1286 } 1287 1288 /** 1289 * Retrieves the DIT structure rule definitions for the provided name form. 1290 * 1291 * @param nameForm 1292 * The name form for the DIT structure rule to retrieve. 1293 * @return The requested DIT structure rules, or {@code null} if no DIT structure rule is 1294 * registered with the provided name form. 1295 */ 1296 public Collection<DITStructureRule> getDITStructureRules(NameForm nameForm) 1297 { 1298 return schemaNG.getDITStructureRules(nameForm); 1299 } 1300 1301 1302 1303 /** 1304 * Registers the provided DIT structure rule definition with this schema. 1305 * 1306 * @param ditStructureRule 1307 * The DIT structure rule to register. 1308 * @param schemaFile 1309 * The schema file where this definition belongs, maybe {@code null} 1310 * @param overwriteExisting 1311 * Indicates whether to overwrite an existing mapping if there are any conflicts (i.e., 1312 * another DIT structure rule with the same name form). 1313 * @throws DirectoryException 1314 * If a conflict is encountered and the {@code overwriteExisting} flag is set to 1315 * {@code false} 1316 */ 1317 public void registerDITStructureRule(DITStructureRule ditStructureRule, String schemaFile, boolean overwriteExisting) 1318 throws DirectoryException 1319 { 1320 exclusiveLock.lock(); 1321 try 1322 { 1323 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1324 DITStructureRule.Builder dsrBuilder = builder.buildDITStructureRule(ditStructureRule); 1325 if (schemaFile != null) 1326 { 1327 dsrBuilder.removeExtraProperty(SCHEMA_PROPERTY_FILENAME) 1328 .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile); 1329 } 1330 if (overwriteExisting) 1331 { 1332 dsrBuilder.addToSchemaOverwrite(); 1333 } 1334 else 1335 { 1336 dsrBuilder.addToSchema(); 1337 } 1338 switchSchema(builder.toSchema()); 1339 } 1340 catch (LocalizedIllegalArgumentException e) 1341 { 1342 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 1343 } 1344 finally 1345 { 1346 exclusiveLock.unlock(); 1347 } 1348 } 1349 1350 /** 1351 * Deregisters the provided DIT structure rule definition with this schema. 1352 * 1353 * @param ditStructureRule 1354 * The DIT structure rule to deregister with this schema. 1355 * @throws DirectoryException 1356 * If an error occurs. 1357 */ 1358 public void deregisterDITStructureRule(DITStructureRule ditStructureRule) throws DirectoryException 1359 { 1360 exclusiveLock.lock(); 1361 try 1362 { 1363 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1364 builder.removeDITStructureRule(ditStructureRule.getRuleID()); 1365 switchSchema(builder.toSchema()); 1366 } 1367 finally 1368 { 1369 exclusiveLock.unlock(); 1370 } 1371 } 1372 1373 1374 1375 /** 1376 * Retrieves the name form definitions for this schema. 1377 * 1378 * @return The name form definitions for this schema. 1379 */ 1380 public Collection<NameForm> getNameForms() 1381 { 1382 return schemaNG.getNameForms(); 1383 } 1384 1385 /** 1386 * Indicates whether this schema definition includes a name form with the specified name or OID. 1387 * 1388 * @param nameOrOid 1389 * The name or OID for which to make the determination. 1390 * @return {@code true} if this schema contains a name form with the provided name or OID, or 1391 * {@code false} if not. 1392 */ 1393 public boolean hasNameForm(String nameOrOid) 1394 { 1395 return schemaNG.hasNameForm(nameOrOid); 1396 } 1397 1398 1399 1400 /** 1401 * Retrieves the name forms definition for the specified objectclass. 1402 * 1403 * @param objectClass 1404 * The objectclass for the name form to retrieve. 1405 * @return The requested name forms, or {@code null} if no name forms are registered with the 1406 * provided objectClass. 1407 */ 1408 public Collection<NameForm> getNameForm(ObjectClass objectClass) 1409 { 1410 return schemaNG.getNameForms(objectClass); 1411 } 1412 1413 1414 1415 /** 1416 * Retrieves the name form definition with the provided name or OID. 1417 * 1418 * @param nameOrOid 1419 * The name or OID of the name form to retrieve. 1420 * @return The requested name form, or {@code null} if no name form is registered with the 1421 * provided name or OID. 1422 */ 1423 public NameForm getNameForm(String nameOrOid) 1424 { 1425 return schemaNG.getNameForm(nameOrOid); 1426 } 1427 1428 1429 1430 /** 1431 * Registers the provided name form definition with this schema. 1432 * 1433 * @param nameForm 1434 * The name form definition to register. 1435 * @param schemaFile 1436 * The schema file where this definition belongs, maybe {@code null} 1437 * @param overwriteExisting 1438 * Indicates whether to overwrite an existing mapping if there are any conflicts (i.e., 1439 * another name form with the same objectclass). 1440 * @throws DirectoryException 1441 * If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to 1442 * {@code false} 1443 */ 1444 public void registerNameForm(NameForm nameForm, String schemaFile, boolean overwriteExisting) 1445 throws DirectoryException 1446 { 1447 exclusiveLock.lock(); 1448 try 1449 { 1450 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1451 NameForm.Builder formBuilder = builder.buildNameForm(nameForm); 1452 if (schemaFile != null) 1453 { 1454 formBuilder.removeExtraProperty(SCHEMA_PROPERTY_FILENAME) 1455 .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile); 1456 } 1457 if (overwriteExisting) 1458 { 1459 formBuilder.addToSchemaOverwrite(); 1460 } 1461 else 1462 { 1463 formBuilder.addToSchema(); 1464 } 1465 switchSchema(builder.toSchema()); 1466 } 1467 finally 1468 { 1469 exclusiveLock.unlock(); 1470 } 1471 } 1472 1473 /** 1474 * Deregisters the provided name form definition with this schema. 1475 * 1476 * @param nameForm The name form definition to deregister. 1477 * @throws DirectoryException 1478 * If an error occurs. 1479 */ 1480 public void deregisterNameForm(NameForm nameForm) throws DirectoryException 1481 { 1482 exclusiveLock.lock(); 1483 try 1484 { 1485 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1486 builder.removeNameForm(nameForm.getNameOrOID()); 1487 switchSchema(builder.toSchema()); 1488 } 1489 finally 1490 { 1491 exclusiveLock.unlock(); 1492 } 1493 } 1494 1495 1496 1497 /** 1498 * Retrieves the modification timestamp for the file in the schema 1499 * configuration directory with the oldest last modified time. 1500 * 1501 * @return The modification timestamp for the file in the schema 1502 * configuration directory with the oldest last modified 1503 * time. 1504 */ 1505 public long getOldestModificationTime() 1506 { 1507 return oldestModificationTime; 1508 } 1509 1510 1511 1512 /** 1513 * Sets the modification timestamp for the oldest file in the schema 1514 * configuration directory. 1515 * 1516 * @param oldestModificationTime The modification timestamp for 1517 * the oldest file in the schema 1518 * configuration directory. 1519 */ 1520 public void setOldestModificationTime(long oldestModificationTime) 1521 { 1522 this.oldestModificationTime = oldestModificationTime; 1523 } 1524 1525 1526 1527 /** 1528 * Retrieves the modification timestamp for the file in the schema 1529 * configuration directory with the youngest last modified time. 1530 * 1531 * @return The modification timestamp for the file in the schema 1532 * configuration directory with the youngest last modified 1533 * time. 1534 */ 1535 public long getYoungestModificationTime() 1536 { 1537 return youngestModificationTime; 1538 } 1539 1540 1541 1542 /** 1543 * Sets the modification timestamp for the youngest file in the 1544 * schema configuration directory. 1545 * 1546 * @param youngestModificationTime The modification timestamp for 1547 * the youngest file in the schema 1548 * configuration directory. 1549 */ 1550 public void setYoungestModificationTime( 1551 long youngestModificationTime) 1552 { 1553 this.youngestModificationTime = youngestModificationTime; 1554 } 1555 1556 /** 1557 * Creates a new {@link Schema} object that is a duplicate of this one. It elements may be added 1558 * and removed from the duplicate without impacting this version. 1559 * 1560 * @return A new {@link Schema} object that is a duplicate of this one. 1561 */ 1562 public Schema duplicate() 1563 { 1564 Schema dupSchema; 1565 try 1566 { 1567 dupSchema = new Schema(schemaNG); 1568 } 1569 catch (DirectoryException unexpected) 1570 { 1571 // the schema has already been validated 1572 throw new RuntimeException(unexpected); 1573 } 1574 1575 dupSchema.oldestModificationTime = oldestModificationTime; 1576 dupSchema.youngestModificationTime = youngestModificationTime; 1577 if (extraAttributes != null) 1578 { 1579 dupSchema.extraAttributes = new HashMap<>(extraAttributes); 1580 } 1581 1582 return dupSchema; 1583 } 1584 1585 1586 /** 1587 * Get the extraAttributes stored in this schema. 1588 * 1589 * @return The extraAttributes stored in this schema. 1590 */ 1591 public Collection<Attribute> getExtraAttributes() 1592 { 1593 return extraAttributes.values(); 1594 } 1595 1596 1597 /** 1598 * Add a new extra Attribute for this schema. 1599 * 1600 * @param name The identifier of the extra Attribute. 1601 * 1602 * @param attr The extra attribute that must be added to 1603 * this Schema. 1604 */ 1605 public void addExtraAttribute(String name, Attribute attr) 1606 { 1607 extraAttributes.put(name, attr); 1608 } 1609 1610 1611 /** 1612 * Writes a single file containing all schema element definitions, 1613 * which can be used on startup to determine whether the schema 1614 * files were edited with the server offline. 1615 */ 1616 public static void writeConcatenatedSchema() 1617 { 1618 String concatFilePath = null; 1619 try 1620 { 1621 Set<String> attributeTypes = new LinkedHashSet<>(); 1622 Set<String> objectClasses = new LinkedHashSet<>(); 1623 Set<String> nameForms = new LinkedHashSet<>(); 1624 Set<String> ditContentRules = new LinkedHashSet<>(); 1625 Set<String> ditStructureRules = new LinkedHashSet<>(); 1626 Set<String> matchingRuleUses = new LinkedHashSet<>(); 1627 Set<String> ldapSyntaxes = new LinkedHashSet<>(); 1628 genConcatenatedSchema(attributeTypes, objectClasses, nameForms, 1629 ditContentRules, ditStructureRules, 1630 matchingRuleUses,ldapSyntaxes); 1631 1632 1633 File configFile = new File(DirectoryServer.getConfigFile()); 1634 File configDirectory = configFile.getParentFile(); 1635 File upgradeDirectory = new File(configDirectory, "upgrade"); 1636 upgradeDirectory.mkdir(); 1637 File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME); 1638 concatFilePath = concatFile.getAbsolutePath(); 1639 1640 File tempFile = new File(concatFilePath + ".tmp"); 1641 try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, false))) 1642 { 1643 writeLines(writer, 1644 "dn: " + DirectoryServer.getSchemaDN(), 1645 "objectClass: top", 1646 "objectClass: ldapSubentry", 1647 "objectClass: subschema"); 1648 1649 writeLines(writer, ATTR_ATTRIBUTE_TYPES, attributeTypes); 1650 writeLines(writer, ATTR_OBJECTCLASSES, objectClasses); 1651 writeLines(writer, ATTR_NAME_FORMS, nameForms); 1652 writeLines(writer, ATTR_DIT_CONTENT_RULES, ditContentRules); 1653 writeLines(writer, ATTR_DIT_STRUCTURE_RULES, ditStructureRules); 1654 writeLines(writer, ATTR_MATCHING_RULE_USE, matchingRuleUses); 1655 writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes); 1656 } 1657 1658 if (concatFile.exists()) 1659 { 1660 concatFile.delete(); 1661 } 1662 tempFile.renameTo(concatFile); 1663 } 1664 catch (Exception e) 1665 { 1666 logger.traceException(e); 1667 1668 // This is definitely not ideal, but it's not the end of the 1669 // world. The worst that should happen is that the schema 1670 // changes could potentially be sent to the other servers again 1671 // when this server is restarted, which shouldn't hurt anything. 1672 // Still, we should log a warning message. 1673 logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e)); 1674 } 1675 } 1676 1677 private static void writeLines(BufferedWriter writer, String... lines) throws IOException 1678 { 1679 for (String line : lines) 1680 { 1681 writer.write(line); 1682 writer.newLine(); 1683 } 1684 } 1685 1686 private static void writeLines(BufferedWriter writer, String beforeColumn, Set<String> lines) throws IOException 1687 { 1688 for (String line : lines) 1689 { 1690 writer.write(beforeColumn); 1691 writer.write(": "); 1692 writer.write(line); 1693 writer.newLine(); 1694 } 1695 } 1696 1697 /** 1698 * Reads the files contained in the schema directory and generates a 1699 * concatenated view of their contents in the provided sets. 1700 * 1701 * @param attributeTypes The set into which to place the 1702 * attribute types read from the schema 1703 * files. 1704 * @param objectClasses The set into which to place the object 1705 * classes read from the schema files. 1706 * @param nameForms The set into which to place the name 1707 * forms read from the schema files. 1708 * @param ditContentRules The set into which to place the DIT 1709 * content rules read from the schema 1710 * files. 1711 * @param ditStructureRules The set into which to place the DIT 1712 * structure rules read from the schema 1713 * files. 1714 * @param matchingRuleUses The set into which to place the 1715 * matching rule uses read from the 1716 * schema files. 1717 * @param ldapSyntaxes The set into which to place the 1718 * ldap syntaxes read from the 1719 * schema files. 1720 * 1721 * @throws IOException If a problem occurs while reading the 1722 * schema file elements. 1723 */ 1724 public static void genConcatenatedSchema( 1725 Set<String> attributeTypes, 1726 Set<String> objectClasses, 1727 Set<String> nameForms, 1728 Set<String> ditContentRules, 1729 Set<String> ditStructureRules, 1730 Set<String> matchingRuleUses, 1731 Set<String> ldapSyntaxes) 1732 throws IOException 1733 { 1734 // Get a sorted list of the files in the schema directory. 1735 TreeSet<File> schemaFiles = new TreeSet<>(); 1736 String schemaDirectory = SchemaConfigManager.getSchemaDirectoryPath(); 1737 1738 final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter(); 1739 for (File f : new File(schemaDirectory).listFiles(filter)) 1740 { 1741 if (f.isFile()) 1742 { 1743 schemaFiles.add(f); 1744 } 1745 } 1746 1747 1748 // Open each of the files in order and read the elements that they 1749 // contain, appending them to the appropriate lists. 1750 for (File f : schemaFiles) 1751 { 1752 List<StringBuilder> lines = readSchemaElementsFromLdif(f); 1753 1754 // Iterate through each line in the list. Find the colon and 1755 // get the attribute name at the beginning. If it's something 1756 // that we don't recognize, then skip it. Otherwise, add the 1757 // X-SCHEMA-FILE extension and add it to the appropriate schema 1758 // element list. 1759 for (StringBuilder buffer : lines) 1760 { 1761 String line = buffer.toString().trim(); 1762 parseSchemaLine(line, f.getName(), attributeTypes, objectClasses, 1763 nameForms, ditContentRules, ditStructureRules, matchingRuleUses, 1764 ldapSyntaxes); 1765 } 1766 } 1767 } 1768 1769 private static List<StringBuilder> readSchemaElementsFromLdif(File f) throws IOException, FileNotFoundException 1770 { 1771 final LinkedList<StringBuilder> lines = new LinkedList<>(); 1772 1773 try (BufferedReader reader = new BufferedReader(new FileReader(f))) 1774 { 1775 String line; 1776 while ((line = reader.readLine()) != null) 1777 { 1778 if (line.startsWith("#") || line.length() == 0) 1779 { 1780 continue; 1781 } 1782 else if (line.startsWith(" ")) 1783 { 1784 lines.getLast().append(line.substring(1)); 1785 } 1786 else 1787 { 1788 lines.add(new StringBuilder(line)); 1789 } 1790 } 1791 } 1792 return lines; 1793 } 1794 1795 1796 1797 /** 1798 * Reads data from the specified concatenated schema file into the 1799 * provided sets. 1800 * 1801 * @param concatSchemaFile The concatenated schema file to be read. 1802 * @param attributeTypes The set into which to place the attribute types 1803 * read from the concatenated schema file. 1804 * @param objectClasses The set into which to place the object classes 1805 * read from the concatenated schema file. 1806 * @param nameForms The set into which to place the name forms 1807 * read from the concatenated schema file. 1808 * @param ditContentRules The set into which to place the DIT content rules 1809 * read from the concatenated schema file. 1810 * @param ditStructureRules The set into which to place the DIT structure rules 1811 * read from the concatenated schema file. 1812 * @param matchingRuleUses The set into which to place the matching rule 1813 * uses read from the concatenated schema file. 1814 * @param ldapSyntaxes The set into which to place the ldap syntaxes 1815 * read from the concatenated schema file. 1816 * 1817 * @throws IOException If a problem occurs while reading the 1818 * schema file elements. 1819 */ 1820 public static void readConcatenatedSchema(File concatSchemaFile, 1821 Set<String> attributeTypes, 1822 Set<String> objectClasses, 1823 Set<String> nameForms, 1824 Set<String> ditContentRules, 1825 Set<String> ditStructureRules, 1826 Set<String> matchingRuleUses, 1827 Set<String> ldapSyntaxes) 1828 throws IOException 1829 { 1830 try (BufferedReader reader = new BufferedReader(new FileReader(concatSchemaFile))) 1831 { 1832 String line; 1833 while ((line = reader.readLine()) != null) 1834 { 1835 parseSchemaLine(line, null, attributeTypes, objectClasses, 1836 nameForms, ditContentRules, ditStructureRules, matchingRuleUses, 1837 ldapSyntaxes); 1838 } 1839 } 1840 } 1841 1842 private static void parseSchemaLine(String definition, String fileName, 1843 Set<String> attributeTypes, 1844 Set<String> objectClasses, 1845 Set<String> nameForms, 1846 Set<String> ditContentRules, 1847 Set<String> ditStructureRules, 1848 Set<String> matchingRuleUses, 1849 Set<String> ldapSyntaxes) 1850 { 1851 String lowerLine = toLowerCase(definition); 1852 1853 try 1854 { 1855 if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC)) 1856 { 1857 addSchemaDefinition(attributeTypes, definition, ATTR_ATTRIBUTE_TYPES_LC, fileName); 1858 } 1859 else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC)) 1860 { 1861 addSchemaDefinition(objectClasses, definition, ATTR_OBJECTCLASSES_LC, fileName); 1862 } 1863 else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC)) 1864 { 1865 addSchemaDefinition(nameForms, definition, ATTR_NAME_FORMS_LC, fileName); 1866 } 1867 else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC)) 1868 { 1869 addSchemaDefinition(ditContentRules, definition, ATTR_DIT_CONTENT_RULES_LC, fileName); 1870 } 1871 else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC)) 1872 { 1873 addSchemaDefinition(ditStructureRules, definition, ATTR_DIT_STRUCTURE_RULES_LC, fileName); 1874 } 1875 else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC)) 1876 { 1877 addSchemaDefinition(matchingRuleUses, definition, ATTR_MATCHING_RULE_USE_LC, fileName); 1878 } 1879 else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC)) 1880 { 1881 addSchemaDefinition(ldapSyntaxes, definition, ATTR_LDAP_SYNTAXES_LC, fileName); 1882 } 1883 } catch (ParseException pe) 1884 { 1885 logger.error(ERR_SCHEMA_PARSE_LINE.get(definition, pe.getLocalizedMessage())); 1886 } 1887 } 1888 1889 private static void addSchemaDefinition(Set<String> definitions, String line, String attrName, String fileName) 1890 throws ParseException 1891 { 1892 definitions.add(getSchemaDefinition(line.substring(attrName.length()), fileName)); 1893 } 1894 1895 private static String getSchemaDefinition(String definition, String schemaFile) throws ParseException 1896 { 1897 if (definition.startsWith("::")) 1898 { 1899 // See OPENDJ-2792: the definition of the ds-cfg-csv-delimiter-char attribute type 1900 // had a space accidentally added after the closing parenthesis. 1901 // This was unfortunately interpreted as base64 1902 definition = ByteString.wrap(Base64.decode(definition.substring(2).trim())).toString(); 1903 } 1904 else if (definition.startsWith(":")) 1905 { 1906 definition = definition.substring(1).trim(); 1907 } 1908 else 1909 { 1910 throw new ParseException(ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION.get().toString(), 0); 1911 } 1912 1913 return addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile); 1914 } 1915 1916 /** 1917 * Compares the provided sets of schema element definitions and 1918 * writes any differences found into the given list of 1919 * modifications. 1920 * 1921 * @param oldElements The set of elements of the specified type 1922 * read from the previous concatenated schema 1923 * files. 1924 * @param newElements The set of elements of the specified type 1925 * read from the server's current schema. 1926 * @param elementType The attribute type associated with the 1927 * schema element being compared. 1928 * @param mods The list of modifications into which any 1929 * identified differences should be written. 1930 */ 1931 public static void compareConcatenatedSchema( 1932 Set<String> oldElements, 1933 Set<String> newElements, 1934 AttributeType elementType, 1935 List<Modification> mods) 1936 { 1937 AttributeBuilder builder = new AttributeBuilder(elementType); 1938 addModification(mods, DELETE, oldElements, newElements, builder); 1939 1940 builder.setAttributeDescription(AttributeDescription.create(elementType)); 1941 addModification(mods, ADD, newElements, oldElements, builder); 1942 } 1943 1944 private static void addModification(List<Modification> mods, ModificationType modType, Set<String> included, 1945 Set<String> excluded, AttributeBuilder builder) 1946 { 1947 for (String val : included) 1948 { 1949 if (!excluded.contains(val)) 1950 { 1951 builder.add(val); 1952 } 1953 } 1954 1955 if (!builder.isEmpty()) 1956 { 1957 mods.add(new Modification(modType, builder.toAttribute())); 1958 } 1959 } 1960 1961 /** 1962 * Destroys the structures maintained by the schema so that they are 1963 * no longer usable. This should only be called at the end of the 1964 * server shutdown process, and it can help detect inappropriate 1965 * cached references. 1966 */ 1967 @org.opends.server.types.PublicAPI( 1968 stability=org.opends.server.types.StabilityLevel.PRIVATE, 1969 mayInstantiate=false, 1970 mayExtend=false, 1971 mayInvoke=true) 1972 public synchronized void destroy() 1973 { 1974 if (schemaNG != null) 1975 { 1976 schemaNG = null; 1977 } 1978 1979 if (extraAttributes != null) 1980 { 1981 extraAttributes.clear(); 1982 extraAttributes = null; 1983 } 1984 } 1985 1986 /** 1987 * Update the schema using the provided schema updater. 1988 * <p> 1989 * An implicit lock is performed, so it is in general not necessary 1990 * to call the {code lock()} and {code unlock() methods. 1991 * However, these method should be used if/when the SchemaBuilder passed 1992 * as an argument to the updater is not used to return the schema 1993 * (see for example usage in {@code CoreSchemaProvider} class). This 1994 * case should remain exceptional. 1995 * 1996 * @param updater 1997 * the updater that returns a new schema 1998 * @throws DirectoryException if there is any problem updating the schema 1999 */ 2000 public void updateSchema(SchemaUpdater updater) throws DirectoryException 2001 { 2002 exclusiveLock.lock(); 2003 try 2004 { 2005 switchSchema(updater.update(new SchemaBuilder(schemaNG))); 2006 } 2007 finally 2008 { 2009 exclusiveLock.unlock(); 2010 } 2011 } 2012 2013 /** Interface to update a schema provided a schema builder. */ 2014 public interface SchemaUpdater 2015 { 2016 /** 2017 * Returns an updated schema. 2018 * 2019 * @param builder 2020 * The builder on the current schema 2021 * @return the new schema 2022 */ 2023 org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder); 2024 } 2025 2026 /** 2027 * Updates the schema option if the new value differs from the old value. 2028 * 2029 * @param <T> the schema option's type 2030 * @param option the schema option to update 2031 * @param newValue the new value for the schema option 2032 * @throws DirectoryException if there is any problem updating the schema 2033 */ 2034 public <T> void updateSchemaOption(final Option<T> option, final T newValue) throws DirectoryException 2035 { 2036 final T oldValue = schemaNG.getOption(option); 2037 if (!oldValue.equals(newValue)) 2038 { 2039 updateSchema(new SchemaUpdater() 2040 { 2041 @Override 2042 public org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder) 2043 { 2044 return builder.setOption(option, newValue).toSchema(); 2045 } 2046 }); 2047 } 2048 } 2049 2050 /** Takes an exclusive lock on the schema. */ 2051 public void exclusiveLock() 2052 { 2053 exclusiveLock.lock(); 2054 } 2055 2056 /** Releases an exclusive lock on the schema. */ 2057 public void exclusiveUnlock() 2058 { 2059 exclusiveLock.unlock(); 2060 } 2061 2062 /** 2063 * Adds the provided schema file to the provided schema element definition. 2064 * 2065 * @param definition 2066 * The schema element definition 2067 * @param schemaFile 2068 * The name of the schema file to include in the definition 2069 * @return The definition string of the element 2070 * including the X-SCHEMA-FILE extension. 2071 */ 2072 public static String addSchemaFileToElementDefinitionIfAbsent(String definition, String schemaFile) 2073 { 2074 if (schemaFile != null && !definition.contains(SCHEMA_PROPERTY_FILENAME)) 2075 { 2076 int pos = definition.lastIndexOf(')'); 2077 return definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )"; 2078 } 2079 return definition; 2080 } 2081 2082 private void switchSchema(org.forgerock.opendj.ldap.schema.Schema newSchema) throws DirectoryException 2083 { 2084 rejectSchemaWithWarnings(newSchema); 2085 schemaNG = newSchema.asNonStrictSchema(); 2086 if (DirectoryServer.getSchema() == this) 2087 { 2088 org.forgerock.opendj.ldap.schema.Schema.setDefaultSchema(schemaNG); 2089 } 2090 } 2091 2092 private void rejectSchemaWithWarnings(org.forgerock.opendj.ldap.schema.Schema newSchema) throws DirectoryException 2093 { 2094 Collection<LocalizableMessage> warnings = newSchema.getWarnings(); 2095 if (!warnings.isEmpty()) 2096 { 2097 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 2098 ERR_SCHEMA_HAS_WARNINGS.get(warnings.size(), Utils.joinAsString("; ", warnings))); 2099 } 2100 } 2101 2102 /** 2103 * Replaces an existing object class by another object class. 2104 * 2105 * @param objectClass 2106 * Object class to register to the schema. 2107 * @param existingClass 2108 * Object class to remove from the schema. 2109 * @param schemaFile 2110 * The schema file which the new object class belongs to. 2111 * @throws DirectoryException 2112 * If an errors occurs. 2113 */ 2114 public void replaceObjectClass(ObjectClass objectClass, ObjectClass existingClass, String schemaFile) 2115 throws DirectoryException 2116 { 2117 exclusiveLock.lock(); 2118 try 2119 { 2120 SchemaBuilder builder = new SchemaBuilder(schemaNG); 2121 builder.removeObjectClass(existingClass.getNameOrOID()); 2122 registerObjectClass0(builder, objectClass, schemaFile, false); 2123 switchSchema(builder.toSchema()); 2124 } 2125 finally 2126 { 2127 exclusiveLock.unlock(); 2128 } 2129 } 2130}