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