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

Matthew Swift
21.52.2011 975c043fb68244888e4f4b08583b4704dc3c9076
Partial fix for OPENDJ-205: Add support for rejecting and skipping records to the LDIF readers

Added new interface RejectedRecordListener which can be configured for LDIFEntryReader and LDIFChangeRecordReaders.
Renamed Schema.nonStrict() -> Schema.asNonStrictSchema() and added Schema.asStrictSchema().
Fixed all usages of raw messages if LDIF package.
2 files added
14 files modified
1275 ■■■■ changed files
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeDescription.java 29 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/CoreSchemaImpl.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java 50 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java 135 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFStream.java 4 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFWriter.java 4 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/ChangeRecordWriter.java 2 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/EntryWriter.java 2 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java 238 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFEntryReader.java 105 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/RejectedRecordListener.java 141 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties 84 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionFactoryTestCase.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptionsTest.java 4 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java 471 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeDescription.java
@@ -784,6 +784,9 @@
   * @param attributeDescription
   *          The LDAP string representation of an attribute description.
   * @return The parsed attribute description.
   * @throws UnknownSchemaElementException
   *           If {@code attributeDescription} contains an attribute type which
   *           is not contained in the default schema and the schema is strict.
   * @throws LocalizedIllegalArgumentException
   *           If {@code attributeDescription} is not a valid LDAP string
   *           representation of an attribute description.
@@ -791,7 +794,8 @@
   *           If {@code attributeDescription} was {@code null}.
   */
  public static AttributeDescription valueOf(final String attributeDescription)
      throws LocalizedIllegalArgumentException, NullPointerException
      throws UnknownSchemaElementException, LocalizedIllegalArgumentException,
      NullPointerException
  {
    return valueOf(attributeDescription, Schema.getDefaultSchema());
  }
@@ -807,17 +811,20 @@
   * @param schema
   *          The schema to use when parsing the attribute description.
   * @return The parsed attribute description.
   * @throws UnknownSchemaElementException
   *           If {@code attributeDescription} contains an attribute type which
   *           is not contained in the provided schema and the schema is strict.
   * @throws LocalizedIllegalArgumentException
   *           If {@code attributeDescription} is not a valid LDAP string
   *           representation of an attribute description.
   * @throws NullPointerException
   *           If {@code attributeDescription} or {@code schema} was {@code
   *           null}.
   *           If {@code attributeDescription} or {@code schema} was
   *           {@code null}.
   */
  @SuppressWarnings("serial")
  public static AttributeDescription valueOf(final String attributeDescription,
      final Schema schema) throws LocalizedIllegalArgumentException,
      NullPointerException
      final Schema schema) throws UnknownSchemaElementException,
      LocalizedIllegalArgumentException, NullPointerException
  {
    Validator.ensureNotNull(attributeDescription, schema);
@@ -1005,17 +1012,7 @@
    }
    // Get the attribute type from the schema.
    final AttributeType attributeType;
    try
    {
      attributeType = schema.getAttributeType(oid);
    }
    catch (final UnknownSchemaElementException e)
    {
      final LocalizableMessage message = ERR_ATTRIBUTE_DESCRIPTION_TYPE_NOT_FOUND
          .get(attributeDescription, e.getMessageObject());
      throw new LocalizedIllegalArgumentException(message);
    }
    final AttributeType attributeType = schema.getAttributeType(oid);
    // If we're already at the end of the attribute description then it
    // does not contain any options.
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/CoreSchemaImpl.java
@@ -91,7 +91,7 @@
    addRFC3112(builder);
    addSunProprietary(builder);
    SINGLETON = builder.toSchema().nonStrict();
    SINGLETON = builder.toSchema().asNonStrictSchema();
  }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java
