mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
24.57.2011 a849a42d97109bc9242c50c7abbd9857e865bb5e
Fix OPENDJ-205: Add support for rejecting and skipping records to the LDIF readers

Add full schema validation support:

* Entries#conformsToSchema and Schema#validateEntry - validates an entry against a schema
* SchemaValidationPolicy - validation configuration parameters
* Removed SchemaCompatOptions and merged into SchemaBuilder / Schema
* Some additional utility methods in Entries class
* Added call-back for recording schema validation warnings in RejectedRecordListener.
1 files deleted
1 files added
1 files renamed
14 files modified
3559 ■■■■ changed files
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java 225 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java 52 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DirectoryStringSyntaxImpl.java 4 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java 50 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java 1401 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java 639 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptions.java 227 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaValidationPolicy.java 505 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/TelephoneNumberSyntaxImpl.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java 133 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java 122 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFEntryReader.java 50 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/RejectedRecordListener.java 28 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties 45 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatTest.java 16 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java 56 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFEntryReaderTestCase.java 4 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
@@ -23,18 +23,22 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
import java.util.Collection;
import java.util.Iterator;
import static org.forgerock.opendj.ldap.AttributeDescription.objectClass;
import java.util.*;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.schema.*;
import com.forgerock.opendj.util.Function;
import com.forgerock.opendj.util.Iterables;
@@ -352,6 +356,79 @@
  /**
   * Returns {@code true} if the provided entry is valid according to the
   * specified schema and schema validation policy.
   * <p>
   * If attribute value validation is enabled then following checks will be
   * performed:
   * <ul>
   * <li>checking that there is at least one value
   * <li>checking that single-valued attributes contain only a single value
   * </ul>
   * In particular, attribute values will not be checked for conformance to
   * their syntax since this is expected to have already been performed while
   * adding the values to the entry.
   *
   * @param entry
   *          The entry to be validated.
   * @param schema
   *          The schema against which the entry will be validated.
   * @param policy
   *          The schema validation policy.
   * @param errorMessages
   *          A collection into which any schema validation warnings or error
   *          messages can be placed, or {@code null} if they should not be
   *          saved.
   * @return {@code true} if the provided entry is valid according to the
   *         specified schema and schema validation policy.
   * @see Schema#validateEntry(Entry, SchemaValidationPolicy, Collection)
   */
  public static boolean conformsToSchema(final Entry entry,
      final Schema schema, final SchemaValidationPolicy policy,
      final Collection<LocalizableMessage> errorMessages)
  {
    return schema.validateEntry(entry, policy, errorMessages);
  }
  /**
   * Returns {@code true} if the provided entry is valid according to the
   * default schema and schema validation policy.
   * <p>
   * If attribute value validation is enabled then following checks will be
   * performed:
   * <ul>
   * <li>checking that there is at least one value
   * <li>checking that single-valued attributes contain only a single value
   * </ul>
   * In particular, attribute values will not be checked for conformance to
   * their syntax since this is expected to have already been performed while
   * adding the values to the entry.
   *
   * @param entry
   *          The entry to be validated.
   * @param policy
   *          The schema validation policy.
   * @param errorMessages
   *          A collection into which any schema validation warnings or error
   *          messages can be placed, or {@code null} if they should not be
   *          saved.
   * @return {@code true} if the provided entry is valid according to the
   *         default schema and schema validation policy.
   * @see Schema#validateEntry(Entry, SchemaValidationPolicy, Collection)
   */
  public static boolean conformsToSchema(final Entry entry,
      final SchemaValidationPolicy policy,
      final Collection<LocalizableMessage> errorMessages)
  {
    return conformsToSchema(entry, Schema.getDefaultSchema(), policy,
        errorMessages);
  }
  /**
   * Creates a new modify request containing a list of modifications which can
   * be used to transform {@code fromEntry} into entry {@code toEntry}.
   * <p>
@@ -379,7 +456,7 @@
   *           If {@code fromEntry} or {@code toEntry} were {@code null}.
   * @see Requests#newModifyRequest(Entry, Entry)
   */
  public static final ModifyRequest diffEntries(final Entry fromEntry,
  public static ModifyRequest diffEntries(final Entry fromEntry,
      final Entry toEntry) throws NullPointerException
  {
    Validator.ensureNotNull(fromEntry, toEntry);
@@ -488,6 +565,146 @@
  /**
   * Returns an unmodifiable set containing the object classes associated with
   * the provided entry. This method will ignore unrecognized object classes.
   * <p>
   * This method uses the default schema for decoding the object class attribute
   * values.
   *
   * @param entry
   *          The entry whose object classes are required.
   * @return An unmodifiable set containing the object classes associated with
   *         the provided entry.
   */
  public static Set<ObjectClass> getObjectClasses(final Entry entry)
  {
    return getObjectClasses(entry, Schema.getDefaultSchema());
  }
  /**
   * Returns an unmodifiable set containing the object classes associated with
   * the provided entry. This method will ignore unrecognized object classes.
   *
   * @param entry
   *          The entry whose object classes are required.
   * @param schema
   *          The schema which should be used for decoding the object class
   *          attribute values.
   * @return An unmodifiable set containing the object classes associated with
   *         the provided entry.
   */
  public static Set<ObjectClass> getObjectClasses(final Entry entry,
      final Schema schema)
  {
    final Attribute objectClassAttribute = entry
        .getAttribute(AttributeDescription.objectClass());
    if (objectClassAttribute == null)
    {
      return Collections.emptySet();
    }
    else
    {
      final Set<ObjectClass> objectClasses = new HashSet<ObjectClass>(
          objectClassAttribute.size());
      for (final ByteString v : objectClassAttribute)
      {
        final String objectClassName = v.toString();
        final ObjectClass objectClass;
        try
        {
          objectClass = schema.getObjectClass(objectClassName);
          objectClasses.add(objectClass);
        }
        catch (final UnknownSchemaElementException e)
        {
          // Ignore.
          continue;
        }
      }
      return Collections.unmodifiableSet(objectClasses);
    }
  }
  /**
   * Returns the structural object class associated with the provided entry, or
   * {@code null} if none was found. If the entry contains multiple structural
   * object classes then the first will be returned. This method will ignore
   * unrecognized object classes.
   * <p>
   * This method uses the default schema for decoding the object class attribute
   * values.
   *
   * @param entry
   *          The entry whose structural object class is required.
   * @return The structural object class associated with the provided entry, or
   *         {@code null} if none was found.
   */
  public static ObjectClass getStructuralObjectClass(final Entry entry)
  {
    return getStructuralObjectClass(entry, Schema.getDefaultSchema());
  }
  /**
   * Returns the structural object class associated with the provided entry, or
   * {@code null} if none was found. If the entry contains multiple structural
   * object classes then the first will be returned. This method will ignore
   * unrecognized object classes.
   *
   * @param entry
   *          The entry whose structural object class is required.
   * @param schema
   *          The schema which should be used for decoding the object class
   *          attribute values.
   * @return The structural object class associated with the provided entry, or
   *         {@code null} if none was found.
   */
  public static ObjectClass getStructuralObjectClass(final Entry entry,
      final Schema schema)
  {
    ObjectClass structuralObjectClass = null;
    final Attribute objectClassAttribute = entry.getAttribute(objectClass());
    if (objectClassAttribute == null)
    {
      return null;
    }
    for (final ByteString v : objectClassAttribute)
    {
      final String objectClassName = v.toString();
      final ObjectClass objectClass;
      try
      {
        objectClass = schema.getObjectClass(objectClassName);
      }
      catch (final UnknownSchemaElementException e)
      {
        // Ignore.
        continue;
      }
      if (objectClass.getObjectClassType() == ObjectClassType.STRUCTURAL)
      {
        if (structuralObjectClass == null
            || objectClass.isDescendantOf(structuralObjectClass))
        {
          structuralObjectClass = objectClass;
        }
      }
    }
    return structuralObjectClass;
  }
  /**
   * Returns a read-only view of {@code entry} and its attributes. Query
   * operations on the returned entry and its attributes "read-through" to the
   * underlying entry or attribute, and attempts to modify the returned entry
@@ -500,7 +717,7 @@
   * @throws NullPointerException
   *           If {@code entry} was {@code null}.
   */
  public static final Entry unmodifiableEntry(final Entry entry)
  public static Entry unmodifiableEntry(final Entry entry)
      throws NullPointerException
  {
    return new UnmodifiableEntry(entry);
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS
 */
package org.forgerock.opendj.ldap.schema;
@@ -42,7 +43,7 @@
/**
 * This class defines a DIT content rule, which defines the set of allowed,
 * required, and prohibited attributes for entries with a given structural
 * objectclass, and also indicates which auxiliary classes that may be included
 * objectclass, and also indicates which auxiliary classes may be included
 * in the entry.
 */
public final class DITContentRule extends SchemaElement
@@ -287,6 +288,55 @@
  /**
   * Indicates whether the provided attribute type is included in the optional
   * attribute list for this DIT content rule.
   *
   * @param attributeType
   *          The attribute type for which to make the determination.
   * @return <code>true</code> if the provided attribute type is optional for
   *         this DIT content rule, or <code>false</code> if not.
   */
  public boolean isOptional(final AttributeType attributeType)
  {
    return optionalAttributes.contains(attributeType);
  }
  /**
   * Indicates whether the provided attribute type is included in the required
   * attribute list for this DIT content rule.
   *
   * @param attributeType
   *          The attribute type for which to make the determination.
   * @return <code>true</code> if the provided attribute type is required by
   *         this DIT content rule, or <code>false</code> if not.
   */
  public boolean isRequired(final AttributeType attributeType)
  {
    return requiredAttributes.contains(attributeType);
  }
  /**
   * Indicates whether the provided attribute type is in the list of required or
   * optional attributes for this DIT content rule.
   *
   * @param attributeType
   *          The attribute type for which to make the determination.
   * @return <code>true</code> if the provided attribute type is required or
   *         allowed for this DIT content rule, or <code>false</code> if it is
   *         not.
   */
  public boolean isRequiredOrOptional(final AttributeType attributeType)
  {
    return isRequired(attributeType) || isOptional(attributeType);
  }
  /**
   * Returns the string representation of this schema definition in the form
   * specified in RFC 2252.
   *
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DirectoryStringSyntaxImpl.java
@@ -109,9 +109,7 @@
  public boolean valueIsAcceptable(final Schema schema,
      final ByteSequence value, final LocalizableMessageBuilder invalidReason)
  {
    if (value.length() > 0
        || schema.getSchemaCompatOptions()
            .allowZeroLengthDirectoryStrings())
    if (value.length() > 0 || schema.allowZeroLengthDirectoryStrings())
    {
      return true;
    }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS
 */
package org.forgerock.opendj.ldap.schema;
@@ -250,6 +251,55 @@
  /**
   * Indicates whether the provided attribute type is included in the optional
   * attribute list for this name form.
   *
   * @param attributeType
   *          The attribute type for which to make the determination.
   * @return <code>true</code> if the provided attribute type is optional for
   *         this name form, or <code>false</code> if not.
   */
  public boolean isOptional(final AttributeType attributeType)
  {
    return optionalAttributes.contains(attributeType);
  }
  /**
   * Indicates whether the provided attribute type is included in the required
   * attribute list for this name form.
   *
   * @param attributeType
   *          The attribute type for which to make the determination.
   * @return <code>true</code> if the provided attribute type is required by
   *         this name form, or <code>false</code> if not.
   */
  public boolean isRequired(final AttributeType attributeType)
  {
    return requiredAttributes.contains(attributeType);
  }
  /**
   * Indicates whether the provided attribute type is in the list of required or
   * optional attributes for this name form.
   *
   * @param attributeType
   *          The attribute type for which to make the determination.
   * @return <code>true</code> if the provided attribute type is required or
   *         allowed for this name form, or <code>false</code> if it is
   *         not.
   */
  public boolean isRequiredOrOptional(final AttributeType attributeType)
  {
    return isRequired(attributeType) || isOptional(attributeType);
  }
  /**
   * Returns the string representation of this schema definition in the form
   * specified in RFC 2252.
   *
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
@@ -29,12 +29,10 @@
import static org.forgerock.opendj.ldap.AttributeDescription.objectClass;
import static org.forgerock.opendj.ldap.CoreMessages.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.*;
@@ -63,17 +61,36 @@
{
  private static final class EmptyImpl implements Impl
  {
    private final SchemaCompatOptions options;
    private EmptyImpl()
    {
      this.options = SchemaCompatOptions.defaultOptions();
      // Nothing to do.
    }
    public boolean allowMalformedNamesAndOptions()
    {
      return true;
    }
    public boolean allowNonStandardTelephoneNumbers()
    {
      return true;
    }
    public boolean allowZeroLengthDirectoryStrings()
    {
      return false;
    }
    @Override
    public AttributeType getAttributeType(final String name)
    {
      // Construct an placeholder attribute type with the given name,
@@ -91,6 +108,7 @@
    @Override
    public Collection<AttributeType> getAttributeTypes()
    {
      return Collections.emptyList();
@@ -98,13 +116,23 @@
    public List<AttributeType> getAttributeTypesByName(final String name)
    @Override
    public List<AttributeType> getAttributeTypesWithName(final String name)
    {
      return Collections.emptyList();
    }
    @Override
    public DITContentRule getDITContentRule(final ObjectClass structuralClass)
    {
      return null;
    }
    @Override
    public DITContentRule getDITContentRule(final String name)
        throws UnknownSchemaElementException
    {
@@ -113,6 +141,7 @@
    @Override
    public Collection<DITContentRule> getDITContentRules()
    {
      return Collections.emptyList();
@@ -120,13 +149,16 @@
    public Collection<DITContentRule> getDITContentRulesByName(final String name)
    @Override
    public Collection<DITContentRule> getDITContentRulesWithName(
        final String name)
    {
      return Collections.emptyList();
    }
    @Override
    public DITStructureRule getDITStructureRule(final int ruleID)
        throws UnknownSchemaElementException
    {
@@ -136,15 +168,8 @@
    public Collection<DITStructureRule> getDITStructureRulesByName(
        final String name)
    {
      return Collections.emptyList();
    }
    public Collection<DITStructureRule> getDITStructureRulesByNameForm(
    @Override
    public Collection<DITStructureRule> getDITStructureRules(
        final NameForm nameForm)
    {
      return Collections.emptyList();
@@ -152,6 +177,16 @@
    @Override
    public Collection<DITStructureRule> getDITStructureRulesWithName(
        final String name)
    {
      return Collections.emptyList();
    }
    @Override
    public Collection<DITStructureRule> getDITStuctureRules()
    {
      return Collections.emptyList();
@@ -159,6 +194,7 @@
    @Override
    public MatchingRule getMatchingRule(final String name)
        throws UnknownSchemaElementException
    {
@@ -167,6 +203,7 @@
    @Override
    public Collection<MatchingRule> getMatchingRules()
    {
      return Collections.emptyList();
@@ -174,21 +211,23 @@
    public Collection<MatchingRule> getMatchingRulesByName(final String name)
    @Override
    public Collection<MatchingRule> getMatchingRulesWithName(final String name)
    {
      return Collections.emptyList();
    }
    @Override
    public MatchingRuleUse getMatchingRuleUse(final MatchingRule matchingRule)
        throws UnknownSchemaElementException
    {
      return getMatchingRuleUse(matchingRule.getOID());
      return null;
    }
    @Override
    public MatchingRuleUse getMatchingRuleUse(final String name)
        throws UnknownSchemaElementException
    {
@@ -197,6 +236,7 @@
    @Override
    public Collection<MatchingRuleUse> getMatchingRuleUses()
    {
      return Collections.emptyList();
@@ -204,7 +244,8 @@
    public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
    @Override
    public Collection<MatchingRuleUse> getMatchingRuleUsesWithName(
        final String name)
    {
      return Collections.emptyList();
@@ -212,6 +253,7 @@
    @Override
    public NameForm getNameForm(final String name)
        throws UnknownSchemaElementException
    {
@@ -220,14 +262,7 @@
    public Collection<NameForm> getNameFormByObjectClass(
        final ObjectClass structuralClass)
    {
      return Collections.emptyList();
    }
    @Override
    public Collection<NameForm> getNameForms()
    {
      return Collections.emptyList();
@@ -235,22 +270,33 @@
    public Collection<NameForm> getNameFormsByName(final String name)
    @Override
    public Collection<NameForm> getNameForms(final ObjectClass structuralClass)
    {
      return Collections.emptyList();
    }
    public ObjectClass getObjectClass(final String name)
        throws UnknownSchemaElementException
    @Override
    public Collection<NameForm> getNameFormsWithName(final String name)
    {
      throw new UnknownSchemaElementException(WARN_OBJECTCLASS_UNKNOWN
          .get(name));
      return Collections.emptyList();
    }
    @Override
    public ObjectClass getObjectClass(final String name)
        throws UnknownSchemaElementException
    {
      throw new UnknownSchemaElementException(
          WARN_OBJECTCLASS_UNKNOWN.get(name));
    }
    @Override
    public Collection<ObjectClass> getObjectClasses()
    {
      return Collections.emptyList();
@@ -258,23 +304,18 @@
    public Collection<ObjectClass> getObjectClassesByName(final String name)
    @Override
    public Collection<ObjectClass> getObjectClassesWithName(final String name)
    {
      return Collections.emptyList();
    }
    public SchemaCompatOptions getSchemaCompatOptions()
    {
      return options;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getSchemaName()
    {
      return "Empty Schema";
@@ -282,6 +323,7 @@
    @Override
    public Syntax getSyntax(final String numericOID)
    {
      // Fake up a syntax substituted by the default syntax.
@@ -290,6 +332,7 @@
    @Override
    public Collection<Syntax> getSyntaxes()
    {
      return Collections.emptyList();
@@ -297,6 +340,7 @@
    @Override
    public Collection<LocalizableMessage> getWarnings()
    {
      return Collections.emptyList();
@@ -304,6 +348,7 @@
    @Override
    public boolean hasAttributeType(final String name)
    {
      // In theory a non-strict schema always contains the requested
@@ -315,6 +360,7 @@
    @Override
    public boolean hasDITContentRule(final String name)
    {
      return false;
@@ -322,6 +368,7 @@
    @Override
    public boolean hasDITStructureRule(final int ruleID)
    {
      return false;
@@ -329,6 +376,7 @@
    @Override
    public boolean hasMatchingRule(final String name)
    {
      return false;
@@ -336,6 +384,7 @@
    @Override
    public boolean hasMatchingRuleUse(final String name)
    {
      return false;
@@ -343,6 +392,7 @@
    @Override
    public boolean hasNameForm(final String name)
    {
      return false;
@@ -350,6 +400,7 @@
    @Override
    public boolean hasObjectClass(final String name)
    {
      return false;
@@ -357,6 +408,7 @@
    @Override
    public boolean hasSyntax(final String numericOID)
    {
      return false;
@@ -364,6 +416,7 @@
    @Override
    public boolean isStrict()
    {
      return false;
@@ -383,7 +436,11 @@
    List<AttributeType> getAttributeTypesByName(String name);
    List<AttributeType> getAttributeTypesWithName(String name);
    DITContentRule getDITContentRule(ObjectClass structuralClass);
@@ -396,7 +453,7 @@
    Collection<DITContentRule> getDITContentRulesByName(String name);
    Collection<DITContentRule> getDITContentRulesWithName(String name);
@@ -405,12 +462,11 @@
    Collection<DITStructureRule> getDITStructureRulesByName(String name);
    Collection<DITStructureRule> getDITStructureRules(NameForm nameForm);
    Collection<DITStructureRule> getDITStructureRulesByNameForm(
        NameForm nameForm);
    Collection<DITStructureRule> getDITStructureRulesWithName(String name);
@@ -427,12 +483,11 @@
    Collection<MatchingRule> getMatchingRulesByName(String name);
    Collection<MatchingRule> getMatchingRulesWithName(String name);
    MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
        throws UnknownSchemaElementException;
    MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule);
@@ -445,7 +500,7 @@
    Collection<MatchingRuleUse> getMatchingRuleUsesByName(String name);
    Collection<MatchingRuleUse> getMatchingRuleUsesWithName(String name);
@@ -453,15 +508,15 @@
    Collection<NameForm> getNameFormByObjectClass(ObjectClass structuralClass);
    Collection<NameForm> getNameForms();
    Collection<NameForm> getNameFormsByName(String name);
    Collection<NameForm> getNameForms(ObjectClass structuralClass);
    Collection<NameForm> getNameFormsWithName(String name);
@@ -474,11 +529,7 @@
    Collection<ObjectClass> getObjectClassesByName(String name);
    SchemaCompatOptions getSchemaCompatOptions();
    Collection<ObjectClass> getObjectClassesWithName(String name);
@@ -531,6 +582,18 @@
    boolean isStrict();
    boolean allowMalformedNamesAndOptions();
    boolean allowNonStandardTelephoneNumbers();
    boolean allowZeroLengthDirectoryStrings();
  }
@@ -548,6 +611,28 @@
    public boolean allowMalformedNamesAndOptions()
    {
      return strictImpl.allowMalformedNamesAndOptions();
    }
    public boolean allowNonStandardTelephoneNumbers()
    {
      return strictImpl.allowNonStandardTelephoneNumbers();
    }
    public boolean allowZeroLengthDirectoryStrings()
    {
      return strictImpl.allowZeroLengthDirectoryStrings();
    }
    @Override
    public AttributeType getAttributeType(final String name)
        throws UnknownSchemaElementException
    {
@@ -570,6 +655,7 @@
    @Override
    public Collection<AttributeType> getAttributeTypes()
    {
      return strictImpl.getAttributeTypes();
@@ -577,13 +663,23 @@
    public List<AttributeType> getAttributeTypesByName(final String name)
    @Override
    public List<AttributeType> getAttributeTypesWithName(final String name)
    {
      return strictImpl.getAttributeTypesByName(name);
      return strictImpl.getAttributeTypesWithName(name);
    }
    @Override
    public DITContentRule getDITContentRule(final ObjectClass structuralClass)
    {
      return strictImpl.getDITContentRule(structuralClass);
    }
    @Override
    public DITContentRule getDITContentRule(final String name)
        throws UnknownSchemaElementException
    {
@@ -592,6 +688,7 @@
    @Override
    public Collection<DITContentRule> getDITContentRules()
    {
      return strictImpl.getDITContentRules();
@@ -599,13 +696,16 @@
    public Collection<DITContentRule> getDITContentRulesByName(final String name)
    @Override
    public Collection<DITContentRule> getDITContentRulesWithName(
        final String name)
    {
      return strictImpl.getDITContentRulesByName(name);
      return strictImpl.getDITContentRulesWithName(name);
    }
    @Override
    public DITStructureRule getDITStructureRule(final int ruleID)
        throws UnknownSchemaElementException
    {
@@ -614,22 +714,25 @@
    public Collection<DITStructureRule> getDITStructureRulesByName(
        final String name)
    {
      return strictImpl.getDITStructureRulesByName(name);
    }
    public Collection<DITStructureRule> getDITStructureRulesByNameForm(
    @Override
    public Collection<DITStructureRule> getDITStructureRules(
        final NameForm nameForm)
    {
      return strictImpl.getDITStructureRulesByNameForm(nameForm);
      return strictImpl.getDITStructureRules(nameForm);
    }
    @Override
    public Collection<DITStructureRule> getDITStructureRulesWithName(
        final String name)
    {
      return strictImpl.getDITStructureRulesWithName(name);
    }
    @Override
    public Collection<DITStructureRule> getDITStuctureRules()
    {
      return strictImpl.getDITStuctureRules();
@@ -637,6 +740,7 @@
    @Override
    public MatchingRule getMatchingRule(final String name)
        throws UnknownSchemaElementException
    {
@@ -645,6 +749,7 @@
    @Override
    public Collection<MatchingRule> getMatchingRules()
    {
      return strictImpl.getMatchingRules();
@@ -652,21 +757,23 @@
    public Collection<MatchingRule> getMatchingRulesByName(final String name)
    @Override
    public Collection<MatchingRule> getMatchingRulesWithName(final String name)
    {
      return strictImpl.getMatchingRulesByName(name);
      return strictImpl.getMatchingRulesWithName(name);
    }
    @Override
    public MatchingRuleUse getMatchingRuleUse(final MatchingRule matchingRule)
        throws UnknownSchemaElementException
    {
      return strictImpl.getMatchingRuleUse(matchingRule);
    }
    @Override
    public MatchingRuleUse getMatchingRuleUse(final String name)
        throws UnknownSchemaElementException
    {
@@ -675,6 +782,7 @@
    @Override
    public Collection<MatchingRuleUse> getMatchingRuleUses()
    {
      return strictImpl.getMatchingRuleUses();
@@ -682,14 +790,16 @@
    public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
    @Override
    public Collection<MatchingRuleUse> getMatchingRuleUsesWithName(
        final String name)
    {
      return strictImpl.getMatchingRuleUsesByName(name);
      return strictImpl.getMatchingRuleUsesWithName(name);
    }
    @Override
    public NameForm getNameForm(final String name)
        throws UnknownSchemaElementException
    {
@@ -698,14 +808,7 @@
    public Collection<NameForm> getNameFormByObjectClass(
        final ObjectClass structuralClass)
    {
      return strictImpl.getNameFormByObjectClass(structuralClass);
    }
    @Override
    public Collection<NameForm> getNameForms()
    {
      return strictImpl.getNameForms();
@@ -713,13 +816,23 @@
    public Collection<NameForm> getNameFormsByName(final String name)
    @Override
    public Collection<NameForm> getNameForms(final ObjectClass structuralClass)
    {
      return strictImpl.getNameFormsByName(name);
      return strictImpl.getNameForms(structuralClass);
    }
    @Override
    public Collection<NameForm> getNameFormsWithName(final String name)
    {
      return strictImpl.getNameFormsWithName(name);
    }
    @Override
    public ObjectClass getObjectClass(final String name)
        throws UnknownSchemaElementException
    {
@@ -728,6 +841,7 @@
    @Override
    public Collection<ObjectClass> getObjectClasses()
    {
      return strictImpl.getObjectClasses();
@@ -735,20 +849,15 @@
    public Collection<ObjectClass> getObjectClassesByName(final String name)
    @Override
    public Collection<ObjectClass> getObjectClassesWithName(final String name)
    {
      return strictImpl.getObjectClassesByName(name);
      return strictImpl.getObjectClassesWithName(name);
    }
    public SchemaCompatOptions getSchemaCompatOptions()
    {
      return strictImpl.getSchemaCompatOptions();
    }
    @Override
    public String getSchemaName()
    {
      return strictImpl.getSchemaName();
@@ -756,6 +865,7 @@
    @Override
    public Syntax getSyntax(final String numericOID)
    {
      if (!strictImpl.hasSyntax(numericOID))
@@ -767,6 +877,7 @@
    @Override
    public Collection<Syntax> getSyntaxes()
    {
      return strictImpl.getSyntaxes();
@@ -774,6 +885,7 @@
    @Override
    public Collection<LocalizableMessage> getWarnings()
    {
      return strictImpl.getWarnings();
@@ -781,6 +893,7 @@
    @Override
    public boolean hasAttributeType(final String name)
    {
      // In theory a non-strict schema always contains the requested
@@ -792,6 +905,7 @@
    @Override
    public boolean hasDITContentRule(final String name)
    {
      return strictImpl.hasDITContentRule(name);
@@ -799,6 +913,7 @@
    @Override
    public boolean hasDITStructureRule(final int ruleID)
    {
      return strictImpl.hasDITStructureRule(ruleID);
@@ -806,6 +921,7 @@
    @Override
    public boolean hasMatchingRule(final String name)
    {
      return strictImpl.hasMatchingRule(name);
@@ -813,6 +929,7 @@
    @Override
    public boolean hasMatchingRuleUse(final String name)
    {
      return strictImpl.hasMatchingRuleUse(name);
@@ -820,6 +937,7 @@
    @Override
    public boolean hasNameForm(final String name)
    {
      return strictImpl.hasNameForm(name);
@@ -827,6 +945,7 @@
    @Override
    public boolean hasObjectClass(final String name)
    {
      return strictImpl.hasObjectClass(name);
@@ -834,6 +953,7 @@
    @Override
    public boolean hasSyntax(final String numericOID)
    {
      return strictImpl.hasSyntax(numericOID);
@@ -841,6 +961,7 @@
    @Override
    public boolean isStrict()
    {
      return false;
@@ -885,15 +1006,22 @@
    private final Map<String, List<NameForm>> objectClass2NameForms;
    private final SchemaCompatOptions options;
    private final List<LocalizableMessage> warnings;
    private final String schemaName;
    private final boolean allowNonStandardTelephoneNumbers;
    private final boolean allowZeroLengthDirectoryStrings;
    private final boolean allowMalformedNamesAndOptions;
    private StrictImpl(final String schemaName,
    StrictImpl(final String schemaName,
        final boolean allowMalformedNamesAndOptions,
        final boolean allowNonStandardTelephoneNumbers,
        final boolean allowZeroLengthDirectoryStrings,
        final Map<String, Syntax> numericOID2Syntaxes,
        final Map<String, MatchingRule> numericOID2MatchingRules,
        final Map<String, MatchingRuleUse> numericOID2MatchingRuleUses,
@@ -911,10 +1039,12 @@
        final Map<String, List<DITStructureRule>> name2StructureRules,
        final Map<String, List<NameForm>> objectClass2NameForms,
        final Map<String, List<DITStructureRule>> nameForm2StructureRules,
        final SchemaCompatOptions options,
        final List<LocalizableMessage> warnings)
    {
      this.schemaName = schemaName;
      this.allowMalformedNamesAndOptions = allowMalformedNamesAndOptions;
      this.allowNonStandardTelephoneNumbers = allowNonStandardTelephoneNumbers;
      this.allowZeroLengthDirectoryStrings = allowZeroLengthDirectoryStrings;
      this.numericOID2Syntaxes = Collections
          .unmodifiableMap(numericOID2Syntaxes);
      this.numericOID2MatchingRules = Collections
@@ -944,12 +1074,33 @@
          .unmodifiableMap(objectClass2NameForms);
      this.nameForm2StructureRules = Collections
          .unmodifiableMap(nameForm2StructureRules);
      this.options = options;
      this.warnings = Collections.unmodifiableList(warnings);
    }
    public boolean allowMalformedNamesAndOptions()
    {
      return allowMalformedNamesAndOptions;
    }
    public boolean allowNonStandardTelephoneNumbers()
    {
      return allowNonStandardTelephoneNumbers;
    }
    public boolean allowZeroLengthDirectoryStrings()
    {
      return allowZeroLengthDirectoryStrings;
    }
    @Override
    public AttributeType getAttributeType(final String name)
        throws UnknownSchemaElementException
    {
@@ -966,14 +1117,15 @@
        {
          return attributes.get(0);
        }
        throw new UnknownSchemaElementException(WARN_ATTR_TYPE_AMBIGIOUS
            .get(name));
        throw new UnknownSchemaElementException(
            WARN_ATTR_TYPE_AMBIGIOUS.get(name));
      }
      throw new UnknownSchemaElementException(WARN_ATTR_TYPE_UNKNOWN.get(name));
    }
    @Override
    public Collection<AttributeType> getAttributeTypes()
    {
      return numericOID2AttributeTypes.values();
@@ -981,7 +1133,8 @@
    public List<AttributeType> getAttributeTypesByName(final String name)
    @Override
    public List<AttributeType> getAttributeTypesWithName(final String name)
    {
      final List<AttributeType> attributes = name2AttributeTypes
          .get(StaticUtils.toLowerCase(name));
@@ -997,6 +1150,15 @@
    @Override
    public DITContentRule getDITContentRule(final ObjectClass structuralClass)
    {
      return numericOID2ContentRules.get(structuralClass.getOID());
    }
    @Override
    public DITContentRule getDITContentRule(final String name)
        throws UnknownSchemaElementException
    {
@@ -1020,6 +1182,7 @@
    @Override
    public Collection<DITContentRule> getDITContentRules()
    {
      return numericOID2ContentRules.values();
@@ -1027,7 +1190,9 @@
    public Collection<DITContentRule> getDITContentRulesByName(final String name)
    @Override
    public Collection<DITContentRule> getDITContentRulesWithName(
        final String name)
    {
      final List<DITContentRule> rules = name2ContentRules.get(StaticUtils
          .toLowerCase(name));
@@ -1043,6 +1208,7 @@
    @Override
    public DITStructureRule getDITStructureRule(final int ruleID)
        throws UnknownSchemaElementException
    {
@@ -1057,24 +1223,8 @@
    public Collection<DITStructureRule> getDITStructureRulesByName(
        final String name)
    {
      final List<DITStructureRule> rules = name2StructureRules.get(StaticUtils
          .toLowerCase(name));
      if (rules == null)
      {
        return Collections.emptyList();
      }
      else
      {
        return rules;
      }
    }
    public Collection<DITStructureRule> getDITStructureRulesByNameForm(
    @Override
    public Collection<DITStructureRule> getDITStructureRules(
        final NameForm nameForm)
    {
      final List<DITStructureRule> rules = nameForm2StructureRules.get(nameForm
@@ -1091,6 +1241,25 @@
    @Override
    public Collection<DITStructureRule> getDITStructureRulesWithName(
        final String name)
    {
      final List<DITStructureRule> rules = name2StructureRules.get(StaticUtils
          .toLowerCase(name));
      if (rules == null)
      {
        return Collections.emptyList();
      }
      else
      {
        return rules;
      }
    }
    @Override
    public Collection<DITStructureRule> getDITStuctureRules()
    {
      return id2StructureRules.values();
@@ -1098,6 +1267,7 @@
    @Override
    public MatchingRule getMatchingRule(final String name)
        throws UnknownSchemaElementException
    {
@@ -1121,6 +1291,7 @@
    @Override
    public Collection<MatchingRule> getMatchingRules()
    {
      return numericOID2MatchingRules.values();
@@ -1128,7 +1299,8 @@
    public Collection<MatchingRule> getMatchingRulesByName(final String name)
    @Override
    public Collection<MatchingRule> getMatchingRulesWithName(final String name)
    {
      final List<MatchingRule> rules = name2MatchingRules.get(StaticUtils
          .toLowerCase(name));
@@ -1144,14 +1316,15 @@
    @Override
    public MatchingRuleUse getMatchingRuleUse(final MatchingRule matchingRule)
        throws UnknownSchemaElementException
    {
      return getMatchingRuleUse(matchingRule.getOID());
      return numericOID2MatchingRuleUses.get(matchingRule.getOID());
    }
    @Override
    public MatchingRuleUse getMatchingRuleUse(final String name)
        throws UnknownSchemaElementException
    {
@@ -1175,6 +1348,7 @@
    @Override
    public Collection<MatchingRuleUse> getMatchingRuleUses()
    {
      return numericOID2MatchingRuleUses.values();
@@ -1182,7 +1356,8 @@
    public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
    @Override
    public Collection<MatchingRuleUse> getMatchingRuleUsesWithName(
        final String name)
    {
      final List<MatchingRuleUse> rules = name2MatchingRuleUses.get(StaticUtils
@@ -1199,6 +1374,7 @@
    @Override
    public NameForm getNameForm(final String name)
        throws UnknownSchemaElementException
    {
@@ -1215,16 +1391,24 @@
        {
          return forms.get(0);
        }
        throw new UnknownSchemaElementException(WARN_NAMEFORM_AMBIGIOUS
            .get(name));
        throw new UnknownSchemaElementException(
            WARN_NAMEFORM_AMBIGIOUS.get(name));
      }
      throw new UnknownSchemaElementException(WARN_NAMEFORM_UNKNOWN.get(name));
    }
    public Collection<NameForm> getNameFormByObjectClass(
        final ObjectClass structuralClass)
    @Override
    public Collection<NameForm> getNameForms()
    {
      return numericOID2NameForms.values();
    }
    @Override
    public Collection<NameForm> getNameForms(final ObjectClass structuralClass)
    {
      final List<NameForm> forms = objectClass2NameForms.get(structuralClass
          .getOID());
@@ -1240,14 +1424,8 @@
    public Collection<NameForm> getNameForms()
    {
      return numericOID2NameForms.values();
    }
    public Collection<NameForm> getNameFormsByName(final String name)
    @Override
    public Collection<NameForm> getNameFormsWithName(final String name)
    {
      final List<NameForm> forms = name2NameForms.get(StaticUtils
          .toLowerCase(name));
@@ -1263,6 +1441,7 @@
    @Override
    public ObjectClass getObjectClass(final String name)
        throws UnknownSchemaElementException
    {
@@ -1279,15 +1458,16 @@
        {
          return classes.get(0);
        }
        throw new UnknownSchemaElementException(WARN_OBJECTCLASS_AMBIGIOUS
            .get(name));
        throw new UnknownSchemaElementException(
            WARN_OBJECTCLASS_AMBIGIOUS.get(name));
      }
      throw new UnknownSchemaElementException(WARN_OBJECTCLASS_UNKNOWN
          .get(name));
      throw new UnknownSchemaElementException(
          WARN_OBJECTCLASS_UNKNOWN.get(name));
    }
    @Override
    public Collection<ObjectClass> getObjectClasses()
    {
      return numericOID2ObjectClasses.values();
@@ -1295,7 +1475,8 @@
    public Collection<ObjectClass> getObjectClassesByName(final String name)
    @Override
    public Collection<ObjectClass> getObjectClassesWithName(final String name)
    {
      final List<ObjectClass> classes = name2ObjectClasses.get(StaticUtils
          .toLowerCase(name));
@@ -1311,13 +1492,7 @@
    public SchemaCompatOptions getSchemaCompatOptions()
    {
      return options;
    }
    @Override
    public String getSchemaName()
    {
      return schemaName;
@@ -1325,20 +1500,22 @@
    @Override
    public Syntax getSyntax(final String numericOID)
        throws UnknownSchemaElementException
    {
      final Syntax syntax = numericOID2Syntaxes.get(numericOID);
      if (syntax == null)
      {
        throw new UnknownSchemaElementException(WARN_SYNTAX_UNKNOWN
            .get(numericOID));
        throw new UnknownSchemaElementException(
            WARN_SYNTAX_UNKNOWN.get(numericOID));
      }
      return syntax;
    }
    @Override
    public Collection<Syntax> getSyntaxes()
    {
      return numericOID2Syntaxes.values();
@@ -1346,6 +1523,7 @@
    @Override
    public Collection<LocalizableMessage> getWarnings()
    {
      return warnings;
@@ -1353,6 +1531,7 @@
    @Override
    public boolean hasAttributeType(final String name)
    {
      if (numericOID2AttributeTypes.containsKey(name))
@@ -1366,6 +1545,7 @@
    @Override
    public boolean hasDITContentRule(final String name)
    {
      if (numericOID2ContentRules.containsKey(name))
@@ -1379,6 +1559,7 @@
    @Override
    public boolean hasDITStructureRule(final int ruleID)
    {
      return id2StructureRules.containsKey(ruleID);
@@ -1386,6 +1567,7 @@
    @Override
    public boolean hasMatchingRule(final String name)
    {
      if (numericOID2MatchingRules.containsKey(name))
@@ -1399,6 +1581,7 @@
    @Override
    public boolean hasMatchingRuleUse(final String name)
    {
      if (numericOID2MatchingRuleUses.containsKey(name))
@@ -1412,6 +1595,7 @@
    @Override
    public boolean hasNameForm(final String name)
    {
      if (numericOID2NameForms.containsKey(name))
@@ -1425,6 +1609,7 @@
    @Override
    public boolean hasObjectClass(final String name)
    {
      if (numericOID2ObjectClasses.containsKey(name))
@@ -1438,6 +1623,7 @@
    @Override
    public boolean hasSyntax(final String numericOID)
    {
      return numericOID2Syntaxes.containsKey(numericOID);
@@ -1445,6 +1631,7 @@
    @Override
    public boolean isStrict()
    {
      return true;
@@ -1452,6 +1639,7 @@
  }
  /*
   * WARNING: do not reference the core schema in the following declarations.
   */
@@ -1600,8 +1788,8 @@
   * @throws UnsupportedOperationException
   *           If the connection does not support search operations.
   * @throws IllegalStateException
   *           If the connection has already been closed, i.e. if {@code
   *           connection.isClosed() == true}.
   *           If the connection has already been closed, i.e. if
   *           {@code connection.isClosed() == true}.
   * @throws NullPointerException
   *           If the {@code connection} or {@code name} was {@code null}.
   */
@@ -1701,8 +1889,8 @@
   * @throws UnsupportedOperationException
   *           If the connection does not support search operations.
   * @throws IllegalStateException
   *           If the connection has already been closed, i.e. if {@code
   *           connection.isClosed() == true}.
   *           If the connection has already been closed, i.e. if
   *           {@code connection.isClosed() == true}.
   * @throws NullPointerException
   *           If the {@code connection} or {@code name} was {@code null}.
   */
@@ -1767,6 +1955,9 @@
  Schema(final String schemaName,
      final boolean allowMalformedNamesAndOptions,
      final boolean allowNonStandardTelephoneNumbers,
      final boolean allowZeroLengthDirectoryStrings,
      final Map<String, Syntax> numericOID2Syntaxes,
      final Map<String, MatchingRule> numericOID2MatchingRules,
      final Map<String, MatchingRuleUse> numericOID2MatchingRuleUses,
@@ -1784,16 +1975,17 @@
      final Map<String, List<DITStructureRule>> name2StructureRules,
      final Map<String, List<NameForm>> objectClass2NameForms,
      final Map<String, List<DITStructureRule>> nameForm2StructureRules,
      final SchemaCompatOptions options, final List<LocalizableMessage> warnings)
      final List<LocalizableMessage> warnings)
  {
    impl = new StrictImpl(schemaName, numericOID2Syntaxes,
        numericOID2MatchingRules, numericOID2MatchingRuleUses,
        numericOID2AttributeTypes, numericOID2ObjectClasses,
        numericOID2NameForms, numericOID2ContentRules, id2StructureRules,
        name2MatchingRules, name2MatchingRuleUses, name2AttributeTypes,
        name2ObjectClasses, name2NameForms, name2ContentRules,
        name2StructureRules, objectClass2NameForms, nameForm2StructureRules,
        options, warnings);
    impl = new StrictImpl(schemaName, allowMalformedNamesAndOptions,
        allowNonStandardTelephoneNumbers, allowZeroLengthDirectoryStrings,
        numericOID2Syntaxes, numericOID2MatchingRules,
        numericOID2MatchingRuleUses, numericOID2AttributeTypes,
        numericOID2ObjectClasses, numericOID2NameForms,
        numericOID2ContentRules, id2StructureRules, name2MatchingRules,
        name2MatchingRuleUses, name2AttributeTypes, name2ObjectClasses,
        name2NameForms, name2ContentRules, name2StructureRules,
        objectClass2NameForms, nameForm2StructureRules, warnings);
  }
@@ -1806,8 +1998,8 @@
  /**
   * Returns {@code true} if this schema allows certain illegal characters
   * in OIDs and attribute options. When this compatibility option is set to
   * Returns {@code true} if this schema allows certain illegal characters in
   * OIDs and attribute options. When this compatibility option is set to
   * {@code true} the following illegal characters will be permitted in addition
   * to those permitted in section 1.4 of RFC 4512:
   *
@@ -1819,14 +2011,14 @@
   * By default this compatibility option is set to {@code true} because these
   * characters are often used for naming purposes (such as collation rules).
   *
   * @return {@code true} if this schema allows certain illegal characters
   *         in OIDs and attribute options.
   * @return {@code true} if this schema allows certain illegal characters in
   *         OIDs and attribute options.
   * @see <a href="http://tools.ietf.org/html/rfc4512">RFC 4512 - Lightweight
   *      Directory Access Protocol (LDAP): Directory Information Models </a>
   */
  public boolean allowMalformedNamesAndOptions()
  {
    return impl.getSchemaCompatOptions().allowMalformedNamesAndOptions();
    return impl.allowMalformedNamesAndOptions();
  }
@@ -1844,8 +2036,7 @@
   */
  public boolean allowNonStandardTelephoneNumbers()
  {
    return impl.getSchemaCompatOptions()
        .allowNonStandardTelephoneNumbers();
    return impl.allowNonStandardTelephoneNumbers();
  }
@@ -1864,8 +2055,51 @@
   */
  public boolean allowZeroLengthDirectoryStrings()
  {
    return impl.getSchemaCompatOptions()
        .allowZeroLengthDirectoryStrings();
    return impl.allowZeroLengthDirectoryStrings();
  }
  /**
   * Returns a non-strict view of this schema.
   * <p>
   * See the description of {@link #isStrict()} for more details.
   *
   * @return A non-strict view of this schema.
   * @see Schema#isStrict()
   */
  public Schema asNonStrictSchema()
  {
    if (impl.isStrict())
    {
      return new Schema(new NonStrictImpl(impl));
    }
    else
    {
      return this;
    }
  }
  /**
   * Returns a strict view of this schema.
   * <p>
   * See the description of {@link #isStrict()} for more details.
   *
   * @return A strict view of this schema.
   * @see Schema#isStrict()
   */
  public Schema asStrictSchema()
  {
    if (impl.isStrict())
    {
      return this;
    }
    else
    {
      return new Schema(((NonStrictImpl) impl).strictImpl);
    }
  }
@@ -1911,9 +2145,25 @@
   * @return An unmodifiable collection containing all of the attribute types
   *         having the specified name or numeric OID.
   */
  public List<AttributeType> getAttributeTypesByName(final String name)
  public List<AttributeType> getAttributeTypesWithName(final String name)
  {
    return impl.getAttributeTypesByName(name);
    return impl.getAttributeTypesWithName(name);
  }
  /**
   * Returns the DIT content rule associated with the provided structural object
   * class, or {@code null} if no rule is defined.
   *
   * @param structuralClass
   *          The structural object class .
   * @return The DIT content rule associated with the provided structural object
   *         class, or {@code null} if no rule is defined.
   */
  public DITContentRule getDITContentRule(final ObjectClass structuralClass)
  {
    return impl.getDITContentRule(structuralClass);
  }
@@ -1959,9 +2209,9 @@
   * @return An unmodifiable collection containing all of the DIT content rules
   *         having the specified name or numeric OID.
   */
  public Collection<DITContentRule> getDITContentRulesByName(final String name)
  public Collection<DITContentRule> getDITContentRulesWithName(final String name)
  {
    return impl.getDITContentRulesByName(name);
    return impl.getDITContentRulesWithName(name);
  }
@@ -1986,6 +2236,23 @@
  /**
   * Returns an unmodifiable collection containing all of the DIT structure
   * rules associated with the provided name form.
   *
   * @param nameForm
   *          The name form.
   * @return An unmodifiable collection containing all of the DIT structure
   *         rules associated with the provided name form.
   */
  public Collection<DITStructureRule> getDITStructureRules(
      final NameForm nameForm)
  {
    return impl.getDITStructureRules(nameForm);
  }
  /**
   * Returns an unmodifiable collection containing all of the DIT structure
   * rules having the specified name or numeric OID.
   *
   * @param name
@@ -1993,25 +2260,10 @@
   * @return An unmodifiable collection containing all of the DIT structure
   *         rules having the specified name or numeric OID.
   */
  public Collection<DITStructureRule> getDITStructureRulesByName(
  public Collection<DITStructureRule> getDITStructureRulesWithName(
      final String name)
  {
    return impl.getDITStructureRulesByName(name);
  }
  /**
   * Retrieves the DIT structure rules for the provided name form.
   *
   * @param nameForm
   *          The name form.
   * @return The requested DIT structure rules.
   */
  public Collection<DITStructureRule> getDITStructureRulesByNameForm(
      final NameForm nameForm)
  {
    return impl.getDITStructureRulesByNameForm(nameForm);
    return impl.getDITStructureRulesWithName(name);
  }
@@ -2071,25 +2323,23 @@
   * @return An unmodifiable collection containing all of the matching rules
   *         having the specified name or numeric OID.
   */
  public Collection<MatchingRule> getMatchingRulesByName(final String name)
  public Collection<MatchingRule> getMatchingRulesWithName(final String name)
  {
    return impl.getMatchingRulesByName(name);
    return impl.getMatchingRulesWithName(name);
  }
  /**
   * Returns the matching rule use associated with the provided matching rule.
   * Returns the matching rule use associated with the provided matching rule,
   * or {@code null} if no use is defined.
   *
   * @param matchingRule
   *          The matching rule whose matching rule use is to be retrieved.
   * @return The requested matching rule use.
   * @throws UnknownSchemaElementException
   *           If this is a strict schema and the requested matching rule use
   *           was not found or if the provided name is ambiguous.
   * @return The matching rule use associated with the provided matching rule,
   *         or {@code null} if no use is defined.
   */
  public MatchingRuleUse getMatchingRuleUse(final MatchingRule matchingRule)
      throws UnknownSchemaElementException
  {
    return getMatchingRuleUse(matchingRule.getOID());
  }
@@ -2137,9 +2387,10 @@
   * @return An unmodifiable collection containing all of the matching rule uses
   *         having the specified name or numeric OID.
   */
  public Collection<MatchingRuleUse> getMatchingRuleUsesByName(final String name)
  public Collection<MatchingRuleUse> getMatchingRuleUsesWithName(
      final String name)
  {
    return impl.getMatchingRuleUsesByName(name);
    return impl.getMatchingRuleUsesWithName(name);
  }
@@ -2163,21 +2414,6 @@
  /**
   * Retrieves the name forms for the specified structural objectclass.
   *
   * @param structuralClass
   *          The structural objectclass for the name form to retrieve.
   * @return The requested name forms
   */
  public Collection<NameForm> getNameFormByObjectClass(
      final ObjectClass structuralClass)
  {
    return impl.getNameFormByObjectClass(structuralClass);
  }
  /**
   * Returns an unmodifiable collection containing all of the name forms
   * contained in this schema.
   *
@@ -2192,6 +2428,22 @@
  /**
   * Returns an unmodifiable collection containing all of the name forms
   * associated with the provided structural object class.
   *
   * @param structuralClass
   *          The structural object class whose name forms are to be retrieved.
   * @return An unmodifiable collection containing all of the name forms
   *         associated with the provided structural object class.
   */
  public Collection<NameForm> getNameForms(final ObjectClass structuralClass)
  {
    return impl.getNameForms(structuralClass);
  }
  /**
   * Returns an unmodifiable collection containing all of the name forms having
   * the specified name or numeric OID.
   *
@@ -2200,9 +2452,9 @@
   * @return An unmodifiable collection containing all of the name forms having
   *         the specified name or numeric OID.
   */
  public Collection<NameForm> getNameFormsByName(final String name)
  public Collection<NameForm> getNameFormsWithName(final String name)
  {
    return impl.getNameFormsByName(name);
    return impl.getNameFormsWithName(name);
  }
@@ -2248,9 +2500,9 @@
   * @return An unmodifiable collection containing all of the object classes
   *         having the specified name or numeric OID.
   */
  public Collection<ObjectClass> getObjectClassesByName(final String name)
  public Collection<ObjectClass> getObjectClassesWithName(final String name)
  {
    return impl.getObjectClassesByName(name);
    return impl.getObjectClassesWithName(name);
  }
@@ -2467,50 +2719,6 @@
  /**
   * Returns a non-strict view of this schema.
   * <p>
   * See the description of {@link #isStrict()} for more details.
   *
   * @return A non-strict view of this schema.
   * @see Schema#isStrict()
   */
  public Schema asNonStrictSchema()
  {
    if (impl.isStrict())
    {
      return new Schema(new NonStrictImpl(impl));
    }
    else
    {
      return this;
    }
  }
  /**
   * Returns a strict view of this schema.
   * <p>
   * See the description of {@link #isStrict()} for more details.
   *
   * @return A strict view of this schema.
   * @see Schema#isStrict()
   */
  public Schema asStrictSchema()
  {
    if (impl.isStrict())
    {
      return this;
    }
    else
    {
      return new Schema(((NonStrictImpl) impl).strictImpl);
    }
  }
  /**
   * Adds the definitions of all the schema elements contained in this schema to
   * the provided subschema subentry. Any existing attributes (including schema
   * definitions) contained in the provided entry will be preserved.
@@ -2522,10 +2730,10 @@
   * @throws NullPointerException
   *           If {@code entry} was {@code null}.
   */
  public Entry toEntry(Entry entry) throws NullPointerException
  public Entry toEntry(final Entry entry) throws NullPointerException
  {
    Attribute attr = new LinkedAttribute(Schema.ATTR_LDAP_SYNTAXES);
    for (Syntax syntax : getSyntaxes())
    for (final Syntax syntax : getSyntaxes())
    {
      attr.add(syntax.toString());
    }
@@ -2535,7 +2743,7 @@
    }
    attr = new LinkedAttribute(Schema.ATTR_ATTRIBUTE_TYPES);
    for (AttributeType attributeType : getAttributeTypes())
    for (final AttributeType attributeType : getAttributeTypes())
    {
      attr.add(attributeType.toString());
    }
@@ -2545,7 +2753,7 @@
    }
    attr = new LinkedAttribute(Schema.ATTR_OBJECT_CLASSES);
    for (ObjectClass objectClass : getObjectClasses())
    for (final ObjectClass objectClass : getObjectClasses())
    {
      attr.add(objectClass.toString());
    }
@@ -2555,7 +2763,7 @@
    }
    attr = new LinkedAttribute(Schema.ATTR_MATCHING_RULE_USE);
    for (MatchingRuleUse matchingRuleUse : getMatchingRuleUses())
    for (final MatchingRuleUse matchingRuleUse : getMatchingRuleUses())
    {
      attr.add(matchingRuleUse.toString());
    }
@@ -2565,7 +2773,7 @@
    }
    attr = new LinkedAttribute(Schema.ATTR_MATCHING_RULES);
    for (MatchingRule matchingRule : getMatchingRules())
    for (final MatchingRule matchingRule : getMatchingRules())
    {
      attr.add(matchingRule.toString());
    }
