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 java.io.BufferedReader; 020import java.io.BufferedWriter; 021import java.io.File; 022import java.io.FileReader; 023import java.io.FileWriter; 024import java.io.FilenameFilter; 025import java.io.IOException; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.HashMap; 031import java.util.LinkedHashSet; 032import java.util.LinkedList; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036import java.util.TreeSet; 037import java.util.concurrent.ConcurrentHashMap; 038import java.util.concurrent.locks.Lock; 039import java.util.concurrent.locks.ReentrantLock; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0; 043import org.forgerock.i18n.LocalizedIllegalArgumentException; 044import org.forgerock.i18n.slf4j.LocalizedLogger; 045import org.forgerock.opendj.ldap.ByteString; 046import org.forgerock.opendj.ldap.ModificationType; 047import org.forgerock.opendj.ldap.ResultCode; 048import org.forgerock.opendj.ldap.schema.AttributeType; 049import org.forgerock.opendj.ldap.schema.ConflictingSchemaElementException; 050import org.forgerock.opendj.ldap.schema.CoreSchema; 051import org.forgerock.opendj.ldap.schema.MatchingRule; 052import org.forgerock.opendj.ldap.schema.SchemaBuilder; 053import org.forgerock.opendj.ldap.schema.Syntax; 054import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException; 055import org.forgerock.util.Option; 056import org.forgerock.util.Utils; 057import org.opends.server.core.DirectoryServer; 058import org.opends.server.core.SchemaConfigManager; 059import org.opends.server.schema.DITContentRuleSyntax; 060import org.opends.server.schema.DITStructureRuleSyntax; 061import org.opends.server.schema.MatchingRuleUseSyntax; 062import org.opends.server.schema.NameFormSyntax; 063import org.opends.server.schema.ObjectClassSyntax; 064import org.opends.server.schema.SomeSchemaElement; 065import org.opends.server.util.ServerConstants; 066import org.opends.server.util.StaticUtils; 067 068import static org.opends.messages.BackendMessages.*; 069import static org.opends.messages.CoreMessages.*; 070import static org.opends.messages.SchemaMessages.*; 071import static org.opends.server.config.ConfigConstants.*; 072import static org.opends.server.types.CommonSchemaElements.*; 073import static org.opends.server.util.CollectionUtils.*; 074import static org.opends.server.util.ServerConstants.*; 075import static org.opends.server.util.StaticUtils.*; 076 077/** 078 * This class defines a data structure that holds information about 079 * the components of the Directory Server schema. It includes the 080 * following kinds of elements: 081 * 082 * <UL> 083 * <LI>Attribute type definitions</LI> 084 * <LI>Objectclass definitions</LI> 085 * <LI>Attribute syntax definitions</LI> 086 * <LI>Matching rule definitions</LI> 087 * <LI>Matching rule use definitions</LI> 088 * <LI>DIT content rule definitions</LI> 089 * <LI>DIT structure rule definitions</LI> 090 * <LI>Name form definitions</LI> 091 * </UL> 092 * It always uses non-strict {@link org.forgerock.opendj.ldap.schema.Schema} under the hood. 093 */ 094@org.opends.server.types.PublicAPI( 095 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 096 mayInstantiate=false, 097 mayExtend=false, 098 mayInvoke=true) 099public final class Schema 100{ 101 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 102 103 /** 104 * Provides for each attribute type having at least one subordinate type the complete list of 105 * its descendants. 106 */ 107 private Map<AttributeType, List<AttributeType>> subordinateTypes; 108 109 /** 110 * The set of objectclass definitions for this schema, mapped between the 111 * lowercase names and OID for the definition and the objectclass itself. 112 */ 113 private ConcurrentHashMap<String,ObjectClass> objectClasses; 114 115 /** 116 * The set of matching rule uses for this schema, mapped between the matching 117 * rule for the definition and the matching rule use itself. 118 */ 119 private ConcurrentHashMap<MatchingRule,MatchingRuleUse> 120 matchingRuleUses; 121 122 /** 123 * The set of DIT content rules for this schema, mapped between the structural 124 * objectclass for the definition and the DIT content rule itself. 125 */ 126 private ConcurrentHashMap<ObjectClass,DITContentRule> 127 ditContentRules; 128 129 /** 130 * The set of DIT structure rules for this schema, mapped between the name 131 * form for the definition and the DIT structure rule itself. 132 */ 133 private ConcurrentHashMap<Integer,DITStructureRule> 134 ditStructureRulesByID; 135 136 /** 137 * The set of DIT structure rules for this schema, mapped between the name 138 * form for the definition and the DIT structure rule itself. 139 */ 140 private ConcurrentHashMap<NameForm,DITStructureRule> 141 ditStructureRulesByNameForm; 142 143 /** 144 * The set of name forms for this schema, mapped between the structural 145 * objectclass for the definition and the list of name forms. 146 */ 147 private ConcurrentHashMap<ObjectClass,List<NameForm>> 148 nameFormsByOC; 149 150 /** 151 * The set of name forms for this schema, mapped between the names/OID and the 152 * name form itself. 153 */ 154 private ConcurrentHashMap<String,NameForm> nameFormsByName; 155 156 /** 157 * The set of ldap syntax descriptions for this schema, mapped the OID and the 158 * ldap syntax description itself. 159 */ 160 private ConcurrentHashMap<String,LDAPSyntaxDescription> 161 ldapSyntaxDescriptions; 162 163 /** The oldest modification timestamp for any schema configuration file. */ 164 private long oldestModificationTime; 165 166 /** The youngest modification timestamp for any schema configuration file. */ 167 private long youngestModificationTime; 168 169 /** 170 * A set of extra attributes that are not used directly by the schema but may 171 * be used by other component to store information in the schema. 172 * <p> 173 * ex : Replication uses this to store its state and GenerationID. 174 */ 175 private Map<String, Attribute> extraAttributes = new HashMap<>(); 176 177 /** 178 * The SDK schema. 179 * <p> 180 * It will progressively take over server implementation of the schema. 181 * <p> 182 * @GuardedBy("exclusiveLock") 183 */ 184 private volatile org.forgerock.opendj.ldap.schema.Schema schemaNG; 185 186 /** Guards updates to the schema. */ 187 private final Lock exclusiveLock = new ReentrantLock(); 188 189 /** 190 * Creates a new schema structure with all elements initialized but empty. 191 * 192 * @param schemaNG 193 * The SDK schema 194 * @throws DirectoryException 195 * if the schema has warnings 196 */ 197 public Schema(org.forgerock.opendj.ldap.schema.Schema schemaNG) throws DirectoryException 198 { 199 switchSchema(schemaNG); 200 201 objectClasses = new ConcurrentHashMap<String,ObjectClass>(); 202 matchingRuleUses = new ConcurrentHashMap<MatchingRule,MatchingRuleUse>(); 203 ditContentRules = new ConcurrentHashMap<ObjectClass,DITContentRule>(); 204 ditStructureRulesByID = new ConcurrentHashMap<Integer,DITStructureRule>(); 205 ditStructureRulesByNameForm = new ConcurrentHashMap<NameForm,DITStructureRule>(); 206 nameFormsByOC = new ConcurrentHashMap<ObjectClass,List<NameForm>>(); 207 nameFormsByName = new ConcurrentHashMap<String,NameForm>(); 208 ldapSyntaxDescriptions = new ConcurrentHashMap<String,LDAPSyntaxDescription>(); 209 subordinateTypes = new ConcurrentHashMap<AttributeType,List<AttributeType>>(); 210 211 oldestModificationTime = System.currentTimeMillis(); 212 youngestModificationTime = oldestModificationTime; 213 } 214 215 /** 216 * Returns the SDK schema. 217 * 218 * @return the SDK schema 219 */ 220 public org.forgerock.opendj.ldap.schema.Schema getSchemaNG() 221 { 222 return schemaNG; 223 } 224 225 /** 226 * Retrieves the attribute type definitions for this schema. 227 * 228 * @return The attribute type definitions for this schema. 229 */ 230 public Collection<AttributeType> getAttributeTypes() 231 { 232 return schemaNG.getAttributeTypes(); 233 } 234 235 /** 236 * Indicates whether this schema definition includes an attribute 237 * type with the provided name or OID. 238 * 239 * @param nameOrOid The name or OID for which to make the determination, ignoring case considerations 240 * @return {@code true} if this schema contains an attribute type 241 * with the provided name or OID, or {@code false} if not. 242 */ 243 public boolean hasAttributeType(String nameOrOid) 244 { 245 return schemaNG.hasAttributeType(nameOrOid); 246 } 247 248 /** 249 * Retrieves the attribute type definition with the specified name or OID. 250 * 251 * @param nameOrOid 252 * The name or OID of the attribute type to retrieve, ignoring case considerations 253 * @return The requested attribute type 254 */ 255 public AttributeType getAttributeType(String nameOrOid) 256 { 257 try 258 { 259 return schemaNG.getAttributeType(nameOrOid); 260 } 261 catch (UnknownSchemaElementException e) 262 { 263 // It should never happen because we only use non-strict schemas 264 throw new RuntimeException(e); 265 } 266 } 267 268 /** 269 * Retrieves the attribute type definition with the specified name or OID. 270 * 271 * @param nameOrOid 272 * The name or OID of the attribute type to retrieve, ignoring case considerations 273 * @param syntax 274 * The syntax to use when creating the temporary "place-holder" attribute type. 275 * @return The requested attribute type 276 */ 277 public AttributeType getAttributeType(String nameOrOid, Syntax syntax) 278 { 279 try 280 { 281 return schemaNG.getAttributeType(nameOrOid, syntax); 282 } 283 catch (UnknownSchemaElementException e) 284 { 285 // It should never happen because we only use non-strict schemas 286 throw new RuntimeException(e); 287 } 288 } 289 290 /** 291 * Parses an attribute type from its provided definition. 292 * 293 * @param definition 294 * The definition of the attribute type 295 * @return the attribute type 296 * @throws DirectoryException 297 * If an error occurs 298 */ 299 public AttributeType parseAttributeType(final String definition) throws DirectoryException 300 { 301 try 302 { 303 SchemaBuilder builder = new SchemaBuilder(schemaNG); 304 builder.addAttributeType(definition, true); 305 org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema(); 306 rejectSchemaWithWarnings(newSchema); 307 return newSchema.getAttributeType(parseAttributeTypeOID(definition)); 308 } 309 catch (UnknownSchemaElementException e) 310 { 311 // this should never happen 312 LocalizableMessage msg = ERR_ATTR_TYPE_CANNOT_REGISTER.get(definition); 313 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e); 314 } 315 catch (LocalizedIllegalArgumentException e) 316 { 317 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 318 } 319 } 320 321 /** 322 * Registers a list of attribute types from their provided definitions. 323 * <p> 324 * This method allows to do only one schema change for multiple definitions, 325 * thus avoiding the cost (and the issue of stale schema references) of rebuilding a new schema for each definition. 326 * 327 * @param definitions 328 * The definitions of the attribute types 329 * @param schemaFile 330 * The schema file where these definitions belong, can be {@code null} 331 * @param overwrite 332 * Indicates whether to overwrite the attribute 333 * type if it already exists based on OID or name 334 * @throws DirectoryException 335 * If an error occurs 336 */ 337 public void registerAttributeTypes(final List<String> definitions, final String schemaFile, final boolean overwrite) 338 throws DirectoryException 339 { 340 exclusiveLock.lock(); 341 try 342 { 343 SchemaBuilder builder = new SchemaBuilder(schemaNG); 344 for (String definition : definitions) 345 { 346 String defWithFile = schemaFile != null ? 347 addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile) : definition; 348 builder.addAttributeType(defWithFile, overwrite); 349 } 350 switchSchema(builder.toSchema()); 351 352 for (String definition : definitions) 353 { 354 updateSubordinateTypes(schemaNG.getAttributeType(parseAttributeTypeOID(definition))); 355 } 356 } 357 catch (ConflictingSchemaElementException | UnknownSchemaElementException e) 358 { 359 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 360 } 361 catch (LocalizedIllegalArgumentException e) 362 { 363 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 364 } 365 finally 366 { 367 exclusiveLock.unlock(); 368 } 369 } 370 371 /** 372 * Registers an attribute type from its provided definition. 373 * 374 * @param definition 375 * The definition of the attribute type 376 * @param schemaFile 377 * The schema file where this definition belongs, 378 * maybe {@code null} 379 * @param overwrite 380 * Indicates whether to overwrite the attribute 381 * type if it already exists based on OID or name 382 * @throws DirectoryException 383 * If an error occurs 384 */ 385 public void registerAttributeType(final String definition, final String schemaFile, final boolean overwrite) 386 throws DirectoryException 387 { 388 registerAttributeTypes(Arrays.asList(definition), schemaFile, overwrite); 389 } 390 391 /** 392 * Registers the provided attribute type definition with this schema. 393 * 394 * @param attributeType 395 * The attribute type to register with this schema. 396 * @param overwriteExisting 397 * Indicates whether to overwrite an existing mapping if there are 398 * any conflicts (i.e., another attribute type with the same OID or 399 * name). 400 * @throws DirectoryException 401 * If a conflict is encountered and the 402 * <CODE>overwriteExisting</CODE> flag is set to <CODE>false</CODE> 403 */ 404 public void registerAttributeType(final AttributeType attributeType, final boolean overwriteExisting) 405 throws DirectoryException 406 { 407 registerAttributeType(attributeType, null, overwriteExisting); 408 } 409 410 /** 411 * Registers the provided attribute type definition with this schema. 412 * 413 * @param attributeType 414 * The attribute type to register with this schema. 415 * @param schemaFile 416 * The schema file where this definition belongs, maybe {@code null} 417 * @param overwriteExisting 418 * Indicates whether to overwrite an existing mapping if there are 419 * any conflicts (i.e., another attribute type with the same OID or 420 * name). 421 * @throws DirectoryException 422 * If a conflict is encountered and the 423 * <CODE>overwriteExisting</CODE> flag is set to <CODE>false</CODE> 424 */ 425 public void registerAttributeType(final AttributeType attributeType, final String schemaFile, 426 final boolean overwriteExisting) throws DirectoryException 427 { 428 exclusiveLock.lock(); 429 try 430 { 431 SchemaBuilder builder = new SchemaBuilder(schemaNG); 432 AttributeType.Builder b = builder.buildAttributeType(attributeType); 433 if (schemaFile != null) 434 { 435 b.removeExtraProperty(SCHEMA_PROPERTY_FILENAME).extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile); 436 } 437 if (overwriteExisting) 438 { 439 b.addToSchemaOverwrite(); 440 } 441 else 442 { 443 b.addToSchema(); 444 } 445 switchSchema(builder.toSchema()); 446 447 updateSubordinateTypes(attributeType); 448 } 449 catch (LocalizedIllegalArgumentException e) 450 { 451 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 452 } 453 finally 454 { 455 exclusiveLock.unlock(); 456 } 457 } 458 459 private String parseAttributeTypeOID(String definition) throws DirectoryException 460 { 461 return parseOID(definition, ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE); 462 } 463 464 /** 465 * Returns the OID from the provided attribute type definition, assuming the 466 * definition is valid. 467 * <p> 468 * This method does not perform any check. 469 * 470 * @param definition 471 * The definition, assumed to be valid 472 * @param parsingErrorResultCode the result code to use if a problem occurs while parsing the definition 473 * @param parsingErrorMsg the message to use if a problem occurs while parsing the definition 474 * @return the OID, which is never {@code null} 475 * @throws DirectoryException 476 * If a problem occurs while parsing the definition 477 */ 478 public static String parseOID(String definition, ResultCode parsingErrorResultCode, Arg0 parsingErrorMsg) 479 throws DirectoryException 480 { 481 try 482 { 483 int pos = 0; 484 int length = definition.length(); 485 // Skip over any leading whitespace. 486 while (pos < length && (definition.charAt(pos) == ' ')) 487 { 488 pos++; 489 } 490 // Skip the open parenthesis. 491 pos++; 492 // Skip over any spaces immediately following the opening parenthesis. 493 while (pos < length && definition.charAt(pos) == ' ') 494 { 495 pos++; 496 } 497 // The next set of characters must be the OID. 498 int oidStartPos = pos; 499 while (pos < length && definition.charAt(pos) != ' ' && definition.charAt(pos) != ')') 500 { 501 pos++; 502 } 503 return definition.substring(oidStartPos, pos); 504 } 505 catch (IndexOutOfBoundsException e) 506 { 507 throw new DirectoryException(parsingErrorResultCode, parsingErrorMsg.get(), e); 508 } 509 } 510 511 /** 512 * Deregisters the provided attribute type definition with this schema. 513 * 514 * @param attributeType The attribute type to deregister with this schema. 515 * @throws DirectoryException 516 * If the attribute type is referenced by another schema element. 517 */ 518 public void deregisterAttributeType(final AttributeType attributeType) throws DirectoryException 519 { 520 exclusiveLock.lock(); 521 try 522 { 523 SchemaBuilder builder = new SchemaBuilder(schemaNG); 524 if (builder.removeAttributeType(attributeType.getNameOrOID())) 525 { 526 AttributeType superiorType = attributeType.getSuperiorType(); 527 if (superiorType != null) 528 { 529 deregisterSubordinateType(attributeType, superiorType); 530 } 531 switchSchema(builder.toSchema()); 532 } 533 } 534 finally 535 { 536 exclusiveLock.unlock(); 537 } 538 } 539 540 private void updateSubordinateTypes(AttributeType attributeType) 541 { 542 AttributeType superiorType = attributeType.getSuperiorType(); 543 if (superiorType != null) 544 { 545 registerSubordinateType(attributeType, superiorType); 546 } 547 } 548 549 /** 550 * Registers the provided attribute type as a subtype of the given 551 * superior attribute type, recursively following any additional 552 * elements in the superior chain. 553 * 554 * @param attributeType The attribute type to be registered as a 555 * subtype for the given superior type. 556 * @param superiorType The superior type for which to register 557 * the given attribute type as a subtype. 558 */ 559 private void registerSubordinateType(AttributeType attributeType, AttributeType superiorType) 560 { 561 List<AttributeType> subTypes = subordinateTypes.get(superiorType); 562 if (subTypes == null) 563 { 564 subordinateTypes.put(superiorType, newLinkedList(attributeType)); 565 } 566 else if (!subTypes.contains(attributeType)) 567 { 568 subTypes.add(attributeType); 569 570 AttributeType higherSuperior = superiorType.getSuperiorType(); 571 if (higherSuperior != null) 572 { 573 registerSubordinateType(attributeType, higherSuperior); 574 } 575 } 576 } 577 578 /** 579 * Deregisters the provided attribute type as a subtype of the given 580 * superior attribute type, recursively following any additional 581 * elements in the superior chain. 582 * 583 * @param attributeType The attribute type to be deregistered as a 584 * subtype for the given superior type. 585 * @param superiorType The superior type for which to deregister 586 * the given attribute type as a subtype. 587 */ 588 private void deregisterSubordinateType(AttributeType attributeType, AttributeType superiorType) 589 { 590 List<AttributeType> subTypes = subordinateTypes.get(superiorType); 591 if (subTypes != null && subTypes.remove(attributeType)) 592 { 593 AttributeType higherSuperior = superiorType.getSuperiorType(); 594 if (higherSuperior != null) 595 { 596 deregisterSubordinateType(attributeType, higherSuperior); 597 } 598 } 599 } 600 601 /** 602 * Retrieves the set of subtypes registered for the given attribute 603 * type. 604 * 605 * @param attributeType The attribute type for which to retrieve 606 * the set of registered subtypes. 607 * 608 * @return The set of subtypes registered for the given attribute 609 * type, or an empty set if there are no subtypes 610 * registered for the attribute type. 611 */ 612 public List<AttributeType> getSubTypes(AttributeType attributeType) 613 { 614 List<AttributeType> subTypes = subordinateTypes.get(attributeType); 615 return subTypes != null ? subTypes : Collections.<AttributeType> emptyList(); 616 } 617 618 619 620 /** 621 * Retrieves the objectclass definitions for this schema, as a 622 * mapping between the lowercase names and OIDs for the objectclass 623 * and the objectclass itself. Each objectclass may be associated 624 * with multiple keys (once for the OID and again for each name). 625 * The contents of the returned mapping must not be altered. 626 * 627 * @return The objectclass definitions for this schema. 628 */ 629 public ConcurrentHashMap<String,ObjectClass> getObjectClasses() 630 { 631 return objectClasses; 632 } 633 634 635 636 /** 637 * Indicates whether this schema definition includes an objectclass 638 * with the provided name or OID. 639 * 640 * @param lowerName The name or OID for which to make the 641 * determination, formatted in all lowercase 642 * characters. 643 * 644 * @return {@code true} if this schema contains an objectclass with 645 * the provided name or OID, or {@code false} if not. 646 */ 647 public boolean hasObjectClass(String lowerName) 648 { 649 return objectClasses.containsKey(lowerName); 650 } 651 652 653 654 /** 655 * Retrieves the objectclass definition with the specified name or 656 * OID. 657 * 658 * @param lowerName The name or OID of the objectclass to 659 * retrieve, formatted in all lowercase 660 * characters. 661 * 662 * @return The requested objectclass, or <CODE>null</CODE> if no 663 * class is registered with the provided name or OID. 664 */ 665 public ObjectClass getObjectClass(String lowerName) 666 { 667 return objectClasses.get(lowerName); 668 } 669 670 671 672 /** 673 * Registers the provided objectclass definition with this schema. 674 * 675 * @param objectClass The objectclass to register with this 676 * schema. 677 * @param overwriteExisting Indicates whether to overwrite an 678 * existing mapping if there are any 679 * conflicts (i.e., another objectclass 680 * with the same OID or name). 681 * 682 * @throws DirectoryException If a conflict is encountered and the 683 * <CODE>overwriteExisting</CODE> flag 684 * is set to <CODE>false</CODE>. 685 */ 686 public void registerObjectClass(ObjectClass objectClass, 687 boolean overwriteExisting) 688 throws DirectoryException 689 { 690 exclusiveLock.lock(); 691 try 692 { 693 if (! overwriteExisting) 694 { 695 String oid = toLowerCase(objectClass.getOID()); 696 if (objectClasses.containsKey(oid)) 697 { 698 ObjectClass conflictingClass = objectClasses.get(oid); 699 700 LocalizableMessage message = ERR_SCHEMA_CONFLICTING_OBJECTCLASS_OID. 701 get(objectClass.getNameOrOID(), oid, 702 conflictingClass.getNameOrOID()); 703 throw new DirectoryException( 704 ResultCode.CONSTRAINT_VIOLATION, message); 705 } 706 707 for (String name : objectClass.getNormalizedNames()) 708 { 709 if (objectClasses.containsKey(name)) 710 { 711 ObjectClass conflictingClass = objectClasses.get(name); 712 713 LocalizableMessage message = ERR_SCHEMA_CONFLICTING_OBJECTCLASS_NAME. 714 get(objectClass.getNameOrOID(), name, 715 conflictingClass.getNameOrOID()); 716 throw new DirectoryException( 717 ResultCode.CONSTRAINT_VIOLATION, message); 718 } 719 } 720 } 721 722 ObjectClass old = objectClasses.put(toLowerCase(objectClass.getOID()), 723 objectClass); 724 if (old != null && old != objectClass) 725 { 726 // Mark the old object class as stale so that caches (such as compressed 727 // schema) can detect changes. 728 old.setDirty(); 729 } 730 731 for (String name : objectClass.getNormalizedNames()) 732 { 733 objectClasses.put(name, objectClass); 734 } 735 } 736 finally 737 { 738 exclusiveLock.unlock(); 739 } 740 } 741 742 743 744 /** 745 * Deregisters the provided objectclass definition with this schema. 746 * 747 * @param objectClass The objectclass to deregister with this 748 * schema. 749 */ 750 public void deregisterObjectClass(ObjectClass objectClass) 751 { 752 synchronized (objectClasses) 753 { 754 if (objectClasses.remove(toLowerCase(objectClass.getOID()), objectClass)) 755 { 756 // Mark the old object class as stale so that caches (such as 757 // compressed schema) can detect changes. 758 objectClass.setDirty(); 759 } 760 761 for (String name : objectClass.getNormalizedNames()) 762 { 763 objectClasses.remove(name, objectClass); 764 } 765 } 766 } 767 768 769 770 /** 771 * Retrieves the attribute syntax definitions for this schema. 772 * 773 * @return The attribute syntax definitions for this schema. 774 */ 775 public Collection<Syntax> getSyntaxes() 776 { 777 return schemaNG.getSyntaxes(); 778 } 779 780 781 782 /** 783 * Indicates whether this schema definition includes an attribute 784 * syntax with the provided OID. 785 * 786 * @param oid The OID for which to make the determination 787 * @return {@code true} if this schema contains an attribute syntax 788 * with the provided OID, or {@code false} if not. 789 */ 790 public boolean hasSyntax(String oid) 791 { 792 return schemaNG.hasSyntax(oid); 793 } 794 795 /** 796 * Retrieves the attribute syntax definition with the OID. 797 * 798 * @param oid The OID of the attribute syntax to retrieve. 799 * @return The requested attribute syntax, 800 * or {@code null} if no syntax is registered with the provided OID. 801 */ 802 public Syntax getSyntax(String oid) 803 { 804 return schemaNG.getSyntax(oid); 805 } 806 807 /** 808 * Retrieves the default attribute syntax that should be used for attributes 809 * that are not defined in the server schema. 810 * 811 * @return The default attribute syntax that should be used for attributes 812 * that are not defined in the server schema. 813 */ 814 public Syntax getDefaultSyntax() 815 { 816 return schemaNG.getSyntax(CoreSchema.getDirectoryStringSyntax().getOID()); 817 } 818 819 /** 820 * Registers the provided attribute syntax definition with this 821 * schema. 822 * 823 * @param syntax The attribute syntax to register with 824 * this schema. 825 * @param overwriteExisting Indicates whether to overwrite an 826 * existing mapping if there are any 827 * conflicts (i.e., another attribute 828 * syntax with the same OID). 829 * 830 * @throws DirectoryException If a conflict is encountered and the 831 * <CODE>overwriteExisting</CODE> flag 832 * is set to <CODE>false</CODE> 833 */ 834 public void registerSyntax(final Syntax syntax, final boolean overwriteExisting) throws DirectoryException 835 { 836 exclusiveLock.lock(); 837 try 838 { 839 SchemaBuilder builder = new SchemaBuilder(schemaNG); 840 Syntax.Builder b = builder.buildSyntax(syntax); 841 if (overwriteExisting) 842 { 843 b.addToSchemaOverwrite(); 844 } 845 else 846 { 847 b.addToSchema(); 848 } 849 switchSchema(builder.toSchema()); 850 } 851 catch (LocalizedIllegalArgumentException e) 852 { 853 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 854 } 855 finally 856 { 857 exclusiveLock.unlock(); 858 } 859 } 860 861 private void registerSyntax(final String definition, final boolean overwriteExisting) throws DirectoryException 862 { 863 exclusiveLock.lock(); 864 try 865 { 866 org.forgerock.opendj.ldap.schema.Schema newSchema = 867 new SchemaBuilder(schemaNG) 868 .addSyntax(definition, overwriteExisting) 869 .toSchema(); 870 switchSchema(newSchema); 871 } 872 catch (ConflictingSchemaElementException | UnknownSchemaElementException e) 873 { 874 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 875 } 876 catch (LocalizedIllegalArgumentException e) 877 { 878 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e); 879 } 880 finally 881 { 882 exclusiveLock.unlock(); 883 } 884 } 885 886 /** 887 * Deregisters the provided attribute syntax definition with this schema. 888 * 889 * @param syntax The attribute syntax to deregister with this schema. 890 * @throws DirectoryException 891 * If the LDAP syntax is referenced by another schema element. 892 */ 893 public void deregisterSyntax(final Syntax syntax) throws DirectoryException 894 { 895 exclusiveLock.lock(); 896 try 897 { 898 SchemaBuilder builder = new SchemaBuilder(schemaNG); 899 builder.removeSyntax(syntax.getOID()); 900 switchSchema(builder.toSchema()); 901 } 902 finally 903 { 904 exclusiveLock.unlock(); 905 } 906 } 907 908 909 910 /** 911 * Retrieves the ldap syntax definitions for this schema, as a 912 * mapping between the OID for the syntax and the ldap syntax 913 * definition itself. Each ldap syntax should only be present once, 914 * since its only key is its OID. The contents of the returned 915 * mapping must not be altered. 916 * 917 * @return The ldap syntax definitions for this schema. 918 */ 919 public ConcurrentHashMap<String,LDAPSyntaxDescription> getLdapSyntaxDescriptions() 920 { 921 return ldapSyntaxDescriptions; 922 } 923 924 /** 925 * Retrieves the ldap syntax definition with the OID. 926 * 927 * @param lowerName The OID of the ldap syntax to retrieve, 928 * formatted in all lowercase characters. 929 * 930 * @return The requested ldap syntax, or <CODE>null</CODE> if 931 * no syntax is registered with the provided OID. 932 */ 933 public LDAPSyntaxDescription getLdapSyntaxDescription(String lowerName) 934 { 935 return ldapSyntaxDescriptions.get(lowerName); 936 } 937 938 /** 939 * Registers the provided ldap syntax description with this schema. 940 * 941 * @param definition 942 * The ldap syntax definition to register with this schema. 943 * @param overwriteExisting 944 * Indicates whether to overwrite an existing mapping if there are 945 * any conflicts (i.e., another ldap syntax with the same OID). 946 * @throws DirectoryException 947 * If a conflict is encountered and <CODE>overwriteExisting</CODE> 948 * flag is set to <CODE>false</CODE> 949 */ 950 public void registerLdapSyntaxDescription(String definition, boolean overwriteExisting) 951 throws DirectoryException 952 { 953 /** 954 * ldapsyntaxes is part real and part virtual. For any 955 * ldapsyntaxes attribute this is real, an LDAPSyntaxDescription 956 * object is created and stored with the schema. Also, the 957 * associated LDAPSyntaxDescriptionSyntax is added into the 958 * virtual syntax set to make this available through virtual 959 * ldapsyntaxes attribute. 960 */ 961 exclusiveLock.lock(); 962 try 963 { 964 String oid = parseAttributeTypeOID(definition); 965 if (! overwriteExisting && ldapSyntaxDescriptions.containsKey(oid)) 966 { 967 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 968 ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_LDAP_SYNTAX.get(oid)); 969 } 970 971 // Register the attribute syntax with the schema. 972 // It will ensure syntax is available along with the other virtual values for ldapsyntaxes. 973 registerSyntax(definition, overwriteExisting); 974 975 Syntax syntax = schemaNG.getSyntax(oid); 976 LDAPSyntaxDescription syntaxDesc = new LDAPSyntaxDescription(definition, oid, syntax.getExtraProperties()); 977 ldapSyntaxDescriptions.put(oid, syntaxDesc); 978 } 979 finally 980 { 981 exclusiveLock.unlock(); 982 } 983 } 984 985 986 987 /** 988 * Deregisters the provided ldap syntax description with this schema. 989 * 990 * @param syntaxDesc 991 * The ldap syntax to deregister with this schema. 992 * @throws DirectoryException 993 * If the LDAP syntax is referenced by another schema element. 994 */ 995 public void deregisterLdapSyntaxDescription(LDAPSyntaxDescription syntaxDesc) throws DirectoryException 996 { 997 exclusiveLock.lock(); 998 try 999 { 1000 // Remove the real value. 1001 ldapSyntaxDescriptions.remove(toLowerCase(syntaxDesc.getOID()), syntaxDesc); 1002 1003 // Get rid of this from the virtual ldapsyntaxes. 1004 deregisterSyntax(getSyntax(syntaxDesc.getOID())); 1005 } 1006 finally 1007 { 1008 exclusiveLock.unlock(); 1009 } 1010 } 1011 1012 1013 1014 /** 1015 * Retrieves all matching rule definitions for this schema. 1016 * 1017 * @return The matching rule definitions for this schema 1018 */ 1019 public Collection<MatchingRule> getMatchingRules() 1020 { 1021 return schemaNG.getMatchingRules(); 1022 } 1023 1024 1025 1026 /** 1027 * Indicates whether this schema definition includes a matching rule 1028 * with the provided name or OID. 1029 * 1030 * @param nameOrOid The name or OID for which to make the determination, ignoring case considerations 1031 * @return {@code true} if this schema contains a matching rule 1032 * with the provided name or OID, or {@code false} if not. 1033 */ 1034 public boolean hasMatchingRule(String nameOrOid) 1035 { 1036 return schemaNG.hasMatchingRule(nameOrOid); 1037 } 1038 1039 1040 1041 /** 1042 * Retrieves the matching rule definition with the specified name or OID. 1043 * 1044 * @param nameOrOid The name or OID of the matching rule to retrieve, ignoring case considerations 1045 * @return The requested matching rule, or {@code null} if no rule is registered with the provided name or OID. 1046 */ 1047 public MatchingRule getMatchingRule(String nameOrOid) 1048 { 1049 return schemaNG.getMatchingRule(nameOrOid); 1050 } 1051 1052 1053 1054 /** 1055 * Registers the provided matching rule definition with this schema. 1056 * 1057 * @param matchingRule 1058 * The matching rule to register with this schema. 1059 * @param overwriteExisting 1060 * Indicates whether to overwrite an existing mapping if there are 1061 * any conflicts (i.e., another matching rule with the same OID or 1062 * name). 1063 * @throws DirectoryException 1064 * If a conflict is encountered and the 1065 * {@code overwriteExisting} flag is set to {@code false} 1066 */ 1067 public void registerMatchingRule(final MatchingRule matchingRule, final boolean overwriteExisting) 1068 throws DirectoryException 1069 { 1070 exclusiveLock.lock(); 1071 try 1072 { 1073 if (!overwriteExisting) 1074 { 1075 // check all names of the matching rules because it is not checked by SDK schema 1076 for (String name : matchingRule.getNames()) 1077 { 1078 if (schemaNG.hasMatchingRule(name)) 1079 { 1080 Collection<MatchingRule> conflictingRules = schemaNG.getMatchingRulesWithName(name); 1081 // there should be only one 1082 MatchingRule conflictingRule = conflictingRules.iterator().next(); 1083 1084 LocalizableMessage message = 1085 ERR_SCHEMA_CONFLICTING_MR_NAME.get(matchingRule.getOID(), name, conflictingRule.getOID()); 1086 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1087 } 1088 } 1089 } 1090 1091 // now register the matching rule 1092 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1093 MatchingRule.Builder b = builder.buildMatchingRule(matchingRule); 1094 if (overwriteExisting) 1095 { 1096 b.addToSchemaOverwrite(); 1097 } 1098 else 1099 { 1100 b.addToSchema(); 1101 } 1102 switchSchema(builder.toSchema()); 1103 } 1104 catch (LocalizedIllegalArgumentException e) 1105 { 1106 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e); 1107 } 1108 finally 1109 { 1110 exclusiveLock.unlock(); 1111 } 1112 } 1113 1114 /** 1115 * Deregisters the provided matching rule definition with this schema. 1116 * 1117 * @param matchingRule 1118 * The matching rule to deregister with this schema. 1119 * @throws DirectoryException 1120 * If the matching rule is referenced by another schema element. 1121 */ 1122 public void deregisterMatchingRule(final MatchingRule matchingRule) throws DirectoryException 1123 { 1124 exclusiveLock.lock(); 1125 try 1126 { 1127 SchemaBuilder builder = new SchemaBuilder(schemaNG); 1128 builder.removeMatchingRule(matchingRule.getNameOrOID()); 1129 switchSchema(builder.toSchema()); 1130 } 1131 finally 1132 { 1133 exclusiveLock.unlock(); 1134 } 1135 } 1136 1137 1138 /** 1139 * Retrieves the matching rule use definitions for this schema, as a 1140 * mapping between the matching rule for the matching rule use 1141 * definition and the matching rule use itself. Each matching rule 1142 * use should only be present once, since its only key is its 1143 * matching rule. The contents of the returned mapping must not be 1144 * altered. 1145 * 1146 * @return The matching rule use definitions for this schema. 1147 */ 1148 public ConcurrentHashMap<MatchingRule,MatchingRuleUse> 1149 getMatchingRuleUses() 1150 { 1151 return matchingRuleUses; 1152 } 1153 1154 1155 1156 /** 1157 * Retrieves the matching rule use definition for the specified 1158 * matching rule. 1159 * 1160 * @param matchingRule The matching rule for which to retrieve the 1161 * matching rule use definition. 1162 * 1163 * @return The matching rule use definition, or <CODE>null</CODE> 1164 * if none exists for the specified matching rule. 1165 */ 1166 public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule) 1167 { 1168 return matchingRuleUses.get(matchingRule); 1169 } 1170 1171 1172 1173 /** 1174 * Registers the provided matching rule use definition with this 1175 * schema. 1176 * 1177 * @param matchingRuleUse The matching rule use definition to 1178 * register. 1179 * @param overwriteExisting Indicates whether to overwrite an 1180 * existing mapping if there are any 1181 * conflicts (i.e., another matching rule 1182 * use with the same matching rule). 1183 * 1184 * @throws DirectoryException If a conflict is encountered and the 1185 * <CODE>overwriteExisting</CODE> flag 1186 * is set to <CODE>false</CODE> 1187 */ 1188 public void registerMatchingRuleUse(MatchingRuleUse matchingRuleUse, 1189 boolean overwriteExisting) 1190 throws DirectoryException 1191 { 1192 synchronized (matchingRuleUses) 1193 { 1194 MatchingRule matchingRule = matchingRuleUse.getMatchingRule(); 1195 1196 if (!overwriteExisting && matchingRuleUses.containsKey(matchingRule)) 1197 { 1198 MatchingRuleUse conflictingUse = matchingRuleUses.get(matchingRule); 1199 1200 LocalizableMessage message = ERR_SCHEMA_CONFLICTING_MATCHING_RULE_USE. 1201 get(matchingRuleUse.getNameOrOID(), 1202 matchingRule.getNameOrOID(), 1203 conflictingUse.getNameOrOID()); 1204 throw new DirectoryException( 1205 ResultCode.CONSTRAINT_VIOLATION, message); 1206 } 1207 1208 matchingRuleUses.put(matchingRule, matchingRuleUse); 1209 } 1210 } 1211 1212 1213 1214 /** 1215 * Deregisters the provided matching rule use definition with this 1216 * schema. 1217 * 1218 * @param matchingRuleUse The matching rule use to deregister with 1219 * this schema. 1220 */ 1221 public void deregisterMatchingRuleUse( 1222 MatchingRuleUse matchingRuleUse) 1223 { 1224 synchronized (matchingRuleUses) 1225 { 1226 matchingRuleUses.remove(matchingRuleUse.getMatchingRule(), 1227 matchingRuleUse); 1228 } 1229 } 1230 1231 1232 1233 /** 1234 * Retrieves the DIT content rule definitions for this schema, as a 1235 * mapping between the objectclass for the rule and the DIT content 1236 * rule itself. Each DIT content rule should only be present once, 1237 * since its only key is its objectclass. The contents of the 1238 * returned mapping must not be altered. 1239 * 1240 * @return The DIT content rule definitions for this schema. 1241 */ 1242 public ConcurrentHashMap<ObjectClass,DITContentRule> 1243 getDITContentRules() 1244 { 1245 return ditContentRules; 1246 } 1247 1248 1249 1250 /** 1251 * Retrieves the DIT content rule definition for the specified 1252 * objectclass. 1253 * 1254 * @param objectClass The objectclass for the DIT content rule to 1255 * retrieve. 1256 * 1257 * @return The requested DIT content rule, or <CODE>null</CODE> if 1258 * no DIT content rule is registered with the provided 1259 * objectclass. 1260 */ 1261 public DITContentRule getDITContentRule(ObjectClass objectClass) 1262 { 1263 return ditContentRules.get(objectClass); 1264 } 1265 1266 1267 1268 /** 1269 * Registers the provided DIT content rule definition with this 1270 * schema. 1271 * 1272 * @param ditContentRule The DIT content rule to register. 1273 * @param overwriteExisting Indicates whether to overwrite an 1274 * existing mapping if there are any 1275 * conflicts (i.e., another DIT content 1276 * rule with the same objectclass). 1277 * 1278 * @throws DirectoryException If a conflict is encountered and the 1279 * <CODE>overwriteExisting</CODE> flag 1280 * is set to <CODE>false</CODE> 1281 */ 1282 public void registerDITContentRule(DITContentRule ditContentRule, 1283 boolean overwriteExisting) 1284 throws DirectoryException 1285 { 1286 synchronized (ditContentRules) 1287 { 1288 ObjectClass objectClass = ditContentRule.getStructuralClass(); 1289 1290 if (! overwriteExisting && ditContentRules.containsKey(objectClass)) 1291 { 1292 DITContentRule conflictingRule = 1293 ditContentRules.get(objectClass); 1294 1295 LocalizableMessage message = ERR_SCHEMA_CONFLICTING_DIT_CONTENT_RULE. 1296 get(ditContentRule.getNameOrOID(), 1297 objectClass.getNameOrOID(), 1298 conflictingRule.getNameOrOID()); 1299 throw new DirectoryException( 1300 ResultCode.CONSTRAINT_VIOLATION, message); 1301 } 1302 1303 ditContentRules.put(objectClass, ditContentRule); 1304 } 1305 } 1306 1307 1308 1309 /** 1310 * Deregisters the provided DIT content rule definition with this 1311 * schema. 1312 * 1313 * @param ditContentRule The DIT content rule to deregister with 1314 * this schema. 1315 */ 1316 public void deregisterDITContentRule(DITContentRule ditContentRule) 1317 { 1318 synchronized (ditContentRules) 1319 { 1320 ditContentRules.remove(ditContentRule.getStructuralClass(), 1321 ditContentRule); 1322 } 1323 } 1324 1325 1326 1327 /** 1328 * Retrieves the DIT structure rule definitions for this schema, as 1329 * a mapping between the rule ID for the rule and the DIT structure 1330 * rule itself. Each DIT structure rule should only be present 1331 * once, since its only key is its rule ID. The contents of the 1332 * returned mapping must not be altered. 1333 * 1334 * @return The DIT structure rule definitions for this schema. 1335 */ 1336 public ConcurrentHashMap<Integer,DITStructureRule> 1337 getDITStructureRulesByID() 1338 { 1339 return ditStructureRulesByID; 1340 } 1341 1342 1343 1344 /** 1345 * Retrieves the DIT structure rule definitions for this schema, as 1346 * a mapping between the name form for the rule and the DIT 1347 * structure rule itself. Each DIT structure rule should only be 1348 * present once, since its only key is its name form. The contents 1349 * of the returned mapping must not be altered. 1350 * 1351 * @return The DIT structure rule definitions for this schema. 1352 */ 1353 public ConcurrentHashMap<NameForm,DITStructureRule> 1354 getDITStructureRulesByNameForm() 1355 { 1356 return ditStructureRulesByNameForm; 1357 } 1358 1359 1360 1361 /** 1362 * Retrieves the DIT structure rule definition with the provided 1363 * rule ID. 1364 * 1365 * @param ruleID The rule ID for the DIT structure rule to 1366 * retrieve. 1367 * 1368 * @return The requested DIT structure rule, or <CODE>null</CODE> 1369 * if no DIT structure rule is registered with the provided 1370 * rule ID. 1371 */ 1372 public DITStructureRule getDITStructureRule(int ruleID) 1373 { 1374 return ditStructureRulesByID.get(ruleID); 1375 } 1376 1377 1378 1379 /** 1380 * Retrieves the DIT structure rule definition for the provided name 1381 * form. 1382 * 1383 * @param nameForm The name form for the DIT structure rule to 1384 * retrieve. 1385 * 1386 * @return The requested DIT structure rule, or <CODE>null</CODE> 1387 * if no DIT structure rule is registered with the provided 1388 * name form. 1389 */ 1390 public DITStructureRule getDITStructureRule(NameForm nameForm) 1391 { 1392 return ditStructureRulesByNameForm.get(nameForm); 1393 } 1394 1395 1396 1397 /** 1398 * Registers the provided DIT structure rule definition with this 1399 * schema. 1400 * 1401 * @param ditStructureRule The DIT structure rule to register. 1402 * @param overwriteExisting Indicates whether to overwrite an 1403 * existing mapping if there are any 1404 * conflicts (i.e., another DIT structure 1405 * rule with the same name form). 1406 * 1407 * @throws DirectoryException If a conflict is encountered and the 1408 * <CODE>overwriteExisting</CODE> flag 1409 * is set to <CODE>false</CODE> 1410 */ 1411 public void registerDITStructureRule( 1412 DITStructureRule ditStructureRule, 1413 boolean overwriteExisting) 1414 throws DirectoryException 1415 { 1416 synchronized (ditStructureRulesByNameForm) 1417 { 1418 NameForm nameForm = ditStructureRule.getNameForm(); 1419 int ruleID = ditStructureRule.getRuleID(); 1420 1421 if (! overwriteExisting) 1422 { 1423 if (ditStructureRulesByNameForm.containsKey(nameForm)) 1424 { 1425 DITStructureRule conflictingRule = 1426 ditStructureRulesByNameForm.get(nameForm); 1427 1428 LocalizableMessage message = 1429 ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_NAME_FORM. 1430 get(ditStructureRule.getNameOrRuleID(), 1431 nameForm.getNameOrOID(), 1432 conflictingRule.getNameOrRuleID()); 1433 throw new DirectoryException( 1434 ResultCode.CONSTRAINT_VIOLATION, message); 1435 } 1436 1437 if (ditStructureRulesByID.containsKey(ruleID)) 1438 { 1439 DITStructureRule conflictingRule = 1440 ditStructureRulesByID.get(ruleID); 1441 1442 LocalizableMessage message = 1443 ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_ID. 1444 get(ditStructureRule.getNameOrRuleID(), ruleID, 1445 conflictingRule.getNameOrRuleID()); 1446 throw new DirectoryException( 1447 ResultCode.CONSTRAINT_VIOLATION, message); 1448 } 1449 } 1450 1451 ditStructureRulesByNameForm.put(nameForm, ditStructureRule); 1452 ditStructureRulesByID.put(ruleID, ditStructureRule); 1453 } 1454 } 1455 1456 1457 1458 /** 1459 * Deregisters the provided DIT structure rule definition with this 1460 * schema. 1461 * 1462 * @param ditStructureRule The DIT structure rule to deregister 1463 * with this schema. 1464 */ 1465 public void deregisterDITStructureRule( 1466 DITStructureRule ditStructureRule) 1467 { 1468 synchronized (ditStructureRulesByNameForm) 1469 { 1470 ditStructureRulesByNameForm.remove( 1471 ditStructureRule.getNameForm(), ditStructureRule); 1472 ditStructureRulesByID.remove(ditStructureRule.getRuleID(), 1473 ditStructureRule); 1474 } 1475 } 1476 1477 1478 1479 /** 1480 * Retrieves the name form definitions for this schema, as a mapping 1481 * between the objectclass for the name forms and the name forms 1482 * themselves. 1483 * 1484 * @return The name form definitions for this schema. 1485 */ 1486 public ConcurrentHashMap<ObjectClass,List<NameForm>> 1487 getNameFormsByObjectClass() 1488 { 1489 return nameFormsByOC; 1490 } 1491 1492 1493 1494 /** 1495 * Retrieves the name form definitions for this schema, as a mapping 1496 * between the names/OID for the name form and the name form itself. 1497 * Each name form may be present multiple times with different names 1498 * and its OID. The contents of the returned mapping must not be 1499 * altered. 1500 * 1501 * @return The name form definitions for this schema. 1502 */ 1503 public ConcurrentHashMap<String,NameForm> getNameFormsByNameOrOID() 1504 { 1505 return nameFormsByName; 1506 } 1507 1508 1509 1510 /** 1511 * Indicates whether this schema definition includes a name form 1512 * with the specified name or OID. 1513 * 1514 * @param lowerName The name or OID for which to make the 1515 * determination, formatted in all lowercase 1516 * characters. 1517 * 1518 * @return {@code true} if this schema contains a name form with 1519 * the provided name or OID, or {@code false} if not. 1520 */ 1521 public boolean hasNameForm(String lowerName) 1522 { 1523 return nameFormsByName.containsKey(lowerName); 1524 } 1525 1526 1527 1528 /** 1529 * Retrieves the name forms definition for the specified 1530 * objectclass. 1531 * 1532 * @param objectClass The objectclass for the name form to 1533 * retrieve. 1534 * 1535 * @return The requested name forms, or <CODE>null</CODE> if no 1536 * name forms are registered with the provided 1537 * objectClass. 1538 */ 1539 public List<NameForm> getNameForm(ObjectClass objectClass) 1540 { 1541 return nameFormsByOC.get(objectClass); 1542 } 1543 1544 1545 1546 /** 1547 * Retrieves the name form definition with the provided name or OID. 1548 * 1549 * @param lowerName The name or OID of the name form to retrieve, 1550 * formatted in all lowercase characters. 1551 * 1552 * @return The requested name form, or <CODE>null</CODE> if no name 1553 * form is registered with the provided name or OID. 1554 */ 1555 public NameForm getNameForm(String lowerName) 1556 { 1557 return nameFormsByName.get(lowerName); 1558 } 1559 1560 1561 1562 /** 1563 * Registers the provided name form definition with this schema. 1564 * 1565 * @param nameForm The name form definition to register. 1566 * @param overwriteExisting Indicates whether to overwrite an 1567 * existing mapping if there are any 1568 * conflicts (i.e., another name form 1569 * with the same objectclass). 1570 * 1571 * @throws DirectoryException If a conflict is encountered and the 1572 * <CODE>overwriteExisting</CODE> flag 1573 * is set to <CODE>false</CODE> 1574 */ 1575 public void registerNameForm(NameForm nameForm, 1576 boolean overwriteExisting) 1577 throws DirectoryException 1578 { 1579 synchronized (nameFormsByOC) 1580 { 1581 ObjectClass objectClass = nameForm.getStructuralClass(); 1582 List<NameForm> mappedForms = nameFormsByOC.get(objectClass); 1583 if (! overwriteExisting) 1584 { 1585 if(mappedForms !=null) 1586 { 1587 //Iterate over the forms to make sure we aren't adding a 1588 //duplicate. 1589 for(NameForm nf : mappedForms) 1590 { 1591 if(nf.equals(nameForm)) 1592 { 1593 LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_OC. 1594 get(nameForm.getNameOrOID(), 1595 objectClass.getNameOrOID(), 1596 nf.getNameOrOID()); 1597 throw new DirectoryException( 1598 ResultCode.CONSTRAINT_VIOLATION, message); 1599 } 1600 } 1601 } 1602 1603 String oid = toLowerCase(nameForm.getOID()); 1604 if (nameFormsByName.containsKey(oid)) 1605 { 1606 NameForm conflictingNameForm = nameFormsByName.get(oid); 1607 1608 LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_OID. 1609 get(nameForm.getNameOrOID(), oid, 1610 conflictingNameForm.getNameOrOID()); 1611 throw new DirectoryException( 1612 ResultCode.CONSTRAINT_VIOLATION, message); 1613 } 1614 1615 for (String name : nameForm.getNames().keySet()) 1616 { 1617 if (nameFormsByName.containsKey(name)) 1618 { 1619 NameForm conflictingNameForm = nameFormsByName.get(name); 1620 1621 LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_NAME. 1622 get(nameForm.getNameOrOID(), oid, 1623 conflictingNameForm.getNameOrOID()); 1624 throw new DirectoryException( 1625 ResultCode.CONSTRAINT_VIOLATION, message); 1626 } 1627 } 1628 } 1629 1630 if(mappedForms == null) 1631 { 1632 mappedForms = new ArrayList<>(); 1633 } 1634 1635 mappedForms.add(nameForm); 1636 nameFormsByOC.put(objectClass, mappedForms); 1637 nameFormsByName.put(toLowerCase(nameForm.getOID()), nameForm); 1638 1639 for (String name : nameForm.getNames().keySet()) 1640 { 1641 nameFormsByName.put(name, nameForm); 1642 } 1643 } 1644 } 1645 1646 1647 1648 /** 1649 * Deregisters the provided name form definition with this schema. 1650 * 1651 * @param nameForm The name form definition to deregister. 1652 */ 1653 public void deregisterNameForm(NameForm nameForm) 1654 { 1655 synchronized (nameFormsByOC) 1656 { 1657 List<NameForm> mappedForms = nameFormsByOC.get( 1658 nameForm.getStructuralClass()); 1659 if(mappedForms != null) 1660 { 1661 mappedForms.remove(nameForm); 1662 if(mappedForms.isEmpty()) 1663 { 1664 nameFormsByOC.remove(nameForm.getStructuralClass()); 1665 } 1666 } 1667 nameFormsByOC.remove(nameForm.getStructuralClass()); 1668 nameFormsByName.remove(toLowerCase(nameForm.getOID()), 1669 nameForm); 1670 1671 for (String name : nameForm.getNames().keySet()) 1672 { 1673 nameFormsByName.remove(name, nameForm); 1674 } 1675 } 1676 } 1677 1678 1679 1680 /** 1681 * Retrieves the modification timestamp for the file in the schema 1682 * configuration directory with the oldest last modified time. 1683 * 1684 * @return The modification timestamp for the file in the schema 1685 * configuration directory with the oldest last modified 1686 * time. 1687 */ 1688 public long getOldestModificationTime() 1689 { 1690 return oldestModificationTime; 1691 } 1692 1693 1694 1695 /** 1696 * Sets the modification timestamp for the oldest file in the schema 1697 * configuration directory. 1698 * 1699 * @param oldestModificationTime The modification timestamp for 1700 * the oldest file in the schema 1701 * configuration directory. 1702 */ 1703 public void setOldestModificationTime(long oldestModificationTime) 1704 { 1705 this.oldestModificationTime = oldestModificationTime; 1706 } 1707 1708 1709 1710 /** 1711 * Retrieves the modification timestamp for the file in the schema 1712 * configuration directory with the youngest last modified time. 1713 * 1714 * @return The modification timestamp for the file in the schema 1715 * configuration directory with the youngest last modified 1716 * time. 1717 */ 1718 public long getYoungestModificationTime() 1719 { 1720 return youngestModificationTime; 1721 } 1722 1723 1724 1725 /** 1726 * Sets the modification timestamp for the youngest file in the 1727 * schema configuration directory. 1728 * 1729 * @param youngestModificationTime The modification timestamp for 1730 * the youngest file in the schema 1731 * configuration directory. 1732 */ 1733 public void setYoungestModificationTime( 1734 long youngestModificationTime) 1735 { 1736 this.youngestModificationTime = youngestModificationTime; 1737 } 1738 1739 /** 1740 * Recursively rebuilds all schema elements that are dependent upon 1741 * the provided element. This must be invoked whenever an existing 1742 * schema element is modified in order to ensure that any elements 1743 * that depend on it should also be recreated to reflect the change. 1744 * <BR><BR> 1745 * The following conditions create dependencies between schema 1746 * elements: 1747 * <UL> 1748 * <LI>If an attribute type references a superior attribute type, 1749 * then it is dependent upon that superior attribute 1750 * type.</LI> 1751 * <LI>If an objectclass requires or allows an attribute type, 1752 * then it is dependent upon that attribute type.</LI> 1753 * <LI>If a name form requires or allows an attribute type in the 1754 * RDN, then it is dependent upon that attribute type.</LI> 1755 * <LI>If a DIT content rule requires, allows, or forbids the use 1756 * of an attribute type, then it is dependent upon that 1757 * attribute type.</LI> 1758 * <LI>If a matching rule use references an attribute type, then 1759 * it is dependent upon that attribute type.</LI> 1760 * <LI>If an objectclass references a superior objectclass, then 1761 * it is dependent upon that superior objectclass.</LI> 1762 * <LI>If a name form references a structural objectclass, then it 1763 * is dependent upon that objectclass.</LI> 1764 * <LI>If a DIT content rule references a structural or auxiliary 1765 * objectclass, then it is dependent upon that 1766 * objectclass.</LI> 1767 * <LI>If a DIT structure rule references a name form, then it is 1768 * dependent upon that name form.</LI> 1769 * <LI>If a DIT structure rule references a superior DIT structure 1770 * rule, then it is dependent upon that superior DIT structure 1771 * rule.</LI> 1772 * </UL> 1773 * 1774 * @param element The element for which to recursively rebuild all 1775 * dependent elements. 1776 * 1777 * @throws DirectoryException If a problem occurs while rebuilding 1778 * any of the schema elements. 1779 */ 1780 public void rebuildDependentElements(SchemaFileElement element) throws DirectoryException 1781 { 1782 try 1783 { 1784 // increase the depth for each level of recursion to protect against errors due to circular references. 1785 final int depth = 0; 1786 1787 if (element instanceof SomeSchemaElement) 1788 { 1789 SomeSchemaElement elt = (SomeSchemaElement) element; 1790 if (elt.isAttributeType()) 1791 { 1792 rebuildDependentElements(elt.getAttributeType(), depth); 1793 } 1794 } 1795 else if (element instanceof ObjectClass) 1796 { 1797 rebuildDependentElements((ObjectClass) element, depth); 1798 } 1799 else if (element instanceof NameForm) 1800 { 1801 rebuildDependentElements((NameForm) element, depth); 1802 } 1803 else if (element instanceof DITStructureRule) 1804 { 1805 rebuildDependentElements((DITStructureRule) element, depth); 1806 } 1807 } 1808 catch (DirectoryException de) 1809 { 1810 // If we got an error as a result of a circular reference, then 1811 // we want to make sure that the schema element we call out is 1812 // the one that is at the root of the problem. 1813 if (StaticUtils.hasDescriptor(de.getMessageObject(), 1814 ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE)) 1815 { 1816 LocalizableMessage message = 1817 ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE.get(element); 1818 throw new DirectoryException(de.getResultCode(), message, de); 1819 } 1820 1821 // It wasn't a circular reference error, so just re-throw the exception. 1822 throw de; 1823 } 1824 } 1825 1826 private void circularityCheck(int depth, SchemaFileElement element) throws DirectoryException 1827 { 1828 if (depth > 20) 1829 { 1830 // FIXME use a stack of already traversed elements and verify we're updating them only once instead of depth only 1831 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1832 ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE.get(element)); 1833 } 1834 } 1835 1836 private void rebuildDependentElements(AttributeType type, int depth) throws DirectoryException 1837 { 1838 circularityCheck(depth, null); 1839 1840 for (AttributeType at : schemaNG.getAttributeTypes()) 1841 { 1842 if ((at.getSuperiorType() != null) && at.getSuperiorType().equals(type)) 1843 { 1844 deregisterAttributeType(at); 1845 registerAttributeType(at.toString(), getSchemaFileName(at), true); 1846 rebuildDependentElements(at, depth + 1); 1847 } 1848 } 1849 1850 for (ObjectClass oc : objectClasses.values()) 1851 { 1852 if (oc.getRequiredAttributes().contains(type) || oc.getOptionalAttributes().contains(type)) 1853 { 1854 ObjectClass newOC = recreateFromDefinition(oc); 1855 deregisterObjectClass(oc); 1856 registerObjectClass(newOC, true); 1857 rebuildDependentElements(oc, depth + 1); 1858 } 1859 } 1860 1861 for (List<NameForm> mappedForms : nameFormsByOC.values()) 1862 { 1863 for (NameForm nf : mappedForms) 1864 { 1865 if (nf.getRequiredAttributes().contains(type) || nf.getOptionalAttributes().contains(type)) 1866 { 1867 NameForm newNF = recreateFromDefinition(nf); 1868 deregisterNameForm(nf); 1869 registerNameForm(newNF, true); 1870 rebuildDependentElements(nf, depth + 1); 1871 } 1872 } 1873 } 1874 1875 for (DITContentRule dcr : ditContentRules.values()) 1876 { 1877 if (dcr.getRequiredAttributes().contains(type) || dcr.getOptionalAttributes().contains(type) 1878 || dcr.getProhibitedAttributes().contains(type)) 1879 { 1880 DITContentRule newDCR = recreateFromDefinition(dcr); 1881 deregisterDITContentRule(dcr); 1882 registerDITContentRule(newDCR, true); 1883 } 1884 } 1885 1886 for (MatchingRuleUse mru : matchingRuleUses.values()) 1887 { 1888 if (mru.getAttributes().contains(type)) 1889 { 1890 MatchingRuleUse newMRU = recreateFromDefinition(mru); 1891 deregisterMatchingRuleUse(mru); 1892 registerMatchingRuleUse(newMRU, true); 1893 } 1894 } 1895 } 1896 1897 private void rebuildDependentElements(ObjectClass c, int depth) throws DirectoryException 1898 { 1899 circularityCheck(depth, c); 1900 for (ObjectClass oc : objectClasses.values()) 1901 { 1902 if (oc.getSuperiorClasses().contains(c)) 1903 { 1904 ObjectClass newOC = recreateFromDefinition(oc); 1905 deregisterObjectClass(oc); 1906 registerObjectClass(newOC, true); 1907 rebuildDependentElements(oc, depth + 1); 1908 } 1909 } 1910 1911 List<NameForm> mappedForms = nameFormsByOC.get(c); 1912 if (mappedForms != null) 1913 { 1914 for (NameForm nf : mappedForms) 1915 { 1916 if (nf != null) 1917 { 1918 NameForm newNF = recreateFromDefinition(nf); 1919 deregisterNameForm(nf); 1920 registerNameForm(newNF, true); 1921 rebuildDependentElements(nf, depth + 1); 1922 } 1923 } 1924 } 1925 1926 for (DITContentRule dcr : ditContentRules.values()) 1927 { 1928 if (dcr.getStructuralClass().equals(c) || dcr.getAuxiliaryClasses().contains(c)) 1929 { 1930 DITContentRule newDCR = recreateFromDefinition(dcr); 1931 deregisterDITContentRule(dcr); 1932 registerDITContentRule(newDCR, true); 1933 } 1934 } 1935 } 1936 1937 private void rebuildDependentElements(NameForm n, int depth) throws DirectoryException 1938 { 1939 circularityCheck(depth, n); 1940 DITStructureRule dsr = ditStructureRulesByNameForm.get(n); 1941 if (dsr != null) 1942 { 1943 DITStructureRule newDSR = recreateFromDefinition(dsr); 1944 deregisterDITStructureRule(dsr); 1945 registerDITStructureRule(newDSR, true); 1946 rebuildDependentElements(dsr, depth + 1); 1947 } 1948 } 1949 1950 private void rebuildDependentElements(DITStructureRule d, int depth) throws DirectoryException 1951 { 1952 circularityCheck(depth, d); 1953 for (DITStructureRule dsr : ditStructureRulesByID.values()) 1954 { 1955 if (dsr.getSuperiorRules().contains(d)) 1956 { 1957 DITStructureRule newDSR = recreateFromDefinition(dsr); 1958 deregisterDITStructureRule(dsr); 1959 registerDITStructureRule(newDSR, true); 1960 rebuildDependentElements(dsr, depth + 1); 1961 } 1962 } 1963 } 1964 1965 private String getSchemaFileName(AttributeType attributeType) 1966 { 1967 List<String> values = attributeType.getExtraProperties().get(ServerConstants.SCHEMA_PROPERTY_FILENAME); 1968 return values != null && ! values.isEmpty() ? values.get(0) : null; 1969 } 1970 1971 private DITContentRule recreateFromDefinition(DITContentRule dcr) 1972 throws DirectoryException 1973 { 1974 ByteString value = ByteString.valueOfUtf8(dcr.toString()); 1975 DITContentRule copy = 1976 DITContentRuleSyntax.decodeDITContentRule(value, this, false); 1977 setSchemaFile(copy, getSchemaFile(dcr)); 1978 return copy; 1979 } 1980 1981 private DITStructureRule recreateFromDefinition(DITStructureRule dsr) 1982 throws DirectoryException 1983 { 1984 ByteString value = ByteString.valueOfUtf8(dsr.toString()); 1985 DITStructureRule copy = 1986 DITStructureRuleSyntax.decodeDITStructureRule(value, this, false); 1987 setSchemaFile(copy, getSchemaFile(dsr)); 1988 return copy; 1989 } 1990 1991 private MatchingRuleUse recreateFromDefinition(MatchingRuleUse mru) 1992 throws DirectoryException 1993 { 1994 ByteString value = ByteString.valueOfUtf8(mru.toString()); 1995 MatchingRuleUse copy = 1996 MatchingRuleUseSyntax.decodeMatchingRuleUse(value, this, false); 1997 setSchemaFile(copy, getSchemaFile(mru)); 1998 return copy; 1999 } 2000 2001 private NameForm recreateFromDefinition(NameForm nf) 2002 throws DirectoryException 2003 { 2004 ByteString value = ByteString.valueOfUtf8(nf.toString()); 2005 NameForm copy = NameFormSyntax.decodeNameForm(value, this, false); 2006 setSchemaFile(copy, getSchemaFile(nf)); 2007 return copy; 2008 } 2009 2010 private ObjectClass recreateFromDefinition(ObjectClass oc) 2011 throws DirectoryException 2012 { 2013 ByteString value = ByteString.valueOfUtf8(oc.toString()); 2014 ObjectClass copy = ObjectClassSyntax.decodeObjectClass(value, this, false); 2015 setSchemaFile(copy, getSchemaFile(oc)); 2016 return copy; 2017 } 2018 2019 /** 2020 * Creates a new {@link Schema} object that is a duplicate of this one. It elements may be added 2021 * and removed from the duplicate without impacting this version. 2022 * 2023 * @return A new {@link Schema} object that is a duplicate of this one. 2024 */ 2025 public Schema duplicate() 2026 { 2027 Schema dupSchema; 2028 try 2029 { 2030 dupSchema = new Schema(schemaNG); 2031 } 2032 catch (DirectoryException unexpected) 2033 { 2034 // the schema has already been validated 2035 throw new RuntimeException(unexpected); 2036 } 2037 2038 dupSchema.subordinateTypes.putAll(subordinateTypes); 2039 dupSchema.objectClasses.putAll(objectClasses); 2040 dupSchema.matchingRuleUses.putAll(matchingRuleUses); 2041 dupSchema.ditContentRules.putAll(ditContentRules); 2042 dupSchema.ditStructureRulesByID.putAll(ditStructureRulesByID); 2043 dupSchema.ditStructureRulesByNameForm.putAll( 2044 ditStructureRulesByNameForm); 2045 dupSchema.nameFormsByOC.putAll(nameFormsByOC); 2046 dupSchema.nameFormsByName.putAll(nameFormsByName); 2047 dupSchema.ldapSyntaxDescriptions.putAll(ldapSyntaxDescriptions); 2048 dupSchema.oldestModificationTime = oldestModificationTime; 2049 dupSchema.youngestModificationTime = youngestModificationTime; 2050 if (extraAttributes != null) 2051 { 2052 dupSchema.extraAttributes = new HashMap<>(extraAttributes); 2053 } 2054 2055 return dupSchema; 2056 } 2057 2058 2059 /** 2060 * Get the extraAttributes stored in this schema. 2061 * 2062 * @return The extraAttributes stored in this schema. 2063 */ 2064 public Map<String, Attribute> getExtraAttributes() 2065 { 2066 return extraAttributes; 2067 } 2068 2069 2070 /** 2071 * Add a new extra Attribute for this schema. 2072 * 2073 * @param name The identifier of the extra Attribute. 2074 * 2075 * @param attr The extra attribute that must be added to 2076 * this Schema. 2077 */ 2078 public void addExtraAttribute(String name, Attribute attr) 2079 { 2080 extraAttributes.put(name, attr); 2081 } 2082 2083 2084 /** 2085 * Writes a single file containing all schema element definitions, 2086 * which can be used on startup to determine whether the schema 2087 * files were edited with the server offline. 2088 */ 2089 public static void writeConcatenatedSchema() 2090 { 2091 String concatFilePath = null; 2092 try 2093 { 2094 Set<String> attributeTypes = new LinkedHashSet<>(); 2095 Set<String> objectClasses = new LinkedHashSet<>(); 2096 Set<String> nameForms = new LinkedHashSet<>(); 2097 Set<String> ditContentRules = new LinkedHashSet<>(); 2098 Set<String> ditStructureRules = new LinkedHashSet<>(); 2099 Set<String> matchingRuleUses = new LinkedHashSet<>(); 2100 Set<String> ldapSyntaxes = new LinkedHashSet<>(); 2101 genConcatenatedSchema(attributeTypes, objectClasses, nameForms, 2102 ditContentRules, ditStructureRules, 2103 matchingRuleUses,ldapSyntaxes); 2104 2105 2106 File configFile = new File(DirectoryServer.getConfigFile()); 2107 File configDirectory = configFile.getParentFile(); 2108 File upgradeDirectory = new File(configDirectory, "upgrade"); 2109 upgradeDirectory.mkdir(); 2110 File concatFile = new File(upgradeDirectory, 2111 SCHEMA_CONCAT_FILE_NAME); 2112 concatFilePath = concatFile.getAbsolutePath(); 2113 2114 File tempFile = new File(concatFilePath + ".tmp"); 2115 try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, false))) 2116 { 2117 writer.write("dn: " + DirectoryServer.getSchemaDN()); 2118 writer.newLine(); 2119 writer.write("objectClass: top"); 2120 writer.newLine(); 2121 writer.write("objectClass: ldapSubentry"); 2122 writer.newLine(); 2123 writer.write("objectClass: subschema"); 2124 writer.newLine(); 2125 2126 writeLines(writer, ATTR_ATTRIBUTE_TYPES, attributeTypes); 2127 writeLines(writer, ATTR_OBJECTCLASSES, objectClasses); 2128 writeLines(writer, ATTR_NAME_FORMS, nameForms); 2129 writeLines(writer, ATTR_DIT_CONTENT_RULES, ditContentRules); 2130 writeLines(writer, ATTR_DIT_STRUCTURE_RULES, ditStructureRules); 2131 writeLines(writer, ATTR_MATCHING_RULE_USE, matchingRuleUses); 2132 writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes); 2133 } 2134 2135 if (concatFile.exists()) 2136 { 2137 concatFile.delete(); 2138 } 2139 tempFile.renameTo(concatFile); 2140 } 2141 catch (Exception e) 2142 { 2143 logger.traceException(e); 2144 2145 // This is definitely not ideal, but it's not the end of the 2146 // world. The worst that should happen is that the schema 2147 // changes could potentially be sent to the other servers again 2148 // when this server is restarted, which shouldn't hurt anything. 2149 // Still, we should log a warning message. 2150 logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e)); 2151 } 2152 } 2153 2154 private static void writeLines(BufferedWriter writer, String beforeColumn, Set<String> lines) throws IOException 2155 { 2156 for (String line : lines) 2157 { 2158 writer.write(beforeColumn); 2159 writer.write(": "); 2160 writer.write(line); 2161 writer.newLine(); 2162 } 2163 } 2164 2165 2166 2167 /** 2168 * Reads the files contained in the schema directory and generates a 2169 * concatenated view of their contents in the provided sets. 2170 * 2171 * @param attributeTypes The set into which to place the 2172 * attribute types read from the schema 2173 * files. 2174 * @param objectClasses The set into which to place the object 2175 * classes read from the schema files. 2176 * @param nameForms The set into which to place the name 2177 * forms read from the schema files. 2178 * @param ditContentRules The set into which to place the DIT 2179 * content rules read from the schema 2180 * files. 2181 * @param ditStructureRules The set into which to place the DIT 2182 * structure rules read from the schema 2183 * files. 2184 * @param matchingRuleUses The set into which to place the 2185 * matching rule uses read from the 2186 * schema files. 2187 * @param ldapSyntaxes The set into which to place the 2188 * ldap syntaxes read from the 2189 * schema files. 2190 * 2191 * @throws IOException If a problem occurs while reading the 2192 * schema file elements. 2193 */ 2194 public static void genConcatenatedSchema( 2195 Set<String> attributeTypes, 2196 Set<String> objectClasses, 2197 Set<String> nameForms, 2198 Set<String> ditContentRules, 2199 Set<String> ditStructureRules, 2200 Set<String> matchingRuleUses, 2201 Set<String> ldapSyntaxes) 2202 throws IOException 2203 { 2204 // Get a sorted list of the files in the schema directory. 2205 TreeSet<File> schemaFiles = new TreeSet<>(); 2206 String schemaDirectory = 2207 SchemaConfigManager.getSchemaDirectoryPath(); 2208 2209 final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter(); 2210 for (File f : new File(schemaDirectory).listFiles(filter)) 2211 { 2212 if (f.isFile()) 2213 { 2214 schemaFiles.add(f); 2215 } 2216 } 2217 2218 2219 // Open each of the files in order and read the elements that they 2220 // contain, appending them to the appropriate lists. 2221 for (File f : schemaFiles) 2222 { 2223 // Read the contents of the file into a list with one schema 2224 // element per list element. 2225 LinkedList<StringBuilder> lines = new LinkedList<>(); 2226 BufferedReader reader = new BufferedReader(new FileReader(f)); 2227 2228 while (true) 2229 { 2230 String line = reader.readLine(); 2231 if (line == null) 2232 { 2233 break; 2234 } 2235 else if (line.startsWith("#") || line.length() == 0) 2236 { 2237 continue; 2238 } 2239 else if (line.startsWith(" ")) 2240 { 2241 lines.getLast().append(line.substring(1)); 2242 } 2243 else 2244 { 2245 lines.add(new StringBuilder(line)); 2246 } 2247 } 2248 2249 reader.close(); 2250 2251 2252 // Iterate through each line in the list. Find the colon and 2253 // get the attribute name at the beginning. If it's something 2254 // that we don't recognize, then skip it. Otherwise, add the 2255 // X-SCHEMA-FILE extension and add it to the appropriate schema 2256 // element list. 2257 for (StringBuilder buffer : lines) 2258 { 2259 // Get the line and add the X-SCHEMA-FILE extension to the end 2260 // of it. All of them should end with " )" but some might 2261 // have the parenthesis crammed up against the last character 2262 // so deal with that as well. 2263 String line = buffer.toString().trim(); 2264 if (line.endsWith(" )")) 2265 { 2266 line = line.substring(0, line.length()-1) + 2267 SCHEMA_PROPERTY_FILENAME + " '" + f.getName() + "' )"; 2268 } 2269 else if (line.endsWith(")")) 2270 { 2271 line = line.substring(0, line.length()-1) + " " + 2272 SCHEMA_PROPERTY_FILENAME + " '" + f.getName() + "' )"; 2273 } 2274 else 2275 { 2276 continue; 2277 } 2278 2279 parseSchemaLine(line, attributeTypes, objectClasses, 2280 nameForms, ditContentRules, ditStructureRules, matchingRuleUses, 2281 ldapSyntaxes); 2282 } 2283 } 2284 } 2285 2286 2287 2288 /** 2289 * Reads data from the specified concatenated schema file into the 2290 * provided sets. 2291 * 2292 * @param concatSchemaFile The path to the concatenated schema 2293 * file to be read. 2294 * @param attributeTypes The set into which to place the 2295 * attribute types read from the 2296 * concatenated schema file. 2297 * @param objectClasses The set into which to place the object 2298 * classes read from the concatenated 2299 * schema file. 2300 * @param nameForms The set into which to place the name 2301 * forms read from the concatenated 2302 * schema file. 2303 * @param ditContentRules The set into which to place the DIT 2304 * content rules read from the 2305 * concatenated schema file. 2306 * @param ditStructureRules The set into which to place the DIT 2307 * structure rules read from the 2308 * concatenated schema file. 2309 * @param matchingRuleUses The set into which to place the 2310 * matching rule uses read from the 2311 * concatenated schema file. 2312 * @param ldapSyntaxes The set into which to place the 2313 * ldap syntaxes read from the 2314 * concatenated schema file. 2315 * 2316 * @throws IOException If a problem occurs while reading the 2317 * schema file elements. 2318 */ 2319 public static void readConcatenatedSchema(String concatSchemaFile, 2320 Set<String> attributeTypes, 2321 Set<String> objectClasses, 2322 Set<String> nameForms, 2323 Set<String> ditContentRules, 2324 Set<String> ditStructureRules, 2325 Set<String> matchingRuleUses, 2326 Set<String> ldapSyntaxes) 2327 throws IOException 2328 { 2329 BufferedReader reader = 2330 new BufferedReader(new FileReader(concatSchemaFile)); 2331 while (true) 2332 { 2333 String line = reader.readLine(); 2334 if (line == null) 2335 { 2336 break; 2337 } 2338 parseSchemaLine(line, attributeTypes, objectClasses, 2339 nameForms, ditContentRules, ditStructureRules, matchingRuleUses, 2340 ldapSyntaxes); 2341 } 2342 2343 reader.close(); 2344 } 2345 2346 /** 2347 * Parse a line of a schema file into the provided sets. 2348 * 2349 * @param line The current line of schema. 2350 * @param attributeTypes The set into which to place the 2351 * attribute type if the line represents 2352 * one. 2353 * @param objectClasses The set into which to place the object 2354 * class if the line represents one. 2355 * @param nameForms The set into which to place the name 2356 * form if the line represents one. 2357 * @param ditContentRules The set into which to place the DIT 2358 * content rule if the line represents one. 2359 * @param ditStructureRules The set into which to place the DIT 2360 * structure rule if the line represents one. 2361 * @param matchingRuleUses The set into which to place the 2362 * matching rule use if the line represents 2363 * one. 2364 * @param ldapSyntaxes The set into which to place the ldap 2365 * syntax if the line represents one. 2366 */ 2367 2368 private static void parseSchemaLine(String line, 2369 Set<String> attributeTypes, 2370 Set<String> objectClasses, 2371 Set<String> nameForms, 2372 Set<String> ditContentRules, 2373 Set<String> ditStructureRules, 2374 Set<String> matchingRuleUses, 2375 Set<String> ldapSyntaxes) 2376 { 2377 String value; 2378 String lowerLine = toLowerCase(line); 2379 if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC)) 2380 { 2381 value = 2382 line.substring(ATTR_ATTRIBUTE_TYPES.length()+1).trim(); 2383 attributeTypes.add(value); 2384 } 2385 else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC)) 2386 { 2387 value = line.substring(ATTR_OBJECTCLASSES.length()+1).trim(); 2388 objectClasses.add(value); 2389 } 2390 else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC)) 2391 { 2392 value = line.substring(ATTR_NAME_FORMS.length()+1).trim(); 2393 nameForms.add(value); 2394 } 2395 else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC)) 2396 { 2397 value = line.substring( 2398 ATTR_DIT_CONTENT_RULES.length()+1).trim(); 2399 ditContentRules.add(value); 2400 } 2401 else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC)) 2402 { 2403 value = line.substring( 2404 ATTR_DIT_STRUCTURE_RULES.length()+1).trim(); 2405 ditStructureRules.add(value); 2406 } 2407 else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC)) 2408 { 2409 value = line.substring( 2410 ATTR_MATCHING_RULE_USE.length()+1).trim(); 2411 matchingRuleUses.add(value); 2412 } 2413 else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC)) 2414 { 2415 value = line.substring( 2416 ATTR_LDAP_SYNTAXES.length()+1).trim(); 2417 ldapSyntaxes.add(value); 2418 } 2419 } 2420 2421 /** 2422 * Compares the provided sets of schema element definitions and 2423 * writes any differences found into the given list of 2424 * modifications. 2425 * 2426 * @param oldElements The set of elements of the specified type 2427 * read from the previous concatenated schema 2428 * files. 2429 * @param newElements The set of elements of the specified type 2430 * read from the server's current schema. 2431 * @param elementType The attribute type associated with the 2432 * schema element being compared. 2433 * @param mods The list of modifications into which any 2434 * identified differences should be written. 2435 */ 2436 public static void compareConcatenatedSchema( 2437 Set<String> oldElements, 2438 Set<String> newElements, 2439 AttributeType elementType, 2440 List<Modification> mods) 2441 { 2442 AttributeBuilder builder = new AttributeBuilder(elementType); 2443 for (String s : oldElements) 2444 { 2445 if (!newElements.contains(s)) 2446 { 2447 builder.add(s); 2448 } 2449 } 2450 2451 if (!builder.isEmpty()) 2452 { 2453 mods.add(new Modification(ModificationType.DELETE, 2454 builder.toAttribute())); 2455 } 2456 2457 builder.setAttributeType(elementType); 2458 for (String s : newElements) 2459 { 2460 if (!oldElements.contains(s)) 2461 { 2462 builder.add(s); 2463 } 2464 } 2465 2466 if (!builder.isEmpty()) 2467 { 2468 mods.add(new Modification(ModificationType.ADD, 2469 builder.toAttribute())); 2470 } 2471 } 2472 2473 2474 2475 /** 2476 * Destroys the structures maintained by the schema so that they are 2477 * no longer usable. This should only be called at the end of the 2478 * server shutdown process, and it can help detect inappropriate 2479 * cached references. 2480 */ 2481 @org.opends.server.types.PublicAPI( 2482 stability=org.opends.server.types.StabilityLevel.PRIVATE, 2483 mayInstantiate=false, 2484 mayExtend=false, 2485 mayInvoke=true) 2486 public synchronized void destroy() 2487 { 2488 if (schemaNG != null) 2489 { 2490 schemaNG = null; 2491 } 2492 2493 if (ditContentRules != null) 2494 { 2495 ditContentRules.clear(); 2496 ditContentRules = null; 2497 } 2498 2499 if (ditStructureRulesByID != null) 2500 { 2501 ditStructureRulesByID.clear(); 2502 ditStructureRulesByID = null; 2503 } 2504 2505 if (ditStructureRulesByNameForm != null) 2506 { 2507 ditStructureRulesByNameForm.clear(); 2508 ditStructureRulesByNameForm = null; 2509 } 2510 2511 if (matchingRuleUses != null) 2512 { 2513 matchingRuleUses.clear(); 2514 matchingRuleUses = null; 2515 } 2516 2517 if (nameFormsByName != null) 2518 { 2519 nameFormsByName.clear(); 2520 nameFormsByName = null; 2521 } 2522 2523 if (nameFormsByOC != null) 2524 { 2525 nameFormsByOC.clear(); 2526 nameFormsByOC = null; 2527 } 2528 2529 if (objectClasses != null) 2530 { 2531 objectClasses.clear(); 2532 objectClasses = null; 2533 } 2534 2535 if (subordinateTypes != null) 2536 { 2537 subordinateTypes.clear(); 2538 subordinateTypes = null; 2539 } 2540 2541 if (extraAttributes != null) 2542 { 2543 extraAttributes.clear(); 2544 extraAttributes = null; 2545 } 2546 2547 if(ldapSyntaxDescriptions != null) 2548 { 2549 ldapSyntaxDescriptions.clear(); 2550 ldapSyntaxDescriptions = null; 2551 } 2552 } 2553 2554 /** 2555 * Update the schema using the provided schema updater. 2556 * <p> 2557 * An implicit lock is performed, so it is in general not necessary 2558 * to call the {code lock()} and {code unlock() methods. 2559 * However, these method should be used if/when the SchemaBuilder passed 2560 * as an argument to the updater is not used to return the schema 2561 * (see for example usage in {@code CoreSchemaProvider} class). This 2562 * case should remain exceptional. 2563 * 2564 * @param updater 2565 * the updater that returns a new schema 2566 * @throws DirectoryException if there is any problem updating the schema 2567 */ 2568 public void updateSchema(SchemaUpdater updater) throws DirectoryException 2569 { 2570 exclusiveLock.lock(); 2571 try 2572 { 2573 switchSchema(updater.update(new SchemaBuilder(schemaNG))); 2574 } 2575 finally 2576 { 2577 exclusiveLock.unlock(); 2578 } 2579 } 2580 2581 /** Interface to update a schema provided a schema builder. */ 2582 public interface SchemaUpdater 2583 { 2584 /** 2585 * Returns an updated schema. 2586 * 2587 * @param builder 2588 * The builder on the current schema 2589 * @return the new schema 2590 */ 2591 org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder); 2592 } 2593 2594 /** 2595 * Updates the schema option if the new value differs from the old value. 2596 * 2597 * @param <T> the schema option's type 2598 * @param option the schema option to update 2599 * @param newValue the new value for the schema option 2600 * @throws DirectoryException if there is any problem updating the schema 2601 */ 2602 public <T> void updateSchemaOption(final Option<T> option, final T newValue) throws DirectoryException 2603 { 2604 final T oldValue = schemaNG.getOption(option); 2605 if (!oldValue.equals(newValue)) 2606 { 2607 updateSchema(new SchemaUpdater() 2608 { 2609 @Override 2610 public org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder) 2611 { 2612 return builder.setOption(option, newValue).toSchema(); 2613 } 2614 }); 2615 } 2616 } 2617 2618 /** Takes an exclusive lock on the schema. */ 2619 public void exclusiveLock() 2620 { 2621 exclusiveLock.lock(); 2622 } 2623 2624 /** Releases an exclusive lock on the schema. */ 2625 public void exclusiveUnlock() 2626 { 2627 exclusiveLock.unlock(); 2628 } 2629 2630 /** 2631 * Adds the provided schema file to the provided schema element definition. 2632 * 2633 * @param definition 2634 * The schema element definition 2635 * @param schemaFile 2636 * The name of the schema file to include in the definition 2637 * @return The definition string of the element 2638 * including the X-SCHEMA-FILE extension. 2639 */ 2640 public static String addSchemaFileToElementDefinitionIfAbsent(String definition, String schemaFile) 2641 { 2642 if (schemaFile != null && !definition.contains(SCHEMA_PROPERTY_FILENAME)) 2643 { 2644 int pos = definition.lastIndexOf(')'); 2645 return definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )"; 2646 } 2647 return definition; 2648 } 2649 2650 private void switchSchema(org.forgerock.opendj.ldap.schema.Schema newSchema) throws DirectoryException 2651 { 2652 rejectSchemaWithWarnings(newSchema); 2653 schemaNG = newSchema.asNonStrictSchema(); 2654 if (DirectoryServer.getSchema() == this) 2655 { 2656 org.forgerock.opendj.ldap.schema.Schema.setDefaultSchema(schemaNG); 2657 } 2658 } 2659 2660 private void rejectSchemaWithWarnings(org.forgerock.opendj.ldap.schema.Schema newSchema) throws DirectoryException 2661 { 2662 Collection<LocalizableMessage> warnings = newSchema.getWarnings(); 2663 if (!warnings.isEmpty()) 2664 { 2665 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 2666 ERR_SCHEMA_HAS_WARNINGS.get(warnings.size(), Utils.joinAsString("; ", warnings))); 2667 } 2668 } 2669}