@@ -54,7 +54,7 @@
  {
    try
    {
      DN dn = DN.valueOf(value.toString(), schema.nonStrict());
      DN dn = DN.valueOf(value.toString(), schema.asNonStrictSchema());
      StringBuilder builder = new StringBuilder(value.length());
      return ByteString.valueOf(normalizeDN(builder, dn));
    }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
@@ -2446,12 +2446,16 @@
  /**
   * Indicates whether or not this schema is strict. Attribute type queries in
   * non-strict schema always succeed: if the requested attribute type is not
   * found then a temporary attribute type is created automatically having the
   * Octet String syntax and associated matching rules. Strict schema, on the
   * other hand, throw an {@link UnknownSchemaElementException} whenever an
   * attempt is made to retrieve a non-existent attribute type.
   * Indicates whether or not this schema is strict.
   * <p>
   * Attribute type queries against non-strict schema always succeed: if the
   * requested attribute type is not found then a temporary attribute type is
   * created automatically having the Octet String syntax and associated
   * matching rules.
   * <p>
   * Strict schema, on the other hand, throw an
   * {@link UnknownSchemaElementException} whenever an attempt is made to
   * retrieve a non-existent attribute type.
   *
   * @return {@code true} if this schema is strict.
   */
@@ -2463,16 +2467,14 @@
  /**
   * Returns a non-strict view of this schema. Attribute type queries in
   * non-strict schema always succeed: if the requested attribute type is not
   * found then a temporary attribute type is created automatically having the
   * Octet String syntax and associated matching rules. Strict schema, on the
   * other hand, throw an {@link UnknownSchemaElementException} whenever an
   * attempt is made to retrieve a non-existent attribute type.
   * 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 nonStrict()
  public Schema asNonStrictSchema()
  {
    if (impl.isStrict())
    {
@@ -2487,6 +2489,28 @@
  /**
   * 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.
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java
@@ -46,6 +46,8 @@
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.*;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException;
import com.forgerock.opendj.util.Base64;
import com.forgerock.opendj.util.Validator;
@@ -155,7 +157,7 @@
    /**
     *{@inheritDoc}
     * {@inheritDoc}
     */
    public String readLine() throws IOException
    {
@@ -209,7 +211,7 @@
    /**
     *{@inheritDoc}
     * {@inheritDoc}
     */
    public String readLine() throws IOException
    {
@@ -226,7 +228,11 @@
  boolean validateSchema = true;
  RejectedRecordListener rejectedRecordListener = RejectedRecordListener.FAIL_FAST;
  Schema schema = Schema.getDefaultSchema().asNonStrictSchema();
  boolean validateSchema = false;
  private final LDIFReaderImpl impl;
@@ -322,8 +328,8 @@
        {
          // The value did not have a valid base64-encoding.
          final LocalizableMessage message = ERR_LDIF_COULD_NOT_BASE64_DECODE_ATTR
              .get(entryDN.toString(), record.lineNumber, ldifLine, e
                  .getMessageObject());
              .get(entryDN.toString(), record.lineNumber, ldifLine,
                  e.getMessageObject());
          throw DecodeException.error(message);
        }
      }
@@ -345,8 +351,9 @@
        catch (final Exception e)
        {
          // The URL was malformed or had an invalid protocol.
          final LocalizableMessage message = ERR_LDIF_INVALID_URL.get(entryDN
              .toString(), record.lineNumber, attrName, String.valueOf(e));
          final LocalizableMessage message = ERR_LDIF_INVALID_URL.get(
              entryDN.toString(), record.lineNumber, attrName,
              String.valueOf(e));
          throw DecodeException.error(message);
        }
@@ -370,9 +377,9 @@
        {
          // We were unable to read the contents of that URL for some
          // reason.
          final LocalizableMessage message = ERR_LDIF_URL_IO_ERROR.get(entryDN
              .toString(), record.lineNumber, attrName, String
              .valueOf(contentURL), String.valueOf(e));
          final LocalizableMessage message = ERR_LDIF_URL_IO_ERROR.get(
              entryDN.toString(), record.lineNumber, attrName,
              String.valueOf(contentURL), String.valueOf(e));
          throw DecodeException.error(message);
        }
        finally
@@ -583,7 +590,8 @@
  final void readLDIFRecordAttributeValue(final LDIFRecord record,
      final String ldifLine, final Entry entry) throws DecodeException
      final String ldifLine, final Entry entry,
      final List<LocalizableMessage> schemaErrors) throws DecodeException
  {
    // Parse the attribute description.
    final int colonPos = parseColonPosition(record, ldifLine);
@@ -594,9 +602,25 @@
    {
      attributeDescription = AttributeDescription.valueOf(attrDescr, schema);
    }
    catch (final UnknownSchemaElementException e)
    {
      final LocalizableMessage message = ERR_LDIF_UNKNOWN_ATTRIBUTE_TYPE.get(
          record.lineNumber, entry.getName().toString(), attrDescr);
      if (validateSchema)
      {
        schemaErrors.add(message);
        return;
      }
      else
      {
        throw DecodeException.error(message);
      }
    }
    catch (final LocalizedIllegalArgumentException e)
    {
      throw DecodeException.error(e.getMessageObject());
      final LocalizableMessage message = ERR_LDIF_MALFORMED_ATTRIBUTE_NAME.get(
          record.lineNumber, entry.getName().toString(), attrDescr);
      throw DecodeException.error(message);
    }
    // Now parse the attribute value.
@@ -617,9 +641,10 @@
    {
      if (validateSchema && attributeDescription.containsOption("binary"))
      {
        final LocalizableMessage message = ERR_LDIF_INVALID_ATTR_OPTION.get(
            entry.getName().toString(), record.lineNumber, attrDescr);
        throw DecodeException.error(message);
        final LocalizableMessage message = ERR_LDIF_UNEXPECTED_BINARY_OPTION
            .get(record.lineNumber, entry.getName().toString(), attrDescr);
        schemaErrors.add(message);
        return;
      }
    }
    else
@@ -636,10 +661,11 @@
        if (!attributeDescription.getAttributeType().getSyntax()
            .valueIsAcceptable(value, invalidReason))
        {
          final LocalizableMessage message = WARN_LDIF_VALUE_VIOLATES_SYNTAX
              .get(entry.getName().toString(), record.lineNumber, value
                  .toString(), attrDescr, invalidReason);
          throw DecodeException.error(message);
          final LocalizableMessage message = WARN_LDIF_MALFORMED_ATTRIBUTE_VALUE
              .get(record.lineNumber, entry.getName().toString(),
                  value.toString(), attrDescr, invalidReason);
          schemaErrors.add(message);
          return;
        }
      }
@@ -654,25 +680,29 @@
        if (!attributeDescription.getAttributeType().getSyntax()
            .valueIsAcceptable(value, invalidReason))
        {
          final LocalizableMessage message = WARN_LDIF_VALUE_VIOLATES_SYNTAX
              .get(entry.getName().toString(), record.lineNumber, value
                  .toString(), attrDescr, invalidReason);
          throw DecodeException.error(message);
          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))
        if (!attribute.add(value) && validateSchema)
        {
          final LocalizableMessage message = WARN_LDIF_DUPLICATE_ATTR.get(entry
              .getName().toString(), record.lineNumber, attrDescr, value
              .toString());
          throw DecodeException.error(message);
          final LocalizableMessage message = WARN_LDIF_DUPLICATE_ATTRIBUTE_VALUE
              .get(record.lineNumber, entry.getName().toString(), attrDescr,
                  value.toString());
          schemaErrors.add(message);
          return;
        }
        if (attributeDescription.getAttributeType().isSingleValue())
        if (validateSchema
            && attributeDescription.getAttributeType().isSingleValue())
        {
          final LocalizableMessage message = ERR_LDIF_MULTIPLE_VALUES_FOR_SINGLE_VALUED_ATTR
              .get(entry.getName().toString(), record.lineNumber, attrDescr);
          throw DecodeException.error(message);
          final LocalizableMessage message = ERR_LDIF_MULTI_VALUED_SINGLE_VALUED_ATTRIBUTE
              .get(record.lineNumber, entry.getName().toString(), attrDescr);
          schemaErrors.add(message);
          return;
        }
      }
      else
@@ -787,15 +817,13 @@
  final String readLDIFRecordKeyValuePair(final LDIFRecord record,
      final KeyValuePair pair, final boolean allowBase64)
      throws DecodeException
  {
    final String ldifLine = record.iterator.next();
    final int colonPos = ldifLine.indexOf(":");
    if (colonPos <= 0)
    {
      final LocalizableMessage message = ERR_LDIF_NO_ATTR_NAME.get(
          record.lineNumber, ldifLine);
      throw DecodeException.error(message);
      pair.key = null;
      return ldifLine;
    }
    pair.key = ldifLine.substring(0, colonPos);
@@ -804,10 +832,8 @@
    final int length = ldifLine.length();
    if (colonPos == length - 1)
    {
      // FIXME: improve error.
      final LocalizableMessage message = LocalizableMessage
          .raw("Malformed changetype attribute");
      throw DecodeException.error(message);
      pair.key = null;
      return ldifLine;
    }
    if (allowBase64 && ldifLine.charAt(colonPos + 1) == ':')
@@ -826,11 +852,8 @@
      }
      catch (final LocalizedIllegalArgumentException e)
      {
        // The value did not have a valid base64-encoding.
        // FIXME: improve error.
        final LocalizableMessage message = LocalizableMessage
            .raw("Malformed base64 changetype attribute");
        throw DecodeException.error(message);
        pair.key = null;
        return ldifLine;
      }
    }
    else
