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