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