From a849a42d97109bc9242c50c7abbd9857e865bb5e Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 24 Jun 2011 14:57:52 +0000
Subject: [PATCH] Fix OPENDJ-205: Add support for rejecting and skipping records to the LDIF readers

---
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java                  |   50 
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java             |  639 ++++++----
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java           |  122 +
 /dev/null                                                                                             |  227 ---
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaValidationPolicy.java    |  505 ++++++++
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java               |  133 +
 opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatTest.java          |   16 
 opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties                  |   45 
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DirectoryStringSyntaxImpl.java |    4 
 opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFEntryReaderTestCase.java          |    4 
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java                    | 1401 ++++++++++++++++++----
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFEntryReader.java                  |   50 
 opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java   |   56 
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java            |   52 
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/TelephoneNumberSyntaxImpl.java |    2 
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/RejectedRecordListener.java           |   28 
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java                          |  225 +++
 17 files changed, 2,663 insertions(+), 896 deletions(-)

diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
index 9e0f33c..de67fc7 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
+++ b/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);
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java
index 63b3c08..bb898db 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DITContentRule.java
+++ b/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.
    *
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DirectoryStringSyntaxImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DirectoryStringSyntaxImpl.java
index c1d3bff..a395e9e 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DirectoryStringSyntaxImpl.java
+++ b/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;
     }
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java
index 5059b69..e876bf6 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java
+++ b/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.
    *
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
index f64b02e..8f7e96e 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
+++ b/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;
   }
 }
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
index 11cbb23..3e4b81b 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
+++ b/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()));
       }
     }
 
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptions.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptions.java
deleted file mode 100644
index 1d30f9b..0000000
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptions.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * 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 2009 Sun Microsystems, Inc.
- *      Portions copyright 2011 ForgeRock AS
- */
-
-package org.forgerock.opendj.ldap.schema;
-
-
-
-/**
- * This class provides various schema compatibility options which may be used to
- * facilitate interoperability with legacy LDAP applications.
- */
-public final class SchemaCompatOptions
-{
-  /**
-   * Creates a copy of the provided schema compatibility options.
-   *
-   * @param options
-   *          The options to be copied.
-   * @return The copy of the provided schema compatibility options.
-   */
-  public static SchemaCompatOptions copyOf(
-      final SchemaCompatOptions options)
-  {
-    return defaultOptions().assign(options);
-  }
-
-
-
-  /**
-   * Creates a new set of schema compatibility options with default settings.
-   *
-   * @return The new schema compatibility options.
-   */
-  public static SchemaCompatOptions defaultOptions()
-  {
-    return new SchemaCompatOptions();
-  }
-
-
-
-  private boolean allowNonStandardTelephoneNumbers = true;
-
-  private boolean allowZeroLengthDirectoryStrings = false;
-
-  private boolean allowMalformedNamesAndOptions = true;
-
-
-
-  // Prevent direct instantiation.
-  private SchemaCompatOptions()
-  {
-    // Nothing to do.
-  }
-
-
-
-  /**
-   * Returns {@code true} if 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).
-   *
-   * @return {@code true} if the schema should allow 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 allowMalformedNamesAndOptions;
-  }
-
-
-
-  /**
-   * 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 SchemaCompatOptions}.
-   * @see <a href="http://tools.ietf.org/html/rfc4512">RFC 4512 - Lightweight
-   *      Directory Access Protocol (LDAP): Directory Information Models </a>
-   */
-  public SchemaCompatOptions allowMalformedNamesAndOptions(
-      final boolean allowMalformedNamesAndOptions)
-  {
-    this.allowMalformedNamesAndOptions = allowMalformedNamesAndOptions;
-    return this;
-  }
-
-
-
-  /**
-   * Returns {@code true} if 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}.
-   *
-   * @return {@code true} if the Telephone Number syntax should allow values
-   *         which do not conform to the E.123 international telephone number
-   *         format.
-   */
-  public boolean allowNonStandardTelephoneNumbers()
-  {
-    return allowNonStandardTelephoneNumbers;
-  }
-
-
-
-  /**
-   * 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 SchemaCompatOptions}.
-   */
-  public SchemaCompatOptions allowNonStandardTelephoneNumbers(
-      final boolean allowNonStandardTelephoneNumbers)
-  {
-    this.allowNonStandardTelephoneNumbers = allowNonStandardTelephoneNumbers;
-    return this;
-  }
-
-
-
-  /**
-   * Returns {@code true} if 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}.
-   *
-   * @return {@code true} if zero-length values will be allowed by the Directory
-   *         String syntax, or {@code false} if not.
-   */
-  public boolean allowZeroLengthDirectoryStrings()
-  {
-    return allowZeroLengthDirectoryStrings;
-  }
-
-
-
-  /**
-   * 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 SchemaCompatOptions}.
-   */
-  public SchemaCompatOptions allowZeroLengthDirectoryStrings(
-      final boolean allowZeroLengthDirectoryStrings)
-  {
-    this.allowZeroLengthDirectoryStrings = allowZeroLengthDirectoryStrings;
-    return this;
-  }
-
-
-
-  // Assigns the provided options to this set of options.
-  SchemaCompatOptions assign(final SchemaCompatOptions options)
-  {
-    this.allowMalformedNamesAndOptions = options.allowMalformedNamesAndOptions;
-    this.allowNonStandardTelephoneNumbers =options.allowNonStandardTelephoneNumbers;
-    this.allowZeroLengthDirectoryStrings = options.allowZeroLengthDirectoryStrings;
-    return this;
-  }
-
-}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaValidationPolicy.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaValidationPolicy.java
new file mode 100644
index 0000000..1c52df7
--- /dev/null
+++ b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/SchemaValidationPolicy.java
@@ -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;
+  }
+
+}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/TelephoneNumberSyntaxImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/TelephoneNumberSyntaxImpl.java
index 9543f95..99c2dbf 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/TelephoneNumberSyntaxImpl.java
+++ b/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.
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java
index 4230d08..7b8b0e7 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java
+++ b/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
   {
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java
index df050fa..c8498ad 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java
+++ b/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;
   }
 
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFEntryReader.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFEntryReader.java
index 5f13925..5e15fb0 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFEntryReader.java
+++ b/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)
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/RejectedRecordListener.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/RejectedRecordListener.java
index 79e167e..07517b7 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/RejectedRecordListener.java
+++ b/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.
    *
diff --git a/opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties b/opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
index 6c6c410..5a6cc2f 100755
--- a/opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
+++ b/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
diff --git a/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptionsTest.java b/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatTest.java
similarity index 94%
rename from opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptionsTest.java
rename to opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatTest.java
index 69d9e7d..17297c0 100644
--- a/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptionsTest.java
+++ b/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatTest.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)
     {
diff --git a/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java b/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java
index cfbce2f..99a99c4 100644
--- a/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java
+++ b/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));
+  }
 }
diff --git a/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFEntryReaderTestCase.java b/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFEntryReaderTestCase.java
index fc636de..281850c 100644
--- a/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFEntryReaderTestCase.java
+++ b/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);

--
Gitblit v1.10.0