@@ -852,19 +875,29 @@
  final void rejectLDIFRecord(final LDIFRecord record,
  final void handleMalformedRecord(final LDIFRecord record,
      final LocalizableMessage message) throws DecodeException
  {
    // FIXME: not yet implemented.
    throw DecodeException.error(message);
    rejectedRecordListener.handleMalformedRecord(record.lineNumber,
        record.ldifLines, message);
  }
  final void skipLDIFRecord(final LDIFRecord record,
      final LocalizableMessage message)
  final void handleSchemaValidationFailure(final LDIFRecord record,
      final List<LocalizableMessage> messages) throws DecodeException
  {
    // FIXME: not yet implemented.
    rejectedRecordListener.handleSchemaValidationFailure(record.lineNumber,
        record.ldifLines, messages);
  }
  final void handleSkippedRecord(final LDIFRecord record,
      final LocalizableMessage message) throws DecodeException
  {
    rejectedRecordListener.handleSkippedRecord(record.lineNumber,
        record.ldifLines, message);
  }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFStream.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS
 */
package org.forgerock.opendj.ldif;
@@ -39,7 +40,6 @@
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.Matcher;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.Schema;
@@ -57,8 +57,6 @@
  final Set<AttributeDescription> includeAttributes = new HashSet<AttributeDescription>();
  Schema schema = Schema.getDefaultSchema();
  final Set<DN> includeBranches = new HashSet<DN>();
  final Set<DN> excludeBranches = new HashSet<DN>();
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFWriter.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS
 */
package org.forgerock.opendj.ldif;
@@ -39,6 +40,7 @@
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.schema.Schema;
import com.forgerock.opendj.util.Base64;
import com.forgerock.opendj.util.Validator;
@@ -253,6 +255,8 @@
  private final StringBuilder builder = new StringBuilder(80);
  Schema schema = Schema.getDefaultSchema();
  /**
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/ChangeRecordWriter.java
@@ -43,8 +43,6 @@
/**
 * An interface for writing change records to a data source, typically an LDIF
 * file.
 * <p>
 * TODO: FilteredChangeRecordWriter
 */
public interface ChangeRecordWriter extends Closeable, Flushable
{
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/EntryWriter.java
@@ -39,8 +39,6 @@
/**
 * An interface for writing entries to a data source, typically an LDIF file.
 * <p>
 * TODO: FilteredChangeRecordWriter
 */
public interface EntryWriter extends Closeable, Flushable
{
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java
@@ -35,10 +35,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.*;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
@@ -47,6 +44,7 @@
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.UnknownSchemaElementException;
import com.forgerock.opendj.util.Validator;
@@ -335,6 +333,25 @@
  /**
   * Sets the rejected record listener which should be notified whenever a
   * record is skipped, malformed, or fails schema validation.
   * <p>
   * By default the {@link RejectedRecordListener#FAIL_FAST} listener is used.
   *
   * @param listener
   *          The rejected record listener.
   * @return A reference to this {@code LDIFChangeRecordReader}.
   */
  public LDIFChangeRecordReader setRejectedRecordListener(
      final RejectedRecordListener listener)
  {
    this.rejectedRecordListener = listener;
    return this;
  }
  /**
   * Sets the schema which should be used for decoding change records that are
   * read from LDIF. The default schema is used if no other is specified.
   *
@@ -346,7 +363,8 @@
  public LDIFChangeRecordReader setSchema(final Schema schema)
  {
    Validator.ensureNotNull(schema);
    this.schema = schema;
    this.schema = validateSchema ? schema.asStrictSchema() : schema
        .asNonStrictSchema();
    return this;
  }
@@ -354,7 +372,12 @@
  /**
   * Specifies whether or not schema validation should be performed for change
   * records that are read from LDIF. The default is {@code true} .
   * 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.
   * <p>
   * Schema validation is disabled by default.
   *
   * @param validateSchema
   *          {@code true} if schema validation should be performed, or
@@ -364,6 +387,8 @@
  public LDIFChangeRecordReader setValidateSchema(final boolean validateSchema)
  {
    this.validateSchema = validateSchema;
    this.schema = validateSchema ? schema.asStrictSchema() : schema
        .asNonStrictSchema();
    return this;
  }
@@ -374,104 +399,92 @@
  {
    while (nextChangeRecord == null)
    {
      LDIFRecord record = null;
      // Read the set of lines that make up the next entry.
      record = readLDIFRecord();
      final LDIFRecord record = readLDIFRecord();
      if (record == null)
      {
        nextChangeRecord = EOF;
        break;
      }
      // Read the DN of the entry and see if it is one that should be
      // included in the import.
      DN entryDN;
      try
      {
        entryDN = readLDIFRecordDN(record);
        // Read the DN of the entry and see if it is one that should be
        // included in the import.
        final DN entryDN = readLDIFRecordDN(record);
        if (entryDN == null)
        {
          // Skip version record.
          continue;
        }
      }
      catch (final DecodeException e)
      {
        rejectLDIFRecord(record, e.getMessageObject());
        continue;
      }
      // Skip if branch containing the entry DN is excluded.
      if (isBranchExcluded(entryDN))
      {
        final LocalizableMessage message = LocalizableMessage
            .raw("Skipping entry because it is in excluded branch");
        skipLDIFRecord(record, message);
        continue;
      }
        // Skip if branch containing the entry DN is excluded.
        if (isBranchExcluded(entryDN))
        {
          final LocalizableMessage message = ERR_LDIF_CHANGE_EXCLUDED_BY_DN
              .get(record.lineNumber, entryDN.toString());
          handleSkippedRecord(record, message);
          continue;
        }
      ChangeRecord changeRecord = null;
      try
      {
        if (!record.iterator.hasNext())
        {
          // FIXME: improve error.
          final LocalizableMessage message = LocalizableMessage
              .raw("Missing changetype");
          final LocalizableMessage message = ERR_LDIF_NO_CHANGE_TYPE.get(
              record.lineNumber, entryDN.toString());
          throw DecodeException.error(message);
        }
        final KeyValuePair pair = new KeyValuePair();
        final String ldifLine = readLDIFRecordKeyValuePair(record, pair, false);
        if (pair.key == null)
        {
          final LocalizableMessage message = ERR_LDIF_MALFORMED_CHANGE_TYPE
              .get(record.lineNumber, entryDN.toString(), ldifLine);
          throw DecodeException.error(message);
        }
        if (!toLowerCase(pair.key).equals("changetype"))
        {
          // Default to add change record.
          changeRecord = parseAddChangeRecordEntry(entryDN, ldifLine, record);
          nextChangeRecord = parseAddChangeRecordEntry(entryDN, ldifLine,
              record);
        }
        else
        {
          final String changeType = toLowerCase(pair.value);
          if (changeType.equals("add"))
          {
            changeRecord = parseAddChangeRecordEntry(entryDN, null, record);
            nextChangeRecord = parseAddChangeRecordEntry(entryDN, null, record);
          }
          else if (changeType.equals("delete"))
          {
            changeRecord = parseDeleteChangeRecordEntry(entryDN, record);
            nextChangeRecord = parseDeleteChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("modify"))
          {
            changeRecord = parseModifyChangeRecordEntry(entryDN, record);
            nextChangeRecord = parseModifyChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("modrdn"))
          {
            changeRecord = parseModifyDNChangeRecordEntry(entryDN, record);
            nextChangeRecord = parseModifyDNChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("moddn"))
          {
            changeRecord = parseModifyDNChangeRecordEntry(entryDN, record);
            nextChangeRecord = parseModifyDNChangeRecordEntry(entryDN, record);
          }
          else
          {
            // FIXME: improve error.
            final LocalizableMessage message = ERR_LDIF_INVALID_CHANGETYPE_ATTRIBUTE
                .get(pair.value, "add, delete, modify, moddn, modrdn");
            final LocalizableMessage message = ERR_LDIF_BAD_CHANGE_TYPE.get(
                record.lineNumber, entryDN.toString(), pair.value);
            throw DecodeException.error(message);
          }
        }
      }
      catch (final DecodeException e)
      {
        rejectLDIFRecord(record, e.getMessageObject());
        handleMalformedRecord(record, e.getMessageObject());
        continue;
      }
      if (changeRecord != null)
      {
        nextChangeRecord = changeRecord;
      }
    }
    return nextChangeRecord;
  }
@@ -484,17 +497,24 @@
  {
    // Use an Entry for the AttributeSequence.
    final Entry entry = new LinkedHashMapEntry(entryDN);
    final List<LocalizableMessage> schemaErrors = new LinkedList<LocalizableMessage>();
    if (lastLDIFLine != null)
    {
      // This line was read when looking for the change type.
      readLDIFRecordAttributeValue(record, lastLDIFLine, entry);
      readLDIFRecordAttributeValue(record, lastLDIFLine, entry, schemaErrors);
    }
    while (record.iterator.hasNext())
    {
      final String ldifLine = record.iterator.next();
      readLDIFRecordAttributeValue(record, ldifLine, entry);
      readLDIFRecordAttributeValue(record, ldifLine, entry, schemaErrors);
    }
    if (!schemaErrors.isEmpty())
    {
      handleSchemaValidationFailure(record, schemaErrors);
      return null;
    }
    return Requests.newAddRequest(entry);
@@ -507,9 +527,8 @@
  {
    if (record.iterator.hasNext())
    {
      // FIXME: include line number in error.
      final LocalizableMessage message = ERR_LDIF_INVALID_DELETE_ATTRIBUTES
          .get();
      final LocalizableMessage message = ERR_LDIF_MALFORMED_DELETE.get(
          record.lineNumber, entryDN.toString());
      throw DecodeException.error(message);
    }
@@ -525,10 +544,18 @@
    final KeyValuePair pair = new KeyValuePair();
    final List<ByteString> attributeValues = new ArrayList<ByteString>();
    final List<LocalizableMessage> schemaErrors = new LinkedList<LocalizableMessage>();
    while (record.iterator.hasNext())
    {
      readLDIFRecordKeyValuePair(record, pair, false);
      String ldifLine = readLDIFRecordKeyValuePair(record, pair, false);
      if (pair.key == null)
      {
        final LocalizableMessage message = ERR_LDIF_MALFORMED_MODIFICATION_TYPE
            .get(record.lineNumber, entryDN.toString(), ldifLine);
        throw DecodeException.error(message);
      }
      final String changeType = toLowerCase(pair.key);
      ModificationType modType;
@@ -550,9 +577,8 @@
      }
      else
      {
        // FIXME: improve error.
        final LocalizableMessage message = ERR_LDIF_INVALID_MODIFY_ATTRIBUTE
            .get(pair.key, "add, delete, replace, increment");
        final LocalizableMessage message = ERR_LDIF_BAD_MODIFICATION_TYPE.get(
            record.lineNumber, entryDN.toString(), pair.key);
        throw DecodeException.error(message);
      }
@@ -561,9 +587,25 @@
      {
        attributeDescription = AttributeDescription.valueOf(pair.value, schema);
      }
      catch (final UnknownSchemaElementException e)
      {
        final LocalizableMessage message = ERR_LDIF_UNKNOWN_ATTRIBUTE_TYPE.get(
            record.lineNumber, entryDN.toString(), pair.value);
        if (validateSchema)
        {
          schemaErrors.add(message);
          continue;
        }
        else
        {
          throw DecodeException.error(message);
        }
      }
      catch (final LocalizedIllegalArgumentException e)
      {
        throw DecodeException.error(e.getMessageObject());
        final LocalizableMessage message = ERR_LDIF_MALFORMED_ATTRIBUTE_NAME
            .get(record.lineNumber, entryDN.toString(), pair.value);
        throw DecodeException.error(message);
      }
      // Skip the attribute if requested before performing any schema
@@ -580,9 +622,10 @@
      {
        if (validateSchema && attributeDescription.containsOption("binary"))
        {
          final LocalizableMessage message = ERR_LDIF_INVALID_ATTR_OPTION.get(
              entryDN.toString(), record.lineNumber, pair.value);
          throw DecodeException.error(message);
          final LocalizableMessage message = ERR_LDIF_UNEXPECTED_BINARY_OPTION
              .get(record.lineNumber, entryDN.toString(), pair.value);
          schemaErrors.add(message);
          continue;
        }
      }
      else
@@ -595,7 +638,7 @@
      attributeValues.clear();
      while (record.iterator.hasNext())
      {
        final String ldifLine = record.iterator.next();
        ldifLine = record.iterator.next();
        if (ldifLine.equals("-"))
        {
          break;
@@ -613,7 +656,11 @@
        }
        catch (final LocalizedIllegalArgumentException e)
        {
          throw DecodeException.error(e.getMessageObject());
          // No need to catch schema exception here because it implies that the
          // attribute name is wrong.
          final LocalizableMessage message = ERR_LDIF_MALFORMED_ATTRIBUTE_NAME
              .get(record.lineNumber, entryDN.toString(), attrDescr);
          throw DecodeException.error(message);
        }
        // Ensure that the binary option is present if required.
@@ -625,9 +672,9 @@
        if (!attributeDescription2.equals(attributeDescription))
        {
          // TODO: include line number.
          final LocalizableMessage message = ERR_LDIF_INVALID_CHANGERECORD_ATTRIBUTE
              .get(attributeDescription2.toString(),
          final LocalizableMessage message = ERR_LDIF_ATTRIBUTE_NAME_MISMATCH
              .get(record.lineNumber, entryDN.toString(),
                  attributeDescription2.toString(),
                  attributeDescription.toString());
          throw DecodeException.error(message);
        }
@@ -642,6 +689,12 @@
      modifyRequest.addModification(change);
    }
    if (!schemaErrors.isEmpty())
    {
      handleSchemaValidationFailure(record, schemaErrors);
      return null;
    }
    return modifyRequest;
  }
@@ -655,19 +708,18 @@
    // Parse the newrdn.
    if (!record.iterator.hasNext())
    {
      // TODO: include line number.
      final LocalizableMessage message = ERR_LDIF_NO_MOD_DN_ATTRIBUTES.get();
      final LocalizableMessage message = ERR_LDIF_NO_NEW_RDN.get(
          record.lineNumber, entryDN.toString());
      throw DecodeException.error(message);
    }
    final KeyValuePair pair = new KeyValuePair();
    String ldifLine = record.iterator.next();
    readLDIFRecordKeyValuePair(record, pair, true);
    if (!toLowerCase(pair.key).equals("newrdn"))
    String ldifLine = readLDIFRecordKeyValuePair(record, pair, true);
    if (pair.key == null || !toLowerCase(pair.key).equals("newrdn"))
    {
      // FIXME: improve error.
      final LocalizableMessage message = LocalizableMessage
          .raw("Missing newrdn");
      final LocalizableMessage message = ERR_LDIF_MALFORMED_NEW_RDN.get(
          record.lineNumber, entryDN.toString(), ldifLine);
      throw DecodeException.error(message);
    }
@@ -678,27 +730,24 @@
    }
    catch (final LocalizedIllegalArgumentException e)
    {
      final LocalizableMessage message = ERR_LDIF_INVALID_DN.get(
          record.lineNumber, ldifLine, e.getMessageObject());
      final LocalizableMessage message = ERR_LDIF_MALFORMED_NEW_RDN.get(
          record.lineNumber, entryDN.toString(), pair.value);
      throw DecodeException.error(message);
    }
    // Parse the deleteoldrdn.
    if (!record.iterator.hasNext())
    {
      // TODO: include line number.
      final LocalizableMessage message = ERR_LDIF_NO_DELETE_OLDRDN_ATTRIBUTE
          .get();
      final LocalizableMessage message = ERR_LDIF_NO_DELETE_OLD_RDN.get(
          record.lineNumber, entryDN.toString());
      throw DecodeException.error(message);
    }
    ldifLine = record.iterator.next();
    readLDIFRecordKeyValuePair(record, pair, true);
    if (!toLowerCase(pair.key).equals("deleteoldrdn"))
    ldifLine = readLDIFRecordKeyValuePair(record, pair, true);
    if (pair.key == null || !toLowerCase(pair.key).equals("deleteoldrdn"))
    {
      // FIXME: improve error.
      final LocalizableMessage message = LocalizableMessage
          .raw("Missing deleteoldrdn");
      final LocalizableMessage message = ERR_LDIF_MALFORMED_DELETE_OLD_RDN.get(
          record.lineNumber, entryDN.toString(), ldifLine);
      throw DecodeException.error(message);
    }
@@ -714,22 +763,19 @@
    }
    else
    {
      // FIXME: improve error.
      final LocalizableMessage message = ERR_LDIF_INVALID_DELETE_OLDRDN_ATTRIBUTE
          .get(pair.value);
      final LocalizableMessage message = ERR_LDIF_MALFORMED_DELETE_OLD_RDN.get(
          record.lineNumber, entryDN.toString(), pair.value);
      throw DecodeException.error(message);
    }
    // Parse the newsuperior if present.
    if (record.iterator.hasNext())
    {
      ldifLine = record.iterator.next();
      readLDIFRecordKeyValuePair(record, pair, true);
      if (!toLowerCase(pair.key).equals("newsuperior"))
      ldifLine = readLDIFRecordKeyValuePair(record, pair, true);
      if (pair.key == null || !toLowerCase(pair.key).equals("newsuperior"))
      {
        // FIXME: improve error.
        final LocalizableMessage message = LocalizableMessage
            .raw("Missing newsuperior");
        final LocalizableMessage message = ERR_LDIF_MALFORMED_NEW_SUPERIOR.get(
            record.lineNumber, entryDN.toString(), ldifLine);
        throw DecodeException.error(message);
      }
@@ -740,8 +786,8 @@
      }
      catch (final LocalizedIllegalArgumentException e)
      {
        final LocalizableMessage message = ERR_LDIF_INVALID_DN.get(
            record.lineNumber, ldifLine, e.getMessageObject());
        final LocalizableMessage message = ERR_LDIF_MALFORMED_NEW_SUPERIOR.get(
            record.lineNumber, entryDN.toString(), pair.value);
        throw DecodeException.error(message);
      }
    }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFEntryReader.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS
 */
package org.forgerock.opendj.ldif;
@@ -34,6 +35,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
@@ -361,6 +363,25 @@
  /**
   * Sets the rejected record listener which should be notified whenever a
   * record is skipped, malformed, or fails schema validation.
   * <p>
   * By default the {@link RejectedRecordListener#FAIL_FAST} listener is used.
   *
   * @param listener
   *          The rejected record listener.
   * @return A reference to this {@code LDIFEntryReader}.
   */
  public LDIFEntryReader setRejectedRecordListener(
      final RejectedRecordListener listener)
  {
    this.rejectedRecordListener = listener;
    return this;
  }
  /**
   * Sets the schema which should be used for decoding entries that are read
   * from LDIF. The default schema is used if no other is specified.
   *
@@ -372,7 +393,8 @@
  public LDIFEntryReader setSchema(final Schema schema)
  {
    Validator.ensureNotNull(schema);
    this.schema = schema;
    this.schema = validateSchema ? schema.asStrictSchema() : schema
        .asNonStrictSchema();
    return this;
  }
@@ -380,7 +402,12 @@
  /**
   * Specifies whether or not schema validation should be performed for entries
   * that are read from LDIF. The default is {@code true}.
   * 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.
   * <p>
   * Schema validation is disabled by default.
   *
   * @param validateSchema
   *          {@code true} if schema validation should be performed, or
@@ -390,6 +417,8 @@
  public LDIFEntryReader setValidateSchema(final boolean validateSchema)
  {
    this.validateSchema = validateSchema;
    this.schema = validateSchema ? schema.asStrictSchema() : schema
        .asNonStrictSchema();
    return this;
  }
@@ -399,69 +428,65 @@
  {
    while (nextEntry == null)
    {
      LDIFRecord record = null;
      // Read the set of lines that make up the next entry.
      record = readLDIFRecord();
      final LDIFRecord record = readLDIFRecord();
      if (record == null)
      {
        nextEntry = EOF;
        break;
      }
      // Read the DN of the entry and see if it is one that should be
      // included in the import.
      DN entryDN;
      try
      {
        entryDN = readLDIFRecordDN(record);
        // Read the DN of the entry and see if it is one that should be
        // included in the import.
        final DN entryDN = readLDIFRecordDN(record);
        if (entryDN == null)
        {
          // Skip version record.
          continue;
        }
      }
      catch (final DecodeException e)
      {
        rejectLDIFRecord(record, e.getMessageObject());
        continue;
      }
      // Skip if branch containing the entry DN is excluded.
      if (isBranchExcluded(entryDN))
      {
        final LocalizableMessage message = LocalizableMessage
            .raw("Skipping entry because it is in excluded branch");
        skipLDIFRecord(record, message);
        continue;
      }
        // Skip if branch containing the entry DN is excluded.
        if (isBranchExcluded(entryDN))
        {
          final LocalizableMessage message = ERR_LDIF_ENTRY_EXCLUDED_BY_DN.get(
              record.lineNumber, entryDN.toString());
          handleSkippedRecord(record, message);
          continue;
        }
      // Use an Entry for the AttributeSequence.
      final Entry entry = new LinkedHashMapEntry(entryDN);
      try
      {
        // Use an Entry for the AttributeSequence.
        final Entry entry = new LinkedHashMapEntry(entryDN);
        final List<LocalizableMessage> schemaErrors = new LinkedList<LocalizableMessage>();
        while (record.iterator.hasNext())
        {
          final String ldifLine = record.iterator.next();
          readLDIFRecordAttributeValue(record, ldifLine, entry);
          readLDIFRecordAttributeValue(record, ldifLine, entry, schemaErrors);
        }
        // Skip if the entry is excluded by any filters.
        if (isEntryExcluded(entry))
        {
          final LocalizableMessage message = ERR_LDIF_ENTRY_EXCLUDED_BY_FILTER
              .get(record.lineNumber, entryDN.toString());
          handleSkippedRecord(record, message);
          continue;
        }
        if (!schemaErrors.isEmpty())
        {
          handleSchemaValidationFailure(record, schemaErrors);
          continue;
        }
        nextEntry = entry;
      }
      catch (final DecodeException e)
      {
        rejectLDIFRecord(record, e.getMessageObject());
        handleMalformedRecord(record, e.getMessageObject());
        continue;
      }
      // Skip if the entry is excluded by any filters.
      if (isEntryExcluded(entry))
      {
        final LocalizableMessage message = LocalizableMessage
            .raw("Skipping entry due to exclusing filters");
        skipLDIFRecord(record, message);
        continue;
      }
      nextEntry = entry;
    }
    return nextEntry;
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/RejectedRecordListener.java
New file
@@ -0,0 +1,141 @@
/*
 * 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.ldif;
import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.DecodeException;
/**
 * A listener interface which is notified whenever records are skipped,
 * malformed, or fail schema validation.
 */
public interface RejectedRecordListener
{
  /**
   * The default handler which ignores skipped records but which terminates
   * processing by throwing a {@code DecodeException} as soon as a record is
   * found to be malformed or rejected due to a schema validation failure.
   */
  public static final RejectedRecordListener FAIL_FAST = new RejectedRecordListener()
  {
    @Override
    public void handleMalformedRecord(final long lineNumber,
        final List<String> ldifRecord, final LocalizableMessage reason)
        throws DecodeException
    {
      // Fail fast.
      throw DecodeException.error(reason);
    }
    @Override
    public void handleSchemaValidationFailure(final long lineNumber,
        final List<String> ldifRecord, final List<LocalizableMessage> reasons)
        throws DecodeException
    {
      // Fail fast - just use first message.
      throw DecodeException.error(reasons.get(0));
    }
    @Override
    public void handleSkippedRecord(final long lineNumber,
        final List<String> ldifRecord, final LocalizableMessage reason)
        throws DecodeException
    {
      // Ignore skipped records.
    }
  };
  /**
   * Invoked when a record was rejected because it was malformed in some way and
   * could not be decoded.
   *
   * @param lineNumber
   *          The line number within the source location in which the malformed
   *          record is located, if known, otherwise {@code -1}.
   * @param ldifRecord
   *          An LDIF representation of the malformed record.
   * @param reason
   *          The reason why the record is malformed.
   * @throws DecodeException
   *           If processing should terminate.
   */
  void handleMalformedRecord(long lineNumber, List<String> ldifRecord,
      LocalizableMessage reason) throws DecodeException;
  /**
   * Invoked when a record was rejected because it does not conform to the
   * schema and schema validation is enabled.
   *
   * @param lineNumber
   *          The line number within the source location in which the rejected
   *          record is located, if known, otherwise {@code -1}.
   * @param ldifRecord
   *          An LDIF representation of the record which failed schema
   *          validation.
   * @param reasons
   *          The reasons why the record failed schema validation.
   * @throws DecodeException
   *           If processing should terminate.
   */
  void handleSchemaValidationFailure(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.
   *
   * @param lineNumber
   *          The line number within the source location in which the skipped
   *          record is located, if known, otherwise {@code -1}.
   * @param ldifRecord
   *          An LDIF representation of the skipped record.
   * @param reason
   *          The reason why the record was skipped.
   * @throws DecodeException
   *           If processing should terminate.
   */
  void handleSkippedRecord(long lineNumber, List<String> ldifRecord,
      LocalizableMessage reason) throws DecodeException;
}
opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
@@ -477,8 +477,6 @@
 failed and will be removed from the schema: %s
ERR_NO_SUBSCHEMA_SUBENTRY_ATTR=The entry %s does not include \
 a subschemaSubentry attribute 
ERR_ATTRIBUTE_DESCRIPTION_TYPE_NOT_FOUND=The attribute description "%s" \
 could not be parsed due to the following reason: %s
ERR_RDN_TYPE_NOT_FOUND=The RDN "%s" could not be parsed due to the \
 following reason: %s
ERR_DN_TYPE_NOT_FOUND=The DN "%s" could not be parsed due to the \
@@ -880,34 +878,12 @@
ERR_LDIF_COULD_NOT_BASE64_DECODE_ATTR=Unable to parse LDIF entry %s \
 starting at line %d because it was not possible to base64-decode the \
 attribute on line "%s":  %s
WARN_LDIF_DUPLICATE_ATTR=Entry %s read from LDIF starting at line %d \
 includes a duplicate attribute %s with value %s.  The second occurrence of \
 that attribute value has been skipped
ERR_LDIF_MULTIPLE_VALUES_FOR_SINGLE_VALUED_ATTR=Entry %s starting at \
 line %d includes multiple values for single-valued attribute %s
ERR_LDIF_INVALID_URL=Unable to parse LDIF entry %s starting at line \
 %d because the value of attribute %s was to be read from a URL but the URL \
 was invalid:  %s
ERR_LDIF_URL_IO_ERROR=Unable to parse LDIF entry %s starting at line \
 %d because the value of attribute %s was to be read from URL %s but an error \
 occurred while trying to read that content:  %s
ERR_LDIF_INVALID_DELETE_ATTRIBUTES=Error in the LDIF change record \
 entry. Invalid attributes specified for the delete operation
ERR_LDIF_NO_MOD_DN_ATTRIBUTES=Error in the LDIF change record \
 entry. No attributes specified for the mod DN operation
ERR_LDIF_NO_DELETE_OLDRDN_ATTRIBUTE=Error in the LDIF change record \
 entry. No delete old RDN attribute specified for the mod DN operation
ERR_LDIF_INVALID_DELETE_OLDRDN_ATTRIBUTE=Error in the LDIF change \
 record entry. Invalid value "%s" for the delete old RDN attribute specified \
 for the mod DN operation
ERR_LDIF_INVALID_CHANGERECORD_ATTRIBUTE=Error in the LDIF change \
 record entry. Invalid attribute "%s" specified. Expecting attribute "%s"
ERR_LDIF_INVALID_MODIFY_ATTRIBUTE=Error in the LDIF change record \
 entry. Invalid attribute "%s" specified. Expecting one of the following \
 attributes "%s"
ERR_LDIF_INVALID_CHANGETYPE_ATTRIBUTE=Error in the LDIF change \
 record entry. Invalid value "%s" for the changetype specified. Expecting one \
 of the following values "%s"
ERR_LDAPURL_NO_SCHEME=The provided string "%s" cannot be decoded \
 as an LDAP URL because it does not contain a protocol scheme
ERR_LDAPURL_BAD_SCHEME=The provided string "%s" cannot be decoded \
@@ -930,11 +906,6 @@
ERR_LDAPURL_INVALID_HEX_BYTE=The provided URL component "%s" could \
 not be decoded because the character at byte %d was not a valid hexadecimal \
 digit
WARN_LDIF_VALUE_VIOLATES_SYNTAX=Entry %s read from LDIF starting at \
 line %d includes value "%s" for attribute %s that is invalid according to the \
 associated syntax:  %s
ERR_LDIF_INVALID_ATTR_OPTION=Unable to parse LDIF entry %s starting \
 at line %d because it has an invalid binary option for attribute %s
ERR_INVALID_ESCAPE_CHAR=The value %s cannot be decoded because %c \
 is not a valid escape character
WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND=The provided LDIF \
@@ -1303,3 +1274,58 @@
 the optional attribute "%s" which is not defined in the schema
WARN_ATTR_SYNTAX_NOT_IMPLEMENTED1=The "%s" syntax with OID %s is not \
 implemented. It will be substituted by the default syntax with OID %s
ERR_LDIF_ENTRY_EXCLUDED_BY_DN=Skipping LDIF entry starting at line %d with \
 distinguished name "%s" because it is within an excluded branch
ERR_LDIF_ENTRY_EXCLUDED_BY_FILTER=Skipping LDIF entry starting at line %d with \
 distinguished name "%s" because it does not meet the filter criteria
ERR_LDIF_CHANGE_EXCLUDED_BY_DN=Skipping LDIF change record starting at line %d \
 with distinguished name "%s" because it is within an excluded branch
ERR_LDIF_NO_CHANGE_TYPE=Unable to parse LDIF change record starting at line %d \
 with distinguished name "%s" because there was no change type
ERR_LDIF_MALFORMED_CHANGE_TYPE=Unable to parse LDIF change record starting at line %d \
 with distinguished name "%s" because it contained a malformed changetype \
 "%s"
ERR_LDIF_BAD_CHANGE_TYPE=Unable to parse LDIF change record starting at line %d \
 with distinguished name "%s" because it contained an unrecognized changetype \
 "%s". The changetype must be one of add, delete, modify, modrdn, or moddn
ERR_LDIF_MALFORMED_DELETE=Unable to parse LDIF delete record starting at line %d \
 with distinguished name "%s" because it contained additional lines after the \
 changetype when none were expected
ERR_LDIF_BAD_MODIFICATION_TYPE=Unable to parse LDIF modify record starting at line %d \
 with distinguished name "%s" because it contained an unrecognized modification type \
 "%s". The modification type must be one of add, delete, replace, or increment
ERR_LDIF_MALFORMED_MODIFICATION_TYPE=Unable to parse LDIF modify record starting at line %d \
 with distinguished name "%s" because it contained a malformed modification type \
 "%s"
ERR_LDIF_UNKNOWN_ATTRIBUTE_TYPE=Rejecting the LDIF change record \
 starting at line %d with distinguished name "%s" because it contains an \
 unrecognized attribute type "%s"
ERR_LDIF_MALFORMED_ATTRIBUTE_NAME=Unable to parse LDIF change record \
 starting at line %d with distinguished name "%s" because it contained a \
 malformed attribute description "%s"
ERR_LDIF_UNEXPECTED_BINARY_OPTION=Unable to parse LDIF change record starting \
 at line %d with distinguished name "%s" because it has an unexpected binary \
 option for attribute %s
ERR_LDIF_ATTRIBUTE_NAME_MISMATCH=Unable to parse LDIF change record \
 starting at line %d with distinguished name "%s" because it contained a \
 an unexpected attribute "%s" when "%s" was expected
WARN_LDIF_MALFORMED_ATTRIBUTE_VALUE=Rejecting the LDIF change record \
 starting at line %d with distinguished name "%s" because it includes the \
 value "%s" for attribute "%s" which is invalid according to the syntax: %s
WARN_LDIF_DUPLICATE_ATTRIBUTE_VALUE=Rejecting the LDIF change record \
 starting at line %d with distinguished name "%s" because it includes a \
 duplicate attribute "%s" with value "%s"
ERR_LDIF_MULTI_VALUED_SINGLE_VALUED_ATTRIBUTE=Rejecting the LDIF change record \
 starting at line %d with distinguished name "%s" because it includes multiple \
 values for single-valued attribute "%s"
ERR_LDIF_NO_NEW_RDN=Unable to parse LDIF modify DN record starting at line %d \
 with distinguished name "%s" because there was no new RDN
ERR_LDIF_MALFORMED_NEW_RDN=Unable to parse LDIF modify DN record starting at line %d \
 with distinguished name "%s" because it contained a malformed new RDN "%s"
ERR_LDIF_NO_DELETE_OLD_RDN=Unable to parse LDIF modify DN record starting at line %d \
 with distinguished name "%s" because there was no deleteoldrdn field
ERR_LDIF_MALFORMED_DELETE_OLD_RDN=Unable to parse LDIF modify DN record starting at line %d \
 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"
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionFactoryTestCase.java
@@ -345,7 +345,7 @@
                  + "caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch "
                  + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )",
              false);
      final Schema testSchema = builder.toSchema().nonStrict();
      final Schema testSchema = builder.toSchema().asNonStrictSchema();
      assertThat(testSchema.getWarnings()).isEmpty();
      Schema.setDefaultSchema(testSchema);
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/schema/SchemaCompatOptionsTest.java
@@ -87,7 +87,7 @@
    SchemaBuilder builder = new SchemaBuilder(Schema.getCoreSchema())
        .setSchemaCompatOptions(SchemaCompatOptions.defaultOptions()
            .allowMalformedNamesAndOptions(allowIllegalCharacters));
    Schema schema = builder.toSchema().nonStrict();
    Schema schema = builder.toSchema().asNonStrictSchema();
    AttributeDescription.valueOf(atd, schema);
  }
@@ -136,7 +136,7 @@
    SchemaBuilder builder = new SchemaBuilder(Schema.getCoreSchema())
        .setSchemaCompatOptions(SchemaCompatOptions.defaultOptions()
            .allowMalformedNamesAndOptions(allowIllegalCharacters));
    Schema schema = builder.toSchema().nonStrict();
    Schema schema = builder.toSchema().asNonStrictSchema();
    AttributeDescription.valueOf(atd, schema);
  }
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java
New file
@@ -0,0 +1,471 @@
/*
 * 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.ldif;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Iterator;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.*;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.testng.annotations.Test;
/**
 * This class tests the LDIFChangeRecordReader functionality.
 */