@@ -2575,7 +2783,7 @@
    }
    attr = new LinkedAttribute(Schema.ATTR_DIT_CONTENT_RULES);
    for (DITContentRule ditContentRule : getDITContentRules())
    for (final DITContentRule ditContentRule : getDITContentRules())
    {
      attr.add(ditContentRule.toString());
    }
@@ -2585,7 +2793,7 @@
    }
    attr = new LinkedAttribute(Schema.ATTR_DIT_STRUCTURE_RULES);
    for (DITStructureRule ditStructureRule : getDITStuctureRules())
    for (final DITStructureRule ditStructureRule : getDITStuctureRules())
    {
      attr.add(ditStructureRule.toString());
    }
@@ -2595,7 +2803,7 @@
    }
    attr = new LinkedAttribute(Schema.ATTR_NAME_FORMS);
    for (NameForm nameForm : getNameForms())
    for (final NameForm nameForm : getNameForms())
    {
      attr.add(nameForm.toString());
    }
@@ -2609,8 +2817,631 @@
  SchemaCompatOptions getSchemaCompatOptions()
  /**
   * Returns {@code true} if the provided entry is valid according to this
   * schema and the specified schema validation policy.
   * <p>
   * If attribute value validation is enabled then following checks will be
   * performed:
   * <ul>
   * <li>checking that there is at least one value
   * <li>checking that single-valued attributes contain only a single value
   * </ul>
   * In particular, attribute values will not be checked for conformance to
   * their syntax since this is expected to have already been performed while
   * adding the values to the entry.
   *
   * @param entry
   *          The entry to be validated.
   * @param policy
   *          The schema validation policy.
   * @param errorMessages
   *          A collection into which any schema validation warnings or error
   *          messages can be placed, or {@code null} if they should not be
   *          saved.
   * @return {@code true} if an entry conforms to this schema based on the
   *         provided schema validation policy.
   */
  public boolean validateEntry(final Entry entry,
      final SchemaValidationPolicy policy,
      final Collection<LocalizableMessage> errorMessages)
  {
    return impl.getSchemaCompatOptions();
    // First check that the object classes are recognized and that there is one
    // structural object class.
    ObjectClass structuralObjectClass = null;
    final Attribute objectClassAttribute = entry.getAttribute(objectClass());
    final List<ObjectClass> objectClasses = new LinkedList<ObjectClass>();
    if (objectClassAttribute != null)
    {
      for (final ByteString v : objectClassAttribute)
      {
        final String objectClassName = v.toString();
        final ObjectClass objectClass;
        try
        {
          objectClass = getObjectClass(objectClassName);
          objectClasses.add(objectClass);
        }
        catch (final UnknownSchemaElementException e)
        {
          if (policy.checkAttributesAndObjectClasses().needsChecking())
          {
            if (errorMessages != null)
            {
              final LocalizableMessage message = ERR_ENTRY_SCHEMA_UNKNOWN_OBJECT_CLASS
                  .get(entry.getName().toString(), objectClassName);
              errorMessages.add(message);
            }
            if (policy.checkAttributesAndObjectClasses().isReject())
            {
              return false;
            }
          }
          continue;
        }
        if (objectClass.getObjectClassType() == ObjectClassType.STRUCTURAL)
        {
          if (structuralObjectClass == null
              || objectClass.isDescendantOf(structuralObjectClass))
          {
            structuralObjectClass = objectClass;
          }
          else if (!structuralObjectClass.isDescendantOf(objectClass))
          {
            if (policy.requireSingleStructuralObjectClass().needsChecking())
            {
              if (errorMessages != null)
              {
                final LocalizableMessage message = ERR_ENTRY_SCHEMA_MULTIPLE_STRUCTURAL_CLASSES
                    .get(entry.getName().toString(),
                        structuralObjectClass.getNameOrOID(), objectClassName);
                errorMessages.add(message);
              }
              if (policy.requireSingleStructuralObjectClass().isReject())
              {
                return false;
              }
            }
          }
        }
      }
    }
    Collection<DITStructureRule> ditStructureRules = Collections.emptyList();
    DITContentRule ditContentRule = null;
    if (structuralObjectClass == null)
    {
      if (policy.requireSingleStructuralObjectClass().needsChecking())
      {
        if (errorMessages != null)
        {
          final LocalizableMessage message = ERR_ENTRY_SCHEMA_NO_STRUCTURAL_CLASS
              .get(entry.getName().toString());
          errorMessages.add(message);
        }
        if (policy.requireSingleStructuralObjectClass().isReject())
        {
          return false;
        }
      }
    }
    else
    {
      ditContentRule = getDITContentRule(structuralObjectClass);
      if (ditContentRule != null && ditContentRule.isObsolete())
      {
        ditContentRule = null;
      }
    }
    // Check entry conforms to object classes and optional content rule.
    if (!checkAttributesAndObjectClasses(entry, policy, errorMessages,
        objectClasses, ditContentRule))
    {
      return false;
    }
    // Check that the name of the entry conforms to at least one applicable name
    // form.
    if (policy.checkNameForms().needsChecking()
        && structuralObjectClass != null)
    {
      /**
       * There may be multiple name forms registered with this structural object
       * class. However, we need to select only one of the name forms and its
       * corresponding DIT structure rule(s). We will iterate over all the name
       * forms and see if at least one is acceptable before rejecting the entry.
       * DIT structure rules corresponding to other non-acceptable name forms
       * are not applied.
       */
      boolean foundMatchingNameForms = false;
      NameForm nameForm = null;
      final List<LocalizableMessage> nameFormWarnings =
        (errorMessages != null) ? new LinkedList<LocalizableMessage>() : null;
      for (final NameForm nf : getNameForms(structuralObjectClass))
      {
        if (nf.isObsolete())
        {
          continue;
        }
        // If there are any candidate name forms then at least one should be
        // valid.
        foundMatchingNameForms = true;
        if (checkNameForm(entry, policy, nameFormWarnings, nf))
        {
          nameForm = nf;
          break;
        }
      }
      if (foundMatchingNameForms)
      {
        if (nameForm != null)
        {
          ditStructureRules = getDITStructureRules(nameForm);
        }
        else
        {
          // We couldn't match this entry against any of the name forms, so
          // append the reasons why they didn't match and reject if required.
          if (errorMessages != null)
          {
            errorMessages.addAll(nameFormWarnings);
          }
          if (policy.checkNameForms().isReject())
          {
            return false;
          }
        }
      }
    }
    // Check DIT structure rules - this needs the parent entry.
    if (policy.checkDITStructureRules().needsChecking()
        && !entry.getName().isRootDN())
    {
      boolean foundMatchingRules = false;
      boolean foundValidRule = false;
      final List<LocalizableMessage> ruleWarnings =
        (errorMessages != null) ? new LinkedList<LocalizableMessage>() : null;
      ObjectClass parentStructuralObjectClass = null;
      boolean parentEntryHasBeenRead = false;
      for (final DITStructureRule rule : ditStructureRules)
      {
        if (rule.isObsolete())
        {
          continue;
        }
        foundMatchingRules = true;
        // A DIT structure rule with no superiors is automatically valid, so
        // avoid reading the parent.
        if (rule.getSuperiorRules().isEmpty())
        {
          foundValidRule = true;
          break;
        }
        if (!parentEntryHasBeenRead)
        {
          // Don't drop out immediately on failure because there may be some
          // applicable rules which do not require the parent entry.
          parentStructuralObjectClass = getParentStructuralObjectClass(entry,
              policy, ruleWarnings);
          parentEntryHasBeenRead = true;
        }
        if (parentStructuralObjectClass != null)
        {
          if (checkDITStructureRule(entry, policy, ruleWarnings, rule,
              structuralObjectClass, parentStructuralObjectClass))
          {
            foundValidRule = true;
            break;
          }
        }
      }
      if (foundMatchingRules)
      {
        if (!foundValidRule)
        {
          // We couldn't match this entry against any of the rules, so
          // append the reasons why they didn't match and reject if required.
          if (errorMessages != null)
          {
            errorMessages.addAll(ruleWarnings);
          }
          if (policy.checkDITStructureRules().isReject())
          {
            return false;
          }
        }
      }
      else
      {
        // There is no DIT structure rule for this entry, but there may
        // be one for the parent entry. If there is such a rule for the
        // parent entry, then this entry will not be valid.
        // The parent won't have been read yet.
        parentStructuralObjectClass = getParentStructuralObjectClass(entry,
            policy, ruleWarnings);
        if (parentStructuralObjectClass == null)
        {
          if (errorMessages != null)
          {
            errorMessages.addAll(ruleWarnings);
          }
          if (policy.checkDITStructureRules().isReject())
          {
            return false;
          }
        }
        else
        {
          for (final NameForm nf : getNameForms(parentStructuralObjectClass))
          {
            if (!nf.isObsolete())
            {
              for (final DITStructureRule rule : getDITStructureRules(nf))
              {
                if (!rule.isObsolete())
                {
                  if (errorMessages != null)
                  {
                    final LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_MISSING_DSR
                        .get(entry.getName().toString(), rule.getNameOrRuleID());
                    errorMessages.add(message);
                  }
                  if (policy.checkDITStructureRules().isReject())
                  {
                    return false;
                  }
                  // We could break out of the loop here in warn mode but
                  // continuing allows us to collect all conflicts.
                }
              }
            }
          }
        }
      }
    }
    // If we've gotten here, then the entry is acceptable.
    return true;
  }
  private boolean checkAttributesAndObjectClasses(final Entry entry,
      final SchemaValidationPolicy policy,
      final Collection<LocalizableMessage> errorMessages,
      final List<ObjectClass> objectClasses, final DITContentRule ditContentRule)
  {
    // Check object classes.
    final boolean checkDITContentRule = policy.checkDITContentRules()
        .needsChecking() && ditContentRule != null;
    final boolean checkObjectClasses = policy.checkAttributesAndObjectClasses()
        .needsChecking();
    final boolean checkAttributeValues = policy.checkAttributeValues()
        .needsChecking();
    if (checkObjectClasses || checkDITContentRule)
    {
      for (final ObjectClass objectClass : objectClasses)
      {
        // Make sure that any auxiliary object classes are permitted by the
        // content rule.
        if (checkDITContentRule)
        {
          if (objectClass.getObjectClassType() == ObjectClassType.AUXILIARY)
          {
            if (errorMessages != null)
            {
              final LocalizableMessage message = ERR_ENTRY_SCHEMA_DCR_PROHIBITED_AUXILIARY_OC
                  .get(entry.getName().toString(), objectClass.getNameOrOID(),
                      ditContentRule.getNameOrOID());
              errorMessages.add(message);
            }
            if (policy.checkDITContentRules().isReject())
            {
              return false;
            }
          }
        }
        // Make sure that all of the attributes required by the object class are
        // present.
        if (checkObjectClasses)
        {
          for (final AttributeType t : objectClass.getRequiredAttributes())
          {
            final Attribute a = Attributes.emptyAttribute(AttributeDescription
                .create(t));
            if (entry.containsAttribute(a, null))
            {
              if (errorMessages != null)
              {
                final LocalizableMessage message = ERR_ENTRY_SCHEMA_OC_MISSING_MUST_ATTRIBUTES
                    .get(entry.getName().toString(), t.getNameOrOID(),
                        objectClass.getNameOrOID());
                errorMessages.add(message);
              }
              if (policy.checkAttributesAndObjectClasses().isReject())
              {
                return false;
              }
            }
          }
        }
      }
      // Make sure that all of the attributes required by the content rule are
      // present.
      if (checkDITContentRule)
      {
        for (final AttributeType t : ditContentRule.getRequiredAttributes())
        {
          final Attribute a = Attributes.emptyAttribute(AttributeDescription
              .create(t));
          if (entry.containsAttribute(a, null))
          {
            if (errorMessages != null)
            {
              final LocalizableMessage message = ERR_ENTRY_SCHEMA_DCR_MISSING_MUST_ATTRIBUTES
                  .get(entry.getName().toString(), t.getNameOrOID(),
                      ditContentRule.getNameOrOID());
              errorMessages.add(message);
            }
            if (policy.checkDITContentRules().isReject())
            {
              return false;
            }
          }
        }
        // Make sure that attributes prohibited by the content rule are not
        // present.
        for (final AttributeType t : ditContentRule.getProhibitedAttributes())
        {
          final Attribute a = Attributes.emptyAttribute(AttributeDescription
              .create(t));
          if (entry.containsAttribute(a, null))
          {
            if (errorMessages != null)
            {
              final LocalizableMessage message = ERR_ENTRY_SCHEMA_DCR_PROHIBITED_ATTRIBUTES
                  .get(entry.getName().toString(), t.getNameOrOID(),
                      ditContentRule.getNameOrOID());
              errorMessages.add(message);
            }
            if (policy.checkDITContentRules().isReject())
            {
              return false;
            }
          }
        }
      }
    }
    // Check attributes.
    if (checkObjectClasses || checkDITContentRule || checkAttributeValues)
    {
      for (final Attribute attribute : entry.getAllAttributes())
      {
        final AttributeType t = attribute.getAttributeDescription()
            .getAttributeType();
        if (!t.isOperational())
        {
          if (checkObjectClasses || checkDITContentRule)
          {
            boolean isAllowed = false;
            for (final ObjectClass objectClass : objectClasses)
            {
              if (objectClass.isRequiredOrOptional(t))
              {
                isAllowed = true;
                break;
              }
            }
            if (!isAllowed && ditContentRule != null)
            {
              if (ditContentRule.isRequiredOrOptional(t))
              {
                isAllowed = true;
              }
            }
            if (!isAllowed)
            {
              if (errorMessages != null)
              {
                final LocalizableMessage message;
                if (ditContentRule == null)
                {
                  message = ERR_ENTRY_SCHEMA_OC_DISALLOWED_ATTRIBUTES.get(entry
                      .getName().toString(), t.getNameOrOID());
                }
                else
                {
                  message = ERR_ENTRY_SCHEMA_DCR_DISALLOWED_ATTRIBUTES.get(
                      entry.getName().toString(), t.getNameOrOID(),
                      ditContentRule.getNameOrOID());
                }
                errorMessages.add(message);
              }
              if (policy.checkAttributesAndObjectClasses().isReject()
                  || policy.checkDITContentRules().isReject())
              {
                return false;
              }
            }
          }
          // Check attributes contain an appropriate number of values.
          if (checkAttributeValues)
          {
            final int sz = attribute.size();
            if (sz == 0)
            {
              if (errorMessages != null)
              {
                final LocalizableMessage message = ERR_ENTRY_SCHEMA_AT_EMPTY_ATTRIBUTE
                    .get(entry.getName().toString(), t.getNameOrOID());
                errorMessages.add(message);
              }
              if (policy.checkAttributeValues().isReject())
              {
                return false;
              }
            }
            else if (sz > 1 && t.isSingleValue())
            {
              if (errorMessages != null)
              {
                final LocalizableMessage message = ERR_ENTRY_SCHEMA_AT_SINGLE_VALUED_ATTRIBUTE
                    .get(entry.getName().toString(), t.getNameOrOID());
                errorMessages.add(message);
              }
              if (policy.checkAttributeValues().isReject())
              {
                return false;
              }
            }
          }
        }
      }
    }
    // If we've gotten here, then things are OK.
    return true;
  }
  private boolean checkDITStructureRule(final Entry entry,
      final SchemaValidationPolicy policy,
      final List<LocalizableMessage> ruleWarnings, final DITStructureRule rule,
      final ObjectClass structuralObjectClass,
      final ObjectClass parentStructuralObjectClass)
  {
    boolean matchFound = false;
    for (final DITStructureRule parentRule : rule.getSuperiorRules())
    {
      if (parentRule.getNameForm().getStructuralClass()
          .equals(parentStructuralObjectClass))
      {
        matchFound = true;
      }
    }
    if (!matchFound)
    {
      if (ruleWarnings != null)
      {
        final LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_ILLEGAL_OC.get(
            entry.getName().toString(), rule.getNameOrRuleID(),
            structuralObjectClass.getNameOrOID(),
            parentStructuralObjectClass.getNameOrOID());
        ruleWarnings.add(message);
      }
      return false;
    }
    return true;
  }
  private boolean checkNameForm(final Entry entry,
      final SchemaValidationPolicy policy,
      final List<LocalizableMessage> nameFormWarnings, final NameForm nameForm)
  {
    final RDN rdn = entry.getName().rdn();
    if (rdn != null)
    {
      // Make sure that all the required AVAs are present.
      for (final AttributeType t : nameForm.getRequiredAttributes())
      {
        if (rdn.getAttributeValue(t) == null)
        {
          if (nameFormWarnings != null)
          {
            final LocalizableMessage message = ERR_ENTRY_SCHEMA_NF_MISSING_MUST_ATTRIBUTES
                .get(entry.getName().toString(), t.getNameOrOID(),
                    nameForm.getNameOrOID());
            nameFormWarnings.add(message);
          }
          return false;
        }
      }
      // Make sure that all AVAs in the RDN are allowed.
      for (final AVA ava : rdn)
      {
        final AttributeType t = ava.getAttributeType();
        if (nameForm.isRequiredOrOptional(t))
        {
          if (nameFormWarnings != null)
          {
            final LocalizableMessage message = ERR_ENTRY_SCHEMA_NF_DISALLOWED_ATTRIBUTES
                .get(entry.getName().toString(), t.getNameOrOID(),
                    nameForm.getNameOrOID());
            nameFormWarnings.add(message);
          }
          return false;
        }
      }
    }
    // If we've gotten here, then things are OK.
    return true;
  }
  private ObjectClass getParentStructuralObjectClass(final Entry entry,
      final SchemaValidationPolicy policy,
      final List<LocalizableMessage> ruleWarnings)
  {
    final Entry parentEntry;
    try
    {
      parentEntry = policy.checkDITStructureRulesEntryResolver().getEntry(
          entry.getName().parent());
    }
    catch (final ErrorResultException e)
    {
      if (ruleWarnings != null)
      {
        final LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_PARENT_NOT_FOUND
            .get(entry.getName().toString(), e.getResult()
                .getDiagnosticMessage());
        ruleWarnings.add(message);
      }
      return null;
    }
    final ObjectClass parentStructuralObjectClass = Entries
        .getStructuralObjectClass(parentEntry, this);
    if (parentStructuralObjectClass == null)
    {
      if (ruleWarnings != null)
      {
        final LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC
            .get(entry.getName().toString());
        ruleWarnings.add(message);
      }
      return null;
    }
    return parentStructuralObjectClass;
  }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
