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}