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.backends; 018 019import static org.forgerock.util.Reject.*; 020import static org.opends.messages.BackendMessages.*; 021import static org.opends.messages.ConfigMessages.*; 022import static org.opends.messages.SchemaMessages.*; 023import static org.opends.server.config.ConfigConstants.*; 024import static org.opends.server.core.DirectoryServer.*; 025import static org.opends.server.types.CommonSchemaElements.*; 026import static org.opends.server.util.CollectionUtils.*; 027import static org.opends.server.util.ServerConstants.*; 028import static org.opends.server.util.StaticUtils.*; 029 030import java.io.File; 031import java.io.FileFilter; 032import java.io.FileInputStream; 033import java.io.FileOutputStream; 034import java.io.IOException; 035import java.nio.file.Path; 036import java.util.ArrayList; 037import java.util.Collection; 038import java.util.Collections; 039import java.util.HashMap; 040import java.util.HashSet; 041import java.util.LinkedHashMap; 042import java.util.LinkedHashSet; 043import java.util.LinkedList; 044import java.util.List; 045import java.util.ListIterator; 046import java.util.Map; 047import java.util.Set; 048import java.util.TreeSet; 049import java.util.concurrent.ConcurrentHashMap; 050 051import org.forgerock.i18n.LocalizableMessage; 052import org.forgerock.i18n.slf4j.LocalizedLogger; 053import org.forgerock.opendj.config.server.ConfigChangeResult; 054import org.forgerock.opendj.config.server.ConfigException; 055import org.forgerock.opendj.ldap.AVA; 056import org.forgerock.opendj.ldap.ByteString; 057import org.forgerock.opendj.ldap.ConditionResult; 058import org.forgerock.opendj.ldap.DN; 059import org.forgerock.opendj.ldap.ModificationType; 060import org.forgerock.opendj.ldap.RDN; 061import org.forgerock.opendj.ldap.ResultCode; 062import org.forgerock.opendj.ldap.SearchScope; 063import org.forgerock.opendj.ldap.schema.AttributeType; 064import org.forgerock.opendj.ldap.schema.CoreSchema; 065import org.forgerock.opendj.ldap.schema.MatchingRule; 066import org.forgerock.opendj.ldap.schema.ObjectClassType; 067import org.opends.server.admin.server.ConfigurationChangeListener; 068import org.opends.server.admin.std.server.SchemaBackendCfg; 069import org.opends.server.api.AlertGenerator; 070import org.opends.server.api.Backend; 071import org.opends.server.api.Backupable; 072import org.opends.server.api.ClientConnection; 073import org.opends.server.config.ConfigEntry; 074import org.opends.server.core.AddOperation; 075import org.opends.server.core.DeleteOperation; 076import org.opends.server.core.DirectoryServer; 077import org.opends.server.core.ModifyDNOperation; 078import org.opends.server.core.ModifyOperation; 079import org.opends.server.core.SchemaConfigManager; 080import org.opends.server.core.SearchOperation; 081import org.opends.server.core.ServerContext; 082import org.opends.server.schema.AttributeTypeSyntax; 083import org.opends.server.schema.DITContentRuleSyntax; 084import org.opends.server.schema.DITStructureRuleSyntax; 085import org.opends.server.schema.GeneralizedTimeSyntax; 086import org.opends.server.schema.MatchingRuleUseSyntax; 087import org.opends.server.schema.NameFormSyntax; 088import org.opends.server.schema.ObjectClassSyntax; 089import org.opends.server.schema.SomeSchemaElement; 090import org.opends.server.types.Attribute; 091import org.opends.server.types.AttributeBuilder; 092import org.opends.server.types.Attributes; 093import org.opends.server.types.BackupConfig; 094import org.opends.server.types.BackupDirectory; 095import org.opends.server.types.CommonSchemaElements; 096import org.opends.server.types.DITContentRule; 097import org.opends.server.types.DITStructureRule; 098import org.opends.server.types.DirectoryException; 099import org.opends.server.types.Entry; 100import org.opends.server.types.ExistingFileBehavior; 101import org.opends.server.types.IndexType; 102import org.opends.server.types.InitializationException; 103import org.opends.server.types.LDAPSyntaxDescription; 104import org.opends.server.types.LDIFExportConfig; 105import org.opends.server.types.LDIFImportConfig; 106import org.opends.server.types.LDIFImportResult; 107import org.opends.server.types.MatchingRuleUse; 108import org.opends.server.types.Modification; 109import org.opends.server.types.NameForm; 110import org.opends.server.types.ObjectClass; 111import org.opends.server.types.Privilege; 112import org.opends.server.types.RestoreConfig; 113import org.opends.server.types.Schema; 114import org.opends.server.types.SchemaFileElement; 115import org.opends.server.types.SearchFilter; 116import org.opends.server.util.BackupManager; 117import org.opends.server.util.DynamicConstants; 118import org.opends.server.util.LDIFException; 119import org.opends.server.util.LDIFReader; 120import org.opends.server.util.LDIFWriter; 121import org.opends.server.util.StaticUtils; 122 123/** 124 * This class defines a backend to hold the Directory Server schema information. 125 * It is a kind of meta-backend in that it doesn't actually hold any data but 126 * rather dynamically generates the schema entry whenever it is requested. 127 */ 128public class SchemaBackend extends Backend<SchemaBackendCfg> 129 implements ConfigurationChangeListener<SchemaBackendCfg>, AlertGenerator, Backupable 130{ 131 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 132 133 /** The fully-qualified name of this class. */ 134 private static final String CLASS_NAME = 135 "org.opends.server.backends.SchemaBackend"; 136 137 private static final String CONFIG_SCHEMA_ELEMENTS_FILE = "02-config.ldif"; 138 private static final String CORE_SCHEMA_ELEMENTS_FILE = "00-core.ldif"; 139 140 /** The set of user-defined attributes that will be included in the schema entry. */ 141 private ArrayList<Attribute> userDefinedAttributes; 142 143 /** The attribute type that will be used to include the defined attribute types. */ 144 private AttributeType attributeTypesType; 145 /** The attribute type that will be used to hold the schema creation timestamp. */ 146 private AttributeType createTimestampType; 147 /** The attribute type that will be used to hold the schema creator's name. */ 148 private AttributeType creatorsNameType; 149 /** The attribute type that will be used to include the defined DIT content rules. */ 150 private AttributeType ditContentRulesType; 151 /** The attribute type that will be used to include the defined DIT structure rules. */ 152 private AttributeType ditStructureRulesType; 153 /** The attribute type that will be used to include the defined attribute syntaxes. */ 154 private AttributeType ldapSyntaxesType; 155 /** The attribute type that will be used to include the defined matching rules. */ 156 private AttributeType matchingRulesType; 157 /** The attribute type that will be used to include the defined matching rule uses. */ 158 private AttributeType matchingRuleUsesType; 159 /** The attribute that will be used to hold the schema modifier's name. */ 160 private AttributeType modifiersNameType; 161 /** The attribute type that will be used to hold the schema modification timestamp. */ 162 private AttributeType modifyTimestampType; 163 /** The attribute type that will be used to include the defined object classes. */ 164 private AttributeType objectClassesType; 165 /** The attribute type that will be used to include the defined name forms. */ 166 private AttributeType nameFormsType; 167 168 /** The value containing DN of the user we'll say created the configuration. */ 169 private ByteString creatorsName; 170 /** The value containing the DN of the last user to modify the configuration. */ 171 private ByteString modifiersName; 172 /** The timestamp that will be used for the schema creation time. */ 173 private ByteString createTimestamp; 174 /** The timestamp that will be used for the latest schema modification time. */ 175 private ByteString modifyTimestamp; 176 177 /** 178 * Indicates whether the attributes of the schema entry should always be 179 * treated as user attributes even if they are defined as operational. 180 */ 181 private boolean showAllAttributes; 182 183 /** The DN of the configuration entry for this backend. */ 184 private DN configEntryDN; 185 186 /** The current configuration state. */ 187 private SchemaBackendCfg currentConfig; 188 189 /** The set of base DNs for this backend. */ 190 private DN[] baseDNs; 191 192 /** The set of objectclasses that will be used in the schema entry. */ 193 private HashMap<ObjectClass,String> schemaObjectClasses; 194 195 /** The time that the schema was last modified. */ 196 private long modifyTime; 197 198 /** 199 * Regular expression used to strip minimum upper bound value from syntax 200 * Attribute Type Description. The value looks like: {count}. 201 */ 202 private String stripMinUpperBoundRegEx = "\\{\\d+\\}"; 203 204 private ServerContext serverContext; 205 206 /** 207 * Creates a new backend with the provided information. All backend 208 * implementations must implement a default constructor that use 209 * <CODE>super()</CODE> to invoke this constructor. 210 */ 211 public SchemaBackend() 212 { 213 super(); 214 215 // Perform all initialization in initializeBackend. 216 } 217 218 @Override 219 public void configureBackend(SchemaBackendCfg cfg, ServerContext serverContext) throws ConfigException 220 { 221 this.serverContext = serverContext; 222 223 // Make sure that a configuration entry was provided. If not, then we will 224 // not be able to complete initialization. 225 if (cfg == null) 226 { 227 LocalizableMessage message = ERR_SCHEMA_CONFIG_ENTRY_NULL.get(); 228 throw new ConfigException(message); 229 } 230 231 ConfigEntry configEntry = DirectoryServer.getConfigEntry(cfg.dn()); 232 233 configEntryDN = configEntry.getDN(); 234 235 // Get all of the attribute types that we will use for schema elements. 236 attributeTypesType = getAttributeType(ATTR_ATTRIBUTE_TYPES_LC); 237 objectClassesType = getAttributeType(ATTR_OBJECTCLASSES_LC); 238 matchingRulesType = getAttributeType(ATTR_MATCHING_RULES_LC); 239 ldapSyntaxesType = getAttributeType(ATTR_LDAP_SYNTAXES_LC); 240 ditContentRulesType = getAttributeType(ATTR_DIT_CONTENT_RULES_LC); 241 ditStructureRulesType = getAttributeType(ATTR_DIT_STRUCTURE_RULES_LC); 242 matchingRuleUsesType = getAttributeType(ATTR_MATCHING_RULE_USE_LC); 243 nameFormsType = getAttributeType(ATTR_NAME_FORMS_LC); 244 245 // Initialize the lastmod attributes. 246 creatorsNameType = getAttributeType(OP_ATTR_CREATORS_NAME_LC); 247 createTimestampType = getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC); 248 modifiersNameType = getAttributeType(OP_ATTR_MODIFIERS_NAME_LC); 249 modifyTimestampType = getAttributeType(OP_ATTR_MODIFY_TIMESTAMP_LC); 250 251 // Construct the set of objectclasses to include in the schema entry. 252 schemaObjectClasses = new LinkedHashMap<>(3); 253 schemaObjectClasses.put(DirectoryServer.getTopObjectClass(), OC_TOP); 254 255 ObjectClass subentryOC = DirectoryServer.getObjectClass(OC_LDAP_SUBENTRY_LC, true); 256 schemaObjectClasses.put(subentryOC, OC_LDAP_SUBENTRY); 257 258 ObjectClass subschemaOC = DirectoryServer.getObjectClass(OC_SUBSCHEMA, true); 259 schemaObjectClasses.put(subschemaOC, OC_SUBSCHEMA); 260 261 262 configEntryDN = configEntry.getDN(); 263 264 DN[] newBaseDNs = new DN[cfg.getBaseDN().size()]; 265 cfg.getBaseDN().toArray(newBaseDNs); 266 this.baseDNs = newBaseDNs; 267 268 creatorsName = ByteString.valueOfUtf8(newBaseDNs[0].toString()); 269 modifiersName = ByteString.valueOfUtf8(newBaseDNs[0].toString()); 270 271 long createTime = DirectoryServer.getSchema().getOldestModificationTime(); 272 createTimestamp = 273 GeneralizedTimeSyntax.createGeneralizedTimeValue(createTime); 274 275 long newModifyTime = 276 DirectoryServer.getSchema().getYoungestModificationTime(); 277 modifyTimestamp = 278 GeneralizedTimeSyntax.createGeneralizedTimeValue(newModifyTime); 279 280 281 // Get the set of user-defined attributes for the configuration entry. Any 282 // attributes that we don't recognize will be included directly in the 283 // schema entry. 284 userDefinedAttributes = new ArrayList<>(); 285 addAll(configEntry.getEntry().getUserAttributes().values()); 286 addAll(configEntry.getEntry().getOperationalAttributes().values()); 287 288 showAllAttributes = cfg.isShowAllAttributes(); 289 290 currentConfig = cfg; 291 } 292 293 private void addAll(Collection<List<Attribute>> attrsList) 294 { 295 for (List<Attribute> attrs : attrsList) 296 { 297 for (Attribute a : attrs) 298 { 299 if (! isSchemaConfigAttribute(a)) 300 { 301 userDefinedAttributes.add(a); 302 } 303 } 304 } 305 } 306 307 @Override 308 public void openBackend() throws ConfigException, InitializationException 309 { 310 // Register each of the suffixes with the Directory Server. Also, register 311 // the first one as the schema base. 312 DirectoryServer.setSchemaDN(baseDNs[0]); 313 for (DN baseDN : baseDNs) { 314 try { 315 DirectoryServer.registerBaseDN(baseDN, this, true); 316 } catch (Exception e) { 317 logger.traceException(e); 318 319 LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get( 320 baseDN, getExceptionMessage(e)); 321 throw new InitializationException(message, e); 322 } 323 } 324 325 326 // Identify any differences that may exist between the concatenated schema 327 // file from the last online modification and the current schema files. If 328 // there are any differences, then they should be from making changes to the 329 // schema files with the server offline. 330 try 331 { 332 // First, generate lists of elements from the current schema. 333 Set<String> newATs = new LinkedHashSet<>(); 334 Set<String> newOCs = new LinkedHashSet<>(); 335 Set<String> newNFs = new LinkedHashSet<>(); 336 Set<String> newDCRs = new LinkedHashSet<>(); 337 Set<String> newDSRs = new LinkedHashSet<>(); 338 Set<String> newMRUs = new LinkedHashSet<>(); 339 Set<String> newLSDs = new LinkedHashSet<>(); 340 Schema.genConcatenatedSchema(newATs, newOCs, newNFs, newDCRs, newDSRs, newMRUs,newLSDs); 341 342 // Next, generate lists of elements from the previous concatenated schema. 343 // If there isn't a previous concatenated schema, then use the base 344 // schema for the current revision. 345 String concatFilePath; 346 File configFile = new File(DirectoryServer.getConfigFile()); 347 File configDirectory = configFile.getParentFile(); 348 File upgradeDirectory = new File(configDirectory, "upgrade"); 349 File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME); 350 if (concatFile.exists()) 351 { 352 concatFilePath = concatFile.getAbsolutePath(); 353 } 354 else 355 { 356 concatFile = new File(upgradeDirectory, SCHEMA_BASE_FILE_NAME_WITHOUT_REVISION + DynamicConstants.REVISION); 357 if (concatFile.exists()) 358 { 359 concatFilePath = concatFile.getAbsolutePath(); 360 } 361 else 362 { 363 String runningUnitTestsStr = 364 System.getProperty(PROPERTY_RUNNING_UNIT_TESTS); 365 if ("true".equalsIgnoreCase(runningUnitTestsStr)) 366 { 367 Schema.writeConcatenatedSchema(); 368 concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME); 369 concatFilePath = concatFile.getAbsolutePath(); 370 } 371 else 372 { 373 LocalizableMessage message = ERR_SCHEMA_CANNOT_FIND_CONCAT_FILE. 374 get(upgradeDirectory.getAbsolutePath(), SCHEMA_CONCAT_FILE_NAME, 375 concatFile.getName()); 376 throw new InitializationException(message); 377 } 378 } 379 } 380 381 Set<String> oldATs = new LinkedHashSet<>(); 382 Set<String> oldOCs = new LinkedHashSet<>(); 383 Set<String> oldNFs = new LinkedHashSet<>(); 384 Set<String> oldDCRs = new LinkedHashSet<>(); 385 Set<String> oldDSRs = new LinkedHashSet<>(); 386 Set<String> oldMRUs = new LinkedHashSet<>(); 387 Set<String> oldLSDs = new LinkedHashSet<>(); 388 Schema.readConcatenatedSchema(concatFilePath, oldATs, oldOCs, oldNFs, 389 oldDCRs, oldDSRs, oldMRUs,oldLSDs); 390 391 // Create a list of modifications and add any differences between the old 392 // and new schema into them. 393 List<Modification> mods = new LinkedList<>(); 394 Schema.compareConcatenatedSchema(oldATs, newATs, attributeTypesType, mods); 395 Schema.compareConcatenatedSchema(oldOCs, newOCs, objectClassesType, mods); 396 Schema.compareConcatenatedSchema(oldNFs, newNFs, nameFormsType, mods); 397 Schema.compareConcatenatedSchema(oldDCRs, newDCRs, ditContentRulesType, mods); 398 Schema.compareConcatenatedSchema(oldDSRs, newDSRs, ditStructureRulesType, mods); 399 Schema.compareConcatenatedSchema(oldMRUs, newMRUs, matchingRuleUsesType, mods); 400 Schema.compareConcatenatedSchema(oldLSDs, newLSDs, ldapSyntaxesType, mods); 401 if (! mods.isEmpty()) 402 { 403 // TODO : Raise an alert notification. 404 405 DirectoryServer.setOfflineSchemaChanges(mods); 406 407 // Write a new concatenated schema file with the most recent information 408 // so we don't re-find these same changes on the next startup. 409 Schema.writeConcatenatedSchema(); 410 } 411 } 412 catch (InitializationException ie) 413 { 414 throw ie; 415 } 416 catch (Exception e) 417 { 418 logger.traceException(e); 419 420 logger.error(ERR_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES, getExceptionMessage(e)); 421 } 422 423 424 // Register with the Directory Server as a configurable component. 425 currentConfig.addSchemaChangeListener(this); 426 } 427 428 @Override 429 public void closeBackend() 430 { 431 currentConfig.removeSchemaChangeListener(this); 432 433 for (DN baseDN : baseDNs) 434 { 435 try 436 { 437 DirectoryServer.deregisterBaseDN(baseDN); 438 } 439 catch (Exception e) 440 { 441 logger.traceException(e); 442 } 443 } 444 } 445 446 447 448 /** 449 * Indicates whether the provided attribute is one that is used in the 450 * configuration of this backend. 451 * 452 * @param attribute The attribute for which to make the determination. 453 * 454 * @return <CODE>true</CODE> if the provided attribute is one that is used in 455 * the configuration of this backend, <CODE>false</CODE> if not. 456 */ 457 private boolean isSchemaConfigAttribute(Attribute attribute) 458 { 459 AttributeType attrType = attribute.getAttributeDescription().getAttributeType(); 460 return attrType.hasName(ATTR_SCHEMA_ENTRY_DN.toLowerCase()) || 461 attrType.hasName(ATTR_BACKEND_ENABLED.toLowerCase()) || 462 attrType.hasName(ATTR_BACKEND_CLASS.toLowerCase()) || 463 attrType.hasName(ATTR_BACKEND_ID.toLowerCase()) || 464 attrType.hasName(ATTR_BACKEND_BASE_DN.toLowerCase()) || 465 attrType.hasName(ATTR_BACKEND_WRITABILITY_MODE.toLowerCase()) || 466 attrType.hasName(ATTR_SCHEMA_SHOW_ALL_ATTRIBUTES.toLowerCase()) || 467 attrType.hasName(ATTR_COMMON_NAME) || 468 attrType.hasName(OP_ATTR_CREATORS_NAME_LC) || 469 attrType.hasName(OP_ATTR_CREATE_TIMESTAMP_LC) || 470 attrType.hasName(OP_ATTR_MODIFIERS_NAME_LC) || 471 attrType.hasName(OP_ATTR_MODIFY_TIMESTAMP_LC); 472 473 } 474 475 @Override 476 public DN[] getBaseDNs() 477 { 478 return baseDNs; 479 } 480 481 @Override 482 public long getEntryCount() 483 { 484 // There is always only a single entry in this backend. 485 return 1; 486 } 487 488 @Override 489 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 490 { 491 // All searches in this backend will always be considered indexed. 492 return true; 493 } 494 495 @Override 496 public ConditionResult hasSubordinates(DN entryDN) 497 throws DirectoryException 498 { 499 return ConditionResult.FALSE; 500 } 501 502 @Override 503 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException 504 { 505 checkNotNull(baseDN, "baseDN must not be null"); 506 return 1L; 507 } 508 509 @Override 510 public long getNumberOfChildren(DN parentDN) throws DirectoryException 511 { 512 checkNotNull(parentDN, "parentDN must not be null"); 513 return 0L; 514 } 515 516 @Override 517 public Entry getEntry(DN entryDN) throws DirectoryException 518 { 519 // If the requested entry was one of the schema entries, then create and return it. 520 if (entryExists(entryDN)) 521 { 522 return getSchemaEntry(entryDN, false, true); 523 } 524 525 // There is never anything below the schema entries, so we will return null. 526 return null; 527 } 528 529 530 /** 531 * Generates and returns a schema entry for the Directory Server. 532 * 533 * @param entryDN The DN to use for the generated entry. 534 * @param includeSchemaFile A boolean indicating if the X-SCHEMA-FILE 535 * extension should be used when generating 536 * the entry. 537 * 538 * @return The schema entry that was generated. 539 */ 540 public Entry getSchemaEntry(DN entryDN, boolean includeSchemaFile) 541 { 542 return getSchemaEntry(entryDN, includeSchemaFile, false); 543 } 544 545 /** 546 * Generates and returns a schema entry for the Directory Server. 547 * 548 * @param entryDN The DN to use for the generated entry. 549 * @param includeSchemaFile A boolean indicating if the X-SCHEMA-FILE 550 * extension should be used when generating 551 * the entry. 552 * @param ignoreShowAllOption A boolean indicating if the showAllAttributes 553 * parameter should be ignored or not. It must 554 * only considered for Search operation, and 555 * definitely ignored for Modify operations, i.e. 556 * when calling through getEntry(). 557 * 558 * @return The schema entry that was generated. 559 */ 560 private Entry getSchemaEntry(DN entryDN, boolean includeSchemaFile, 561 boolean ignoreShowAllOption) 562 { 563 Map<AttributeType, List<Attribute>> userAttrs = new LinkedHashMap<>(); 564 Map<AttributeType, List<Attribute>> operationalAttrs = new LinkedHashMap<>(); 565 566 // Add the RDN attribute(s) for the provided entry. 567 RDN rdn = entryDN.rdn(); 568 if (rdn != null) 569 { 570 for (AVA ava : rdn) 571 { 572 AttributeType attrType = ava.getAttributeType(); 573 Attribute attribute = Attributes.create(attrType, ava.getAttributeValue()); 574 addAttributeToSchemaEntry(attribute, userAttrs, operationalAttrs); 575 } 576 } 577 578 /* 579 * Add the schema definition attributes. 580 */ 581 Schema schema = DirectoryServer.getSchema(); 582 buildSchemaAttribute(schema.getAttributeTypes(), userAttrs, 583 operationalAttrs, attributeTypesType, includeSchemaFile, 584 AttributeTypeSyntax.isStripSyntaxMinimumUpperBound(), 585 ignoreShowAllOption); 586 buildSchemaAttribute(schema.getObjectClasses().values(), userAttrs, 587 operationalAttrs, objectClassesType, includeSchemaFile, false, 588 ignoreShowAllOption); 589 buildSchemaAttribute(schema.getMatchingRules(), userAttrs, 590 operationalAttrs, matchingRulesType, includeSchemaFile, false, 591 ignoreShowAllOption); 592 593 /* 594 * Note that we intentionally ignore showAllAttributes for attribute 595 * syntaxes, name forms, matching rule uses, DIT content rules, and DIT 596 * structure rules because those attributes aren't allowed in the subschema 597 * objectclass, and treating them as user attributes would cause schema 598 * updates to fail. This means that you'll always have to explicitly request 599 * these attributes in order to be able to see them. 600 */ 601 buildSchemaAttribute(schema.getSyntaxes(), userAttrs, 602 operationalAttrs, ldapSyntaxesType, includeSchemaFile, false, true); 603 buildSchemaAttribute(schema.getNameFormsByNameOrOID().values(), userAttrs, 604 operationalAttrs, nameFormsType, includeSchemaFile, false, true); 605 buildSchemaAttribute(schema.getDITContentRules().values(), userAttrs, 606 operationalAttrs, ditContentRulesType, includeSchemaFile, false, true); 607 buildSchemaAttribute(schema.getDITStructureRulesByID().values(), userAttrs, 608 operationalAttrs, ditStructureRulesType, includeSchemaFile, false, true); 609 buildSchemaAttribute(schema.getMatchingRuleUses().values(), userAttrs, 610 operationalAttrs, matchingRuleUsesType, includeSchemaFile, false, true); 611 612 // Add the lastmod attributes. 613 if (DirectoryServer.getSchema().getYoungestModificationTime() != modifyTime) 614 { 615 synchronized (this) 616 { 617 modifyTime = DirectoryServer.getSchema().getYoungestModificationTime(); 618 modifyTimestamp = GeneralizedTimeSyntax 619 .createGeneralizedTimeValue(modifyTime); 620 } 621 } 622 addAttributeToSchemaEntry( 623 Attributes.create(creatorsNameType, creatorsName), userAttrs, operationalAttrs); 624 addAttributeToSchemaEntry( 625 Attributes.create(createTimestampType, createTimestamp), userAttrs, operationalAttrs); 626 addAttributeToSchemaEntry( 627 Attributes.create(modifiersNameType, modifiersName), userAttrs, operationalAttrs); 628 addAttributeToSchemaEntry( 629 Attributes.create(modifyTimestampType, modifyTimestamp), userAttrs, operationalAttrs); 630 631 // Add the extra attributes. 632 for (Attribute attribute : DirectoryServer.getSchema().getExtraAttributes().values()) 633 { 634 addAttributeToSchemaEntry(attribute, userAttrs, operationalAttrs); 635 } 636 637 // Add all the user-defined attributes. 638 for (Attribute attribute : userDefinedAttributes) 639 { 640 addAttributeToSchemaEntry(attribute, userAttrs, operationalAttrs); 641 } 642 643 // Construct and return the entry. 644 Entry e = new Entry(entryDN, schemaObjectClasses, userAttrs, operationalAttrs); 645 e.processVirtualAttributes(); 646 return e; 647 } 648 649 650 651 private void addAttributeToSchemaEntry(Attribute attribute, 652 Map<AttributeType, List<Attribute>> userAttrs, 653 Map<AttributeType, List<Attribute>> operationalAttrs) 654 { 655 AttributeType type = attribute.getAttributeDescription().getAttributeType(); 656 Map<AttributeType, List<Attribute>> attrsMap = type.isOperational() ? operationalAttrs : userAttrs; 657 List<Attribute> attrs = attrsMap.get(type); 658 if (attrs == null) 659 { 660 attrs = new ArrayList<>(1); 661 attrsMap.put(type, attrs); 662 } 663 attrs.add(attribute); 664 } 665 666 667 668 private void buildSchemaAttribute(Collection<?> elements, 669 Map<AttributeType, List<Attribute>> userAttrs, 670 Map<AttributeType, List<Attribute>> operationalAttrs, 671 AttributeType schemaAttributeType, boolean includeSchemaFile, 672 final boolean stripSyntaxMinimumUpperBound, boolean ignoreShowAllOption) 673 { 674 // Skip the schema attribute if it is empty. 675 if (elements.isEmpty()) 676 { 677 return; 678 } 679 680 AttributeBuilder builder = new AttributeBuilder(schemaAttributeType); 681 for (Object element : elements) 682 { 683 /* 684 * Add the file name to the description of the element if this was 685 * requested by the caller. 686 */ 687 String value; 688 if (includeSchemaFile && element instanceof CommonSchemaElements) 689 { 690 value = getDefinitionWithFileName((CommonSchemaElements) element); 691 } 692 else 693 { 694 value = element.toString(); 695 } 696 if (stripSyntaxMinimumUpperBound && value.indexOf('{') != -1) 697 { 698 // Strip the minimum upper bound value from the attribute value. 699 value = value.replaceFirst(stripMinUpperBoundRegEx, ""); 700 } 701 builder.add(value); 702 } 703 704 Attribute attribute = builder.toAttribute(); 705 ArrayList<Attribute> attrList = newArrayList(attribute); 706 if (attribute.getAttributeDescription().getAttributeType().isOperational() 707 && (ignoreShowAllOption || !showAllAttributes)) 708 { 709 operationalAttrs.put(attribute.getAttributeDescription().getAttributeType(), attrList); 710 } 711 else 712 { 713 userAttrs.put(attribute.getAttributeDescription().getAttributeType(), attrList); 714 } 715 } 716 717 @Override 718 public boolean entryExists(DN entryDN) throws DirectoryException 719 { 720 // The specified DN must be one of the specified schema DNs. 721 DN[] baseArray = baseDNs; 722 for (DN baseDN : baseArray) 723 { 724 if (entryDN.equals(baseDN)) 725 { 726 return true; 727 } 728 } 729 return false; 730 } 731 732 @Override 733 public void addEntry(Entry entry, AddOperation addOperation) 734 throws DirectoryException 735 { 736 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 737 ERR_BACKEND_ADD_NOT_SUPPORTED.get(entry.getName(), getBackendID())); 738 } 739 740 @Override 741 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) 742 throws DirectoryException 743 { 744 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 745 ERR_BACKEND_DELETE_NOT_SUPPORTED.get(entryDN, getBackendID())); 746 } 747 748 @Override 749 public void replaceEntry(Entry oldEntry, Entry newEntry, 750 ModifyOperation modifyOperation) throws DirectoryException 751 { 752 // Make sure that the authenticated user has the necessary UPDATE_SCHEMA 753 // privilege. 754 ClientConnection clientConnection = modifyOperation.getClientConnection(); 755 if (! clientConnection.hasPrivilege(Privilege.UPDATE_SCHEMA, 756 modifyOperation)) 757 { 758 LocalizableMessage message = ERR_SCHEMA_MODIFY_INSUFFICIENT_PRIVILEGES.get(); 759 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 760 message); 761 } 762 763 764 ArrayList<Modification> mods = new ArrayList<>(modifyOperation.getModifications()); 765 if (mods.isEmpty()) 766 { 767 // There aren't any modifications, so we don't need to do anything. 768 return; 769 } 770 771 Schema newSchema = DirectoryServer.getSchema().duplicate(); 772 TreeSet<String> modifiedSchemaFiles = new TreeSet<>(); 773 774 int pos = -1; 775 for (Modification m : mods) 776 { 777 pos++; 778 779 // Determine the type of modification to perform. We will support add and 780 // delete operations in the schema, and we will also support the ability 781 // to add a schema element that already exists and treat it as a 782 // replacement of that existing element. 783 Attribute a = m.getAttribute(); 784 AttributeType at = a.getAttributeDescription().getAttributeType(); 785 switch (m.getModificationType().asEnum()) 786 { 787 case ADD: 788 if (at.equals(attributeTypesType)) 789 { 790 for (ByteString v : a) 791 { 792 AttributeType attributeType = newSchema.parseAttributeType(v.toString()); 793 addAttributeType(attributeType, newSchema, modifiedSchemaFiles); 794 } 795 } 796 else if (at.equals(objectClassesType)) 797 { 798 for (ByteString v : a) 799 { 800 ObjectClass oc; 801 try 802 { 803 oc = ObjectClassSyntax.decodeObjectClass(v, newSchema, false); 804 } 805 catch (DirectoryException de) 806 { 807 logger.traceException(de); 808 809 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS. 810 get(v, de.getMessageObject()); 811 throw new DirectoryException( 812 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 813 } 814 815 addObjectClass(oc, newSchema, modifiedSchemaFiles); 816 } 817 } 818 else if (at.equals(nameFormsType)) 819 { 820 for (ByteString v : a) 821 { 822 NameForm nf; 823 try 824 { 825 nf = NameFormSyntax.decodeNameForm(v, newSchema, false); 826 } 827 catch (DirectoryException de) 828 { 829 logger.traceException(de); 830 831 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM.get( 832 v, de.getMessageObject()); 833 throw new DirectoryException( 834 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 835 } 836 837 addNameForm(nf, newSchema, modifiedSchemaFiles); 838 } 839 } 840 else if (at.equals(ditContentRulesType)) 841 { 842 for (ByteString v : a) 843 { 844 DITContentRule dcr; 845 try 846 { 847 dcr = DITContentRuleSyntax.decodeDITContentRule(v, newSchema, false); 848 } 849 catch (DirectoryException de) 850 { 851 logger.traceException(de); 852 853 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DCR.get( 854 v, de.getMessageObject()); 855 throw new DirectoryException( 856 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 857 } 858 859 addDITContentRule(dcr, newSchema, modifiedSchemaFiles); 860 } 861 } 862 else if (at.equals(ditStructureRulesType)) 863 { 864 for (ByteString v : a) 865 { 866 DITStructureRule dsr; 867 try 868 { 869 dsr = DITStructureRuleSyntax.decodeDITStructureRule(v, newSchema, false); 870 } 871 catch (DirectoryException de) 872 { 873 logger.traceException(de); 874 875 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DSR.get( 876 v, de.getMessageObject()); 877 throw new DirectoryException( 878 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 879 } 880 881 addDITStructureRule(dsr, newSchema, modifiedSchemaFiles); 882 } 883 } 884 else if (at.equals(matchingRuleUsesType)) 885 { 886 for (ByteString v : a) 887 { 888 MatchingRuleUse mru; 889 try 890 { 891 mru = MatchingRuleUseSyntax.decodeMatchingRuleUse(v, newSchema, false); 892 } 893 catch (DirectoryException de) 894 { 895 logger.traceException(de); 896 897 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_MR_USE.get( 898 v, de.getMessageObject()); 899 throw new DirectoryException( 900 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 901 } 902 903 addMatchingRuleUse(mru, newSchema, modifiedSchemaFiles); 904 } 905 } 906 else if (at.equals(ldapSyntaxesType)) 907 { 908 for (ByteString v : a) 909 { 910 try 911 { 912 addLdapSyntaxDescription(v.toString(), newSchema, modifiedSchemaFiles); 913 } 914 catch (DirectoryException de) 915 { 916 logger.traceException(de); 917 918 LocalizableMessage message = 919 ERR_SCHEMA_MODIFY_CANNOT_DECODE_LDAP_SYNTAX.get(v, de.getMessageObject()); 920 throw new DirectoryException( 921 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 922 } 923 } 924 } 925 else 926 { 927 LocalizableMessage message = 928 ERR_SCHEMA_MODIFY_UNSUPPORTED_ATTRIBUTE_TYPE.get(a.getName()); 929 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 930 message); 931 } 932 933 break; 934 935 936 case DELETE: 937 if (a.isEmpty()) 938 { 939 LocalizableMessage message = 940 ERR_SCHEMA_MODIFY_DELETE_NO_VALUES.get(a.getName()); 941 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 942 message); 943 } 944 945 if (at.equals(attributeTypesType)) 946 { 947 for (ByteString v : a) 948 { 949 AttributeType type = newSchema.parseAttributeType(v.toString()); 950 removeAttributeType(type, newSchema, modifiedSchemaFiles); 951 } 952 } 953 else if (at.equals(objectClassesType)) 954 { 955 for (ByteString v : a) 956 { 957 ObjectClass oc; 958 try 959 { 960 oc = ObjectClassSyntax.decodeObjectClass(v, newSchema, false); 961 } 962 catch (DirectoryException de) 963 { 964 logger.traceException(de); 965 966 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS. 967 get(v, de.getMessageObject()); 968 throw new DirectoryException( 969 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 970 } 971 972 removeObjectClass(oc, newSchema, mods, pos, modifiedSchemaFiles); 973 } 974 } 975 else if (at.equals(nameFormsType)) 976 { 977 for (ByteString v : a) 978 { 979 NameForm nf; 980 try 981 { 982 nf = NameFormSyntax.decodeNameForm(v, newSchema, false); 983 } 984 catch (DirectoryException de) 985 { 986 logger.traceException(de); 987 988 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM.get( 989 v, de.getMessageObject()); 990 throw new DirectoryException( 991 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 992 } 993 994 removeNameForm(nf, newSchema, mods, pos, modifiedSchemaFiles); 995 } 996 } 997 else if (at.equals(ditContentRulesType)) 998 { 999 for (ByteString v : a) 1000 { 1001 DITContentRule dcr; 1002 try 1003 { 1004 dcr = DITContentRuleSyntax.decodeDITContentRule(v, newSchema, false); 1005 } 1006 catch (DirectoryException de) 1007 { 1008 logger.traceException(de); 1009 1010 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DCR.get( 1011 v, de.getMessageObject()); 1012 throw new DirectoryException( 1013 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 1014 } 1015 1016 removeDITContentRule(dcr, newSchema, modifiedSchemaFiles); 1017 } 1018 } 1019 else if (at.equals(ditStructureRulesType)) 1020 { 1021 for (ByteString v : a) 1022 { 1023 DITStructureRule dsr; 1024 try 1025 { 1026 dsr = DITStructureRuleSyntax.decodeDITStructureRule(v, newSchema, false); 1027 } 1028 catch (DirectoryException de) 1029 { 1030 logger.traceException(de); 1031 1032 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DSR.get( 1033 v, de.getMessageObject()); 1034 throw new DirectoryException( 1035 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 1036 } 1037 1038 removeDITStructureRule(dsr, newSchema, mods, pos, 1039 modifiedSchemaFiles); 1040 } 1041 } 1042 else if (at.equals(matchingRuleUsesType)) 1043 { 1044 for (ByteString v : a) 1045 { 1046 MatchingRuleUse mru; 1047 try 1048 { 1049 mru = MatchingRuleUseSyntax.decodeMatchingRuleUse(v, newSchema, false); 1050 } 1051 catch (DirectoryException de) 1052 { 1053 logger.traceException(de); 1054 1055 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_MR_USE.get( 1056 v, de.getMessageObject()); 1057 throw new DirectoryException( 1058 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 1059 } 1060 1061 removeMatchingRuleUse(mru, newSchema, modifiedSchemaFiles); 1062 } 1063 } 1064 else if (at.equals(ldapSyntaxesType)) 1065 { 1066 for (ByteString v : a) 1067 { 1068 try 1069 { 1070 removeLdapSyntaxDescription(v.toString(), newSchema, modifiedSchemaFiles); 1071 } 1072 catch (DirectoryException de) 1073 { 1074 logger.traceException(de); 1075 1076 LocalizableMessage message = 1077 ERR_SCHEMA_MODIFY_CANNOT_DECODE_LDAP_SYNTAX.get( 1078 v, de.getMessageObject()); 1079 throw new DirectoryException( 1080 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 1081 } 1082 } 1083 } 1084 else 1085 { 1086 LocalizableMessage message = 1087 ERR_SCHEMA_MODIFY_UNSUPPORTED_ATTRIBUTE_TYPE.get(a.getName()); 1088 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1089 message); 1090 } 1091 1092 break; 1093 1094 1095 case REPLACE: 1096 if (!m.isInternal() 1097 && !modifyOperation.isSynchronizationOperation()) 1098 { 1099 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1100 ERR_SCHEMA_INVALID_MODIFICATION_TYPE.get(m.getModificationType())); 1101 } 1102 else if (SchemaConfigManager.isSchemaAttribute(a)) 1103 { 1104 logger.error(ERR_SCHEMA_INVALID_REPLACE_MODIFICATION, a.getNameWithOptions()); 1105 } 1106 else 1107 { 1108 // If this is not a Schema attribute, we put it 1109 // in the extraAttribute map. This in fact acts as a replace. 1110 newSchema.addExtraAttribute(at.getNameOrOID(), a); 1111 modifiedSchemaFiles.add(FILE_USER_SCHEMA_ELEMENTS); 1112 } 1113 break; 1114 1115 default: 1116 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1117 ERR_SCHEMA_INVALID_MODIFICATION_TYPE.get(m.getModificationType())); 1118 } 1119 } 1120 1121 1122 // If we've gotten here, then everything looks OK, re-write all the 1123 // modified Schema Files. 1124 updateSchemaFiles(newSchema, modifiedSchemaFiles); 1125 1126 // Finally set DirectoryServer to use the new Schema. 1127 DirectoryServer.setSchema(newSchema); 1128 1129 1130 DN authzDN = modifyOperation.getAuthorizationDN(); 1131 if (authzDN == null) 1132 { 1133 authzDN = DN.rootDN(); 1134 } 1135 1136 modifiersName = ByteString.valueOfUtf8(authzDN.toString()); 1137 modifyTimestamp = GeneralizedTimeSyntax.createGeneralizedTimeValue( 1138 System.currentTimeMillis()); 1139 } 1140 1141 1142 1143 /** 1144 * Re-write all schema files using the provided new Schema and list of 1145 * modified files. 1146 * 1147 * @param newSchema The new schema that should be used. 1148 * 1149 * @param modifiedSchemaFiles The list of files that should be modified. 1150 * 1151 * @throws DirectoryException When the new file cannot be written. 1152 */ 1153 private void updateSchemaFiles( 1154 Schema newSchema, TreeSet<String> modifiedSchemaFiles) 1155 throws DirectoryException 1156 { 1157 // We'll re-write all 1158 // impacted schema files by first creating them in a temporary location 1159 // and then replacing the existing schema files with the new versions. 1160 // If all that goes successfully, then activate the new schema. 1161 HashMap<String, File> tempSchemaFiles = new HashMap<>(); 1162 try 1163 { 1164 for (String schemaFile : modifiedSchemaFiles) 1165 { 1166 File tempSchemaFile = writeTempSchemaFile(newSchema, schemaFile); 1167 tempSchemaFiles.put(schemaFile, tempSchemaFile); 1168 } 1169 1170 installSchemaFiles(tempSchemaFiles); 1171 } 1172 catch (DirectoryException de) 1173 { 1174 logger.traceException(de); 1175 1176 throw de; 1177 } 1178 catch (Exception e) 1179 { 1180 logger.traceException(e); 1181 1182 LocalizableMessage message = 1183 ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_SCHEMA.get(getExceptionMessage(e)); 1184 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 1185 message, e); 1186 } 1187 finally 1188 { 1189 cleanUpTempSchemaFiles(tempSchemaFiles); 1190 } 1191 1192 1193 // Create a single file with all of the concatenated schema information 1194 // that we can use on startup to detect whether the schema files have been 1195 // edited with the server offline. 1196 Schema.writeConcatenatedSchema(); 1197 } 1198 1199 1200 1201 /** 1202 * Handles all processing required for adding the provided attribute type to 1203 * the given schema, replacing an existing type if necessary, and ensuring all 1204 * other metadata is properly updated. 1205 * 1206 * @param attributeType The attribute type to add or replace in the 1207 * server schema. 1208 * @param schema The schema to which the attribute type should 1209 * be added. 1210 * @param modifiedSchemaFiles The names of the schema files containing 1211 * schema elements that have been updated as part 1212 * of the schema modification. 1213 * 1214 * @throws DirectoryException If a problem occurs while attempting to add 1215 * the provided attribute type to the server 1216 * schema. 1217 */ 1218 private void addAttributeType(AttributeType attributeType, Schema schema, Set<String> modifiedSchemaFiles) 1219 throws DirectoryException 1220 { 1221 // Check if there is only a single attribute type for each name 1222 // This is not checked by the SDK schema. 1223 AttributeType existingType = schema.getAttributeType(attributeType.getOID()); 1224 for (String name : attributeType.getNames()) 1225 { 1226 AttributeType t = schema.getAttributeType(name); 1227 if (t.isPlaceHolder()) 1228 { 1229 continue; 1230 } 1231 if (existingType.isPlaceHolder()) 1232 { 1233 existingType = t; 1234 } 1235 else if (existingType != t) 1236 { 1237 // NOTE: We really do want to use "!=" instead of "! t.equals()" 1238 // because we want to check whether it's the same object instance, not 1239 // just a logical equivalent. 1240 LocalizableMessage message = ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_ATTRTYPE.get( 1241 attributeType.getNameOrOID(), existingType.getNameOrOID(), t.getNameOrOID()); 1242 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1243 } 1244 } 1245 1246 // Make sure that the new attribute type doesn't reference an 1247 // OBSOLETE superior attribute type. 1248 AttributeType superiorType = attributeType.getSuperiorType(); 1249 if (superiorType != null && superiorType.isObsolete()) 1250 { 1251 LocalizableMessage message = ERR_SCHEMA_MODIFY_OBSOLETE_SUPERIOR_ATTRIBUTE_TYPE.get( 1252 attributeType.getNameOrOID(), superiorType.getNameOrOID()); 1253 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1254 } 1255 1256 // Make sure that none of the associated matching rules are marked OBSOLETE. 1257 throwIfObsoleteMatchingRule(attributeType, attributeType.getEqualityMatchingRule()); 1258 throwIfObsoleteMatchingRule(attributeType, attributeType.getOrderingMatchingRule()); 1259 throwIfObsoleteMatchingRule(attributeType, attributeType.getSubstringMatchingRule()); 1260 throwIfObsoleteMatchingRule(attributeType, attributeType.getApproximateMatchingRule()); 1261 1262 // If there is no existing type, then we're adding a new attribute. 1263 // Otherwise, we're replacing an existing one. 1264 if (existingType.isPlaceHolder()) 1265 { 1266 String schemaFile = addNewSchemaElement(modifiedSchemaFiles, new SomeSchemaElement(attributeType)); 1267 schema.registerAttributeType(attributeType, schemaFile, false); 1268 } 1269 else 1270 { 1271 schema.deregisterAttributeType(existingType); 1272 1273 String schemaFile = replaceExistingSchemaElement( 1274 modifiedSchemaFiles, new SomeSchemaElement(attributeType), new SomeSchemaElement(existingType)); 1275 schema.registerAttributeType(attributeType, schemaFile, false); 1276 schema.rebuildDependentElements(new SomeSchemaElement(existingType)); 1277 } 1278 } 1279 1280 private void throwIfObsoleteMatchingRule(AttributeType attributeType, MatchingRule mr) throws DirectoryException 1281 { 1282 if (mr != null && mr.isObsolete()) 1283 { 1284 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1285 ERR_SCHEMA_MODIFY_ATTRTYPE_OBSOLETE_MR.get(attributeType.getNameOrOID(), mr.getNameOrOID())); 1286 } 1287 } 1288 1289 private void addNewSchemaElement(Set<String> modifiedSchemaFiles, SchemaFileElement elem) 1290 { 1291 String schemaFile = getSchemaFile(elem); 1292 if (schemaFile == null || schemaFile.length() == 0) 1293 { 1294 schemaFile = FILE_USER_SCHEMA_ELEMENTS; 1295 setSchemaFile(elem, schemaFile); 1296 } 1297 modifiedSchemaFiles.add(schemaFile); 1298 } 1299 1300 /** Update list of modified files and return the schema file to use for the added element (may be null). */ 1301 private String addNewSchemaElement(Set<String> modifiedSchemaFiles, SomeSchemaElement elem) 1302 { 1303 String schemaFile = elem.getSchemaFile(); 1304 String finalFile = schemaFile != null ? schemaFile : FILE_USER_SCHEMA_ELEMENTS; 1305 modifiedSchemaFiles.add(finalFile); 1306 return schemaFile == null ? finalFile : null; 1307 } 1308 1309 private <T extends SchemaFileElement> void replaceExistingSchemaElement( 1310 Set<String> modifiedSchemaFiles, T newElem, T existingElem) 1311 { 1312 String newSchemaFile = getSchemaFile(newElem); 1313 String oldSchemaFile = getSchemaFile(existingElem); 1314 if (newSchemaFile == null || newSchemaFile.length() == 0) 1315 { 1316 if (oldSchemaFile == null || oldSchemaFile.length() == 0) 1317 { 1318 oldSchemaFile = FILE_USER_SCHEMA_ELEMENTS; 1319 } 1320 1321 setSchemaFile(newElem, oldSchemaFile); 1322 modifiedSchemaFiles.add(oldSchemaFile); 1323 } 1324 else if (oldSchemaFile == null || oldSchemaFile.equals(newSchemaFile)) 1325 { 1326 modifiedSchemaFiles.add(newSchemaFile); 1327 } 1328 else 1329 { 1330 modifiedSchemaFiles.add(newSchemaFile); 1331 modifiedSchemaFiles.add(oldSchemaFile); 1332 } 1333 } 1334 1335 /** Update list of modified files and return the schema file to use for the new element (may be null). */ 1336 private String replaceExistingSchemaElement( 1337 Set<String> modifiedSchemaFiles, SomeSchemaElement newElem, SomeSchemaElement existingElem) 1338 { 1339 String newSchemaFile = newElem.getSchemaFile(); 1340 String oldSchemaFile = existingElem.getSchemaFile(); 1341 if (newSchemaFile == null) 1342 { 1343 if (oldSchemaFile == null) 1344 { 1345 oldSchemaFile = FILE_USER_SCHEMA_ELEMENTS; 1346 } 1347 modifiedSchemaFiles.add(oldSchemaFile); 1348 return oldSchemaFile; 1349 } 1350 else if (oldSchemaFile == null || oldSchemaFile.equals(newSchemaFile)) 1351 { 1352 modifiedSchemaFiles.add(newSchemaFile); 1353 } 1354 else 1355 { 1356 modifiedSchemaFiles.add(newSchemaFile); 1357 modifiedSchemaFiles.add(oldSchemaFile); 1358 } 1359 return null; 1360 } 1361 1362 /** 1363 * Handles all processing required to remove the provided attribute type from 1364 * the server schema, ensuring all other metadata is properly updated. Note 1365 * that this method will first check to see whether the same attribute type 1366 * will be later added to the server schema with an updated definition, and if 1367 * so then the removal will be ignored because the later add will be handled 1368 * as a replace. If the attribute type will not be replaced with a new 1369 * definition, then this method will ensure that there are no other schema 1370 * elements that depend on the attribute type before allowing it to be 1371 * removed. 1372 * 1373 * @param attributeType The attribute type to remove from the server 1374 * schema. 1375 * @param schema The schema from which the attribute type 1376 * should be removed. 1377 * @param modifications The full set of modifications to be processed 1378 * against the server schema. 1379 * @param currentPosition The position of the modification currently 1380 * being performed. 1381 * @param modifiedSchemaFiles The names of the schema files containing 1382 * schema elements that have been updated as part 1383 * of the schema modification. 1384 * 1385 * @throws DirectoryException If a problem occurs while attempting to remove 1386 * the provided attribute type from the server 1387 * schema. 1388 */ 1389 private void removeAttributeType(AttributeType attributeType, Schema schema, Set<String> modifiedSchemaFiles) 1390 throws DirectoryException 1391 { 1392 // See if the specified attribute type is actually defined in the server 1393 // schema. If not, then fail. 1394 AttributeType removeType = schema.getAttributeType(attributeType.getOID()); 1395 if (removeType.isPlaceHolder() || !removeType.equals(attributeType)) 1396 { 1397 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_ATTRIBUTE_TYPE.get( 1398 attributeType.getNameOrOID()); 1399 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1400 } 1401 1402 // Make sure that the attribute type isn't used as the superior type for 1403 // any other attributes. 1404 for (AttributeType at : schema.getAttributeTypes()) 1405 { 1406 AttributeType superiorType = at.getSuperiorType(); 1407 if (superiorType != null && superiorType.equals(removeType)) 1408 { 1409 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_AT_SUPERIOR_TYPE.get( 1410 removeType.getNameOrOID(), superiorType.getNameOrOID()); 1411 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1412 } 1413 } 1414 1415 // Make sure that the attribute type isn't used as a required or optional 1416 // attribute type in any objectclass. 1417 for (ObjectClass oc : schema.getObjectClasses().values()) 1418 { 1419 if (oc.getRequiredAttributes().contains(removeType) || 1420 oc.getOptionalAttributes().contains(removeType)) 1421 { 1422 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_AT_IN_OC.get( 1423 removeType.getNameOrOID(), oc.getNameOrOID()); 1424 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1425 } 1426 } 1427 1428 // Make sure that the attribute type isn't used as a required or optional 1429 // attribute type in any name form. 1430 for (List<NameForm> mappedForms : 1431 schema.getNameFormsByObjectClass().values()) 1432 { 1433 for(NameForm nf : mappedForms) 1434 { 1435 if (nf.getRequiredAttributes().contains(removeType) || 1436 nf.getOptionalAttributes().contains(removeType)) 1437 { 1438 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_AT_IN_NF.get( 1439 removeType.getNameOrOID(), nf.getNameOrOID()); 1440 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1441 message); 1442 } 1443 } 1444 } 1445 1446 // Make sure that the attribute type isn't used as a required, optional, or 1447 // prohibited attribute type in any DIT content rule. 1448 for (DITContentRule dcr : schema.getDITContentRules().values()) 1449 { 1450 if (dcr.getRequiredAttributes().contains(removeType) || 1451 dcr.getOptionalAttributes().contains(removeType) || 1452 dcr.getProhibitedAttributes().contains(removeType)) 1453 { 1454 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_AT_IN_DCR.get( 1455 removeType.getNameOrOID(), dcr.getNameOrOID()); 1456 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1457 } 1458 } 1459 1460 // Make sure that the attribute type isn't referenced by any matching rule 1461 // use. 1462 for (MatchingRuleUse mru : schema.getMatchingRuleUses().values()) 1463 { 1464 if (mru.getAttributes().contains(removeType)) 1465 { 1466 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_AT_IN_MR_USE.get( 1467 removeType.getNameOrOID(), mru.getNameOrOID()); 1468 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1469 } 1470 } 1471 1472 // If we've gotten here, then it's OK to remove the attribute type from 1473 // the schema. 1474 schema.deregisterAttributeType(removeType); 1475 String schemaFile = new SomeSchemaElement(removeType).getSchemaFile(); 1476 if (schemaFile != null) 1477 { 1478 modifiedSchemaFiles.add(schemaFile); 1479 } 1480 } 1481 1482 1483 1484 /** 1485 * Handles all processing required for adding the provided objectclass to the 1486 * given schema, replacing an existing class if necessary, and ensuring 1487 * all other metadata is properly updated. 1488 * 1489 * @param objectClass The objectclass to add or replace in the 1490 * server schema. 1491 * @param schema The schema to which the objectclass should be 1492 * added. 1493 * @param modifiedSchemaFiles The names of the schema files containing 1494 * schema elements that have been updated as part 1495 * of the schema modification. 1496 * 1497 * @throws DirectoryException If a problem occurs while attempting to add 1498 * the provided objectclass to the server schema. 1499 */ 1500 private void addObjectClass(ObjectClass objectClass, Schema schema, 1501 Set<String> modifiedSchemaFiles) 1502 throws DirectoryException 1503 { 1504 // First, see if the specified objectclass already exists. We'll check the 1505 // OID and all of the names, which means that it's possible there could be 1506 // more than one match (although if there is, then we'll refuse the 1507 // operation). 1508 ObjectClass existingClass = 1509 schema.getObjectClass(objectClass.getOID()); 1510 for (String name : objectClass.getNormalizedNames()) 1511 { 1512 ObjectClass oc = schema.getObjectClass(name); 1513 if (oc == null) 1514 { 1515 continue; 1516 } 1517 else if (existingClass == null) 1518 { 1519 existingClass = oc; 1520 } 1521 else if (existingClass != oc) 1522 { 1523 // NOTE: We really do want to use "!=" instead of "! t.equals()" 1524 // because we want to check whether it's the same object instance, not 1525 // just a logical equivalent. 1526 LocalizableMessage message = 1527 ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_OBJECTCLASS 1528 .get(objectClass.getNameOrOID(), 1529 existingClass.getNameOrOID(), 1530 oc.getNameOrOID()); 1531 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1532 } 1533 } 1534 1535 1536 // Make sure that the new objectclass doesn't reference an undefined 1537 // superior class, or an undefined required or optional attribute type, 1538 // and that none of them are OBSOLETE. 1539 for(ObjectClass superiorClass : objectClass.getSuperiorClasses()) 1540 { 1541 if (! schema.hasObjectClass(superiorClass.getOID())) 1542 { 1543 LocalizableMessage message = ERR_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_OBJECTCLASS.get( 1544 objectClass.getNameOrOID(), superiorClass.getNameOrOID()); 1545 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1546 } 1547 else if (superiorClass.isObsolete()) 1548 { 1549 LocalizableMessage message = ERR_SCHEMA_MODIFY_OBSOLETE_SUPERIOR_OBJECTCLASS.get( 1550 objectClass.getNameOrOID(), superiorClass.getNameOrOID()); 1551 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1552 } 1553 } 1554 1555 for (AttributeType at : objectClass.getRequiredAttributes()) 1556 { 1557 if (! schema.hasAttributeType(at.getOID())) 1558 { 1559 LocalizableMessage message = ERR_SCHEMA_MODIFY_OC_UNDEFINED_REQUIRED_ATTR.get( 1560 objectClass.getNameOrOID(), at.getNameOrOID()); 1561 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1562 } 1563 else if (at.isObsolete()) 1564 { 1565 LocalizableMessage message = ERR_SCHEMA_MODIFY_OC_OBSOLETE_REQUIRED_ATTR.get( 1566 objectClass.getNameOrOID(), at.getNameOrOID()); 1567 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1568 } 1569 } 1570 1571 for (AttributeType at : objectClass.getOptionalAttributes()) 1572 { 1573 if (! schema.hasAttributeType(at.getOID())) 1574 { 1575 LocalizableMessage message = ERR_SCHEMA_MODIFY_OC_UNDEFINED_OPTIONAL_ATTR.get( 1576 objectClass.getNameOrOID(), at.getNameOrOID()); 1577 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1578 } 1579 else if (at.isObsolete()) 1580 { 1581 LocalizableMessage message = ERR_SCHEMA_MODIFY_OC_OBSOLETE_OPTIONAL_ATTR.get( 1582 objectClass.getNameOrOID(), at.getNameOrOID()); 1583 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1584 } 1585 } 1586 1587 1588 // If there is no existing class, then we're adding a new objectclass. 1589 // Otherwise, we're replacing an existing one. 1590 if (existingClass == null) 1591 { 1592 schema.registerObjectClass(objectClass, false); 1593 addNewSchemaElement(modifiedSchemaFiles, objectClass); 1594 } 1595 else 1596 { 1597 schema.deregisterObjectClass(existingClass); 1598 schema.registerObjectClass(objectClass, false); 1599 schema.rebuildDependentElements(existingClass); 1600 replaceExistingSchemaElement(modifiedSchemaFiles, objectClass, existingClass); 1601 } 1602 } 1603 1604 1605 1606 /** 1607 * Handles all processing required to remove the provided objectclass from the 1608 * server schema, ensuring all other metadata is properly updated. Note that 1609 * this method will first check to see whether the same objectclass will be 1610 * later added to the server schema with an updated definition, and if so then 1611 * the removal will be ignored because the later add will be handled as a 1612 * replace. If the objectclass will not be replaced with a new definition, 1613 * then this method will ensure that there are no other schema elements that 1614 * depend on the objectclass before allowing it to be removed. 1615 * 1616 * @param objectClass The objectclass to remove from the server 1617 * schema. 1618 * @param schema The schema from which the objectclass should 1619 * be removed. 1620 * @param modifications The full set of modifications to be processed 1621 * against the server schema. 1622 * @param currentPosition The position of the modification currently 1623 * being performed. 1624 * @param modifiedSchemaFiles The names of the schema files containing 1625 * schema elements that have been updated as part 1626 * of the schema modification. 1627 * 1628 * @throws DirectoryException If a problem occurs while attempting to remove 1629 * the provided objectclass from the server 1630 * schema. 1631 */ 1632 private void removeObjectClass(ObjectClass objectClass, Schema schema, 1633 ArrayList<Modification> modifications, 1634 int currentPosition, 1635 Set<String> modifiedSchemaFiles) 1636 throws DirectoryException 1637 { 1638 // See if the specified objectclass is actually defined in the server 1639 // schema. If not, then fail. 1640 ObjectClass removeClass = schema.getObjectClass(objectClass.getOID()); 1641 if (removeClass == null || !removeClass.equals(objectClass)) 1642 { 1643 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_OBJECTCLASS.get( 1644 objectClass.getNameOrOID()); 1645 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1646 } 1647 1648 1649 // See if there is another modification later to add the objectclass back 1650 // into the schema. If so, then it's a replace and we should ignore the 1651 // remove because adding it back will handle the replace. 1652 for (int i=currentPosition+1; i < modifications.size(); i++) 1653 { 1654 Modification m = modifications.get(i); 1655 Attribute a = m.getAttribute(); 1656 1657 if (m.getModificationType() != ModificationType.ADD || 1658 !a.getAttributeDescription().getAttributeType().equals(objectClassesType)) 1659 { 1660 continue; 1661 } 1662 1663 for (ByteString v : a) 1664 { 1665 ObjectClass oc; 1666 try 1667 { 1668 oc = ObjectClassSyntax.decodeObjectClass(v, schema, true); 1669 } 1670 catch (DirectoryException de) 1671 { 1672 logger.traceException(de); 1673 1674 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS.get( 1675 v, de.getMessageObject()); 1676 throw new DirectoryException( 1677 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 1678 } 1679 1680 if (objectClass.getOID().equals(oc.getOID())) 1681 { 1682 // We found a match where the objectClass is added back later, so we 1683 // don't need to do anything else here. 1684 return; 1685 } 1686 } 1687 } 1688 1689 1690 // Make sure that the objectclass isn't used as the superior class for any 1691 // other objectclass. 1692 for (ObjectClass oc : schema.getObjectClasses().values()) 1693 { 1694 for(ObjectClass superiorClass : oc.getSuperiorClasses()) 1695 { 1696 if (superiorClass.equals(removeClass)) 1697 { 1698 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_OC_SUPERIOR_CLASS.get( 1699 removeClass.getNameOrOID(), superiorClass.getNameOrOID()); 1700 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1701 message); 1702 } 1703 } 1704 } 1705 1706 1707 // Make sure that the objectclass isn't used as the structural class for 1708 // any name form. 1709 List<NameForm> mappedForms = schema.getNameForm(removeClass); 1710 if (mappedForms != null) 1711 { 1712 StringBuilder buffer = new StringBuilder(); 1713 for(NameForm nf : mappedForms) 1714 { 1715 buffer.append(nf.getNameOrOID()); 1716 buffer.append("\t"); 1717 } 1718 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_OC_IN_NF.get( 1719 removeClass.getNameOrOID(), buffer); 1720 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1721 } 1722 1723 1724 // Make sure that the objectclass isn't used as a structural or auxiliary 1725 // class for any DIT content rule. 1726 for (DITContentRule dcr : schema.getDITContentRules().values()) 1727 { 1728 if (dcr.getStructuralClass().equals(removeClass) || 1729 dcr.getAuxiliaryClasses().contains(removeClass)) 1730 { 1731 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_OC_IN_DCR.get( 1732 removeClass.getNameOrOID(), dcr.getNameOrOID()); 1733 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1734 } 1735 } 1736 1737 1738 // If we've gotten here, then it's OK to remove the objectclass from the 1739 // schema. 1740 schema.deregisterObjectClass(removeClass); 1741 String schemaFile = getSchemaFile(removeClass); 1742 if (schemaFile != null) 1743 { 1744 modifiedSchemaFiles.add(schemaFile); 1745 } 1746 } 1747 1748 1749 1750 /** 1751 * Handles all processing required for adding the provided name form to the 1752 * the given schema, replacing an existing name form if necessary, and 1753 * ensuring all other metadata is properly updated. 1754 * 1755 * @param nameForm The name form to add or replace in the server 1756 * schema. 1757 * @param schema The schema to which the name form should be 1758 * added. 1759 * @param modifiedSchemaFiles The names of the schema files containing 1760 * schema elements that have been updated as part 1761 * of the schema modification. 1762 * 1763 * @throws DirectoryException If a problem occurs while attempting to add 1764 * the provided name form to the server schema. 1765 */ 1766 private void addNameForm(NameForm nameForm, Schema schema, 1767 Set<String> modifiedSchemaFiles) 1768 throws DirectoryException 1769 { 1770 // First, see if the specified name form already exists. We'll check the 1771 // OID and all of the names, which means that it's possible there could be 1772 // more than one match (although if there is, then we'll refuse the 1773 // operation). 1774 NameForm existingNF = 1775 schema.getNameForm(nameForm.getOID()); 1776 for (String name : nameForm.getNames().keySet()) 1777 { 1778 NameForm nf = schema.getNameForm(name); 1779 if (nf == null) 1780 { 1781 continue; 1782 } 1783 else if (existingNF == null) 1784 { 1785 existingNF = nf; 1786 } 1787 else if (existingNF != nf) 1788 { 1789 // NOTE: We really do want to use "!=" instead of "! t.equals()" 1790 // because we want to check whether it's the same object instance, not 1791 // just a logical equivalent. 1792 LocalizableMessage message = 1793 ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_NAME_FORM 1794 .get(nameForm.getNameOrOID(), existingNF.getNameOrOID(), 1795 nf.getNameOrOID()); 1796 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1797 } 1798 } 1799 1800 1801 // Make sure that the new name form doesn't reference an undefined 1802 // structural class, or an undefined required or optional attribute type, or 1803 // that any of them are marked OBSOLETE. 1804 ObjectClass structuralClass = nameForm.getStructuralClass(); 1805 if (! schema.hasObjectClass(structuralClass.getOID())) 1806 { 1807 LocalizableMessage message = ERR_SCHEMA_MODIFY_NF_UNDEFINED_STRUCTURAL_OC.get( 1808 nameForm.getNameOrOID(), structuralClass.getNameOrOID()); 1809 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1810 } 1811 if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL) 1812 { 1813 LocalizableMessage message = ERR_SCHEMA_MODIFY_NF_OC_NOT_STRUCTURAL.get( 1814 nameForm.getNameOrOID(), structuralClass.getNameOrOID()); 1815 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1816 } 1817 if (structuralClass.isObsolete()) 1818 { 1819 LocalizableMessage message = ERR_SCHEMA_MODIFY_NF_OC_OBSOLETE.get( 1820 nameForm.getNameOrOID(), structuralClass.getNameOrOID()); 1821 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1822 } 1823 1824 for (AttributeType at : nameForm.getRequiredAttributes()) 1825 { 1826 if (! schema.hasAttributeType(at.getOID())) 1827 { 1828 LocalizableMessage message = ERR_SCHEMA_MODIFY_NF_UNDEFINED_REQUIRED_ATTR.get( 1829 nameForm.getNameOrOID(), at.getNameOrOID()); 1830 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1831 } 1832 else if (at.isObsolete()) 1833 { 1834 LocalizableMessage message = ERR_SCHEMA_MODIFY_NF_OBSOLETE_REQUIRED_ATTR.get( 1835 nameForm.getNameOrOID(), at.getNameOrOID()); 1836 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1837 } 1838 } 1839 1840 for (AttributeType at : nameForm.getOptionalAttributes()) 1841 { 1842 if (! schema.hasAttributeType(at.getOID())) 1843 { 1844 LocalizableMessage message = ERR_SCHEMA_MODIFY_NF_UNDEFINED_OPTIONAL_ATTR.get( 1845 nameForm.getNameOrOID(), at.getNameOrOID()); 1846 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1847 } 1848 else if (at.isObsolete()) 1849 { 1850 LocalizableMessage message = ERR_SCHEMA_MODIFY_NF_OBSOLETE_OPTIONAL_ATTR.get( 1851 nameForm.getNameOrOID(), at.getNameOrOID()); 1852 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 1853 } 1854 } 1855 1856 1857 // If there is no existing class, then we're adding a new name form. 1858 // Otherwise, we're replacing an existing one. 1859 if (existingNF == null) 1860 { 1861 schema.registerNameForm(nameForm, false); 1862 addNewSchemaElement(modifiedSchemaFiles, nameForm); 1863 } 1864 else 1865 { 1866 schema.deregisterNameForm(existingNF); 1867 schema.registerNameForm(nameForm, false); 1868 schema.rebuildDependentElements(existingNF); 1869 replaceExistingSchemaElement(modifiedSchemaFiles, nameForm, existingNF); 1870 } 1871 } 1872 1873 1874 1875 /** 1876 * Handles all processing required to remove the provided name form from the 1877 * server schema, ensuring all other metadata is properly updated. Note that 1878 * this method will first check to see whether the same name form will be 1879 * later added to the server schema with an updated definition, and if so then 1880 * the removal will be ignored because the later add will be handled as a 1881 * replace. If the name form will not be replaced with a new definition, then 1882 * this method will ensure that there are no other schema elements that depend 1883 * on the name form before allowing it to be removed. 1884 * 1885 * @param nameForm The name form to remove from the server 1886 * schema. 1887 * @param schema The schema from which the name form should be 1888 * be removed. 1889 * @param modifications The full set of modifications to be processed 1890 * against the server schema. 1891 * @param currentPosition The position of the modification currently 1892 * being performed. 1893 * @param modifiedSchemaFiles The names of the schema files containing 1894 * schema elements that have been updated as part 1895 * of the schema modification. 1896 * 1897 * @throws DirectoryException If a problem occurs while attempting to remove 1898 * the provided name form from the server schema. 1899 */ 1900 private void removeNameForm(NameForm nameForm, Schema schema, 1901 ArrayList<Modification> modifications, 1902 int currentPosition, 1903 Set<String> modifiedSchemaFiles) 1904 throws DirectoryException 1905 { 1906 // See if the specified name form is actually defined in the server schema. 1907 // If not, then fail. 1908 NameForm removeNF = schema.getNameForm(nameForm.getOID()); 1909 if (removeNF == null || !removeNF.equals(nameForm)) 1910 { 1911 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_NAME_FORM.get( 1912 nameForm.getNameOrOID()); 1913 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1914 } 1915 1916 1917 // See if there is another modification later to add the name form back 1918 // into the schema. If so, then it's a replace and we should ignore the 1919 // remove because adding it back will handle the replace. 1920 for (int i=currentPosition+1; i < modifications.size(); i++) 1921 { 1922 Modification m = modifications.get(i); 1923 Attribute a = m.getAttribute(); 1924 1925 if (m.getModificationType() != ModificationType.ADD || 1926 !a.getAttributeDescription().getAttributeType().equals(nameFormsType)) 1927 { 1928 continue; 1929 } 1930 1931 for (ByteString v : a) 1932 { 1933 NameForm nf; 1934 try 1935 { 1936 nf = NameFormSyntax.decodeNameForm(v, schema, true); 1937 } 1938 catch (DirectoryException de) 1939 { 1940 logger.traceException(de); 1941 1942 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM.get( 1943 v, de.getMessageObject()); 1944 throw new DirectoryException( 1945 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 1946 } 1947 1948 if (nameForm.getOID().equals(nf.getOID())) 1949 { 1950 // We found a match where the name form is added back later, so we 1951 // don't need to do anything else here. 1952 return; 1953 } 1954 } 1955 } 1956 1957 1958 // Make sure that the name form isn't referenced by any DIT structure 1959 // rule. 1960 DITStructureRule dsr = schema.getDITStructureRule(removeNF); 1961 if (dsr != null) 1962 { 1963 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_NF_IN_DSR.get( 1964 removeNF.getNameOrOID(), dsr.getNameOrRuleID()); 1965 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1966 } 1967 1968 1969 // If we've gotten here, then it's OK to remove the name form from the 1970 // schema. 1971 schema.deregisterNameForm(removeNF); 1972 String schemaFile = getSchemaFile(removeNF); 1973 if (schemaFile != null) 1974 { 1975 modifiedSchemaFiles.add(schemaFile); 1976 } 1977 } 1978 1979 1980 1981 /** 1982 * Handles all processing required for adding the provided DIT content rule to 1983 * the given schema, replacing an existing rule if necessary, and ensuring 1984 * all other metadata is properly updated. 1985 * 1986 * @param ditContentRule The DIT content rule to add or replace in the 1987 * server schema. 1988 * @param schema The schema to which the DIT content rule 1989 * should be added. 1990 * @param modifiedSchemaFiles The names of the schema files containing 1991 * schema elements that have been updated as part 1992 * of the schema modification. 1993 * 1994 * @throws DirectoryException If a problem occurs while attempting to add 1995 * the provided DIT content rule to the server 1996 * schema. 1997 */ 1998 private void addDITContentRule(DITContentRule ditContentRule, Schema schema, 1999 Set<String> modifiedSchemaFiles) 2000 throws DirectoryException 2001 { 2002 // First, see if the specified DIT content rule already exists. We'll check 2003 // all of the names, which means that it's possible there could be more than 2004 // one match (although if there is, then we'll refuse the operation). 2005 DITContentRule existingDCR = null; 2006 for (DITContentRule dcr : schema.getDITContentRules().values()) 2007 { 2008 for (String name : ditContentRule.getNames().keySet()) 2009 { 2010 if (dcr.hasName(name)) 2011 { 2012 if (existingDCR == null) 2013 { 2014 existingDCR = dcr; 2015 break; 2016 } 2017 else 2018 { 2019 LocalizableMessage message = ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DCR. 2020 get(ditContentRule.getNameOrOID(), existingDCR.getNameOrOID(), 2021 dcr.getNameOrOID()); 2022 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2023 message); 2024 } 2025 } 2026 } 2027 } 2028 2029 2030 // Get the structural class for the new DIT content rule and see if there's 2031 // already an existing rule that is associated with that class. If there 2032 // is, then it will only be acceptable if it's the DIT content rule that we 2033 // are replacing (in which case we really do want to use the "!=" operator). 2034 ObjectClass structuralClass = ditContentRule.getStructuralClass(); 2035 DITContentRule existingRuleForClass = 2036 schema.getDITContentRule(structuralClass); 2037 if (existingRuleForClass != null && existingRuleForClass != existingDCR) 2038 { 2039 LocalizableMessage message = ERR_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_DCR. 2040 get(ditContentRule.getNameOrOID(), structuralClass.getNameOrOID(), 2041 existingRuleForClass.getNameOrOID()); 2042 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2043 } 2044 2045 2046 // Make sure that the new DIT content rule doesn't reference an undefined 2047 // structural or auxiliary class, or an undefined required, optional, or 2048 // prohibited attribute type. 2049 if (! schema.hasObjectClass(structuralClass.getOID())) 2050 { 2051 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_STRUCTURAL_OC.get( 2052 ditContentRule.getNameOrOID(), structuralClass.getNameOrOID()); 2053 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2054 } 2055 2056 if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL) 2057 { 2058 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_OC_NOT_STRUCTURAL.get( 2059 ditContentRule.getNameOrOID(), structuralClass.getNameOrOID()); 2060 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2061 } 2062 2063 if (structuralClass.isObsolete()) 2064 { 2065 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_STRUCTURAL_OC_OBSOLETE.get( 2066 ditContentRule.getNameOrOID(), structuralClass.getNameOrOID()); 2067 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2068 } 2069 2070 for (ObjectClass oc : ditContentRule.getAuxiliaryClasses()) 2071 { 2072 if (! schema.hasObjectClass(oc.getOID())) 2073 { 2074 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_AUXILIARY_OC.get( 2075 ditContentRule.getNameOrOID(), oc.getNameOrOID()); 2076 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2077 } 2078 if (oc.getObjectClassType() != ObjectClassType.AUXILIARY) 2079 { 2080 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_OC_NOT_AUXILIARY.get( 2081 ditContentRule.getNameOrOID(), oc.getNameOrOID()); 2082 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2083 } 2084 if (oc.isObsolete()) 2085 { 2086 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_OBSOLETE_AUXILIARY_OC.get( 2087 ditContentRule.getNameOrOID(), oc.getNameOrOID()); 2088 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2089 } 2090 } 2091 2092 for (AttributeType at : ditContentRule.getRequiredAttributes()) 2093 { 2094 if (! schema.hasAttributeType(at.getOID())) 2095 { 2096 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_REQUIRED_ATTR.get( 2097 ditContentRule.getNameOrOID(), at.getNameOrOID()); 2098 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2099 } 2100 else if (at.isObsolete()) 2101 { 2102 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_OBSOLETE_REQUIRED_ATTR.get( 2103 ditContentRule.getNameOrOID(), at.getNameOrOID()); 2104 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2105 } 2106 } 2107 2108 for (AttributeType at : ditContentRule.getOptionalAttributes()) 2109 { 2110 if (! schema.hasAttributeType(at.getOID())) 2111 { 2112 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_OPTIONAL_ATTR.get( 2113 ditContentRule.getNameOrOID(), at.getNameOrOID()); 2114 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2115 } 2116 else if (at.isObsolete()) 2117 { 2118 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_OBSOLETE_OPTIONAL_ATTR.get( 2119 ditContentRule.getNameOrOID(), at.getNameOrOID()); 2120 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2121 } 2122 } 2123 2124 for (AttributeType at : ditContentRule.getProhibitedAttributes()) 2125 { 2126 if (! schema.hasAttributeType(at.getOID())) 2127 { 2128 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_PROHIBITED_ATTR.get( 2129 ditContentRule.getNameOrOID(), at.getNameOrOID()); 2130 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2131 } 2132 else if (at.isObsolete()) 2133 { 2134 LocalizableMessage message = ERR_SCHEMA_MODIFY_DCR_OBSOLETE_PROHIBITED_ATTR.get( 2135 ditContentRule.getNameOrOID(), at.getNameOrOID()); 2136 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2137 } 2138 } 2139 2140 2141 // If there is no existing rule, then we're adding a new DIT content rule. 2142 // Otherwise, we're replacing an existing one. 2143 if (existingDCR == null) 2144 { 2145 schema.registerDITContentRule(ditContentRule, false); 2146 addNewSchemaElement(modifiedSchemaFiles, ditContentRule); 2147 } 2148 else 2149 { 2150 schema.deregisterDITContentRule(existingDCR); 2151 schema.registerDITContentRule(ditContentRule, false); 2152 schema.rebuildDependentElements(existingDCR); 2153 replaceExistingSchemaElement(modifiedSchemaFiles, ditContentRule, 2154 existingDCR); 2155 } 2156 } 2157 2158 2159 2160 /** 2161 * Handles all processing required to remove the provided DIT content rule 2162 * from the server schema, ensuring all other metadata is properly updated. 2163 * Note that this method will first check to see whether the same rule will be 2164 * later added to the server schema with an updated definition, and if so then 2165 * the removal will be ignored because the later add will be handled as a 2166 * replace. If the DIT content rule will not be replaced with a new 2167 * definition, then this method will ensure that there are no other schema 2168 * elements that depend on the rule before allowing it to be removed. 2169 * 2170 * @param ditContentRule The DIT content rule to remove from the server 2171 * schema. 2172 * @param schema The schema from which the DIT content rule 2173 * should be removed. 2174 * @param modifiedSchemaFiles The names of the schema files containing 2175 * schema elements that have been updated as part 2176 * of the schema modification. 2177 * 2178 * @throws DirectoryException If a problem occurs while attempting to remove 2179 * the provided DIT content rule from the server 2180 * schema. 2181 */ 2182 private void removeDITContentRule(DITContentRule ditContentRule, 2183 Schema schema, Set<String> modifiedSchemaFiles) throws DirectoryException 2184 { 2185 // See if the specified DIT content rule is actually defined in the server 2186 // schema. If not, then fail. 2187 DITContentRule removeDCR = 2188 schema.getDITContentRule(ditContentRule.getStructuralClass()); 2189 if (removeDCR == null || !removeDCR.equals(ditContentRule)) 2190 { 2191 LocalizableMessage message = 2192 ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_DCR.get(ditContentRule.getNameOrOID()); 2193 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2194 } 2195 2196 2197 // Since DIT content rules don't have any dependencies, then we don't need 2198 // to worry about the difference between a remove or a replace. We can 2199 // just remove the DIT content rule now, and if it is added back later then 2200 // there still won't be any conflict. 2201 schema.deregisterDITContentRule(removeDCR); 2202 String schemaFile = getSchemaFile(removeDCR); 2203 if (schemaFile != null) 2204 { 2205 modifiedSchemaFiles.add(schemaFile); 2206 } 2207 } 2208 2209 2210 2211 /** 2212 * Handles all processing required for adding the provided DIT structure rule 2213 * to the given schema, replacing an existing rule if necessary, and ensuring 2214 * all other metadata is properly updated. 2215 * 2216 * @param ditStructureRule The DIT structure rule to add or replace in 2217 * the server schema. 2218 * @param schema The schema to which the DIT structure rule 2219 * should be added. 2220 * @param modifiedSchemaFiles The names of the schema files containing 2221 * schema elements that have been updated as part 2222 * of the schema modification. 2223 * 2224 * @throws DirectoryException If a problem occurs while attempting to add 2225 * the provided DIT structure rule to the server 2226 * schema. 2227 */ 2228 private void addDITStructureRule(DITStructureRule ditStructureRule, 2229 Schema schema, 2230 Set<String> modifiedSchemaFiles) 2231 throws DirectoryException 2232 { 2233 // First, see if the specified DIT structure rule already exists. We'll 2234 // check the rule ID and all of the names, which means that it's possible 2235 // there could be more than one match (although if there is, then we'll 2236 // refuse the operation). 2237 DITStructureRule existingDSR = 2238 schema.getDITStructureRule(ditStructureRule.getRuleID()); 2239 //Boolean to check if the new rule is in use or not. 2240 boolean inUse = false; 2241 for (DITStructureRule dsr : schema.getDITStructureRulesByID().values()) 2242 { 2243 for (String name : ditStructureRule.getNames().keySet()) 2244 { 2245 if (dsr.hasName(name)) 2246 { 2247 // We really do want to use the "!=" operator here because it's 2248 // acceptable if we find match for the same object instance. 2249 if (existingDSR != null && existingDSR != dsr) 2250 { 2251 LocalizableMessage message = ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DSR. 2252 get(ditStructureRule.getNameOrRuleID(), 2253 existingDSR.getNameOrRuleID(), dsr.getNameOrRuleID()); 2254 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2255 message); 2256 } 2257 inUse = true; 2258 } 2259 } 2260 } 2261 2262 if(existingDSR != null && !inUse) 2263 { 2264 //We have an existing DSR with the same rule id but we couldn't find 2265 //any existing rules sharing this name. It means that it is a 2266 //new rule with a conflicting rule id.Raise an Exception as the 2267 //rule id should be unique. 2268 LocalizableMessage message = ERR_SCHEMA_MODIFY_RULEID_CONFLICTS_FOR_ADD_DSR. 2269 get(ditStructureRule.getNameOrRuleID(), 2270 existingDSR.getNameOrRuleID()); 2271 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2272 message); 2273 } 2274 2275 // Get the name form for the new DIT structure rule and see if there's 2276 // already an existing rule that is associated with that name form. If 2277 // there is, then it will only be acceptable if it's the DIT structure rule 2278 // that we are replacing (in which case we really do want to use the "!=" 2279 // operator). 2280 NameForm nameForm = ditStructureRule.getNameForm(); 2281 DITStructureRule existingRuleForNameForm = 2282 schema.getDITStructureRule(nameForm); 2283 if (existingRuleForNameForm != null && 2284 existingRuleForNameForm != existingDSR) 2285 { 2286 LocalizableMessage message = ERR_SCHEMA_MODIFY_NAME_FORM_CONFLICT_FOR_ADD_DSR. 2287 get(ditStructureRule.getNameOrRuleID(), nameForm.getNameOrOID(), 2288 existingRuleForNameForm.getNameOrRuleID()); 2289 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2290 } 2291 2292 2293 // Make sure that the new DIT structure rule doesn't reference an undefined 2294 // name form or superior DIT structure rule. 2295 if (! schema.hasNameForm(nameForm.getOID())) 2296 { 2297 LocalizableMessage message = ERR_SCHEMA_MODIFY_DSR_UNDEFINED_NAME_FORM.get( 2298 ditStructureRule.getNameOrRuleID(), nameForm.getNameOrOID()); 2299 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2300 } 2301 if (nameForm.isObsolete()) 2302 { 2303 LocalizableMessage message = ERR_SCHEMA_MODIFY_DSR_OBSOLETE_NAME_FORM.get( 2304 ditStructureRule.getNameOrRuleID(), nameForm.getNameOrOID()); 2305 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2306 } 2307 2308 2309 // If there are any superior rules, then make sure none of them are marked 2310 // OBSOLETE. 2311 for (DITStructureRule dsr : ditStructureRule.getSuperiorRules()) 2312 { 2313 if (dsr.isObsolete()) 2314 { 2315 LocalizableMessage message = ERR_SCHEMA_MODIFY_DSR_OBSOLETE_SUPERIOR_RULE.get( 2316 ditStructureRule.getNameOrRuleID(), dsr.getNameOrRuleID()); 2317 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2318 } 2319 } 2320 2321 2322 // If there is no existing rule, then we're adding a new DIT structure rule. 2323 // Otherwise, we're replacing an existing one. 2324 if (existingDSR == null) 2325 { 2326 schema.registerDITStructureRule(ditStructureRule, false); 2327 addNewSchemaElement(modifiedSchemaFiles, ditStructureRule); 2328 } 2329 else 2330 { 2331 schema.deregisterDITStructureRule(existingDSR); 2332 schema.registerDITStructureRule(ditStructureRule, false); 2333 schema.rebuildDependentElements(existingDSR); 2334 replaceExistingSchemaElement(modifiedSchemaFiles, ditStructureRule, 2335 existingDSR); 2336 } 2337 } 2338 2339 2340 2341 /** 2342 * Handles all processing required to remove the provided DIT structure rule 2343 * from the server schema, ensuring all other metadata is properly updated. 2344 * Note that this method will first check to see whether the same rule will be 2345 * later added to the server schema with an updated definition, and if so then 2346 * the removal will be ignored because the later add will be handled as a 2347 * replace. If the DIT structure rule will not be replaced with a new 2348 * definition, then this method will ensure that there are no other schema 2349 * elements that depend on the rule before allowing it to be removed. 2350 * 2351 * @param ditStructureRule The DIT structure rule to remove from the 2352 * server schema. 2353 * @param schema The schema from which the DIT structure rule 2354 * should be removed. 2355 * @param modifications The full set of modifications to be processed 2356 * against the server schema. 2357 * @param currentPosition The position of the modification currently 2358 * being performed. 2359 * @param modifiedSchemaFiles The names of the schema files containing 2360 * schema elements that have been updated as part 2361 * of the schema modification. 2362 * 2363 * @throws DirectoryException If a problem occurs while attempting to remove 2364 * the provided DIT structure rule from the 2365 * server schema. 2366 */ 2367 private void removeDITStructureRule(DITStructureRule ditStructureRule, 2368 Schema schema, 2369 ArrayList<Modification> modifications, 2370 int currentPosition, 2371 Set<String> modifiedSchemaFiles) 2372 throws DirectoryException 2373 { 2374 // See if the specified DIT structure rule is actually defined in the server 2375 // schema. If not, then fail. 2376 DITStructureRule removeDSR = 2377 schema.getDITStructureRule(ditStructureRule.getRuleID()); 2378 if (removeDSR == null || !removeDSR.equals(ditStructureRule)) 2379 { 2380 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_DSR.get( 2381 ditStructureRule.getNameOrRuleID()); 2382 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2383 } 2384 2385 2386 // See if there is another modification later to add the DIT structure rule 2387 // back into the schema. If so, then it's a replace and we should ignore 2388 // the remove because adding it back will handle the replace. 2389 for (int i=currentPosition+1; i < modifications.size(); i++) 2390 { 2391 Modification m = modifications.get(i); 2392 Attribute a = m.getAttribute(); 2393 2394 if (m.getModificationType() != ModificationType.ADD || 2395 !a.getAttributeDescription().getAttributeType().equals(ditStructureRulesType)) 2396 { 2397 continue; 2398 } 2399 2400 for (ByteString v : a) 2401 { 2402 DITStructureRule dsr; 2403 try 2404 { 2405 dsr = DITStructureRuleSyntax.decodeDITStructureRule(v, schema, true); 2406 } 2407 catch (DirectoryException de) 2408 { 2409 logger.traceException(de); 2410 2411 LocalizableMessage message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DSR.get( 2412 v, de.getMessageObject()); 2413 throw new DirectoryException( 2414 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, de); 2415 } 2416 2417 if (ditStructureRule.getRuleID() == dsr.getRuleID()) 2418 { 2419 // We found a match where the DIT structure rule is added back later, 2420 // so we don't need to do anything else here. 2421 return; 2422 } 2423 } 2424 } 2425 2426 2427 // Make sure that the DIT structure rule isn't the superior for any other 2428 // DIT structure rule. 2429 for (DITStructureRule dsr : schema.getDITStructureRulesByID().values()) 2430 { 2431 if (dsr.getSuperiorRules().contains(removeDSR)) 2432 { 2433 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_DSR_SUPERIOR_RULE.get( 2434 removeDSR.getNameOrRuleID(), dsr.getNameOrRuleID()); 2435 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2436 } 2437 } 2438 2439 2440 // If we've gotten here, then it's OK to remove the DIT structure rule from 2441 // the schema. 2442 schema.deregisterDITStructureRule(removeDSR); 2443 String schemaFile = getSchemaFile(removeDSR); 2444 if (schemaFile != null) 2445 { 2446 modifiedSchemaFiles.add(schemaFile); 2447 } 2448 } 2449 2450 2451 2452 /** 2453 * Handles all processing required for adding the provided matching rule use 2454 * to the given schema, replacing an existing use if necessary, and ensuring 2455 * all other metadata is properly updated. 2456 * 2457 * @param matchingRuleUse The matching rule use to add or replace in the 2458 * server schema. 2459 * @param schema The schema to which the matching rule use 2460 * should be added. 2461 * @param modifiedSchemaFiles The names of the schema files containing 2462 * schema elements that have been updated as part 2463 * of the schema modification. 2464 * 2465 * @throws DirectoryException If a problem occurs while attempting to add 2466 * the provided matching rule use to the server 2467 * schema. 2468 */ 2469 private void addMatchingRuleUse(MatchingRuleUse matchingRuleUse, 2470 Schema schema, 2471 Set<String> modifiedSchemaFiles) 2472 throws DirectoryException 2473 { 2474 // First, see if the specified matching rule use already exists. We'll 2475 // check all of the names, which means that it's possible that there could 2476 // be more than one match (although if there is, then we'll refuse the 2477 // operation). 2478 MatchingRuleUse existingMRU = null; 2479 for (MatchingRuleUse mru : schema.getMatchingRuleUses().values()) 2480 { 2481 for (String name : matchingRuleUse.getNames().keySet()) 2482 { 2483 if (mru.hasName(name)) 2484 { 2485 if (existingMRU == null) 2486 { 2487 existingMRU = mru; 2488 break; 2489 } 2490 else 2491 { 2492 LocalizableMessage message = 2493 ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_MR_USE.get( 2494 matchingRuleUse.getNameOrOID(), 2495 existingMRU.getNameOrOID(), 2496 mru.getNameOrOID()); 2497 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2498 message); 2499 } 2500 } 2501 } 2502 } 2503 2504 2505 // Get the matching rule for the new matching rule use and see if there's 2506 // already an existing matching rule use that is associated with that 2507 // matching rule. If there is, then it will only be acceptable if it's the 2508 // matching rule use that we are replacing (in which case we really do want 2509 // to use the "!=" operator). 2510 MatchingRule matchingRule = matchingRuleUse.getMatchingRule(); 2511 MatchingRuleUse existingMRUForRule = 2512 schema.getMatchingRuleUse(matchingRule); 2513 if (existingMRUForRule != null && existingMRUForRule != existingMRU) 2514 { 2515 LocalizableMessage message = ERR_SCHEMA_MODIFY_MR_CONFLICT_FOR_ADD_MR_USE. 2516 get(matchingRuleUse.getNameOrOID(), matchingRule.getNameOrOID(), 2517 existingMRUForRule.getNameOrOID()); 2518 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2519 } 2520 2521 if (matchingRule.isObsolete()) 2522 { 2523 LocalizableMessage message = ERR_SCHEMA_MODIFY_MRU_OBSOLETE_MR.get( 2524 matchingRuleUse.getNameOrOID(), matchingRule.getNameOrOID()); 2525 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2526 } 2527 2528 2529 // Make sure that the new matching rule use doesn't reference an undefined 2530 // attribute type. 2531 for (AttributeType at : matchingRuleUse.getAttributes()) 2532 { 2533 if (! schema.hasAttributeType(at.getOID())) 2534 { 2535 LocalizableMessage message = ERR_SCHEMA_MODIFY_MRU_UNDEFINED_ATTR.get( 2536 matchingRuleUse.getNameOrOID(), at.getNameOrOID()); 2537 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2538 } 2539 else if (at.isObsolete()) 2540 { 2541 LocalizableMessage message = ERR_SCHEMA_MODIFY_MRU_OBSOLETE_ATTR.get( 2542 matchingRuleUse.getNameOrOID(), matchingRule.getNameOrOID()); 2543 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2544 } 2545 } 2546 2547 2548 // If there is no existing matching rule use, then we're adding a new one. 2549 // Otherwise, we're replacing an existing matching rule use. 2550 if (existingMRU == null) 2551 { 2552 schema.registerMatchingRuleUse(matchingRuleUse, false); 2553 addNewSchemaElement(modifiedSchemaFiles, matchingRuleUse); 2554 } 2555 else 2556 { 2557 schema.deregisterMatchingRuleUse(existingMRU); 2558 schema.registerMatchingRuleUse(matchingRuleUse, false); 2559 schema.rebuildDependentElements(existingMRU); 2560 replaceExistingSchemaElement(modifiedSchemaFiles, matchingRuleUse, 2561 existingMRU); 2562 } 2563 } 2564 2565 2566 2567 /** 2568 * Handles all processing required to remove the provided matching rule use 2569 * from the server schema, ensuring all other metadata is properly updated. 2570 * Note that this method will first check to see whether the same matching 2571 * rule use will be later added to the server schema with an updated 2572 * definition, and if so then the removal will be ignored because the later 2573 * add will be handled as a replace. If the matching rule use will not be 2574 * replaced with a new definition, then this method will ensure that there are 2575 * no other schema elements that depend on the matching rule use before 2576 * allowing it to be removed. 2577 * 2578 * @param matchingRuleUse The matching rule use to remove from the 2579 * server schema. 2580 * @param schema The schema from which the matching rule use 2581 * should be removed. 2582 * @param modifiedSchemaFiles The names of the schema files containing 2583 * schema elements that have been updated as part 2584 * of the schema modification. 2585 * @throws DirectoryException If a problem occurs while attempting to remove 2586 * the provided matching rule use from the server 2587 * schema. 2588 */ 2589 private void removeMatchingRuleUse(MatchingRuleUse matchingRuleUse, 2590 Schema schema, 2591 Set<String> modifiedSchemaFiles) 2592 throws DirectoryException 2593 { 2594 // See if the specified DIT content rule is actually defined in the server 2595 // schema. If not, then fail. 2596 MatchingRuleUse removeMRU = 2597 schema.getMatchingRuleUse(matchingRuleUse.getMatchingRule()); 2598 if (removeMRU == null || !removeMRU.equals(matchingRuleUse)) 2599 { 2600 LocalizableMessage message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_MR_USE.get( 2601 matchingRuleUse.getNameOrOID()); 2602 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2603 } 2604 2605 2606 // Since matching rule uses don't have any dependencies, then we don't need 2607 // to worry about the difference between a remove or a replace. We can 2608 // just remove the DIT content rule now, and if it is added back later then 2609 // there still won't be any conflict. 2610 schema.deregisterMatchingRuleUse(removeMRU); 2611 String schemaFile = getSchemaFile(removeMRU); 2612 if (schemaFile != null) 2613 { 2614 modifiedSchemaFiles.add(schemaFile); 2615 } 2616 } 2617 2618 /** 2619 * Handles all processing required for adding the provided ldap syntax description to the given 2620 * schema, replacing an existing ldap syntax description if necessary, and ensuring all other 2621 * metadata is properly updated. 2622 * 2623 * @param definition 2624 * The definition of the ldap syntax description to add or replace in the server schema. 2625 * @param schema 2626 * The schema to which the LDAP syntax description should be added. 2627 * @param modifiedSchemaFiles 2628 * The names of the schema files containing schema elements that have been updated as 2629 * part of the schema modification. 2630 * @throws DirectoryException 2631 * If a problem occurs while attempting to add the provided ldap syntax description to 2632 * the server schema. 2633 */ 2634 private void addLdapSyntaxDescription(final String definition, Schema schema, Set<String> modifiedSchemaFiles) 2635 throws DirectoryException 2636 { 2637 // Check if there is an existing syntax with this oid. 2638 String oid = 2639 Schema.parseOID(definition, ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_ATTR_SYNTAX_LDAPSYNTAX_EMPTY_VALUE); 2640 2641 // We allow only unimplemented syntaxes to be substituted. 2642 if (schema.hasSyntax(oid)) 2643 { 2644 LocalizableMessage message = ERR_ATTR_SYNTAX_INVALID_LDAP_SYNTAX.get(definition, oid); 2645 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 2646 } 2647 2648 LDAPSyntaxDescription existingLSD = schema.getLdapSyntaxDescription(oid); 2649 // If there is no existing lsd, then we're adding a new ldapsyntax. 2650 // Otherwise, we're replacing an existing one. 2651 if (existingLSD == null) 2652 { 2653 String def = Schema.addSchemaFileToElementDefinitionIfAbsent(definition, FILE_USER_SCHEMA_ELEMENTS); 2654 schema.registerLdapSyntaxDescription(def, false); 2655 2656 String schemaFile = getSchemaFile(schema.getLdapSyntaxDescription(oid)); 2657 modifiedSchemaFiles.add(schemaFile); 2658 } 2659 else 2660 { 2661 schema.deregisterLdapSyntaxDescription(existingLSD); 2662 2663 String oldSchemaFile = getSchemaFile(existingLSD); 2664 String schemaFile = oldSchemaFile != null && oldSchemaFile.length() > 0 ? 2665 oldSchemaFile : FILE_USER_SCHEMA_ELEMENTS; 2666 String def = Schema.addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile); 2667 schema.registerLdapSyntaxDescription(def, false); 2668 2669 schema.rebuildDependentElements(existingLSD); 2670 String newSchemaFile = getSchemaFile(schema.getLdapSyntaxDescription(oid)); 2671 2672 if (oldSchemaFile != null) 2673 { 2674 modifiedSchemaFiles.add(oldSchemaFile); 2675 } 2676 if (newSchemaFile != null) 2677 { 2678 modifiedSchemaFiles.add(newSchemaFile); 2679 } 2680 } 2681 } 2682 2683 2684 2685 /** Gets rid of the ldap syntax description. */ 2686 private void removeLdapSyntaxDescription(String definition, Schema schema, Set<String> modifiedSchemaFiles) 2687 throws DirectoryException 2688 { 2689 /* 2690 * See if the specified ldap syntax description is actually defined in the 2691 * server schema. If not, then fail. Note that we are checking only the real 2692 * part of the ldapsyntaxes attribute. A virtual value is not searched and 2693 * hence never deleted. 2694 */ 2695 String oid = 2696 Schema.parseOID(definition, ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_ATTR_SYNTAX_LDAPSYNTAX_EMPTY_VALUE); 2697 2698 LDAPSyntaxDescription removeLSD = schema.getLdapSyntaxDescription(oid); 2699 if (removeLSD == null) 2700 { 2701 LocalizableMessage message = 2702 ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_LSD.get(oid); 2703 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2704 } 2705 2706 schema.deregisterLdapSyntaxDescription(removeLSD); 2707 String schemaFile = getSchemaFile(removeLSD); 2708 if (schemaFile != null) 2709 { 2710 modifiedSchemaFiles.add(schemaFile); 2711 } 2712 } 2713 2714 2715 2716 /** 2717 * Creates an empty entry that may be used as the basis for a new schema file. 2718 * 2719 * @return An empty entry that may be used as the basis for a new schema 2720 * file. 2721 */ 2722 private Entry createEmptySchemaEntry() 2723 { 2724 Map<ObjectClass,String> objectClasses = new LinkedHashMap<>(); 2725 objectClasses.put(DirectoryServer.getTopObjectClass(), OC_TOP); 2726 objectClasses.put(DirectoryServer.getObjectClass(OC_LDAP_SUBENTRY_LC, true), OC_LDAP_SUBENTRY); 2727 objectClasses.put(DirectoryServer.getObjectClass(OC_SUBSCHEMA, true), OC_SUBSCHEMA); 2728 2729 Map<AttributeType,List<Attribute>> userAttributes = new LinkedHashMap<>(); 2730 Map<AttributeType,List<Attribute>> operationalAttributes = new LinkedHashMap<>(); 2731 2732 DN dn = DirectoryServer.getSchemaDN(); 2733 for (AVA ava : dn.rdn()) 2734 { 2735 AttributeType type = ava.getAttributeType(); 2736 Map<AttributeType, List<Attribute>> attrs = type.isOperational() ? operationalAttributes : userAttributes; 2737 attrs.put(type, newLinkedList(Attributes.create(type, ava.getAttributeValue()))); 2738 } 2739 2740 return new Entry(dn, objectClasses, userAttributes, operationalAttributes); 2741 } 2742 2743 2744 2745 2746 /** 2747 * Writes a temporary version of the specified schema file. 2748 * 2749 * @param schema The schema from which to take the definitions to be 2750 * written. 2751 * @param schemaFile The name of the schema file to be written. 2752 * 2753 * @throws DirectoryException If an unexpected problem occurs while 2754 * identifying the schema definitions to include 2755 * in the schema file. 2756 * 2757 * @throws IOException If an unexpected error occurs while attempting to 2758 * write the temporary schema file. 2759 * 2760 * @throws LDIFException If an unexpected problem occurs while generating 2761 * the LDIF representation of the schema entry. 2762 */ 2763 private File writeTempSchemaFile(Schema schema, String schemaFile) 2764 throws DirectoryException, IOException, LDIFException 2765 { 2766 // Start with an empty schema entry. 2767 Entry schemaEntry = createEmptySchemaEntry(); 2768 2769 /* 2770 * Add all of the ldap syntax descriptions to the schema entry. We do 2771 * this only for the real part of the ldapsyntaxes attribute. The real part 2772 * is read and write to/from the schema files. 2773 */ 2774 Set<ByteString> values = new LinkedHashSet<>(); 2775 for (LDAPSyntaxDescription ldapSyntax : 2776 schema.getLdapSyntaxDescriptions().values()) 2777 { 2778 if (schemaFile.equals(getSchemaFile(ldapSyntax))) 2779 { 2780 values.add(ByteString.valueOfUtf8(ldapSyntax.toString())); 2781 } 2782 } 2783 2784 if (! values.isEmpty()) 2785 { 2786 AttributeBuilder builder = new AttributeBuilder(ldapSyntaxesType); 2787 builder.addAll(values); 2788 schemaEntry.putAttribute(ldapSyntaxesType, newArrayList(builder.toAttribute())); 2789 } 2790 2791 // Add all of the appropriate attribute types to the schema entry. We need 2792 // to be careful of the ordering to ensure that any superior types in the 2793 // same file are written before the subordinate types. 2794 Set<AttributeType> addedTypes = new HashSet<>(); 2795 values = new LinkedHashSet<>(); 2796 for (AttributeType at : schema.getAttributeTypes()) 2797 { 2798 String atSchemaFile = new SomeSchemaElement(at).getSchemaFile(); 2799 if (schemaFile.equals(atSchemaFile)) 2800 { 2801 addAttrTypeToSchemaFile(schema, schemaFile, at, values, addedTypes, 0); 2802 } 2803 } 2804 2805 if (! values.isEmpty()) 2806 { 2807 AttributeBuilder builder = new AttributeBuilder(attributeTypesType); 2808 builder.addAll(values); 2809 schemaEntry.putAttribute(attributeTypesType, newArrayList(builder.toAttribute())); 2810 } 2811 2812 2813 // Add all of the appropriate objectclasses to the schema entry. We need 2814 // to be careful of the ordering to ensure that any superior classes in the 2815 // same file are written before the subordinate classes. 2816 Set<ObjectClass> addedClasses = new HashSet<>(); 2817 values = new LinkedHashSet<>(); 2818 for (ObjectClass oc : schema.getObjectClasses().values()) 2819 { 2820 if (schemaFile.equals(getSchemaFile(oc))) 2821 { 2822 addObjectClassToSchemaFile(schema, schemaFile, oc, values, addedClasses, 2823 0); 2824 } 2825 } 2826 2827 if (! values.isEmpty()) 2828 { 2829 AttributeBuilder builder = new AttributeBuilder(objectClassesType); 2830 builder.addAll(values); 2831 schemaEntry.putAttribute(objectClassesType, newArrayList(builder.toAttribute())); 2832 } 2833 2834 2835 // Add all of the appropriate name forms to the schema entry. Since there 2836 // is no hierarchical relationship between name forms, we don't need to 2837 // worry about ordering. 2838 values = new LinkedHashSet<>(); 2839 for (List<NameForm> forms : schema.getNameFormsByObjectClass().values()) 2840 { 2841 for(NameForm nf : forms) 2842 { 2843 if (schemaFile.equals(getSchemaFile(nf))) 2844 { 2845 values.add(ByteString.valueOfUtf8(nf.toString())); 2846 } 2847 } 2848 } 2849 2850 if (! values.isEmpty()) 2851 { 2852 AttributeBuilder builder = new AttributeBuilder(nameFormsType); 2853 builder.addAll(values); 2854 schemaEntry.putAttribute(nameFormsType, newArrayList(builder.toAttribute())); 2855 } 2856 2857 2858 // Add all of the appropriate DIT content rules to the schema entry. Since 2859 // there is no hierarchical relationship between DIT content rules, we don't 2860 // need to worry about ordering. 2861 values = new LinkedHashSet<>(); 2862 for (DITContentRule dcr : schema.getDITContentRules().values()) 2863 { 2864 if (schemaFile.equals(getSchemaFile(dcr))) 2865 { 2866 values.add(ByteString.valueOfUtf8(dcr.toString())); 2867 } 2868 } 2869 2870 if (! values.isEmpty()) 2871 { 2872 AttributeBuilder builder = new AttributeBuilder(ditContentRulesType); 2873 builder.addAll(values); 2874 schemaEntry.putAttribute(ditContentRulesType, newArrayList(builder.toAttribute())); 2875 } 2876 2877 2878 // Add all of the appropriate DIT structure rules to the schema entry. We 2879 // need to be careful of the ordering to ensure that any superior rules in 2880 // the same file are written before the subordinate rules. 2881 Set<DITStructureRule> addedDSRs = new HashSet<>(); 2882 values = new LinkedHashSet<>(); 2883 for (DITStructureRule dsr : schema.getDITStructureRulesByID().values()) 2884 { 2885 if (schemaFile.equals(getSchemaFile(dsr))) 2886 { 2887 addDITStructureRuleToSchemaFile(schema, schemaFile, dsr, values, 2888 addedDSRs, 0); 2889 } 2890 } 2891 2892 if (! values.isEmpty()) 2893 { 2894 AttributeBuilder builder = new AttributeBuilder(ditStructureRulesType); 2895 builder.addAll(values); 2896 schemaEntry.putAttribute(ditStructureRulesType, newArrayList(builder.toAttribute())); 2897 } 2898 2899 2900 // Add all of the appropriate matching rule uses to the schema entry. Since 2901 // there is no hierarchical relationship between matching rule uses, we 2902 // don't need to worry about ordering. 2903 values = new LinkedHashSet<>(); 2904 for (MatchingRuleUse mru : schema.getMatchingRuleUses().values()) 2905 { 2906 if (schemaFile.equals(getSchemaFile(mru))) 2907 { 2908 values.add(ByteString.valueOfUtf8(mru.toString())); 2909 } 2910 } 2911 2912 if (! values.isEmpty()) 2913 { 2914 AttributeBuilder builder = new AttributeBuilder(matchingRuleUsesType); 2915 builder.addAll(values); 2916 schemaEntry.putAttribute(matchingRuleUsesType, newArrayList(builder.toAttribute())); 2917 } 2918 2919 2920 if (FILE_USER_SCHEMA_ELEMENTS.equals(schemaFile)) 2921 { 2922 Map<String, Attribute> attributes = schema.getExtraAttributes(); 2923 for (Attribute attribute : attributes.values()) 2924 { 2925 ArrayList<Attribute> attrList = newArrayList(attribute); 2926 schemaEntry.putAttribute(attribute.getAttributeDescription().getAttributeType(), attrList); 2927 } 2928 } 2929 2930 // Create a temporary file to which we can write the schema entry. 2931 File tempFile = File.createTempFile(schemaFile, "temp"); 2932 LDIFExportConfig exportConfig = 2933 new LDIFExportConfig(tempFile.getAbsolutePath(), 2934 ExistingFileBehavior.OVERWRITE); 2935 LDIFWriter ldifWriter = new LDIFWriter(exportConfig); 2936 ldifWriter.writeEntry(schemaEntry); 2937 ldifWriter.close(); 2938 2939 return tempFile; 2940 } 2941 2942 2943 2944 /** 2945 * Adds the definition for the specified attribute type to the provided set of 2946 * attribute values, recursively adding superior types as appropriate. 2947 * 2948 * @param schema The schema containing the attribute type. 2949 * @param schemaFile The schema file with which the attribute type is 2950 * associated. 2951 * @param attributeType The attribute type whose definition should be added 2952 * to the value set. 2953 * @param values The set of values for attribute type definitions 2954 * already added. 2955 * @param addedTypes The set of attribute types whose definitions have 2956 * already been added to the set of values. 2957 * @param depth A depth counter to use in an attempt to detect 2958 * circular references. 2959 */ 2960 private void addAttrTypeToSchemaFile(Schema schema, String schemaFile, 2961 AttributeType attributeType, 2962 Set<ByteString> values, 2963 Set<AttributeType> addedTypes, 2964 int depth) 2965 throws DirectoryException 2966 { 2967 if (depth > 20) 2968 { 2969 LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_AT.get( 2970 attributeType.getNameOrOID()); 2971 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2972 } 2973 2974 if (addedTypes.contains(attributeType)) 2975 { 2976 return; 2977 } 2978 2979 AttributeType superiorType = attributeType.getSuperiorType(); 2980 if (superiorType != null && 2981 schemaFile.equals(new SomeSchemaElement(attributeType).getSchemaFile()) && 2982 !addedTypes.contains(superiorType)) 2983 { 2984 addAttrTypeToSchemaFile(schema, schemaFile, superiorType, values, 2985 addedTypes, depth+1); 2986 } 2987 2988 values.add(ByteString.valueOfUtf8(attributeType.toString())); 2989 addedTypes.add(attributeType); 2990 } 2991 2992 2993 2994 /** 2995 * Adds the definition for the specified objectclass to the provided set of 2996 * attribute values, recursively adding superior classes as appropriate. 2997 * 2998 * @param schema The schema containing the objectclass. 2999 * @param schemaFile The schema file with which the objectclass is 3000 * associated. 3001 * @param objectClass The objectclass whose definition should be added to 3002 * the value set. 3003 * @param values The set of values for objectclass definitions 3004 * already added. 3005 * @param addedClasses The set of objectclasses whose definitions have 3006 * already been added to the set of values. 3007 * @param depth A depth counter to use in an attempt to detect 3008 * circular references. 3009 */ 3010 private void addObjectClassToSchemaFile(Schema schema, String schemaFile, 3011 ObjectClass objectClass, 3012 Set<ByteString> values, 3013 Set<ObjectClass> addedClasses, 3014 int depth) 3015 throws DirectoryException 3016 { 3017 if (depth > 20) 3018 { 3019 LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_OC.get( 3020 objectClass.getNameOrOID()); 3021 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 3022 } 3023 3024 if (addedClasses.contains(objectClass)) 3025 { 3026 return; 3027 } 3028 3029 for(ObjectClass superiorClass : objectClass.getSuperiorClasses()) 3030 { 3031 if (schemaFile.equals(getSchemaFile(superiorClass)) && 3032 !addedClasses.contains(superiorClass)) 3033 { 3034 addObjectClassToSchemaFile(schema, schemaFile, superiorClass, values, 3035 addedClasses, depth+1); 3036 } 3037 } 3038 values.add(ByteString.valueOfUtf8(objectClass.toString())); 3039 addedClasses.add(objectClass); 3040 } 3041 3042 3043 3044 /** 3045 * Adds the definition for the specified DIT structure rule to the provided 3046 * set of attribute values, recursively adding superior rules as appropriate. 3047 * 3048 * @param schema The schema containing the DIT structure rule. 3049 * @param schemaFile The schema file with which the DIT structure rule 3050 * is associated. 3051 * @param ditStructureRule The DIT structure rule whose definition should be 3052 * added to the value set. 3053 * @param values The set of values for DIT structure rule 3054 * definitions already added. 3055 * @param addedDSRs The set of DIT structure rules whose definitions 3056 * have already been added added to the set of 3057 * values. 3058 * @param depth A depth counter to use in an attempt to detect 3059 * circular references. 3060 */ 3061 private void addDITStructureRuleToSchemaFile(Schema schema, String schemaFile, 3062 DITStructureRule ditStructureRule, 3063 Set<ByteString> values, 3064 Set<DITStructureRule> addedDSRs, int depth) 3065 throws DirectoryException 3066 { 3067 if (depth > 20) 3068 { 3069 LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_DSR.get( 3070 ditStructureRule.getNameOrRuleID()); 3071 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 3072 } 3073 3074 if (addedDSRs.contains(ditStructureRule)) 3075 { 3076 return; 3077 } 3078 3079 for (DITStructureRule dsr : ditStructureRule.getSuperiorRules()) 3080 { 3081 if (schemaFile.equals(getSchemaFile(dsr)) && !addedDSRs.contains(dsr)) 3082 { 3083 addDITStructureRuleToSchemaFile(schema, schemaFile, dsr, values, 3084 addedDSRs, depth+1); 3085 } 3086 } 3087 3088 values.add(ByteString.valueOfUtf8(ditStructureRule.toString())); 3089 addedDSRs.add(ditStructureRule); 3090 } 3091 3092 3093 3094 /** 3095 * Moves the specified temporary schema files in place of the active versions. 3096 * If an error occurs in the process, then this method will attempt to restore 3097 * the original schema files if possible. 3098 * 3099 * @param tempSchemaFiles The set of temporary schema files to be activated. 3100 * 3101 * @throws DirectoryException If a problem occurs while attempting to 3102 * install the temporary schema files. 3103 */ 3104 private void installSchemaFiles(HashMap<String,File> tempSchemaFiles) 3105 throws DirectoryException 3106 { 3107 // Create lists that will hold the three types of files we'll be dealing 3108 // with (the temporary files that will be installed, the installed schema 3109 // files, and the previously-installed schema files). 3110 ArrayList<File> installedFileList = new ArrayList<>(); 3111 ArrayList<File> tempFileList = new ArrayList<>(); 3112 ArrayList<File> origFileList = new ArrayList<>(); 3113 3114 File schemaInstanceDir = 3115 new File(SchemaConfigManager.getSchemaDirectoryPath()); 3116 3117 for (String name : tempSchemaFiles.keySet()) 3118 { 3119 installedFileList.add(new File(schemaInstanceDir, name)); 3120 tempFileList.add(tempSchemaFiles.get(name)); 3121 origFileList.add(new File(schemaInstanceDir, name + ".orig")); 3122 } 3123 3124 3125 // If there are any old ".orig" files laying around from a previous 3126 // attempt, then try to clean them up. 3127 for (File f : origFileList) 3128 { 3129 if (f.exists()) 3130 { 3131 f.delete(); 3132 } 3133 } 3134 3135 3136 // Copy all of the currently-installed files with a ".orig" extension. If 3137 // this fails, then try to clean up the copies. 3138 try 3139 { 3140 for (int i=0; i < installedFileList.size(); i++) 3141 { 3142 File installedFile = installedFileList.get(i); 3143 File origFile = origFileList.get(i); 3144 3145 if (installedFile.exists()) 3146 { 3147 copyFile(installedFile, origFile); 3148 } 3149 } 3150 } 3151 catch (Exception e) 3152 { 3153 logger.traceException(e); 3154 3155 boolean allCleaned = true; 3156 for (File f : origFileList) 3157 { 3158 try 3159 { 3160 if (f.exists() && !f.delete()) 3161 { 3162 allCleaned = false; 3163 } 3164 } 3165 catch (Exception e2) 3166 { 3167 logger.traceException(e2); 3168 3169 allCleaned = false; 3170 } 3171 } 3172 3173 LocalizableMessage message; 3174 if (allCleaned) 3175 { 3176 message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_CLEANED.get(getExceptionMessage(e)); 3177 } 3178 else 3179 { 3180 message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_NOT_CLEANED.get(getExceptionMessage(e)); 3181 3182 DirectoryServer.sendAlertNotification(this, 3183 ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES, 3184 message); 3185 } 3186 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 3187 } 3188 3189 3190 // Try to copy all of the temporary files into place over the installed 3191 // files. If this fails, then try to restore the originals. 3192 try 3193 { 3194 for (int i=0; i < installedFileList.size(); i++) 3195 { 3196 File installedFile = installedFileList.get(i); 3197 File tempFile = tempFileList.get(i); 3198 copyFile(tempFile, installedFile); 3199 } 3200 } 3201 catch (Exception e) 3202 { 3203 logger.traceException(e); 3204 3205 deleteFiles(installedFileList); 3206 3207 boolean allRestored = true; 3208 for (int i=0; i < installedFileList.size(); i++) 3209 { 3210 File installedFile = installedFileList.get(i); 3211 File origFile = origFileList.get(i); 3212 3213 try 3214 { 3215 if (origFile.exists() && !origFile.renameTo(installedFile)) 3216 { 3217 allRestored = false; 3218 } 3219 } 3220 catch (Exception e2) 3221 { 3222 logger.traceException(e2); 3223 3224 allRestored = false; 3225 } 3226 } 3227 3228 LocalizableMessage message; 3229 if (allRestored) 3230 { 3231 message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_RESTORED.get(getExceptionMessage(e)); 3232 } 3233 else 3234 { 3235 message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_NOT_RESTORED.get(getExceptionMessage(e)); 3236 3237 DirectoryServer.sendAlertNotification(this, 3238 ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES, 3239 message); 3240 } 3241 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 3242 } 3243 3244 deleteFiles(origFileList); 3245 deleteFiles(tempFileList); 3246 } 3247 3248 private void deleteFiles(Iterable<File> files) 3249 { 3250 if (files != null) 3251 { 3252 for (File f : files) 3253 { 3254 try 3255 { 3256 if (f.exists()) 3257 { 3258 f.delete(); 3259 } 3260 } 3261 catch (Exception e) 3262 { 3263 logger.traceException(e); 3264 } 3265 } 3266 } 3267 } 3268 3269 3270 3271 /** 3272 * Creates a copy of the specified file. 3273 * 3274 * @param from The source file to be copied. 3275 * @param to The destination file to be created. 3276 * 3277 * @throws IOException If a problem occurs. 3278 */ 3279 private void copyFile(File from, File to) throws IOException 3280 { 3281 byte[] buffer = new byte[4096]; 3282 FileInputStream inputStream = null; 3283 FileOutputStream outputStream = null; 3284 try 3285 { 3286 inputStream = new FileInputStream(from); 3287 outputStream = new FileOutputStream(to, false); 3288 3289 int bytesRead = inputStream.read(buffer); 3290 while (bytesRead > 0) 3291 { 3292 outputStream.write(buffer, 0, bytesRead); 3293 bytesRead = inputStream.read(buffer); 3294 } 3295 } 3296 finally 3297 { 3298 close(inputStream, outputStream); 3299 } 3300 } 3301 3302 3303 3304 /** 3305 * Performs any necessary cleanup in an attempt to delete any temporary schema 3306 * files that may have been left over after trying to install the new schema. 3307 * 3308 * @param tempSchemaFiles The set of temporary schema files that have been 3309 * created and are candidates for cleanup. 3310 */ 3311 private void cleanUpTempSchemaFiles(HashMap<String,File> tempSchemaFiles) 3312 { 3313 deleteFiles(tempSchemaFiles.values()); 3314 } 3315 3316 @Override 3317 public void renameEntry(DN currentDN, Entry entry, 3318 ModifyDNOperation modifyDNOperation) 3319 throws DirectoryException 3320 { 3321 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 3322 ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID())); 3323 } 3324 3325 @Override 3326 public void search(SearchOperation searchOperation) 3327 throws DirectoryException 3328 { 3329 DN baseDN = searchOperation.getBaseDN(); 3330 3331 boolean found = false; 3332 DN[] dnArray = baseDNs; 3333 DN matchedDN = null; 3334 for (DN dn : dnArray) 3335 { 3336 if (dn.equals(baseDN)) 3337 { 3338 found = true; 3339 break; 3340 } 3341 else if (dn.isSuperiorOrEqualTo(baseDN)) 3342 { 3343 matchedDN = dn; 3344 break; 3345 } 3346 } 3347 3348 if (! found) 3349 { 3350 LocalizableMessage message = ERR_SCHEMA_INVALID_BASE.get(baseDN); 3351 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, 3352 matchedDN, null); 3353 } 3354 3355 3356 // If it's a onelevel or subordinate subtree search, then we will never 3357 // match anything since there isn't anything below the schema. 3358 SearchScope scope = searchOperation.getScope(); 3359 if (scope == SearchScope.SINGLE_LEVEL || 3360 scope == SearchScope.SUBORDINATES) 3361 { 3362 return; 3363 } 3364 3365 3366 // Get the schema entry and see if it matches the filter. If so, then send 3367 // it to the client. 3368 Entry schemaEntry = getSchemaEntry(baseDN, false); 3369 SearchFilter filter = searchOperation.getFilter(); 3370 if (filter.matchesEntry(schemaEntry)) 3371 { 3372 searchOperation.returnEntry(schemaEntry, null); 3373 } 3374 } 3375 3376 @Override 3377 public Set<String> getSupportedControls() 3378 { 3379 return Collections.emptySet(); 3380 } 3381 3382 @Override 3383 public Set<String> getSupportedFeatures() 3384 { 3385 return Collections.emptySet(); 3386 } 3387 3388 @Override 3389 public void exportLDIF(LDIFExportConfig exportConfig) 3390 throws DirectoryException 3391 { 3392 // Create the LDIF writer. 3393 LDIFWriter ldifWriter; 3394 try 3395 { 3396 ldifWriter = new LDIFWriter(exportConfig); 3397 } 3398 catch (Exception e) 3399 { 3400 logger.traceException(e); 3401 3402 LocalizableMessage message = ERR_SCHEMA_UNABLE_TO_CREATE_LDIF_WRITER.get( 3403 stackTraceToSingleLineString(e)); 3404 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3405 message); 3406 } 3407 3408 3409 // Write the root schema entry to it. Make sure to close the LDIF 3410 // writer when we're done. 3411 try 3412 { 3413 ldifWriter.writeEntry(getSchemaEntry(baseDNs[0], true, true)); 3414 } 3415 catch (Exception e) 3416 { 3417 logger.traceException(e); 3418 3419 LocalizableMessage message = 3420 ERR_SCHEMA_UNABLE_TO_EXPORT_BASE.get(stackTraceToSingleLineString(e)); 3421 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3422 message); 3423 } 3424 finally 3425 { 3426 close(ldifWriter); 3427 } 3428 } 3429 3430 @Override 3431 public boolean supports(BackendOperation backendOperation) 3432 { 3433 switch (backendOperation) 3434 { 3435 case LDIF_EXPORT: 3436 case LDIF_IMPORT: 3437 case RESTORE: 3438 // We will provide a restore, but only for offline operations. 3439 case BACKUP: 3440 // We do support an online backup mechanism for the schema. 3441 return true; 3442 3443 default: 3444 return false; 3445 } 3446 } 3447 3448 @Override 3449 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 3450 throws DirectoryException 3451 { 3452 LDIFReader reader; 3453 try 3454 { 3455 reader = new LDIFReader(importConfig); 3456 } 3457 catch (Exception e) 3458 { 3459 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3460 ERR_MEMORYBACKEND_CANNOT_CREATE_LDIF_READER.get(e), e); 3461 } 3462 3463 3464 try 3465 { 3466 while (true) 3467 { 3468 Entry e = null; 3469 try 3470 { 3471 e = reader.readEntry(); 3472 if (e == null) 3473 { 3474 break; 3475 } 3476 } 3477 catch (LDIFException le) 3478 { 3479 if (! le.canContinueReading()) 3480 { 3481 throw new DirectoryException( 3482 DirectoryServer.getServerErrorResultCode(), 3483 ERR_MEMORYBACKEND_ERROR_READING_LDIF.get(e), le); 3484 } 3485 else 3486 { 3487 continue; 3488 } 3489 } 3490 3491 importEntry(e); 3492 } 3493 3494 return new LDIFImportResult(reader.getEntriesRead(), 3495 reader.getEntriesRejected(), 3496 reader.getEntriesIgnored()); 3497 } 3498 catch (DirectoryException de) 3499 { 3500 throw de; 3501 } 3502 catch (Exception e) 3503 { 3504 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3505 ERR_MEMORYBACKEND_ERROR_DURING_IMPORT.get(e), e); 3506 } 3507 finally 3508 { 3509 close(reader); 3510 } 3511 } 3512 3513 3514 /** 3515 * Import an entry in a new schema by : 3516 * - duplicating the schema 3517 * - iterating over each element of the newSchemaEntry and comparing 3518 * with the existing schema 3519 * - if the new schema element do not exist : add it 3520 * 3521 * FIXME : attributeTypes and objectClasses are the only elements 3522 * currently taken into account. 3523 * 3524 * @param newSchemaEntry The entry to be imported. 3525 */ 3526 private void importEntry(Entry newSchemaEntry) 3527 throws DirectoryException 3528 { 3529 Schema schema = serverContext.getSchema(); 3530 Schema newSchema = schema.duplicate(); 3531 TreeSet<String> modifiedSchemaFiles = new TreeSet<>(); 3532 3533 // Get the attributeTypes attribute from the entry. 3534 AttributeType attributeAttrType = CoreSchema.getAttributeTypesAttributeType(); 3535 3536 // loop on the attribute types in the entry just received 3537 // and add them in the existing schema. 3538 Set<String> oidList = new HashSet<>(1000); 3539 for (Attribute a : newSchemaEntry.getAttribute(attributeAttrType)) 3540 { 3541 // Look for attribute types that could have been added to the schema 3542 // or modified in the schema 3543 for (ByteString v : a) 3544 { 3545 // Parse the attribute type. 3546 AttributeType attrType = schema.parseAttributeType(v.toString()); 3547 String schemaFile = new SomeSchemaElement(attrType).getSchemaFile(); 3548 if (CONFIG_SCHEMA_ELEMENTS_FILE.equals(schemaFile)) 3549 { 3550 // Don't import the file containing the definitions of the 3551 // Schema elements used for configuration because these 3552 // definitions may vary between versions of OpenDJ. 3553 continue; 3554 } 3555 3556 oidList.add(attrType.getOID()); 3557 try 3558 { 3559 // Register this attribute type in the new schema 3560 // unless it is already defined with the same syntax. 3561 AttributeType oldAttrType = schema.getAttributeType(attrType.getOID()); 3562 if (oldAttrType == null || !oldAttrType.toString().equals(attrType.toString())) 3563 { 3564 newSchema.registerAttributeType(attrType, true); 3565 3566 if (schemaFile != null) 3567 { 3568 modifiedSchemaFiles.add(schemaFile); 3569 } 3570 } 3571 } 3572 catch (Exception e) 3573 { 3574 logger.info(NOTE_SCHEMA_IMPORT_FAILED, attrType, e.getMessage()); 3575 } 3576 } 3577 } 3578 3579 // loop on all the attribute types in the current schema and delete 3580 // them from the new schema if they are not in the imported schema entry. 3581 for (AttributeType removeType : newSchema.getAttributeTypes()) 3582 { 3583 String schemaFile = new SomeSchemaElement(removeType).getSchemaFile(); 3584 if (CONFIG_SCHEMA_ELEMENTS_FILE.equals(schemaFile) || CORE_SCHEMA_ELEMENTS_FILE.equals(schemaFile)) 3585 { 3586 // Don't import the file containing the definitions of the 3587 // Schema elements used for configuration because these 3588 // definitions may vary between versions of OpenDJ. 3589 // Also never delete anything from the core schema file. 3590 continue; 3591 } 3592 if (!oidList.contains(removeType.getOID())) 3593 { 3594 newSchema.deregisterAttributeType(removeType); 3595 if (schemaFile != null) 3596 { 3597 modifiedSchemaFiles.add(schemaFile); 3598 } 3599 } 3600 } 3601 3602 // loop on the objectClasses from the entry, search if they are 3603 // already in the current schema, add them if not. 3604 AttributeType objectclassAttrType = CoreSchema.getObjectClassesAttributeType(); 3605 3606 oidList.clear(); 3607 for (Attribute a : newSchemaEntry.getAttribute(objectclassAttrType)) 3608 { 3609 for (ByteString v : a) 3610 { 3611 // It IS important here to allow the unknown elements that could 3612 // appear in the new config schema. 3613 ObjectClass newObjectClass = ObjectClassSyntax.decodeObjectClass(v, newSchema, true); 3614 String schemaFile = getSchemaFile(newObjectClass); 3615 if (CONFIG_SCHEMA_ELEMENTS_FILE.equals(schemaFile)) 3616 { 3617 // Don't import the file containing the definitions of the 3618 // Schema elements used for configuration because these 3619 // definitions may vary between versions of OpenDJ. 3620 continue; 3621 } 3622 3623 // Now we know we are not in the config schema, let's check 3624 // the unknown elements ... sadly but simply by redoing the 3625 // whole decoding. 3626 newObjectClass = ObjectClassSyntax.decodeObjectClass(v, newSchema, false); 3627 oidList.add(newObjectClass.getOID()); 3628 try 3629 { 3630 // Register this ObjectClass in the new schema 3631 // unless it is already defined with the same syntax. 3632 ObjectClass oldObjectClass = schema.getObjectClass(newObjectClass.getOID()); 3633 if (oldObjectClass == null || !oldObjectClass.toString().equals(newObjectClass.toString())) 3634 { 3635 newSchema.registerObjectClass(newObjectClass, true); 3636 3637 if (schemaFile != null) 3638 { 3639 modifiedSchemaFiles.add(schemaFile); 3640 } 3641 } 3642 } 3643 catch (Exception e) 3644 { 3645 logger.info(NOTE_SCHEMA_IMPORT_FAILED, newObjectClass, e.getMessage()); 3646 } 3647 } 3648 } 3649 3650 // loop on all the object classes in the current schema and delete 3651 // them from the new schema if they are not in the imported schema entry. 3652 ConcurrentHashMap<String, ObjectClass> currentObjectClasses = newSchema.getObjectClasses(); 3653 3654 for (ObjectClass removeClass : currentObjectClasses.values()) 3655 { 3656 String schemaFile = getSchemaFile(removeClass); 3657 if (CONFIG_SCHEMA_ELEMENTS_FILE.equals(schemaFile)) 3658 { 3659 // Don't import the file containing the definition of the 3660 // Schema elements used for configuration because these 3661 // definitions may vary between versions of OpenDJ. 3662 continue; 3663 } 3664 if (!oidList.contains(removeClass.getOID())) 3665 { 3666 newSchema.deregisterObjectClass(removeClass); 3667 3668 if (schemaFile != null) 3669 { 3670 modifiedSchemaFiles.add(schemaFile); 3671 } 3672 } 3673 } 3674 3675 // Finally, if there were some modifications, save the new schema 3676 // in the Schema Files and update DirectoryServer. 3677 if (!modifiedSchemaFiles.isEmpty()) 3678 { 3679 updateSchemaFiles(newSchema, modifiedSchemaFiles); 3680 DirectoryServer.setSchema(newSchema); 3681 } 3682 } 3683 3684 @Override 3685 public void createBackup(BackupConfig backupConfig) throws DirectoryException 3686 { 3687 new BackupManager(getBackendID()).createBackup(this, backupConfig); 3688 } 3689 3690 @Override 3691 public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException 3692 { 3693 new BackupManager(getBackendID()).removeBackup(backupDirectory, backupID); 3694 } 3695 3696 @Override 3697 public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException 3698 { 3699 new BackupManager(getBackendID()).restoreBackup(this, restoreConfig); 3700 } 3701 3702 @Override 3703 public boolean isConfigurationChangeAcceptable( 3704 SchemaBackendCfg configEntry, 3705 List<LocalizableMessage> unacceptableReasons) 3706 { 3707 return true; 3708 } 3709 3710 @Override 3711 public ConfigChangeResult applyConfigurationChange(SchemaBackendCfg backendCfg) 3712 { 3713 final ConfigChangeResult ccr = new ConfigChangeResult(); 3714 3715 3716 // Check to see if we should apply a new set of base DNs. 3717 Set<DN> newBaseDNs; 3718 try 3719 { 3720 newBaseDNs = new HashSet<>(backendCfg.getSchemaEntryDN()); 3721 if (newBaseDNs.isEmpty()) 3722 { 3723 newBaseDNs.add(DN.valueOf(DN_DEFAULT_SCHEMA_ROOT)); 3724 } 3725 } 3726 catch (Exception e) 3727 { 3728 logger.traceException(e); 3729 3730 ccr.addMessage(ERR_SCHEMA_CANNOT_DETERMINE_BASE_DN.get( 3731 configEntryDN, getExceptionMessage(e))); 3732 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 3733 newBaseDNs = null; 3734 } 3735 3736 3737 // Check to see if we should change the behavior regarding whether to show 3738 // all schema attributes. 3739 boolean newShowAllAttributes = backendCfg.isShowAllAttributes(); 3740 3741 3742 // Check to see if there is a new set of user-defined attributes. 3743 ArrayList<Attribute> newUserAttrs = new ArrayList<>(); 3744 try 3745 { 3746 ConfigEntry configEntry = DirectoryServer.getConfigEntry(configEntryDN); 3747 for (List<Attribute> attrs : 3748 configEntry.getEntry().getUserAttributes().values()) 3749 { 3750 for (Attribute a : attrs) 3751 { 3752 if (! isSchemaConfigAttribute(a)) 3753 { 3754 newUserAttrs.add(a); 3755 } 3756 } 3757 } 3758 for (List<Attribute> attrs : 3759 configEntry.getEntry().getOperationalAttributes().values()) 3760 { 3761 for (Attribute a : attrs) 3762 { 3763 if (! isSchemaConfigAttribute(a)) 3764 { 3765 newUserAttrs.add(a); 3766 } 3767 } 3768 } 3769 } 3770 catch (ConfigException e) 3771 { 3772 logger.traceException(e); 3773 3774 ccr.addMessage(ERR_CONFIG_BACKEND_ERROR_INTERACTING_WITH_BACKEND_ENTRY.get( 3775 configEntryDN, stackTraceToSingleLineString(e))); 3776 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 3777 } 3778 3779 3780 if (ccr.getResultCode() == ResultCode.SUCCESS) 3781 { 3782 // Get an array containing the new base DNs to use. 3783 DN[] dnArray = new DN[newBaseDNs.size()]; 3784 newBaseDNs.toArray(dnArray); 3785 3786 3787 // Determine the set of DNs to add and delete. When this is done, the 3788 // deleteBaseDNs will contain the set of DNs that should no longer be used 3789 // and should be deregistered from the server, and the newBaseDNs set will 3790 // just contain the set of DNs to add. 3791 Set<DN> deleteBaseDNs = new HashSet<>(baseDNs.length); 3792 for (DN baseDN : baseDNs) 3793 { 3794 if (! newBaseDNs.remove(baseDN)) 3795 { 3796 deleteBaseDNs.add(baseDN); 3797 } 3798 } 3799 3800 for (DN dn : deleteBaseDNs) 3801 { 3802 try 3803 { 3804 DirectoryServer.deregisterBaseDN(dn); 3805 ccr.addMessage(INFO_SCHEMA_DEREGISTERED_BASE_DN.get(dn)); 3806 } 3807 catch (Exception e) 3808 { 3809 logger.traceException(e); 3810 3811 ccr.addMessage(ERR_SCHEMA_CANNOT_DEREGISTER_BASE_DN.get(dn, getExceptionMessage(e))); 3812 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 3813 } 3814 } 3815 3816 baseDNs = dnArray; 3817 for (DN dn : newBaseDNs) 3818 { 3819 try 3820 { 3821 DirectoryServer.registerBaseDN(dn, this, true); 3822 ccr.addMessage(INFO_SCHEMA_REGISTERED_BASE_DN.get(dn)); 3823 } 3824 catch (Exception e) 3825 { 3826 logger.traceException(e); 3827 3828 ccr.addMessage(ERR_SCHEMA_CANNOT_REGISTER_BASE_DN.get(dn, getExceptionMessage(e))); 3829 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 3830 } 3831 } 3832 3833 3834 showAllAttributes = newShowAllAttributes; 3835 3836 3837 userDefinedAttributes = newUserAttrs; 3838 LocalizableMessage message = INFO_SCHEMA_USING_NEW_USER_ATTRS.get(); 3839 ccr.addMessage(message); 3840 } 3841 3842 3843 currentConfig = backendCfg; 3844 return ccr; 3845 } 3846 3847 3848 3849 /** 3850 * Indicates whether to treat common schema attributes like user attributes 3851 * rather than operational attributes. 3852 * 3853 * @return {@code true} if common attributes should be treated like user 3854 * attributes, or {@code false} if not. 3855 */ 3856 boolean showAllAttributes() 3857 { 3858 return showAllAttributes; 3859 } 3860 3861 3862 3863 /** 3864 * Specifies whether to treat common schema attributes like user attributes 3865 * rather than operational attributes. 3866 * 3867 * @param showAllAttributes Specifies whether to treat common schema 3868 * attributes like user attributes rather than 3869 * operational attributes. 3870 */ 3871 void setShowAllAttributes(boolean showAllAttributes) 3872 { 3873 this.showAllAttributes = showAllAttributes; 3874 } 3875 3876 @Override 3877 public DN getComponentEntryDN() 3878 { 3879 return configEntryDN; 3880 } 3881 3882 @Override 3883 public String getClassName() 3884 { 3885 return CLASS_NAME; 3886 } 3887 3888 @Override 3889 public Map<String, String> getAlerts() 3890 { 3891 Map<String, String> alerts = new LinkedHashMap<>(); 3892 3893 alerts.put(ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES, 3894 ALERT_DESCRIPTION_CANNOT_COPY_SCHEMA_FILES); 3895 alerts.put(ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES, 3896 ALERT_DESCRIPTION_CANNOT_WRITE_NEW_SCHEMA_FILES); 3897 3898 return alerts; 3899 } 3900 3901 @Override 3902 public File getDirectory() 3903 { 3904 return new File(SchemaConfigManager.getSchemaDirectoryPath()); 3905 } 3906 3907 private static final FileFilter BACKUP_FILES_FILTER = new FileFilter() 3908 { 3909 @Override 3910 public boolean accept(File file) 3911 { 3912 return file.getName().endsWith(".ldif"); 3913 } 3914 }; 3915 3916 @Override 3917 public ListIterator<Path> getFilesToBackup() throws DirectoryException 3918 { 3919 return BackupManager.getFiles(getDirectory(), BACKUP_FILES_FILTER, getBackendID()).listIterator(); 3920 } 3921 3922 @Override 3923 public boolean isDirectRestore() 3924 { 3925 return true; 3926 } 3927 3928 @Override 3929 public Path beforeRestore() throws DirectoryException 3930 { 3931 // save current schema files in save directory 3932 return BackupManager.saveCurrentFilesToDirectory(this, getBackendID()); 3933 } 3934 3935 @Override 3936 public void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException 3937 { 3938 // restore was successful, delete save directory 3939 StaticUtils.recursiveDelete(saveDirectory.toFile()); 3940 } 3941}