@@ -30,11 +30,17 @@
import static com.forgerock.opendj.util.StaticUtils.toLowerCase;
import static org.forgerock.opendj.ldap.CoreMessages.*;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.schema.Schema.*;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.*;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.*;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.OMR_GENERIC_ENUM_NAME;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.SCHEMA_PROPERTY_APPROX_RULE;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.unmodifiableCopyOfExtraProperties;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.unmodifiableCopyOfList;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.unmodifiableCopyOfSet;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -67,18 +73,7 @@
  private static final Filter SUBSCHEMA_FILTER = Filter
      .valueOf("(objectClass=subschema)");
  private static final String[] SUBSCHEMA_SUBENTRY_ATTRS =
    new String[] { ATTR_SUBSCHEMA_SUBENTRY };
  // Constructs a search request for retrieving the named subschema
  // sub-entry.
  private static SearchRequest getReadSchemaSearchRequest(final DN dn)
  {
    return Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT,
        SUBSCHEMA_FILTER, SUBSCHEMA_ATTRS);
  }
  private static final String[] SUBSCHEMA_SUBENTRY_ATTRS = new String[] { ATTR_SUBSCHEMA_SUBENTRY };
@@ -92,6 +87,16 @@
  // Constructs a search request for retrieving the named subschema
  // sub-entry.
  private static SearchRequest getReadSchemaSearchRequest(final DN dn)
  {
    return Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT,
        SUBSCHEMA_FILTER, SUBSCHEMA_ATTRS);
  }
  private static DN getSubschemaSubentryDN(final DN name, final Entry entry)
      throws ErrorResultException
  {
@@ -156,11 +161,15 @@
  private Map<String, List<NameForm>> objectClass2NameForms;
  private SchemaCompatOptions options;
  private String schemaName;
  private List<LocalizableMessage> warnings;
  private Schema schema;
  private boolean allowNonStandardTelephoneNumbers;
  private boolean allowZeroLengthDirectoryStrings;
  private boolean allowMalformedNamesAndOptions;
  // A unique ID which can be used to uniquely identify schemas
  // constructed without a name.
@@ -211,7 +220,6 @@
  public SchemaBuilder(final Schema schema) throws NullPointerException
  {
    initBuilder(schema.getSchemaName());
    setSchemaCompatOptions(schema.getSchemaCompatOptions());
    addSchema(schema, true);
  }
@@ -287,7 +295,7 @@
      // The next set of characters must be the OID.
      final String oid = SchemaUtils.readOID(reader,
          options.allowMalformedNamesAndOptions());
          allowMalformedNamesAndOptions);
      List<String> names = Collections.emptyList();
      String description = "".intern();
