001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.types;
018
019import java.io.BufferedReader;
020import java.io.BufferedWriter;
021import java.io.File;
022import java.io.FileReader;
023import java.io.FileWriter;
024import java.io.FilenameFilter;
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.LinkedHashSet;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.TreeSet;
037import java.util.concurrent.ConcurrentHashMap;
038import java.util.concurrent.locks.Lock;
039import java.util.concurrent.locks.ReentrantLock;
040
041import org.forgerock.i18n.LocalizableMessage;
042import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0;
043import org.forgerock.i18n.LocalizedIllegalArgumentException;
044import org.forgerock.i18n.slf4j.LocalizedLogger;
045import org.forgerock.opendj.ldap.ByteString;
046import org.forgerock.opendj.ldap.ModificationType;
047import org.forgerock.opendj.ldap.ResultCode;
048import org.forgerock.opendj.ldap.schema.AttributeType;
049import org.forgerock.opendj.ldap.schema.ConflictingSchemaElementException;
050import org.forgerock.opendj.ldap.schema.CoreSchema;
051import org.forgerock.opendj.ldap.schema.MatchingRule;
052import org.forgerock.opendj.ldap.schema.SchemaBuilder;
053import org.forgerock.opendj.ldap.schema.Syntax;
054import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException;
055import org.forgerock.util.Option;
056import org.forgerock.util.Utils;
057import org.opends.server.core.DirectoryServer;
058import org.opends.server.core.SchemaConfigManager;
059import org.opends.server.schema.DITContentRuleSyntax;
060import org.opends.server.schema.DITStructureRuleSyntax;
061import org.opends.server.schema.MatchingRuleUseSyntax;
062import org.opends.server.schema.NameFormSyntax;
063import org.opends.server.schema.ObjectClassSyntax;
064import org.opends.server.schema.SomeSchemaElement;
065import org.opends.server.util.ServerConstants;
066import org.opends.server.util.StaticUtils;
067
068import static org.opends.messages.BackendMessages.*;
069import static org.opends.messages.CoreMessages.*;
070import static org.opends.messages.SchemaMessages.*;
071import static org.opends.server.config.ConfigConstants.*;
072import static org.opends.server.types.CommonSchemaElements.*;
073import static org.opends.server.util.CollectionUtils.*;
074import static org.opends.server.util.ServerConstants.*;
075import static org.opends.server.util.StaticUtils.*;
076
077/**
078 * This class defines a data structure that holds information about
079 * the components of the Directory Server schema.  It includes the
080 * following kinds of elements:
081 *
082 * <UL>
083 *   <LI>Attribute type definitions</LI>
084 *   <LI>Objectclass definitions</LI>
085 *   <LI>Attribute syntax definitions</LI>
086 *   <LI>Matching rule definitions</LI>
087 *   <LI>Matching rule use definitions</LI>
088 *   <LI>DIT content rule definitions</LI>
089 *   <LI>DIT structure rule definitions</LI>
090 *   <LI>Name form definitions</LI>
091 * </UL>
092 * It always uses non-strict {@link org.forgerock.opendj.ldap.schema.Schema} under the hood.
093 */
094@org.opends.server.types.PublicAPI(
095     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
096     mayInstantiate=false,
097     mayExtend=false,
098     mayInvoke=true)
099public final class Schema
100{
101  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
102
103  /**
104   * Provides for each attribute type having at least one subordinate type the complete list of
105   * its descendants.
106   */
107  private Map<AttributeType, List<AttributeType>> subordinateTypes;
108
109  /**
110   * The set of objectclass definitions for this schema, mapped between the
111   * lowercase names and OID for the definition and the objectclass itself.
112   */
113  private ConcurrentHashMap<String,ObjectClass> objectClasses;
114
115  /**
116   * The set of matching rule uses for this schema, mapped between the matching
117   * rule for the definition and the matching rule use itself.
118   */
119  private ConcurrentHashMap<MatchingRule,MatchingRuleUse>
120               matchingRuleUses;
121
122  /**
123   * The set of DIT content rules for this schema, mapped between the structural
124   * objectclass for the definition and the DIT content rule itself.
125   */
126  private ConcurrentHashMap<ObjectClass,DITContentRule>
127               ditContentRules;
128
129  /**
130   * The set of DIT structure rules for this schema, mapped between the name
131   * form for the definition and the DIT structure rule itself.
132   */
133  private ConcurrentHashMap<Integer,DITStructureRule>
134               ditStructureRulesByID;
135
136  /**
137   * The set of DIT structure rules for this schema, mapped between the name
138   * form for the definition and the DIT structure rule itself.
139   */
140  private ConcurrentHashMap<NameForm,DITStructureRule>
141               ditStructureRulesByNameForm;
142
143  /**
144   * The set of name forms for this schema, mapped between the structural
145   * objectclass for the definition and the list of name forms.
146   */
147  private ConcurrentHashMap<ObjectClass,List<NameForm>>
148          nameFormsByOC;
149
150  /**
151   * The set of name forms for this schema, mapped between the names/OID and the
152   * name form itself.
153   */
154  private ConcurrentHashMap<String,NameForm> nameFormsByName;
155
156  /**
157   * The set of ldap syntax descriptions for this schema, mapped the OID and the
158   * ldap syntax description itself.
159   */
160  private ConcurrentHashMap<String,LDAPSyntaxDescription>
161          ldapSyntaxDescriptions;
162
163  /** The oldest modification timestamp for any schema configuration file. */
164  private long oldestModificationTime;
165
166  /** The youngest modification timestamp for any schema configuration file. */
167  private long youngestModificationTime;
168
169  /**
170   * A set of extra attributes that are not used directly by the schema but may
171   * be used by other component to store information in the schema.
172   * <p>
173   * ex : Replication uses this to store its state and GenerationID.
174   */
175  private Map<String, Attribute> extraAttributes = new HashMap<>();
176
177  /**
178   * The SDK schema.
179   * <p>
180   * It will progressively take over server implementation of the schema.
181   * <p>
182   * @GuardedBy("exclusiveLock")
183   */
184  private volatile org.forgerock.opendj.ldap.schema.Schema schemaNG;
185
186  /** Guards updates to the schema. */
187  private final Lock exclusiveLock = new ReentrantLock();
188
189  /**
190   * Creates a new schema structure with all elements initialized but empty.
191   *
192   * @param schemaNG
193   *          The SDK schema
194   * @throws DirectoryException
195   *           if the schema has warnings
196   */
197  public Schema(org.forgerock.opendj.ldap.schema.Schema schemaNG) throws DirectoryException
198  {
199    switchSchema(schemaNG);
200
201    objectClasses = new ConcurrentHashMap<String,ObjectClass>();
202    matchingRuleUses = new ConcurrentHashMap<MatchingRule,MatchingRuleUse>();
203    ditContentRules = new ConcurrentHashMap<ObjectClass,DITContentRule>();
204    ditStructureRulesByID = new ConcurrentHashMap<Integer,DITStructureRule>();
205    ditStructureRulesByNameForm = new ConcurrentHashMap<NameForm,DITStructureRule>();
206    nameFormsByOC = new ConcurrentHashMap<ObjectClass,List<NameForm>>();
207    nameFormsByName = new ConcurrentHashMap<String,NameForm>();
208    ldapSyntaxDescriptions = new ConcurrentHashMap<String,LDAPSyntaxDescription>();
209    subordinateTypes = new ConcurrentHashMap<AttributeType,List<AttributeType>>();
210
211    oldestModificationTime    = System.currentTimeMillis();
212    youngestModificationTime  = oldestModificationTime;
213  }
214
215  /**
216   * Returns the SDK schema.
217   *
218   * @return the SDK schema
219   */
220  public org.forgerock.opendj.ldap.schema.Schema getSchemaNG()
221  {
222    return schemaNG;
223  }
224
225  /**
226   * Retrieves the attribute type definitions for this schema.
227   *
228   * @return  The attribute type definitions for this schema.
229   */
230  public Collection<AttributeType> getAttributeTypes()
231  {
232    return schemaNG.getAttributeTypes();
233  }
234
235  /**
236   * Indicates whether this schema definition includes an attribute
237   * type with the provided name or OID.
238   *
239   * @param  nameOrOid  The name or OID for which to make the determination, ignoring case considerations
240   * @return  {@code true} if this schema contains an attribute type
241   *          with the provided name or OID, or {@code false} if not.
242   */
243  public boolean hasAttributeType(String nameOrOid)
244  {
245    return schemaNG.hasAttributeType(nameOrOid);
246  }
247
248  /**
249   * Retrieves the attribute type definition with the specified name or OID.
250   *
251   * @param nameOrOid
252   *          The name or OID of the attribute type to retrieve, ignoring case considerations
253   * @return The requested attribute type
254   */
255  public AttributeType getAttributeType(String nameOrOid)
256  {
257    try
258    {
259      return schemaNG.getAttributeType(nameOrOid);
260    }
261    catch (UnknownSchemaElementException e)
262    {
263      // It should never happen because we only use non-strict schemas
264      throw new RuntimeException(e);
265    }
266  }
267
268  /**
269   * Retrieves the attribute type definition with the specified name or OID.
270   *
271   * @param nameOrOid
272   *          The name or OID of the attribute type to retrieve, ignoring case considerations
273   * @param syntax
274   *          The syntax to use when creating the temporary "place-holder" attribute type.
275   * @return The requested attribute type
276   */
277  public AttributeType getAttributeType(String nameOrOid, Syntax syntax)
278  {
279    try
280    {
281      return schemaNG.getAttributeType(nameOrOid, syntax);
282    }
283    catch (UnknownSchemaElementException e)
284    {
285      // It should never happen because we only use non-strict schemas
286      throw new RuntimeException(e);
287    }
288  }
289
290  /**
291   * Parses an attribute type from its provided definition.
292   *
293   * @param definition
294   *          The definition of the attribute type
295   * @return the attribute type
296   * @throws DirectoryException
297   *            If an error occurs
298   */
299  public AttributeType parseAttributeType(final String definition) throws DirectoryException
300  {
301    try
302    {
303      SchemaBuilder builder = new SchemaBuilder(schemaNG);
304      builder.addAttributeType(definition, true);
305      org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema();
306      rejectSchemaWithWarnings(newSchema);
307      return newSchema.getAttributeType(parseAttributeTypeOID(definition));
308    }
309    catch (UnknownSchemaElementException e)
310    {
311      // this should never happen
312      LocalizableMessage msg = ERR_ATTR_TYPE_CANNOT_REGISTER.get(definition);
313      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e);
314    }
315    catch (LocalizedIllegalArgumentException e)
316    {
317      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
318    }
319  }
320
321  /**
322   * Registers a list of attribute types from their provided definitions.
323   * <p>
324   * This method allows to do only one schema change for multiple definitions,
325   * thus avoiding the cost (and the issue of stale schema references) of rebuilding a new schema for each definition.
326   *
327   * @param definitions
328   *          The definitions of the attribute types
329   * @param schemaFile
330   *          The schema file where these definitions belong, can be {@code null}
331   * @param overwrite
332   *          Indicates whether to overwrite the attribute
333   *          type if it already exists based on OID or name
334   * @throws DirectoryException
335   *            If an error occurs
336   */
337  public void registerAttributeTypes(final List<String> definitions, final String schemaFile, final boolean overwrite)
338      throws DirectoryException
339  {
340    exclusiveLock.lock();
341    try
342    {
343      SchemaBuilder builder = new SchemaBuilder(schemaNG);
344      for (String definition : definitions)
345      {
346        String defWithFile = schemaFile != null ?
347            addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile) : definition;
348        builder.addAttributeType(defWithFile, overwrite);
349      }
350      switchSchema(builder.toSchema());
351
352      for (String definition : definitions)
353      {
354        updateSubordinateTypes(schemaNG.getAttributeType(parseAttributeTypeOID(definition)));
355      }
356    }
357    catch (ConflictingSchemaElementException | UnknownSchemaElementException e)
358    {
359      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
360    }
361    catch (LocalizedIllegalArgumentException e)
362    {
363      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
364    }
365    finally
366    {
367      exclusiveLock.unlock();
368    }
369  }
370
371  /**
372   * Registers an attribute type from its provided definition.
373   *
374   * @param definition
375   *          The definition of the attribute type
376   * @param schemaFile
377   *          The schema file where this definition belongs,
378   *          maybe {@code null}
379   * @param overwrite
380   *          Indicates whether to overwrite the attribute
381   *          type if it already exists based on OID or name
382   * @throws DirectoryException
383   *            If an error occurs
384   */
385  public void registerAttributeType(final String definition, final String schemaFile, final boolean overwrite)
386      throws DirectoryException
387  {
388    registerAttributeTypes(Arrays.asList(definition), schemaFile, overwrite);
389  }
390
391  /**
392   * Registers the provided attribute type definition with this schema.
393   *
394   * @param attributeType
395   *          The attribute type to register with this schema.
396   * @param overwriteExisting
397   *          Indicates whether to overwrite an existing mapping if there are
398   *          any conflicts (i.e., another attribute type with the same OID or
399   *          name).
400   * @throws DirectoryException
401   *           If a conflict is encountered and the
402   *           <CODE>overwriteExisting</CODE> flag is set to <CODE>false</CODE>
403   */
404  public void registerAttributeType(final AttributeType attributeType, final boolean overwriteExisting)
405      throws DirectoryException
406  {
407    registerAttributeType(attributeType, null, overwriteExisting);
408  }
409
410  /**
411   * Registers the provided attribute type definition with this schema.
412   *
413   * @param attributeType
414   *          The attribute type to register with this schema.
415   * @param schemaFile
416   *          The schema file where this definition belongs, maybe {@code null}
417   * @param overwriteExisting
418   *          Indicates whether to overwrite an existing mapping if there are
419   *          any conflicts (i.e., another attribute type with the same OID or
420   *          name).
421   * @throws DirectoryException
422   *           If a conflict is encountered and the
423   *           <CODE>overwriteExisting</CODE> flag is set to <CODE>false</CODE>
424   */
425  public void registerAttributeType(final AttributeType attributeType, final String schemaFile,
426      final boolean overwriteExisting) throws DirectoryException
427  {
428    exclusiveLock.lock();
429    try
430    {
431      SchemaBuilder builder = new SchemaBuilder(schemaNG);
432      AttributeType.Builder b = builder.buildAttributeType(attributeType);
433      if (schemaFile != null)
434      {
435        b.removeExtraProperty(SCHEMA_PROPERTY_FILENAME).extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile);
436      }
437      if (overwriteExisting)
438      {
439        b.addToSchemaOverwrite();
440      }
441      else
442      {
443        b.addToSchema();
444      }
445      switchSchema(builder.toSchema());
446
447      updateSubordinateTypes(attributeType);
448    }
449    catch (LocalizedIllegalArgumentException e)
450    {
451      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
452    }
453    finally
454    {
455      exclusiveLock.unlock();
456    }
457  }
458
459  private String parseAttributeTypeOID(String definition) throws DirectoryException
460  {
461    return parseOID(definition, ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE);
462  }
463
464  /**
465   * Returns the OID from the provided attribute type definition, assuming the
466   * definition is valid.
467   * <p>
468   * This method does not perform any check.
469   *
470   * @param definition
471   *          The definition, assumed to be valid
472   * @param parsingErrorResultCode the result code to use if a problem occurs while parsing the definition
473   * @param parsingErrorMsg the message to use if a problem occurs while parsing the definition
474   * @return the OID, which is never {@code null}
475   * @throws DirectoryException
476   *           If a problem occurs while parsing the definition
477   */
478  public static String parseOID(String definition, ResultCode parsingErrorResultCode, Arg0 parsingErrorMsg)
479      throws DirectoryException
480  {
481    try
482    {
483      int pos = 0;
484      int length = definition.length();
485      // Skip over any leading whitespace.
486      while (pos < length && (definition.charAt(pos) == ' '))
487      {
488        pos++;
489      }
490      // Skip the open parenthesis.
491      pos++;
492      // Skip over any spaces immediately following the opening parenthesis.
493      while (pos < length && definition.charAt(pos) == ' ')
494      {
495        pos++;
496      }
497      // The next set of characters must be the OID.
498      int oidStartPos = pos;
499      while (pos < length && definition.charAt(pos) != ' ' && definition.charAt(pos) != ')')
500      {
501        pos++;
502      }
503      return definition.substring(oidStartPos, pos);
504    }
505    catch (IndexOutOfBoundsException e)
506    {
507      throw new DirectoryException(parsingErrorResultCode, parsingErrorMsg.get(), e);
508    }
509  }
510
511  /**
512   * Deregisters the provided attribute type definition with this schema.
513   *
514   * @param  attributeType  The attribute type to deregister with this schema.
515   * @throws DirectoryException
516   *           If the attribute type is referenced by another schema element.
517   */
518  public void deregisterAttributeType(final AttributeType attributeType) throws DirectoryException
519  {
520    exclusiveLock.lock();
521    try
522    {
523      SchemaBuilder builder = new SchemaBuilder(schemaNG);
524      if (builder.removeAttributeType(attributeType.getNameOrOID()))
525      {
526        AttributeType superiorType = attributeType.getSuperiorType();
527        if (superiorType != null)
528        {
529          deregisterSubordinateType(attributeType, superiorType);
530        }
531        switchSchema(builder.toSchema());
532      }
533    }
534    finally
535    {
536      exclusiveLock.unlock();
537    }
538  }
539
540  private void updateSubordinateTypes(AttributeType attributeType)
541  {
542    AttributeType superiorType = attributeType.getSuperiorType();
543    if (superiorType != null)
544    {
545      registerSubordinateType(attributeType, superiorType);
546    }
547  }
548
549  /**
550   * Registers the provided attribute type as a subtype of the given
551   * superior attribute type, recursively following any additional
552   * elements in the superior chain.
553   *
554   * @param  attributeType  The attribute type to be registered as a
555   *                        subtype for the given superior type.
556   * @param  superiorType   The superior type for which to register
557   *                        the given attribute type as a subtype.
558   */
559  private void registerSubordinateType(AttributeType attributeType, AttributeType superiorType)
560  {
561    List<AttributeType> subTypes = subordinateTypes.get(superiorType);
562    if (subTypes == null)
563    {
564      subordinateTypes.put(superiorType, newLinkedList(attributeType));
565    }
566    else if (!subTypes.contains(attributeType))
567    {
568      subTypes.add(attributeType);
569
570      AttributeType higherSuperior = superiorType.getSuperiorType();
571      if (higherSuperior != null)
572      {
573        registerSubordinateType(attributeType, higherSuperior);
574      }
575    }
576  }
577
578  /**
579   * Deregisters the provided attribute type as a subtype of the given
580   * superior attribute type, recursively following any additional
581   * elements in the superior chain.
582   *
583   * @param  attributeType  The attribute type to be deregistered as a
584   *                        subtype for the given superior type.
585   * @param  superiorType   The superior type for which to deregister
586   *                        the given attribute type as a subtype.
587   */
588  private void deregisterSubordinateType(AttributeType attributeType, AttributeType superiorType)
589  {
590    List<AttributeType> subTypes = subordinateTypes.get(superiorType);
591    if (subTypes != null && subTypes.remove(attributeType))
592    {
593      AttributeType higherSuperior = superiorType.getSuperiorType();
594      if (higherSuperior != null)
595      {
596        deregisterSubordinateType(attributeType, higherSuperior);
597      }
598    }
599  }
600
601  /**
602   * Retrieves the set of subtypes registered for the given attribute
603   * type.
604   *
605   * @param  attributeType  The attribute type for which to retrieve
606   *                        the set of registered subtypes.
607   *
608   * @return  The set of subtypes registered for the given attribute
609   *          type, or an empty set if there are no subtypes
610   *          registered for the attribute type.
611   */
612  public List<AttributeType> getSubTypes(AttributeType attributeType)
613  {
614    List<AttributeType> subTypes = subordinateTypes.get(attributeType);
615    return subTypes != null ? subTypes : Collections.<AttributeType> emptyList();
616  }
617
618
619
620  /**
621   * Retrieves the objectclass definitions for this schema, as a
622   * mapping between the lowercase names and OIDs for the objectclass
623   * and the objectclass itself.  Each objectclass may be associated
624   * with multiple keys (once for the OID and again for each name).
625   * The contents of the returned mapping must not be altered.
626   *
627   * @return  The objectclass definitions for this schema.
628   */
629  public ConcurrentHashMap<String,ObjectClass> getObjectClasses()
630  {
631    return objectClasses;
632  }
633
634
635
636  /**
637   * Indicates whether this schema definition includes an objectclass
638   * with the provided name or OID.
639   *
640   * @param  lowerName  The name or OID for which to make the
641   *                    determination, formatted in all lowercase
642   *                    characters.
643   *
644   * @return  {@code true} if this schema contains an objectclass with
645   *          the provided name or OID, or {@code false} if not.
646   */
647  public boolean hasObjectClass(String lowerName)
648  {
649    return objectClasses.containsKey(lowerName);
650  }
651
652
653
654  /**
655   * Retrieves the objectclass definition with the specified name or
656   * OID.
657   *
658   * @param  lowerName  The name or OID of the objectclass to
659   *                    retrieve, formatted in all lowercase
660   *                    characters.
661   *
662   * @return  The requested objectclass, or <CODE>null</CODE> if no
663   *          class is registered with the provided name or OID.
664   */
665  public ObjectClass getObjectClass(String lowerName)
666  {
667    return objectClasses.get(lowerName);
668  }
669
670
671
672  /**
673   * Registers the provided objectclass definition with this schema.
674   *
675   * @param  objectClass        The objectclass to register with this
676   *                            schema.
677   * @param  overwriteExisting  Indicates whether to overwrite an
678   *                            existing mapping if there are any
679   *                            conflicts (i.e., another objectclass
680   *                            with the same OID or name).
681   *
682   * @throws  DirectoryException  If a conflict is encountered and the
683   *                              <CODE>overwriteExisting</CODE> flag
684   *                              is set to <CODE>false</CODE>.
685   */
686  public void registerObjectClass(ObjectClass objectClass,
687                                  boolean overwriteExisting)
688         throws DirectoryException
689  {
690    exclusiveLock.lock();
691    try
692    {
693      if (! overwriteExisting)
694      {
695        String oid = toLowerCase(objectClass.getOID());
696        if (objectClasses.containsKey(oid))
697        {
698          ObjectClass conflictingClass = objectClasses.get(oid);
699
700          LocalizableMessage message = ERR_SCHEMA_CONFLICTING_OBJECTCLASS_OID.
701              get(objectClass.getNameOrOID(), oid,
702                  conflictingClass.getNameOrOID());
703          throw new DirectoryException(
704                       ResultCode.CONSTRAINT_VIOLATION, message);
705        }
706
707        for (String name : objectClass.getNormalizedNames())
708        {
709          if (objectClasses.containsKey(name))
710          {
711            ObjectClass conflictingClass = objectClasses.get(name);
712
713            LocalizableMessage message = ERR_SCHEMA_CONFLICTING_OBJECTCLASS_NAME.
714                get(objectClass.getNameOrOID(), name,
715                    conflictingClass.getNameOrOID());
716            throw new DirectoryException(
717                           ResultCode.CONSTRAINT_VIOLATION, message);
718          }
719        }
720      }
721
722      ObjectClass old = objectClasses.put(toLowerCase(objectClass.getOID()),
723          objectClass);
724      if (old != null && old != objectClass)
725      {
726        // Mark the old object class as stale so that caches (such as compressed
727        // schema) can detect changes.
728        old.setDirty();
729      }
730
731      for (String name : objectClass.getNormalizedNames())
732      {
733        objectClasses.put(name, objectClass);
734      }
735    }
736    finally
737    {
738      exclusiveLock.unlock();
739    }
740  }
741
742
743
744  /**
745   * Deregisters the provided objectclass definition with this schema.
746   *
747   * @param  objectClass  The objectclass to deregister with this
748   *                      schema.
749   */
750  public void deregisterObjectClass(ObjectClass objectClass)
751  {
752    synchronized (objectClasses)
753    {
754      if (objectClasses.remove(toLowerCase(objectClass.getOID()), objectClass))
755      {
756        // Mark the old object class as stale so that caches (such as
757        // compressed schema) can detect changes.
758        objectClass.setDirty();
759      }
760
761      for (String name : objectClass.getNormalizedNames())
762      {
763        objectClasses.remove(name, objectClass);
764      }
765    }
766  }
767
768
769
770  /**
771   * Retrieves the attribute syntax definitions for this schema.
772   *
773   * @return  The attribute syntax definitions for this schema.
774   */
775  public Collection<Syntax> getSyntaxes()
776  {
777    return schemaNG.getSyntaxes();
778  }
779
780
781
782  /**
783   * Indicates whether this schema definition includes an attribute
784   * syntax with the provided OID.
785   *
786   * @param  oid  The OID for which to make the determination
787   * @return  {@code true} if this schema contains an attribute syntax
788   *          with the provided OID, or {@code false} if not.
789   */
790  public boolean hasSyntax(String oid)
791  {
792    return schemaNG.hasSyntax(oid);
793  }
794
795  /**
796   * Retrieves the attribute syntax definition with the OID.
797   *
798   * @param  oid  The OID of the attribute syntax to retrieve.
799   * @return  The requested attribute syntax,
800   *          or {@code null} if no syntax is registered with the provided OID.
801   */
802  public Syntax getSyntax(String oid)
803  {
804    return schemaNG.getSyntax(oid);
805  }
806
807  /**
808   * Retrieves the default attribute syntax that should be used for attributes
809   * that are not defined in the server schema.
810   *
811   * @return  The default attribute syntax that should be used for attributes
812   *          that are not defined in the server schema.
813   */
814  public Syntax getDefaultSyntax()
815  {
816    return schemaNG.getSyntax(CoreSchema.getDirectoryStringSyntax().getOID());
817  }
818
819  /**
820   * Registers the provided attribute syntax definition with this
821   * schema.
822   *
823   * @param  syntax             The attribute syntax to register with
824   *                            this schema.
825   * @param  overwriteExisting  Indicates whether to overwrite an
826   *                            existing mapping if there are any
827   *                            conflicts (i.e., another attribute
828   *                            syntax with the same OID).
829   *
830   * @throws  DirectoryException  If a conflict is encountered and the
831   *                              <CODE>overwriteExisting</CODE> flag
832   *                              is set to <CODE>false</CODE>
833   */
834  public void registerSyntax(final Syntax syntax, final boolean overwriteExisting) throws DirectoryException
835  {
836    exclusiveLock.lock();
837    try
838    {
839      SchemaBuilder builder = new SchemaBuilder(schemaNG);
840      Syntax.Builder b = builder.buildSyntax(syntax);
841      if (overwriteExisting)
842      {
843        b.addToSchemaOverwrite();
844      }
845      else
846      {
847        b.addToSchema();
848      }
849      switchSchema(builder.toSchema());
850    }
851    catch (LocalizedIllegalArgumentException e)
852    {
853      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
854    }
855    finally
856    {
857      exclusiveLock.unlock();
858    }
859  }
860
861  private void registerSyntax(final String definition, final boolean overwriteExisting) throws DirectoryException
862  {
863    exclusiveLock.lock();
864    try
865    {
866      org.forgerock.opendj.ldap.schema.Schema newSchema =
867          new SchemaBuilder(schemaNG)
868          .addSyntax(definition, overwriteExisting)
869          .toSchema();
870      switchSchema(newSchema);
871    }
872    catch (ConflictingSchemaElementException | UnknownSchemaElementException e)
873    {
874      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
875    }
876    catch (LocalizedIllegalArgumentException e)
877    {
878      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
879    }
880    finally
881    {
882      exclusiveLock.unlock();
883    }
884  }
885
886  /**
887   * Deregisters the provided attribute syntax definition with this schema.
888   *
889   * @param  syntax  The attribute syntax to deregister with this schema.
890   * @throws DirectoryException
891   *           If the LDAP syntax is referenced by another schema element.
892   */
893  public void deregisterSyntax(final Syntax syntax) throws DirectoryException
894  {
895    exclusiveLock.lock();
896    try
897    {
898      SchemaBuilder builder = new SchemaBuilder(schemaNG);
899      builder.removeSyntax(syntax.getOID());
900      switchSchema(builder.toSchema());
901    }
902    finally
903    {
904      exclusiveLock.unlock();
905    }
906  }
907
908
909
910  /**
911   * Retrieves the ldap syntax definitions for this schema, as a
912   * mapping between the OID for the syntax and the ldap syntax
913   * definition itself. Each ldap syntax should only be present once,
914   * since its only key is its OID.  The contents of the returned
915   * mapping must not be altered.
916   *
917   * @return  The ldap syntax definitions for this schema.
918   */
919  public ConcurrentHashMap<String,LDAPSyntaxDescription> getLdapSyntaxDescriptions()
920  {
921    return ldapSyntaxDescriptions;
922  }
923
924  /**
925   * Retrieves the ldap syntax definition with the OID.
926   *
927   * @param  lowerName  The OID of the ldap syntax to retrieve,
928   *                    formatted in all lowercase characters.
929   *
930   * @return  The requested ldap syntax, or <CODE>null</CODE> if
931   *          no syntax is registered with the provided OID.
932   */
933  public LDAPSyntaxDescription getLdapSyntaxDescription(String lowerName)
934  {
935    return ldapSyntaxDescriptions.get(lowerName);
936  }
937
938  /**
939   * Registers the provided ldap syntax description with this schema.
940   *
941   * @param definition
942   *          The ldap syntax definition to register with this schema.
943   * @param overwriteExisting
944   *          Indicates whether to overwrite an existing mapping if there are
945   *          any conflicts (i.e., another ldap syntax with the same OID).
946   * @throws DirectoryException
947   *           If a conflict is encountered and <CODE>overwriteExisting</CODE>
948   *           flag is set to <CODE>false</CODE>
949   */
950  public void registerLdapSyntaxDescription(String definition, boolean overwriteExisting)
951      throws DirectoryException
952  {
953    /**
954     * ldapsyntaxes is part real and part virtual. For any
955     * ldapsyntaxes attribute this is real, an LDAPSyntaxDescription
956     * object is created and stored with the schema. Also, the
957     * associated LDAPSyntaxDescriptionSyntax is added into the
958     * virtual syntax set to make this available through virtual
959     * ldapsyntaxes attribute.
960     */
961    exclusiveLock.lock();
962    try
963    {
964      String oid = parseAttributeTypeOID(definition);
965      if (! overwriteExisting && ldapSyntaxDescriptions.containsKey(oid))
966      {
967        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
968            ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_LDAP_SYNTAX.get(oid));
969      }
970
971      // Register the attribute syntax with the schema.
972      // It will ensure syntax is available along with the other virtual values for ldapsyntaxes.
973      registerSyntax(definition, overwriteExisting);
974
975      Syntax syntax = schemaNG.getSyntax(oid);
976      LDAPSyntaxDescription syntaxDesc = new LDAPSyntaxDescription(definition, oid, syntax.getExtraProperties());
977      ldapSyntaxDescriptions.put(oid, syntaxDesc);
978    }
979    finally
980    {
981      exclusiveLock.unlock();
982    }
983  }
984
985
986
987  /**
988   * Deregisters the provided ldap syntax description with this schema.
989   *
990   * @param syntaxDesc
991   *          The ldap syntax to deregister with this schema.
992   * @throws DirectoryException
993   *           If the LDAP syntax is referenced by another schema element.
994   */
995  public void deregisterLdapSyntaxDescription(LDAPSyntaxDescription syntaxDesc) throws DirectoryException
996  {
997    exclusiveLock.lock();
998    try
999    {
1000      // Remove the real value.
1001      ldapSyntaxDescriptions.remove(toLowerCase(syntaxDesc.getOID()), syntaxDesc);
1002
1003      // Get rid of this from the virtual ldapsyntaxes.
1004      deregisterSyntax(getSyntax(syntaxDesc.getOID()));
1005    }
1006    finally
1007    {
1008      exclusiveLock.unlock();
1009    }
1010  }
1011
1012
1013
1014  /**
1015   * Retrieves all matching rule definitions for this schema.
1016   *
1017   * @return The matching rule definitions for this schema
1018   */
1019  public Collection<MatchingRule> getMatchingRules()
1020  {
1021    return schemaNG.getMatchingRules();
1022  }
1023
1024
1025
1026  /**
1027   * Indicates whether this schema definition includes a matching rule
1028   * with the provided name or OID.
1029   *
1030   * @param  nameOrOid  The name or OID for which to make the determination, ignoring case considerations
1031   * @return  {@code true} if this schema contains a matching rule
1032   *          with the provided name or OID, or {@code false} if not.
1033   */
1034  public boolean hasMatchingRule(String nameOrOid)
1035  {
1036    return schemaNG.hasMatchingRule(nameOrOid);
1037  }
1038
1039
1040
1041  /**
1042   * Retrieves the matching rule definition with the specified name or OID.
1043   *
1044   * @param nameOrOid The name or OID of the matching rule to retrieve, ignoring case considerations
1045   * @return The requested matching rule, or {@code null} if no rule is registered with the provided name or OID.
1046   */
1047  public MatchingRule getMatchingRule(String nameOrOid)
1048  {
1049    return schemaNG.getMatchingRule(nameOrOid);
1050  }
1051
1052
1053
1054  /**
1055   * Registers the provided matching rule definition with this schema.
1056   *
1057   * @param matchingRule
1058   *          The matching rule to register with this schema.
1059   * @param overwriteExisting
1060   *          Indicates whether to overwrite an existing mapping if there are
1061   *          any conflicts (i.e., another matching rule with the same OID or
1062   *          name).
1063   * @throws DirectoryException
1064   *           If a conflict is encountered and the
1065   *           {@code overwriteExisting} flag is set to {@code false}
1066   */
1067  public void registerMatchingRule(final MatchingRule matchingRule, final boolean overwriteExisting)
1068         throws DirectoryException
1069  {
1070    exclusiveLock.lock();
1071    try
1072    {
1073      if (!overwriteExisting)
1074      {
1075        // check all names of the matching rules because it is not checked by SDK schema
1076        for (String name : matchingRule.getNames())
1077        {
1078          if (schemaNG.hasMatchingRule(name))
1079          {
1080            Collection<MatchingRule> conflictingRules = schemaNG.getMatchingRulesWithName(name);
1081            // there should be only one
1082            MatchingRule conflictingRule = conflictingRules.iterator().next();
1083
1084            LocalizableMessage message =
1085                ERR_SCHEMA_CONFLICTING_MR_NAME.get(matchingRule.getOID(), name, conflictingRule.getOID());
1086            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1087          }
1088        }
1089      }
1090
1091      // now register the matching rule
1092      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1093      MatchingRule.Builder b = builder.buildMatchingRule(matchingRule);
1094      if (overwriteExisting)
1095      {
1096        b.addToSchemaOverwrite();
1097      }
1098      else
1099      {
1100        b.addToSchema();
1101      }
1102      switchSchema(builder.toSchema());
1103    }
1104    catch (LocalizedIllegalArgumentException e)
1105    {
1106      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
1107    }
1108    finally
1109    {
1110      exclusiveLock.unlock();
1111    }
1112  }
1113
1114  /**
1115   * Deregisters the provided matching rule definition with this schema.
1116   *
1117   * @param matchingRule
1118   *          The matching rule to deregister with this schema.
1119   * @throws DirectoryException
1120   *           If the matching rule is referenced by another schema element.
1121   */
1122  public void deregisterMatchingRule(final MatchingRule matchingRule) throws DirectoryException
1123  {
1124    exclusiveLock.lock();
1125    try
1126    {
1127      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1128      builder.removeMatchingRule(matchingRule.getNameOrOID());
1129      switchSchema(builder.toSchema());
1130    }
1131    finally
1132    {
1133      exclusiveLock.unlock();
1134    }
1135  }
1136
1137
1138  /**
1139   * Retrieves the matching rule use definitions for this schema, as a
1140   * mapping between the matching rule for the matching rule use
1141   * definition and the matching rule use itself.  Each matching rule
1142   * use should only be present once, since its only key is its
1143   * matching rule.  The contents of the returned mapping must not be
1144   * altered.
1145   *
1146   * @return  The matching rule use definitions for this schema.
1147   */
1148  public ConcurrentHashMap<MatchingRule,MatchingRuleUse>
1149              getMatchingRuleUses()
1150  {
1151    return matchingRuleUses;
1152  }
1153
1154
1155
1156  /**
1157   * Retrieves the matching rule use definition for the specified
1158   * matching rule.
1159   *
1160   * @param  matchingRule  The matching rule for which to retrieve the
1161   *                       matching rule use definition.
1162   *
1163   * @return  The matching rule use definition, or <CODE>null</CODE>
1164   *          if none exists for the specified matching rule.
1165   */
1166  public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
1167  {
1168    return matchingRuleUses.get(matchingRule);
1169  }
1170
1171
1172
1173  /**
1174   * Registers the provided matching rule use definition with this
1175   * schema.
1176   *
1177   * @param  matchingRuleUse    The matching rule use definition to
1178   *                            register.
1179   * @param  overwriteExisting  Indicates whether to overwrite an
1180   *                            existing mapping if there are any
1181   *                            conflicts (i.e., another matching rule
1182   *                            use with the same matching rule).
1183   *
1184   * @throws  DirectoryException  If a conflict is encountered and the
1185   *                              <CODE>overwriteExisting</CODE> flag
1186   *                              is set to <CODE>false</CODE>
1187   */
1188  public void registerMatchingRuleUse(MatchingRuleUse matchingRuleUse,
1189                                      boolean overwriteExisting)
1190         throws DirectoryException
1191  {
1192    synchronized (matchingRuleUses)
1193    {
1194      MatchingRule matchingRule = matchingRuleUse.getMatchingRule();
1195
1196      if (!overwriteExisting && matchingRuleUses.containsKey(matchingRule))
1197      {
1198        MatchingRuleUse conflictingUse = matchingRuleUses.get(matchingRule);
1199
1200        LocalizableMessage message = ERR_SCHEMA_CONFLICTING_MATCHING_RULE_USE.
1201            get(matchingRuleUse.getNameOrOID(),
1202                matchingRule.getNameOrOID(),
1203                conflictingUse.getNameOrOID());
1204        throw new DirectoryException(
1205                       ResultCode.CONSTRAINT_VIOLATION, message);
1206      }
1207
1208      matchingRuleUses.put(matchingRule, matchingRuleUse);
1209    }
1210  }
1211
1212
1213
1214  /**
1215   * Deregisters the provided matching rule use definition with this
1216   * schema.
1217   *
1218   * @param  matchingRuleUse  The matching rule use to deregister with
1219   *                          this schema.
1220   */
1221  public void deregisterMatchingRuleUse(
1222                   MatchingRuleUse matchingRuleUse)
1223  {
1224    synchronized (matchingRuleUses)
1225    {
1226      matchingRuleUses.remove(matchingRuleUse.getMatchingRule(),
1227                              matchingRuleUse);
1228    }
1229  }
1230
1231
1232
1233  /**
1234   * Retrieves the DIT content rule definitions for this schema, as a
1235   * mapping between the objectclass for the rule and the DIT content
1236   * rule itself.  Each DIT content rule should only be present once,
1237   * since its only key is its objectclass.  The contents of the
1238   * returned mapping must not be altered.
1239   *
1240   * @return  The DIT content rule definitions for this schema.
1241   */
1242  public ConcurrentHashMap<ObjectClass,DITContentRule>
1243              getDITContentRules()
1244  {
1245    return ditContentRules;
1246  }
1247
1248
1249
1250  /**
1251   * Retrieves the DIT content rule definition for the specified
1252   * objectclass.
1253   *
1254   * @param  objectClass  The objectclass for the DIT content rule to
1255   *                      retrieve.
1256   *
1257   * @return  The requested DIT content rule, or <CODE>null</CODE> if
1258   *          no DIT content rule is registered with the provided
1259   *          objectclass.
1260   */
1261  public DITContentRule getDITContentRule(ObjectClass objectClass)
1262  {
1263    return ditContentRules.get(objectClass);
1264  }
1265
1266
1267
1268  /**
1269   * Registers the provided DIT content rule definition with this
1270   * schema.
1271   *
1272   * @param  ditContentRule     The DIT content rule to register.
1273   * @param  overwriteExisting  Indicates whether to overwrite an
1274   *                            existing mapping if there are any
1275   *                            conflicts (i.e., another DIT content
1276   *                            rule with the same objectclass).
1277   *
1278   * @throws  DirectoryException  If a conflict is encountered and the
1279   *                              <CODE>overwriteExisting</CODE> flag
1280   *                              is set to <CODE>false</CODE>
1281   */
1282  public void registerDITContentRule(DITContentRule ditContentRule,
1283                                     boolean overwriteExisting)
1284         throws DirectoryException
1285  {
1286    synchronized (ditContentRules)
1287    {
1288      ObjectClass objectClass = ditContentRule.getStructuralClass();
1289
1290      if (! overwriteExisting && ditContentRules.containsKey(objectClass))
1291      {
1292        DITContentRule conflictingRule =
1293                            ditContentRules.get(objectClass);
1294
1295        LocalizableMessage message = ERR_SCHEMA_CONFLICTING_DIT_CONTENT_RULE.
1296            get(ditContentRule.getNameOrOID(),
1297                objectClass.getNameOrOID(),
1298                conflictingRule.getNameOrOID());
1299        throw new DirectoryException(
1300                       ResultCode.CONSTRAINT_VIOLATION, message);
1301      }
1302
1303      ditContentRules.put(objectClass, ditContentRule);
1304    }
1305  }
1306
1307
1308
1309  /**
1310   * Deregisters the provided DIT content rule definition with this
1311   * schema.
1312   *
1313   * @param  ditContentRule  The DIT content rule to deregister with
1314   *                         this schema.
1315   */
1316  public void deregisterDITContentRule(DITContentRule ditContentRule)
1317  {
1318    synchronized (ditContentRules)
1319    {
1320      ditContentRules.remove(ditContentRule.getStructuralClass(),
1321                             ditContentRule);
1322    }
1323  }
1324
1325
1326
1327  /**
1328   * Retrieves the DIT structure rule definitions for this schema, as
1329   * a mapping between the rule ID for the rule and the DIT structure
1330   * rule itself.  Each DIT structure rule should only be present
1331   * once, since its only key is its rule ID.  The contents of the
1332   * returned mapping must not be altered.
1333   *
1334   * @return  The DIT structure rule definitions for this schema.
1335   */
1336  public ConcurrentHashMap<Integer,DITStructureRule>
1337              getDITStructureRulesByID()
1338  {
1339    return ditStructureRulesByID;
1340  }
1341
1342
1343
1344  /**
1345   * Retrieves the DIT structure rule definitions for this schema, as
1346   * a mapping between the name form for the rule and the DIT
1347   * structure rule itself.  Each DIT structure rule should only be
1348   * present once, since its only key is its name form.  The contents
1349   * of the returned mapping must not be altered.
1350   *
1351   * @return  The DIT structure rule definitions for this schema.
1352   */
1353  public ConcurrentHashMap<NameForm,DITStructureRule>
1354              getDITStructureRulesByNameForm()
1355  {
1356    return ditStructureRulesByNameForm;
1357  }
1358
1359
1360
1361  /**
1362   * Retrieves the DIT structure rule definition with the provided
1363   * rule ID.
1364   *
1365   * @param  ruleID  The rule ID for the DIT structure rule to
1366   *                 retrieve.
1367   *
1368   * @return  The requested DIT structure rule, or <CODE>null</CODE>
1369   *          if no DIT structure rule is registered with the provided
1370   *          rule ID.
1371   */
1372  public DITStructureRule getDITStructureRule(int ruleID)
1373  {
1374    return ditStructureRulesByID.get(ruleID);
1375  }
1376
1377
1378
1379  /**
1380   * Retrieves the DIT structure rule definition for the provided name
1381   * form.
1382   *
1383   * @param  nameForm  The name form for the DIT structure rule to
1384   *                   retrieve.
1385   *
1386   * @return  The requested DIT structure rule, or <CODE>null</CODE>
1387   *          if no DIT structure rule is registered with the provided
1388   *          name form.
1389   */
1390  public DITStructureRule getDITStructureRule(NameForm nameForm)
1391  {
1392    return ditStructureRulesByNameForm.get(nameForm);
1393  }
1394
1395
1396
1397  /**
1398   * Registers the provided DIT structure rule definition with this
1399   * schema.
1400   *
1401   * @param  ditStructureRule   The DIT structure rule to register.
1402   * @param  overwriteExisting  Indicates whether to overwrite an
1403   *                            existing mapping if there are any
1404   *                            conflicts (i.e., another DIT structure
1405   *                            rule with the same name form).
1406   *
1407   * @throws  DirectoryException  If a conflict is encountered and the
1408   *                              <CODE>overwriteExisting</CODE> flag
1409   *                              is set to <CODE>false</CODE>
1410   */
1411  public void registerDITStructureRule(
1412                   DITStructureRule ditStructureRule,
1413                   boolean overwriteExisting)
1414         throws DirectoryException
1415  {
1416    synchronized (ditStructureRulesByNameForm)
1417    {
1418      NameForm nameForm = ditStructureRule.getNameForm();
1419      int      ruleID   = ditStructureRule.getRuleID();
1420
1421      if (! overwriteExisting)
1422      {
1423        if (ditStructureRulesByNameForm.containsKey(nameForm))
1424        {
1425          DITStructureRule conflictingRule =
1426               ditStructureRulesByNameForm.get(nameForm);
1427
1428          LocalizableMessage message =
1429              ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_NAME_FORM.
1430                get(ditStructureRule.getNameOrRuleID(),
1431                    nameForm.getNameOrOID(),
1432                    conflictingRule.getNameOrRuleID());
1433          throw new DirectoryException(
1434                         ResultCode.CONSTRAINT_VIOLATION, message);
1435        }
1436
1437        if (ditStructureRulesByID.containsKey(ruleID))
1438        {
1439          DITStructureRule conflictingRule =
1440               ditStructureRulesByID.get(ruleID);
1441
1442          LocalizableMessage message =
1443              ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_ID.
1444                get(ditStructureRule.getNameOrRuleID(), ruleID,
1445                    conflictingRule.getNameOrRuleID());
1446          throw new DirectoryException(
1447                         ResultCode.CONSTRAINT_VIOLATION, message);
1448        }
1449      }
1450
1451      ditStructureRulesByNameForm.put(nameForm, ditStructureRule);
1452      ditStructureRulesByID.put(ruleID, ditStructureRule);
1453    }
1454  }
1455
1456
1457
1458  /**
1459   * Deregisters the provided DIT structure rule definition with this
1460   * schema.
1461   *
1462   * @param  ditStructureRule  The DIT structure rule to deregister
1463   *                           with this schema.
1464   */
1465  public void deregisterDITStructureRule(
1466                   DITStructureRule ditStructureRule)
1467  {
1468    synchronized (ditStructureRulesByNameForm)
1469    {
1470      ditStructureRulesByNameForm.remove(
1471           ditStructureRule.getNameForm(), ditStructureRule);
1472      ditStructureRulesByID.remove(ditStructureRule.getRuleID(),
1473                                   ditStructureRule);
1474    }
1475  }
1476
1477
1478
1479  /**
1480   * Retrieves the name form definitions for this schema, as a mapping
1481   * between the objectclass for the name forms and the name forms
1482   * themselves.
1483   *
1484   * @return  The name form definitions for this schema.
1485   */
1486  public ConcurrentHashMap<ObjectClass,List<NameForm>>
1487              getNameFormsByObjectClass()
1488  {
1489    return nameFormsByOC;
1490  }
1491
1492
1493
1494  /**
1495   * Retrieves the name form definitions for this schema, as a mapping
1496   * between the names/OID for the name form and the name form itself.
1497   * Each name form may be present multiple times with different names
1498   * and its OID.  The contents of the returned mapping must not be
1499   * altered.
1500   *
1501   * @return  The name form definitions for this schema.
1502   */
1503  public ConcurrentHashMap<String,NameForm> getNameFormsByNameOrOID()
1504  {
1505    return nameFormsByName;
1506  }
1507
1508
1509
1510  /**
1511   * Indicates whether this schema definition includes a name form
1512   * with the specified name or OID.
1513   *
1514   * @param  lowerName  The name or OID for which to make the
1515   *                    determination, formatted in all lowercase
1516   *                    characters.
1517   *
1518   * @return  {@code true} if this schema contains a name form with
1519   *          the provided name or OID, or {@code false} if not.
1520   */
1521  public boolean hasNameForm(String lowerName)
1522  {
1523    return nameFormsByName.containsKey(lowerName);
1524  }
1525
1526
1527
1528  /**
1529   * Retrieves the name forms definition for the specified
1530   * objectclass.
1531   *
1532   * @param  objectClass  The objectclass for the name form to
1533   *                      retrieve.
1534   *
1535   * @return  The requested name forms, or <CODE>null</CODE> if no
1536   *           name forms are registered with the provided
1537   *           objectClass.
1538   */
1539  public List<NameForm> getNameForm(ObjectClass objectClass)
1540  {
1541    return nameFormsByOC.get(objectClass);
1542  }
1543
1544
1545
1546  /**
1547   * Retrieves the name form definition with the provided name or OID.
1548   *
1549   * @param  lowerName  The name or OID of the name form to retrieve,
1550   *                    formatted in all lowercase characters.
1551   *
1552   * @return  The requested name form, or <CODE>null</CODE> if no name
1553   *          form is registered with the provided name or OID.
1554   */
1555  public NameForm getNameForm(String lowerName)
1556  {
1557    return nameFormsByName.get(lowerName);
1558  }
1559
1560
1561
1562  /**
1563   * Registers the provided name form definition with this schema.
1564   *
1565   * @param  nameForm           The name form definition to register.
1566   * @param  overwriteExisting  Indicates whether to overwrite an
1567   *                            existing mapping if there are any
1568   *                            conflicts (i.e., another name form
1569   *                            with the same objectclass).
1570   *
1571   * @throws  DirectoryException  If a conflict is encountered and the
1572   *                              <CODE>overwriteExisting</CODE> flag
1573   *                              is set to <CODE>false</CODE>
1574   */
1575  public void registerNameForm(NameForm nameForm,
1576                               boolean overwriteExisting)
1577         throws DirectoryException
1578  {
1579    synchronized (nameFormsByOC)
1580    {
1581      ObjectClass objectClass = nameForm.getStructuralClass();
1582      List<NameForm> mappedForms = nameFormsByOC.get(objectClass);
1583      if (! overwriteExisting)
1584      {
1585        if(mappedForms !=null)
1586        {
1587          //Iterate over the forms to make sure we aren't adding a
1588          //duplicate.
1589          for(NameForm nf : mappedForms)
1590          {
1591            if(nf.equals(nameForm))
1592            {
1593              LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_OC.
1594                get(nameForm.getNameOrOID(),
1595                    objectClass.getNameOrOID(),
1596                    nf.getNameOrOID());
1597              throw new DirectoryException(
1598                           ResultCode.CONSTRAINT_VIOLATION, message);
1599            }
1600          }
1601        }
1602
1603        String oid = toLowerCase(nameForm.getOID());
1604        if (nameFormsByName.containsKey(oid))
1605        {
1606          NameForm conflictingNameForm = nameFormsByName.get(oid);
1607
1608          LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_OID.
1609              get(nameForm.getNameOrOID(), oid,
1610                  conflictingNameForm.getNameOrOID());
1611          throw new DirectoryException(
1612                         ResultCode.CONSTRAINT_VIOLATION, message);
1613        }
1614
1615        for (String name : nameForm.getNames().keySet())
1616        {
1617          if (nameFormsByName.containsKey(name))
1618          {
1619            NameForm conflictingNameForm = nameFormsByName.get(name);
1620
1621            LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_NAME.
1622                get(nameForm.getNameOrOID(), oid,
1623                    conflictingNameForm.getNameOrOID());
1624            throw new DirectoryException(
1625                           ResultCode.CONSTRAINT_VIOLATION, message);
1626          }
1627        }
1628      }
1629
1630      if(mappedForms == null)
1631      {
1632        mappedForms = new ArrayList<>();
1633      }
1634
1635      mappedForms.add(nameForm);
1636      nameFormsByOC.put(objectClass, mappedForms);
1637      nameFormsByName.put(toLowerCase(nameForm.getOID()), nameForm);
1638
1639      for (String name : nameForm.getNames().keySet())
1640      {
1641        nameFormsByName.put(name, nameForm);
1642      }
1643    }
1644  }
1645
1646
1647
1648  /**
1649   * Deregisters the provided name form definition with this schema.
1650   *
1651   * @param  nameForm  The name form definition to deregister.
1652   */
1653  public void deregisterNameForm(NameForm nameForm)
1654  {
1655    synchronized (nameFormsByOC)
1656    {
1657      List<NameForm> mappedForms = nameFormsByOC.get(
1658              nameForm.getStructuralClass());
1659      if(mappedForms != null)
1660      {
1661        mappedForms.remove(nameForm);
1662        if(mappedForms.isEmpty())
1663        {
1664          nameFormsByOC.remove(nameForm.getStructuralClass());
1665        }
1666      }
1667      nameFormsByOC.remove(nameForm.getStructuralClass());
1668      nameFormsByName.remove(toLowerCase(nameForm.getOID()),
1669                             nameForm);
1670
1671      for (String name : nameForm.getNames().keySet())
1672      {
1673        nameFormsByName.remove(name, nameForm);
1674      }
1675    }
1676  }
1677
1678
1679
1680  /**
1681   * Retrieves the modification timestamp for the file in the schema
1682   * configuration directory with the oldest last modified time.
1683   *
1684   * @return  The modification timestamp for the file in the schema
1685   *          configuration directory with the oldest last modified
1686   *          time.
1687   */
1688  public long getOldestModificationTime()
1689  {
1690    return oldestModificationTime;
1691  }
1692
1693
1694
1695  /**
1696   * Sets the modification timestamp for the oldest file in the schema
1697   * configuration directory.
1698   *
1699   * @param  oldestModificationTime  The modification timestamp for
1700   *                                 the oldest file in the schema
1701   *                                 configuration directory.
1702   */
1703  public void setOldestModificationTime(long oldestModificationTime)
1704  {
1705    this.oldestModificationTime = oldestModificationTime;
1706  }
1707
1708
1709
1710  /**
1711   * Retrieves the modification timestamp for the file in the schema
1712   * configuration directory with the youngest last modified time.
1713   *
1714   * @return  The modification timestamp for the file in the schema
1715   *          configuration directory with the youngest last modified
1716   *          time.
1717   */
1718  public long getYoungestModificationTime()
1719  {
1720    return youngestModificationTime;
1721  }
1722
1723
1724
1725  /**
1726   * Sets the modification timestamp for the youngest file in the
1727   * schema configuration directory.
1728   *
1729   * @param  youngestModificationTime  The modification timestamp for
1730   *                                   the youngest file in the schema
1731   *                                   configuration directory.
1732   */
1733  public void setYoungestModificationTime(
1734                   long youngestModificationTime)
1735  {
1736    this.youngestModificationTime = youngestModificationTime;
1737  }
1738
1739  /**
1740   * Recursively rebuilds all schema elements that are dependent upon
1741   * the provided element.  This must be invoked whenever an existing
1742   * schema element is modified in order to ensure that any elements
1743   * that depend on it should also be recreated to reflect the change.
1744   * <BR><BR>
1745   * The following conditions create dependencies between schema
1746   * elements:
1747   * <UL>
1748   *   <LI>If an attribute type references a superior attribute type,
1749   *       then it is dependent upon that superior attribute
1750   *       type.</LI>
1751   *   <LI>If an objectclass requires or allows an attribute type,
1752   *       then it is dependent upon that attribute type.</LI>
1753   *   <LI>If a name form requires or allows an attribute type in the
1754   *       RDN, then it is dependent upon that attribute type.</LI>
1755   *   <LI>If a DIT content rule requires, allows, or forbids the use
1756   *       of an attribute type, then it is dependent upon that
1757   *       attribute type.</LI>
1758   *   <LI>If a matching rule use references an attribute type, then
1759   *       it is dependent upon that attribute type.</LI>
1760   *   <LI>If an objectclass references a superior objectclass, then
1761   *       it is dependent upon that superior objectclass.</LI>
1762   *   <LI>If a name form references a structural objectclass, then it
1763   *       is dependent upon that objectclass.</LI>
1764   *   <LI>If a DIT content rule references a structural or auxiliary
1765   *       objectclass, then it is dependent upon that
1766   *       objectclass.</LI>
1767   *   <LI>If a DIT structure rule references a name form, then it is
1768   *       dependent upon that name form.</LI>
1769   *   <LI>If a DIT structure rule references a superior DIT structure
1770   *       rule, then it is dependent upon that superior DIT structure
1771   *       rule.</LI>
1772   * </UL>
1773   *
1774   * @param  element  The element for which to recursively rebuild all
1775   *                  dependent elements.
1776   *
1777   * @throws  DirectoryException  If a problem occurs while rebuilding
1778   *                              any of the schema elements.
1779   */
1780  public void rebuildDependentElements(SchemaFileElement element) throws DirectoryException
1781  {
1782    try
1783    {
1784      // increase the depth for each level of recursion to protect against errors due to circular references.
1785      final int depth = 0;
1786
1787      if (element instanceof SomeSchemaElement)
1788      {
1789        SomeSchemaElement elt = (SomeSchemaElement) element;
1790        if (elt.isAttributeType())
1791        {
1792          rebuildDependentElements(elt.getAttributeType(), depth);
1793        }
1794      }
1795      else if (element instanceof ObjectClass)
1796      {
1797        rebuildDependentElements((ObjectClass) element, depth);
1798      }
1799      else if (element instanceof NameForm)
1800      {
1801        rebuildDependentElements((NameForm) element, depth);
1802      }
1803      else if (element instanceof DITStructureRule)
1804      {
1805        rebuildDependentElements((DITStructureRule) element, depth);
1806      }
1807    }
1808    catch (DirectoryException de)
1809    {
1810      // If we got an error as a result of a circular reference, then
1811      // we want to make sure that the schema element we call out is
1812      // the one that is at the root of the problem.
1813      if (StaticUtils.hasDescriptor(de.getMessageObject(),
1814          ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE))
1815      {
1816        LocalizableMessage message =
1817            ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE.get(element);
1818        throw new DirectoryException(de.getResultCode(), message, de);
1819      }
1820
1821      // It wasn't a circular reference error, so just re-throw the exception.
1822      throw de;
1823    }
1824  }
1825
1826  private void circularityCheck(int depth, SchemaFileElement element) throws DirectoryException
1827  {
1828    if (depth > 20)
1829    {
1830      // FIXME use a stack of already traversed elements and verify we're updating them only once instead of depth only
1831      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1832          ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE.get(element));
1833    }
1834  }
1835
1836  private void rebuildDependentElements(AttributeType type, int depth) throws DirectoryException
1837  {
1838    circularityCheck(depth, null);
1839
1840    for (AttributeType at : schemaNG.getAttributeTypes())
1841    {
1842      if ((at.getSuperiorType() != null) && at.getSuperiorType().equals(type))
1843      {
1844        deregisterAttributeType(at);
1845        registerAttributeType(at.toString(), getSchemaFileName(at), true);
1846        rebuildDependentElements(at, depth + 1);
1847      }
1848    }
1849
1850    for (ObjectClass oc : objectClasses.values())
1851    {
1852      if (oc.getRequiredAttributes().contains(type) || oc.getOptionalAttributes().contains(type))
1853      {
1854        ObjectClass newOC = recreateFromDefinition(oc);
1855        deregisterObjectClass(oc);
1856        registerObjectClass(newOC, true);
1857        rebuildDependentElements(oc, depth + 1);
1858      }
1859    }
1860
1861    for (List<NameForm> mappedForms : nameFormsByOC.values())
1862    {
1863      for (NameForm nf : mappedForms)
1864      {
1865        if (nf.getRequiredAttributes().contains(type) || nf.getOptionalAttributes().contains(type))
1866        {
1867          NameForm newNF = recreateFromDefinition(nf);
1868          deregisterNameForm(nf);
1869          registerNameForm(newNF, true);
1870          rebuildDependentElements(nf, depth + 1);
1871        }
1872      }
1873    }
1874
1875    for (DITContentRule dcr : ditContentRules.values())
1876    {
1877      if (dcr.getRequiredAttributes().contains(type) || dcr.getOptionalAttributes().contains(type)
1878          || dcr.getProhibitedAttributes().contains(type))
1879      {
1880        DITContentRule newDCR = recreateFromDefinition(dcr);
1881        deregisterDITContentRule(dcr);
1882        registerDITContentRule(newDCR, true);
1883      }
1884    }
1885
1886    for (MatchingRuleUse mru : matchingRuleUses.values())
1887    {
1888      if (mru.getAttributes().contains(type))
1889      {
1890        MatchingRuleUse newMRU = recreateFromDefinition(mru);
1891        deregisterMatchingRuleUse(mru);
1892        registerMatchingRuleUse(newMRU, true);
1893      }
1894    }
1895  }
1896
1897  private void rebuildDependentElements(ObjectClass c, int depth) throws DirectoryException
1898  {
1899    circularityCheck(depth, c);
1900    for (ObjectClass oc : objectClasses.values())
1901    {
1902      if (oc.getSuperiorClasses().contains(c))
1903      {
1904        ObjectClass newOC = recreateFromDefinition(oc);
1905        deregisterObjectClass(oc);
1906        registerObjectClass(newOC, true);
1907        rebuildDependentElements(oc, depth + 1);
1908      }
1909    }
1910
1911    List<NameForm> mappedForms = nameFormsByOC.get(c);
1912    if (mappedForms != null)
1913    {
1914      for (NameForm nf : mappedForms)
1915      {
1916        if (nf != null)
1917        {
1918          NameForm newNF = recreateFromDefinition(nf);
1919          deregisterNameForm(nf);
1920          registerNameForm(newNF, true);
1921          rebuildDependentElements(nf, depth + 1);
1922        }
1923      }
1924    }
1925
1926    for (DITContentRule dcr : ditContentRules.values())
1927    {
1928      if (dcr.getStructuralClass().equals(c) || dcr.getAuxiliaryClasses().contains(c))
1929      {
1930        DITContentRule newDCR = recreateFromDefinition(dcr);
1931        deregisterDITContentRule(dcr);
1932        registerDITContentRule(newDCR, true);
1933      }
1934    }
1935  }
1936
1937  private void rebuildDependentElements(NameForm n, int depth) throws DirectoryException
1938  {
1939    circularityCheck(depth, n);
1940    DITStructureRule dsr = ditStructureRulesByNameForm.get(n);
1941    if (dsr != null)
1942    {
1943      DITStructureRule newDSR = recreateFromDefinition(dsr);
1944      deregisterDITStructureRule(dsr);
1945      registerDITStructureRule(newDSR, true);
1946      rebuildDependentElements(dsr, depth + 1);
1947    }
1948  }
1949
1950  private void rebuildDependentElements(DITStructureRule d, int depth) throws DirectoryException
1951  {
1952    circularityCheck(depth, d);
1953    for (DITStructureRule dsr : ditStructureRulesByID.values())
1954    {
1955      if (dsr.getSuperiorRules().contains(d))
1956      {
1957        DITStructureRule newDSR = recreateFromDefinition(dsr);
1958        deregisterDITStructureRule(dsr);
1959        registerDITStructureRule(newDSR, true);
1960        rebuildDependentElements(dsr, depth + 1);
1961      }
1962    }
1963  }
1964
1965  private String getSchemaFileName(AttributeType attributeType)
1966  {
1967    List<String> values = attributeType.getExtraProperties().get(ServerConstants.SCHEMA_PROPERTY_FILENAME);
1968    return values != null && ! values.isEmpty() ? values.get(0) : null;
1969  }
1970
1971  private DITContentRule recreateFromDefinition(DITContentRule dcr)
1972      throws DirectoryException
1973  {
1974    ByteString value = ByteString.valueOfUtf8(dcr.toString());
1975    DITContentRule copy =
1976        DITContentRuleSyntax.decodeDITContentRule(value, this, false);
1977    setSchemaFile(copy, getSchemaFile(dcr));
1978    return copy;
1979  }
1980
1981  private DITStructureRule recreateFromDefinition(DITStructureRule dsr)
1982      throws DirectoryException
1983  {
1984    ByteString value = ByteString.valueOfUtf8(dsr.toString());
1985    DITStructureRule copy =
1986        DITStructureRuleSyntax.decodeDITStructureRule(value, this, false);
1987    setSchemaFile(copy, getSchemaFile(dsr));
1988    return copy;
1989  }
1990
1991  private MatchingRuleUse recreateFromDefinition(MatchingRuleUse mru)
1992      throws DirectoryException
1993  {
1994    ByteString value = ByteString.valueOfUtf8(mru.toString());
1995    MatchingRuleUse copy =
1996        MatchingRuleUseSyntax.decodeMatchingRuleUse(value, this, false);
1997    setSchemaFile(copy, getSchemaFile(mru));
1998    return copy;
1999  }
2000
2001  private NameForm recreateFromDefinition(NameForm nf)
2002      throws DirectoryException
2003  {
2004    ByteString value = ByteString.valueOfUtf8(nf.toString());
2005    NameForm copy = NameFormSyntax.decodeNameForm(value, this, false);
2006    setSchemaFile(copy, getSchemaFile(nf));
2007    return copy;
2008  }
2009
2010  private ObjectClass recreateFromDefinition(ObjectClass oc)
2011      throws DirectoryException
2012  {
2013    ByteString value = ByteString.valueOfUtf8(oc.toString());
2014    ObjectClass copy = ObjectClassSyntax.decodeObjectClass(value, this, false);
2015    setSchemaFile(copy, getSchemaFile(oc));
2016    return copy;
2017  }
2018
2019  /**
2020   * Creates a new {@link Schema} object that is a duplicate of this one. It elements may be added
2021   * and removed from the duplicate without impacting this version.
2022   *
2023   * @return A new {@link Schema} object that is a duplicate of this one.
2024   */
2025  public Schema duplicate()
2026  {
2027    Schema dupSchema;
2028    try
2029    {
2030      dupSchema = new Schema(schemaNG);
2031    }
2032    catch (DirectoryException unexpected)
2033    {
2034      // the schema has already been validated
2035      throw new RuntimeException(unexpected);
2036    }
2037
2038    dupSchema.subordinateTypes.putAll(subordinateTypes);
2039    dupSchema.objectClasses.putAll(objectClasses);
2040    dupSchema.matchingRuleUses.putAll(matchingRuleUses);
2041    dupSchema.ditContentRules.putAll(ditContentRules);
2042    dupSchema.ditStructureRulesByID.putAll(ditStructureRulesByID);
2043    dupSchema.ditStructureRulesByNameForm.putAll(
2044         ditStructureRulesByNameForm);
2045    dupSchema.nameFormsByOC.putAll(nameFormsByOC);
2046    dupSchema.nameFormsByName.putAll(nameFormsByName);
2047    dupSchema.ldapSyntaxDescriptions.putAll(ldapSyntaxDescriptions);
2048    dupSchema.oldestModificationTime   = oldestModificationTime;
2049    dupSchema.youngestModificationTime = youngestModificationTime;
2050    if (extraAttributes != null)
2051    {
2052      dupSchema.extraAttributes = new HashMap<>(extraAttributes);
2053    }
2054
2055    return dupSchema;
2056  }
2057
2058
2059  /**
2060   * Get the extraAttributes stored in this schema.
2061   *
2062   * @return  The extraAttributes stored in this schema.
2063   */
2064  public Map<String, Attribute> getExtraAttributes()
2065  {
2066    return extraAttributes;
2067  }
2068
2069
2070  /**
2071   * Add a new extra Attribute for this schema.
2072   *
2073   * @param  name     The identifier of the extra Attribute.
2074   *
2075   * @param  attr     The extra attribute that must be added to
2076   *                  this Schema.
2077   */
2078  public void addExtraAttribute(String name, Attribute attr)
2079  {
2080    extraAttributes.put(name, attr);
2081  }
2082
2083
2084  /**
2085   * Writes a single file containing all schema element definitions,
2086   * which can be used on startup to determine whether the schema
2087   * files were edited with the server offline.
2088   */
2089  public static void writeConcatenatedSchema()
2090  {
2091    String concatFilePath = null;
2092    try
2093    {
2094      Set<String> attributeTypes = new LinkedHashSet<>();
2095      Set<String> objectClasses = new LinkedHashSet<>();
2096      Set<String> nameForms = new LinkedHashSet<>();
2097      Set<String> ditContentRules = new LinkedHashSet<>();
2098      Set<String> ditStructureRules = new LinkedHashSet<>();
2099      Set<String> matchingRuleUses = new LinkedHashSet<>();
2100      Set<String> ldapSyntaxes = new LinkedHashSet<>();
2101      genConcatenatedSchema(attributeTypes, objectClasses, nameForms,
2102                            ditContentRules, ditStructureRules,
2103                            matchingRuleUses,ldapSyntaxes);
2104
2105
2106      File configFile = new File(DirectoryServer.getConfigFile());
2107      File configDirectory  = configFile.getParentFile();
2108      File upgradeDirectory = new File(configDirectory, "upgrade");
2109      upgradeDirectory.mkdir();
2110      File concatFile       = new File(upgradeDirectory,
2111                                       SCHEMA_CONCAT_FILE_NAME);
2112      concatFilePath = concatFile.getAbsolutePath();
2113
2114      File tempFile = new File(concatFilePath + ".tmp");
2115      try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, false)))
2116      {
2117        writer.write("dn: " + DirectoryServer.getSchemaDN());
2118        writer.newLine();
2119        writer.write("objectClass: top");
2120        writer.newLine();
2121        writer.write("objectClass: ldapSubentry");
2122        writer.newLine();
2123        writer.write("objectClass: subschema");
2124        writer.newLine();
2125
2126        writeLines(writer, ATTR_ATTRIBUTE_TYPES, attributeTypes);
2127        writeLines(writer, ATTR_OBJECTCLASSES, objectClasses);
2128        writeLines(writer, ATTR_NAME_FORMS, nameForms);
2129        writeLines(writer, ATTR_DIT_CONTENT_RULES, ditContentRules);
2130        writeLines(writer, ATTR_DIT_STRUCTURE_RULES, ditStructureRules);
2131        writeLines(writer, ATTR_MATCHING_RULE_USE, matchingRuleUses);
2132        writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes);
2133      }
2134
2135      if (concatFile.exists())
2136      {
2137        concatFile.delete();
2138      }
2139      tempFile.renameTo(concatFile);
2140    }
2141    catch (Exception e)
2142    {
2143      logger.traceException(e);
2144
2145      // This is definitely not ideal, but it's not the end of the
2146      // world.  The worst that should happen is that the schema
2147      // changes could potentially be sent to the other servers again
2148      // when this server is restarted, which shouldn't hurt anything.
2149      // Still, we should log a warning message.
2150      logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e));
2151    }
2152  }
2153
2154  private static void writeLines(BufferedWriter writer, String beforeColumn, Set<String> lines) throws IOException
2155  {
2156    for (String line : lines)
2157    {
2158      writer.write(beforeColumn);
2159      writer.write(": ");
2160      writer.write(line);
2161      writer.newLine();
2162    }
2163  }
2164
2165
2166
2167  /**
2168   * Reads the files contained in the schema directory and generates a
2169   * concatenated view of their contents in the provided sets.
2170   *
2171   * @param  attributeTypes     The set into which to place the
2172   *                            attribute types read from the schema
2173   *                            files.
2174   * @param  objectClasses      The set into which to place the object
2175   *                            classes read from the schema files.
2176   * @param  nameForms          The set into which to place the name
2177   *                            forms read from the schema files.
2178   * @param  ditContentRules    The set into which to place the DIT
2179   *                            content rules read from the schema
2180   *                            files.
2181   * @param  ditStructureRules  The set into which to place the DIT
2182   *                            structure rules read from the schema
2183   *                            files.
2184   * @param  matchingRuleUses   The set into which to place the
2185   *                            matching rule uses read from the
2186   *                            schema files.
2187   * @param ldapSyntaxes The set into which to place the
2188   *                            ldap syntaxes read from the
2189   *                            schema files.
2190   *
2191   * @throws  IOException  If a problem occurs while reading the
2192   *                       schema file elements.
2193   */
2194  public static void genConcatenatedSchema(
2195                          Set<String> attributeTypes,
2196                          Set<String> objectClasses,
2197                          Set<String> nameForms,
2198                          Set<String> ditContentRules,
2199                          Set<String> ditStructureRules,
2200                          Set<String> matchingRuleUses,
2201                          Set<String> ldapSyntaxes)
2202          throws IOException
2203  {
2204    // Get a sorted list of the files in the schema directory.
2205    TreeSet<File> schemaFiles = new TreeSet<>();
2206    String schemaDirectory =
2207      SchemaConfigManager.getSchemaDirectoryPath();
2208
2209    final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter();
2210    for (File f : new File(schemaDirectory).listFiles(filter))
2211    {
2212      if (f.isFile())
2213      {
2214        schemaFiles.add(f);
2215      }
2216    }
2217
2218
2219    // Open each of the files in order and read the elements that they
2220    // contain, appending them to the appropriate lists.
2221    for (File f : schemaFiles)
2222    {
2223      // Read the contents of the file into a list with one schema
2224      // element per list element.
2225      LinkedList<StringBuilder> lines = new LinkedList<>();
2226      BufferedReader reader = new BufferedReader(new FileReader(f));
2227
2228      while (true)
2229      {
2230        String line = reader.readLine();
2231        if (line == null)
2232        {
2233          break;
2234        }
2235        else if (line.startsWith("#") || line.length() == 0)
2236        {
2237          continue;
2238        }
2239        else if (line.startsWith(" "))
2240        {
2241          lines.getLast().append(line.substring(1));
2242        }
2243        else
2244        {
2245          lines.add(new StringBuilder(line));
2246        }
2247      }
2248
2249      reader.close();
2250
2251
2252      // Iterate through each line in the list.  Find the colon and
2253      // get the attribute name at the beginning.  If it's something
2254      // that we don't recognize, then skip it.  Otherwise, add the
2255      // X-SCHEMA-FILE extension and add it to the appropriate schema
2256      // element list.
2257      for (StringBuilder buffer : lines)
2258      {
2259        // Get the line and add the X-SCHEMA-FILE extension to the end
2260        // of it.  All of them should end with " )" but some might
2261        // have the parenthesis crammed up against the last character
2262        // so deal with that as well.
2263        String line = buffer.toString().trim();
2264        if (line.endsWith(" )"))
2265        {
2266         line = line.substring(0, line.length()-1) +
2267                SCHEMA_PROPERTY_FILENAME + " '" + f.getName() + "' )";
2268        }
2269        else if (line.endsWith(")"))
2270        {
2271         line = line.substring(0, line.length()-1) + " " +
2272                SCHEMA_PROPERTY_FILENAME + " '" + f.getName() + "' )";
2273        }
2274        else
2275        {
2276          continue;
2277        }
2278
2279        parseSchemaLine(line, attributeTypes, objectClasses,
2280            nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
2281            ldapSyntaxes);
2282      }
2283    }
2284  }
2285
2286
2287
2288  /**
2289   * Reads data from the specified concatenated schema file into the
2290   * provided sets.
2291   *
2292   * @param  concatSchemaFile   The path to the concatenated schema
2293   *                            file to be read.
2294   * @param  attributeTypes     The set into which to place the
2295   *                            attribute types read from the
2296   *                            concatenated schema file.
2297   * @param  objectClasses      The set into which to place the object
2298   *                            classes read from the concatenated
2299   *                            schema file.
2300   * @param  nameForms          The set into which to place the name
2301   *                            forms read from the concatenated
2302   *                            schema file.
2303   * @param  ditContentRules    The set into which to place the DIT
2304   *                            content rules read from the
2305   *                            concatenated schema file.
2306   * @param  ditStructureRules  The set into which to place the DIT
2307   *                            structure rules read from the
2308   *                            concatenated schema file.
2309   * @param  matchingRuleUses   The set into which to place the
2310   *                            matching rule uses read from the
2311   *                            concatenated schema file.
2312   * @param ldapSyntaxes The set into which to place the
2313   *                            ldap syntaxes read from the
2314   *                            concatenated schema file.
2315   *
2316   * @throws  IOException  If a problem occurs while reading the
2317   *                       schema file elements.
2318   */
2319  public static void readConcatenatedSchema(String concatSchemaFile,
2320                          Set<String> attributeTypes,
2321                          Set<String> objectClasses,
2322                          Set<String> nameForms,
2323                          Set<String> ditContentRules,
2324                          Set<String> ditStructureRules,
2325                          Set<String> matchingRuleUses,
2326                          Set<String> ldapSyntaxes)
2327          throws IOException
2328  {
2329    BufferedReader reader =
2330         new BufferedReader(new FileReader(concatSchemaFile));
2331    while (true)
2332    {
2333      String line = reader.readLine();
2334      if (line == null)
2335      {
2336        break;
2337      }
2338      parseSchemaLine(line, attributeTypes, objectClasses,
2339          nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
2340          ldapSyntaxes);
2341    }
2342
2343    reader.close();
2344  }
2345
2346  /**
2347   * Parse a line of a schema file into the provided sets.
2348   *
2349   * @param line                The current line of schema.
2350   * @param  attributeTypes     The set into which to place the
2351   *                            attribute type if the line represents
2352   *                            one.
2353   * @param  objectClasses      The set into which to place the object
2354   *                            class if the line represents one.
2355   * @param  nameForms          The set into which to place the name
2356   *                            form if the line represents one.
2357   * @param  ditContentRules    The set into which to place the DIT
2358   *                            content rule if the line represents one.
2359   * @param  ditStructureRules  The set into which to place the DIT
2360   *                            structure rule if the line represents one.
2361   * @param  matchingRuleUses   The set into which to place the
2362   *                            matching rule use if the line represents
2363   *                            one.
2364   * @param ldapSyntaxes        The set into which to place the ldap
2365   *                            syntax if the line represents one.
2366   */
2367
2368  private static void parseSchemaLine(String line,
2369                               Set<String> attributeTypes,
2370                               Set<String> objectClasses,
2371                               Set<String> nameForms,
2372                               Set<String> ditContentRules,
2373                               Set<String> ditStructureRules,
2374                               Set<String> matchingRuleUses,
2375                               Set<String> ldapSyntaxes)
2376  {
2377    String value;
2378    String lowerLine = toLowerCase(line);
2379    if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
2380    {
2381      value =
2382          line.substring(ATTR_ATTRIBUTE_TYPES.length()+1).trim();
2383      attributeTypes.add(value);
2384    }
2385    else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
2386    {
2387      value = line.substring(ATTR_OBJECTCLASSES.length()+1).trim();
2388      objectClasses.add(value);
2389    }
2390    else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
2391    {
2392      value = line.substring(ATTR_NAME_FORMS.length()+1).trim();
2393      nameForms.add(value);
2394    }
2395    else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
2396    {
2397      value = line.substring(
2398          ATTR_DIT_CONTENT_RULES.length()+1).trim();
2399      ditContentRules.add(value);
2400    }
2401    else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
2402    {
2403      value = line.substring(
2404          ATTR_DIT_STRUCTURE_RULES.length()+1).trim();
2405      ditStructureRules.add(value);
2406    }
2407    else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
2408    {
2409      value = line.substring(
2410          ATTR_MATCHING_RULE_USE.length()+1).trim();
2411      matchingRuleUses.add(value);
2412    }
2413    else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC))
2414    {
2415      value = line.substring(
2416          ATTR_LDAP_SYNTAXES.length()+1).trim();
2417      ldapSyntaxes.add(value);
2418    }
2419  }
2420
2421  /**
2422   * Compares the provided sets of schema element definitions and
2423   * writes any differences found into the given list of
2424   * modifications.
2425   *
2426   * @param  oldElements  The set of elements of the specified type
2427   *                      read from the previous concatenated schema
2428   *                      files.
2429   * @param  newElements  The set of elements of the specified type
2430   *                      read from the server's current schema.
2431   * @param  elementType  The attribute type associated with the
2432   *                      schema element being compared.
2433   * @param  mods         The list of modifications into which any
2434   *                      identified differences should be written.
2435   */
2436  public static void compareConcatenatedSchema(
2437                          Set<String> oldElements,
2438                          Set<String> newElements,
2439                          AttributeType elementType,
2440                          List<Modification> mods)
2441  {
2442    AttributeBuilder builder = new AttributeBuilder(elementType);
2443    for (String s : oldElements)
2444    {
2445      if (!newElements.contains(s))
2446      {
2447        builder.add(s);
2448      }
2449    }
2450
2451    if (!builder.isEmpty())
2452    {
2453      mods.add(new Modification(ModificationType.DELETE,
2454                                builder.toAttribute()));
2455    }
2456
2457    builder.setAttributeType(elementType);
2458    for (String s : newElements)
2459    {
2460      if (!oldElements.contains(s))
2461      {
2462        builder.add(s);
2463      }
2464    }
2465
2466    if (!builder.isEmpty())
2467    {
2468      mods.add(new Modification(ModificationType.ADD,
2469                                builder.toAttribute()));
2470    }
2471  }
2472
2473
2474
2475  /**
2476   * Destroys the structures maintained by the schema so that they are
2477   * no longer usable. This should only be called at the end of the
2478   * server shutdown process, and it can help detect inappropriate
2479   * cached references.
2480   */
2481  @org.opends.server.types.PublicAPI(
2482       stability=org.opends.server.types.StabilityLevel.PRIVATE,
2483       mayInstantiate=false,
2484       mayExtend=false,
2485       mayInvoke=true)
2486  public synchronized void destroy()
2487  {
2488    if (schemaNG != null)
2489    {
2490      schemaNG = null;
2491    }
2492
2493    if (ditContentRules != null)
2494    {
2495      ditContentRules.clear();
2496      ditContentRules = null;
2497    }
2498
2499    if (ditStructureRulesByID != null)
2500    {
2501      ditStructureRulesByID.clear();
2502      ditStructureRulesByID = null;
2503    }
2504
2505    if (ditStructureRulesByNameForm != null)
2506    {
2507      ditStructureRulesByNameForm.clear();
2508      ditStructureRulesByNameForm = null;
2509    }
2510
2511    if (matchingRuleUses != null)
2512    {
2513      matchingRuleUses.clear();
2514      matchingRuleUses = null;
2515    }
2516
2517    if (nameFormsByName != null)
2518    {
2519      nameFormsByName.clear();
2520      nameFormsByName = null;
2521    }
2522
2523    if (nameFormsByOC != null)
2524    {
2525      nameFormsByOC.clear();
2526      nameFormsByOC = null;
2527    }
2528
2529    if (objectClasses != null)
2530    {
2531      objectClasses.clear();
2532      objectClasses = null;
2533    }
2534
2535    if (subordinateTypes != null)
2536    {
2537      subordinateTypes.clear();
2538      subordinateTypes = null;
2539    }
2540
2541    if (extraAttributes != null)
2542    {
2543      extraAttributes.clear();
2544      extraAttributes = null;
2545    }
2546
2547    if(ldapSyntaxDescriptions != null)
2548    {
2549      ldapSyntaxDescriptions.clear();
2550      ldapSyntaxDescriptions = null;
2551    }
2552  }
2553
2554  /**
2555   * Update the schema using the provided schema updater.
2556   * <p>
2557   * An implicit lock is performed, so it is in general not necessary
2558   * to call the {code lock()}  and {code unlock() methods.
2559   * However, these method should be used if/when the SchemaBuilder passed
2560   * as an argument to the updater is not used to return the schema
2561   * (see for example usage in {@code CoreSchemaProvider} class). This
2562   * case should remain exceptional.
2563   *
2564   * @param updater
2565   *          the updater that returns a new schema
2566   * @throws DirectoryException if there is any problem updating the schema
2567   */
2568  public void updateSchema(SchemaUpdater updater) throws DirectoryException
2569  {
2570    exclusiveLock.lock();
2571    try
2572    {
2573      switchSchema(updater.update(new SchemaBuilder(schemaNG)));
2574    }
2575    finally
2576    {
2577      exclusiveLock.unlock();
2578    }
2579  }
2580
2581  /** Interface to update a schema provided a schema builder. */
2582  public interface SchemaUpdater
2583  {
2584    /**
2585     * Returns an updated schema.
2586     *
2587     * @param builder
2588     *          The builder on the current schema
2589     * @return the new schema
2590     */
2591    org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder);
2592  }
2593
2594  /**
2595   * Updates the schema option  if the new value differs from the old value.
2596   *
2597   * @param <T> the schema option's type
2598   * @param option the schema option to update
2599   * @param newValue the new value for the schema option
2600   * @throws DirectoryException if there is any problem updating the schema
2601   */
2602  public <T> void updateSchemaOption(final Option<T> option, final T newValue) throws DirectoryException
2603  {
2604    final T oldValue = schemaNG.getOption(option);
2605    if (!oldValue.equals(newValue))
2606    {
2607      updateSchema(new SchemaUpdater()
2608      {
2609        @Override
2610        public org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder)
2611        {
2612          return builder.setOption(option, newValue).toSchema();
2613        }
2614      });
2615    }
2616  }
2617
2618  /** Takes an exclusive lock on the schema. */
2619  public void exclusiveLock()
2620  {
2621    exclusiveLock.lock();
2622  }
2623
2624  /** Releases an exclusive lock on the schema. */
2625  public void exclusiveUnlock()
2626  {
2627    exclusiveLock.unlock();
2628  }
2629
2630  /**
2631   * Adds the provided schema file to the provided schema element definition.
2632   *
2633   * @param definition
2634   *            The schema element definition
2635   * @param schemaFile
2636   *            The name of the schema file to include in the definition
2637   * @return  The definition string of the element
2638   *          including the X-SCHEMA-FILE extension.
2639   */
2640  public static String addSchemaFileToElementDefinitionIfAbsent(String definition, String schemaFile)
2641  {
2642    if (schemaFile != null && !definition.contains(SCHEMA_PROPERTY_FILENAME))
2643    {
2644      int pos = definition.lastIndexOf(')');
2645      return definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )";
2646    }
2647    return definition;
2648  }
2649
2650  private void switchSchema(org.forgerock.opendj.ldap.schema.Schema newSchema) throws DirectoryException
2651  {
2652    rejectSchemaWithWarnings(newSchema);
2653    schemaNG = newSchema.asNonStrictSchema();
2654    if (DirectoryServer.getSchema() == this)
2655    {
2656      org.forgerock.opendj.ldap.schema.Schema.setDefaultSchema(schemaNG);
2657    }
2658  }
2659
2660  private void rejectSchemaWithWarnings(org.forgerock.opendj.ldap.schema.Schema newSchema) throws DirectoryException
2661  {
2662    Collection<LocalizableMessage> warnings = newSchema.getWarnings();
2663    if (!warnings.isEmpty())
2664    {
2665      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
2666          ERR_SCHEMA_HAS_WARNINGS.get(warnings.size(), Utils.joinAsString("; ", warnings)));
2667    }
2668  }
2669}