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 static org.forgerock.opendj.ldap.ModificationType.*;
020import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
021import static org.forgerock.opendj.ldap.schema.SchemaOptions.*;
022import static org.opends.messages.BackendMessages.*;
023import static org.opends.messages.SchemaMessages.*;
024import static org.opends.server.config.ConfigConstants.*;
025import static org.opends.server.util.ServerConstants.*;
026import static org.opends.server.util.StaticUtils.*;
027
028import java.io.BufferedReader;
029import java.io.BufferedWriter;
030import java.io.File;
031import java.io.FileNotFoundException;
032import java.io.FileReader;
033import java.io.FileWriter;
034import java.io.FilenameFilter;
035import java.io.IOException;
036import java.text.ParseException;
037import java.util.Collection;
038import java.util.HashMap;
039import java.util.LinkedHashSet;
040import java.util.LinkedList;
041import java.util.List;
042import java.util.Map;
043import java.util.Set;
044import java.util.TreeSet;
045import java.util.concurrent.locks.Lock;
046import java.util.concurrent.locks.ReentrantLock;
047
048import org.forgerock.i18n.LocalizableMessage;
049import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
050import org.forgerock.i18n.LocalizedIllegalArgumentException;
051import org.forgerock.i18n.slf4j.LocalizedLogger;
052import org.forgerock.opendj.ldap.AttributeDescription;
053import org.forgerock.opendj.ldap.ByteString;
054import org.forgerock.opendj.ldap.ModificationType;
055import org.forgerock.opendj.ldap.ResultCode;
056import org.forgerock.opendj.ldap.schema.AttributeType;
057import org.forgerock.opendj.ldap.schema.ConflictingSchemaElementException;
058import org.forgerock.opendj.ldap.schema.DITContentRule;
059import org.forgerock.opendj.ldap.schema.DITStructureRule;
060import org.forgerock.opendj.ldap.schema.MatchingRule;
061import org.forgerock.opendj.ldap.schema.MatchingRuleUse;
062import org.forgerock.opendj.ldap.schema.MatchingRuleUse.Builder;
063import org.forgerock.opendj.ldap.schema.NameForm;
064import org.forgerock.opendj.ldap.schema.ObjectClass;
065import org.forgerock.opendj.ldap.schema.SchemaBuilder;
066import org.forgerock.opendj.ldap.schema.Syntax;
067import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException;
068import org.forgerock.util.Option;
069import org.forgerock.util.Utils;
070import org.opends.server.core.DirectoryServer;
071import org.opends.server.core.SchemaConfigManager;
072import org.opends.server.util.Base64;
073
074/**
075 * This class defines a data structure that holds information about the components of the Directory
076 * Server schema. It includes the following kinds of elements:
077 * <UL>
078 * <LI>Attribute type definitions</LI>
079 * <LI>Objectclass definitions</LI>
080 * <LI>syntax definitions</LI>
081 * <LI>Matching rule definitions</LI>
082 * <LI>Matching rule use definitions</LI>
083 * <LI>DIT content rule definitions</LI>
084 * <LI>DIT structure rule definitions</LI>
085 * <LI>Name form definitions</LI>
086 * </UL>
087 * It always uses non-strict {@link org.forgerock.opendj.ldap.schema.Schema} under the hood.
088 */
089@org.opends.server.types.PublicAPI(
090     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
091     mayInstantiate=false,
092     mayExtend=false,
093     mayInvoke=true)
094public final class Schema
095{
096  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
097
098  /** The oldest modification timestamp for any schema configuration file. */
099  private long oldestModificationTime;
100  /** The youngest modification timestamp for any schema configuration file. */
101  private long youngestModificationTime;
102
103  /**
104   * A set of extra attributes that are not used directly by the schema but may
105   * be used by other component to store information in the schema.
106   * <p>
107   * ex : Replication uses this to store its state and GenerationID.
108   */
109  private Map<String, Attribute> extraAttributes = new HashMap<>();
110
111  /**
112   * The SDK schema.
113   * <p>
114   * It will progressively take over server implementation of the schema.
115   * <p>
116   * @GuardedBy("exclusiveLock")
117   */
118  private volatile org.forgerock.opendj.ldap.schema.Schema schemaNG;
119
120  /** Guards updates to the schema. */
121  private final Lock exclusiveLock = new ReentrantLock();
122
123  /**
124   * Creates a new schema structure with all elements initialized but empty.
125   *
126   * @param schemaNG
127   *          The SDK schema
128   * @throws DirectoryException
129   *           if the schema has warnings
130   */
131  public Schema(org.forgerock.opendj.ldap.schema.Schema schemaNG) throws DirectoryException
132  {
133    final org.forgerock.opendj.ldap.schema.Schema newSchemaNG =
134        new SchemaBuilder(schemaNG)
135        .setOption(DEFAULT_SYNTAX_OID, getDirectoryStringSyntax().getOID())
136        .toSchema();
137    switchSchema(newSchemaNG);
138
139    oldestModificationTime    = System.currentTimeMillis();
140    youngestModificationTime  = oldestModificationTime;
141  }
142
143  /**
144   * Returns the SDK schema.
145   *
146   * @return the SDK schema
147   */
148  public org.forgerock.opendj.ldap.schema.Schema getSchemaNG()
149  {
150    return schemaNG;
151  }
152
153  /**
154   * Retrieves the attribute type definitions for this schema.
155   *
156   * @return  The attribute type definitions for this schema.
157   */
158  public Collection<AttributeType> getAttributeTypes()
159  {
160    return schemaNG.getAttributeTypes();
161  }
162
163  /**
164   * Indicates whether this schema definition includes an attribute type with the provided name or
165   * OID.
166   *
167   * @param nameOrOid
168   *          The name or OID for which to make the determination
169   * @return {@code true} if this schema contains an attribute type with the provided name or OID,
170   *         or {@code false} if not.
171   */
172  public boolean hasAttributeType(String nameOrOid)
173  {
174    return schemaNG.hasAttributeType(nameOrOid);
175  }
176
177  /**
178   * Retrieves the attribute type definition with the specified name or OID.
179   *
180   * @param nameOrOid
181   *          The name or OID of the attribute type to retrieve
182   * @return The requested attribute type
183   */
184  public AttributeType getAttributeType(String nameOrOid)
185  {
186    try
187    {
188      return schemaNG.getAttributeType(nameOrOid);
189    }
190    catch (UnknownSchemaElementException e)
191    {
192      // It should never happen because we only use non-strict schemas
193      throw new RuntimeException(e);
194    }
195  }
196
197  /**
198   * Retrieves the attribute type definition with the specified name or OID.
199   *
200   * @param nameOrOid
201   *          The name or OID of the attribute type to retrieve
202   * @param syntax
203   *          The syntax to use when creating the temporary "place-holder" attribute type.
204   * @return The requested attribute type
205   */
206  public AttributeType getAttributeType(String nameOrOid, Syntax syntax)
207  {
208    try
209    {
210      return schemaNG.getAttributeType(nameOrOid, syntax);
211    }
212    catch (UnknownSchemaElementException e)
213    {
214      // It should never happen because we only use non-strict schemas
215      throw new RuntimeException(e);
216    }
217  }
218
219  /**
220   * Parses an object class from its provided definition.
221   *
222   * @param definition
223   *          The definition of the object class
224   * @return the object class
225   * @throws DirectoryException
226   *           If an error occurs
227   */
228  public ObjectClass parseObjectClass(final String definition) throws DirectoryException
229  {
230    try
231    {
232      SchemaBuilder builder = new SchemaBuilder(schemaNG);
233      builder.addObjectClass(definition, true);
234      org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema();
235      rejectSchemaWithWarnings(newSchema);
236      return newSchema.getObjectClass(parseObjectClassOID(definition));
237    }
238    catch (UnknownSchemaElementException e)
239    {
240      // this should never happen
241      LocalizableMessage msg = ERR_OBJECT_CLASS_CANNOT_REGISTER.get(definition);
242      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e);
243    }
244    catch (LocalizedIllegalArgumentException e)
245    {
246      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
247    }
248  }
249
250  /**
251   * Parses an attribute type from its provided definition.
252   *
253   * @param definition
254   *          The definition of the attribute type
255   * @return the attribute type
256   * @throws DirectoryException
257   *            If an error occurs
258   */
259  public AttributeType parseAttributeType(final String definition) throws DirectoryException
260  {
261    try
262    {
263      SchemaBuilder builder = new SchemaBuilder(schemaNG);
264      builder.addAttributeType(definition, true);
265      org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema();
266      rejectSchemaWithWarnings(newSchema);
267      return newSchema.getAttributeType(parseAttributeTypeOID(definition));
268    }
269    catch (UnknownSchemaElementException e)
270    {
271      // this should never happen
272      LocalizableMessage msg = ERR_ATTR_TYPE_CANNOT_REGISTER.get(definition);
273      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e);
274    }
275    catch (LocalizedIllegalArgumentException e)
276    {
277      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
278    }
279  }
280
281  /**
282   * Parses a matching rule use from its provided definition.
283   *
284   * @param definition
285   *          The definition of the matching rule use
286   * @return the matching rule use
287   * @throws DirectoryException
288   *            If an error occurs
289   */
290  public MatchingRuleUse parseMatchingRuleUse(final String definition) throws DirectoryException
291  {
292    try
293    {
294      SchemaBuilder builder = new SchemaBuilder(schemaNG);
295      builder.addMatchingRuleUse(definition, true);
296      org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema();
297      rejectSchemaWithWarnings(newSchema);
298      return newSchema.getMatchingRuleUse(parseMatchingRuleUseOID(definition));
299    }
300    catch (UnknownSchemaElementException e)
301    {
302      LocalizableMessage msg = ERR_MATCHING_RULE_USE_CANNOT_REGISTER.get(definition);
303      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e);
304    }
305    catch (LocalizedIllegalArgumentException e)
306    {
307      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
308    }
309  }
310
311  /**
312   * Parses a name form from its provided definition.
313   *
314   * @param definition
315   *          The definition of the name form
316   * @return the name form
317   * @throws DirectoryException
318   *           If an error occurs
319   */
320  public NameForm parseNameForm(final String definition) throws DirectoryException
321  {
322    try
323    {
324      SchemaBuilder builder = new SchemaBuilder(schemaNG);
325      builder.addNameForm(definition, true);
326      org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema();
327      rejectSchemaWithWarnings(newSchema);
328      return newSchema.getNameForm(parseNameFormOID(definition));
329    }
330    catch (UnknownSchemaElementException e)
331    {
332      LocalizableMessage msg = ERR_NAME_FORM_CANNOT_REGISTER.get(definition);
333      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e);
334    }
335    catch (LocalizedIllegalArgumentException e)
336    {
337      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
338    }
339  }
340
341  /**
342   * Parses a a DIT content rule from its provided definition.
343   *
344   * @param definition
345   *          The definition of the matching rule use
346   * @return the DIT content rule
347   * @throws DirectoryException
348   *           If an error occurs
349   */
350  public DITContentRule parseDITContentRule(final String definition) throws DirectoryException
351  {
352    try
353    {
354      SchemaBuilder builder = new SchemaBuilder(schemaNG);
355      builder.addDITContentRule(definition, true);
356      org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema();
357      rejectSchemaWithWarnings(newSchema);
358      return newSchema.getDITContentRule(parseDITContentRuleOID(definition));
359    }
360    catch (UnknownSchemaElementException e)
361    {
362      LocalizableMessage msg = ERR_DIT_CONTENT_RULE_CANNOT_REGISTER.get(definition);
363      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e);
364    }
365    catch (LocalizedIllegalArgumentException e)
366    {
367      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
368    }
369  }
370
371  /**
372   * Parses a DIT structure rule from its provided definition.
373   *
374   * @param definition
375   *          The definition of the DIT structure rule
376   * @return the DIT structure rule
377   * @throws DirectoryException
378   *           If an error occurs
379   */
380  public DITStructureRule parseDITStructureRule(String definition) throws DirectoryException
381  {
382    try
383    {
384      SchemaBuilder builder = new SchemaBuilder(schemaNG);
385      builder.addDITStructureRule(definition, true);
386      org.forgerock.opendj.ldap.schema.Schema newSchema = builder.toSchema();
387      rejectSchemaWithWarnings(newSchema);
388      return newSchema.getDITStructureRule(parseRuleID(definition));
389    }
390    catch (UnknownSchemaElementException e)
391    {
392      LocalizableMessage msg = ERR_NAME_FORM_CANNOT_REGISTER.get(definition);
393      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg, e);
394    }
395    catch (LocalizedIllegalArgumentException e)
396    {
397      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
398    }
399  }
400
401  /**
402   * Registers an attribute type from its provided definition.
403   *
404   * @param definition
405   *          The definition of the attribute type
406   * @param schemaFile
407   *          The schema file where this definition belongs,
408   *          maybe {@code null}
409   * @param overwrite
410   *          Indicates whether to overwrite the attribute
411   *          type if it already exists based on OID or name
412   * @throws DirectoryException
413   *            If an error occurs
414   */
415  public void registerAttributeType(final String definition, final String schemaFile, final boolean overwrite)
416      throws DirectoryException
417  {
418    exclusiveLock.lock();
419    try
420    {
421      String defWithFile = getDefinitionWithSchemaFile(definition, schemaFile);
422      switchSchema(new SchemaBuilder(schemaNG)
423          .addAttributeType(defWithFile, overwrite)
424          .toSchema());
425    }
426    catch (ConflictingSchemaElementException | UnknownSchemaElementException e)
427    {
428      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
429    }
430    catch (LocalizedIllegalArgumentException e)
431    {
432      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
433    }
434    finally
435    {
436      exclusiveLock.unlock();
437    }
438  }
439
440  /**
441   * Registers the provided attribute type definition with this schema.
442   *
443   * @param attributeType
444   *          The attribute type to register with this schema.
445   * @param schemaFile
446   *          The schema file where this definition belongs, maybe {@code null}
447   * @param overwriteExisting
448   *          Indicates whether to overwrite an existing mapping if there are any conflicts (i.e.,
449   *          another attribute type with the same OID or name).
450   * @throws DirectoryException
451   *           If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to
452   *           {@code false}
453   */
454  public void registerAttributeType(final AttributeType attributeType, final String schemaFile,
455      final boolean overwriteExisting) throws DirectoryException
456  {
457    exclusiveLock.lock();
458    try
459    {
460      SchemaBuilder builder = new SchemaBuilder(schemaNG);
461      registerAttributeType0(builder, attributeType, schemaFile, overwriteExisting);
462      switchSchema(builder.toSchema());
463    }
464    catch (LocalizedIllegalArgumentException e)
465    {
466      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
467    }
468    finally
469    {
470      exclusiveLock.unlock();
471    }
472  }
473
474  private void registerAttributeType0(SchemaBuilder builder, final AttributeType attributeType,
475      final String schemaFile, final boolean overwriteExisting)
476  {
477    AttributeType.Builder b = builder.buildAttributeType(attributeType);
478    if (schemaFile != null)
479    {
480      b.removeExtraProperty(SCHEMA_PROPERTY_FILENAME)
481       .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile);
482    }
483    if (overwriteExisting)
484    {
485      b.addToSchemaOverwrite();
486    }
487    else
488    {
489      b.addToSchema();
490    }
491  }
492
493  /**
494   * Replaces an existing attribute type by the provided new attribute type.
495   *
496   * @param newAttributeType
497   *          Attribute type to register to the schema.
498   * @param existingAttributeType
499   *          Attribute type to remove from the schema.
500   * @param schemaFile
501   *          The schema file which the new object class belongs to.
502   * @throws DirectoryException
503   *            If an errors occurs.
504   */
505  public void replaceAttributeType(AttributeType newAttributeType, AttributeType existingAttributeType,
506      String schemaFile) throws DirectoryException
507  {
508    exclusiveLock.lock();
509    try
510    {
511      SchemaBuilder builder = new SchemaBuilder(schemaNG);
512      builder.removeAttributeType(existingAttributeType.getNameOrOID());
513      registerAttributeType0(builder, newAttributeType, schemaFile, false);
514      switchSchema(builder.toSchema());
515    }
516    finally
517    {
518      exclusiveLock.unlock();
519    }
520  }
521
522  /**
523   * Returns the OID from the provided object class definition, assuming the definition is valid.
524   * <p>
525   * This method does not perform any check.
526   *
527   * @param definition
528   *          The definition of a object class, assumed to be valid
529   * @return the OID, which is never {@code null}
530   * @throws DirectoryException
531   *           If a problem occurs while parsing the definition
532   */
533  public static String parseObjectClassOID(String definition) throws DirectoryException
534  {
535    return parseOID(definition, ERR_PARSING_OBJECTCLASS_OID);
536  }
537
538  /**
539   * Returns the OID from the provided attribute type definition, assuming the definition is valid.
540   * <p>
541   * This method does not perform any check.
542   *
543   * @param definition
544   *          The definition of an attribute type, assumed to be valid
545   * @return the OID, which is never {@code null}
546   * @throws DirectoryException
547   *           If a problem occurs while parsing the definition
548   */
549  public static String parseAttributeTypeOID(String definition) throws DirectoryException
550  {
551    return parseOID(definition, ERR_PARSING_ATTRIBUTE_TYPE_OID);
552  }
553
554  private static String parseMatchingRuleUseOID(String definition) throws DirectoryException
555  {
556    return parseOID(definition, ERR_PARSING_MATCHING_RULE_USE_OID);
557  }
558
559  /**
560   * Returns the OID from the provided name form definition, assuming the definition is valid.
561   * <p>
562   * This method does not perform any check.
563   *
564   * @param definition
565   *          The definition of a name form, assumed to be valid
566   * @return the OID, which is never {@code null}
567   * @throws DirectoryException
568   *           If a problem occurs while parsing the definition
569   */
570  public static String parseNameFormOID(String definition) throws DirectoryException
571  {
572    return parseOID(definition, ERR_PARSING_NAME_FORM_OID);
573  }
574
575  private static String parseDITContentRuleOID(String definition) throws DirectoryException
576  {
577    return parseOID(definition, ERR_PARSING_DIT_CONTENT_RULE_OID);
578  }
579
580  /**
581   * Returns the ruleID from the provided dit structure rule definition, assuming the definition is
582   * valid.
583   * <p>
584   * This method does not perform any check.
585   *
586   * @param definition
587   *          The definition of a dit structure rule, assumed to be valid
588   * @return the OID, which is never {@code null}
589   * @throws DirectoryException
590   *           If a problem occurs while parsing the definition
591   */
592  public static int parseRuleID(String definition) throws DirectoryException
593  {
594    // Reuse code of parseOID, even though this is not an OID
595    return Integer.parseInt(parseOID(definition, ERR_PARSING_DIT_STRUCTURE_RULE_RULEID));
596  }
597
598  /**
599   * Returns the OID from the provided syntax definition, assuming the definition is valid.
600   * <p>
601   * This method does not perform any check.
602   *
603   * @param definition
604   *          The definition of a syntax, assumed to be valid
605   * @return the OID, which is never {@code null}
606   * @throws DirectoryException
607   *           If a problem occurs while parsing the definition
608   */
609  public static String parseSyntaxOID(String definition) throws DirectoryException
610  {
611    return parseOID(definition, ERR_PARSING_LDAP_SYNTAX_OID);
612  }
613
614  private static String parseOID(String definition, Arg1<Object> parsingErrorMsg) throws DirectoryException
615  {
616    try
617    {
618      int pos = 0;
619      int length = definition.length();
620      // Skip over any leading whitespace.
621      while (pos < length && (definition.charAt(pos) == ' '))
622      {
623        pos++;
624      }
625      // Skip the open parenthesis.
626      pos++;
627      // Skip over any spaces immediately following the opening parenthesis.
628      while (pos < length && definition.charAt(pos) == ' ')
629      {
630        pos++;
631      }
632      // The next set of characters must be the OID.
633      int oidStartPos = pos;
634      while (pos < length && definition.charAt(pos) != ' ' && definition.charAt(pos) != ')')
635      {
636        pos++;
637      }
638      return definition.substring(oidStartPos, pos);
639    }
640    catch (IndexOutOfBoundsException e)
641    {
642      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, parsingErrorMsg.get(definition), e);
643    }
644  }
645
646  /**
647   * Deregisters the provided attribute type definition with this schema.
648   *
649   * @param  attributeType  The attribute type to deregister with this schema.
650   * @throws DirectoryException
651   *           If the attribute type is referenced by another schema element.
652   */
653  public void deregisterAttributeType(final AttributeType attributeType) throws DirectoryException
654  {
655    exclusiveLock.lock();
656    try
657    {
658      SchemaBuilder builder = new SchemaBuilder(schemaNG);
659      if (builder.removeAttributeType(attributeType.getNameOrOID()))
660      {
661        switchSchema(builder.toSchema());
662      }
663    }
664    finally
665    {
666      exclusiveLock.unlock();
667    }
668  }
669
670  /**
671   * Retrieves the objectclass definitions for this schema.
672   *
673   * @return The objectclass definitions for this schema.
674   */
675  public Collection<ObjectClass> getObjectClasses()
676  {
677    return schemaNG.getObjectClasses();
678  }
679
680  /**
681   * Indicates whether this schema definition includes an objectclass with the provided name or OID.
682   *
683   * @param nameOrOid
684   *          The name or OID for which to make the determination.
685   * @return {@code true} if this schema contains an objectclass with the provided name or OID, or
686   *         {@code false} if not.
687   */
688  public boolean hasObjectClass(String nameOrOid)
689  {
690    return schemaNG.hasObjectClass(nameOrOid);
691  }
692
693
694
695  /**
696   * Retrieves the objectclass definition with the specified name or OID.
697   *
698   * @param nameOrOid
699   *          The name or OID of the objectclass to retrieve.
700   * @return The requested objectclass, or {@code null} if no class is registered with the provided
701   *         name or OID.
702   */
703  public ObjectClass getObjectClass(String nameOrOid)
704  {
705    return schemaNG.getObjectClass(nameOrOid);
706  }
707
708  /**
709   * Registers the provided objectclass definition with this schema.
710   *
711   * @param objectClass
712   *          The objectclass to register with this schema.
713   * @param schemaFile
714   *          The schema file where this definition belongs, maybe {@code null}
715   * @param overwriteExisting
716   *          Indicates whether to overwrite an existing mapping if there are any conflicts (i.e.,
717   *          another objectclass with the same OID or name).
718   * @throws DirectoryException
719   *           If a conflict is encountered and the {@code overwriteExisting} flag is set to
720   *           {@code false}.
721   */
722  public void registerObjectClass(ObjectClass objectClass, String schemaFile, boolean overwriteExisting)
723         throws DirectoryException
724  {
725    exclusiveLock.lock();
726    try
727    {
728      SchemaBuilder builder = new SchemaBuilder(schemaNG);
729      registerObjectClass0(builder, objectClass, schemaFile, overwriteExisting);
730      switchSchema(builder.toSchema());
731    }
732    finally
733    {
734      exclusiveLock.unlock();
735    }
736  }
737
738  private void registerObjectClass0(SchemaBuilder builder, ObjectClass objectClass, String schemaFile,
739      boolean overwriteExisting)
740  {
741    ObjectClass.Builder b = builder.buildObjectClass(objectClass);
742    if (schemaFile != null)
743    {
744      b.removeExtraProperty(SCHEMA_PROPERTY_FILENAME)
745       .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile);
746    }
747    if (overwriteExisting)
748    {
749      b.addToSchemaOverwrite();
750    }
751    else
752    {
753      b.addToSchema();
754    }
755  }
756
757  /**
758   * Registers an object class from its provided definition.
759   *
760   * @param definition
761   *          The definition of the object class
762   * @param schemaFile
763   *          The schema file where this definition belongs, may be {@code null}
764   * @param overwriteExisting
765   *          Indicates whether to overwrite the object class
766   *          if it already exists based on OID or name
767   * @throws DirectoryException
768   *            If an error occurs
769   */
770  public void registerObjectClass(String definition, String schemaFile, boolean overwriteExisting)
771      throws DirectoryException
772  {
773    exclusiveLock.lock();
774    try
775    {
776      String defWithFile = getDefinitionWithSchemaFile(definition, schemaFile);
777      switchSchema(new SchemaBuilder(schemaNG)
778          .addObjectClass(defWithFile, overwriteExisting)
779          .toSchema());
780    }
781    catch (ConflictingSchemaElementException | UnknownSchemaElementException e)
782    {
783      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
784    }
785    catch (LocalizedIllegalArgumentException e)
786    {
787      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
788    }
789    finally
790    {
791      exclusiveLock.unlock();
792    }
793  }
794
795  /**
796   * Deregisters the provided objectclass definition with this schema.
797   *
798   * @param  objectClass  The objectclass to deregister with this schema.
799   * @throws DirectoryException
800   *           If the object class is referenced by another schema element.
801   */
802  public void deregisterObjectClass(ObjectClass objectClass) throws DirectoryException
803  {
804    exclusiveLock.lock();
805    try
806    {
807      SchemaBuilder builder = new SchemaBuilder(schemaNG);
808      if (builder.removeObjectClass(objectClass.getNameOrOID()))
809      {
810        switchSchema(builder.toSchema());
811      }
812    }
813    finally
814    {
815      exclusiveLock.unlock();
816    }
817  }
818
819
820
821  /**
822   * Retrieves the syntax definitions for this schema.
823   *
824   * @return The syntax definitions for this schema.
825   */
826  public Collection<Syntax> getSyntaxes()
827  {
828    return schemaNG.getSyntaxes();
829  }
830
831
832
833  /**
834   * Indicates whether this schema definition includes an attribute syntax with the provided OID.
835   *
836   * @param oid
837   *          The OID for which to make the determination
838   * @return {@code true} if this schema contains an syntax with the provided OID, or {@code false}
839   *         if not.
840   */
841  public boolean hasSyntax(String oid)
842  {
843    return schemaNG.hasSyntax(oid);
844  }
845
846  /**
847   * Retrieves the syntax definition with the OID.
848   *
849   * @param numericOid
850   *          The OID of the syntax to retrieve.
851   * @return The requested syntax, or {@code null} if no syntax is registered with the provided OID.
852   */
853  public Syntax getSyntax(String numericOid)
854  {
855    return schemaNG.getSyntax(numericOid);
856  }
857
858  /**
859   * Registers the provided syntax definition with this schema.
860   *
861   * @param syntax
862   *          The syntax to register with this schema.
863   * @param overwriteExisting
864   *          Indicates whether to overwrite an existing mapping if there are any conflicts (i.e.,
865   *          another attribute syntax with the same OID).
866   * @throws DirectoryException
867   *           If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to
868   *           {@code false}
869   */
870  public void registerSyntax(final Syntax syntax, final boolean overwriteExisting) throws DirectoryException
871  {
872    exclusiveLock.lock();
873    try
874    {
875      SchemaBuilder builder = new SchemaBuilder(schemaNG);
876      Syntax.Builder b = builder.buildSyntax(syntax);
877      if (overwriteExisting)
878      {
879        b.addToSchemaOverwrite();
880      }
881      else
882      {
883        b.addToSchema();
884      }
885      switchSchema(builder.toSchema());
886    }
887    catch (LocalizedIllegalArgumentException e)
888    {
889      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
890    }
891    finally
892    {
893      exclusiveLock.unlock();
894    }
895  }
896
897  /**
898   * Registers the provided syntax definition with this schema.
899   *
900   * @param definition
901   *          The definition to register with this schema.
902   * @param overwriteExisting
903   *          Indicates whether to overwrite an existing mapping if there are any conflicts (i.e.,
904   *          another attribute syntax with the same OID).
905   * @throws DirectoryException
906   *           If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to
907   *           {@code false}
908   */
909  public void registerSyntax(final String definition, final boolean overwriteExisting) throws DirectoryException
910  {
911    exclusiveLock.lock();
912    try
913    {
914      org.forgerock.opendj.ldap.schema.Schema newSchema =
915          new SchemaBuilder(schemaNG)
916          .addSyntax(definition, overwriteExisting)
917          .toSchema();
918      switchSchema(newSchema);
919    }
920    catch (ConflictingSchemaElementException | UnknownSchemaElementException e)
921    {
922      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
923    }
924    catch (LocalizedIllegalArgumentException e)
925    {
926      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, e.getMessageObject(), e);
927    }
928    finally
929    {
930      exclusiveLock.unlock();
931    }
932  }
933
934  /**
935   * Deregisters the provided syntax definition with this schema.
936   *
937   * @param syntax
938   *          The syntax to deregister with this schema.
939   * @throws DirectoryException
940   *           If the LDAP syntax is referenced by another schema element.
941   */
942  public void deregisterSyntax(final Syntax syntax) throws DirectoryException
943  {
944    exclusiveLock.lock();
945    try
946    {
947      SchemaBuilder builder = new SchemaBuilder(schemaNG);
948      builder.removeSyntax(syntax.getOID());
949      switchSchema(builder.toSchema());
950    }
951    finally
952    {
953      exclusiveLock.unlock();
954    }
955  }
956
957  /**
958   * Retrieves all matching rule definitions for this schema.
959   *
960   * @return The matching rule definitions for this schema
961   */
962  public Collection<MatchingRule> getMatchingRules()
963  {
964    return schemaNG.getMatchingRules();
965  }
966
967  /**
968   * Indicates whether this schema definition includes a matching rule with the provided name or
969   * OID.
970   *
971   * @param nameOrOid
972   *          The name or OID for which to make the determination
973   * @return {@code true} if this schema contains a matching rule with the provided name or OID, or
974   *         {@code false} if not.
975   */
976  public boolean hasMatchingRule(String nameOrOid)
977  {
978    return schemaNG.hasMatchingRule(nameOrOid);
979  }
980
981  /**
982   * Retrieves the matching rule definition with the specified name or OID.
983   *
984   * @param nameOrOid
985   *          The name or OID of the matching rule to retrieve
986   * @return The requested matching rule, or {@code null} if no rule is registered with the provided
987   *         name or OID.
988   * @throws UnknownSchemaElementException
989   *           If the requested matching rule was not found or if the provided name is ambiguous.
990   */
991  public MatchingRule getMatchingRule(String nameOrOid)
992  {
993    return schemaNG.getMatchingRule(nameOrOid);
994  }
995
996  /**
997   * Registers the provided matching rule definitions with this schema.
998   *
999   * @param matchingRules
1000   *          The matching rules to register with this schema.
1001   * @param overwriteExisting
1002   *          Indicates whether to overwrite an existing mapping if there are
1003   *          any conflicts (i.e., another matching rule with the same OID or
1004   *          name).
1005   * @throws DirectoryException
1006   *           If a conflict is encountered and the
1007   *           {@code overwriteExisting} flag is set to {@code false}
1008   */
1009  public void registerMatchingRules(Collection<MatchingRule> matchingRules, boolean overwriteExisting)
1010      throws DirectoryException
1011  {
1012    exclusiveLock.lock();
1013    try
1014    {
1015      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1016      for (MatchingRule matchingRule : matchingRules)
1017      {
1018        MatchingRule.Builder b = builder.buildMatchingRule(matchingRule);
1019        if (overwriteExisting)
1020        {
1021          b.addToSchemaOverwrite();
1022        }
1023        else
1024        {
1025          b.addToSchema();
1026        }
1027      }
1028      switchSchema(builder.toSchema());
1029    }
1030    catch (LocalizedIllegalArgumentException e)
1031    {
1032      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
1033    }
1034    finally
1035    {
1036      exclusiveLock.unlock();
1037    }
1038  }
1039
1040  /**
1041   * Deregisters the provided matching rule definition with this schema.
1042   *
1043   * @param matchingRule
1044   *          The matching rule to deregister with this schema.
1045   * @throws DirectoryException
1046   *           If the schema has constraints violations.
1047   */
1048  public void deregisterMatchingRule(final MatchingRule matchingRule) throws DirectoryException
1049  {
1050    exclusiveLock.lock();
1051    try
1052    {
1053      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1054      builder.removeMatchingRule(matchingRule.getNameOrOID());
1055      switchSchema(builder.toSchema());
1056    }
1057    finally
1058    {
1059      exclusiveLock.unlock();
1060    }
1061  }
1062
1063
1064  /**
1065   * Retrieves the matching rule use definitions for this schema, as a
1066   * mapping between the matching rule for the matching rule use
1067   * definition and the matching rule use itself.  Each matching rule
1068   * use should only be present once, since its only key is its
1069   * matching rule.  The contents of the returned mapping must not be
1070   * altered.
1071   *
1072   * @return  The matching rule use definitions for this schema.
1073   */
1074  public Collection<MatchingRuleUse> getMatchingRuleUses()
1075  {
1076    return schemaNG.getMatchingRuleUses();
1077  }
1078
1079
1080
1081  /**
1082   * Retrieves the matching rule use definition for the specified matching rule.
1083   *
1084   * @param matchingRule
1085   *          The matching rule for which to retrieve the matching rule use definition.
1086   * @return The matching rule use definition, or {@code null} if none exists for the specified
1087   *         matching rule.
1088   */
1089  public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
1090  {
1091    return schemaNG.getMatchingRuleUse(matchingRule);
1092  }
1093
1094  /**
1095   * Registers the provided matching rule use definition with this schema.
1096   *
1097   * @param matchingRuleUse
1098   *          The matching rule use definition to register.
1099   * @param schemaFile
1100   *          The schema file where this definition belongs, maybe {@code null}
1101   * @param overwriteExisting
1102   *          Indicates whether to overwrite an existing mapping if there are any conflicts (i.e.,
1103   *          another matching rule use with the same matching rule).
1104   * @throws DirectoryException
1105   *           If a conflict is encountered and the {@code overwriteExisting} flag is set to
1106   *           {@code false}
1107   */
1108  public void registerMatchingRuleUse(MatchingRuleUse matchingRuleUse, String schemaFile, boolean overwriteExisting)
1109      throws DirectoryException
1110  {
1111    exclusiveLock.lock();
1112    try
1113    {
1114      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1115      Builder mruBuilder = builder.buildMatchingRuleUse(matchingRuleUse);
1116      if (schemaFile != null)
1117      {
1118        mruBuilder.removeExtraProperty(SCHEMA_PROPERTY_FILENAME)
1119                  .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile);
1120      }
1121      if (overwriteExisting)
1122      {
1123        mruBuilder.addToSchemaOverwrite();
1124      }
1125      else
1126      {
1127        mruBuilder.addToSchema();
1128      }
1129      switchSchema(builder.toSchema());
1130    }
1131    finally
1132    {
1133      exclusiveLock.unlock();
1134    }
1135  }
1136
1137  private String getDefinitionWithSchemaFile(String definition, String schemaFile)
1138  {
1139    return schemaFile != null ? addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile) : definition;
1140  }
1141
1142  /**
1143   * Deregisters the provided matching rule use definition with this schema.
1144   *
1145   * @param matchingRuleUse
1146   *          The matching rule use to deregister with this schema.
1147   * @throws DirectoryException
1148   *            If the schema has constraints violations.
1149   */
1150  public void deregisterMatchingRuleUse(org.forgerock.opendj.ldap.schema.MatchingRuleUse matchingRuleUse)
1151      throws DirectoryException
1152  {
1153    exclusiveLock.lock();
1154    try
1155    {
1156      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1157      builder.removeMatchingRuleUse(matchingRuleUse.getNameOrOID());
1158      switchSchema(builder.toSchema());
1159    }
1160    finally
1161    {
1162      exclusiveLock.unlock();
1163    }
1164  }
1165
1166
1167
1168  /**
1169   * Retrieves the DIT content rule definitions for this schema.
1170   *
1171   * @return  The DIT content rule definitions for this schema.
1172   */
1173  public Collection<DITContentRule> getDITContentRules()
1174  {
1175    return schemaNG.getDITContentRules();
1176  }
1177
1178
1179  /**
1180   * Retrieves the DIT content rule definition for the specified objectclass.
1181   *
1182   * @param objectClass
1183   *          The objectclass for the DIT content rule to retrieve.
1184   * @return The requested DIT content rule, or {@code null} if no DIT content rule is registered
1185   *         with the provided objectclass.
1186   */
1187  public DITContentRule getDITContentRule(ObjectClass objectClass)
1188  {
1189    return schemaNG.getDITContentRule(objectClass);
1190  }
1191
1192
1193
1194  /**
1195   * Registers the provided DIT content rule definition with this schema.
1196   *
1197   * @param ditContentRule
1198   *          The DIT content rule to register.
1199   * @param schemaFile
1200   *          The schema file where this definition belongs, maybe {@code null}
1201   * @param overwriteExisting
1202   *          Indicates whether to overwrite an existing mapping if there are any conflicts (i.e.,
1203   *          another DIT content rule with the same objectclass).
1204   * @throws DirectoryException
1205   *           If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to
1206   *           {@code false}
1207   */
1208  public void registerDITContentRule(DITContentRule ditContentRule, String schemaFile, boolean overwriteExisting)
1209      throws DirectoryException
1210  {
1211    exclusiveLock.lock();
1212    try
1213    {
1214      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1215      DITContentRule.Builder dcrBuilder = builder.buildDITContentRule(ditContentRule);
1216      if (schemaFile != null)
1217      {
1218        dcrBuilder.removeExtraProperty(SCHEMA_PROPERTY_FILENAME)
1219                  .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile);
1220      }
1221      if (overwriteExisting)
1222      {
1223        dcrBuilder.addToSchemaOverwrite();
1224      }
1225      else
1226      {
1227        dcrBuilder.addToSchema();
1228      }
1229      switchSchema(builder.toSchema());
1230    }
1231    finally
1232    {
1233      exclusiveLock.unlock();
1234    }
1235  }
1236
1237  /**
1238   * Deregisters the provided DIT content rule definition with this
1239   * schema.
1240   *
1241   * @param  ditContentRule  The DIT content rule to deregister with
1242   *                         this schema.
1243   * @throws DirectoryException
1244   *            May be thrown if the schema has constraint violations, although
1245   *            deregistering a DIT content rule should not break any constraint.
1246   */
1247  public void deregisterDITContentRule(DITContentRule ditContentRule) throws DirectoryException
1248  {
1249    exclusiveLock.lock();
1250    try
1251    {
1252      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1253      builder.removeDITContentRule(ditContentRule.getNameOrOID());
1254      switchSchema(builder.toSchema());
1255    }
1256    finally
1257    {
1258      exclusiveLock.unlock();
1259    }
1260  }
1261
1262
1263
1264  /**
1265   * Retrieves the DIT structure rule definitions for this schema.
1266   * The contents of the returned mapping must not be altered.
1267   *
1268   * @return The DIT structure rule definitions for this schema.
1269   */
1270  public Collection<DITStructureRule> getDITStructureRules()
1271  {
1272    return schemaNG.getDITStuctureRules();
1273  }
1274
1275  /**
1276   * Retrieves the DIT structure rule definition with the provided rule ID.
1277   *
1278   * @param ruleID
1279   *          The rule ID for the DIT structure rule to retrieve.
1280   * @return The requested DIT structure rule, or {@code null} if no DIT structure rule is
1281   *         registered with the provided rule ID.
1282   */
1283  public DITStructureRule getDITStructureRule(int ruleID)
1284  {
1285    return schemaNG.getDITStructureRule(ruleID);
1286  }
1287
1288  /**
1289   * Retrieves the DIT structure rule definitions for the provided name form.
1290   *
1291   * @param nameForm
1292   *          The name form for the DIT structure rule to retrieve.
1293   * @return The requested DIT structure rules, or {@code null} if no DIT structure rule is
1294   *         registered with the provided name form.
1295   */
1296  public Collection<DITStructureRule> getDITStructureRules(NameForm nameForm)
1297  {
1298    return schemaNG.getDITStructureRules(nameForm);
1299  }
1300
1301
1302
1303  /**
1304   * Registers the provided DIT structure rule definition with this schema.
1305   *
1306   * @param ditStructureRule
1307   *          The DIT structure rule to register.
1308   * @param schemaFile
1309   *          The schema file where this definition belongs, maybe {@code null}
1310   * @param overwriteExisting
1311   *          Indicates whether to overwrite an existing mapping if there are any conflicts (i.e.,
1312   *          another DIT structure rule with the same name form).
1313   * @throws DirectoryException
1314   *           If a conflict is encountered and the {@code overwriteExisting} flag is set to
1315   *           {@code false}
1316   */
1317  public void registerDITStructureRule(DITStructureRule ditStructureRule, String schemaFile, boolean overwriteExisting)
1318      throws DirectoryException
1319  {
1320    exclusiveLock.lock();
1321    try
1322    {
1323      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1324      DITStructureRule.Builder dsrBuilder = builder.buildDITStructureRule(ditStructureRule);
1325      if (schemaFile != null)
1326      {
1327        dsrBuilder.removeExtraProperty(SCHEMA_PROPERTY_FILENAME)
1328                  .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile);
1329      }
1330      if (overwriteExisting)
1331      {
1332        dsrBuilder.addToSchemaOverwrite();
1333      }
1334      else
1335      {
1336        dsrBuilder.addToSchema();
1337      }
1338      switchSchema(builder.toSchema());
1339    }
1340    catch (LocalizedIllegalArgumentException e)
1341    {
1342      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
1343    }
1344    finally
1345    {
1346      exclusiveLock.unlock();
1347    }
1348  }
1349
1350  /**
1351   * Deregisters the provided DIT structure rule definition with this schema.
1352   *
1353   * @param ditStructureRule
1354   *          The DIT structure rule to deregister with this schema.
1355   * @throws DirectoryException
1356   *           If an error occurs.
1357   */
1358  public void deregisterDITStructureRule(DITStructureRule ditStructureRule) throws DirectoryException
1359  {
1360    exclusiveLock.lock();
1361    try
1362    {
1363      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1364      builder.removeDITStructureRule(ditStructureRule.getRuleID());
1365      switchSchema(builder.toSchema());
1366    }
1367    finally
1368    {
1369      exclusiveLock.unlock();
1370    }
1371  }
1372
1373
1374
1375  /**
1376   * Retrieves the name form definitions for this schema.
1377   *
1378   * @return  The name form definitions for this schema.
1379   */
1380  public Collection<NameForm> getNameForms()
1381  {
1382    return schemaNG.getNameForms();
1383  }
1384
1385  /**
1386   * Indicates whether this schema definition includes a name form with the specified name or OID.
1387   *
1388   * @param nameOrOid
1389   *          The name or OID for which to make the determination.
1390   * @return {@code true} if this schema contains a name form with the provided name or OID, or
1391   *         {@code false} if not.
1392   */
1393  public boolean hasNameForm(String nameOrOid)
1394  {
1395    return schemaNG.hasNameForm(nameOrOid);
1396  }
1397
1398
1399
1400  /**
1401   * Retrieves the name forms definition for the specified objectclass.
1402   *
1403   * @param objectClass
1404   *          The objectclass for the name form to retrieve.
1405   * @return The requested name forms, or {@code null} if no name forms are registered with the
1406   *         provided objectClass.
1407   */
1408  public Collection<NameForm> getNameForm(ObjectClass objectClass)
1409  {
1410    return schemaNG.getNameForms(objectClass);
1411  }
1412
1413
1414
1415  /**
1416   * Retrieves the name form definition with the provided name or OID.
1417   *
1418   * @param nameOrOid
1419   *          The name or OID of the name form to retrieve.
1420   * @return The requested name form, or {@code null} if no name form is registered with the
1421   *         provided name or OID.
1422   */
1423  public NameForm getNameForm(String nameOrOid)
1424  {
1425    return schemaNG.getNameForm(nameOrOid);
1426  }
1427
1428
1429
1430  /**
1431   * Registers the provided name form definition with this schema.
1432   *
1433   * @param nameForm
1434   *          The name form definition to register.
1435   * @param schemaFile
1436   *          The schema file where this definition belongs, maybe {@code null}
1437   * @param overwriteExisting
1438   *          Indicates whether to overwrite an existing mapping if there are any conflicts (i.e.,
1439   *          another name form with the same objectclass).
1440   * @throws DirectoryException
1441   *           If a conflict is encountered and the <CODE>overwriteExisting</CODE> flag is set to
1442   *           {@code false}
1443   */
1444  public void registerNameForm(NameForm nameForm, String schemaFile, boolean overwriteExisting)
1445      throws DirectoryException
1446  {
1447    exclusiveLock.lock();
1448    try
1449    {
1450      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1451      NameForm.Builder formBuilder = builder.buildNameForm(nameForm);
1452      if (schemaFile != null)
1453      {
1454        formBuilder.removeExtraProperty(SCHEMA_PROPERTY_FILENAME)
1455                   .extraProperties(SCHEMA_PROPERTY_FILENAME, schemaFile);
1456      }
1457      if (overwriteExisting)
1458      {
1459        formBuilder.addToSchemaOverwrite();
1460      }
1461      else
1462      {
1463        formBuilder.addToSchema();
1464      }
1465      switchSchema(builder.toSchema());
1466    }
1467    finally
1468    {
1469      exclusiveLock.unlock();
1470    }
1471  }
1472
1473  /**
1474   * Deregisters the provided name form definition with this schema.
1475   *
1476   * @param  nameForm  The name form definition to deregister.
1477   * @throws DirectoryException
1478   *            If an error occurs.
1479   */
1480  public void deregisterNameForm(NameForm nameForm) throws DirectoryException
1481  {
1482    exclusiveLock.lock();
1483    try
1484    {
1485      SchemaBuilder builder = new SchemaBuilder(schemaNG);
1486      builder.removeNameForm(nameForm.getNameOrOID());
1487      switchSchema(builder.toSchema());
1488    }
1489    finally
1490    {
1491      exclusiveLock.unlock();
1492    }
1493  }
1494
1495
1496
1497  /**
1498   * Retrieves the modification timestamp for the file in the schema
1499   * configuration directory with the oldest last modified time.
1500   *
1501   * @return  The modification timestamp for the file in the schema
1502   *          configuration directory with the oldest last modified
1503   *          time.
1504   */
1505  public long getOldestModificationTime()
1506  {
1507    return oldestModificationTime;
1508  }
1509
1510
1511
1512  /**
1513   * Sets the modification timestamp for the oldest file in the schema
1514   * configuration directory.
1515   *
1516   * @param  oldestModificationTime  The modification timestamp for
1517   *                                 the oldest file in the schema
1518   *                                 configuration directory.
1519   */
1520  public void setOldestModificationTime(long oldestModificationTime)
1521  {
1522    this.oldestModificationTime = oldestModificationTime;
1523  }
1524
1525
1526
1527  /**
1528   * Retrieves the modification timestamp for the file in the schema
1529   * configuration directory with the youngest last modified time.
1530   *
1531   * @return  The modification timestamp for the file in the schema
1532   *          configuration directory with the youngest last modified
1533   *          time.
1534   */
1535  public long getYoungestModificationTime()
1536  {
1537    return youngestModificationTime;
1538  }
1539
1540
1541
1542  /**
1543   * Sets the modification timestamp for the youngest file in the
1544   * schema configuration directory.
1545   *
1546   * @param  youngestModificationTime  The modification timestamp for
1547   *                                   the youngest file in the schema
1548   *                                   configuration directory.
1549   */
1550  public void setYoungestModificationTime(
1551                   long youngestModificationTime)
1552  {
1553    this.youngestModificationTime = youngestModificationTime;
1554  }
1555
1556  /**
1557   * Creates a new {@link Schema} object that is a duplicate of this one. It elements may be added
1558   * and removed from the duplicate without impacting this version.
1559   *
1560   * @return A new {@link Schema} object that is a duplicate of this one.
1561   */
1562  public Schema duplicate()
1563  {
1564    Schema dupSchema;
1565    try
1566    {
1567      dupSchema = new Schema(schemaNG);
1568    }
1569    catch (DirectoryException unexpected)
1570    {
1571      // the schema has already been validated
1572      throw new RuntimeException(unexpected);
1573    }
1574
1575    dupSchema.oldestModificationTime   = oldestModificationTime;
1576    dupSchema.youngestModificationTime = youngestModificationTime;
1577    if (extraAttributes != null)
1578    {
1579      dupSchema.extraAttributes = new HashMap<>(extraAttributes);
1580    }
1581
1582    return dupSchema;
1583  }
1584
1585
1586  /**
1587   * Get the extraAttributes stored in this schema.
1588   *
1589   * @return  The extraAttributes stored in this schema.
1590   */
1591  public Collection<Attribute> getExtraAttributes()
1592  {
1593    return extraAttributes.values();
1594  }
1595
1596
1597  /**
1598   * Add a new extra Attribute for this schema.
1599   *
1600   * @param  name     The identifier of the extra Attribute.
1601   *
1602   * @param  attr     The extra attribute that must be added to
1603   *                  this Schema.
1604   */
1605  public void addExtraAttribute(String name, Attribute attr)
1606  {
1607    extraAttributes.put(name, attr);
1608  }
1609
1610
1611  /**
1612   * Writes a single file containing all schema element definitions,
1613   * which can be used on startup to determine whether the schema
1614   * files were edited with the server offline.
1615   */
1616  public static void writeConcatenatedSchema()
1617  {
1618    String concatFilePath = null;
1619    try
1620    {
1621      Set<String> attributeTypes = new LinkedHashSet<>();
1622      Set<String> objectClasses = new LinkedHashSet<>();
1623      Set<String> nameForms = new LinkedHashSet<>();
1624      Set<String> ditContentRules = new LinkedHashSet<>();
1625      Set<String> ditStructureRules = new LinkedHashSet<>();
1626      Set<String> matchingRuleUses = new LinkedHashSet<>();
1627      Set<String> ldapSyntaxes = new LinkedHashSet<>();
1628      genConcatenatedSchema(attributeTypes, objectClasses, nameForms,
1629                            ditContentRules, ditStructureRules,
1630                            matchingRuleUses,ldapSyntaxes);
1631
1632
1633      File configFile = new File(DirectoryServer.getConfigFile());
1634      File configDirectory = configFile.getParentFile();
1635      File upgradeDirectory = new File(configDirectory, "upgrade");
1636      upgradeDirectory.mkdir();
1637      File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
1638      concatFilePath = concatFile.getAbsolutePath();
1639
1640      File tempFile = new File(concatFilePath + ".tmp");
1641      try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, false)))
1642      {
1643        writeLines(writer,
1644            "dn: " + DirectoryServer.getSchemaDN(),
1645            "objectClass: top",
1646            "objectClass: ldapSubentry",
1647            "objectClass: subschema");
1648
1649        writeLines(writer, ATTR_ATTRIBUTE_TYPES, attributeTypes);
1650        writeLines(writer, ATTR_OBJECTCLASSES, objectClasses);
1651        writeLines(writer, ATTR_NAME_FORMS, nameForms);
1652        writeLines(writer, ATTR_DIT_CONTENT_RULES, ditContentRules);
1653        writeLines(writer, ATTR_DIT_STRUCTURE_RULES, ditStructureRules);
1654        writeLines(writer, ATTR_MATCHING_RULE_USE, matchingRuleUses);
1655        writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes);
1656      }
1657
1658      if (concatFile.exists())
1659      {
1660        concatFile.delete();
1661      }
1662      tempFile.renameTo(concatFile);
1663    }
1664    catch (Exception e)
1665    {
1666      logger.traceException(e);
1667
1668      // This is definitely not ideal, but it's not the end of the
1669      // world.  The worst that should happen is that the schema
1670      // changes could potentially be sent to the other servers again
1671      // when this server is restarted, which shouldn't hurt anything.
1672      // Still, we should log a warning message.
1673      logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e));
1674    }
1675  }
1676
1677  private static void writeLines(BufferedWriter writer, String... lines) throws IOException
1678  {
1679    for (String line : lines)
1680    {
1681      writer.write(line);
1682      writer.newLine();
1683    }
1684  }
1685
1686  private static void writeLines(BufferedWriter writer, String beforeColumn, Set<String> lines) throws IOException
1687  {
1688    for (String line : lines)
1689    {
1690      writer.write(beforeColumn);
1691      writer.write(": ");
1692      writer.write(line);
1693      writer.newLine();
1694    }
1695  }
1696
1697  /**
1698   * Reads the files contained in the schema directory and generates a
1699   * concatenated view of their contents in the provided sets.
1700   *
1701   * @param  attributeTypes     The set into which to place the
1702   *                            attribute types read from the schema
1703   *                            files.
1704   * @param  objectClasses      The set into which to place the object
1705   *                            classes read from the schema files.
1706   * @param  nameForms          The set into which to place the name
1707   *                            forms read from the schema files.
1708   * @param  ditContentRules    The set into which to place the DIT
1709   *                            content rules read from the schema
1710   *                            files.
1711   * @param  ditStructureRules  The set into which to place the DIT
1712   *                            structure rules read from the schema
1713   *                            files.
1714   * @param  matchingRuleUses   The set into which to place the
1715   *                            matching rule uses read from the
1716   *                            schema files.
1717   * @param ldapSyntaxes The set into which to place the
1718   *                            ldap syntaxes read from the
1719   *                            schema files.
1720   *
1721   * @throws  IOException  If a problem occurs while reading the
1722   *                       schema file elements.
1723   */
1724  public static void genConcatenatedSchema(
1725                          Set<String> attributeTypes,
1726                          Set<String> objectClasses,
1727                          Set<String> nameForms,
1728                          Set<String> ditContentRules,
1729                          Set<String> ditStructureRules,
1730                          Set<String> matchingRuleUses,
1731                          Set<String> ldapSyntaxes)
1732          throws IOException
1733  {
1734    // Get a sorted list of the files in the schema directory.
1735    TreeSet<File> schemaFiles = new TreeSet<>();
1736    String schemaDirectory = SchemaConfigManager.getSchemaDirectoryPath();
1737
1738    final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter();
1739    for (File f : new File(schemaDirectory).listFiles(filter))
1740    {
1741      if (f.isFile())
1742      {
1743        schemaFiles.add(f);
1744      }
1745    }
1746
1747
1748    // Open each of the files in order and read the elements that they
1749    // contain, appending them to the appropriate lists.
1750    for (File f : schemaFiles)
1751    {
1752      List<StringBuilder> lines = readSchemaElementsFromLdif(f);
1753
1754      // Iterate through each line in the list.  Find the colon and
1755      // get the attribute name at the beginning.  If it's something
1756      // that we don't recognize, then skip it.  Otherwise, add the
1757      // X-SCHEMA-FILE extension and add it to the appropriate schema
1758      // element list.
1759      for (StringBuilder buffer : lines)
1760      {
1761        String line = buffer.toString().trim();
1762        parseSchemaLine(line, f.getName(), attributeTypes, objectClasses,
1763            nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
1764            ldapSyntaxes);
1765      }
1766    }
1767  }
1768
1769  private static List<StringBuilder> readSchemaElementsFromLdif(File f) throws IOException, FileNotFoundException
1770  {
1771    final LinkedList<StringBuilder> lines = new LinkedList<>();
1772
1773    try (BufferedReader reader = new BufferedReader(new FileReader(f)))
1774    {
1775      String line;
1776      while ((line = reader.readLine()) != null)
1777      {
1778        if (line.startsWith("#") || line.length() == 0)
1779        {
1780          continue;
1781        }
1782        else if (line.startsWith(" "))
1783        {
1784          lines.getLast().append(line.substring(1));
1785        }
1786        else
1787        {
1788          lines.add(new StringBuilder(line));
1789        }
1790      }
1791    }
1792    return lines;
1793  }
1794
1795
1796
1797  /**
1798   * Reads data from the specified concatenated schema file into the
1799   * provided sets.
1800   *
1801   * @param  concatSchemaFile   The concatenated schema file to be read.
1802   * @param  attributeTypes     The set into which to place the attribute types
1803   *                            read from the concatenated schema file.
1804   * @param  objectClasses      The set into which to place the object classes
1805   *                            read from the concatenated schema file.
1806   * @param  nameForms          The set into which to place the name forms
1807   *                            read from the concatenated schema file.
1808   * @param  ditContentRules    The set into which to place the DIT content rules
1809   *                            read from the concatenated schema file.
1810   * @param  ditStructureRules  The set into which to place the DIT structure rules
1811   *                            read from the concatenated schema file.
1812   * @param  matchingRuleUses   The set into which to place the matching rule
1813   *                            uses read from the concatenated schema file.
1814   * @param ldapSyntaxes        The set into which to place the ldap syntaxes
1815   *                            read from the concatenated schema file.
1816   *
1817   * @throws  IOException  If a problem occurs while reading the
1818   *                       schema file elements.
1819   */
1820  public static void readConcatenatedSchema(File concatSchemaFile,
1821                          Set<String> attributeTypes,
1822                          Set<String> objectClasses,
1823                          Set<String> nameForms,
1824                          Set<String> ditContentRules,
1825                          Set<String> ditStructureRules,
1826                          Set<String> matchingRuleUses,
1827                          Set<String> ldapSyntaxes)
1828          throws IOException
1829  {
1830    try (BufferedReader reader = new BufferedReader(new FileReader(concatSchemaFile)))
1831    {
1832      String line;
1833      while ((line = reader.readLine()) != null)
1834      {
1835        parseSchemaLine(line, null, attributeTypes, objectClasses,
1836            nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
1837            ldapSyntaxes);
1838      }
1839    }
1840  }
1841
1842  private static void parseSchemaLine(String definition, String fileName,
1843      Set<String> attributeTypes,
1844      Set<String> objectClasses,
1845      Set<String> nameForms,
1846      Set<String> ditContentRules,
1847      Set<String> ditStructureRules,
1848      Set<String> matchingRuleUses,
1849      Set<String> ldapSyntaxes)
1850  {
1851    String lowerLine = toLowerCase(definition);
1852
1853    try
1854    {
1855      if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
1856      {
1857        addSchemaDefinition(attributeTypes, definition, ATTR_ATTRIBUTE_TYPES_LC, fileName);
1858      }
1859      else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
1860      {
1861        addSchemaDefinition(objectClasses, definition, ATTR_OBJECTCLASSES_LC, fileName);
1862      }
1863      else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
1864      {
1865        addSchemaDefinition(nameForms, definition, ATTR_NAME_FORMS_LC, fileName);
1866      }
1867      else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
1868      {
1869        addSchemaDefinition(ditContentRules, definition, ATTR_DIT_CONTENT_RULES_LC, fileName);
1870      }
1871      else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
1872      {
1873        addSchemaDefinition(ditStructureRules, definition, ATTR_DIT_STRUCTURE_RULES_LC, fileName);
1874      }
1875      else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
1876      {
1877        addSchemaDefinition(matchingRuleUses, definition, ATTR_MATCHING_RULE_USE_LC, fileName);
1878      }
1879      else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC))
1880      {
1881        addSchemaDefinition(ldapSyntaxes, definition, ATTR_LDAP_SYNTAXES_LC, fileName);
1882      }
1883    } catch (ParseException pe)
1884    {
1885      logger.error(ERR_SCHEMA_PARSE_LINE.get(definition, pe.getLocalizedMessage()));
1886    }
1887  }
1888
1889  private static void addSchemaDefinition(Set<String> definitions, String line, String attrName, String fileName)
1890      throws ParseException
1891  {
1892    definitions.add(getSchemaDefinition(line.substring(attrName.length()), fileName));
1893  }
1894
1895  private static String getSchemaDefinition(String definition, String schemaFile) throws ParseException
1896  {
1897    if (definition.startsWith("::"))
1898    {
1899      // See OPENDJ-2792: the definition of the ds-cfg-csv-delimiter-char attribute type
1900      // had a space accidentally added after the closing parenthesis.
1901      // This was unfortunately interpreted as base64
1902      definition = ByteString.wrap(Base64.decode(definition.substring(2).trim())).toString();
1903    }
1904    else if (definition.startsWith(":"))
1905    {
1906      definition = definition.substring(1).trim();
1907    }
1908    else
1909    {
1910      throw new ParseException(ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION.get().toString(), 0);
1911    }
1912
1913    return addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile);
1914  }
1915
1916  /**
1917   * Compares the provided sets of schema element definitions and
1918   * writes any differences found into the given list of
1919   * modifications.
1920   *
1921   * @param  oldElements  The set of elements of the specified type
1922   *                      read from the previous concatenated schema
1923   *                      files.
1924   * @param  newElements  The set of elements of the specified type
1925   *                      read from the server's current schema.
1926   * @param  elementType  The attribute type associated with the
1927   *                      schema element being compared.
1928   * @param  mods         The list of modifications into which any
1929   *                      identified differences should be written.
1930   */
1931  public static void compareConcatenatedSchema(
1932                          Set<String> oldElements,
1933                          Set<String> newElements,
1934                          AttributeType elementType,
1935                          List<Modification> mods)
1936  {
1937    AttributeBuilder builder = new AttributeBuilder(elementType);
1938    addModification(mods, DELETE, oldElements, newElements, builder);
1939
1940    builder.setAttributeDescription(AttributeDescription.create(elementType));
1941    addModification(mods, ADD, newElements, oldElements, builder);
1942  }
1943
1944  private static void addModification(List<Modification> mods, ModificationType modType, Set<String> included,
1945      Set<String> excluded, AttributeBuilder builder)
1946  {
1947    for (String val : included)
1948    {
1949      if (!excluded.contains(val))
1950      {
1951        builder.add(val);
1952      }
1953    }
1954
1955    if (!builder.isEmpty())
1956    {
1957      mods.add(new Modification(modType, builder.toAttribute()));
1958    }
1959  }
1960
1961  /**
1962   * Destroys the structures maintained by the schema so that they are
1963   * no longer usable. This should only be called at the end of the
1964   * server shutdown process, and it can help detect inappropriate
1965   * cached references.
1966   */
1967  @org.opends.server.types.PublicAPI(
1968       stability=org.opends.server.types.StabilityLevel.PRIVATE,
1969       mayInstantiate=false,
1970       mayExtend=false,
1971       mayInvoke=true)
1972  public synchronized void destroy()
1973  {
1974    if (schemaNG != null)
1975    {
1976      schemaNG = null;
1977    }
1978
1979    if (extraAttributes != null)
1980    {
1981      extraAttributes.clear();
1982      extraAttributes = null;
1983    }
1984  }
1985
1986  /**
1987   * Update the schema using the provided schema updater.
1988   * <p>
1989   * An implicit lock is performed, so it is in general not necessary
1990   * to call the {code lock()}  and {code unlock() methods.
1991   * However, these method should be used if/when the SchemaBuilder passed
1992   * as an argument to the updater is not used to return the schema
1993   * (see for example usage in {@code CoreSchemaProvider} class). This
1994   * case should remain exceptional.
1995   *
1996   * @param updater
1997   *          the updater that returns a new schema
1998   * @throws DirectoryException if there is any problem updating the schema
1999   */
2000  public void updateSchema(SchemaUpdater updater) throws DirectoryException
2001  {
2002    exclusiveLock.lock();
2003    try
2004    {
2005      switchSchema(updater.update(new SchemaBuilder(schemaNG)));
2006    }
2007    finally
2008    {
2009      exclusiveLock.unlock();
2010    }
2011  }
2012
2013  /** Interface to update a schema provided a schema builder. */
2014  public interface SchemaUpdater
2015  {
2016    /**
2017     * Returns an updated schema.
2018     *
2019     * @param builder
2020     *          The builder on the current schema
2021     * @return the new schema
2022     */
2023    org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder);
2024  }
2025
2026  /**
2027   * Updates the schema option  if the new value differs from the old value.
2028   *
2029   * @param <T> the schema option's type
2030   * @param option the schema option to update
2031   * @param newValue the new value for the schema option
2032   * @throws DirectoryException if there is any problem updating the schema
2033   */
2034  public <T> void updateSchemaOption(final Option<T> option, final T newValue) throws DirectoryException
2035  {
2036    final T oldValue = schemaNG.getOption(option);
2037    if (!oldValue.equals(newValue))
2038    {
2039      updateSchema(new SchemaUpdater()
2040      {
2041        @Override
2042        public org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder)
2043        {
2044          return builder.setOption(option, newValue).toSchema();
2045        }
2046      });
2047    }
2048  }
2049
2050  /** Takes an exclusive lock on the schema. */
2051  public void exclusiveLock()
2052  {
2053    exclusiveLock.lock();
2054  }
2055
2056  /** Releases an exclusive lock on the schema. */
2057  public void exclusiveUnlock()
2058  {
2059    exclusiveLock.unlock();
2060  }
2061
2062  /**
2063   * Adds the provided schema file to the provided schema element definition.
2064   *
2065   * @param definition
2066   *            The schema element definition
2067   * @param schemaFile
2068   *            The name of the schema file to include in the definition
2069   * @return  The definition string of the element
2070   *          including the X-SCHEMA-FILE extension.
2071   */
2072  public static String addSchemaFileToElementDefinitionIfAbsent(String definition, String schemaFile)
2073  {
2074    if (schemaFile != null && !definition.contains(SCHEMA_PROPERTY_FILENAME))
2075    {
2076      int pos = definition.lastIndexOf(')');
2077      return definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )";
2078    }
2079    return definition;
2080  }
2081
2082  private void switchSchema(org.forgerock.opendj.ldap.schema.Schema newSchema) throws DirectoryException
2083  {
2084    rejectSchemaWithWarnings(newSchema);
2085    schemaNG = newSchema.asNonStrictSchema();
2086    if (DirectoryServer.getSchema() == this)
2087    {
2088      org.forgerock.opendj.ldap.schema.Schema.setDefaultSchema(schemaNG);
2089    }
2090  }
2091
2092  private void rejectSchemaWithWarnings(org.forgerock.opendj.ldap.schema.Schema newSchema) throws DirectoryException
2093  {
2094    Collection<LocalizableMessage> warnings = newSchema.getWarnings();
2095    if (!warnings.isEmpty())
2096    {
2097      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
2098          ERR_SCHEMA_HAS_WARNINGS.get(warnings.size(), Utils.joinAsString("; ", warnings)));
2099    }
2100  }
2101
2102  /**
2103   * Replaces an existing object class by another object class.
2104   *
2105   * @param objectClass
2106   *          Object class to register to the schema.
2107   * @param existingClass
2108   *          Object class to remove from the schema.
2109   * @param schemaFile
2110   *          The schema file which the new object class belongs to.
2111   * @throws DirectoryException
2112   *            If an errors occurs.
2113   */
2114  public void replaceObjectClass(ObjectClass objectClass, ObjectClass existingClass, String schemaFile)
2115      throws DirectoryException
2116  {
2117    exclusiveLock.lock();
2118    try
2119    {
2120      SchemaBuilder builder = new SchemaBuilder(schemaNG);
2121      builder.removeObjectClass(existingClass.getNameOrOID());
2122      registerObjectClass0(builder, objectClass, schemaFile, false);
2123      switchSchema(builder.toSchema());
2124    }
2125    finally
2126    {
2127      exclusiveLock.unlock();
2128    }
2129  }
2130}