@@ -324,7 +332,7 @@
        else if (tokenName.equalsIgnoreCase("name"))
        {
          names = SchemaUtils.readNameDescriptors(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("desc"))
        {
@@ -346,28 +354,28 @@
          // type from which this attribute type should inherit its
          // properties.
          superiorType = SchemaUtils.readOID(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("equality"))
        {
          // This specifies the name or OID of the equality matching
          // rule to use for this attribute type.
          equalityMatchingRule = SchemaUtils.readOID(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("ordering"))
        {
          // This specifies the name or OID of the ordering matching
          // rule to use for this attribute type.
          orderingMatchingRule = SchemaUtils.readOID(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("substr"))
        {
          // This specifies the name or OID of the substring matching
          // rule to use for this attribute type.
          substringMatchingRule = SchemaUtils.readOID(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("syntax"))
        {
@@ -379,8 +387,8 @@
          // implementation will ignore any such length because it does
          // not impose any practical limit on the length of attribute
          // values.
          syntax = SchemaUtils.readOIDLen(reader,
              options.allowMalformedNamesAndOptions());
          syntax = SchemaUtils
              .readOIDLen(reader, allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("single-definition"))
        {
@@ -494,8 +502,8 @@
    }
    catch (final DecodeException e)
    {
      LocalizableMessage msg = ERR_ATTR_SYNTAX_ATTRTYPE_INVALID1.get(definition,
          e.getMessageObject());
      final LocalizableMessage msg = ERR_ATTR_SYNTAX_ATTRTYPE_INVALID1.get(
          definition, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(msg, e.getCause());
    }
    return this;
@@ -533,10 +541,10 @@
   *          be used or, if none is defined, the default matching rule
   *          associated with the syntax.
   * @param approximateMatchingRule
   *          The OID of the approximate matching rule, which may be {@code
   *          null} indicating that the superior attribute type's matching rule
   *          should be used or, if none is defined, the default matching rule
   *          associated with the syntax.
   *          The OID of the approximate matching rule, which may be
   *          {@code null} indicating that the superior attribute type's
   *          matching rule should be used or, if none is defined, the default
   *          matching rule associated with the syntax.
   * @param syntax
   *          The OID of the syntax definition.
   * @param singleValue
@@ -638,7 +646,7 @@
      // The next set of characters must be the OID.
      final String structuralClass = SchemaUtils.readOID(reader,
          options.allowMalformedNamesAndOptions());
          allowMalformedNamesAndOptions);
      List<String> names = Collections.emptyList();
      String description = "".intern();
@@ -669,7 +677,7 @@
        else if (tokenName.equalsIgnoreCase("name"))
        {
          names = SchemaUtils.readNameDescriptors(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("desc"))
        {
@@ -688,22 +696,22 @@
        else if (tokenName.equalsIgnoreCase("aux"))
        {
          auxiliaryClasses = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("must"))
        {
          requiredAttributes = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("may"))
        {
          optionalAttributes = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("not"))
        {
          prohibitedAttributes = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
        {
@@ -738,8 +746,8 @@
    }
    catch (final DecodeException e)
    {
      LocalizableMessage msg = ERR_ATTR_SYNTAX_DCR_INVALID1.get(definition,
          e.getMessageObject());
      final LocalizableMessage msg = ERR_ATTR_SYNTAX_DCR_INVALID1.get(
          definition, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(msg, e.getCause());
    }
    return this;
@@ -933,7 +941,7 @@
        else if (tokenName.equalsIgnoreCase("name"))
        {
          names = SchemaUtils.readNameDescriptors(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("desc"))
        {
@@ -951,8 +959,7 @@
        }
        else if (tokenName.equalsIgnoreCase("form"))
        {
          nameForm = SchemaUtils.readOID(reader,
              options.allowMalformedNamesAndOptions());
          nameForm = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("sup"))
        {
@@ -998,8 +1005,8 @@
    }
    catch (final DecodeException e)
    {
      LocalizableMessage msg = ERR_ATTR_SYNTAX_DSR_INVALID1.get(definition,
          e.getMessageObject());
      final LocalizableMessage msg = ERR_ATTR_SYNTAX_DSR_INVALID1.get(
          definition, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(msg, e.getCause());
    }
    return this;
@@ -1031,13 +1038,14 @@
  {
    Validator.ensureNotNull((Object) enumerations);
    final EnumSyntaxImpl enumImpl = new EnumSyntaxImpl(oid, Arrays
        .asList(enumerations));
    final Syntax enumSyntax = new Syntax(oid, description, Collections
        .singletonMap("X-ENUM", Arrays.asList(enumerations)), null, enumImpl);
    final MatchingRule enumOMR = new MatchingRule(enumImpl
        .getOrderingMatchingRule(), Collections
        .singletonList(OMR_GENERIC_ENUM_NAME + oid), "", false, oid,
    final EnumSyntaxImpl enumImpl = new EnumSyntaxImpl(oid,
        Arrays.asList(enumerations));
    final Syntax enumSyntax = new Syntax(oid, description,
        Collections.singletonMap("X-ENUM", Arrays.asList(enumerations)), null,
        enumImpl);
    final MatchingRule enumOMR = new MatchingRule(
        enumImpl.getOrderingMatchingRule(),
        Collections.singletonList(OMR_GENERIC_ENUM_NAME + oid), "", false, oid,
        CoreSchemaImpl.OPENDS_ORIGIN, null, new EnumOrderingMatchingRule(
            enumImpl));
@@ -1110,7 +1118,7 @@
      // The next set of characters must be the OID.
      final String oid = SchemaUtils.readOID(reader,
          options.allowMalformedNamesAndOptions());
          allowMalformedNamesAndOptions);
      List<String> names = Collections.emptyList();
      String description = "".intern();
@@ -1138,7 +1146,7 @@
        else if (tokenName.equalsIgnoreCase("name"))
        {
          names = SchemaUtils.readNameDescriptors(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("desc"))
        {
@@ -1156,8 +1164,7 @@
        }
        else if (tokenName.equalsIgnoreCase("syntax"))
        {
          syntax = SchemaUtils.readOID(reader,
              options.allowMalformedNamesAndOptions());
          syntax = SchemaUtils.readOID(reader, allowMalformedNamesAndOptions);
        }
        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
        {
@@ -1198,8 +1205,8 @@
    }
    catch (final DecodeException e)
    {
      LocalizableMessage msg = ERR_ATTR_SYNTAX_MR_INVALID1.get(definition,
          e.getMessageObject());
      final LocalizableMessage msg = ERR_ATTR_SYNTAX_MR_INVALID1.get(
          definition, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(msg, e.getCause());
    }
    return this;
@@ -1307,7 +1314,7 @@
      // The next set of characters must be the OID.
      final String oid = SchemaUtils.readOID(reader,
          options.allowMalformedNamesAndOptions());
          allowMalformedNamesAndOptions);
      List<String> names = Collections.emptyList();
      String description = "".intern();
@@ -1335,7 +1342,7 @@
        else if (tokenName.equalsIgnoreCase("name"))
        {
          names = SchemaUtils.readNameDescriptors(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("desc"))
        {
@@ -1354,7 +1361,7 @@
        else if (tokenName.equalsIgnoreCase("applies"))
        {
          attributes = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
        {
@@ -1396,8 +1403,8 @@
    }
    catch (final DecodeException e)
    {
      LocalizableMessage msg = ERR_ATTR_SYNTAX_MRUSE_INVALID1.get(definition,
          e.getMessageObject());
      final LocalizableMessage msg = ERR_ATTR_SYNTAX_MRUSE_INVALID1.get(
          definition, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(msg, e.getCause());
    }
    return this;
@@ -1501,7 +1508,7 @@
      // The next set of characters must be the OID.
      final String oid = SchemaUtils.readOID(reader,
          options.allowMalformedNamesAndOptions());
          allowMalformedNamesAndOptions);
      List<String> names = Collections.emptyList();
      String description = "".intern();
@@ -1531,7 +1538,7 @@
        else if (tokenName.equalsIgnoreCase("name"))
        {
          names = SchemaUtils.readNameDescriptors(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("desc"))
        {
@@ -1550,17 +1557,17 @@
        else if (tokenName.equalsIgnoreCase("oc"))
        {
          structuralClass = SchemaUtils.readOID(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("must"))
        {
          requiredAttributes = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("may"))
        {
          optionalAttributes = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
        {
@@ -1611,8 +1618,8 @@
    }
    catch (final DecodeException e)
    {
      LocalizableMessage msg = ERR_ATTR_SYNTAX_NAME_FORM_INVALID1.get(definition,
          e.getMessageObject());
      final LocalizableMessage msg = ERR_ATTR_SYNTAX_NAME_FORM_INVALID1.get(
          definition, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(msg, e.getCause());
    }
    return this;
@@ -1724,7 +1731,7 @@
      // The next set of characters must be the OID.
      final String oid = SchemaUtils.readOID(reader,
          options.allowMalformedNamesAndOptions());
          allowMalformedNamesAndOptions);
      List<String> names = Collections.emptyList();
      String description = "".intern();
@@ -1755,7 +1762,7 @@
        else if (tokenName.equalsIgnoreCase("name"))
        {
          names = SchemaUtils.readNameDescriptors(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("desc"))
        {
@@ -1774,7 +1781,7 @@
        else if (tokenName.equalsIgnoreCase("sup"))
        {
          superiorClasses = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("abstract"))
        {
@@ -1799,12 +1806,12 @@
        else if (tokenName.equalsIgnoreCase("must"))
        {
          requiredAttributes = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.equalsIgnoreCase("may"))
        {
          optionalAttributes = SchemaUtils.readOIDs(reader,
              options.allowMalformedNamesAndOptions());
              allowMalformedNamesAndOptions);
        }
        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
        {
@@ -1851,8 +1858,8 @@
    }
    catch (final DecodeException e)
    {
      LocalizableMessage msg = ERR_ATTR_SYNTAX_OBJECTCLASS_INVALID1.get(definition,
          e.getMessageObject());
      final LocalizableMessage msg = ERR_ATTR_SYNTAX_OBJECTCLASS_INVALID1.get(
          definition, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(msg, e.getCause());
    }
    return this;
@@ -1958,6 +1965,102 @@
  /**
   * Reads the schema elements contained in the named subschema sub-entry and
   * adds them to this schema builder.
   * <p>
   * If the requested schema is not returned by the Directory Server then the
   * request will fail with an {@link EntryNotFoundException}.
   *
   * @param connection
   *          A connection to the Directory Server whose schema is to be read.
   * @param name
   *          The distinguished name of the subschema sub-entry.
   * @param handler
   *          A result handler which can be used to asynchronously process the
   *          operation result when it is received, may be {@code null}.
   * @param overwrite
   *          {@code true} if existing schema elements with the same conflicting
   *          OIDs should be overwritten.
   * @return A future representing the updated schema builder.
   * @throws UnsupportedOperationException
   *           If the connection does not support search operations.
   * @throws IllegalStateException
   *           If the connection has already been closed, i.e. if
   *           {@code connection.isClosed() == true}.
   * @throws NullPointerException
   *           If the {@code connection} or {@code name} was {@code null}.
   */
  public FutureResult<SchemaBuilder> addSchema(
      final AsynchronousConnection connection, final DN name,
      final ResultHandler<? super SchemaBuilder> handler,
      final boolean overwrite) throws UnsupportedOperationException,
      IllegalStateException, NullPointerException
  {
    final SearchRequest request = getReadSchemaSearchRequest(name);
    final FutureResultTransformer<SearchResultEntry, SchemaBuilder> future =
      new FutureResultTransformer<SearchResultEntry, SchemaBuilder>(handler)
    {
      @Override
      protected SchemaBuilder transformResult(final SearchResultEntry result)
          throws ErrorResultException
      {
        addSchema(result, overwrite);
        return SchemaBuilder.this;
      }
    };
    final FutureResult<SearchResultEntry> innerFuture = connection
        .searchSingleEntry(request, future);
    future.setFutureResult(innerFuture);
    return future;
  }
  /**
   * Reads the schema elements contained in the named subschema sub-entry and
   * adds them to this schema builder.
   * <p>
   * If the requested schema is not returned by the Directory Server then the
   * request will fail with an {@link EntryNotFoundException}.
   *
   * @param connection
   *          A connection to the Directory Server whose schema is to be read.
   * @param name
   *          The distinguished name of the subschema sub-entry.
   * @param overwrite
   *          {@code true} if existing schema elements with the same conflicting
   *          OIDs should be overwritten.
   * @return A reference to this schema builder.
   * @throws ErrorResultException
   *           If the result code indicates that the request failed for some
   *           reason.
   * @throws InterruptedException
   *           If the current thread was interrupted while waiting.
   * @throws UnsupportedOperationException
   *           If the connection does not support search operations.
   * @throws IllegalStateException
   *           If the connection has already been closed, i.e. if
   *           {@code isClosed() == true}.
   * @throws NullPointerException
   *           If the {@code connection} or {@code name} was {@code null}.
   */
  public SchemaBuilder addSchema(final Connection connection, final DN name,
      final boolean overwrite) throws ErrorResultException,
      InterruptedException, UnsupportedOperationException,
      IllegalStateException, NullPointerException
  {
    final SearchRequest request = getReadSchemaSearchRequest(name);
    final Entry entry = connection.searchSingleEntry(request);
    return addSchema(entry, overwrite);
  }
  /**
   * Adds all of the schema elements contained in the provided subschema
   * subentry to this schema builder. Any problems encountered while parsing the
   * entry can be retrieved using the returned schema's
@@ -2176,102 +2279,6 @@
  /**
   * Reads the schema elements contained in the named subschema sub-entry and
   * adds them to this schema builder.
   * <p>
   * If the requested schema is not returned by the Directory Server then the
   * request will fail with an {@link EntryNotFoundException}.
   *
   * @param connection
   *          A connection to the Directory Server whose schema is to be read.
   * @param name
   *          The distinguished name of the subschema sub-entry.
   * @param handler
   *          A result handler which can be used to asynchronously process the
   *          operation result when it is received, may be {@code null}.
   * @param overwrite
   *          {@code true} if existing schema elements with the same conflicting
   *          OIDs should be overwritten.
   * @return A future representing the updated schema builder.
   * @throws UnsupportedOperationException
   *           If the connection does not support search operations.
   * @throws IllegalStateException
   *           If the connection has already been closed, i.e. if
   *           {@code connection.isClosed() == true}.
   * @throws NullPointerException
   *           If the {@code connection} or {@code name} was {@code null}.
   */
  public FutureResult<SchemaBuilder> addSchema(
      final AsynchronousConnection connection, final DN name,
      final ResultHandler<? super SchemaBuilder> handler,
      final boolean overwrite) throws UnsupportedOperationException,
      IllegalStateException, NullPointerException
  {
    final SearchRequest request = getReadSchemaSearchRequest(name);
    final FutureResultTransformer<SearchResultEntry, SchemaBuilder> future =
      new FutureResultTransformer<SearchResultEntry, SchemaBuilder>(handler)
    {
      @Override
      protected SchemaBuilder transformResult(final SearchResultEntry result)
          throws ErrorResultException
      {
        addSchema(result, overwrite);
        return SchemaBuilder.this;
      }
    };
    final FutureResult<SearchResultEntry> innerFuture = connection
        .searchSingleEntry(request, future);
    future.setFutureResult(innerFuture);
    return future;
  }
  /**
   * Reads the schema elements contained in the named subschema sub-entry and
   * adds them to this schema builder.
   * <p>
   * If the requested schema is not returned by the Directory Server then the
   * request will fail with an {@link EntryNotFoundException}.
   *
   * @param connection
   *          A connection to the Directory Server whose schema is to be read.
   * @param name
   *          The distinguished name of the subschema sub-entry.
   * @param overwrite
   *          {@code true} if existing schema elements with the same conflicting
   *          OIDs should be overwritten.
   * @return A reference to this schema builder.
   * @throws ErrorResultException
   *           If the result code indicates that the request failed for some
   *           reason.
   * @throws InterruptedException
   *           If the current thread was interrupted while waiting.
   * @throws UnsupportedOperationException
   *           If the connection does not support search operations.
   * @throws IllegalStateException
   *           If the connection has already been closed, i.e. if
   *           {@code isClosed() == true}.
   * @throws NullPointerException
   *           If the {@code connection} or {@code name} was {@code null}.
   */
  public SchemaBuilder addSchema(final Connection connection, final DN name,
      final boolean overwrite) throws ErrorResultException,
      InterruptedException, UnsupportedOperationException,
      IllegalStateException, NullPointerException
  {
    final SearchRequest request = getReadSchemaSearchRequest(name);
    final Entry entry = connection.searchSingleEntry(request);
    return addSchema(entry, overwrite);
  }
  /**
   * Reads the schema elements contained in the subschema sub-entry which
   * applies to the named entry and adds them to this schema builder.
   * <p>
@@ -2403,8 +2410,10 @@
  {
    Validator.ensureNotNull(substituteSyntax);
    addSyntax(new Syntax(oid, description, Collections.singletonMap("X-SUBST",
        Collections.singletonList(substituteSyntax)), null, null), overwrite);
    addSyntax(
        new Syntax(oid, description, Collections.singletonMap("X-SUBST",
            Collections.singletonList(substituteSyntax)), null, null),
        overwrite);
    return this;
  }
@@ -2465,7 +2474,7 @@
      // The next set of characters must be the OID.
      final String oid = SchemaUtils.readOID(reader,
          options.allowMalformedNamesAndOptions());
          allowMalformedNamesAndOptions);
      String description = "".intern();
      Map<String, List<String>> extraProperties = Collections.emptyMap();
@@ -2525,15 +2534,15 @@
      {
        if (property.getKey().equalsIgnoreCase("x-enum"))
        {
          final EnumSyntaxImpl enumImpl = new EnumSyntaxImpl(oid, property
              .getValue());
          final EnumSyntaxImpl enumImpl = new EnumSyntaxImpl(oid,
              property.getValue());
          final Syntax enumSyntax = new Syntax(oid, description,
              extraProperties, definition, enumImpl);
          final MatchingRule enumOMR = new MatchingRule(enumImpl
              .getOrderingMatchingRule(), Collections
              .singletonList(OMR_GENERIC_ENUM_NAME + oid), "", false, oid,
              CoreSchemaImpl.OPENDS_ORIGIN, null, new EnumOrderingMatchingRule(
                  enumImpl));
          final MatchingRule enumOMR = new MatchingRule(
              enumImpl.getOrderingMatchingRule(),
              Collections.singletonList(OMR_GENERIC_ENUM_NAME + oid), "",
              false, oid, CoreSchemaImpl.OPENDS_ORIGIN, null,
              new EnumOrderingMatchingRule(enumImpl));
          addSyntax(enumSyntax, overwrite);
          addMatchingRule(enumOMR, overwrite);
@@ -2547,8 +2556,8 @@
    }
    catch (final DecodeException e)
    {
      LocalizableMessage msg = ERR_ATTR_SYNTAX_ATTRSYNTAX_INVALID1.get(definition,
          e.getMessageObject());
      final LocalizableMessage msg = ERR_ATTR_SYNTAX_ATTRSYNTAX_INVALID1.get(
          definition, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(msg, e.getCause());
    }
    return this;
@@ -2592,6 +2601,80 @@
  /**
   * Specifies whether or not the schema should allow certain illegal characters
   * in OIDs and attribute options. When this compatibility option is set to
   * {@code true} the following illegal characters will be permitted in addition
   * to those permitted in section 1.4 of RFC 4512:
   *
   * <pre>
   * USCORE  = %x5F ; underscore ("_")
   * DOT     = %x2E ; period (".")
   * </pre>
   *
   * By default this compatibility option is set to {@code true} because these
   * characters are often used for naming purposes (such as collation rules).
   *
   * @param allowMalformedNamesAndOptions
   *          {@code true} if the schema should allow certain illegal characters
   *          in OIDs and attribute options.
   * @return A reference to this {@code SchemaBuilder}.
   * @see <a href="http://tools.ietf.org/html/rfc4512">RFC 4512 - Lightweight
   *      Directory Access Protocol (LDAP): Directory Information Models </a>
   */
  public SchemaBuilder allowMalformedNamesAndOptions(
      final boolean allowMalformedNamesAndOptions)
  {
    this.allowMalformedNamesAndOptions = allowMalformedNamesAndOptions;
    return this;
  }
  /**
   * Specifies whether or not the Telephone Number syntax should allow values
   * which do not conform to the E.123 international telephone number format.
   * <p>
   * By default this compatibility option is set to {@code true}.
   *
   * @param allowNonStandardTelephoneNumbers
   *          {@code true} if the Telephone Number syntax should allow values
   *          which do not conform to the E.123 international telephone number
   *          format.
   * @return A reference to this {@code SchemaBuilder}.
   */
  public SchemaBuilder allowNonStandardTelephoneNumbers(
      final boolean allowNonStandardTelephoneNumbers)
  {
    this.allowNonStandardTelephoneNumbers = allowNonStandardTelephoneNumbers;
    return this;
  }
  /**
   * Specifies whether or not zero-length values will be allowed by the
   * Directory String syntax. This is technically forbidden by the LDAP
   * specification, but it was allowed in earlier versions of the server, and
   * the discussion of the directory string syntax in RFC 2252 does not
   * explicitly state that they are not allowed.
   * <p>
   * By default this compatibility option is set to {@code false}.
   *
   * @param allowZeroLengthDirectoryStrings
   *          {@code true} if zero-length values will be allowed by the
   *          Directory String syntax, or {@code false} if not.
   * @return A reference to this {@code SchemaBuilder}.
   */
  public SchemaBuilder allowZeroLengthDirectoryStrings(
      final boolean allowZeroLengthDirectoryStrings)
  {
    this.allowZeroLengthDirectoryStrings = allowZeroLengthDirectoryStrings;
    return this;
  }
  /**
   * Removes the named attribute type from this schema builder.
   *
   * @param name
@@ -2600,9 +2683,20 @@
   */
  public boolean removeAttributeType(final String name)
  {
    if (schema.hasAttributeType(name))
    final AttributeType element = numericOID2AttributeTypes.get(name);
    if (element != null)
    {
      removeAttributeType(schema.getAttributeType(name));
      removeAttributeType(element);
      return true;
    }
    final List<AttributeType> elements = name2AttributeTypes
        .get(toLowerCase(name));
    if (elements != null)
    {
      for (final AttributeType e : elements)
      {
        removeAttributeType(e);
      }
      return true;
    }
    return false;
@@ -2619,9 +2713,20 @@
   */
  public boolean removeDITContentRule(final String name)
  {
    if (schema.hasDITContentRule(name))
    final DITContentRule element = numericOID2ContentRules.get(name);
    if (element != null)
    {
      removeDITContentRule(schema.getDITContentRule(name));
      removeDITContentRule(element);
      return true;
    }
    final List<DITContentRule> elements = name2ContentRules
        .get(toLowerCase(name));
    if (elements != null)
    {
      for (final DITContentRule e : elements)
      {
        removeDITContentRule(e);
      }
      return true;
    }
    return false;
@@ -2636,11 +2741,12 @@
   *          The ID of the DIT structure rule to be removed.
   * @return {@code true} if the DIT structure rule was found.
   */
  public boolean removeDITStructureRule(final Integer ruleID)
  public boolean removeDITStructureRule(final int ruleID)
  {
    if (schema.hasDITStructureRule(ruleID))
    final DITStructureRule element = id2StructureRules.get(ruleID);
    if (element != null)
    {
      removeDITStructureRule(schema.getDITStructureRule(ruleID));
      removeDITStructureRule(element);
      return true;
    }
    return false;
@@ -2657,9 +2763,20 @@
   */
  public boolean removeMatchingRule(final String name)
  {
    if (schema.hasMatchingRule(name))
    final MatchingRule element = numericOID2MatchingRules.get(name);
    if (element != null)
    {
      removeMatchingRule(schema.getMatchingRule(name));
      removeMatchingRule(element);
      return true;
    }
    final List<MatchingRule> elements = name2MatchingRules
        .get(toLowerCase(name));
    if (elements != null)
    {
      for (final MatchingRule e : elements)
      {
        removeMatchingRule(e);
      }
      return true;
    }
    return false;
@@ -2676,9 +2793,20 @@
   */
  public boolean removeMatchingRuleUse(final String name)
  {
    if (schema.hasMatchingRuleUse(name))
    final MatchingRuleUse element = numericOID2MatchingRuleUses.get(name);
    if (element != null)
    {
      removeMatchingRuleUse(schema.getMatchingRuleUse(name));
      removeMatchingRuleUse(element);
      return true;
    }
    final List<MatchingRuleUse> elements = name2MatchingRuleUses
        .get(toLowerCase(name));
    if (elements != null)
    {
      for (final MatchingRuleUse e : elements)
      {
        removeMatchingRuleUse(e);
      }
      return true;
    }
    return false;
@@ -2695,9 +2823,19 @@
   */
  public boolean removeNameForm(final String name)
  {
    if (schema.hasNameForm(name))
    final NameForm element = numericOID2NameForms.get(name);
    if (element != null)
    {
      removeNameForm(schema.getNameForm(name));
      removeNameForm(element);
      return true;
    }
    final List<NameForm> elements = name2NameForms.get(toLowerCase(name));
    if (elements != null)
    {
      for (final NameForm e : elements)
      {
        removeNameForm(e);
      }
      return true;
    }
    return false;
@@ -2714,9 +2852,20 @@
   */
  public boolean removeObjectClass(final String name)
  {
    if (schema.hasObjectClass(name))
    final ObjectClass element = numericOID2ObjectClasses.get(name);
    if (element != null)
    {
      removeObjectClass(schema.getObjectClass(name));
      removeObjectClass(element);
      return true;
    }
    final List<ObjectClass> elements = name2ObjectClasses
        .get(toLowerCase(name));
    if (elements != null)
    {
      for (final ObjectClass e : elements)
      {
        removeObjectClass(e);
      }
      return true;
    }
    return false;
@@ -2733,9 +2882,10 @@
   */
  public boolean removeSyntax(final String numericOID)
  {
    if (schema.hasSyntax(numericOID))
    final Syntax element = numericOID2Syntaxes.get(numericOID);
    if (element != null)
    {
      removeSyntax(schema.getSyntax(numericOID));
      removeSyntax(element);
      return true;
    }
    return false;
@@ -2744,28 +2894,6 @@
  /**
   * Sets the schema compatibility options for this schema builder. The schema
   * builder maintains its own set of compatibility options, so subsequent
   * changes to the provided set of options will not impact this schema builder.
   *
   * @param options
   *          The set of schema compatibility options that this schema builder
   *          should use.
   * @return A reference to this schema builder.
   * @throws NullPointerException
   *           If {@code options} was {@code null}.
   */
  public SchemaBuilder setSchemaCompatOptions(final SchemaCompatOptions options)
      throws NullPointerException
  {
    Validator.ensureNotNull(options);
    this.options.assign(options);
    return this;
  }
  /**
   * Returns a strict {@code Schema} containing all of the schema elements
   * contained in this schema builder as well as the same set of schema
   * compatibility options.
@@ -2779,10 +2907,19 @@
   */
  public Schema toSchema()
  {
    validate();
    final Schema builtSchema = schema;
    final Schema schema = new Schema(schemaName, allowMalformedNamesAndOptions,
        allowNonStandardTelephoneNumbers, allowZeroLengthDirectoryStrings,
        numericOID2Syntaxes, numericOID2MatchingRules,
        numericOID2MatchingRuleUses, numericOID2AttributeTypes,
        numericOID2ObjectClasses, numericOID2NameForms,
        numericOID2ContentRules, id2StructureRules, name2MatchingRules,
        name2MatchingRuleUses, name2AttributeTypes, name2ObjectClasses,
        name2NameForms, name2ContentRules, name2StructureRules,
        objectClass2NameForms, nameForm2StructureRules, warnings);
    validate(schema);
    initBuilder(null);
    return builtSchema;
    return schema;
  }
@@ -2881,8 +3018,8 @@
      if (!overwrite)
      {
        final LocalizableMessage message = ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_ID
            .get(rule.getNameOrRuleID(), rule.getRuleID(), conflictingRule
                .getNameOrRuleID());
            .get(rule.getNameOrRuleID(), rule.getRuleID(),
                conflictingRule.getNameOrRuleID());
        throw new ConflictingSchemaElementException(message);
      }
      removeDITStructureRule(conflictingRule);
@@ -2963,8 +3100,8 @@
      if (!overwrite)
      {
        final LocalizableMessage message = ERR_SCHEMA_CONFLICTING_MATCHING_RULE_USE
            .get(use.getNameOrOID(), use.getMatchingRuleOID(), conflictingUse
                .getNameOrOID());
            .get(use.getNameOrOID(), use.getMatchingRuleOID(),
                conflictingUse.getNameOrOID());
        throw new ConflictingSchemaElementException(message);
      }
      removeMatchingRuleUse(conflictingUse);
@@ -3004,8 +3141,8 @@
      if (!overwrite)
      {
        final LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_OID
            .get(form.getNameOrOID(), form.getOID(), conflictingForm
                .getNameOrOID());
            .get(form.getNameOrOID(), form.getOID(),
                conflictingForm.getNameOrOID());
        throw new ConflictingSchemaElementException(message);
      }
      removeNameForm(conflictingForm);
@@ -3097,6 +3234,15 @@
  private void initBuilder(String schemaName)
  {
    if (schemaName == null)
    {
      schemaName = String.format("Schema#%d", nextSchemaID.getAndIncrement());
    }
    this.schemaName = schemaName;
    allowMalformedNamesAndOptions = true;
    allowNonStandardTelephoneNumbers = true;
    allowZeroLengthDirectoryStrings = false;
    numericOID2Syntaxes = new LinkedHashMap<String, Syntax>();
    numericOID2MatchingRules = new LinkedHashMap<String, MatchingRule>();
    numericOID2MatchingRuleUses = new LinkedHashMap<String, MatchingRuleUse>();
@@ -3116,22 +3262,7 @@
    objectClass2NameForms = new HashMap<String, List<NameForm>>();
    nameForm2StructureRules = new HashMap<String, List<DITStructureRule>>();
    options = SchemaCompatOptions.defaultOptions();
    warnings = new LinkedList<LocalizableMessage>();
    if (schemaName == null)
    {
      schemaName = String.format("Schema#%d", nextSchemaID.getAndIncrement());
    }
    schema = new Schema(schemaName, numericOID2Syntaxes,
        numericOID2MatchingRules, numericOID2MatchingRuleUses,
        numericOID2AttributeTypes, numericOID2ObjectClasses,
        numericOID2NameForms, numericOID2ContentRules, id2StructureRules,
        name2MatchingRules, name2MatchingRuleUses, name2AttributeTypes,
        name2ObjectClasses, name2NameForms, name2ContentRules,
        name2StructureRules, objectClass2NameForms, nameForm2StructureRules,
        options, warnings);
  }
@@ -3306,7 +3437,7 @@
  private void validate()
  private void validate(final Schema schema)
  {
    // Verify all references in all elements
    for (final Syntax syntax : numericOID2Syntaxes.values().toArray(
@@ -3319,8 +3450,8 @@
      catch (final SchemaException e)
      {
        removeSyntax(syntax);
        warnings.add(ERR_SYNTAX_VALIDATION_FAIL.get(
            syntax.toString(), e.getMessageObject()));
        warnings.add(ERR_SYNTAX_VALIDATION_FAIL.get(syntax.toString(),
            e.getMessageObject()));
      }
    }
@@ -3349,8 +3480,8 @@
      catch (final SchemaException e)
      {
        removeAttributeType(attribute);
        warnings.add(ERR_ATTR_TYPE_VALIDATION_FAIL.get(attribute.toString(), e
            .getMessageObject()));
        warnings.add(ERR_ATTR_TYPE_VALIDATION_FAIL.get(attribute.toString(),
            e.getMessageObject()));
      }
    }
@@ -3413,8 +3544,8 @@
      catch (final SchemaException e)
      {
        removeNameForm(form);
        warnings.add(ERR_NAMEFORM_VALIDATION_FAIL.get(form.toString(), e
            .getMessageObject()));
        warnings.add(ERR_NAMEFORM_VALIDATION_FAIL.get(form.toString(),
            e.getMessageObject()));
      }
    }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptions.java
File was deleted
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaValidationPolicy.java
New file
@@ -0,0 +1,505 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opendj3/legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2011 ForgeRock AS
 */
package org.forgerock.opendj.ldap.schema;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.ErrorResultException;
/**
 * This class provides various schema validation policy options for controlling
 * how entries should be validated against the directory schema.
 */
public final class SchemaValidationPolicy
{
  /**
   * A call-back which will be called during DIT structure rule schema
   * validation in order to retrieve the parent of the entry being validated.
   */
  public interface EntryResolver
  {
    /**
     * Returns the named entry in order to enforce DIT structure rules.
     *
     * @param dn
     *          The name of the entry to be returned.
     * @return The named entry.
     * @throws ErrorResultException
     *           If the entry could not be retrieved.
     */
    Entry getEntry(DN dn) throws ErrorResultException;
  }
  /**
   * The schema validation policy.
   */
  public static enum Policy
  {
    /**
     * Schema validation will not be performed.
     */
    IGNORE,
    /**
     * Schema validation will be performed, but failures will not cause the
     * overall validation to fail. Error messages will be returned.
     */
    WARN,
    /**
     * Schema validation will be performed and failures will cause the overall
     * validation to fail. Error messages will be returned.
     */
    REJECT;
    private Policy()
    {
      // Nothing to do.
    }
    /**
     * Returns {@code true} if this policy is {@code IGNORE}.
     *
     * @return {@code true} if this policy is {@code IGNORE}.
     */
    public boolean isIgnore()
    {
      return this == IGNORE;
    }
    /**
     * Returns {@code true} if this policy is {@code REJECT}.
     *
     * @return {@code true} if this policy is {@code REJECT}.
     */
    public boolean isReject()
    {
      return this == REJECT;
    }
    /**
     * Returns {@code true} if this policy is {@code WARN}.
     *
     * @return {@code true} if this policy is {@code WARN}.
     */
    public boolean isWarn()
    {
      return this == WARN;
    }
    /**
     * Returns {@code true} if this policy is {@code WARN} or {@code REJECT}.
     *
     * @return {@code true} if this policy is {@code WARN} or {@code REJECT}.
     */
    public boolean needsChecking()
    {
      return this != IGNORE;
    }
  }
  /**
   * Creates a copy of the provided schema validation policy.
   *
   * @param policy
   *          The policy to be copied.
   * @return The copy of the provided schema validation policy.
   */
  public static SchemaValidationPolicy copyOf(
      final SchemaValidationPolicy policy)
  {
    return defaultPolicy().assign(policy);
  }
  /**
   * Creates a new schema validation policy with default settings. More
   * specifically:
   * <ul>
   * <li>Entries not having a single structural object class will be rejected
   * <li>Entries having attributes which are not permitted by its object classes
   * or DIT content rule (if present) will be rejected
   * <li>Entries not conforming to name forms will be rejected
   * <li>DIT structure rules will not be ignored
   * </ul>
   *
   * @return The new schema validation policy.
   */
  public static SchemaValidationPolicy defaultPolicy()
  {
    return new SchemaValidationPolicy();
  }
  /**
   * Creates a new schema validation policy which will not perform any schema
   * validation.
   *
   * @return The new schema validation policy.
   */
  public static SchemaValidationPolicy ignoreAll()
  {
    return new SchemaValidationPolicy()
        .checkAttributesAndObjectClasses(Policy.IGNORE)
        .checkAttributeValues(Policy.IGNORE)
        .checkDITContentRules(Policy.IGNORE)
        .checkNameForms(Policy.IGNORE)
        .requireSingleStructuralObjectClass(Policy.IGNORE);
  }
  private Policy checkNameForms = Policy.REJECT;
  private Policy checkDITStructureRules = Policy.IGNORE;
  private Policy checkDITContentRules = Policy.REJECT;
  private Policy requireSingleStructuralObjectClass = Policy.REJECT;
  private Policy checkAttributesAndObjectClasses = Policy.REJECT;
  private Policy checkAttributeValues = Policy.REJECT;
  private EntryResolver checkDITStructureRulesEntryResolver = null;
  // Prevent direct instantiation.
  private SchemaValidationPolicy()
  {
    // Nothing to do.
  }
  /**
   * Returns the policy for verifying that the user attributes in an entry
   * conform to its object classes. More specifically, an entry must contain all
   * required user attributes, and must not contain any user attributes which
   * are not declared as required or optional by its object classes.
   * <p>
   * By default entries which have missing or additional user attributes will be
   * rejected.
   *
   * @return The policy for verifying that the user attributes in an entry
   *         conform to its object classes.
   */
  public Policy checkAttributesAndObjectClasses()
  {
    return checkAttributesAndObjectClasses;
  }
  /**
   * Specifies the policy for verifying that the user attributes in an entry
   * conform to its object classes. More specifically, an entry must contain all
   * required user attributes, and must not contain any user attributes which
   * are not declared as required or optional by its object classes.
   * <p>
   * By default entries which have missing or additional user attributes will be
   * rejected.
   *
   * @param policy
   *          The policy for verifying that the user attributes in an entry
   *          conform to its object classes.
   * @return A reference to this {@code SchemaValidationPolicy}.
   */
  public SchemaValidationPolicy checkAttributesAndObjectClasses(
      final Policy policy)
  {
    this.checkAttributesAndObjectClasses = policy;
    return this;
  }
  /**
   * Returns the policy for verifying that the user attributes in an entry
   * conform to their associated attribute type descriptions. This may include:
   * <ul>
   * <li>checking that there is at least one value
   * <li>checking that single-valued attributes contain only a single value
   * <li>checking that there are no duplicate values according to the
   * attribute's default equality matching rule
   * <li>checking that attributes which require BER encoding specify the
   * {@code ;binary} attribute option
   * <li>checking that the values are valid according to the attribute's syntax.
   * </ul>
   * Schema validation implementations specify exactly which of the above checks
   * will be performed.
   * <p>
   * By default entries which have invalid attribute values will be rejected.
   *
   * @return The policy for verifying that the user attributes in an entry
   *         conform to their associated attribute type descriptions.
   */
  public Policy checkAttributeValues()
  {
    return checkAttributeValues;
  }
  /**
   * Specifies the policy for verifying that the user attributes in an entry
   * conform to their associated attribute type descriptions. This may include:
   * <ul>
   * <li>checking that there is at least one value
   * <li>checking that single-valued attributes contain only a single value
   * <li>checking that there are no duplicate values according to the
   * attribute's default equality matching rule
   * <li>checking that attributes which require BER encoding specify the
   * {@code ;binary} attribute option
   * <li>checking that the values are valid according to the attribute's syntax.
   * </ul>
   * Schema validation implementations specify exactly which of the above checks
   * will be performed.
   * <p>
   * By default entries which have invalid attribute values will be rejected.
   *
   * @param policy
   *          The policy for verifying that the user attributes in an entry
   *          conform to their associated attribute type descriptions.
   * @return A reference to this {@code SchemaValidationPolicy}.
   */
  public SchemaValidationPolicy checkAttributeValues(final Policy policy)
  {
    this.checkAttributeValues = policy;
    return this;
  }
  /**
   * Returns the policy for validating entries against content rules defined in
   * the schema.
   * <p>
   * By default content rules will be ignored during validation.
   *
   * @return The policy for validating entries against content rules defined in
   *         the schema.
   */
  public Policy checkDITContentRules()
  {
    return checkDITContentRules;
  }
  /**
   * Specifies the policy for validating entries against content rules defined
   * in the schema.
   * <p>
   * By default content rules will be ignored during validation.
   *
   * @param policy
   *          The policy for validating entries against content rules defined in
   *          the schema.
   * @return A reference to this {@code SchemaValidationPolicy}.
   */
  public SchemaValidationPolicy checkDITContentRules(final Policy policy)
  {
    this.checkDITContentRules = policy;
    return this;
  }
  /**
   * Returns the policy for validating entries against structure rules defined
   * in the schema.
   * <p>
   * By default structure rules will be ignored during validation.
   *
   * @return The policy for validating entries against structure rules defined
   *         in the schema.
   */
  public Policy checkDITStructureRules()
  {
    return checkDITStructureRules;
  }
  /**
   * Specifies the policy for validating entries against structure rules defined
   * in the schema.
   * <p>
   * By default structure rules will be ignored during validation.
   *
   * @param policy
   *          The policy for validating entries against structure rules defined
   *          in the schema.
   * @param resolver
   *          The parent entry resolver which should be used for retrieving the
   *          parent entry during DIT structure rule validation.
   * @return A reference to this {@code SchemaValidationPolicy}.
   * @throws IllegalArgumentException
   *           If {@code resolver} was {@code null} and
   *           {@code checkDITStructureRules} is either {@code WARN} or
   *           {@code REJECT}.
   */
  public SchemaValidationPolicy checkDITStructureRules(final Policy policy,
      final EntryResolver resolver) throws IllegalArgumentException
  {
    if (checkDITStructureRules.needsChecking() && resolver == null)
    {
      throw new IllegalArgumentException(
          "Validation of structure rules enabled by resolver was null");
    }
    this.checkDITStructureRules = policy;
    this.checkDITStructureRulesEntryResolver = resolver;
    return this;
  }
  /**
   * Returns parent entry resolver which should be used for retrieving the
   * parent entry during DIT structure rule validation.
   * <p>
   * By default no resolver is defined because structure rules will be ignored
   * during validation.
   *
   * @return The parent entry resolver which should be used for retrieving the
   *         parent entry during DIT structure rule validation.
   */
  public EntryResolver checkDITStructureRulesEntryResolver()
  {
    return checkDITStructureRulesEntryResolver;
  }
  /**
   * Returns the policy for validating entries against name forms defined in the
   * schema.
   * <p>
   * By default name forms will be ignored during validation.
   *
   * @return The policy for validating entries against name forms defined in the
   *         schema.
   */
  public Policy checkNameForms()
  {
    return checkNameForms;
  }
  /**
   * Specifies the policy for validating entries against name forms defined in
   * the schema.
   * <p>
   * By default name forms will be ignored during validation.
   *
   * @param policy
   *          The policy for validating entries against name forms defined in
   *          the schema.
   * @return A reference to this {@code SchemaValidationPolicy}.
   */
  public SchemaValidationPolicy checkNameForms(final Policy policy)
  {
    this.checkNameForms = policy;
    return this;
  }
  /**
   * Returns the policy for verifying that entries have only a single structural
   * object class.
   * <p>
   * By default entries which do not have a structural object class or which
   * have more than one structural object class will be rejected.
   *
   * @return The policy for checking that entries have one and only one
   *         structural object class.
   */
  public Policy requireSingleStructuralObjectClass()
  {
    return requireSingleStructuralObjectClass;
  }
  /**
   * Specifies the policy for verifying that entries have only a single
   * structural object class.
   * <p>
   * By default entries which do not have a structural object class or which
   * have more than one structural object class will be rejected.
   *
   * @param policy
   *          The policy for checking that entries have one and only one
   *          structural object class.
   * @return A reference to this {@code SchemaValidationPolicy}.
   */
  public SchemaValidationPolicy requireSingleStructuralObjectClass(
      final Policy policy)
  {
    this.requireSingleStructuralObjectClass = policy;
    return this;
  }
  // Assigns the provided options to this set of options.
  SchemaValidationPolicy assign(final SchemaValidationPolicy policy)
  {
    this.checkAttributeValues = policy.checkAttributeValues;
    this.checkNameForms = policy.checkNameForms;
    this.checkAttributesAndObjectClasses = policy.checkAttributesAndObjectClasses;
    this.checkDITContentRules = policy.checkDITContentRules;
    this.checkDITStructureRules = policy.checkDITStructureRules;
    this.checkDITStructureRulesEntryResolver = policy.checkDITStructureRulesEntryResolver;
    this.requireSingleStructuralObjectClass = policy.requireSingleStructuralObjectClass;
    return this;
  }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/TelephoneNumberSyntaxImpl.java
@@ -113,7 +113,7 @@
    final int length = valueStr.length();
    if (!schema.getSchemaCompatOptions().allowNonStandardTelephoneNumbers())
    if (!schema.allowNonStandardTelephoneNumbers())
    {
      // If the value does not start with a plus sign, then that's not
      // acceptable.
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java
@@ -47,6 +47,8 @@
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.*;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException;
import com.forgerock.opendj.util.Base64;
@@ -232,7 +234,7 @@
  Schema schema = Schema.getDefaultSchema().asNonStrictSchema();
  boolean validateSchema = false;
  SchemaValidationPolicy schemaValidationPolicy = SchemaValidationPolicy.ignoreAll();
  private final LDIFReaderImpl impl;
@@ -589,7 +591,7 @@
  final void readLDIFRecordAttributeValue(final LDIFRecord record,
  final boolean readLDIFRecordAttributeValue(final LDIFRecord record,
      final String ldifLine, final Entry entry,
      final List<LocalizableMessage> schemaErrors) throws DecodeException
  {
@@ -606,14 +608,19 @@
    {
      final LocalizableMessage message = ERR_LDIF_UNKNOWN_ATTRIBUTE_TYPE.get(
          record.lineNumber, entry.getName().toString(), attrDescr);
      if (validateSchema)
      switch (schemaValidationPolicy.checkAttributesAndObjectClasses())
      {
      case REJECT:
        schemaErrors.add(message);
        return;
      }
      else
      {
        throw DecodeException.error(message);
        return false;
      case WARN:
        schemaErrors.add(message);
        return true;
      default: // Ignore
        // This should not happen: we should be using a non-strict schema for
        // this policy.
        throw new IllegalStateException("Schema is not consistent with policy",
            e);
      }
    }
    catch (final LocalizedIllegalArgumentException e)
@@ -632,19 +639,29 @@
    // known to violate the schema.
    if (isAttributeExcluded(attributeDescription))
    {
      return;
      return true;
    }
    final Syntax syntax = attributeDescription.getAttributeType().getSyntax();
    // Ensure that the binary option is present if required.
    if (!attributeDescription.getAttributeType().getSyntax()
        .isBEREncodingRequired())
    if (!syntax.isBEREncodingRequired())
    {
      if (validateSchema && attributeDescription.containsOption("binary"))
      if (schemaValidationPolicy.checkAttributeValues().needsChecking()
          && attributeDescription.containsOption("binary"))
      {
        final LocalizableMessage message = ERR_LDIF_UNEXPECTED_BINARY_OPTION
            .get(record.lineNumber, entry.getName().toString(), attrDescr);
        schemaErrors.add(message);
        return;
        if (schemaValidationPolicy.checkAttributeValues().isReject())
        {
          return false;
        }
        else
        {
          // Skip to next attribute value.
          return true;
        }
      }
    }
    else
@@ -652,64 +669,57 @@
      attributeDescription = attributeDescription.withOption("binary");
    }
    final boolean checkAttributeValues = schemaValidationPolicy
        .checkAttributeValues().needsChecking();
    if (checkAttributeValues)
    {
      LocalizableMessageBuilder builder = new LocalizableMessageBuilder();
      if (!syntax.valueIsAcceptable(value, builder))
      {
        schemaErrors.add(builder.toMessage());
        if (schemaValidationPolicy.checkAttributeValues().isReject())
        {
          return false;
        }
      }
    }
    Attribute attribute = entry.getAttribute(attributeDescription);
    if (attribute == null)
    {
      if (validateSchema)
      {
        final LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder();
        if (!attributeDescription.getAttributeType().getSyntax()
            .valueIsAcceptable(value, invalidReason))
        {
          final LocalizableMessage message = WARN_LDIF_MALFORMED_ATTRIBUTE_VALUE
              .get(record.lineNumber, entry.getName().toString(),
                  value.toString(), attrDescr, invalidReason);
          schemaErrors.add(message);
          return;
        }
      }
      attribute = new LinkedAttribute(attributeDescription, value);
      entry.addAttribute(attribute);
    }
    else
    else if (checkAttributeValues)
    {
      if (validateSchema)
      if (!attribute.add(value))
      {
        final LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder();
        if (!attributeDescription.getAttributeType().getSyntax()
            .valueIsAcceptable(value, invalidReason))
        final LocalizableMessage message = WARN_LDIF_DUPLICATE_ATTRIBUTE_VALUE
            .get(record.lineNumber, entry.getName().toString(), attrDescr,
                value.toString());
        schemaErrors.add(message);
        if (schemaValidationPolicy.checkAttributeValues().isReject())
        {
          final LocalizableMessage message = WARN_LDIF_MALFORMED_ATTRIBUTE_VALUE
              .get(record.lineNumber, entry.getName().toString(),
                  value.toString(), attrDescr, invalidReason);
          schemaErrors.add(message);
          return;
        }
        if (!attribute.add(value) && validateSchema)
        {
          final LocalizableMessage message = WARN_LDIF_DUPLICATE_ATTRIBUTE_VALUE
              .get(record.lineNumber, entry.getName().toString(), attrDescr,
                  value.toString());
          schemaErrors.add(message);
          return;
        }
        if (validateSchema
            && attributeDescription.getAttributeType().isSingleValue())
        {
          final LocalizableMessage message = ERR_LDIF_MULTI_VALUED_SINGLE_VALUED_ATTRIBUTE
              .get(record.lineNumber, entry.getName().toString(), attrDescr);
          schemaErrors.add(message);
          return;
          return false;
        }
      }
      else
      else if (attributeDescription.getAttributeType().isSingleValue())
      {
        attribute.add(value);
        final LocalizableMessage message = ERR_LDIF_MULTI_VALUED_SINGLE_VALUED_ATTRIBUTE
            .get(record.lineNumber, entry.getName().toString(), attrDescr);
        schemaErrors.add(message);
        if (schemaValidationPolicy.checkAttributeValues().isReject())
        {
          return false;
        }
      }
    }
    else
    {
      attribute.add(value);
    }
    return true;
  }
@@ -893,6 +903,15 @@
  final void handleSchemaValidationWarning(final LDIFRecord record,
      final List<LocalizableMessage> messages) throws DecodeException
  {
    rejectedRecordListener.handleSchemaValidationWarning(record.lineNumber,
        record.ldifLines, messages);
  }
  final void handleSkippedRecord(final LDIFRecord record,
      final LocalizableMessage message) throws DecodeException
  {
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java
@@ -38,12 +38,15 @@
import java.util.*;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.*;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException;
import com.forgerock.opendj.util.Validator;
@@ -363,32 +366,34 @@
  public LDIFChangeRecordReader setSchema(final Schema schema)
  {
    Validator.ensureNotNull(schema);
    this.schema = validateSchema ? schema.asStrictSchema() : schema
        .asNonStrictSchema();
    this.schema = schemaValidationPolicy.checkAttributesAndObjectClasses()
        .needsChecking() ? schema.asStrictSchema() : schema.asNonStrictSchema();
    return this;
  }
  /**
   * Specifies whether or not schema validation should be performed for change
   * records that are read from LDIF.
   * <p>
   * When enabled, the LDIF reader will implicitly use a strict schema so that
   * unrecognized attribute types will be detected.
   * Specifies the schema validation which should be used when reading LDIF
   * change records. If attribute value validation is enabled then all checks
   * will be performed.
   * <p>
   * Schema validation is disabled by default.
   * <p>
   * <b>NOTE:</b> this method copies the provided policy so changes made to it
   * after this method has been called will have no effect.
   *
   * @param validateSchema
   *          {@code true} if schema validation should be performed, or
   *          {@code false} otherwise.
   * @param policy
   *          The schema validation which should be used when reading LDIF
   *          change records.
   * @return A reference to this {@code LDIFChangeRecordReader}.
   */
  public LDIFChangeRecordReader setValidateSchema(final boolean validateSchema)
  public LDIFChangeRecordReader setSchemaValidationPolicy(
      final SchemaValidationPolicy policy)
  {
    this.validateSchema = validateSchema;
    this.schema = validateSchema ? schema.asStrictSchema() : schema
        .asNonStrictSchema();
    this.schemaValidationPolicy = SchemaValidationPolicy.copyOf(policy);
    this.schema = schemaValidationPolicy.checkAttributesAndObjectClasses()
        .needsChecking() ? schema.asStrictSchema() : schema.asNonStrictSchema();
    return this;
  }
@@ -497,24 +502,42 @@
  {
    // Use an Entry for the AttributeSequence.
    final Entry entry = new LinkedHashMapEntry(entryDN);
    boolean schemaValidationFailure = false;
    final List<LocalizableMessage> schemaErrors = new LinkedList<LocalizableMessage>();
    if (lastLDIFLine != null)
    {
      // This line was read when looking for the change type.
      readLDIFRecordAttributeValue(record, lastLDIFLine, entry, schemaErrors);
      if (!readLDIFRecordAttributeValue(record, lastLDIFLine, entry,
          schemaErrors))
      {
        schemaValidationFailure = true;
      }
    }
    while (record.iterator.hasNext())
    {
      final String ldifLine = record.iterator.next();
      readLDIFRecordAttributeValue(record, ldifLine, entry, schemaErrors);
      if (!readLDIFRecordAttributeValue(record, ldifLine, entry, schemaErrors))
      {
        schemaValidationFailure = true;
      }
    }
    if (!schema.validateEntry(entry, schemaValidationPolicy, schemaErrors))
    {
      schemaValidationFailure = true;
    }
    if (schemaValidationFailure)
    {
      handleSchemaValidationFailure(record, schemaErrors);
      return null;
    }
    if (!schemaErrors.isEmpty())
    {
      handleSchemaValidationFailure(record, schemaErrors);
      return null;
      handleSchemaValidationWarning(record, schemaErrors);
    }
    return Requests.newAddRequest(entry);
@@ -541,9 +564,9 @@
      final LDIFRecord record) throws DecodeException
  {
    final ModifyRequest modifyRequest = Requests.newModifyRequest(entryDN);
    final KeyValuePair pair = new KeyValuePair();
    final List<ByteString> attributeValues = new ArrayList<ByteString>();
    boolean schemaValidationFailure = false;
    final List<LocalizableMessage> schemaErrors = new LinkedList<LocalizableMessage>();
    while (record.iterator.hasNext())
@@ -591,14 +614,20 @@
      {
        final LocalizableMessage message = ERR_LDIF_UNKNOWN_ATTRIBUTE_TYPE.get(
            record.lineNumber, entryDN.toString(), pair.value);
        if (validateSchema)
        switch (schemaValidationPolicy.checkAttributesAndObjectClasses())
        {
        case REJECT:
          schemaValidationFailure = true;
          schemaErrors.add(message);
          continue;
        }
        else
        {
          throw DecodeException.error(message);
        case WARN:
          schemaErrors.add(message);
          continue;
        default: //Ignore
          // This should not happen: we should be using a non-strict schema for
          // this policy.
          throw new IllegalStateException(
              "Schema is not consistent with policy", e);
        }
      }
      catch (final LocalizedIllegalArgumentException e)
@@ -616,14 +645,20 @@
        continue;
      }
      final Syntax syntax = attributeDescription.getAttributeType().getSyntax();
      // Ensure that the binary option is present if required.
      if (!attributeDescription.getAttributeType().getSyntax()
          .isBEREncodingRequired())
      if (!syntax.isBEREncodingRequired())
      {
        if (validateSchema && attributeDescription.containsOption("binary"))
        if (schemaValidationPolicy.checkAttributeValues().needsChecking()
            && attributeDescription.containsOption("binary"))
        {
          final LocalizableMessage message = ERR_LDIF_UNEXPECTED_BINARY_OPTION
              .get(record.lineNumber, entryDN.toString(), pair.value);
          if (schemaValidationPolicy.checkAttributeValues().isReject())
          {
            schemaValidationFailure = true;
          }
          schemaErrors.add(message);
          continue;
        }
@@ -657,7 +692,7 @@
        catch (final LocalizedIllegalArgumentException e)
        {
          // No need to catch schema exception here because it implies that the
          // attribute name is wrong.
          // attribute name is wrong and the record is malformed.
          final LocalizableMessage message = ERR_LDIF_MALFORMED_ATTRIBUTE_NAME
              .get(record.lineNumber, entryDN.toString(), attrDescr);
          throw DecodeException.error(message);
@@ -672,6 +707,7 @@
        if (!attributeDescription2.equals(attributeDescription))
        {
          // Malformed record.
          final LocalizableMessage message = ERR_LDIF_ATTRIBUTE_NAME_MISMATCH
              .get(record.lineNumber, entryDN.toString(),
                  attributeDescription2.toString(),
@@ -679,9 +715,26 @@
          throw DecodeException.error(message);
        }
        // Now parse the attribute value.
        attributeValues.add(parseSingleValue(record, ldifLine, entryDN,
            colonPos, attrDescr));
        // Parse the attribute value and check it if needed.
        final ByteString value = parseSingleValue(record, ldifLine, entryDN,
            colonPos, attrDescr);
        if (schemaValidationPolicy.checkAttributeValues().needsChecking())
        {
          LocalizableMessageBuilder builder = new LocalizableMessageBuilder();
          if (!syntax.valueIsAcceptable(value, builder))
          {
            // Just log a message, but don't skip the value since this could
            // change the semantics of the modification (e.g. if all values in a
            // delete are skipped then this implies that the whole attribute
            // should be removed).
            if (schemaValidationPolicy.checkAttributeValues().isReject())
            {
              schemaValidationFailure = true;
            }
            schemaErrors.add(builder.toMessage());
          }
        }
        attributeValues.add(value);
      }
      final Modification change = new Modification(modType,
@@ -689,12 +742,17 @@
      modifyRequest.addModification(change);
    }
    if (!schemaErrors.isEmpty())
    if (schemaValidationFailure)
    {
      handleSchemaValidationFailure(record, schemaErrors);
      return null;
    }
    if (!schemaErrors.isEmpty())
    {
      handleSchemaValidationWarning(record, schemaErrors);
    }
    return modifyRequest;
  }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFEntryReader.java
@@ -43,6 +43,7 @@
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.*;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy;
import com.forgerock.opendj.util.Validator;
@@ -393,32 +394,34 @@
  public LDIFEntryReader setSchema(final Schema schema)
  {
    Validator.ensureNotNull(schema);
    this.schema = validateSchema ? schema.asStrictSchema() : schema
        .asNonStrictSchema();
    this.schema = schemaValidationPolicy.checkAttributesAndObjectClasses()
        .needsChecking() ? schema.asStrictSchema() : schema.asNonStrictSchema();
    return this;
  }
  /**
   * Specifies whether or not schema validation should be performed for entries
   * that are read from LDIF.
   * <p>
   * When enabled, this LDIF reader will implicitly use a strict schema so that
   * unrecognized attribute types will be detected.
   * Specifies the schema validation which should be used when reading LDIF
   * entry records. If attribute value validation is enabled then all checks
   * will be performed.
   * <p>
   * Schema validation is disabled by default.
   * <p>
   * <b>NOTE:</b> this method copies the provided policy so changes made to it
   * after this method has been called will have no effect.
   *
   * @param validateSchema
   *          {@code true} if schema validation should be performed, or
   *          {@code false} otherwise.
   * @param policy
   *          The schema validation which should be used when reading LDIF entry
   *          records.
   * @return A reference to this {@code LDIFEntryReader}.
   */
  public LDIFEntryReader setValidateSchema(final boolean validateSchema)
  public LDIFEntryReader setSchemaValidationPolicy(
      final SchemaValidationPolicy policy)
  {
    this.validateSchema = validateSchema;
    this.schema = validateSchema ? schema.asStrictSchema() : schema
        .asNonStrictSchema();
    this.schemaValidationPolicy = SchemaValidationPolicy.copyOf(policy);
    this.schema = schemaValidationPolicy.checkAttributesAndObjectClasses()
        .needsChecking() ? schema.asStrictSchema() : schema.asNonStrictSchema();
    return this;
  }
@@ -458,11 +461,16 @@
        // Use an Entry for the AttributeSequence.
        final Entry entry = new LinkedHashMapEntry(entryDN);
        boolean schemaValidationFailure = false;
        final List<LocalizableMessage> schemaErrors = new LinkedList<LocalizableMessage>();
        while (record.iterator.hasNext())
        {
          final String ldifLine = record.iterator.next();
          readLDIFRecordAttributeValue(record, ldifLine, entry, schemaErrors);
          if (!readLDIFRecordAttributeValue(record, ldifLine, entry,
              schemaErrors))
          {
            schemaValidationFailure = true;
          }
        }
        // Skip if the entry is excluded by any filters.
@@ -474,12 +482,22 @@
          continue;
        }
        if (!schemaErrors.isEmpty())
        if (!schema.validateEntry(entry, schemaValidationPolicy, schemaErrors))
        {
          schemaValidationFailure = true;
        }
        if (schemaValidationFailure)
        {
          handleSchemaValidationFailure(record, schemaErrors);
          continue;
        }
        if (!schemaErrors.isEmpty())
        {
          handleSchemaValidationWarning(record, schemaErrors);
        }
        nextEntry = entry;
      }
      catch (final DecodeException e)
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/RejectedRecordListener.java
@@ -78,6 +78,15 @@
    {
      // Ignore skipped records.
    }
    public void handleSchemaValidationWarning(long lineNumber,
        List<String> ldifRecord, List<LocalizableMessage> reasons)
        throws DecodeException
    {
      // Ignore schema validation warnings.
    }
  };
@@ -122,6 +131,25 @@
  /**
   * Invoked when a record was not rejected but contained one or more schema validation warnings.
   *
   * @param lineNumber
   *          The line number within the source location in which the
   *          record is located, if known, otherwise {@code -1}.
   * @param ldifRecord
   *          An LDIF representation of the record which contained schema
   *          validation warnings.
   * @param reasons
   *          The schema validation warnings.
   * @throws DecodeException
   *           If processing should terminate.
   */
  void handleSchemaValidationWarning(long lineNumber, List<String> ldifRecord,
      List<LocalizableMessage> reasons) throws DecodeException;
  /**
   * Invoked when a record was skipped because it did not match filter criteria
   * defined by the reader.
   *
opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
@@ -1328,4 +1328,47 @@
 with distinguished name "%s" because it contained a malformed deleteoldrdn "%s"
ERR_LDIF_MALFORMED_NEW_SUPERIOR=Unable to parse LDIF modify DN record starting at line %d \
 with distinguished name "%s" because it contained a malformed newsuperior "%s"
ERR_ENTRY_SCHEMA_MULTIPLE_STRUCTURAL_CLASSES=Entry "%s" violates the schema \
 because it contains multiple conflicting structural object classes "%s" and \
 "%s". Only a single structural object class is allowed in an entry
ERR_ENTRY_SCHEMA_UNKNOWN_OBJECT_CLASS=Entry "%s" violates the schema \
 because it contains an unrecognized object class "%s"
ERR_ENTRY_SCHEMA_NO_STRUCTURAL_CLASS=Entry "%s" violates the schema because \
 it does not include a structural object class. All entries must contain a \
 structural object class
ERR_ENTRY_SCHEMA_DCR_MISSING_MUST_ATTRIBUTES=Entry "%s" violates the schema \
 because it does not contain attribute "%s" which is required by DIT content rule "%s"
ERR_ENTRY_SCHEMA_DCR_PROHIBITED_ATTRIBUTES=Entry "%s" violates the schema \
 because it contains attribute "%s" which is prohibited by DIT content rule "%s"
ERR_ENTRY_SCHEMA_DCR_PROHIBITED_AUXILIARY_OC=Entry "%s" violates the schema because \
 it contains auxiliary object class "%s" which is not allowed by DIT content rule "%s"
ERR_ENTRY_SCHEMA_OC_MISSING_MUST_ATTRIBUTES=Entry "%s" violates the schema \
 because it does not contain attribute "%s" which is required by object class "%s"
ERR_ENTRY_SCHEMA_OC_DISALLOWED_ATTRIBUTES=Entry "%s" violates the schema \
 because it contains attribute "%s" which is not allowed by any of the object \
 classes in the entry
ERR_ENTRY_SCHEMA_DCR_DISALLOWED_ATTRIBUTES=Entry "%s" violates the schema \
 because it contains attribute "%s" which is not allowed by any of the object \
 classes in the entry nor its DIT content rule "%s"
ERR_ENTRY_SCHEMA_AT_EMPTY_ATTRIBUTE=Entry "%s" violates the schema \
 because it contains an empty attribute "%s"
ERR_ENTRY_SCHEMA_AT_SINGLE_VALUED_ATTRIBUTE=Entry "%s" violates the schema \
 because it contains multiple values for the single-valued attribute "%s"
ERR_ENTRY_SCHEMA_NF_MISSING_MUST_ATTRIBUTES=Entry "%s" violates the schema \
 because its RDN does not contain the attribute "%s" which is required by \
 name form "%s"
ERR_ENTRY_SCHEMA_NF_DISALLOWED_ATTRIBUTES=Entry "%s" violates the schema \
 because its RDN contains attribute "%s" which is not allowed by any the name \
 form "%s"
ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC=Entry "%s" could not be validated against \
 any DIT structure rules because its parent entry does not appear to \
 contain a valid structural object class
ERR_ENTRY_SCHEMA_DSR_PARENT_NOT_FOUND=Entry "%s" could not be validated against \
 any DIT structure rules because its parent entry could not be retrieved \
 for the following reason: %s
ERR_ENTRY_SCHEMA_DSR_ILLEGAL_OC=Entry "%s" violates the schema because DIT \
 structure rule "%s" does not allow entries of type "%s" to be placed \
 immediately below entries of type "%s"
ERR_ENTRY_SCHEMA_DSR_MISSING_DSR=Entry "%s" violates the schema because \
 there is no DIT structure rule that applies to the entry, but there is a DIT \
 structure rule "%s" which applies to the parent entry
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatTest.java
File was renamed from opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptionsTest.java
@@ -36,9 +36,9 @@
/**
 * Test SchemaCompatOptions.
 * Tests schema compatibility options.
 */
public class SchemaCompatOptionsTest extends SchemaTestCase
public class SchemaCompatTest extends SchemaTestCase
{
  /**
   * Returns test data for valid attribute descriptions.
@@ -85,8 +85,7 @@
      boolean allowIllegalCharacters)
  {
    SchemaBuilder builder = new SchemaBuilder(Schema.getCoreSchema())
        .setSchemaCompatOptions(SchemaCompatOptions.defaultOptions()
            .allowMalformedNamesAndOptions(allowIllegalCharacters));
        .allowMalformedNamesAndOptions(allowIllegalCharacters);
    Schema schema = builder.toSchema().asNonStrictSchema();
    AttributeDescription.valueOf(atd, schema);
  }
@@ -134,8 +133,7 @@
      boolean allowIllegalCharacters)
  {
    SchemaBuilder builder = new SchemaBuilder(Schema.getCoreSchema())
        .setSchemaCompatOptions(SchemaCompatOptions.defaultOptions()
            .allowMalformedNamesAndOptions(allowIllegalCharacters));
        .allowMalformedNamesAndOptions(allowIllegalCharacters);
    Schema schema = builder.toSchema().asNonStrictSchema();
    AttributeDescription.valueOf(atd, schema);
  }
@@ -237,8 +235,7 @@
      Syntax syntax, boolean allowIllegalCharacters)
  {
    SchemaBuilder builder = new SchemaBuilder()
        .setSchemaCompatOptions(SchemaCompatOptions.defaultOptions()
            .allowMalformedNamesAndOptions(allowIllegalCharacters));
        .allowMalformedNamesAndOptions(allowIllegalCharacters);
    if (syntax == ATD_SYNTAX)
    {
@@ -340,8 +337,7 @@
      Syntax syntax, boolean allowIllegalCharacters)
  {
    SchemaBuilder builder = new SchemaBuilder()
        .setSchemaCompatOptions(SchemaCompatOptions.defaultOptions()
            .allowMalformedNamesAndOptions(allowIllegalCharacters));
        .allowMalformedNamesAndOptions(allowIllegalCharacters);
    if (syntax == ATD_SYNTAX)
    {
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java
@@ -42,6 +42,8 @@
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy;
import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy.Policy;
import org.testng.annotations.Test;
@@ -457,7 +459,10 @@
        "objectClass: domainComponent",
        "dc: example",
        "xxx: unknown attribute"
        ).setRejectedRecordListener(listener).setValidateSchema(true);
        ).setRejectedRecordListener(listener)
         .setSchemaValidationPolicy(
             SchemaValidationPolicy.ignoreAll()
             .checkAttributesAndObjectClasses(Policy.REJECT));
    // @formatter:on
    assertThat(reader.hasNext()).isFalse();
@@ -468,4 +473,53 @@
            "objectClass: top", "objectClass: domainComponent", "dc: example",
            "xxx: unknown attribute")), anyListOf(LocalizableMessage.class));
  }
  /**
   * Tests reading a record which does not conform to the schema invokes the
   * warning record listener.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testRejectedRecordListenerWarnsBadSchemaRecord()
      throws Exception
  {
    RejectedRecordListener listener = mock(RejectedRecordListener.class);
    // @formatter:off
    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
        "dn: dc=example,dc=com",
        "changetype: add",
        "objectClass: top",
        "objectClass: domainComponent",
        "dc: example",
        "xxx: unknown attribute"
        ).setRejectedRecordListener(listener)
         .setSchemaValidationPolicy(
             SchemaValidationPolicy.ignoreAll()
             .checkAttributesAndObjectClasses(Policy.WARN));
    // @formatter:on
    assertThat(reader.hasNext()).isTrue();
    ChangeRecord record = reader.readChangeRecord();
    assertThat(record).isInstanceOf(AddRequest.class);
    AddRequest addRequest = (AddRequest) record;
    assertThat((Object) addRequest.getName()).isEqualTo(
        DN.valueOf("dc=example,dc=com"));
    assertThat(
        addRequest.containsAttribute("objectClass", "top", "domainComponent"))
        .isTrue();
    assertThat(addRequest.containsAttribute("dc", "example")).isTrue();
    assertThat(addRequest.getAttributeCount()).isEqualTo(2);
    verify(listener).handleSchemaValidationWarning(
        eq(1L),
        eq(Arrays.asList("dn: dc=example,dc=com", "changetype: add",
            "objectClass: top", "objectClass: domainComponent", "dc: example",
            "xxx: unknown attribute")), anyListOf(LocalizableMessage.class));
  }
}
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFEntryReaderTestCase.java
@@ -63,8 +63,6 @@
    final LDIFEntryReader reader = new LDIFEntryReader(in);
    try
    {
      reader.setValidateSchema(false);
      Assert.assertFalse(reader.hasNext());
      Assert.assertFalse(reader.hasNext());
      try
@@ -126,8 +124,6 @@
    final LDIFEntryReader reader = new LDIFEntryReader(in);
    try
    {
      reader.setValidateSchema(false);
      Assert.assertTrue(reader.hasNext());
      final Entry entry = reader.readEntry();
      assertNotNull(entry);