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