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