001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.backends;
028    
029    
030    
031    import java.io.File;
032    import java.io.FileInputStream;
033    import java.io.FileOutputStream;
034    import java.io.InputStream;
035    import java.io.IOException;
036    import java.io.OutputStream;
037    import java.security.MessageDigest;
038    import java.util.ArrayList;
039    import java.util.Arrays;
040    import java.util.Date;
041    import java.util.HashMap;
042    import java.util.HashSet;
043    import java.util.Iterator;
044    import java.util.LinkedHashMap;
045    import java.util.LinkedHashSet;
046    import java.util.LinkedList;
047    import java.util.List;
048    import java.util.Map;
049    import java.util.Set;
050    import java.util.TreeSet;
051    import java.util.concurrent.ConcurrentHashMap;
052    import java.util.zip.Deflater;
053    import java.util.zip.ZipEntry;
054    import java.util.zip.ZipInputStream;
055    import java.util.zip.ZipOutputStream;
056    import javax.crypto.Mac;
057    
058    import org.opends.messages.Message;
059    import org.opends.server.admin.Configuration;
060    import org.opends.server.admin.std.server.SchemaBackendCfg;
061    import org.opends.server.admin.server.ConfigurationChangeListener;
062    import org.opends.server.api.AlertGenerator;
063    import org.opends.server.api.Backend;
064    import org.opends.server.api.ClientConnection;
065    import org.opends.server.api.MatchingRule;
066    import org.opends.server.config.ConfigEntry;
067    import org.opends.server.config.ConfigException;
068    import org.opends.server.core.AddOperation;
069    import org.opends.server.core.DeleteOperation;
070    import org.opends.server.core.DirectoryServer;
071    import org.opends.server.core.ModifyOperation;
072    import org.opends.server.core.ModifyDNOperation;
073    import org.opends.server.core.SchemaConfigManager;
074    import org.opends.server.core.SearchOperation;
075    import org.opends.server.loggers.ErrorLogger;
076    import org.opends.server.loggers.debug.DebugTracer;
077    import org.opends.server.protocols.asn1.ASN1OctetString;
078    import org.opends.server.schema.AttributeTypeSyntax;
079    import org.opends.server.schema.DITContentRuleSyntax;
080    import org.opends.server.schema.DITStructureRuleSyntax;
081    import org.opends.server.schema.GeneralizedTimeSyntax;
082    import org.opends.server.schema.MatchingRuleUseSyntax;
083    import org.opends.server.schema.NameFormSyntax;
084    import org.opends.server.schema.ObjectClassSyntax;
085    import org.opends.server.types.*;
086    import org.opends.server.util.DynamicConstants;
087    import org.opends.server.util.LDIFException;
088    import org.opends.server.util.LDIFReader;
089    import org.opends.server.util.LDIFWriter;
090    import org.opends.server.util.Validator;
091    
092    import static org.opends.messages.BackendMessages.*;
093    import static org.opends.messages.ConfigMessages.*;
094    import static org.opends.messages.SchemaMessages.*;
095    import static org.opends.server.config.ConfigConstants.*;
096    import static org.opends.server.loggers.debug.DebugLogger.*;
097    import static org.opends.server.loggers.ErrorLogger.*;
098    import static org.opends.server.schema.SchemaConstants.*;
099    import static org.opends.server.util.ServerConstants.*;
100    import static org.opends.server.util.StaticUtils.*;
101    
102    
103    /**
104     * This class defines a backend to hold the Directory Server schema information.
105     * It is a kind of meta-backend in that it doesn't actually hold any data but
106     * rather dynamically generates the schema entry whenever it is requested.
107     */
108    public class SchemaBackend
109         extends Backend
110         implements ConfigurationChangeListener<SchemaBackendCfg>, AlertGenerator
111    {
112      /**
113       * The tracer object for the debug logger.
114       */
115      private static final DebugTracer TRACER = getTracer();
116    
117    
118      /**
119       * The fully-qualified name of this class.
120       */
121      private static final String CLASS_NAME =
122           "org.opends.server.backends.SchemaBackend";
123    
124    
125      private static final String CONFIG_SCHEMA_ELEMENTS_FILE = "02-config.ldif";
126    
127    
128    
129      // The set of user-defined attributes that will be included in the schema
130      // entry.
131      private ArrayList<Attribute> userDefinedAttributes;
132    
133      // The attribute type that will be used to include the defined attribute
134      // types.
135      private AttributeType attributeTypesType;
136    
137      // The attribute type that will be used to hold the schema creation timestamp.
138      private AttributeType createTimestampType;
139    
140      // The attribute type that will be used to hold the schema creator's name.
141      private AttributeType creatorsNameType;
142    
143      // The attribute type that will be used to include the defined DIT content
144      // rules.
145      private AttributeType ditContentRulesType;
146    
147      // The attribute type that will be used to include the defined DIT structure
148      // rules.
149      private AttributeType ditStructureRulesType;
150    
151      // The attribute type that will be used to include the defined attribute
152      // syntaxes.
153      private AttributeType ldapSyntaxesType;
154    
155      // The attribute type that will be used to include the defined matching rules.
156      private AttributeType matchingRulesType;
157    
158      // The attribute type that will be used to include the defined matching rule
159      // uses.
160      private AttributeType matchingRuleUsesType;
161    
162      // The attribute that will be used to hold the schema modifier's name.
163      private AttributeType modifiersNameType;
164    
165      // The attribute type that will be used to hold the schema modification
166      // timestamp.
167      private AttributeType modifyTimestampType;
168    
169      // The attribute type that will be used to include the defined object classes.
170      private AttributeType objectClassesType;
171    
172      // The attribute type that will be used to include the defined name forms.
173      private AttributeType nameFormsType;
174    
175      // The value containing DN of the user we'll say created the configuration.
176      private AttributeValue creatorsName;
177    
178      // The value containing the DN of the last user to modify the configuration.
179      private AttributeValue modifiersName;
180    
181      // The timestamp that will be used for the schema creation time.
182      private AttributeValue createTimestamp;
183    
184      // The timestamp that will be used for the latest schema modification time.
185      private AttributeValue modifyTimestamp;
186    
187      // Indicates whether the attributes of the schema entry should always be
188      // treated as user attributes even if they are defined as operational.
189      private boolean showAllAttributes;
190    
191      // The DN of the configuration entry for this backend.
192      private DN configEntryDN;
193    
194      // The current configuration state.
195      private SchemaBackendCfg currentConfig;
196    
197      // The set of base DNs for this backend.
198      private DN[] baseDNs;
199    
200      // The set of objectclasses that will be used in the schema entry.
201      private HashMap<ObjectClass,String> schemaObjectClasses;
202    
203      // The set of supported controls for this backend.
204      private HashSet<String> supportedControls;
205    
206      // The set of supported features for this backend.
207      private HashSet<String> supportedFeatures;
208    
209      // The time that the schema was last modified.
210      private long modifyTime;
211    
212      //Regular expression used to strip minimum upper bound value from
213      //syntax Attribute Type Description. The value looks like: {count}.
214      private String stripMinUpperBoundRegEx = "\\{\\d+\\}";
215    
216    
217    
218      /**
219       * Creates a new backend with the provided information.  All backend
220       * implementations must implement a default constructor that use
221       * <CODE>super()</CODE> to invoke this constructor.
222       */
223      public SchemaBackend()
224      {
225        super();
226    
227        // Perform all initialization in initializeBackend.
228      }
229    
230    
231    
232      /**
233       * {@inheritDoc}
234       */
235      @Override()
236      public void configureBackend(Configuration config)
237           throws ConfigException
238      {
239        // Make sure that a configuration entry was provided.  If not, then we will
240        // not be able to complete initialization.
241        if (config == null)
242        {
243          Message message = ERR_SCHEMA_CONFIG_ENTRY_NULL.get();
244          throw new ConfigException(message);
245        }
246    
247        Validator.ensureTrue(config instanceof SchemaBackendCfg);
248        SchemaBackendCfg cfg = (SchemaBackendCfg)config;
249        ConfigEntry configEntry = DirectoryServer.getConfigEntry(cfg.dn());
250    
251        configEntryDN = configEntry.getDN();
252    
253        // Get all of the attribute types that we will use for schema elements.
254        attributeTypesType =
255             DirectoryServer.getAttributeType(ATTR_ATTRIBUTE_TYPES_LC, true);
256        objectClassesType =
257             DirectoryServer.getAttributeType(ATTR_OBJECTCLASSES_LC, true);
258        matchingRulesType =
259             DirectoryServer.getAttributeType(ATTR_MATCHING_RULES_LC, true);
260        ldapSyntaxesType =
261             DirectoryServer.getAttributeType(ATTR_LDAP_SYNTAXES_LC, true);
262        ditContentRulesType =
263             DirectoryServer.getAttributeType(ATTR_DIT_CONTENT_RULES_LC, true);
264        ditStructureRulesType =
265             DirectoryServer.getAttributeType(ATTR_DIT_STRUCTURE_RULES_LC, true);
266        matchingRuleUsesType =
267             DirectoryServer.getAttributeType(ATTR_MATCHING_RULE_USE_LC, true);
268        nameFormsType = DirectoryServer.getAttributeType(ATTR_NAME_FORMS_LC, true);
269    
270        // Initialize the lastmod attributes.
271        creatorsNameType =
272             DirectoryServer.getAttributeType(OP_ATTR_CREATORS_NAME_LC, true);
273        createTimestampType =
274             DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC, true);
275        modifiersNameType =
276             DirectoryServer.getAttributeType(OP_ATTR_MODIFIERS_NAME_LC, true);
277        modifyTimestampType =
278             DirectoryServer.getAttributeType(OP_ATTR_MODIFY_TIMESTAMP_LC, true);
279    
280        // Construct the set of objectclasses to include in the schema entry.
281        schemaObjectClasses = new LinkedHashMap<ObjectClass,String>(3);
282        schemaObjectClasses.put(DirectoryServer.getTopObjectClass(), OC_TOP);
283    
284        ObjectClass subentryOC = DirectoryServer.getObjectClass(OC_LDAP_SUBENTRY_LC,
285                                                                true);
286        schemaObjectClasses.put(subentryOC, OC_LDAP_SUBENTRY);
287    
288        ObjectClass subschemaOC = DirectoryServer.getObjectClass(OC_SUBSCHEMA,
289                                                                 true);
290        schemaObjectClasses.put(subschemaOC, OC_SUBSCHEMA);
291    
292    
293        // Define empty sets for the supported controls and features.
294        supportedControls = new HashSet<String>(0);
295        supportedFeatures = new HashSet<String>(0);
296    
297    
298        configEntryDN = configEntry.getDN();
299    
300        DN[] baseDNs = new DN[cfg.getBaseDN().size()];
301        cfg.getBaseDN().toArray(baseDNs);
302        this.baseDNs = baseDNs;
303    
304        creatorsName  = new AttributeValue(creatorsNameType, baseDNs[0].toString());
305        modifiersName =
306             new AttributeValue(modifiersNameType, baseDNs[0].toString());
307    
308        long createTime = DirectoryServer.getSchema().getOldestModificationTime();
309        createTimestamp =
310             GeneralizedTimeSyntax.createGeneralizedTimeValue(createTime);
311    
312        long modifyTime = DirectoryServer.getSchema().getYoungestModificationTime();
313        modifyTimestamp =
314             GeneralizedTimeSyntax.createGeneralizedTimeValue(modifyTime);
315    
316    
317        // Get the set of user-defined attributes for the configuration entry.  Any
318        // attributes that we don't recognize will be included directly in the
319        // schema entry.
320        userDefinedAttributes = new ArrayList<Attribute>();
321        for (List<Attribute> attrs :
322             configEntry.getEntry().getUserAttributes().values())
323        {
324          for (Attribute a : attrs)
325          {
326            if (! isSchemaConfigAttribute(a))
327            {
328              userDefinedAttributes.add(a);
329            }
330          }
331        }
332        for (List<Attribute> attrs :
333             configEntry.getEntry().getOperationalAttributes().values())
334        {
335          for (Attribute a : attrs)
336          {
337            if (! isSchemaConfigAttribute(a))
338            {
339              userDefinedAttributes.add(a);
340            }
341          }
342        }
343    
344    
345        // Determine whether to show all attributes.
346        showAllAttributes = cfg.isShowAllAttributes();
347    
348    
349        currentConfig = cfg;
350      }
351    
352    
353    
354      /**
355       * {@inheritDoc}
356       */
357      @Override()
358      public void initializeBackend()
359             throws ConfigException, InitializationException
360      {
361        // Register each of the suffixes with the Directory Server.  Also, register
362        // the first one as the schema base.
363        DirectoryServer.setSchemaDN(baseDNs[0]);
364        for (int i=0; i < baseDNs.length; i++)
365        {
366          try
367          {
368            DirectoryServer.registerBaseDN(baseDNs[i], this, true);
369          }
370          catch (Exception e)
371          {
372            if (debugEnabled())
373            {
374              TRACER.debugCaught(DebugLogLevel.ERROR, e);
375            }
376    
377            Message message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
378                baseDNs[i].toString(), getExceptionMessage(e));
379            throw new InitializationException(message, e);
380          }
381        }
382    
383    
384        // Identify any differences that may exist between the concatenated schema
385        // file from the last online modification and the current schema files.  If
386        // there are any differences, then they should be from making changes to the
387        // schema files with the server offline.
388        try
389        {
390          // First, generate lists of elements from the current schema.
391          LinkedHashSet<String> newATs  = new LinkedHashSet<String>();
392          LinkedHashSet<String> newOCs  = new LinkedHashSet<String>();
393          LinkedHashSet<String> newNFs  = new LinkedHashSet<String>();
394          LinkedHashSet<String> newDCRs = new LinkedHashSet<String>();
395          LinkedHashSet<String> newDSRs = new LinkedHashSet<String>();
396          LinkedHashSet<String> newMRUs = new LinkedHashSet<String>();
397          Schema.genConcatenatedSchema(newATs, newOCs, newNFs, newDCRs, newDSRs,
398                                       newMRUs);
399    
400          // Next, generate lists of elements from the previous concatenated schema.
401          // If there isn't a previous concatenated schema, then use the base
402          // schema for the current revision.
403          String concatFilePath;
404          File configFile       = new File(DirectoryServer.getConfigFile());
405          File configDirectory  = configFile.getParentFile();
406          File upgradeDirectory = new File(configDirectory, "upgrade");
407          File concatFile       = new File(upgradeDirectory,
408                                           SCHEMA_CONCAT_FILE_NAME);
409          if (concatFile.exists())
410          {
411            concatFilePath = concatFile.getAbsolutePath();
412          }
413          else
414          {
415            concatFile = new File(upgradeDirectory,
416                                  SCHEMA_BASE_FILE_NAME_WITHOUT_REVISION +
417                                  DynamicConstants.REVISION_NUMBER);
418            if (concatFile.exists())
419            {
420              concatFilePath = concatFile.getAbsolutePath();
421            }
422            else
423            {
424              String runningUnitTestsStr =
425                   System.getProperty(PROPERTY_RUNNING_UNIT_TESTS);
426              if ((runningUnitTestsStr != null) &&
427                  runningUnitTestsStr.equalsIgnoreCase("true"))
428              {
429                Schema.writeConcatenatedSchema();
430                concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
431                concatFilePath = concatFile.getAbsolutePath();
432              }
433              else
434              {
435                Message message = ERR_SCHEMA_CANNOT_FIND_CONCAT_FILE.
436                    get(upgradeDirectory.getAbsolutePath(), SCHEMA_CONCAT_FILE_NAME,
437                        concatFile.getName());
438                throw new InitializationException(message);
439              }
440            }
441          }
442    
443          LinkedHashSet<String> oldATs  = new LinkedHashSet<String>();
444          LinkedHashSet<String> oldOCs  = new LinkedHashSet<String>();
445          LinkedHashSet<String> oldNFs  = new LinkedHashSet<String>();
446          LinkedHashSet<String> oldDCRs = new LinkedHashSet<String>();
447          LinkedHashSet<String> oldDSRs = new LinkedHashSet<String>();
448          LinkedHashSet<String> oldMRUs = new LinkedHashSet<String>();
449          Schema.readConcatenatedSchema(concatFilePath, oldATs, oldOCs, oldNFs,
450                                        oldDCRs, oldDSRs, oldMRUs);
451    
452          // Create a list of modifications and add any differences between the old
453          // and new schema into them.
454          LinkedList<Modification> mods = new LinkedList<Modification>();
455          Schema.compareConcatenatedSchema(oldATs, newATs, attributeTypesType,
456                                           mods);
457          Schema.compareConcatenatedSchema(oldOCs, newOCs, objectClassesType, mods);
458          Schema.compareConcatenatedSchema(oldNFs, newNFs, nameFormsType, mods);
459          Schema.compareConcatenatedSchema(oldDCRs, newDCRs, ditContentRulesType,
460                                           mods);
461          Schema.compareConcatenatedSchema(oldDSRs, newDSRs, ditStructureRulesType,
462                                           mods);
463          Schema.compareConcatenatedSchema(oldMRUs, newMRUs, matchingRuleUsesType,
464                                           mods);
465          if (! mods.isEmpty())
466          {
467            DirectoryServer.setOfflineSchemaChanges(mods);
468    
469            // Write a new concatenated schema file with the most recent information
470            // so we don't re-find these same changes on the next startup.
471            Schema.writeConcatenatedSchema();
472          }
473        }
474        catch (InitializationException ie)
475        {
476          throw ie;
477        }
478        catch (Exception e)
479        {
480          if (debugEnabled())
481          {
482            TRACER.debugCaught(DebugLogLevel.ERROR, e);
483          }
484    
485          Message message = ERR_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES.get(
486              getExceptionMessage(e));
487          ErrorLogger.logError(message);
488        }
489    
490    
491        // Register with the Directory Server as a configurable component.
492        currentConfig.addSchemaChangeListener(this);
493      }
494    
495    
496    
497      /**
498       * {@inheritDoc}
499       */
500      @Override()
501      public void finalizeBackend()
502      {
503        currentConfig.removeSchemaChangeListener(this);
504    
505        for (DN baseDN : baseDNs)
506        {
507          try
508          {
509            DirectoryServer.deregisterBaseDN(baseDN);
510          }
511          catch (Exception e)
512          {
513            if (debugEnabled())
514            {
515              TRACER.debugCaught(DebugLogLevel.ERROR, e);
516            }
517          }
518        }
519      }
520    
521    
522    
523      /**
524       * Indicates whether the provided attribute is one that is used in the
525       * configuration of this backend.
526       *
527       * @param  attribute  The attribute for which to make the determination.
528       *
529       * @return  <CODE>true</CODE> if the provided attribute is one that is used in
530       *          the configuration of this backend, <CODE>false</CODE> if not.
531       */
532      private boolean isSchemaConfigAttribute(Attribute attribute)
533      {
534        AttributeType attrType = attribute.getAttributeType();
535        if (attrType.hasName(ATTR_SCHEMA_ENTRY_DN.toLowerCase()) ||
536            attrType.hasName(ATTR_BACKEND_ENABLED.toLowerCase()) ||
537            attrType.hasName(ATTR_BACKEND_CLASS.toLowerCase()) ||
538            attrType.hasName(ATTR_BACKEND_ID.toLowerCase()) ||
539            attrType.hasName(ATTR_BACKEND_BASE_DN.toLowerCase()) ||
540            attrType.hasName(ATTR_BACKEND_WRITABILITY_MODE.toLowerCase()) ||
541            attrType.hasName(ATTR_SCHEMA_SHOW_ALL_ATTRIBUTES.toLowerCase()) ||
542            attrType.hasName(ATTR_COMMON_NAME) ||
543            attrType.hasName(OP_ATTR_CREATORS_NAME_LC) ||
544            attrType.hasName(OP_ATTR_CREATE_TIMESTAMP_LC) ||
545            attrType.hasName(OP_ATTR_MODIFIERS_NAME_LC) ||
546            attrType.hasName(OP_ATTR_MODIFY_TIMESTAMP_LC))
547        {
548          return true;
549        }
550    
551        return false;
552      }
553    
554    
555    
556      /**
557       * {@inheritDoc}
558       */
559      @Override()
560      public DN[] getBaseDNs()
561      {
562        return baseDNs;
563      }
564    
565    
566    
567      /**
568       * {@inheritDoc}
569       */
570      @Override()
571      public long getEntryCount()
572      {
573        // There is always only a single entry in this backend.
574        return 1;
575      }
576    
577    
578    
579      /**
580       * {@inheritDoc}
581       */
582      @Override()
583      public boolean isLocal()
584      {
585        // For the purposes of this method, this is a local backend.
586        return true;
587      }
588    
589    
590    
591      /**
592       * {@inheritDoc}
593       */
594      @Override()
595      public boolean isIndexed(AttributeType attributeType, IndexType indexType)
596      {
597        // All searches in this backend will always be considered indexed.
598        return true;
599      }
600    
601    
602    
603      /**
604       * {@inheritDoc}
605       */
606      @Override()
607      public ConditionResult hasSubordinates(DN entryDN)
608             throws DirectoryException
609      {
610        return ConditionResult.FALSE;
611      }
612    
613    
614    
615      /**
616       * {@inheritDoc}
617       */
618      @Override()
619      public long numSubordinates(DN entryDN, boolean subtree)
620             throws DirectoryException
621      {
622        return 0L;
623      }
624    
625    
626    
627      /**
628       * {@inheritDoc}
629       */
630      @Override()
631      public Entry getEntry(DN entryDN)
632             throws DirectoryException
633      {
634        // If the requested entry was one of the schema entries, then create and
635        // return it.
636        DN[] dnArray = baseDNs;
637        for (DN baseDN : dnArray)
638        {
639          if (entryDN.equals(baseDN))
640          {
641            return getSchemaEntry(entryDN, false);
642          }
643        }
644    
645    
646        // There is never anything below the schema entries, so we will return null.
647        return null;
648      }
649    
650    
651    
652      /**
653       * Generates and returns a schema entry for the Directory Server.
654       *
655       * @param  entryDN            The DN to use for the generated entry.
656       * @param  includeSchemaFile  A boolean indicating if the X-SCHEMA-FILE
657       *                            extension should be used when generating
658       *                            the entry.
659       *
660       * @return  The schema entry that was generated.
661       */
662      public Entry getSchemaEntry(DN entryDN, boolean includeSchemaFile)
663      {
664        LinkedHashMap<AttributeType,List<Attribute>> userAttrs =
665             new LinkedHashMap<AttributeType,List<Attribute>>();
666    
667        LinkedHashMap<AttributeType,List<Attribute>> operationalAttrs =
668             new LinkedHashMap<AttributeType,List<Attribute>>();
669    
670    
671        // Add the RDN attribute(s) for the provided entry.
672        RDN rdn = entryDN.getRDN();
673        if (rdn != null)
674        {
675          int numAVAs = rdn.getNumValues();
676          for (int i=0; i < numAVAs; i++)
677          {
678            LinkedHashSet<AttributeValue> valueSet =
679                 new LinkedHashSet<AttributeValue>(1);
680            valueSet.add(rdn.getAttributeValue(i));
681    
682            AttributeType attrType = rdn.getAttributeType(i);
683            String attrName = rdn.getAttributeName(i);
684            Attribute a = new Attribute(attrType, attrName, valueSet);
685            ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
686            attrList.add(a);
687    
688            if (attrType.isOperational())
689            {
690              operationalAttrs.put(attrType, attrList);
691            }
692            else
693            {
694              userAttrs.put(attrType, attrList);
695            }
696          }
697        }
698    
699        Schema schema = DirectoryServer.getSchema();
700    
701        // Add the "attributeTypes" attribute.
702        LinkedHashSet<AttributeValue> valueSet =
703             DirectoryServer.getAttributeTypeSet();
704    
705        // Add the file name to the description of the attribute type if this
706        // was requested by the caller.
707        if (includeSchemaFile)
708        {
709          LinkedHashSet<AttributeValue> newValueSet =
710            new LinkedHashSet<AttributeValue>(valueSet.size());
711    
712          for (AttributeValue value : valueSet)
713          {
714            try
715            {
716              // Build a new attribute from this value,
717              // get the File name from this attribute, build a new attribute
718              // including this file name.
719              AttributeType attrType = AttributeTypeSyntax.decodeAttributeType(
720                  value.getValue(), schema, false);
721              attrType = DirectoryServer.getAttributeType(attrType.getOID());
722    
723              newValueSet.add(new AttributeValue(
724                  attributeTypesType, attrType.getDefinitionWithFileName()));
725            }
726            catch (DirectoryException e)
727            {
728              newValueSet.add(value);
729            }
730          }
731          valueSet = newValueSet;
732        }
733    
734        Attribute attr;
735        if(AttributeTypeSyntax.isStripSyntaxMinimumUpperBound())
736            attr = stripMinUpperBoundValues(valueSet);
737        else
738            attr = new Attribute(attributeTypesType, ATTR_ATTRIBUTE_TYPES,
739                  valueSet);
740        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
741        attrList.add(attr);
742        if (attributeTypesType.isOperational() && (! showAllAttributes))
743        {
744          operationalAttrs.put(attributeTypesType, attrList);
745        }
746        else
747        {
748          userAttrs.put(attributeTypesType, attrList);
749        }
750    
751        // Add the "objectClasses" attribute.
752        valueSet = DirectoryServer.getObjectClassSet();
753    
754        // Add the file name to the description if this was requested by the
755        // caller.
756        if (includeSchemaFile)
757        {
758          LinkedHashSet<AttributeValue> newValueSet =
759            new LinkedHashSet<AttributeValue>(valueSet.size());
760    
761          for (AttributeValue value : valueSet)
762          {
763            try
764            {
765              // Build a new attribute from this value,
766              // get the File name from this attribute, build a new attribute
767              // including this file name.
768              ObjectClass oc = ObjectClassSyntax.decodeObjectClass(
769                  value.getValue(), schema, false);
770              oc = DirectoryServer.getObjectClass(oc.getOID());
771              newValueSet.add(new AttributeValue(
772                  objectClassesType, oc.getDefinitionWithFileName()));
773            }
774            catch (DirectoryException e)
775            {
776              newValueSet.add(value);
777            }
778          }
779          valueSet = newValueSet;
780        }
781    
782        attr = new Attribute(objectClassesType, ATTR_OBJECTCLASSES, valueSet);
783        attrList = new ArrayList<Attribute>(1);
784        attrList.add(attr);
785        if (objectClassesType.isOperational() && (! showAllAttributes))
786        {
787          operationalAttrs.put(objectClassesType, attrList);
788        }
789        else
790        {
791          userAttrs.put(objectClassesType, attrList);
792        }
793    
794    
795        // Add the "matchingRules" attribute.
796        valueSet = DirectoryServer.getMatchingRuleSet();
797        attr = new Attribute(matchingRulesType, ATTR_MATCHING_RULES, valueSet);
798        attrList = new ArrayList<Attribute>(1);
799        attrList.add(attr);
800        if (matchingRulesType.isOperational() && (! showAllAttributes))
801        {
802          operationalAttrs.put(matchingRulesType, attrList);
803        }
804        else
805        {
806          userAttrs.put(matchingRulesType, attrList);
807        }
808    
809    
810        // Add the "ldapSyntaxes" attribute.
811        valueSet = DirectoryServer.getAttributeSyntaxSet();
812        attr = new Attribute(ldapSyntaxesType, ATTR_LDAP_SYNTAXES, valueSet);
813        attrList = new ArrayList<Attribute>(1);
814        attrList.add(attr);
815    
816        // Note that we intentionally ignore showAllAttributes for attribute
817        // syntaxes, name forms, matching rule uses, DIT content rules, and DIT
818        // structure rules because those attributes aren't allowed in the subschema
819        // objectclass, and treating them as user attributes would cause schema
820        // updates to fail.  This means that you'll always have to explicitly
821        // request these attributes in order to be able to see them.
822        if (ldapSyntaxesType.isOperational())
823        {
824          operationalAttrs.put(ldapSyntaxesType, attrList);
825        }
826        else
827        {
828          userAttrs.put(ldapSyntaxesType, attrList);
829        }
830    
831    
832        // If there are any name forms defined, then add them.
833        valueSet = DirectoryServer.getNameFormSet();
834        if (! valueSet.isEmpty())
835        {
836          attr = new Attribute(nameFormsType, ATTR_NAME_FORMS, valueSet);
837          attrList = new ArrayList<Attribute>(1);
838          attrList.add(attr);
839          if (nameFormsType.isOperational())
840          {
841            operationalAttrs.put(nameFormsType, attrList);
842          }
843          else
844          {
845            userAttrs.put(nameFormsType, attrList);
846          }
847        }
848    
849    
850        // If there are any DIT content rules defined, then add them.
851        valueSet = DirectoryServer.getDITContentRuleSet();
852        if (! valueSet.isEmpty())
853        {
854          attr = new Attribute(ditContentRulesType, ATTR_DIT_CONTENT_RULES,
855                               valueSet);
856          attrList = new ArrayList<Attribute>(1);
857          attrList.add(attr);
858          if (ditContentRulesType.isOperational())
859          {
860            operationalAttrs.put(ditContentRulesType, attrList);
861          }
862          else
863          {
864            userAttrs.put(ditContentRulesType, attrList);
865          }
866        }
867    
868    
869        // If there are any DIT structure rules defined, then add them.
870        valueSet = DirectoryServer.getDITStructureRuleSet();
871        if (! valueSet.isEmpty())
872        {
873          attr = new Attribute(ditStructureRulesType, ATTR_DIT_STRUCTURE_RULES,
874                               valueSet);
875          attrList = new ArrayList<Attribute>(1);
876          attrList.add(attr);
877          if (ditStructureRulesType.isOperational())
878          {
879            operationalAttrs.put(ditStructureRulesType, attrList);
880          }
881          else
882          {
883            userAttrs.put(ditStructureRulesType, attrList);
884          }
885        }
886    
887    
888        // If there are any matching rule uses defined, then add them.
889        valueSet = DirectoryServer.getMatchingRuleUseSet();
890        if (! valueSet.isEmpty())
891        {
892          attr = new Attribute(matchingRuleUsesType, ATTR_MATCHING_RULE_USE,
893                               valueSet);
894          attrList = new ArrayList<Attribute>(1);
895          attrList.add(attr);
896          if (matchingRuleUsesType.isOperational())
897          {
898            operationalAttrs.put(matchingRuleUsesType, attrList);
899          }
900          else
901          {
902            userAttrs.put(matchingRuleUsesType, attrList);
903          }
904        }
905    
906    
907        // Add the lastmod attributes.
908        valueSet = new LinkedHashSet<AttributeValue>(1);
909        valueSet.add(creatorsName);
910        attrList = new ArrayList<Attribute>(1);
911        attrList.add(new Attribute(creatorsNameType, OP_ATTR_CREATORS_NAME,
912                                   valueSet));
913        operationalAttrs.put(creatorsNameType, attrList);
914    
915        valueSet = new LinkedHashSet<AttributeValue>(1);
916        valueSet.add(createTimestamp);
917        attrList = new ArrayList<Attribute>(1);
918        attrList.add(new Attribute(createTimestampType, OP_ATTR_CREATE_TIMESTAMP,
919                                   valueSet));
920        operationalAttrs.put(createTimestampType, attrList);
921    
922        if (DirectoryServer.getSchema().getYoungestModificationTime() != modifyTime)
923        {
924          synchronized (this)
925          {
926            modifyTime = DirectoryServer.getSchema().getYoungestModificationTime();
927            modifyTimestamp =
928                 GeneralizedTimeSyntax.createGeneralizedTimeValue(modifyTime);
929          }
930        }
931    
932        valueSet = new LinkedHashSet<AttributeValue>(1);
933        valueSet.add(modifiersName);
934        attrList = new ArrayList<Attribute>(1);
935        attrList.add(new Attribute(modifiersNameType, OP_ATTR_MODIFIERS_NAME,
936                                   valueSet));
937        operationalAttrs.put(modifiersNameType, attrList);
938    
939        valueSet = new LinkedHashSet<AttributeValue>(1);
940        valueSet.add(modifyTimestamp);
941        attrList = new ArrayList<Attribute>(1);
942        attrList.add(new Attribute(modifyTimestampType, OP_ATTR_MODIFY_TIMESTAMP,
943                                   valueSet));
944        operationalAttrs.put(modifyTimestampType, attrList);
945    
946        //  Add the extra attributes.
947        Map<String, Attribute> attributes =
948          DirectoryServer.getSchema().getExtraAttributes();
949        for (Attribute attribute : attributes.values())
950        {
951          attrList = new ArrayList<Attribute>(1);
952          attrList.add(attribute);
953          operationalAttrs.put(attribute.getAttributeType(), attrList);
954        }
955    
956        // Add all the user-defined attributes.
957        for (Attribute a : userDefinedAttributes)
958        {
959          AttributeType type = a.getAttributeType();
960    
961          if (type.isOperational())
962          {
963            List<Attribute> attrs = operationalAttrs.get(type);
964            if (attrs == null)
965            {
966              attrs = new ArrayList<Attribute>();
967              attrs.add(a);
968              operationalAttrs.put(type, attrs);
969            }
970            else
971            {
972              attrs.add(a);
973            }
974          }
975          else
976          {
977            List<Attribute> attrs = userAttrs.get(type);
978            if (attrs == null)
979            {
980              attrs = new ArrayList<Attribute>();
981              attrs.add(a);
982              userAttrs.put(type, attrs);
983            }
984            else
985            {
986              attrs.add(a);
987            }
988          }
989        }
990    
991    
992        // Construct and return the entry.
993        Entry e = new Entry(entryDN, schemaObjectClasses, userAttrs,
994                            operationalAttrs);
995        e.processVirtualAttributes();
996        return e;
997      }
998    
999    
1000    
1001      /**
1002       * {@inheritDoc}
1003       */
1004      @Override()
1005      public boolean entryExists(DN entryDN)
1006             throws DirectoryException
1007      {
1008        // The specified DN must be one of the specified schema DNs.
1009        DN[] baseArray = baseDNs;
1010        for (DN baseDN : baseArray)
1011        {
1012          if (entryDN.equals(baseDN))
1013          {
1014            return true;
1015          }
1016        }
1017    
1018        return false;
1019      }
1020    
1021    
1022    
1023      /**
1024       * {@inheritDoc}
1025       */
1026      @Override()
1027      public void addEntry(Entry entry, AddOperation addOperation)
1028             throws DirectoryException
1029      {
1030        Message message =
1031            ERR_SCHEMA_ADD_NOT_SUPPORTED.get(String.valueOf(entry.getDN()));
1032        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1033      }
1034    
1035    
1036    
1037      /**
1038       * {@inheritDoc}
1039       */
1040      @Override()
1041      public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
1042             throws DirectoryException
1043      {
1044        Message message =
1045            ERR_SCHEMA_DELETE_NOT_SUPPORTED.get(String.valueOf(entryDN));
1046        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1047      }
1048    
1049    
1050    
1051      /**
1052       * {@inheritDoc}
1053       */
1054      @Override()
1055      public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
1056             throws DirectoryException
1057      {
1058        // Make sure that the authenticated user has the necessary UPDATE_SCHEMA
1059        // privilege.
1060        ClientConnection clientConnection = modifyOperation.getClientConnection();
1061        if (! clientConnection.hasPrivilege(Privilege.UPDATE_SCHEMA,
1062                                            modifyOperation))
1063        {
1064          Message message = ERR_SCHEMA_MODIFY_INSUFFICIENT_PRIVILEGES.get();
1065          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
1066                                       message);
1067        }
1068    
1069    
1070        ArrayList<Modification> mods =
1071             new ArrayList<Modification>(modifyOperation.getModifications());
1072        if (mods.isEmpty())
1073        {
1074          // There aren't any modifications, so we don't need to do anything.
1075          return;
1076        }
1077    
1078        Schema newSchema = DirectoryServer.getSchema().duplicate();
1079        TreeSet<String> modifiedSchemaFiles = new TreeSet<String>();
1080    
1081        int pos = -1;
1082        Iterator<Modification> iterator = mods.iterator();
1083        while (iterator.hasNext())
1084        {
1085          Modification m = iterator.next();
1086          pos++;
1087    
1088          // Determine the type of modification to perform.  We will support add and
1089          // delete operations in the schema, and we will also support the ability
1090          // to add a schema element that already exists and treat it as a
1091          // replacement of that existing element.
1092          Attribute     a  = m.getAttribute();
1093          AttributeType at = a.getAttributeType();
1094          switch (m.getModificationType())
1095          {
1096            case ADD:
1097              LinkedHashSet<AttributeValue> values = a.getValues();
1098              if (values.isEmpty())
1099              {
1100                continue;
1101              }
1102    
1103              if (at.equals(attributeTypesType))
1104              {
1105                for (AttributeValue v : values)
1106                {
1107                  AttributeType type;
1108                  try
1109                  {
1110                    type = AttributeTypeSyntax.decodeAttributeType(v.getValue(),
1111                                                    newSchema, false);
1112                  }
1113                  catch (DirectoryException de)
1114                  {
1115                    if (debugEnabled())
1116                    {
1117                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1118                    }
1119    
1120                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_ATTRTYPE.get(
1121                        v.getStringValue(), de.getMessageObject());
1122                    throw new DirectoryException(
1123                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1124                                   de);
1125                  }
1126    
1127                  addAttributeType(type, newSchema, modifiedSchemaFiles);
1128                }
1129              }
1130              else if (at.equals(objectClassesType))
1131              {
1132                for (AttributeValue v : values)
1133                {
1134                  ObjectClass oc;
1135                  try
1136                  {
1137                    oc = ObjectClassSyntax.decodeObjectClass(v.getValue(),
1138                                                             newSchema, false);
1139                  }
1140                  catch (DirectoryException de)
1141                  {
1142                    if (debugEnabled())
1143                    {
1144                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1145                    }
1146    
1147                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS.
1148                        get(v.getStringValue(), de.getMessageObject());
1149                    throw new DirectoryException(
1150                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1151                                   de);
1152                  }
1153    
1154                  addObjectClass(oc, newSchema, modifiedSchemaFiles);
1155                }
1156              }
1157              else if (at.equals(nameFormsType))
1158              {
1159                for (AttributeValue v : values)
1160                {
1161                  NameForm nf;
1162                  try
1163                  {
1164                    nf = NameFormSyntax.decodeNameForm(v.getValue(), newSchema,
1165                                                       false);
1166                  }
1167                  catch (DirectoryException de)
1168                  {
1169                    if (debugEnabled())
1170                    {
1171                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1172                    }
1173    
1174                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM.get(
1175                        v.getStringValue(), de.getMessageObject());
1176                    throw new DirectoryException(
1177                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1178                                   de);
1179                  }
1180    
1181                  addNameForm(nf, newSchema, modifiedSchemaFiles);
1182                }
1183              }
1184              else if (at.equals(ditContentRulesType))
1185              {
1186                for (AttributeValue v : values)
1187                {
1188                  DITContentRule dcr;
1189                  try
1190                  {
1191                    dcr = DITContentRuleSyntax.decodeDITContentRule(v.getValue(),
1192                                                    newSchema, false);
1193                  }
1194                  catch (DirectoryException de)
1195                  {
1196                    if (debugEnabled())
1197                    {
1198                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1199                    }
1200    
1201                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DCR.get(
1202                        v.getStringValue(), de.getMessageObject());
1203                    throw new DirectoryException(
1204                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1205                                   de);
1206                  }
1207    
1208                  addDITContentRule(dcr, newSchema, modifiedSchemaFiles);
1209                }
1210              }
1211              else if (at.equals(ditStructureRulesType))
1212              {
1213                for (AttributeValue v : values)
1214                {
1215                  DITStructureRule dsr;
1216                  try
1217                  {
1218                    dsr = DITStructureRuleSyntax.decodeDITStructureRule(
1219                               v.getValue(), newSchema, false);
1220                  }
1221                  catch (DirectoryException de)
1222                  {
1223                    if (debugEnabled())
1224                    {
1225                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1226                    }
1227    
1228                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DSR.get(
1229                        v.getStringValue(), de.getMessageObject());
1230                    throw new DirectoryException(
1231                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1232                                   de);
1233                  }
1234    
1235                  addDITStructureRule(dsr, newSchema, modifiedSchemaFiles);
1236                }
1237              }
1238              else if (at.equals(matchingRuleUsesType))
1239              {
1240                for (AttributeValue v : values)
1241                {
1242                  MatchingRuleUse mru;
1243                  try
1244                  {
1245                    mru = MatchingRuleUseSyntax.decodeMatchingRuleUse(v.getValue(),
1246                                                     newSchema, false);
1247                  }
1248                  catch (DirectoryException de)
1249                  {
1250                    if (debugEnabled())
1251                    {
1252                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1253                    }
1254    
1255                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_MR_USE.get(
1256                        v.getStringValue(), de.getMessageObject());
1257                    throw new DirectoryException(
1258                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1259                                   de);
1260                  }
1261    
1262                  addMatchingRuleUse(mru, newSchema, modifiedSchemaFiles);
1263                }
1264              }
1265              else
1266              {
1267                Message message =
1268                    ERR_SCHEMA_MODIFY_UNSUPPORTED_ATTRIBUTE_TYPE.get(a.getName());
1269                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1270                                             message);
1271              }
1272    
1273              break;
1274    
1275    
1276            case DELETE:
1277              values = a.getValues();
1278              if (values.isEmpty())
1279              {
1280                Message message =
1281                    ERR_SCHEMA_MODIFY_DELETE_NO_VALUES.get(a.getName());
1282                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1283                                             message);
1284              }
1285    
1286              if (at.equals(attributeTypesType))
1287              {
1288                for (AttributeValue v : values)
1289                {
1290                  AttributeType type;
1291                  try
1292                  {
1293                    type = AttributeTypeSyntax.decodeAttributeType(v.getValue(),
1294                                                    newSchema, false);
1295                  }
1296                  catch (DirectoryException de)
1297                  {
1298                    if (debugEnabled())
1299                    {
1300                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1301                    }
1302    
1303                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_ATTRTYPE.get(
1304                        v.getStringValue(), de.getMessageObject());
1305                    throw new DirectoryException(
1306                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1307                                   de);
1308                  }
1309    
1310                  removeAttributeType(type, newSchema, mods, pos,
1311                                      modifiedSchemaFiles);
1312                }
1313              }
1314              else if (at.equals(objectClassesType))
1315              {
1316                for (AttributeValue v : values)
1317                {
1318                  ObjectClass oc;
1319                  try
1320                  {
1321                    oc = ObjectClassSyntax.decodeObjectClass(v.getValue(),
1322                                                             newSchema, false);
1323                  }
1324                  catch (DirectoryException de)
1325                  {
1326                    if (debugEnabled())
1327                    {
1328                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1329                    }
1330    
1331                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS.
1332                        get(v.getStringValue(), de.getMessageObject());
1333                    throw new DirectoryException(
1334                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1335                                   de);
1336                  }
1337    
1338                  removeObjectClass(oc, newSchema, mods, pos, modifiedSchemaFiles);
1339                }
1340              }
1341              else if (at.equals(nameFormsType))
1342              {
1343                for (AttributeValue v : values)
1344                {
1345                  NameForm nf;
1346                  try
1347                  {
1348                    nf = NameFormSyntax.decodeNameForm(v.getValue(), newSchema,
1349                                                       false);
1350                  }
1351                  catch (DirectoryException de)
1352                  {
1353                    if (debugEnabled())
1354                    {
1355                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1356                    }
1357    
1358                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM.get(
1359                        v.getStringValue(), de.getMessageObject());
1360                    throw new DirectoryException(
1361                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1362                                   de);
1363                  }
1364    
1365                  removeNameForm(nf, newSchema, mods, pos, modifiedSchemaFiles);
1366                }
1367              }
1368              else if (at.equals(ditContentRulesType))
1369              {
1370                for (AttributeValue v : values)
1371                {
1372                  DITContentRule dcr;
1373                  try
1374                  {
1375                    dcr = DITContentRuleSyntax.decodeDITContentRule(v.getValue(),
1376                                                    newSchema, false);
1377                  }
1378                  catch (DirectoryException de)
1379                  {
1380                    if (debugEnabled())
1381                    {
1382                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1383                    }
1384    
1385                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DCR.get(
1386                        v.getStringValue(), de.getMessageObject());
1387                    throw new DirectoryException(
1388                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1389                                   de);
1390                  }
1391    
1392                  removeDITContentRule(dcr, newSchema, mods, pos,
1393                                       modifiedSchemaFiles);
1394                }
1395              }
1396              else if (at.equals(ditStructureRulesType))
1397              {
1398                for (AttributeValue v : values)
1399                {
1400                  DITStructureRule dsr;
1401                  try
1402                  {
1403                    dsr = DITStructureRuleSyntax.decodeDITStructureRule(
1404                               v.getValue(), newSchema, false);
1405                  }
1406                  catch (DirectoryException de)
1407                  {
1408                    if (debugEnabled())
1409                    {
1410                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1411                    }
1412    
1413                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DSR.get(
1414                        v.getStringValue(), de.getMessageObject());
1415                    throw new DirectoryException(
1416                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1417                                   de);
1418                  }
1419    
1420                  removeDITStructureRule(dsr, newSchema, mods, pos,
1421                                         modifiedSchemaFiles);
1422                }
1423              }
1424              else if (at.equals(matchingRuleUsesType))
1425              {
1426                for (AttributeValue v : values)
1427                {
1428                  MatchingRuleUse mru;
1429                  try
1430                  {
1431                    mru = MatchingRuleUseSyntax.decodeMatchingRuleUse(v.getValue(),
1432                                                     newSchema, false);
1433                  }
1434                  catch (DirectoryException de)
1435                  {
1436                    if (debugEnabled())
1437                    {
1438                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
1439                    }
1440    
1441                    Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_MR_USE.get(
1442                        v.getStringValue(), de.getMessageObject());
1443                    throw new DirectoryException(
1444                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1445                                   de);
1446                  }
1447    
1448                  removeMatchingRuleUse(mru, newSchema, mods, pos,
1449                                        modifiedSchemaFiles);
1450                }
1451              }
1452              else
1453              {
1454                Message message =
1455                    ERR_SCHEMA_MODIFY_UNSUPPORTED_ATTRIBUTE_TYPE.get(a.getName());
1456                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1457                                             message);
1458              }
1459    
1460              break;
1461    
1462    
1463            default:
1464              if ((!m.isInternal()) &&
1465                  (!modifyOperation.isSynchronizationOperation()))
1466              {
1467                Message message = ERR_SCHEMA_INVALID_MODIFICATION_TYPE.get(
1468                    String.valueOf(m.getModificationType()));
1469                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1470                                             message);
1471              }
1472              else
1473              {
1474                newSchema.addExtraAttribute(at.getNameOrOID(), a);
1475                modifiedSchemaFiles.add(FILE_USER_SCHEMA_ELEMENTS);
1476              }
1477          }
1478        }
1479    
1480    
1481        // If we've gotten here, then everything looks OK, re-write all the
1482        // modified Schema Files.
1483        updateSchemaFiles(newSchema, modifiedSchemaFiles);
1484    
1485        // Finally set DirectoryServer to use the new Schema.
1486        DirectoryServer.setSchema(newSchema);
1487    
1488    
1489        DN authzDN = modifyOperation.getAuthorizationDN();
1490        if (authzDN == null)
1491        {
1492          authzDN = DN.nullDN();
1493        }
1494    
1495        modifiersName = new AttributeValue(modifiersNameType, authzDN.toString());
1496        modifyTimestamp = GeneralizedTimeSyntax.createGeneralizedTimeValue(
1497                               System.currentTimeMillis());
1498      }
1499    
1500    
1501    
1502      /**
1503       * Re-write all schema files using the provided new Schema and list of
1504       * modified files.
1505       *
1506       * @param newSchema            The new schema that should be used.
1507       *
1508       * @param modifiedSchemaFiles  The list of files that should be modified.
1509       *
1510       * @throws DirectoryException  When the new file cannot be written.
1511       */
1512      private void updateSchemaFiles(
1513                   Schema newSchema, TreeSet<String> modifiedSchemaFiles)
1514              throws DirectoryException
1515      {
1516        // We'll re-write all
1517        // impacted schema files by first creating them in a temporary location
1518        // and then replacing the existing schema files with the new versions.
1519        // If all that goes successfully, then activate the new schema.
1520        HashMap<String,File> tempSchemaFiles = new HashMap<String,File>();
1521        try
1522        {
1523          for (String schemaFile : modifiedSchemaFiles)
1524          {
1525            File tempSchemaFile = writeTempSchemaFile(newSchema, schemaFile);
1526            tempSchemaFiles.put(schemaFile, tempSchemaFile);
1527          }
1528    
1529          installSchemaFiles(tempSchemaFiles);
1530        }
1531        catch (DirectoryException de)
1532        {
1533          if (debugEnabled())
1534          {
1535            TRACER.debugCaught(DebugLogLevel.ERROR, de);
1536          }
1537    
1538          throw de;
1539        }
1540        catch (Exception e)
1541        {
1542          if (debugEnabled())
1543          {
1544            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1545          }
1546    
1547          Message message =
1548              ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_SCHEMA.get(getExceptionMessage(e));
1549          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1550                                       message, e);
1551        }
1552        finally
1553        {
1554          cleanUpTempSchemaFiles(tempSchemaFiles);
1555        }
1556    
1557    
1558        // Create a single file with all of the concatenated schema information
1559        // that we can use on startup to detect whether the schema files have been
1560        // edited with the server offline.
1561        Schema.writeConcatenatedSchema();
1562      }
1563    
1564    
1565    
1566      /**
1567       * Handles all processing required for adding the provided attribute type to
1568       * the given schema, replacing an existing type if necessary, and ensuring all
1569       * other metadata is properly updated.
1570       *
1571       * @param  attributeType        The attribute type to add or replace in the
1572       *                              server schema.
1573       * @param  schema               The schema to which the attribute type should
1574       *                              be added.
1575       * @param  modifiedSchemaFiles  The names of the schema files containing
1576       *                              schema elements that have been updated as part
1577       *                              of the schema modification.
1578       *
1579       * @throws  DirectoryException  If a problem occurs while attempting to add
1580       *                              the provided attribute type to the server
1581       *                              schema.
1582       */
1583      private void addAttributeType(AttributeType attributeType, Schema schema,
1584                                    Set<String> modifiedSchemaFiles)
1585              throws DirectoryException
1586      {
1587        // First, see if the specified attribute type already exists.  We'll check
1588        // the OID and all of the names, which means that it's possible there could
1589        // be more than one match (although if there is, then we'll refuse the
1590        // operation).
1591        AttributeType existingType =
1592             schema.getAttributeType(attributeType.getOID());
1593        for (String name : attributeType.getNormalizedNames())
1594        {
1595          AttributeType t = schema.getAttributeType(name);
1596          if (t == null)
1597          {
1598            continue;
1599          }
1600          else if (existingType == null)
1601          {
1602            existingType = t;
1603          }
1604          else if (existingType != t)
1605          {
1606            // NOTE:  We really do want to use "!=" instead of "! t.equals()"
1607            // because we want to check whether it's the same object instance, not
1608            // just a logical equivalent.
1609            Message message = ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_ATTRTYPE.
1610                get(attributeType.getNameOrOID(), existingType.getNameOrOID(),
1611                    t.getNameOrOID());
1612            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1613          }
1614        }
1615    
1616    
1617        // Make sure that the new attribute type doesn't reference an undefined
1618        // or OBSOLETE superior attribute type.
1619        AttributeType superiorType = attributeType.getSuperiorType();
1620        if (superiorType != null)
1621        {
1622          if (! schema.hasAttributeType(superiorType.getOID()))
1623          {
1624            Message message = ERR_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_ATTRIBUTE_TYPE.
1625                get(attributeType.getNameOrOID(), superiorType.getNameOrOID());
1626            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1627          }
1628          else if (superiorType.isObsolete())
1629          {
1630            Message message = ERR_SCHEMA_MODIFY_OBSOLETE_SUPERIOR_ATTRIBUTE_TYPE.
1631                get(attributeType.getNameOrOID(), superiorType.getNameOrOID());
1632            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1633          }
1634        }
1635    
1636    
1637        // Make sure that none of the associated matching rules are marked OBSOLETE.
1638        MatchingRule mr = attributeType.getEqualityMatchingRule();
1639        if ((mr != null) && mr.isObsolete())
1640        {
1641          Message message = ERR_SCHEMA_MODIFY_ATTRTYPE_OBSOLETE_MR.get(
1642              attributeType.getNameOrOID(), mr.getNameOrOID());
1643          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1644        }
1645    
1646        mr = attributeType.getOrderingMatchingRule();
1647        if ((mr != null) && mr.isObsolete())
1648        {
1649          Message message = ERR_SCHEMA_MODIFY_ATTRTYPE_OBSOLETE_MR.get(
1650              attributeType.getNameOrOID(), mr.getNameOrOID());
1651          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1652        }
1653    
1654        mr = attributeType.getSubstringMatchingRule();
1655        if ((mr != null) && mr.isObsolete())
1656        {
1657          Message message = ERR_SCHEMA_MODIFY_ATTRTYPE_OBSOLETE_MR.get(
1658              attributeType.getNameOrOID(), mr.getNameOrOID());
1659          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1660        }
1661    
1662        mr = attributeType.getApproximateMatchingRule();
1663        if ((mr != null) && mr.isObsolete())
1664        {
1665          Message message = ERR_SCHEMA_MODIFY_ATTRTYPE_OBSOLETE_MR.get(
1666              attributeType.getNameOrOID(), mr.getNameOrOID());
1667          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1668        }
1669    
1670    
1671        // If there is no existing type, then we're adding a new attribute.
1672        // Otherwise, we're replacing an existing one.
1673        if (existingType == null)
1674        {
1675          schema.registerAttributeType(attributeType, false);
1676          String schemaFile = attributeType.getSchemaFile();
1677          if ((schemaFile == null) || (schemaFile.length() == 0))
1678          {
1679            schemaFile = FILE_USER_SCHEMA_ELEMENTS;
1680            attributeType.setSchemaFile(schemaFile);
1681          }
1682    
1683          modifiedSchemaFiles.add(schemaFile);
1684        }
1685        else
1686        {
1687          schema.deregisterAttributeType(existingType);
1688          schema.registerAttributeType(attributeType, false);
1689          schema.rebuildDependentElements(existingType);
1690    
1691          if ((attributeType.getSchemaFile() == null) ||
1692              (attributeType.getSchemaFile().length() == 0))
1693          {
1694            String schemaFile = existingType.getSchemaFile();
1695            if ((schemaFile == null) || (schemaFile.length() == 0))
1696            {
1697              schemaFile = FILE_USER_SCHEMA_ELEMENTS;
1698            }
1699    
1700            attributeType.setSchemaFile(schemaFile);
1701            modifiedSchemaFiles.add(schemaFile);
1702          }
1703          else
1704          {
1705            String newSchemaFile = attributeType.getSchemaFile();
1706            String oldSchemaFile = existingType.getSchemaFile();
1707            if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
1708            {
1709              modifiedSchemaFiles.add(newSchemaFile);
1710            }
1711            else
1712            {
1713              modifiedSchemaFiles.add(newSchemaFile);
1714              modifiedSchemaFiles.add(oldSchemaFile);
1715            }
1716          }
1717        }
1718      }
1719    
1720    
1721    
1722      /**
1723       * Handles all processing required to remove the provided attribute type from
1724       * the server schema, ensuring all other metadata is properly updated.  Note
1725       * that this method will first check to see whether the same attribute type
1726       * will be later added to the server schema with an updated definition, and if
1727       * so then the removal will be ignored because the later add will be handled
1728       * as a replace.  If the attribute type will not be replaced with a new
1729       * definition, then this method will ensure that there are no other schema
1730       * elements that depend on the attribute type before allowing it to be
1731       * removed.
1732       *
1733       * @param  attributeType        The attribute type to remove from the server
1734       *                              schema.
1735       * @param  schema               The schema from which the attribute type
1736       *                              should be removed.
1737       * @param  modifications        The full set of modifications to be processed
1738       *                              against the server schema.
1739       * @param  currentPosition      The position of the modification currently
1740       *                              being performed.
1741       * @param  modifiedSchemaFiles  The names of the schema files containing
1742       *                              schema elements that have been updated as part
1743       *                              of the schema modification.
1744       *
1745       * @throws  DirectoryException  If a problem occurs while attempting to remove
1746       *                              the provided attribute type from the server
1747       *                              schema.
1748       */
1749      private void removeAttributeType(AttributeType attributeType, Schema schema,
1750                                       ArrayList<Modification> modifications,
1751                                       int currentPosition,
1752                                       Set<String> modifiedSchemaFiles)
1753              throws DirectoryException
1754      {
1755        // See if the specified attribute type is actually defined in the server
1756        // schema.  If not, then fail.
1757        AttributeType removeType = schema.getAttributeType(attributeType.getOID());
1758        if ((removeType == null) || (! removeType.equals(attributeType)))
1759        {
1760          Message message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_ATTRIBUTE_TYPE.get(
1761              attributeType.getNameOrOID());
1762          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1763        }
1764    
1765    
1766        // See if there is another modification later to add the attribute type back
1767        // into the schema.  If so, then it's a replace and we should ignore the
1768        // remove because adding it back will handle the replace.
1769        for (int i=currentPosition+1; i < modifications.size(); i++)
1770        {
1771          Modification m = modifications.get(i);
1772          Attribute    a = m.getAttribute();
1773    
1774          if ((m.getModificationType() != ModificationType.ADD) ||
1775              (! a.getAttributeType().equals(attributeTypesType)))
1776          {
1777            continue;
1778          }
1779    
1780          for (AttributeValue v : a.getValues())
1781          {
1782            AttributeType at;
1783            try
1784            {
1785              at = AttributeTypeSyntax.decodeAttributeType(v.getValue(), schema,
1786                                                           true);
1787            }
1788            catch (DirectoryException de)
1789            {
1790              if (debugEnabled())
1791              {
1792                TRACER.debugCaught(DebugLogLevel.ERROR, de);
1793              }
1794    
1795              Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_ATTRTYPE.get(
1796                  v.getStringValue(), de.getMessageObject());
1797              throw new DirectoryException(
1798                             ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
1799                             de);
1800            }
1801    
1802            if (attributeType.getOID().equals(at.getOID()))
1803            {
1804              // We found a match where the attribute type is added back later, so
1805              // we don't need to do anything else here.
1806              return;
1807            }
1808          }
1809        }
1810    
1811    
1812        // Make sure that the attribute type isn't used as the superior type for
1813        // any other attributes.
1814        for (AttributeType at : schema.getAttributeTypes().values())
1815        {
1816          AttributeType superiorType = at.getSuperiorType();
1817          if ((superiorType != null) && superiorType.equals(removeType))
1818          {
1819            Message message = ERR_SCHEMA_MODIFY_REMOVE_AT_SUPERIOR_TYPE.get(
1820                removeType.getNameOrOID(), superiorType.getNameOrOID());
1821            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1822          }
1823        }
1824    
1825    
1826        // Make sure that the attribute type isn't used as a required or optional
1827        // attribute type in any objectclass.
1828        for (ObjectClass oc : schema.getObjectClasses().values())
1829        {
1830          if (oc.getRequiredAttributes().contains(removeType) ||
1831              oc.getOptionalAttributes().contains(removeType))
1832          {
1833            Message message = ERR_SCHEMA_MODIFY_REMOVE_AT_IN_OC.get(
1834                removeType.getNameOrOID(), oc.getNameOrOID());
1835            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1836          }
1837        }
1838    
1839    
1840        // Make sure that the attribute type isn't used as a required or optional
1841        // attribute type in any name form.
1842        for (NameForm nf : schema.getNameFormsByObjectClass().values())
1843        {
1844          if (nf.getRequiredAttributes().contains(removeType) ||
1845              nf.getOptionalAttributes().contains(removeType))
1846          {
1847            Message message = ERR_SCHEMA_MODIFY_REMOVE_AT_IN_NF.get(
1848                removeType.getNameOrOID(), nf.getNameOrOID());
1849            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1850          }
1851        }
1852    
1853    
1854        // Make sure that the attribute type isn't used as a required, optional, or
1855        // prohibited attribute type in any DIT content rule.
1856        for (DITContentRule dcr : schema.getDITContentRules().values())
1857        {
1858          if (dcr.getRequiredAttributes().contains(removeType) ||
1859              dcr.getOptionalAttributes().contains(removeType) ||
1860              dcr.getProhibitedAttributes().contains(removeType))
1861          {
1862            Message message = ERR_SCHEMA_MODIFY_REMOVE_AT_IN_DCR.get(
1863                removeType.getNameOrOID(), dcr.getName());
1864            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1865          }
1866        }
1867    
1868    
1869        // Make sure that the attribute type isn't referenced by any matching rule
1870        // use.
1871        for (MatchingRuleUse mru : schema.getMatchingRuleUses().values())
1872        {
1873          if (mru.getAttributes().contains(removeType))
1874          {
1875            Message message = ERR_SCHEMA_MODIFY_REMOVE_AT_IN_MR_USE.get(
1876                removeType.getNameOrOID(), mru.getName());
1877            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1878          }
1879        }
1880    
1881    
1882        // If we've gotten here, then it's OK to remove the attribute type from
1883        // the schema.
1884        schema.deregisterAttributeType(removeType);
1885        String schemaFile = removeType.getSchemaFile();
1886        if (schemaFile != null)
1887        {
1888          modifiedSchemaFiles.add(schemaFile);
1889        }
1890      }
1891    
1892    
1893    
1894      /**
1895       * Handles all processing required for adding the provided objectclass to the
1896       * given schema, replacing an existing class if necessary, and ensuring
1897       * all other metadata is properly updated.
1898       *
1899       * @param  objectClass          The objectclass to add or replace in the
1900       *                              server schema.
1901       * @param  schema               The schema to which the objectclass should be
1902       *                              added.
1903       * @param  modifiedSchemaFiles  The names of the schema files containing
1904       *                              schema elements that have been updated as part
1905       *                              of the schema modification.
1906       *
1907       * @throws  DirectoryException  If a problem occurs while attempting to add
1908       *                              the provided objectclass to the server schema.
1909       */
1910      private void addObjectClass(ObjectClass objectClass, Schema schema,
1911                                  Set<String> modifiedSchemaFiles)
1912              throws DirectoryException
1913      {
1914        // First, see if the specified objectclass already exists.  We'll check the
1915        // OID and all of the names, which means that it's possible there could be
1916        // more than one match (although if there is, then we'll refuse the
1917        // operation).
1918        ObjectClass existingClass =
1919             schema.getObjectClass(objectClass.getOID());
1920        for (String name : objectClass.getNormalizedNames())
1921        {
1922          ObjectClass oc = schema.getObjectClass(name);
1923          if (oc == null)
1924          {
1925            continue;
1926          }
1927          else if (existingClass == null)
1928          {
1929            existingClass = oc;
1930          }
1931          else if (existingClass != oc)
1932          {
1933            // NOTE:  We really do want to use "!=" instead of "! t.equals()"
1934            // because we want to check whether it's the same object instance, not
1935            // just a logical equivalent.
1936            Message message =
1937                    ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_OBJECTCLASS
1938                            .get(objectClass.getNameOrOID(),
1939                                    existingClass.getNameOrOID(),
1940                                    oc.getNameOrOID());
1941            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1942          }
1943        }
1944    
1945    
1946        // Make sure that the new objectclass doesn't reference an undefined
1947        // superior class, or an undefined required or optional attribute type,
1948        // and that none of them are OBSOLETE.
1949        ObjectClass superiorClass = objectClass.getSuperiorClass();
1950        if (superiorClass != null)
1951        {
1952          if (! schema.hasObjectClass(superiorClass.getOID()))
1953          {
1954            Message message = ERR_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_OBJECTCLASS.get(
1955                objectClass.getNameOrOID(), superiorClass.getNameOrOID());
1956            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1957          }
1958          else if (superiorClass.isObsolete())
1959          {
1960            Message message = ERR_SCHEMA_MODIFY_OBSOLETE_SUPERIOR_OBJECTCLASS.get(
1961                objectClass.getNameOrOID(), superiorClass.getNameOrOID());
1962            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1963          }
1964        }
1965    
1966        for (AttributeType at : objectClass.getRequiredAttributes())
1967        {
1968          if (! schema.hasAttributeType(at.getOID()))
1969          {
1970            Message message = ERR_SCHEMA_MODIFY_OC_UNDEFINED_REQUIRED_ATTR.get(
1971                objectClass.getNameOrOID(), at.getNameOrOID());
1972            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1973          }
1974          else if (at.isObsolete())
1975          {
1976            Message message = ERR_SCHEMA_MODIFY_OC_OBSOLETE_REQUIRED_ATTR.get(
1977                objectClass.getNameOrOID(), at.getNameOrOID());
1978            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1979          }
1980        }
1981    
1982        for (AttributeType at : objectClass.getOptionalAttributes())
1983        {
1984          if (! schema.hasAttributeType(at.getOID()))
1985          {
1986            Message message = ERR_SCHEMA_MODIFY_OC_UNDEFINED_OPTIONAL_ATTR.get(
1987                objectClass.getNameOrOID(), at.getNameOrOID());
1988            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1989          }
1990          else if (at.isObsolete())
1991          {
1992            Message message = ERR_SCHEMA_MODIFY_OC_OBSOLETE_OPTIONAL_ATTR.get(
1993                objectClass.getNameOrOID(), at.getNameOrOID());
1994            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1995          }
1996        }
1997    
1998    
1999        // If there is no existing class, then we're adding a new objectclass.
2000        // Otherwise, we're replacing an existing one.
2001        if (existingClass == null)
2002        {
2003          schema.registerObjectClass(objectClass, false);
2004          String schemaFile = objectClass.getSchemaFile();
2005          if ((schemaFile == null) || (schemaFile.length() == 0))
2006          {
2007            schemaFile = FILE_USER_SCHEMA_ELEMENTS;
2008            objectClass.setSchemaFile(schemaFile);
2009          }
2010    
2011          modifiedSchemaFiles.add(schemaFile);
2012        }
2013        else
2014        {
2015          schema.deregisterObjectClass(existingClass);
2016          schema.registerObjectClass(objectClass, false);
2017          schema.rebuildDependentElements(existingClass);
2018    
2019          if ((objectClass.getSchemaFile() == null) ||
2020              (objectClass.getSchemaFile().length() == 0))
2021          {
2022            String schemaFile = existingClass.getSchemaFile();
2023            if ((schemaFile == null) || (schemaFile.length() == 0))
2024            {
2025              schemaFile = FILE_USER_SCHEMA_ELEMENTS;
2026            }
2027    
2028            objectClass.setSchemaFile(schemaFile);
2029            modifiedSchemaFiles.add(schemaFile);
2030          }
2031          else
2032          {
2033            String newSchemaFile = objectClass.getSchemaFile();
2034            String oldSchemaFile = existingClass.getSchemaFile();
2035            if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
2036            {
2037              modifiedSchemaFiles.add(newSchemaFile);
2038            }
2039            else
2040            {
2041              modifiedSchemaFiles.add(newSchemaFile);
2042              modifiedSchemaFiles.add(oldSchemaFile);
2043            }
2044          }
2045        }
2046      }
2047    
2048    
2049    
2050      /**
2051       * Handles all processing required to remove the provided objectclass from the
2052       * server schema, ensuring all other metadata is properly updated.  Note that
2053       * this method will first check to see whether the same objectclass will be
2054       * later added to the server schema with an updated definition, and if so then
2055       * the removal will be ignored because the later add will be handled as a
2056       * replace.  If the objectclass will not be replaced with a new definition,
2057       * then this method will ensure that there are no other schema elements that
2058       * depend on the objectclass before allowing it to be removed.
2059       *
2060       * @param  objectClass          The objectclass to remove from the server
2061       *                              schema.
2062       * @param  schema               The schema from which the objectclass should
2063       *                              be removed.
2064       * @param  modifications        The full set of modifications to be processed
2065       *                              against the server schema.
2066       * @param  currentPosition      The position of the modification currently
2067       *                              being performed.
2068       * @param  modifiedSchemaFiles  The names of the schema files containing
2069       *                              schema elements that have been updated as part
2070       *                              of the schema modification.
2071       *
2072       * @throws  DirectoryException  If a problem occurs while attempting to remove
2073       *                              the provided objectclass from the server
2074       *                              schema.
2075       */
2076      private void removeObjectClass(ObjectClass objectClass, Schema schema,
2077                                     ArrayList<Modification> modifications,
2078                                     int currentPosition,
2079                                     Set<String> modifiedSchemaFiles)
2080              throws DirectoryException
2081      {
2082        // See if the specified objectclass is actually defined in the server
2083        // schema.  If not, then fail.
2084        ObjectClass removeClass = schema.getObjectClass(objectClass.getOID());
2085        if ((removeClass == null) || (! removeClass.equals(objectClass)))
2086        {
2087          Message message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_OBJECTCLASS.get(
2088              objectClass.getNameOrOID());
2089          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2090        }
2091    
2092    
2093        // See if there is another modification later to add the objectclass back
2094        // into the schema.  If so, then it's a replace and we should ignore the
2095        // remove because adding it back will handle the replace.
2096        for (int i=currentPosition+1; i < modifications.size(); i++)
2097        {
2098          Modification m = modifications.get(i);
2099          Attribute    a = m.getAttribute();
2100    
2101          if ((m.getModificationType() != ModificationType.ADD) ||
2102              (! a.getAttributeType().equals(objectClassesType)))
2103          {
2104            continue;
2105          }
2106    
2107          for (AttributeValue v : a.getValues())
2108          {
2109            ObjectClass oc;
2110            try
2111            {
2112              oc = ObjectClassSyntax.decodeObjectClass(v.getValue(), schema, true);
2113            }
2114            catch (DirectoryException de)
2115            {
2116              if (debugEnabled())
2117              {
2118                TRACER.debugCaught(DebugLogLevel.ERROR, de);
2119              }
2120    
2121              Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS.get(
2122                  v.getStringValue(), de.getMessageObject());
2123              throw new DirectoryException(
2124                             ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
2125                             de);
2126            }
2127    
2128            if (objectClass.getOID().equals(oc.getOID()))
2129            {
2130              // We found a match where the objectClass is added back later, so we
2131              // don't need to do anything else here.
2132              return;
2133            }
2134          }
2135        }
2136    
2137    
2138        // Make sure that the objectclass isn't used as the superior class for any
2139        // other objectclass.
2140        for (ObjectClass oc : schema.getObjectClasses().values())
2141        {
2142          ObjectClass superiorClass = oc.getSuperiorClass();
2143          if ((superiorClass != null) && superiorClass.equals(removeClass))
2144          {
2145            Message message = ERR_SCHEMA_MODIFY_REMOVE_OC_SUPERIOR_CLASS.get(
2146                removeClass.getNameOrOID(), superiorClass.getNameOrOID());
2147            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2148          }
2149        }
2150    
2151    
2152        // Make sure that the objectclass isn't used as the structural class for
2153        // any name form.
2154        NameForm nf = schema.getNameForm(removeClass);
2155        if (nf != null)
2156        {
2157          Message message = ERR_SCHEMA_MODIFY_REMOVE_OC_IN_NF.get(
2158              removeClass.getNameOrOID(), nf.getNameOrOID());
2159          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2160        }
2161    
2162    
2163        // Make sure that the objectclass isn't used as a structural or auxiliary
2164        // class for any DIT content rule.
2165        for (DITContentRule dcr : schema.getDITContentRules().values())
2166        {
2167          if (dcr.getStructuralClass().equals(removeClass) ||
2168              dcr.getAuxiliaryClasses().contains(removeClass))
2169          {
2170            Message message = ERR_SCHEMA_MODIFY_REMOVE_OC_IN_DCR.get(
2171                removeClass.getNameOrOID(), dcr.getName());
2172            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2173          }
2174        }
2175    
2176    
2177        // If we've gotten here, then it's OK to remove the objectclass from the
2178        // schema.
2179        schema.deregisterObjectClass(removeClass);
2180        String schemaFile = removeClass.getSchemaFile();
2181        if (schemaFile != null)
2182        {
2183          modifiedSchemaFiles.add(schemaFile);
2184        }
2185      }
2186    
2187    
2188    
2189      /**
2190       * Handles all processing required for adding the provided name form to the
2191       * the given schema, replacing an existing name form if necessary, and
2192       * ensuring all other metadata is properly updated.
2193       *
2194       * @param  nameForm             The name form to add or replace in the server
2195       *                              schema.
2196       * @param  schema               The schema to which the name form should be
2197       *                              added.
2198       * @param  modifiedSchemaFiles  The names of the schema files containing
2199       *                              schema elements that have been updated as part
2200       *                              of the schema modification.
2201       *
2202       * @throws  DirectoryException  If a problem occurs while attempting to add
2203       *                              the provided name form to the server schema.
2204       */
2205      private void addNameForm(NameForm nameForm, Schema schema,
2206                               Set<String> modifiedSchemaFiles)
2207              throws DirectoryException
2208      {
2209        // First, see if the specified name form already exists.  We'll check the
2210        // OID and all of the names, which means that it's possible there could be
2211        // more than one match (although if there is, then we'll refuse the
2212        // operation).
2213        NameForm existingNF =
2214             schema.getNameForm(nameForm.getOID());
2215        for (String name : nameForm.getNames().keySet())
2216        {
2217          NameForm nf = schema.getNameForm(name);
2218          if (nf == null)
2219          {
2220            continue;
2221          }
2222          else if (existingNF == null)
2223          {
2224            existingNF = nf;
2225          }
2226          else if (existingNF != nf)
2227          {
2228            // NOTE:  We really do want to use "!=" instead of "! t.equals()"
2229            // because we want to check whether it's the same object instance, not
2230            // just a logical equivalent.
2231            Message message =
2232                    ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_NAME_FORM
2233                            .get(nameForm.getNameOrOID(), existingNF.getNameOrOID(),
2234                      nf.getNameOrOID());
2235            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2236          }
2237        }
2238    
2239    
2240        // Make sure that the new name form doesn't reference an undefined
2241        // structural class, or an undefined required or optional attribute type, or
2242        // that any of them are marked OBSOLETE.
2243        ObjectClass structuralClass = nameForm.getStructuralClass();
2244        if (! schema.hasObjectClass(structuralClass.getOID()))
2245        {
2246          Message message = ERR_SCHEMA_MODIFY_NF_UNDEFINED_STRUCTURAL_OC.get(
2247              nameForm.getNameOrOID(), structuralClass.getNameOrOID());
2248          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2249        }
2250        if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL)
2251        {
2252          Message message = ERR_SCHEMA_MODIFY_NF_OC_NOT_STRUCTURAL.get(
2253              nameForm.getNameOrOID(), structuralClass.getNameOrOID());
2254          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2255        }
2256        if (structuralClass.isObsolete())
2257        {
2258          Message message = ERR_SCHEMA_MODIFY_NF_OC_OBSOLETE.get(
2259              nameForm.getNameOrOID(), structuralClass.getNameOrOID());
2260          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2261        }
2262    
2263        NameForm existingNFForClass = schema.getNameForm(structuralClass);
2264        if ((existingNFForClass != null) && (existingNFForClass != existingNF))
2265        {
2266          Message message = ERR_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_NF.
2267              get(nameForm.getNameOrOID(), structuralClass.getNameOrOID(),
2268                  existingNFForClass.getNameOrOID());
2269          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2270        }
2271    
2272        for (AttributeType at : nameForm.getRequiredAttributes())
2273        {
2274          if (! schema.hasAttributeType(at.getOID()))
2275          {
2276            Message message = ERR_SCHEMA_MODIFY_NF_UNDEFINED_REQUIRED_ATTR.get(
2277                nameForm.getNameOrOID(), at.getNameOrOID());
2278            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2279          }
2280          else if (at.isObsolete())
2281          {
2282            Message message = ERR_SCHEMA_MODIFY_NF_OBSOLETE_REQUIRED_ATTR.get(
2283                nameForm.getNameOrOID(), at.getNameOrOID());
2284            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
2285          }
2286        }
2287    
2288        for (AttributeType at : nameForm.getOptionalAttributes())
2289        {
2290          if (! schema.hasAttributeType(at.getOID()))
2291          {
2292            Message message = ERR_SCHEMA_MODIFY_NF_UNDEFINED_OPTIONAL_ATTR.get(
2293                nameForm.getNameOrOID(), at.getNameOrOID());
2294            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2295          }
2296          else if (at.isObsolete())
2297          {
2298            Message message = ERR_SCHEMA_MODIFY_NF_OBSOLETE_OPTIONAL_ATTR.get(
2299                nameForm.getNameOrOID(), at.getNameOrOID());
2300            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
2301          }
2302        }
2303    
2304    
2305        // If there is no existing class, then we're adding a new name form.
2306        // Otherwise, we're replacing an existing one.
2307        if (existingNF == null)
2308        {
2309          schema.registerNameForm(nameForm, false);
2310          String schemaFile = nameForm.getSchemaFile();
2311          if ((schemaFile == null) || (schemaFile.length() == 0))
2312          {
2313            schemaFile = FILE_USER_SCHEMA_ELEMENTS;
2314            nameForm.setSchemaFile(schemaFile);
2315          }
2316    
2317          modifiedSchemaFiles.add(schemaFile);
2318        }
2319        else
2320        {
2321          schema.deregisterNameForm(existingNF);
2322          schema.registerNameForm(nameForm, false);
2323          schema.rebuildDependentElements(existingNF);
2324    
2325          if ((nameForm.getSchemaFile() == null) ||
2326              (nameForm.getSchemaFile().length() == 0))
2327          {
2328            String schemaFile = existingNF.getSchemaFile();
2329            if ((schemaFile == null) || (schemaFile.length() == 0))
2330            {
2331              schemaFile = FILE_USER_SCHEMA_ELEMENTS;
2332            }
2333    
2334            nameForm.setSchemaFile(schemaFile);
2335            modifiedSchemaFiles.add(schemaFile);
2336          }
2337          else
2338          {
2339            String newSchemaFile = nameForm.getSchemaFile();
2340            String oldSchemaFile = existingNF.getSchemaFile();
2341            if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
2342            {
2343              modifiedSchemaFiles.add(newSchemaFile);
2344            }
2345            else
2346            {
2347              modifiedSchemaFiles.add(newSchemaFile);
2348              modifiedSchemaFiles.add(oldSchemaFile);
2349            }
2350          }
2351        }
2352      }
2353    
2354    
2355    
2356      /**
2357       * Handles all processing required to remove the provided name form from the
2358       * server schema, ensuring all other metadata is properly updated.  Note that
2359       * this method will first check to see whether the same name form will be
2360       * later added to the server schema with an updated definition, and if so then
2361       * the removal will be ignored because the later add will be handled as a
2362       * replace.  If the name form will not be replaced with a new definition, then
2363       * this method will ensure that there are no other schema elements that depend
2364       * on the name form before allowing it to be removed.
2365       *
2366       * @param  nameForm             The name form to remove from the server
2367       *                              schema.
2368       * @param  schema               The schema from which the name form should be
2369       *                              be removed.
2370       * @param  modifications        The full set of modifications to be processed
2371       *                              against the server schema.
2372       * @param  currentPosition      The position of the modification currently
2373       *                              being performed.
2374       * @param  modifiedSchemaFiles  The names of the schema files containing
2375       *                              schema elements that have been updated as part
2376       *                              of the schema modification.
2377       *
2378       * @throws  DirectoryException  If a problem occurs while attempting to remove
2379       *                              the provided name form from the server schema.
2380       */
2381      private void removeNameForm(NameForm nameForm, Schema schema,
2382                                  ArrayList<Modification> modifications,
2383                                  int currentPosition,
2384                                  Set<String> modifiedSchemaFiles)
2385              throws DirectoryException
2386      {
2387        // See if the specified name form is actually defined in the server schema.
2388        // If not, then fail.
2389        NameForm removeNF = schema.getNameForm(nameForm.getOID());
2390        if ((removeNF == null) || (! removeNF.equals(nameForm)))
2391        {
2392          Message message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_NAME_FORM.get(
2393              nameForm.getNameOrOID());
2394          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2395        }
2396    
2397    
2398        // See if there is another modification later to add the name form back
2399        // into the schema.  If so, then it's a replace and we should ignore the
2400        // remove because adding it back will handle the replace.
2401        for (int i=currentPosition+1; i < modifications.size(); i++)
2402        {
2403          Modification m = modifications.get(i);
2404          Attribute    a = m.getAttribute();
2405    
2406          if ((m.getModificationType() != ModificationType.ADD) ||
2407              (! a.getAttributeType().equals(nameFormsType)))
2408          {
2409            continue;
2410          }
2411    
2412          for (AttributeValue v : a.getValues())
2413          {
2414            NameForm nf;
2415            try
2416            {
2417              nf = NameFormSyntax.decodeNameForm(v.getValue(), schema, true);
2418            }
2419            catch (DirectoryException de)
2420            {
2421              if (debugEnabled())
2422              {
2423                TRACER.debugCaught(DebugLogLevel.ERROR, de);
2424              }
2425    
2426              Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM.get(
2427                  v.getStringValue(), de.getMessageObject());
2428              throw new DirectoryException(
2429                             ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
2430                             de);
2431            }
2432    
2433            if (nameForm.getOID().equals(nf.getOID()))
2434            {
2435              // We found a match where the name form is added back later, so we
2436              // don't need to do anything else here.
2437              return;
2438            }
2439          }
2440        }
2441    
2442    
2443        // Make sure that the name form isn't referenced by any DIT structure
2444        // rule.
2445        DITStructureRule dsr = schema.getDITStructureRule(removeNF);
2446        if (dsr != null)
2447        {
2448          Message message = ERR_SCHEMA_MODIFY_REMOVE_NF_IN_DSR.get(
2449              removeNF.getNameOrOID(), dsr.getNameOrRuleID());
2450          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2451        }
2452    
2453    
2454        // If we've gotten here, then it's OK to remove the name form from the
2455        // schema.
2456        schema.deregisterNameForm(removeNF);
2457        String schemaFile = removeNF.getSchemaFile();
2458        if (schemaFile != null)
2459        {
2460          modifiedSchemaFiles.add(schemaFile);
2461        }
2462      }
2463    
2464    
2465    
2466      /**
2467       * Handles all processing required for adding the provided DIT content rule to
2468       * the given schema, replacing an existing rule if necessary, and ensuring
2469       * all other metadata is properly updated.
2470       *
2471       * @param  ditContentRule       The DIT content rule to add or replace in the
2472       *                              server schema.
2473       * @param  schema               The schema to which the DIT content rule
2474       *                              should be be added.
2475       * @param  modifiedSchemaFiles  The names of the schema files containing
2476       *                              schema elements that have been updated as part
2477       *                              of the schema modification.
2478       *
2479       * @throws  DirectoryException  If a problem occurs while attempting to add
2480       *                              the provided DIT content rule to the server
2481       *                              schema.
2482       */
2483      private void addDITContentRule(DITContentRule ditContentRule, Schema schema,
2484                                     Set<String> modifiedSchemaFiles)
2485              throws DirectoryException
2486      {
2487        // First, see if the specified DIT content rule already exists.  We'll check
2488        // all of the names, which means that it's possible there could be more than
2489        // one match (although if there is, then we'll refuse the operation).
2490        DITContentRule existingDCR = null;
2491        for (DITContentRule dcr : schema.getDITContentRules().values())
2492        {
2493          for (String name : ditContentRule.getNames().keySet())
2494          {
2495            if (dcr.hasName(name))
2496            {
2497              if (existingDCR == null)
2498              {
2499                existingDCR = dcr;
2500                break;
2501              }
2502              else
2503              {
2504                Message message = ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DCR.
2505                    get(ditContentRule.getName(), existingDCR.getName(),
2506                        dcr.getName());
2507                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2508                                             message);
2509              }
2510            }
2511          }
2512        }
2513    
2514    
2515        // Get the structural class for the new DIT content rule and see if there's
2516        // already an existing rule that is associated with that class.  If there
2517        // is, then it will only be acceptable if it's the DIT content rule that we
2518        // are replacing (in which case we really do want to use the "!=" operator).
2519        ObjectClass structuralClass = ditContentRule.getStructuralClass();
2520        DITContentRule existingRuleForClass =
2521             schema.getDITContentRule(structuralClass);
2522        if ((existingRuleForClass != null) && (existingRuleForClass != existingDCR))
2523        {
2524          Message message = ERR_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_DCR.
2525              get(ditContentRule.getName(), structuralClass.getNameOrOID(),
2526                  existingRuleForClass.getName());
2527          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2528        }
2529    
2530    
2531        // Make sure that the new DIT content rule doesn't reference an undefined
2532        // structural or auxiliaryclass, or an undefined required, optional, or
2533        // prohibited attribute type.
2534        if (! schema.hasObjectClass(structuralClass.getOID()))
2535        {
2536          Message message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_STRUCTURAL_OC.get(
2537              ditContentRule.getName(), structuralClass.getNameOrOID());
2538          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2539        }
2540    
2541        if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL)
2542        {
2543          Message message = ERR_SCHEMA_MODIFY_DCR_OC_NOT_STRUCTURAL.get(
2544              ditContentRule.getName(), structuralClass.getNameOrOID());
2545          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2546        }
2547    
2548        if (structuralClass.isObsolete())
2549        {
2550          Message message = ERR_SCHEMA_MODIFY_DCR_STRUCTURAL_OC_OBSOLETE.get(
2551              ditContentRule.getName(), structuralClass.getNameOrOID());
2552          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
2553        }
2554    
2555        for (ObjectClass oc : ditContentRule.getAuxiliaryClasses())
2556        {
2557          if (! schema.hasObjectClass(oc.getOID()))
2558          {
2559            Message message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_AUXILIARY_OC.get(
2560                ditContentRule.getName(), oc.getNameOrOID());
2561            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2562          }
2563          if (oc.getObjectClassType() != ObjectClassType.AUXILIARY)
2564          {
2565            Message message = ERR_SCHEMA_MODIFY_DCR_OC_NOT_AUXILIARY.get(
2566                ditContentRule.getName(), oc.getNameOrOID());
2567            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2568          }
2569          if (oc.isObsolete())
2570          {
2571            Message message = ERR_SCHEMA_MODIFY_DCR_OBSOLETE_AUXILIARY_OC.get(
2572                ditContentRule.getName(), oc.getNameOrOID());
2573            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2574          }
2575        }
2576    
2577        for (AttributeType at : ditContentRule.getRequiredAttributes())
2578        {
2579          if (! schema.hasAttributeType(at.getOID()))
2580          {
2581            Message message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_REQUIRED_ATTR.get(
2582                ditContentRule.getName(), at.getNameOrOID());
2583            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2584          }
2585          else if (at.isObsolete())
2586          {
2587            Message message = ERR_SCHEMA_MODIFY_DCR_OBSOLETE_REQUIRED_ATTR.get(
2588                ditContentRule.getName(), at.getNameOrOID());
2589            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
2590          }
2591        }
2592    
2593        for (AttributeType at : ditContentRule.getOptionalAttributes())
2594        {
2595          if (! schema.hasAttributeType(at.getOID()))
2596          {
2597            Message message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_OPTIONAL_ATTR.get(
2598                ditContentRule.getName(), at.getNameOrOID());
2599            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2600          }
2601          else if (at.isObsolete())
2602          {
2603            Message message = ERR_SCHEMA_MODIFY_DCR_OBSOLETE_OPTIONAL_ATTR.get(
2604                ditContentRule.getName(), at.getNameOrOID());
2605            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
2606          }
2607        }
2608    
2609        for (AttributeType at : ditContentRule.getProhibitedAttributes())
2610        {
2611          if (! schema.hasAttributeType(at.getOID()))
2612          {
2613            Message message = ERR_SCHEMA_MODIFY_DCR_UNDEFINED_PROHIBITED_ATTR.get(
2614                ditContentRule.getName(), at.getNameOrOID());
2615            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2616          }
2617          else if (at.isObsolete())
2618          {
2619            Message message = ERR_SCHEMA_MODIFY_DCR_OBSOLETE_PROHIBITED_ATTR.get(
2620                ditContentRule.getName(), at.getNameOrOID());
2621            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
2622          }
2623        }
2624    
2625    
2626        // If there is no existing rule, then we're adding a new DIT content rule.
2627        // Otherwise, we're replacing an existing one.
2628        if (existingDCR == null)
2629        {
2630          schema.registerDITContentRule(ditContentRule, false);
2631          String schemaFile = ditContentRule.getSchemaFile();
2632          if ((schemaFile == null) || (schemaFile.length() == 0))
2633          {
2634            schemaFile = FILE_USER_SCHEMA_ELEMENTS;
2635            ditContentRule.setSchemaFile(schemaFile);
2636          }
2637    
2638          modifiedSchemaFiles.add(schemaFile);
2639        }
2640        else
2641        {
2642          schema.deregisterDITContentRule(existingDCR);
2643          schema.registerDITContentRule(ditContentRule, false);
2644          schema.rebuildDependentElements(existingDCR);
2645    
2646          if ((ditContentRule.getSchemaFile() == null) ||
2647              (ditContentRule.getSchemaFile().length() == 0))
2648          {
2649            String schemaFile = existingDCR.getSchemaFile();
2650            if ((schemaFile == null) || (schemaFile.length() == 0))
2651            {
2652              schemaFile = FILE_USER_SCHEMA_ELEMENTS;
2653            }
2654    
2655            ditContentRule.setSchemaFile(schemaFile);
2656            modifiedSchemaFiles.add(schemaFile);
2657          }
2658          else
2659          {
2660            String newSchemaFile = ditContentRule.getSchemaFile();
2661            String oldSchemaFile = existingDCR.getSchemaFile();
2662            if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
2663            {
2664              modifiedSchemaFiles.add(newSchemaFile);
2665            }
2666            else
2667            {
2668              modifiedSchemaFiles.add(newSchemaFile);
2669              modifiedSchemaFiles.add(oldSchemaFile);
2670            }
2671          }
2672        }
2673      }
2674    
2675    
2676    
2677      /**
2678       * Handles all processing required to remove the provided DIT content rule
2679       * from the server schema, ensuring all other metadata is properly updated.
2680       * Note that this method will first check to see whether the same rule will be
2681       * later added to the server schema with an updated definition, and if so then
2682       * the removal will be ignored because the later add will be handled as a
2683       * replace.  If the DIT content rule will not be replaced with a new
2684       * definition, then this method will ensure that there are no other schema
2685       * elements that depend on the rule before allowing it to be removed.
2686       *
2687       * @param  ditContentRule       The DIT content rule to remove from the server
2688       *                              schema.
2689       * @param  schema               The schema from which the DIT content rule
2690       *                              should be removed.
2691       * @param  modifications        The full set of modifications to be processed
2692       *                              against the server schema.
2693       * @param  currentPosition      The position of the modification currently
2694       *                              being performed.
2695       * @param  modifiedSchemaFiles  The names of the schema files containing
2696       *                              schema elements that have been updated as part
2697       *                              of the schema modification.
2698       *
2699       * @throws  DirectoryException  If a problem occurs while attempting to remove
2700       *                              the provided DIT content rule from the server
2701       *                              schema.
2702       */
2703      private void removeDITContentRule(DITContentRule ditContentRule,
2704                                        Schema schema,
2705                                        ArrayList<Modification> modifications,
2706                                        int currentPosition,
2707                                        Set<String> modifiedSchemaFiles)
2708              throws DirectoryException
2709      {
2710        // See if the specified DIT content rule is actually defined in the server
2711        // schema.  If not, then fail.
2712        DITContentRule removeDCR =
2713             schema.getDITContentRule(ditContentRule.getStructuralClass());
2714        if ((removeDCR == null) || (! removeDCR.equals(ditContentRule)))
2715        {
2716          Message message =
2717              ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_DCR.get(ditContentRule.getName());
2718          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2719        }
2720    
2721    
2722        // Since DIT content rules don't have any dependencies, then we don't need
2723        // to worry about the difference between a remove or a replace.  We can
2724        // just remove the DIT content rule now, and if it is added back later then
2725        // there still won't be any conflict.
2726        schema.deregisterDITContentRule(removeDCR);
2727        String schemaFile = removeDCR.getSchemaFile();
2728        if (schemaFile != null)
2729        {
2730          modifiedSchemaFiles.add(schemaFile);
2731        }
2732      }
2733    
2734    
2735    
2736      /**
2737       * Handles all processing required for adding the provided DIT structure rule
2738       * to the given schema, replacing an existing rule if necessary, and ensuring
2739       * all other metadata is properly updated.
2740       *
2741       * @param  ditStructureRule     The DIT structure rule to add or replace in
2742       *                              the server schema.
2743       * @param  schema               The schema to which the DIT structure rule
2744       *                              should be be added.
2745       * @param  modifiedSchemaFiles  The names of the schema files containing
2746       *                              schema elements that have been updated as part
2747       *                              of the schema modification.
2748       *
2749       * @throws  DirectoryException  If a problem occurs while attempting to add
2750       *                              the provided DIT structure rule to the server
2751       *                              schema.
2752       */
2753      private void addDITStructureRule(DITStructureRule ditStructureRule,
2754                                       Schema schema,
2755                                       Set<String> modifiedSchemaFiles)
2756              throws DirectoryException
2757      {
2758        // First, see if the specified DIT structure rule already exists.  We'll
2759        // check the rule ID and all of the names, which means that it's possible
2760        // there could be more than one match (although if there is, then we'll
2761        // refuse the operation).
2762        DITStructureRule existingDSR =
2763             schema.getDITStructureRule(ditStructureRule.getRuleID());
2764        for (DITStructureRule dsr : schema.getDITStructureRulesByID().values())
2765        {
2766          for (String name : ditStructureRule.getNames().keySet())
2767          {
2768            if (dsr.hasName(name))
2769            {
2770              // We really do want to use the "!=" operator here because it's
2771              // acceptable if we find match for the same object instance.
2772              if ((existingDSR != null) && (existingDSR != dsr))
2773              {
2774                Message message = ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DSR.
2775                    get(ditStructureRule.getNameOrRuleID(),
2776                        existingDSR.getNameOrRuleID(), dsr.getNameOrRuleID());
2777                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2778                                             message);
2779              }
2780            }
2781          }
2782        }
2783    
2784    
2785        // Get the name form for the new DIT structure rule and see if there's
2786        // already an existing rule that is associated with that name form.  If
2787        // there is, then it will only be acceptable if it's the DIT structure rule
2788        // that we are replacing (in which case we really do want to use the "!="
2789        // operator).
2790        NameForm nameForm = ditStructureRule.getNameForm();
2791        DITStructureRule existingRuleForNameForm =
2792             schema.getDITStructureRule(nameForm);
2793        if ((existingRuleForNameForm != null) &&
2794            (existingRuleForNameForm != existingDSR))
2795        {
2796          Message message = ERR_SCHEMA_MODIFY_NAME_FORM_CONFLICT_FOR_ADD_DSR.
2797              get(ditStructureRule.getNameOrRuleID(), nameForm.getNameOrOID(),
2798                  existingRuleForNameForm.getNameOrRuleID());
2799          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2800        }
2801    
2802    
2803        // Make sure that the new DIT structure rule doesn't reference an undefined
2804        // name form or superior DIT structure rule.
2805        if (! schema.hasNameForm(nameForm.getOID()))
2806        {
2807          Message message = ERR_SCHEMA_MODIFY_DSR_UNDEFINED_NAME_FORM.get(
2808              ditStructureRule.getNameOrRuleID(), nameForm.getNameOrOID());
2809          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2810        }
2811        if (nameForm.isObsolete())
2812        {
2813          Message message = ERR_SCHEMA_MODIFY_DSR_OBSOLETE_NAME_FORM.get(
2814              ditStructureRule.getNameOrRuleID(), nameForm.getNameOrOID());
2815          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
2816        }
2817    
2818    
2819        // If there are any superior rules, then make sure none of them are marked
2820        // OBSOLETE.
2821        for (DITStructureRule dsr : ditStructureRule.getSuperiorRules())
2822        {
2823          if (dsr.isObsolete())
2824          {
2825            Message message = ERR_SCHEMA_MODIFY_DSR_OBSOLETE_SUPERIOR_RULE.get(
2826                ditStructureRule.getNameOrRuleID(), dsr.getNameOrRuleID());
2827            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
2828          }
2829        }
2830    
2831    
2832        // If there is no existing rule, then we're adding a new DIT structure rule.
2833        // Otherwise, we're replacing an existing one.
2834        if (existingDSR == null)
2835        {
2836          schema.registerDITStructureRule(ditStructureRule, false);
2837          String schemaFile = ditStructureRule.getSchemaFile();
2838          if ((schemaFile == null) || (schemaFile.length() == 0))
2839          {
2840            schemaFile = FILE_USER_SCHEMA_ELEMENTS;
2841            ditStructureRule.setSchemaFile(schemaFile);
2842          }
2843    
2844          modifiedSchemaFiles.add(schemaFile);
2845        }
2846        else
2847        {
2848          schema.deregisterDITStructureRule(existingDSR);
2849          schema.registerDITStructureRule(ditStructureRule, false);
2850          schema.rebuildDependentElements(existingDSR);
2851    
2852          if ((ditStructureRule.getSchemaFile() == null) ||
2853              (ditStructureRule.getSchemaFile().length() == 0))
2854          {
2855            String schemaFile = existingDSR.getSchemaFile();
2856            if ((schemaFile == null) || (schemaFile.length() == 0))
2857            {
2858              schemaFile = FILE_USER_SCHEMA_ELEMENTS;
2859            }
2860    
2861            ditStructureRule.setSchemaFile(schemaFile);
2862            modifiedSchemaFiles.add(schemaFile);
2863          }
2864          else
2865          {
2866            String newSchemaFile = ditStructureRule.getSchemaFile();
2867            String oldSchemaFile = existingDSR.getSchemaFile();
2868            if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
2869            {
2870              modifiedSchemaFiles.add(newSchemaFile);
2871            }
2872            else
2873            {
2874              modifiedSchemaFiles.add(newSchemaFile);
2875              modifiedSchemaFiles.add(oldSchemaFile);
2876            }
2877          }
2878        }
2879      }
2880    
2881    
2882    
2883      /**
2884       * Handles all processing required to remove the provided DIT structure rule
2885       * from the server schema, ensuring all other metadata is properly updated.
2886       * Note that this method will first check to see whether the same rule will be
2887       * later added to the server schema with an updated definition, and if so then
2888       * the removal will be ignored because the later add will be handled as a
2889       * replace.  If the DIT structure rule will not be replaced with a new
2890       * definition, then this method will ensure that there are no other schema
2891       * elements that depend on the rule before allowing it to be removed.
2892       *
2893       * @param  ditStructureRule     The DIT structure rule to remove from the
2894       *                              server schema.
2895       * @param  schema               The schema from which the DIT structure rule
2896       *                              should be removed.
2897       * @param  modifications        The full set of modifications to be processed
2898       *                              against the server schema.
2899       * @param  currentPosition      The position of the modification currently
2900       *                              being performed.
2901       * @param  modifiedSchemaFiles  The names of the schema files containing
2902       *                              schema elements that have been updated as part
2903       *                              of the schema modification.
2904       *
2905       * @throws  DirectoryException  If a problem occurs while attempting to remove
2906       *                              the provided DIT structure rule from the
2907       *                              server schema.
2908       */
2909      private void removeDITStructureRule(DITStructureRule ditStructureRule,
2910                                          Schema schema,
2911                                          ArrayList<Modification> modifications,
2912                                          int currentPosition,
2913                                          Set<String> modifiedSchemaFiles)
2914              throws DirectoryException
2915      {
2916        // See if the specified DIT structure rule is actually defined in the server
2917        // schema.  If not, then fail.
2918        DITStructureRule removeDSR =
2919             schema.getDITStructureRule(ditStructureRule.getRuleID());
2920        if ((removeDSR == null) || (! removeDSR.equals(ditStructureRule)))
2921        {
2922          Message message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_DSR.get(
2923              ditStructureRule.getNameOrRuleID());
2924          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2925        }
2926    
2927    
2928        // See if there is another modification later to add the DIT structure rule
2929        // back into the schema.  If so, then it's a replace and we should ignore
2930        // the remove because adding it back will handle the replace.
2931        for (int i=currentPosition+1; i < modifications.size(); i++)
2932        {
2933          Modification m = modifications.get(i);
2934          Attribute    a = m.getAttribute();
2935    
2936          if ((m.getModificationType() != ModificationType.ADD) ||
2937              (! a.getAttributeType().equals(ditStructureRulesType)))
2938          {
2939            continue;
2940          }
2941    
2942          for (AttributeValue v : a.getValues())
2943          {
2944            DITStructureRule dsr;
2945            try
2946            {
2947              dsr = DITStructureRuleSyntax.decodeDITStructureRule(
2948                         v.getValue(), schema, true);
2949            }
2950            catch (DirectoryException de)
2951            {
2952              if (debugEnabled())
2953              {
2954                TRACER.debugCaught(DebugLogLevel.ERROR, de);
2955              }
2956    
2957              Message message = ERR_SCHEMA_MODIFY_CANNOT_DECODE_DSR.get(
2958                  v.getStringValue(), de.getMessageObject());
2959              throw new DirectoryException(
2960                             ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
2961                             de);
2962            }
2963    
2964            if (ditStructureRule.getRuleID() == dsr.getRuleID())
2965            {
2966              // We found a match where the DIT structure rule is added back later,
2967              // so we don't need to do anything else here.
2968              return;
2969            }
2970          }
2971        }
2972    
2973    
2974        // Make sure that the DIT structure rule isn't the superior for any other
2975        // DIT structure rule.
2976        for (DITStructureRule dsr : schema.getDITStructureRulesByID().values())
2977        {
2978          if (dsr.getSuperiorRules().contains(removeDSR))
2979          {
2980            Message message = ERR_SCHEMA_MODIFY_REMOVE_DSR_SUPERIOR_RULE.get(
2981                removeDSR.getNameOrRuleID(), dsr.getNameOrRuleID());
2982            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
2983          }
2984        }
2985    
2986    
2987        // If we've gotten here, then it's OK to remove the DIT structure rule from
2988        // the schema.
2989        schema.deregisterDITStructureRule(removeDSR);
2990        String schemaFile = removeDSR.getSchemaFile();
2991        if (schemaFile != null)
2992        {
2993          modifiedSchemaFiles.add(schemaFile);
2994        }
2995      }
2996    
2997    
2998    
2999      /**
3000       * Handles all processing required for adding the provided matching rule use
3001       * to the given schema, replacing an existing use if necessary, and ensuring
3002       * all other metadata is properly updated.
3003       *
3004       * @param  matchingRuleUse      The matching rule use to add or replace in the
3005       *                              server schema.
3006       * @param  schema               The schema to which the matching rule use
3007       *                              should be added.
3008       * @param  modifiedSchemaFiles  The names of the schema files containing
3009       *                              schema elements that have been updated as part
3010       *                              of the schema modification.
3011       *
3012       * @throws  DirectoryException  If a problem occurs while attempting to add
3013       *                              the provided matching rule use to the server
3014       *                              schema.
3015       */
3016      private void addMatchingRuleUse(MatchingRuleUse matchingRuleUse,
3017                                      Schema schema,
3018                                      Set<String> modifiedSchemaFiles)
3019              throws DirectoryException
3020      {
3021        // First, see if the specified matching rule use already exists.  We'll
3022        // check all of the names, which means that it's possible that there could
3023        // be more than one match (although if there is, then we'll refuse the
3024        // operation).
3025        MatchingRuleUse existingMRU = null;
3026        for (MatchingRuleUse mru : schema.getMatchingRuleUses().values())
3027        {
3028          for (String name : matchingRuleUse.getNames().keySet())
3029          {
3030            if (mru.hasName(name))
3031            {
3032              if (existingMRU == null)
3033              {
3034                existingMRU = mru;
3035                break;
3036              }
3037              else
3038              {
3039                Message message =
3040                        ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_MR_USE.get(
3041                                matchingRuleUse.getName(),
3042                                existingMRU.getName(),
3043                                mru.getName());
3044                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
3045                                             message);
3046              }
3047            }
3048          }
3049        }
3050    
3051    
3052        // Get the matching rule for the new matching rule use and see if there's
3053        // already an existing matching rule use that is associated with that
3054        // matching rule.  If there is, then it will only be acceptable if it's the
3055        // matching rule use that we are replacing (in which case we really do want
3056        // to use the "!=" operator).
3057        MatchingRule matchingRule = matchingRuleUse.getMatchingRule();
3058        MatchingRuleUse existingMRUForRule =
3059             schema.getMatchingRuleUse(matchingRule);
3060        if ((existingMRUForRule != null) && (existingMRUForRule != existingMRU))
3061        {
3062          Message message = ERR_SCHEMA_MODIFY_MR_CONFLICT_FOR_ADD_MR_USE.
3063              get(matchingRuleUse.getName(), matchingRule.getNameOrOID(),
3064                  existingMRUForRule.getName());
3065          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
3066        }
3067    
3068        if (matchingRule.isObsolete())
3069        {
3070          Message message = ERR_SCHEMA_MODIFY_MRU_OBSOLETE_MR.get(
3071              matchingRuleUse.getName(), matchingRule.getNameOrOID());
3072          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
3073        }
3074    
3075    
3076        // Make sure that the new matching rule use doesn't reference an undefined
3077        // attribute type.
3078        for (AttributeType at : matchingRuleUse.getAttributes())
3079        {
3080          if (! schema.hasAttributeType(at.getOID()))
3081          {
3082            Message message = ERR_SCHEMA_MODIFY_MRU_UNDEFINED_ATTR.get(
3083                matchingRuleUse.getName(), at.getNameOrOID());
3084            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
3085          }
3086          else if (at.isObsolete())
3087          {
3088            Message message = ERR_SCHEMA_MODIFY_MRU_OBSOLETE_ATTR.get(
3089                matchingRuleUse.getName(), matchingRule.getNameOrOID());
3090            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
3091          }
3092        }
3093    
3094    
3095        // If there is no existing matching rule use, then we're adding a new one.
3096        // Otherwise, we're replacing an existing matching rule use.
3097        if (existingMRU == null)
3098        {
3099          schema.registerMatchingRuleUse(matchingRuleUse, false);
3100          String schemaFile = matchingRuleUse.getSchemaFile();
3101          if ((schemaFile == null) || (schemaFile.length() == 0))
3102          {
3103            schemaFile = FILE_USER_SCHEMA_ELEMENTS;
3104            matchingRuleUse.setSchemaFile(schemaFile);
3105          }
3106    
3107          modifiedSchemaFiles.add(schemaFile);
3108        }
3109        else
3110        {
3111          schema.deregisterMatchingRuleUse(existingMRU);
3112          schema.registerMatchingRuleUse(matchingRuleUse, false);
3113          schema.rebuildDependentElements(existingMRU);
3114    
3115          if ((matchingRuleUse.getSchemaFile() == null) ||
3116              (matchingRuleUse.getSchemaFile().length() == 0))
3117          {
3118            String schemaFile = existingMRU.getSchemaFile();
3119            if ((schemaFile == null) || (schemaFile.length() == 0))
3120            {
3121              schemaFile = FILE_USER_SCHEMA_ELEMENTS;
3122            }
3123    
3124            matchingRuleUse.setSchemaFile(schemaFile);
3125            modifiedSchemaFiles.add(schemaFile);
3126          }
3127          else
3128          {
3129            String newSchemaFile = matchingRuleUse.getSchemaFile();
3130            String oldSchemaFile = existingMRU.getSchemaFile();
3131            if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
3132            {
3133              modifiedSchemaFiles.add(newSchemaFile);
3134            }
3135            else
3136            {
3137              modifiedSchemaFiles.add(newSchemaFile);
3138              modifiedSchemaFiles.add(oldSchemaFile);
3139            }
3140          }
3141        }
3142      }
3143    
3144    
3145    
3146      /**
3147       * Handles all processing required to remove the provided matching rule use
3148       * from the server schema, ensuring all other metadata is properly updated.
3149       * Note that this method will first check to see whether the same matching
3150       * rule use will be later added to the server schema with an updated
3151       * definition, and if so then the removal will be ignored because the later
3152       * add will be handled as a replace.  If the matching rule use will not be
3153       * replaced with a new definition, then this method will ensure that there are
3154       * no other schema elements that depend on the matching rule use before
3155       * allowing it to be removed.
3156       *
3157       * @param  matchingRuleUse      The matching rule use to remove from the
3158       *                              server schema.
3159       * @param  schema               The schema from which the matching rule use
3160       *                              should be removed.
3161       * @param  modifications        The full set of modifications to be processed
3162       *                              against the server schema.
3163       * @param  currentPosition      The position of the modification currently
3164       *                              being performed.
3165       * @param  modifiedSchemaFiles  The names of the schema files containing
3166       *                              schema elements that have been updated as part
3167       *                              of the schema modification.
3168       *
3169       * @throws  DirectoryException  If a problem occurs while attempting to remove
3170       *                              the provided matching rule use from the server
3171       *                              schema.
3172       */
3173      private void removeMatchingRuleUse(MatchingRuleUse matchingRuleUse,
3174                                         Schema schema,
3175                                         ArrayList<Modification> modifications,
3176                                         int currentPosition,
3177                                         Set<String> modifiedSchemaFiles)
3178              throws DirectoryException
3179      {
3180        // See if the specified DIT content rule is actually defined in the server
3181        // schema.  If not, then fail.
3182        MatchingRuleUse removeMRU =
3183             schema.getMatchingRuleUse(matchingRuleUse.getMatchingRule());
3184        if ((removeMRU == null) || (! removeMRU.equals(matchingRuleUse)))
3185        {
3186          Message message = ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_MR_USE.get(
3187              matchingRuleUse.getName());
3188          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
3189        }
3190    
3191    
3192        // Since matching rule uses don't have any dependencies, then we don't need
3193        // to worry about the difference between a remove or a replace.  We can
3194        // just remove the DIT content rule now, and if it is added back later then
3195        // there still won't be any conflict.
3196        schema.deregisterMatchingRuleUse(removeMRU);
3197        String schemaFile = removeMRU.getSchemaFile();
3198        if (schemaFile != null)
3199        {
3200          modifiedSchemaFiles.add(schemaFile);
3201        }
3202      }
3203    
3204    
3205    
3206      /**
3207       * Creates an empty entry that may be used as the basis for a new schema file.
3208       *
3209       * @return  An empty entry that may be used as the basis for a new schema
3210       *          file.
3211       */
3212      private Entry createEmptySchemaEntry()
3213      {
3214        LinkedHashMap<ObjectClass,String> objectClasses =
3215             new LinkedHashMap<ObjectClass,String>();
3216        objectClasses.put(DirectoryServer.getTopObjectClass(), OC_TOP);
3217        objectClasses.put(DirectoryServer.getObjectClass(OC_LDAP_SUBENTRY_LC, true),
3218                          OC_LDAP_SUBENTRY);
3219        objectClasses.put(DirectoryServer.getObjectClass(OC_SUBSCHEMA, true),
3220                          OC_SUBSCHEMA);
3221    
3222        LinkedHashMap<AttributeType,List<Attribute>> userAttributes =
3223             new LinkedHashMap<AttributeType,List<Attribute>>();
3224    
3225        LinkedHashMap<AttributeType,List<Attribute>> operationalAttributes =
3226             new LinkedHashMap<AttributeType,List<Attribute>>();
3227    
3228        DN  dn  = DirectoryServer.getSchemaDN();
3229        RDN rdn = dn.getRDN();
3230        for (int i=0; i < rdn.getNumValues(); i++)
3231        {
3232          AttributeType type = rdn.getAttributeType(i);
3233          String        name = rdn.getAttributeName(i);
3234    
3235          LinkedHashSet<AttributeValue> values =
3236               new LinkedHashSet<AttributeValue>(1);
3237          values.add(rdn.getAttributeValue(i));
3238    
3239          LinkedList<Attribute> attrList = new LinkedList<Attribute>();
3240          attrList.add(new Attribute(type, name, values));
3241          if (type.isOperational())
3242          {
3243            operationalAttributes.put(type, attrList);
3244          }
3245          else
3246          {
3247            userAttributes.put(type, attrList);
3248          }
3249        }
3250    
3251        return new Entry(dn, objectClasses,  userAttributes, operationalAttributes);
3252      }
3253    
3254    
3255    
3256    
3257      /**
3258       * Writes a temporary version of the specified schema file.
3259       *
3260       * @param  schema      The schema from which to take the definitions to be
3261       *                     written.
3262       * @param  schemaFile  The name of the schema file to be written.
3263       *
3264       * @throws  DirectoryException  If an unexpected problem occurs while
3265       *                              identifying the schema definitions to include
3266       *                              in the schema file.
3267       *
3268       * @throws  IOException  If an unexpected error occurs while attempting to
3269       *                       write the temporary schema file.
3270       *
3271       * @throws  LDIFException  If an unexpected problem occurs while generating
3272       *                         the LDIF representation of the schema entry.
3273       */
3274      private File writeTempSchemaFile(Schema schema, String schemaFile)
3275              throws DirectoryException, IOException, LDIFException
3276      {
3277        // Start with an empty schema entry.
3278        Entry schemaEntry = createEmptySchemaEntry();
3279    
3280    
3281        // Add all of the appropriate attribute types to the schema entry.  We need
3282        // to be careful of the ordering to ensure that any superior types in the
3283        // same file are written before the subordinate types.
3284        HashSet<AttributeType> addedTypes = new HashSet<AttributeType>();
3285        LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
3286        for (AttributeType at : schema.getAttributeTypes().values())
3287        {
3288          if (schemaFile.equals(at.getSchemaFile()))
3289          {
3290            addAttrTypeToSchemaFile(schema, schemaFile, at, values, addedTypes, 0);
3291          }
3292        }
3293    
3294        if (! values.isEmpty())
3295        {
3296          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
3297          attrList.add(new Attribute(attributeTypesType,
3298                                     attributeTypesType.getPrimaryName(), values));
3299          schemaEntry.putAttribute(attributeTypesType, attrList);
3300        }
3301    
3302    
3303        // Add all of the appropriate objectclasses to the schema entry.  We need
3304        // to be careful of the ordering to ensure that any superior classes in the
3305        // same file are written before the subordinate classes.
3306        HashSet<ObjectClass> addedClasses = new HashSet<ObjectClass>();
3307        values = new LinkedHashSet<AttributeValue>();
3308        for (ObjectClass oc : schema.getObjectClasses().values())
3309        {
3310          if (schemaFile.equals(oc.getSchemaFile()))
3311          {
3312            addObjectClassToSchemaFile(schema, schemaFile, oc, values, addedClasses,
3313                                       0);
3314          }
3315        }
3316    
3317        if (! values.isEmpty())
3318        {
3319          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
3320          attrList.add(new Attribute(objectClassesType,
3321                                     objectClassesType.getPrimaryName(), values));
3322          schemaEntry.putAttribute(objectClassesType, attrList);
3323        }
3324    
3325    
3326        // Add all of the appropriate name forms to the schema entry.  Since there
3327        // is no hierarchical relationship between name forms, we don't need to
3328        // worry about ordering.
3329        values = new LinkedHashSet<AttributeValue>();
3330        for (NameForm nf : schema.getNameFormsByObjectClass().values())
3331        {
3332          if (schemaFile.equals(nf.getSchemaFile()))
3333          {
3334            values.add(new AttributeValue(nameFormsType, nf.getDefinition()));
3335          }
3336        }
3337    
3338        if (! values.isEmpty())
3339        {
3340          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
3341          attrList.add(new Attribute(nameFormsType,
3342                                     nameFormsType.getPrimaryName(), values));
3343          schemaEntry.putAttribute(nameFormsType, attrList);
3344        }
3345    
3346    
3347        // Add all of the appropriate DIT content rules to the schema entry.  Since
3348        // there is no hierarchical relationship between DIT content rules, we don't
3349        // need to worry about ordering.
3350        values = new LinkedHashSet<AttributeValue>();
3351        for (DITContentRule dcr : schema.getDITContentRules().values())
3352        {
3353          if (schemaFile.equals(dcr.getSchemaFile()))
3354          {
3355            values.add(new AttributeValue(ditContentRulesType,
3356                                          dcr.getDefinition()));
3357          }
3358        }
3359    
3360        if (! values.isEmpty())
3361        {
3362          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
3363          attrList.add(new Attribute(ditContentRulesType,
3364                                     ditContentRulesType.getPrimaryName(), values));
3365          schemaEntry.putAttribute(ditContentRulesType, attrList);
3366        }
3367    
3368    
3369        // Add all of the appropriate DIT structure rules to the schema entry.  We
3370        // need to be careful of the ordering to ensure that any superior rules in
3371        // the same file are written before the subordinate rules.
3372        HashSet<DITStructureRule> addedDSRs = new HashSet<DITStructureRule>();
3373        values = new LinkedHashSet<AttributeValue>();
3374        for (DITStructureRule dsr : schema.getDITStructureRulesByID().values())
3375        {
3376          if (schemaFile.equals(dsr.getSchemaFile()))
3377          {
3378            addDITStructureRuleToSchemaFile(schema, schemaFile, dsr, values,
3379                                            addedDSRs, 0);
3380          }
3381        }
3382    
3383        if (! values.isEmpty())
3384        {
3385          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
3386          attrList.add(new Attribute(ditStructureRulesType,
3387                                     ditStructureRulesType.getPrimaryName(),
3388                                     values));
3389          schemaEntry.putAttribute(ditStructureRulesType, attrList);
3390        }
3391    
3392    
3393        // Add all of the appropriate matching rule uses to the schema entry.  Since
3394        // there is no hierarchical relationship between matching rule uses, we
3395        // don't need to worry about ordering.
3396        values = new LinkedHashSet<AttributeValue>();
3397        for (MatchingRuleUse mru : schema.getMatchingRuleUses().values())
3398        {
3399          if (schemaFile.equals(mru.getSchemaFile()))
3400          {
3401            values.add(new AttributeValue(matchingRuleUsesType,
3402                                          mru.getDefinition()));
3403          }
3404        }
3405    
3406        if (! values.isEmpty())
3407        {
3408          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
3409          attrList.add(new Attribute(matchingRuleUsesType,
3410                                     matchingRuleUsesType.getPrimaryName(),
3411                                     values));
3412          schemaEntry.putAttribute(matchingRuleUsesType, attrList);
3413        }
3414    
3415        if (schemaFile.equals(FILE_USER_SCHEMA_ELEMENTS))
3416        {
3417          Map<String, Attribute> attributes = schema.getExtraAttributes();
3418          for (Attribute attribute : attributes.values())
3419          {
3420            ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
3421            attrList.add(attribute);
3422            schemaEntry.putAttribute(attribute.getAttributeType(), attrList);
3423          }
3424        }
3425    
3426        // Create a temporary file to which we can write the schema entry.
3427        File tempFile = File.createTempFile(schemaFile, "temp");
3428        LDIFExportConfig exportConfig =
3429             new LDIFExportConfig(tempFile.getAbsolutePath(),
3430                                  ExistingFileBehavior.OVERWRITE);
3431        LDIFWriter ldifWriter = new LDIFWriter(exportConfig);
3432        ldifWriter.writeEntry(schemaEntry);
3433        ldifWriter.close();
3434    
3435        return tempFile;
3436      }
3437    
3438    
3439    
3440      /**
3441       * Adds the definition for the specified attribute type to the provided set of
3442       * attribute values, recursively adding superior types as appropriate.
3443       *
3444       * @param  schema         The schema containing the attribute type.
3445       * @param  schemaFile     The schema file with which the attribute type is
3446       *                        associated.
3447       * @param  attributeType  The attribute type whose definition should be added
3448       *                        to the value set.
3449       * @param  values         The set of values for attribute type definitions
3450       *                        already added.
3451       * @param  addedTypes     The set of attribute types whose definitions have
3452       *                        already been added to the set of values.
3453       * @param  depth          A depth counter to use in an attempt to detect
3454       *                        circular references.
3455       */
3456      private void addAttrTypeToSchemaFile(Schema schema, String schemaFile,
3457                                           AttributeType attributeType,
3458                                           LinkedHashSet<AttributeValue> values,
3459                                           HashSet<AttributeType> addedTypes,
3460                                           int depth)
3461              throws DirectoryException
3462      {
3463        if (depth > 20)
3464        {
3465          Message message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_AT.get(
3466              attributeType.getNameOrOID());
3467          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
3468        }
3469    
3470        if (addedTypes.contains(attributeType))
3471        {
3472          return;
3473        }
3474    
3475        AttributeType superiorType = attributeType.getSuperiorType();
3476        if ((superiorType != null) &&
3477            schemaFile.equals(superiorType.getSchemaFile()) &&
3478            (! addedTypes.contains(superiorType)))
3479        {
3480          addAttrTypeToSchemaFile(schema, schemaFile, superiorType, values,
3481                                  addedTypes, depth+1);
3482        }
3483    
3484        values.add(new AttributeValue(attributeTypesType,
3485                                      attributeType.getDefinition()));
3486        addedTypes.add(attributeType);
3487      }
3488    
3489    
3490    
3491      /**
3492       * Adds the definition for the specified objectclass to the provided set of
3493       * attribute values, recursively adding superior classes as appropriate.
3494       *
3495       * @param  schema        The schema containing the objectclass.
3496       * @param  schemaFile    The schema file with which the objectclass is
3497       *                       associated.
3498       * @param  objectClass   The objectclass whose definition should be added to
3499       *                       the value set.
3500       * @param  values        The set of values for objectclass definitions
3501       *                       already added.
3502       * @param  addedClasses  The set of objectclasses whose definitions have
3503       *                       already been added to the set of values.
3504       * @param  depth         A depth counter to use in an attempt to detect
3505       *                       circular references.
3506       */
3507      private void addObjectClassToSchemaFile(Schema schema, String schemaFile,
3508                                              ObjectClass objectClass,
3509                                              LinkedHashSet<AttributeValue> values,
3510                                              HashSet<ObjectClass> addedClasses,
3511                                              int depth)
3512              throws DirectoryException
3513      {
3514        if (depth > 20)
3515        {
3516          Message message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_OC.get(
3517              objectClass.getNameOrOID());
3518          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
3519        }
3520    
3521        if (addedClasses.contains(objectClass))
3522        {
3523          return;
3524        }
3525    
3526        ObjectClass superiorClass = objectClass.getSuperiorClass();
3527        if ((superiorClass != null) &&
3528            schemaFile.equals(superiorClass.getSchemaFile()) &&
3529            (! addedClasses.contains(superiorClass)))
3530        {
3531          addObjectClassToSchemaFile(schema, schemaFile, superiorClass, values,
3532                                     addedClasses, depth+1);
3533        }
3534    
3535        values.add(new AttributeValue(objectClassesType,
3536                                      objectClass.getDefinition()));
3537        addedClasses.add(objectClass);
3538      }
3539    
3540    
3541    
3542      /**
3543       * Adds the definition for the specified DIT structure rule to the provided
3544       * set of attribute values, recursively adding superior rules as appropriate.
3545       *
3546       * @param  schema            The schema containing the DIT structure rule.
3547       * @param  schemaFile        The schema file with which the DIT structure rule
3548       *                           is associated.
3549       * @param  ditStructureRule  The DIT structure rule whose definition should be
3550       *                           added to the value set.
3551       * @param  values            The set of values for DIT structure rule
3552       *                           definitions already added.
3553       * @param  addedDSRs         The set of DIT structure rules whose definitions
3554       *                           have already been added added to the set of
3555       *                           values.
3556       * @param  depth             A depth counter to use in an attempt to detect
3557       *                           circular references.
3558       */
3559      private void addDITStructureRuleToSchemaFile(Schema schema, String schemaFile,
3560                        DITStructureRule ditStructureRule,
3561                        LinkedHashSet<AttributeValue> values,
3562                        HashSet<DITStructureRule> addedDSRs, int depth)
3563              throws DirectoryException
3564      {
3565        if (depth > 20)
3566        {
3567          Message message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_DSR.get(
3568              ditStructureRule.getNameOrRuleID());
3569          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
3570        }
3571    
3572        if (addedDSRs.contains(ditStructureRule))
3573        {
3574          return;
3575        }
3576    
3577        for (DITStructureRule dsr : ditStructureRule.getSuperiorRules())
3578        {
3579          if (schemaFile.equals(dsr.getSchemaFile()) && (! addedDSRs.contains(dsr)))
3580          {
3581            addDITStructureRuleToSchemaFile(schema, schemaFile, dsr, values,
3582                                            addedDSRs, depth+1);
3583          }
3584        }
3585    
3586        values.add(new AttributeValue(ditStructureRulesType,
3587                                      ditStructureRule.getDefinition()));
3588        addedDSRs.add(ditStructureRule);
3589      }
3590    
3591    
3592    
3593      /**
3594       * Moves the specified temporary schema files in place of the active versions.
3595       * If an error occurs in the process, then this method will attempt to restore
3596       * the original schema files if possible.
3597       *
3598       * @param  tempSchemaFiles  The set of temporary schema files to be activated.
3599       *
3600       * @throws  DirectoryException  If a problem occurs while attempting to
3601       *                              install the temporary schema files.
3602       */
3603      private void installSchemaFiles(HashMap<String,File> tempSchemaFiles)
3604              throws DirectoryException
3605      {
3606        // Create lists that will hold the three types of files we'll be dealing
3607        // with (the temporary files that will be installed, the installed schema
3608        // files, and the previously-installed schema files).
3609        ArrayList<File> installedFileList = new ArrayList<File>();
3610        ArrayList<File> tempFileList      = new ArrayList<File>();
3611        ArrayList<File> origFileList      = new ArrayList<File>();
3612    
3613        File schemaDir = new File(SchemaConfigManager.getSchemaDirectoryPath());
3614    
3615        for (String name : tempSchemaFiles.keySet())
3616        {
3617          installedFileList.add(new File(schemaDir, name));
3618          tempFileList.add(tempSchemaFiles.get(name));
3619          origFileList.add(new File(schemaDir, name + ".orig"));
3620        }
3621    
3622    
3623        // If there are any old ".orig" files laying around from a previous
3624        // attempt, then try to clean them up.
3625        for (File f : origFileList)
3626        {
3627          if (f.exists())
3628          {
3629            f.delete();
3630          }
3631        }
3632    
3633    
3634        // Copy all of the currently-installed files with a ".orig" extension.  If
3635        // this fails, then try to clean up the copies.
3636        try
3637        {
3638          for (int i=0; i < installedFileList.size(); i++)
3639          {
3640            File installedFile = installedFileList.get(i);
3641            File origFile      = origFileList.get(i);
3642    
3643            if (installedFile.exists())
3644            {
3645              copyFile(installedFile, origFile);
3646            }
3647          }
3648        }
3649        catch (Exception e)
3650        {
3651          if (debugEnabled())
3652          {
3653            TRACER.debugCaught(DebugLogLevel.ERROR, e);
3654          }
3655    
3656          boolean allCleaned = true;
3657          for (File f : origFileList)
3658          {
3659            try
3660            {
3661              if (f.exists())
3662              {
3663                if (! f.delete())
3664                {
3665                  allCleaned = false;
3666                }
3667              }
3668            }
3669            catch (Exception e2)
3670            {
3671              if (debugEnabled())
3672              {
3673                TRACER.debugCaught(DebugLogLevel.ERROR, e2);
3674              }
3675    
3676              allCleaned = false;
3677            }
3678          }
3679    
3680          if (allCleaned)
3681          {
3682            Message message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_CLEANED.get(
3683                getExceptionMessage(e));
3684            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
3685                                         message, e);
3686          }
3687          else
3688          {
3689    
3690            Message message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_NOT_CLEANED
3691                    .get(getExceptionMessage(e));
3692    
3693            DirectoryServer.sendAlertNotification(this,
3694                                 ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES,
3695                                 message);
3696    
3697            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
3698                                         message, e);
3699          }
3700        }
3701    
3702    
3703        // Try to copy all of the temporary files into place over the installed
3704        // files.  If this fails, then try to restore the originals.
3705        try
3706        {
3707          for (int i=0; i < installedFileList.size(); i++)
3708          {
3709            File installedFile = installedFileList.get(i);
3710            File tempFile      = tempFileList.get(i);
3711            copyFile(tempFile, installedFile);
3712          }
3713        }
3714        catch (Exception e)
3715        {
3716          if (debugEnabled())
3717          {
3718            TRACER.debugCaught(DebugLogLevel.ERROR, e);
3719          }
3720    
3721          for (File f : installedFileList)
3722          {
3723            try
3724            {
3725              if (f.exists())
3726              {
3727                f.delete();
3728              }
3729            }
3730            catch (Exception e2)
3731            {
3732              if (debugEnabled())
3733              {
3734                TRACER.debugCaught(DebugLogLevel.ERROR, e2);
3735              }
3736            }
3737          }
3738    
3739          boolean allRestored = true;
3740          for (int i=0; i < installedFileList.size(); i++)
3741          {
3742            File installedFile = installedFileList.get(i);
3743            File origFile      = origFileList.get(i);
3744    
3745            try
3746            {
3747              if (origFile.exists())
3748              {
3749                if (! origFile.renameTo(installedFile))
3750                {
3751                  allRestored = false;
3752                }
3753              }
3754            }
3755            catch (Exception e2)
3756            {
3757              if (debugEnabled())
3758              {
3759                TRACER.debugCaught(DebugLogLevel.ERROR, e2);
3760              }
3761    
3762              allRestored = false;
3763            }
3764          }
3765    
3766          if (allRestored)
3767          {
3768            Message message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_RESTORED.get(
3769                getExceptionMessage(e));
3770            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
3771                                         message, e);
3772          }
3773          else
3774          {
3775            Message message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_NOT_RESTORED
3776                    .get(getExceptionMessage(e));
3777    
3778            DirectoryServer.sendAlertNotification(this,
3779                                 ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES,
3780                                 message);
3781    
3782            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
3783                                         message, e);
3784          }
3785        }
3786    
3787    
3788        // At this point, we're committed to the schema change, so we can't throw
3789        // any more exceptions, but all we have left is to clean up the original and
3790        // temporary files.
3791        for (File f : origFileList)
3792        {
3793          try
3794          {
3795            if (f.exists())
3796            {
3797              f.delete();
3798            }
3799          }
3800          catch (Exception e)
3801          {
3802            if (debugEnabled())
3803            {
3804              TRACER.debugCaught(DebugLogLevel.ERROR, e);
3805            }
3806          }
3807        }
3808    
3809        for (File f : tempFileList)
3810        {
3811          try
3812          {
3813            if (f.exists())
3814            {
3815              f.delete();
3816            }
3817          }
3818          catch (Exception e)
3819          {
3820            if (debugEnabled())
3821            {
3822              TRACER.debugCaught(DebugLogLevel.ERROR, e);
3823            }
3824          }
3825        }
3826      }
3827    
3828    
3829    
3830      /**
3831       * Creates a copy of the specified file.
3832       *
3833       * @param  from  The source file to be copied.
3834       * @param  to    The destination file to be created.
3835       *
3836       * @throws  IOException  If a problem occurs.
3837       */
3838      private void copyFile(File from, File to)
3839              throws IOException
3840      {
3841        byte[]           buffer        = new byte[4096];
3842        FileInputStream  inputStream   = null;
3843        FileOutputStream outputStream  = null;
3844        try
3845        {
3846          inputStream  = new FileInputStream(from);
3847          outputStream = new FileOutputStream(to, false);
3848    
3849          int bytesRead = inputStream.read(buffer);
3850          while (bytesRead > 0)
3851          {
3852            outputStream.write(buffer, 0, bytesRead);
3853            bytesRead = inputStream.read(buffer);
3854          }
3855        }
3856        finally
3857        {
3858          if (inputStream != null)
3859          {
3860            try
3861            {
3862              inputStream.close();
3863            }
3864            catch (Exception e)
3865            {
3866              if (debugEnabled())
3867              {
3868                TRACER.debugCaught(DebugLogLevel.ERROR, e);
3869              }
3870            }
3871          }
3872    
3873          if (outputStream != null)
3874          {
3875            outputStream.close();
3876          }
3877        }
3878      }
3879    
3880    
3881    
3882      /**
3883       * Performs any necessary cleanup in an attempt to delete any temporary schema
3884       * files that may have been left over after trying to install the new schema.
3885       *
3886       * @param  tempSchemaFiles  The set of temporary schema files that have been
3887       *                          created and are candidates for cleanup.
3888       */
3889      private void cleanUpTempSchemaFiles(HashMap<String,File> tempSchemaFiles)
3890      {
3891        if ((tempSchemaFiles == null) || tempSchemaFiles.isEmpty())
3892        {
3893          return;
3894        }
3895    
3896        for (File f : tempSchemaFiles.values())
3897        {
3898          try
3899          {
3900            if (f.exists())
3901            {
3902              f.delete();
3903            }
3904          }
3905          catch (Exception e)
3906          {
3907            if (debugEnabled())
3908            {
3909              TRACER.debugCaught(DebugLogLevel.ERROR, e);
3910            }
3911          }
3912        }
3913      }
3914    
3915    
3916    
3917      /**
3918       * {@inheritDoc}
3919       */
3920      @Override()
3921      public void renameEntry(DN currentDN, Entry entry,
3922                                       ModifyDNOperation modifyDNOperation)
3923             throws DirectoryException
3924      {
3925        Message message =
3926            ERR_SCHEMA_MODIFY_DN_NOT_SUPPORTED.get(String.valueOf(currentDN));
3927        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
3928      }
3929    
3930    
3931    
3932      /**
3933       * {@inheritDoc}
3934       */
3935      @Override()
3936      public void search(SearchOperation searchOperation)
3937             throws DirectoryException
3938      {
3939        DN baseDN = searchOperation.getBaseDN();
3940    
3941        boolean found = false;
3942        DN[] dnArray = baseDNs;
3943        DN matchedDN = null;
3944        for (DN dn : dnArray)
3945        {
3946          if (dn.equals(baseDN))
3947          {
3948            found = true;
3949            break;
3950          }
3951          else if (dn.isAncestorOf(baseDN))
3952          {
3953            matchedDN = dn;
3954            break;
3955          }
3956        }
3957    
3958        if (! found)
3959        {
3960          Message message = ERR_SCHEMA_INVALID_BASE.get(String.valueOf(baseDN));
3961          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message,
3962                  matchedDN, null);
3963        }
3964    
3965    
3966        // If it's a onelevel or subordinate subtree search, then we will never
3967        // match anything since there isn't anything below the schema.
3968        SearchScope scope = searchOperation.getScope();
3969        if ((scope == SearchScope.SINGLE_LEVEL) ||
3970            (scope == SearchScope.SUBORDINATE_SUBTREE))
3971        {
3972          return;
3973        }
3974    
3975    
3976        // Get the schema entry and see if it matches the filter.  If so, then send
3977        // it to the client.
3978        Entry schemaEntry = getSchemaEntry(baseDN, false);
3979        SearchFilter filter = searchOperation.getFilter();
3980        if (filter.matchesEntry(schemaEntry))
3981        {
3982          searchOperation.returnEntry(schemaEntry, null);
3983        }
3984      }
3985    
3986    
3987    
3988      /**
3989       * {@inheritDoc}
3990       */
3991      @Override()
3992      public HashSet<String> getSupportedControls()
3993      {
3994        return supportedControls;
3995      }
3996    
3997    
3998    
3999      /**
4000       * {@inheritDoc}
4001       */
4002      @Override()
4003      public HashSet<String> getSupportedFeatures()
4004      {
4005        return supportedFeatures;
4006      }
4007    
4008    
4009    
4010      /**
4011       * {@inheritDoc}
4012       */
4013      @Override()
4014      public boolean supportsLDIFExport()
4015      {
4016        // We will only export the DSE entry itself.
4017        return true;
4018      }
4019    
4020    
4021    
4022      /**
4023       * {@inheritDoc}
4024       */
4025      @Override()
4026      public void exportLDIF(LDIFExportConfig exportConfig)
4027             throws DirectoryException
4028      {
4029        // Create the LDIF writer.
4030        LDIFWriter ldifWriter;
4031        try
4032        {
4033          ldifWriter = new LDIFWriter(exportConfig);
4034        }
4035        catch (Exception e)
4036        {
4037          if (debugEnabled())
4038          {
4039            TRACER.debugCaught(DebugLogLevel.ERROR, e);
4040          }
4041    
4042          Message message = ERR_SCHEMA_UNABLE_TO_CREATE_LDIF_WRITER.get(
4043              stackTraceToSingleLineString(e));
4044          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4045                                       message);
4046        }
4047    
4048    
4049        // Write the root schema entry to it.  Make sure to close the LDIF
4050        // writer when we're done.
4051        try
4052        {
4053          ldifWriter.writeEntry(getSchemaEntry(baseDNs[0], true));
4054        }
4055        catch (Exception e)
4056        {
4057          if (debugEnabled())
4058          {
4059            TRACER.debugCaught(DebugLogLevel.ERROR, e);
4060          }
4061    
4062          Message message =
4063              ERR_SCHEMA_UNABLE_TO_EXPORT_BASE.get(stackTraceToSingleLineString(e));
4064          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4065                                       message);
4066        }
4067        finally
4068        {
4069          try
4070          {
4071            ldifWriter.close();
4072          }
4073          catch (Exception e)
4074          {
4075            if (debugEnabled())
4076            {
4077              TRACER.debugCaught(DebugLogLevel.ERROR, e);
4078            }
4079          }
4080        }
4081      }
4082    
4083    
4084    
4085      /**
4086       * {@inheritDoc}
4087       */
4088      @Override()
4089      public boolean supportsLDIFImport()
4090      {
4091        return true;
4092      }
4093    
4094    
4095    
4096      /**
4097       * {@inheritDoc}
4098       */
4099      @Override()
4100      public LDIFImportResult importLDIF(LDIFImportConfig importConfig)
4101             throws DirectoryException
4102      {
4103        LDIFReader reader;
4104        try
4105        {
4106          reader = new LDIFReader(importConfig);
4107        }
4108        catch (Exception e)
4109        {
4110          Message message =
4111              ERR_MEMORYBACKEND_CANNOT_CREATE_LDIF_READER.get(String.valueOf(e));
4112          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4113                                       message, e);
4114        }
4115    
4116    
4117        try
4118        {
4119          while (true)
4120          {
4121            Entry e = null;
4122            try
4123            {
4124              e = reader.readEntry();
4125              if (e == null)
4126              {
4127                break;
4128              }
4129            }
4130            catch (LDIFException le)
4131            {
4132              if (! le.canContinueReading())
4133              {
4134                Message message =
4135                    ERR_MEMORYBACKEND_ERROR_READING_LDIF.get(String.valueOf(e));
4136                throw new DirectoryException(
4137                               DirectoryServer.getServerErrorResultCode(),
4138                               message, le);
4139              }
4140              else
4141              {
4142                continue;
4143              }
4144            }
4145    
4146            importEntry(e);
4147          }
4148    
4149          return new LDIFImportResult(reader.getEntriesRead(),
4150                                      reader.getEntriesRejected(),
4151                                      reader.getEntriesIgnored());
4152        }
4153        catch (DirectoryException de)
4154        {
4155          throw de;
4156        }
4157        catch (Exception e)
4158        {
4159          Message message =
4160              ERR_MEMORYBACKEND_ERROR_DURING_IMPORT.get(String.valueOf(e));
4161          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4162                                       message, e);
4163        }
4164        finally
4165        {
4166          reader.close();
4167        }
4168      }
4169    
4170    
4171      /**
4172       * Import an entry in a new schema by :
4173       *   - duplicating the schema
4174       *   - iterating over each element of the newSchemaEntry and comparing
4175       *     with the xisting schema
4176       *   - if the new schema element do not exist : add it
4177       *   - if the new schema.
4178       *
4179       *   FIXME : attributeTypes and objectClasses are the only elements
4180       *   currently taken into account.
4181       *
4182       * @param newSchemaEntry   The entry to be imported.
4183       */
4184      private void importEntry(Entry newSchemaEntry)
4185              throws DirectoryException
4186      {
4187        Schema schema = DirectoryServer.getSchema();
4188        Schema newSchema = DirectoryServer.getSchema().duplicate();
4189        TreeSet<String> modifiedSchemaFiles = new TreeSet<String>();
4190    
4191        // Get the attributeTypes attribute from the entry.
4192        AttributeTypeSyntax attrTypeSyntax;
4193        try
4194        {
4195          attrTypeSyntax = (AttributeTypeSyntax)
4196                           schema.getSyntax(SYNTAX_ATTRIBUTE_TYPE_OID);
4197          if (attrTypeSyntax == null)
4198          {
4199            attrTypeSyntax = new AttributeTypeSyntax();
4200            attrTypeSyntax.initializeSyntax(null);
4201          }
4202        }
4203        catch (Exception e)
4204        {
4205          if (debugEnabled())
4206          {
4207            TRACER.debugCaught(DebugLogLevel.ERROR, e);
4208          }
4209    
4210          attrTypeSyntax = new AttributeTypeSyntax();
4211        }
4212    
4213        AttributeType attributeAttrType =
4214             schema.getAttributeType(ATTR_ATTRIBUTE_TYPES_LC);
4215        if (attributeAttrType == null)
4216        {
4217          attributeAttrType =
4218               DirectoryServer.getDefaultAttributeType(ATTR_ATTRIBUTE_TYPES,
4219                                                       attrTypeSyntax);
4220        }
4221    
4222        // loop on the attribute types in the entry just received
4223        // and add them in the existing schema.
4224        List<Attribute> attrList = newSchemaEntry.getAttribute(attributeAttrType);
4225        Set<String> oidList = new HashSet<String>(1000);
4226        if ((attrList != null) && (! attrList.isEmpty()))
4227        {
4228          for (Attribute a : attrList)
4229          {
4230            // Look for attributetypes that could have been added to the schema
4231            // or modified in the schema
4232            for (AttributeValue v : a.getValues())
4233            {
4234              // Parse the attribute type.
4235              AttributeType attrType = AttributeTypeSyntax.decodeAttributeType(
4236                  v.getValue(), schema, false);
4237              String schemaFile = attrType.getSchemaFile();
4238              if (schemaFile.equals(CONFIG_SCHEMA_ELEMENTS_FILE))
4239              {
4240                // Don't import the file containing the definitions of the
4241                // Schema elements used for configuration because these
4242                // definitions may vary between versions of OpenDS.
4243                continue;
4244              }
4245    
4246              oidList.add(attrType.getOID());
4247              try
4248              {
4249                // Register this attribute type in the new schema
4250                // unless it is already defined with the same syntax.
4251                AttributeType oldAttrType =
4252                  schema.getAttributeType(attrType.getOID());
4253                if ((oldAttrType == null) ||
4254                    (!oldAttrType.toString().equals(attrType.toString())))
4255                {
4256                  newSchema.registerAttributeType(attrType, true);
4257    
4258                  if (schemaFile != null)
4259                  {
4260                    modifiedSchemaFiles.add(schemaFile);
4261                  }
4262                }
4263              }
4264              catch (DirectoryException de)
4265              {
4266                Message message =
4267                  NOTE_SCHEMA_IMPORT_FAILED.get(
4268                      attrType.toString(), de.getMessage());
4269                logError(message);
4270              }
4271              catch (Exception e)
4272              {
4273                Message message =
4274                  NOTE_SCHEMA_IMPORT_FAILED.get(
4275                      attrType.toString(), e.getMessage());
4276                logError(message);
4277              }
4278            }
4279          }
4280        }
4281    
4282        // loop on all the attribute types in the current schema and delete
4283        // them from the new schema if they are not in the imported schema entry.
4284        ConcurrentHashMap<String, AttributeType> currentAttrTypes =
4285          newSchema.getAttributeTypes();
4286    
4287        for (AttributeType removeType : currentAttrTypes.values())
4288        {
4289          String schemaFile = removeType.getSchemaFile();
4290          if (schemaFile.equals(CONFIG_SCHEMA_ELEMENTS_FILE))
4291          {
4292            // Don't import the file containing the definitiong of the
4293            // Schema elements used for configuration because these
4294            // definitions may vary between versions of OpenDS.
4295            continue;
4296          }
4297          if (!oidList.contains(removeType.getOID()))
4298          {
4299            newSchema.deregisterAttributeType(removeType);
4300    
4301            if (schemaFile != null)
4302            {
4303              modifiedSchemaFiles.add(schemaFile);
4304            }
4305          }
4306        }
4307    
4308        // loop on the objectClasses from the entry, search if they are
4309        // already in the current schema, add them if not.
4310        ObjectClassSyntax ocSyntax;
4311        try
4312        {
4313          ocSyntax = (ObjectClassSyntax) schema.getSyntax(SYNTAX_OBJECTCLASS_OID);
4314          if (ocSyntax == null)
4315          {
4316            ocSyntax = new ObjectClassSyntax();
4317            ocSyntax.initializeSyntax(null);
4318          }
4319        }
4320        catch (Exception e)
4321        {
4322          if (debugEnabled())
4323          {
4324            TRACER.debugCaught(DebugLogLevel.ERROR, e);
4325          }
4326    
4327          ocSyntax = new ObjectClassSyntax();
4328        }
4329    
4330        AttributeType objectclassAttrType =
4331          schema.getAttributeType(ATTR_OBJECTCLASSES_LC);
4332        if (objectclassAttrType == null)
4333        {
4334          objectclassAttrType =
4335            DirectoryServer.getDefaultAttributeType(ATTR_OBJECTCLASSES,
4336                                                    ocSyntax);
4337        }
4338    
4339        oidList.clear();
4340        List<Attribute> ocList = newSchemaEntry.getAttribute(objectclassAttrType);
4341        if ((ocList != null) && (! ocList.isEmpty()))
4342        {
4343          for (Attribute a : ocList)
4344          {
4345            for (AttributeValue v : a.getValues())
4346            {
4347              // It IS important here to allow the unknown elements that could
4348              // appear in the new config schema.
4349              ObjectClass newObjectClass = ObjectClassSyntax.decodeObjectClass(
4350                  v.getValue(), newSchema, true);
4351              String schemaFile = newObjectClass.getSchemaFile();
4352              if (schemaFile.equals(CONFIG_SCHEMA_ELEMENTS_FILE))
4353              {
4354                // Don't import the file containing the definitions of the
4355                // Schema elements used for configuration because these
4356                // definitions may vary between versions of OpenDS.
4357                continue;
4358              }
4359    
4360              // Now we know we are not in the config schema, let's check
4361              // the unknown elements ... sadly but simply by redoing the
4362              // whole decoding.
4363              newObjectClass = ObjectClassSyntax.decodeObjectClass(
4364                  v.getValue(), newSchema, false);
4365              oidList.add(newObjectClass.getOID());
4366              try
4367              {
4368                // Register this ObjectClass in the new schema
4369                // unless it is already defined with the same syntax.
4370                ObjectClass oldObjectClass =
4371                  schema.getObjectClass(newObjectClass.getOID());
4372                if ((oldObjectClass == null) ||
4373                    (!oldObjectClass.toString().equals(newObjectClass.toString())))
4374                {
4375                  newSchema.registerObjectClass(newObjectClass, true);
4376    
4377                  if (schemaFile != null)
4378                  {
4379                    modifiedSchemaFiles.add(schemaFile);
4380                  }
4381                }
4382              }
4383              catch (DirectoryException de)
4384              {
4385                Message message =
4386                  NOTE_SCHEMA_IMPORT_FAILED.get(
4387                      newObjectClass.toString(), de.getMessage());
4388                logError(message);
4389              }
4390              catch (Exception e)
4391              {
4392                Message message =
4393                  NOTE_SCHEMA_IMPORT_FAILED.get(
4394                      newObjectClass.toString(), e.getMessage());
4395                logError(message);
4396              }
4397            }
4398          }
4399        }
4400    
4401        // loop on all the attribute types in the current schema and delete
4402        // them from the new schema if they are not in the imported schema entry.
4403        ConcurrentHashMap<String, ObjectClass> currentObjectClasses =
4404          newSchema.getObjectClasses();
4405    
4406        for (ObjectClass removeClass : currentObjectClasses.values())
4407        {
4408          String schemaFile = removeClass.getSchemaFile();
4409          if (schemaFile.equals(CONFIG_SCHEMA_ELEMENTS_FILE))
4410          {
4411            // Don't import the file containing the definitiong of the
4412            // Schema elements used for configuration because these
4413            // definitions may vary between versions of OpenDS.
4414            continue;
4415          }
4416          if (!oidList.contains(removeClass.getOID()))
4417          {
4418            newSchema.deregisterObjectClass(removeClass);
4419    
4420            if (schemaFile != null)
4421            {
4422              modifiedSchemaFiles.add(schemaFile);
4423            }
4424          }
4425        }
4426    
4427        // Finally, if there were some modifications, save the new schema
4428        // in the Schema Files and update DirectoryServer.
4429        if (!modifiedSchemaFiles.isEmpty())
4430        {
4431          updateSchemaFiles(newSchema, modifiedSchemaFiles);
4432          DirectoryServer.setSchema(newSchema);
4433        }
4434      }
4435    
4436    
4437    
4438      /**
4439       * {@inheritDoc}
4440       */
4441      @Override()
4442      public boolean supportsBackup()
4443      {
4444        // We do support an online backup mechanism for the schema.
4445        return true;
4446      }
4447    
4448    
4449    
4450      /**
4451       * {@inheritDoc}
4452       */
4453      @Override()
4454      public boolean supportsBackup(BackupConfig backupConfig,
4455                                    StringBuilder unsupportedReason)
4456      {
4457        // We should support online backup for the schema in any form.  This
4458        // implementation does not support incremental backups, but in this case
4459        // even if we're asked to do an incremental we'll just do a full backup
4460        // instead.  So the answer to this should always be "true".
4461        return true;
4462      }
4463    
4464    
4465    
4466      /**
4467       * {@inheritDoc}
4468       */
4469      @Override()
4470      public void createBackup(BackupConfig backupConfig)
4471             throws DirectoryException
4472      {
4473        // Get the properties to use for the backup.  We don't care whether or not
4474        // it's incremental, so there's no need to get that.
4475        String          backupID        = backupConfig.getBackupID();
4476        BackupDirectory backupDirectory = backupConfig.getBackupDirectory();
4477        boolean         compress        = backupConfig.compressData();
4478        boolean         encrypt         = backupConfig.encryptData();
4479        boolean         hash            = backupConfig.hashData();
4480        boolean         signHash        = backupConfig.signHash();
4481    
4482    
4483        // Create a hash map that will hold the extra backup property information
4484        // for this backup.
4485        HashMap<String,String> backupProperties = new HashMap<String,String>();
4486    
4487    
4488        // Get the crypto manager and use it to obtain references to the message
4489        // digest and/or MAC to use for hashing and/or signing.
4490        CryptoManager cryptoManager   = DirectoryServer.getCryptoManager();
4491        Mac           mac             = null;
4492        MessageDigest digest          = null;
4493        String        digestAlgorithm = null;
4494        String        macKeyID    = null;
4495    
4496        if (hash)
4497        {
4498          if (signHash)
4499          {
4500            try
4501            {
4502              macKeyID = cryptoManager.getMacEngineKeyEntryID();
4503              backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
4504    
4505              mac = cryptoManager.getMacEngine(macKeyID);
4506            }
4507            catch (Exception e)
4508            {
4509              if (debugEnabled())
4510              {
4511                TRACER.debugCaught(DebugLogLevel.ERROR, e);
4512              }
4513    
4514              Message message = ERR_SCHEMA_BACKUP_CANNOT_GET_MAC.get(
4515                  macKeyID, stackTraceToSingleLineString(e));
4516              throw new DirectoryException(
4517                             DirectoryServer.getServerErrorResultCode(), message,
4518                             e);
4519            }
4520          }
4521          else
4522          {
4523            digestAlgorithm = cryptoManager.getPreferredMessageDigestAlgorithm();
4524            backupProperties.put(BACKUP_PROPERTY_DIGEST_ALGORITHM, digestAlgorithm);
4525    
4526            try
4527            {
4528              digest = cryptoManager.getPreferredMessageDigest();
4529            }
4530            catch (Exception e)
4531            {
4532              if (debugEnabled())
4533              {
4534                TRACER.debugCaught(DebugLogLevel.ERROR, e);
4535              }
4536    
4537              Message message = ERR_SCHEMA_BACKUP_CANNOT_GET_DIGEST.get(
4538                  digestAlgorithm, stackTraceToSingleLineString(e));
4539              throw new DirectoryException(
4540                             DirectoryServer.getServerErrorResultCode(), message,
4541                             e);
4542            }
4543          }
4544        }
4545    
4546    
4547        // Create an output stream that will be used to write the archive file.  At
4548        // its core, it will be a file output stream to put a file on the disk.  If
4549        // we are to encrypt the data, then that file output stream will be wrapped
4550        // in a cipher output stream.  The resulting output stream will then be
4551        // wrapped by a zip output stream (which may or may not actually use
4552        // compression).
4553        String filename = null;
4554        OutputStream outputStream;
4555        try
4556        {
4557          filename = SCHEMA_BACKUP_BASE_FILENAME + backupID;
4558          File archiveFile = new File(backupDirectory.getPath() + File.separator +
4559                                      filename);
4560          if (archiveFile.exists())
4561          {
4562            int i=1;
4563            while (true)
4564            {
4565              archiveFile = new File(backupDirectory.getPath() + File.separator +
4566                                     filename  + "." + i);
4567              if (archiveFile.exists())
4568              {
4569                i++;
4570              }
4571              else
4572              {
4573                filename = filename + "." + i;
4574                break;
4575              }
4576            }
4577          }
4578    
4579          outputStream = new FileOutputStream(archiveFile, false);
4580          backupProperties.put(BACKUP_PROPERTY_ARCHIVE_FILENAME, filename);
4581        }
4582        catch (Exception e)
4583        {
4584          if (debugEnabled())
4585          {
4586            TRACER.debugCaught(DebugLogLevel.ERROR, e);
4587          }
4588    
4589          Message message = ERR_SCHEMA_BACKUP_CANNOT_CREATE_ARCHIVE_FILE.
4590              get(String.valueOf(filename), backupDirectory.getPath(),
4591                  getExceptionMessage(e));
4592          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4593                                       message, e);
4594        }
4595    
4596    
4597        // If we should encrypt the data, then wrap the output stream in a cipher
4598        // output stream.
4599        if (encrypt)
4600        {
4601          try
4602          {
4603            outputStream
4604                    = cryptoManager.getCipherOutputStream(outputStream);
4605          }
4606          catch (CryptoManagerException e)
4607          {
4608            if (debugEnabled())
4609            {
4610              TRACER.debugCaught(DebugLogLevel.ERROR, e);
4611            }
4612    
4613            Message message = ERR_SCHEMA_BACKUP_CANNOT_GET_CIPHER.get(
4614                    stackTraceToSingleLineString(e));
4615            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4616                                         message, e);
4617          }
4618        }
4619    
4620    
4621        // Wrap the file output stream in a zip output stream.
4622        ZipOutputStream zipStream = new ZipOutputStream(outputStream);
4623    
4624        Message message = ERR_SCHEMA_BACKUP_ZIP_COMMENT.get(
4625                DynamicConstants.PRODUCT_NAME,
4626                backupID);
4627        zipStream.setComment(String.valueOf(message));
4628    
4629        if (compress)
4630        {
4631          zipStream.setLevel(Deflater.DEFAULT_COMPRESSION);
4632        }
4633        else
4634        {
4635          zipStream.setLevel(Deflater.NO_COMPRESSION);
4636        }
4637    
4638    
4639        // Get the path to the directory in which the schema files reside and
4640        // then get a list of all the files in that directory.
4641        String schemaDirPath = SchemaConfigManager.getSchemaDirectoryPath();
4642        File[] schemaFiles;
4643        try
4644        {
4645          File schemaDir = new File(schemaDirPath);
4646          schemaFiles = schemaDir.listFiles();
4647        }
4648        catch (Exception e)
4649        {
4650          if (debugEnabled())
4651          {
4652            TRACER.debugCaught(DebugLogLevel.ERROR, e);
4653          }
4654    
4655          message = ERR_SCHEMA_BACKUP_CANNOT_LIST_SCHEMA_FILES.get(
4656              schemaDirPath, getExceptionMessage(e));
4657          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4658                                       message, e);
4659        }
4660    
4661    
4662        // Iterate through the schema files and write them to the zip stream.  If
4663        // we're using a hash or MAC, then calculate that as well.
4664        byte[] buffer = new byte[8192];
4665        for (File schemaFile : schemaFiles)
4666        {
4667          if (backupConfig.isCancelled())
4668          {
4669            break;
4670          }
4671    
4672          if (! schemaFile.isFile())
4673          {
4674            // If there are any non-file items in the directory (e.g., one or more
4675            // subdirectories), then we'll skip them.
4676            continue;
4677          }
4678    
4679          String baseName = schemaFile.getName();
4680    
4681    
4682          // We'll put the name in the hash, too.
4683          if (hash)
4684          {
4685            if (signHash)
4686            {
4687              mac.update(getBytes(baseName));
4688            }
4689            else
4690            {
4691              digest.update(getBytes(baseName));
4692            }
4693          }
4694    
4695          InputStream inputStream = null;
4696          try
4697          {
4698            ZipEntry zipEntry = new ZipEntry(baseName);
4699            zipStream.putNextEntry(zipEntry);
4700    
4701            inputStream = new FileInputStream(schemaFile);
4702            while (true)
4703            {
4704              int bytesRead = inputStream.read(buffer);
4705              if (bytesRead < 0 || backupConfig.isCancelled())
4706              {
4707                break;
4708              }
4709    
4710              if (hash)
4711              {
4712                if (signHash)
4713                {
4714                  mac.update(buffer, 0, bytesRead);
4715                }
4716                else
4717                {
4718                  digest.update(buffer, 0, bytesRead);
4719                }
4720              }
4721    
4722              zipStream.write(buffer, 0, bytesRead);
4723            }
4724    
4725            zipStream.closeEntry();
4726            inputStream.close();
4727          }
4728          catch (Exception e)
4729          {
4730            if (debugEnabled())
4731            {
4732              TRACER.debugCaught(DebugLogLevel.ERROR, e);
4733            }
4734    
4735            try
4736            {
4737              inputStream.close();
4738            } catch (Exception e2) {}
4739    
4740            try
4741            {
4742              zipStream.close();
4743            } catch (Exception e2) {}
4744    
4745            message = ERR_SCHEMA_BACKUP_CANNOT_BACKUP_SCHEMA_FILE.get(
4746                baseName, stackTraceToSingleLineString(e));
4747            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4748                                         message, e);
4749          }
4750        }
4751    
4752    
4753        // We're done writing the file, so close the zip stream (which should also
4754        // close the underlying stream).
4755        try
4756        {
4757          zipStream.close();
4758        }
4759        catch (Exception e)
4760        {
4761          if (debugEnabled())
4762          {
4763            TRACER.debugCaught(DebugLogLevel.ERROR, e);
4764          }
4765    
4766          message = ERR_SCHEMA_BACKUP_CANNOT_CLOSE_ZIP_STREAM.get(
4767              filename, backupDirectory.getPath(), stackTraceToSingleLineString(e));
4768          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4769                                       message, e);
4770        }
4771    
4772    
4773        // Get the digest or MAC bytes if appropriate.
4774        byte[] digestBytes = null;
4775        byte[] macBytes    = null;
4776        if (hash)
4777        {
4778          if (signHash)
4779          {
4780            macBytes = mac.doFinal();
4781          }
4782          else
4783          {
4784            digestBytes = digest.digest();
4785          }
4786        }
4787    
4788    
4789        // Create the backup info structure for this backup and add it to the backup
4790        // directory.
4791        // FIXME -- Should I use the date from when I started or finished?
4792        BackupInfo backupInfo = new BackupInfo(backupDirectory, backupID,
4793                                               new Date(), false, compress,
4794                                               encrypt, digestBytes, macBytes,
4795                                               null, backupProperties);
4796    
4797        try
4798        {
4799          backupDirectory.addBackup(backupInfo);
4800          backupDirectory.writeBackupDirectoryDescriptor();
4801        }
4802        catch (Exception e)
4803        {
4804          if (debugEnabled())
4805          {
4806            TRACER.debugCaught(DebugLogLevel.ERROR, e);
4807          }
4808    
4809          message = ERR_SCHEMA_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
4810              backupDirectory.getDescriptorPath(), stackTraceToSingleLineString(e));
4811          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4812                                       message, e);
4813        }
4814      }
4815    
4816    
4817    
4818      /**
4819       * {@inheritDoc}
4820       */
4821      @Override()
4822      public void removeBackup(BackupDirectory backupDirectory,
4823                               String backupID)
4824             throws DirectoryException
4825      {
4826        // This backend does not provide a backup/restore mechanism.
4827        Message message = ERR_SCHEMA_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
4828        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
4829      }
4830    
4831    
4832    
4833      /**
4834       * {@inheritDoc}
4835       */
4836      @Override()
4837      public boolean supportsRestore()
4838      {
4839        // We will provide a restore, but only for offline operations.
4840        return true;
4841      }
4842    
4843    
4844    
4845      /**
4846       * {@inheritDoc}
4847       */
4848      @Override()
4849      public void restoreBackup(RestoreConfig restoreConfig)
4850             throws DirectoryException
4851      {
4852        // First, make sure that the requested backup exists.
4853        BackupDirectory backupDirectory = restoreConfig.getBackupDirectory();
4854        String          backupPath      = backupDirectory.getPath();
4855        String          backupID        = restoreConfig.getBackupID();
4856        BackupInfo      backupInfo      = backupDirectory.getBackupInfo(backupID);
4857        if (backupInfo == null)
4858        {
4859          Message message =
4860              ERR_SCHEMA_RESTORE_NO_SUCH_BACKUP.get(backupID, backupPath);
4861          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4862                                       message);
4863        }
4864    
4865    
4866        // Read the backup info structure to determine the name of the file that
4867        // contains the archive.  Then make sure that file exists.
4868        String backupFilename =
4869             backupInfo.getBackupProperty(BACKUP_PROPERTY_ARCHIVE_FILENAME);
4870        if (backupFilename == null)
4871        {
4872          Message message =
4873              ERR_SCHEMA_RESTORE_NO_BACKUP_FILE.get(backupID, backupPath);
4874          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4875                                       message);
4876        }
4877    
4878        File backupFile = new File(backupPath + File.separator + backupFilename);
4879        try
4880        {
4881          if (! backupFile.exists())
4882          {
4883            Message message =
4884                ERR_SCHEMA_RESTORE_NO_SUCH_FILE.get(backupID, backupFile.getPath());
4885            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4886                                         message);
4887          }
4888        }
4889        catch (DirectoryException de)
4890        {
4891          throw de;
4892        }
4893        catch (Exception e)
4894        {
4895          Message message = ERR_SCHEMA_RESTORE_CANNOT_CHECK_FOR_ARCHIVE.get(
4896              backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
4897          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4898                                       message, e);
4899        }
4900    
4901    
4902        // If the backup is hashed, then we need to get the message digest to use
4903        // to verify it.
4904        byte[] unsignedHash = backupInfo.getUnsignedHash();
4905        MessageDigest digest = null;
4906        if (unsignedHash != null)
4907        {
4908          String digestAlgorithm =
4909               backupInfo.getBackupProperty(BACKUP_PROPERTY_DIGEST_ALGORITHM);
4910          if (digestAlgorithm == null)
4911          {
4912            Message message = ERR_SCHEMA_RESTORE_UNKNOWN_DIGEST.get(backupID);
4913            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4914                                         message);
4915          }
4916    
4917          try
4918          {
4919            digest = DirectoryServer.getCryptoManager().getMessageDigest(
4920                                                             digestAlgorithm);
4921          }
4922          catch (Exception e)
4923          {
4924            Message message =
4925                ERR_SCHEMA_RESTORE_CANNOT_GET_DIGEST.get(backupID, digestAlgorithm);
4926            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4927                                         message, e);
4928          }
4929        }
4930    
4931    
4932        // If the backup is signed, then we need to get the MAC to use to verify it.
4933        byte[] signedHash = backupInfo.getSignedHash();
4934        Mac mac = null;
4935        if (signedHash != null)
4936        {
4937          String macKeyID =
4938               backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID);
4939          if (macKeyID == null)
4940          {
4941            Message message = ERR_SCHEMA_RESTORE_UNKNOWN_MAC.get(backupID);
4942            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4943                                         message);
4944          }
4945    
4946          try
4947          {
4948            mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID);
4949          }
4950          catch (Exception e)
4951          {
4952            Message message = ERR_SCHEMA_RESTORE_CANNOT_GET_MAC.get(
4953                backupID, macKeyID);
4954            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4955                                         message, e);
4956          }
4957        }
4958    
4959    
4960        // Create the input stream that will be used to read the backup file.  At
4961        // its core, it will be a file input stream.
4962        InputStream inputStream;
4963        try
4964        {
4965          inputStream = new FileInputStream(backupFile);
4966        }
4967        catch (Exception e)
4968        {
4969          Message message = ERR_SCHEMA_RESTORE_CANNOT_OPEN_BACKUP_FILE.get(
4970              backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
4971          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4972                                       message, e);
4973        }
4974    
4975        // If the backup is encrypted, then we need to wrap the file input stream
4976        // in a cipher input stream.
4977        if (backupInfo.isEncrypted())
4978        {
4979          try
4980          {
4981            inputStream = DirectoryServer.getCryptoManager()
4982                                             .getCipherInputStream(inputStream);
4983          }
4984          catch (CryptoManagerException e)
4985          {
4986            Message message = ERR_SCHEMA_RESTORE_CANNOT_GET_CIPHER.get(
4987                    backupFile.getPath(), stackTraceToSingleLineString(e));
4988            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
4989                                         message, e);
4990          }
4991        }
4992    
4993        // Now wrap the resulting input stream in a zip stream so that we can read
4994        // its contents.  We don't need to worry about whether to use compression or
4995        // not because it will be handled automatically.
4996        ZipInputStream zipStream = new ZipInputStream(inputStream);
4997    
4998    
4999        // Determine whether we should actually do the restore, or if we should just
5000        // try to verify the archive.  If we are not going to verify only, then
5001        // move the current schema directory out of the way so we can keep it around
5002        // to restore if a problem occurs.
5003        String schemaDirPath   = SchemaConfigManager.getSchemaDirectoryPath();
5004        File   schemaDir       = new File(schemaDirPath);
5005        String backupDirPath   = null;
5006        File   schemaBackupDir = null;
5007        boolean verifyOnly = restoreConfig.verifyOnly();
5008        if (! verifyOnly)
5009        {
5010          // Rename the current schema directory if it exists.
5011          try
5012          {
5013            if (schemaDir.exists())
5014            {
5015              String schemaBackupDirPath = schemaDirPath + ".save";
5016              backupDirPath = schemaBackupDirPath;
5017              schemaBackupDir = new File(backupDirPath);
5018              if (schemaBackupDir.exists())
5019              {
5020                int i=2;
5021                while (true)
5022                {
5023                  backupDirPath = schemaBackupDirPath + i;
5024                  schemaBackupDir = new File(backupDirPath);
5025                  if (schemaBackupDir.exists())
5026                  {
5027                    i++;
5028                  }
5029                  else
5030                  {
5031                    break;
5032                  }
5033                }
5034              }
5035    
5036              schemaDir.renameTo(schemaBackupDir);
5037            }
5038          }
5039          catch (Exception e)
5040          {
5041            Message message = ERR_SCHEMA_RESTORE_CANNOT_RENAME_CURRENT_DIRECTORY.
5042                get(backupID, schemaDirPath, String.valueOf(backupDirPath),
5043                    stackTraceToSingleLineString(e));
5044            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
5045                                         message, e);
5046          }
5047    
5048    
5049          // Create a new directory to hold the restored schema files.
5050          try
5051          {
5052            schemaDir.mkdirs();
5053          }
5054          catch (Exception e)
5055          {
5056            // Try to restore the previous schema directory if possible.  This will
5057            // probably fail in this case, but try anyway.
5058            if (schemaBackupDir != null)
5059            {
5060              try
5061              {
5062                schemaBackupDir.renameTo(schemaDir);
5063                Message message =
5064                    NOTE_SCHEMA_RESTORE_RESTORED_OLD_SCHEMA.get(schemaDirPath);
5065                logError(message);
5066              }
5067              catch (Exception e2)
5068              {
5069                Message message = ERR_SCHEMA_RESTORE_CANNOT_RESTORE_OLD_SCHEMA.get(
5070                    schemaBackupDir.getPath());
5071                logError(message);
5072              }
5073            }
5074    
5075    
5076            Message message = ERR_SCHEMA_RESTORE_CANNOT_CREATE_SCHEMA_DIRECTORY.get(
5077                backupID, schemaDirPath, stackTraceToSingleLineString(e));
5078            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
5079                                         message, e);
5080          }
5081        }
5082    
5083    
5084        // Read through the archive file an entry at a time.  For each entry, update
5085        // the digest or MAC if necessary, and if we're actually doing the restore,
5086        // then write the files out into the schema directory.
5087        byte[] buffer = new byte[8192];
5088        while (true)
5089        {
5090          ZipEntry zipEntry;
5091          try
5092          {
5093            zipEntry = zipStream.getNextEntry();
5094          }
5095          catch (Exception e)
5096          {
5097            // Tell the user where the previous schema was archived.
5098            if (schemaBackupDir != null)
5099            {
5100              Message message = ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED.get(
5101                  schemaBackupDir.getPath());
5102              logError(message);
5103            }
5104    
5105            Message message = ERR_SCHEMA_RESTORE_CANNOT_GET_ZIP_ENTRY.get(
5106                backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
5107            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
5108                                         message, e);
5109          }
5110    
5111          if (zipEntry == null)
5112          {
5113            break;
5114          }
5115    
5116    
5117          // Get the filename for the zip entry and update the digest or MAC as
5118          // necessary.
5119          String fileName = zipEntry.getName();
5120          if (digest != null)
5121          {
5122            digest.update(getBytes(fileName));
5123          }
5124          if (mac != null)
5125          {
5126            mac.update(getBytes(fileName));
5127          }
5128    
5129    
5130          // If we're doing the restore, then create the output stream to write the
5131          // file.
5132          OutputStream outputStream = null;
5133          if (! verifyOnly)
5134          {
5135            String filePath = schemaDirPath + File.separator + fileName;
5136            try
5137            {
5138              outputStream = new FileOutputStream(filePath);
5139            }
5140            catch (Exception e)
5141            {
5142              // Tell the user where the previous schema was archived.
5143              if (schemaBackupDir != null)
5144              {
5145                Message message = ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED.get(
5146                    schemaBackupDir.getPath());
5147                logError(message);
5148              }
5149    
5150              Message message = ERR_SCHEMA_RESTORE_CANNOT_CREATE_FILE.get(
5151                  backupID, filePath, stackTraceToSingleLineString(e));
5152              throw new DirectoryException(
5153                             DirectoryServer.getServerErrorResultCode(), message,
5154                             e);
5155            }
5156          }
5157    
5158    
5159          // Read the contents of the file and update the digest or MAC as
5160          // necessary.  If we're actually restoring it, then write it into the
5161          // new schema directory.
5162          try
5163          {
5164            while (true)
5165            {
5166              int bytesRead = zipStream.read(buffer);
5167              if (bytesRead < 0)
5168              {
5169                // We've reached the end of the entry.
5170                break;
5171              }
5172    
5173    
5174              // Update the digest or MAC if appropriate.
5175              if (digest != null)
5176              {
5177                digest.update(buffer, 0, bytesRead);
5178              }
5179    
5180              if (mac != null)
5181              {
5182                mac.update(buffer, 0, bytesRead);
5183              }
5184    
5185    
5186              //  Write the data to the output stream if appropriate.
5187              if (outputStream != null)
5188              {
5189                outputStream.write(buffer, 0, bytesRead);
5190              }
5191            }
5192    
5193    
5194            // We're at the end of the file so close the output stream if we're
5195            // writing it.
5196            if (outputStream != null)
5197            {
5198              outputStream.close();
5199            }
5200          }
5201          catch (Exception e)
5202          {
5203            // Tell the user where the previous schema was archived.
5204            if (schemaBackupDir != null)
5205            {
5206              Message message = ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED.get(
5207                  schemaBackupDir.getPath());
5208              logError(message);
5209            }
5210    
5211            Message message = ERR_SCHEMA_RESTORE_CANNOT_PROCESS_ARCHIVE_FILE.get(
5212                backupID, fileName, stackTraceToSingleLineString(e));
5213            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
5214                                         message, e);
5215          }
5216        }
5217    
5218    
5219        // Close the zip stream since we don't need it anymore.
5220        try
5221        {
5222          zipStream.close();
5223        }
5224        catch (Exception e)
5225        {
5226          Message message = ERR_SCHEMA_RESTORE_ERROR_ON_ZIP_STREAM_CLOSE.get(
5227              backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
5228          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
5229                                       message, e);
5230        }
5231    
5232    
5233        // At this point, we should be done with the contents of the ZIP file and
5234        // the restore should be complete.  If we were generating a digest or MAC,
5235        // then make sure it checks out.
5236        if (digest != null)
5237        {
5238          byte[] calculatedHash = digest.digest();
5239          if (Arrays.equals(calculatedHash, unsignedHash))
5240          {
5241            Message message = NOTE_SCHEMA_RESTORE_UNSIGNED_HASH_VALID.get();
5242            logError(message);
5243          }
5244          else
5245          {
5246            // Tell the user where the previous schema was archived.
5247            if (schemaBackupDir != null)
5248            {
5249              Message message = ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED.get(
5250                  schemaBackupDir.getPath());
5251              logError(message);
5252            }
5253    
5254            Message message =
5255                ERR_SCHEMA_RESTORE_UNSIGNED_HASH_INVALID.get(backupID);
5256            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
5257                                         message);
5258          }
5259        }
5260    
5261        if (mac != null)
5262        {
5263          byte[] calculatedSignature = mac.doFinal();
5264          if (Arrays.equals(calculatedSignature, signedHash))
5265          {
5266            Message message = NOTE_SCHEMA_RESTORE_SIGNED_HASH_VALID.get();
5267            logError(message);
5268          }
5269          else
5270          {
5271            // Tell the user where the previous schema was archived.
5272            if (schemaBackupDir != null)
5273            {
5274              Message message = ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED.get(
5275                  schemaBackupDir.getPath());
5276              logError(message);
5277            }
5278    
5279            Message message = ERR_SCHEMA_RESTORE_SIGNED_HASH_INVALID.get(
5280                  schemaBackupDir.getPath());
5281            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
5282                                         message);
5283          }
5284        }
5285    
5286    
5287        // If we are just verifying the archive, then we're done.
5288        if (verifyOnly)
5289        {
5290          Message message =
5291              NOTE_SCHEMA_RESTORE_VERIFY_SUCCESSFUL.get(backupID, backupPath);
5292          logError(message);
5293          return;
5294        }
5295    
5296    
5297        // If we've gotten here, then the archive was restored successfully.  Get
5298        // rid of the temporary copy we made of the previous schema directory and
5299        // exit.
5300        if (schemaBackupDir != null)
5301        {
5302          recursiveDelete(schemaBackupDir);
5303        }
5304    
5305        Message message = NOTE_SCHEMA_RESTORE_SUCCESSFUL.get(backupID, backupPath);
5306        logError(message);
5307      }
5308    
5309    
5310    
5311      /**
5312       * {@inheritDoc}
5313       */
5314      public boolean isConfigurationChangeAcceptable(
5315           SchemaBackendCfg configEntry,
5316           List<Message> unacceptableReasons)
5317      {
5318        return true;
5319      }
5320    
5321    
5322    
5323      /**
5324       * {@inheritDoc}
5325       */
5326      public ConfigChangeResult applyConfigurationChange(
5327           SchemaBackendCfg backendCfg)
5328      {
5329        ResultCode        resultCode          = ResultCode.SUCCESS;
5330        boolean           adminActionRequired = false;
5331        ArrayList<Message> messages            = new ArrayList<Message>();
5332    
5333    
5334        // Check to see if we should apply a new set of base DNs.
5335        Set<DN> newBaseDNs;
5336        try
5337        {
5338          newBaseDNs = new HashSet<DN>(backendCfg.getSchemaEntryDN());
5339          if (newBaseDNs.isEmpty())
5340          {
5341            newBaseDNs.add(DN.decode(DN_DEFAULT_SCHEMA_ROOT));
5342          }
5343        }
5344        catch (Exception e)
5345        {
5346          if (debugEnabled())
5347          {
5348            TRACER.debugCaught(DebugLogLevel.ERROR, e);
5349          }
5350    
5351    
5352          messages.add(ERR_SCHEMA_CANNOT_DETERMINE_BASE_DN.get(
5353                  String.valueOf(configEntryDN),
5354                  getExceptionMessage(e)));
5355          resultCode = DirectoryServer.getServerErrorResultCode();
5356          newBaseDNs = null;
5357        }
5358    
5359    
5360        // Check to see if we should change the behavior regarding whether to show
5361        // all schema attributes.
5362        boolean newShowAllAttributes = backendCfg.isShowAllAttributes();
5363    
5364    
5365        // Check to see if there is a new set of user-defined attributes.
5366        ArrayList<Attribute> newUserAttrs = new ArrayList<Attribute>();
5367        try
5368        {
5369          ConfigEntry configEntry = DirectoryServer.getConfigEntry(configEntryDN);
5370          for (List<Attribute> attrs :
5371               configEntry.getEntry().getUserAttributes().values())
5372          {
5373            for (Attribute a : attrs)
5374            {
5375              if (! isSchemaConfigAttribute(a))
5376              {
5377                newUserAttrs.add(a);
5378              }
5379            }
5380          }
5381          for (List<Attribute> attrs :
5382               configEntry.getEntry().getOperationalAttributes().values())
5383          {
5384            for (Attribute a : attrs)
5385            {
5386              if (! isSchemaConfigAttribute(a))
5387              {
5388                newUserAttrs.add(a);
5389              }
5390            }
5391          }
5392        }
5393        catch (ConfigException e)
5394        {
5395          if (debugEnabled())
5396          {
5397            TRACER.debugCaught(DebugLogLevel.ERROR, e);
5398          }
5399    
5400          messages.add(ERR_CONFIG_BACKEND_ERROR_INTERACTING_WITH_BACKEND_ENTRY.get(
5401                  String.valueOf(configEntryDN),
5402                  stackTraceToSingleLineString(e)));
5403          resultCode = DirectoryServer.getServerErrorResultCode();
5404        }
5405    
5406    
5407        if (resultCode == ResultCode.SUCCESS)
5408        {
5409          // Get an array containing the new base DNs to use.
5410          DN[] dnArray = new DN[newBaseDNs.size()];
5411          newBaseDNs.toArray(dnArray);
5412    
5413    
5414          // Determine the set of DNs to add and delete.  When this is done, the
5415          // deleteBaseDNs will contain the set of DNs that should no longer be used
5416          // and should be deregistered from the server, and the newBaseDNs set will
5417          // just contain the set of DNs to add.
5418          HashSet<DN> deleteBaseDNs = new HashSet<DN>(baseDNs.length);
5419          for (DN baseDN : baseDNs)
5420          {
5421            if (! newBaseDNs.remove(baseDN))
5422            {
5423              deleteBaseDNs.add(baseDN);
5424            }
5425          }
5426    
5427          for (DN dn : deleteBaseDNs)
5428          {
5429            try
5430            {
5431              DirectoryServer.deregisterBaseDN(dn);
5432              messages.add(INFO_SCHEMA_DEREGISTERED_BASE_DN.get(
5433                      String.valueOf(dn)));
5434            }
5435            catch (Exception e)
5436            {
5437              if (debugEnabled())
5438              {
5439                TRACER.debugCaught(DebugLogLevel.ERROR, e);
5440              }
5441    
5442              messages.add(ERR_SCHEMA_CANNOT_DEREGISTER_BASE_DN.get(
5443                      String.valueOf(dn),
5444                      getExceptionMessage(e)));
5445              resultCode = DirectoryServer.getServerErrorResultCode();
5446            }
5447          }
5448    
5449          baseDNs = dnArray;
5450          for (DN dn : newBaseDNs)
5451          {
5452            try
5453            {
5454              DirectoryServer.registerBaseDN(dn, this, true);
5455              messages.add(INFO_SCHEMA_REGISTERED_BASE_DN.get(String.valueOf(dn)));
5456            }
5457            catch (Exception e)
5458            {
5459              if (debugEnabled())
5460              {
5461                TRACER.debugCaught(DebugLogLevel.ERROR, e);
5462              }
5463    
5464              messages.add(ERR_SCHEMA_CANNOT_REGISTER_BASE_DN.get(
5465                      String.valueOf(dn),
5466                      getExceptionMessage(e)));
5467              resultCode = DirectoryServer.getServerErrorResultCode();
5468            }
5469          }
5470    
5471    
5472          showAllAttributes = newShowAllAttributes;
5473    
5474    
5475          userDefinedAttributes = newUserAttrs;
5476          Message message = INFO_SCHEMA_USING_NEW_USER_ATTRS.get();
5477          messages.add(message);
5478        }
5479    
5480    
5481        currentConfig = backendCfg;
5482        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
5483      }
5484    
5485    
5486    
5487      /**
5488       * Indicates whether to treat common schema attributes like user attributes
5489       * rather than operational attributes.
5490       *
5491       * @return  {@code true} if common attributes should be treated like user
5492       *          attributes, or {@code false} if not.
5493       */
5494      boolean showAllAttributes()
5495      {
5496        return showAllAttributes;
5497      }
5498    
5499    
5500    
5501      /**
5502       * Specifies whether to treat common schema attributes like user attributes
5503       * rather than operational attributes.
5504       *
5505       * @param  showAllAttributes  Specifies whether to treat common schema
5506       *                            attributes like user attributes rather than
5507       *                            operational attributes.
5508       */
5509      void setShowAllAttributes(boolean showAllAttributes)
5510      {
5511        this.showAllAttributes = showAllAttributes;
5512      }
5513    
5514    
5515    
5516      /**
5517       * {@inheritDoc}
5518       */
5519      public DN getComponentEntryDN()
5520      {
5521        return configEntryDN;
5522      }
5523    
5524    
5525    
5526      /**
5527       * {@inheritDoc}
5528       */
5529      public String getClassName()
5530      {
5531        return CLASS_NAME;
5532      }
5533    
5534    
5535    
5536      /**
5537       * {@inheritDoc}
5538       */
5539      public LinkedHashMap<String,String> getAlerts()
5540      {
5541        LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>();
5542    
5543        alerts.put(ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES,
5544                   ALERT_DESCRIPTION_CANNOT_COPY_SCHEMA_FILES);
5545        alerts.put(ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES,
5546                   ALERT_DESCRIPTION_CANNOT_WRITE_NEW_SCHEMA_FILES);
5547    
5548        return alerts;
5549      }
5550    
5551    
5552    
5553      /**
5554       * Returns an attribute that has the minimum upper bound value removed from
5555       * all of its attribute values.
5556       *
5557       * @param valueSet The original valueset containing the
5558       *                 attribute type definitions.
5559       *
5560       * @return  Attribute that with all of the minimum upper bound values removed
5561       *          from its attribute values.
5562       */
5563      private Attribute
5564      stripMinUpperBoundValues(LinkedHashSet<AttributeValue> valueSet) {
5565    
5566        LinkedHashSet<AttributeValue> valueSetCopy =
5567                                               new LinkedHashSet<AttributeValue>();
5568        for(AttributeValue v : valueSet) {
5569          //If it exists, strip the minimum upper bound value from the
5570          //attribute value.
5571          if(v.toString().indexOf('{') != -1) {
5572            //Create an attribute value from the stripped string and add it to the
5573            //valueset.
5574            String strippedStr=
5575                    v.toString().replaceFirst(stripMinUpperBoundRegEx, "");
5576            ASN1OctetString s=new ASN1OctetString(strippedStr);
5577            AttributeValue strippedVal=new AttributeValue(s,s);
5578            valueSetCopy.add(strippedVal);
5579          } else
5580            valueSetCopy.add(v);
5581        }
5582        return
5583              new Attribute(attributeTypesType, ATTR_ATTRIBUTE_TYPES, valueSetCopy);
5584      }
5585    
5586    
5587    
5588      /**
5589       * {@inheritDoc}
5590       */
5591      public void preloadEntryCache() throws UnsupportedOperationException {
5592        throw new UnsupportedOperationException("Operation not supported.");
5593      }
5594    }
5595