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