public final class LDIFChangeRecordReaderTestCase extends LDIFTestCase
{
  /**
   * Tests reading a valid add change record with a changetype.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testReadAddRecordWithChangeType() throws Exception
  {
    // @formatter:off
    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
        "dn: dc=example,dc=com",
        "changetype: add",
        "objectClass: top",
        "objectClass: domainComponent",
        "dc: example"
        );
    // @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);
  }
  /**
   * Tests reading a valid add change record without a changetype.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testReadAddRecordWithoutChangeType() throws Exception
  {
    // @formatter:off
    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
        "dn: dc=example,dc=com",
        "objectClass: top",
        "objectClass: domainComponent",
        "dc: example"
        );
    // @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);
  }
  /**
   * Tests reading a valid modify change record.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testReadModifyRecord() throws Exception
  {
    // @formatter:off
    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
        "dn: dc=example,dc=com",
        "changetype: modify",
        "add: description",
        "-",
        "add: description",
        "description: value1",
        "-",
        "add: description",
        "description: value1",
        "description: value2",
        "-",
        "delete: description",
        "-",
        "delete: description",
        "description: value1",
        "-",
        "delete: description",
        "description: value1",
        "description: value2",
        "-",
        "replace: description",
        "-",
        "replace: description",
        "description: value1",
        "-",
        "replace: description",
        "description: value1",
        "description: value2",
        "-",
        "increment: description",
        "description: 1"
        );
    // @formatter:on
    assertThat(reader.hasNext()).isTrue();
    ChangeRecord record = reader.readChangeRecord();
    assertThat(record).isInstanceOf(ModifyRequest.class);
    ModifyRequest modifyRequest = (ModifyRequest) record;
    assertThat((Object) modifyRequest.getName()).isEqualTo(
        DN.valueOf("dc=example,dc=com"));
    Iterator<Modification> changes = modifyRequest.getModifications()
        .iterator();
    Modification modification;
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.ADD);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.ADD);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description", "value1"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.ADD);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description", "value1", "value2"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.DELETE);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.DELETE);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description", "value1"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.DELETE);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description", "value1", "value2"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.REPLACE);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.REPLACE);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description", "value1"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.REPLACE);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description", "value1", "value2"));
    modification = changes.next();
    assertThat(modification.getModificationType()).isEqualTo(
        ModificationType.INCREMENT);
    assertThat(modification.getAttribute()).isEqualTo(
        new LinkedAttribute("description", "1"));
    assertThat(changes.hasNext()).isFalse();
  }
  /**
   * Tests reading a valid delete change record.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testReadDeleteRecord() throws Exception
  {
    // @formatter:off
    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
        "dn: dc=example,dc=com",
        "changetype: delete"
        );
    // @formatter:on
    assertThat(reader.hasNext()).isTrue();
    ChangeRecord record = reader.readChangeRecord();
    assertThat(record).isInstanceOf(DeleteRequest.class);
    DeleteRequest deleteRequest = (DeleteRequest) record;
    assertThat((Object) deleteRequest.getName()).isEqualTo(
        DN.valueOf("dc=example,dc=com"));
  }
  /**
   * Tests reading a valid moddn change record.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testReadModdnRecordWithoutNewSuperior() throws Exception
  {
    // @formatter:off
    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
        "dn: dc=example,dc=com",
        "changetype: moddn",
        "newrdn: dc=eggsample",
        "deleteoldrdn: true"
        );
    // @formatter:on
    assertThat(reader.hasNext()).isTrue();
    ChangeRecord record = reader.readChangeRecord();
    assertThat(record).isInstanceOf(ModifyDNRequest.class);
    ModifyDNRequest modifyDNRequest = (ModifyDNRequest) record;
    assertThat((Object) modifyDNRequest.getName()).isEqualTo(
        DN.valueOf("dc=example,dc=com"));
    assertThat((Object) modifyDNRequest.getNewRDN()).isEqualTo(
        RDN.valueOf("dc=eggsample"));
    assertThat(modifyDNRequest.isDeleteOldRDN()).isTrue();
    assertThat(modifyDNRequest.getNewSuperior()).isNull();
  }
  /**
   * Tests reading a valid moddn change record.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testReadModdnRecordWithNewSuperior() throws Exception
  {
    // @formatter:off
    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
        "dn: dc=example,dc=com",
        "changetype: moddn",
        "newrdn: dc=eggsample",
        "deleteoldrdn: true",
        "newsuperior: dc=org"
        );
    // @formatter:on
    assertThat(reader.hasNext()).isTrue();
    ChangeRecord record = reader.readChangeRecord();
    assertThat(record).isInstanceOf(ModifyDNRequest.class);
    ModifyDNRequest modifyDNRequest = (ModifyDNRequest) record;
    assertThat((Object) modifyDNRequest.getName()).isEqualTo(
        DN.valueOf("dc=example,dc=com"));
    assertThat((Object) modifyDNRequest.getNewRDN()).isEqualTo(
        RDN.valueOf("dc=eggsample"));
    assertThat(modifyDNRequest.isDeleteOldRDN()).isTrue();
    assertThat((Object) modifyDNRequest.getNewSuperior()).isEqualTo(
        DN.valueOf("dc=org"));
  }
  /**
   * Tests reading a malformed record invokes the rejected record listener.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testRejectedRecordListenerMalformedFirstRecord() throws Exception
  {
    RejectedRecordListener listener = mock(RejectedRecordListener.class);
    // @formatter:off
    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
        "dn: baddn",
        "changetype: add",
        "objectClass: top",
        "objectClass: domainComponent",
        "dc: example"
        ).setRejectedRecordListener(listener);
    // @formatter:on
    assertThat(reader.hasNext()).isFalse();
    verify(listener).handleMalformedRecord(
        eq(1L),
        eq(Arrays.asList("dn: baddn", "changetype: add", "objectClass: top",
            "objectClass: domainComponent", "dc: example")),
        any(LocalizableMessage.class));
  }
  /**
   * Tests reading a malformed record invokes the rejected record listener.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testRejectedRecordListenerMalformedSecondRecord()
      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",
        "",
        "dn: baddn",
        "changetype: add",
        "objectClass: top",
        "objectClass: domainComponent",
        "dc: example"
        ).setRejectedRecordListener(listener);
    // @formatter:on
    reader.readChangeRecord(); // Skip good record.
    assertThat(reader.hasNext()).isFalse();
    verify(listener).handleMalformedRecord(
        eq(7L),
        eq(Arrays.asList("dn: baddn", "changetype: add", "objectClass: top",
            "objectClass: domainComponent", "dc: example")),
        any(LocalizableMessage.class));
  }
  /**
   * Tests reading a skipped record invokes the rejected record listener.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testRejectedRecordListenerSkipsRecord() 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"
        ).setRejectedRecordListener(listener).setExcludeBranch(DN.valueOf("dc=com"));
    // @formatter:on
    assertThat(reader.hasNext()).isFalse();
    verify(listener)
        .handleSkippedRecord(
            eq(1L),
            eq(Arrays.asList("dn: dc=example,dc=com", "changetype: add",
                "objectClass: top", "objectClass: domainComponent",
                "dc: example")), any(LocalizableMessage.class));
  }
  /**
   * Tests reading a record which does not conform to the schema invokes the
   * rejected record listener.
   *
   * @throws Exception
   *           if an unexpected error occurred.
   */
  @Test
  public void testRejectedRecordListenerRejectsBadSchemaRecord()
      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).setValidateSchema(true);
    // @formatter:on
    assertThat(reader.hasNext()).isFalse();
    verify(listener).handleSchemaValidationFailure(
        eq(1L),
        eq(Arrays.asList("dn: dc=example,dc=com", "changetype: add",
            "objectClass: top", "objectClass: domainComponent", "dc: example",
            "xxx: unknown attribute")), anyListOf(LocalizableMessage.class));
  }
}