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

matthew_swift
09.31.2010 d9722bcadc7bf619808426fc82cbb0c74b1646b0
Make EntryReader and ChangeRecordReader APIs easier to use:

* provide hasNext() method for determining if there are any more results available
* provide isReference() and readReference() methods in ConnectionEntryReader so that applications can avoid additional try...catch complexity.
9 files modified
865 ■■■■■ changed files
sdk/src/com/sun/opends/sdk/tools/LDAPModify.java 9 ●●●●● patch | view | raw | blame | history
sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java 5 ●●●● patch | view | raw | blame | history
sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java 33 ●●●●● patch | view | raw | blame | history
sdk/src/org/opends/sdk/ldif/ConnectionEntryReader.java 230 ●●●● patch | view | raw | blame | history
sdk/src/org/opends/sdk/ldif/EntryReader.java 31 ●●●●● patch | view | raw | blame | history
sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java 261 ●●●●● patch | view | raw | blame | history
sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java 195 ●●●●● patch | view | raw | blame | history
sdk/tests/unit-tests-testng/src/org/opends/sdk/SynchronousConnectionTestCase.java 27 ●●●● patch | view | raw | blame | history
sdk/tests/unit-tests-testng/src/org/opends/sdk/ldif/LDIFEntryReaderTestCase.java 74 ●●●●● patch | view | raw | blame | history
sdk/src/com/sun/opends/sdk/tools/LDAPModify.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package com.sun.opends.sdk.tools;
@@ -762,13 +762,12 @@
        reader = new LDIFChangeRecordReader(getInputStream());
      }
      ChangeRecord cr;
      try
      {
        int result;
        while ((cr = reader.readChangeRecord()) != null)
        while (reader.hasNext())
        {
          result = cr.accept(visitor, null);
          final ChangeRecord cr = reader.readChangeRecord();
          final int result = cr.accept(visitor, null);
          if (result != 0 && !continueOnError.isPresent())
          {
            return result;
sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.sdk.ldif;
@@ -141,9 +141,12 @@
     */
    public void close() throws IOException
    {
      if (reader != null)
      {
      reader.close();
      reader = null;
    }
    }
sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.sdk.ldif;
@@ -31,8 +31,7 @@
import java.io.Closeable;
import java.io.IOException;
import org.opends.sdk.DecodeException;
import java.util.NoSuchElementException;
@@ -46,10 +45,6 @@
 * malformed change records and, if it is possible, how they are handled.
 * <li>Any synchronization limitations.
 * </ul>
 * <p>
 * TODO: LDIFInputStreamReader
 * <p>
 * TODO: SearchResultEntryReader
 */
public interface ChangeRecordReader extends Closeable
{
@@ -62,23 +57,35 @@
   * @throws IOException
   *           If an unexpected IO error occurred while closing.
   */
  @Override
  void close() throws IOException;
  /**
   * Returns {@code true} if this reader contains another change record,
   * blocking if necessary until either the next change record is available or
   * the end of the stream is reached.
   *
   * @return {@code true} if this reader contains another change record.
   * @throws IOException
   *           If an unexpected IO error occurred.
   */
  boolean hasNext() throws IOException;
  /**
   * Reads the next change record, blocking if necessary until a change record
   * is available. If the next change record does not contain a change type then
   * it will be treated as an {@code Add} change record.
   *
   * @return The next change record, or {@code null} if there are no more change
   *         records to be read.
   * @throws DecodeException
   *           If the change record could not be decoded because it was
   *           malformed.
   * @return The next change record.
   * @throws IOException
   *           If an unexpected IO error occurred while reading the change
   *           record.
   * @throws NoSuchElementException
   *           If this reader does not contain any more change records.
   */
  ChangeRecord readChangeRecord() throws DecodeException, IOException;
  ChangeRecord readChangeRecord() throws IOException, NoSuchElementException;
}
sdk/src/org/opends/sdk/ldif/ConnectionEntryReader.java
@@ -30,6 +30,7 @@
import java.io.InterruptedIOException;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -60,21 +61,29 @@
 *
 * <pre>
 * Connection connection = ...;
 * ConnectionEntryReader results = connection.search(
 *     &quot;dc=example,dc=com&quot;,
 *     SearchScope.WHOLE_SUBTREE,
 *     &quot;(objectClass=person)&quot;);
 * SearchResultEntry entry;
 * ConnectionEntryReader results = connection.search(&quot;dc=example,dc=com&quot;,
 *     SearchScope.WHOLE_SUBTREE, &quot;(objectClass=person)&quot;);
 * try
 * {
 *   while ((entry = results.readEntry()) != null)
 *   while (reader.hasNext())
 *   {
 *     // Process search result entry.
 *     if (!reader.isReference())
 *     {
 *       SearchResultEntry entry = reader.readEntry();
 *
 *       // Handle entry...
 *     }
 *     else
 *     {
 *       SearchResultReference ref = reader.readReference();
 *
 *       // Handle continuation reference...
 *   }
 * }
 * catch (Exception e)
 * }
 * catch (IOException e)
 * {
 *   // Handle exceptions
 *   // Handle exceptions...
 * }
 * finally
 * {
@@ -181,6 +190,7 @@
  private final BufferHandler buffer;
  private final FutureResult<Result> future;
  private Response nextResponse = null;
@@ -242,65 +252,69 @@
  /**
   * Returns the next search result entry contained in the search results,
   * waiting if necessary until one becomes available.
   *
   * @return The next search result entry, or {@code null} if there are no more
   *         entries in the search results.
   * @throws SearchResultReferenceIOException
   *           If the next search response was a search result reference. This
   *           connection entry reader may still contain remaining search
   *           results and references which can be retrieved using additional
   *           calls to this method.
   * @throws ErrorResultIOException
   *           If the result code indicates that the search operation failed for
   *           some reason.
   * @throws InterruptedIOException
   *           If the current thread was interrupted while waiting.
   * {@inheritDoc}
   */
  @Override
  public SearchResultEntry readEntry() throws SearchResultReferenceIOException,
      ErrorResultIOException, InterruptedIOException
  public boolean hasNext() throws ErrorResultIOException,
      InterruptedIOException
  {
    Response r;
    try
    // Poll for the next response if needed.
    final Response r = getNextResponse();
    if (!(r instanceof Result))
    {
      while ((r = buffer.responses.poll(50, TimeUnit.MILLISECONDS)) == null)
      {
        if (buffer.isInterrupted)
        {
          // The worker thread processing the result was interrupted so no
          // result will ever arrive. We don't want to hang this thread forever
          // while we wait, so terminate now.
          r = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR);
          break;
        }
      }
    }
    catch (final InterruptedException e)
    {
      throw new InterruptedIOException(e.getMessage());
      // Entry or reference.
      return true;
    }
    if (r instanceof SearchResultEntry)
    {
      return (SearchResultEntry) r;
    }
    else if (r instanceof SearchResultReference)
    {
      throw new SearchResultReferenceIOException((SearchResultReference) r);
    }
    else if (r instanceof Result)
    {
    // Final result.
      final Result result = (Result) r;
      if (result.isSuccess())
      {
        return null;
      return false;
      }
      else
    final ErrorResultException e = ErrorResultException.wrap(result);
    throw new ErrorResultIOException(e);
  }
  /**
   * Waits for the next search result entry or reference to become available and
   * returns {@code true} if it is a reference, or {@code false} if it is an
   * entry.
   *
   * @return {@code true} if the next search result is a reference, or
   *         {@code false} if it is an entry.
   * @throws ErrorResultIOException
   *           If there are no more search result entries or references and the
   *           search result code indicates that the search operation failed for
   *           some reason.
   * @throws InterruptedIOException
   *           If the current thread was interrupted while waiting.
   * @throws NoSuchElementException
   *           If there are no more search result entries or references and the
   *           search result code indicates that the search operation succeeded.
   */
  public boolean isReference() throws ErrorResultIOException,
      InterruptedIOException, NoSuchElementException
      {
        throw new ErrorResultIOException(ErrorResultException.wrap(result));
    // Throws ErrorResultIOException if search returned error.
    if (!hasNext())
    {
      // Search has completed successfully.
      throw new NoSuchElementException();
      }
    // Entry or reference.
    final Response r = nextResponse;
    if (r instanceof SearchResultEntry)
    {
      return false;
    }
    else if (r instanceof SearchResultReference)
    {
      return true;
    }
    else
    {
@@ -308,4 +322,108 @@
          + r.getClass().toString());
    }
  }
  /**
   * Waits for the next search result entry or reference to become available
   * and, if it is an entry, returns it as a {@code SearchResultEntry}. If the
   * next search response is a reference then this method will throw a
   * {@code SearchResultReferenceIOException}.
   *
   * @return The next search result entry.
   * @throws SearchResultReferenceIOException
   *           If the next search response was a search result reference. This
   *           connection entry reader may still contain remaining search
   *           results and references which can be retrieved using additional
   *           calls to this method.
   * @throws ErrorResultIOException
   *           If there are no more search result entries or references and the
   *           search result code indicates that the search operation failed for
   *           some reason.
   * @throws InterruptedIOException
   *           If the current thread was interrupted while waiting.
   * @throws NoSuchElementException
   *           If there are no more search result entries or references and the
   *           search result code indicates that the search operation succeeded.
   */
  @Override
  public SearchResultEntry readEntry() throws SearchResultReferenceIOException,
      ErrorResultIOException, InterruptedIOException, NoSuchElementException
  {
    if (!isReference())
    {
      final SearchResultEntry entry = (SearchResultEntry) nextResponse;
      nextResponse = null;
      return entry;
    }
    else
    {
      final SearchResultReference reference = (SearchResultReference) nextResponse;
      nextResponse = null;
      throw new SearchResultReferenceIOException(reference);
    }
  }
  /**
   * Waits for the next search result entry or reference to become available
   * and, if it is a reference, returns it as a {@code SearchResultReference}.
   * If the next search response is an entry then this method will return
   * {@code null}.
   *
   * @return The next search result reference, or {@code null} if the next
   *         response was a search result entry.
   * @throws ErrorResultIOException
   *           If there are no more search result entries or references and the
   *           search result code indicates that the search operation failed for
   *           some reason.
   * @throws InterruptedIOException
   *           If the current thread was interrupted while waiting.
   * @throws NoSuchElementException
   *           If there are no more search result entries or references and the
   *           search result code indicates that the search operation succeeded.
   */
  public SearchResultReference readReference() throws ErrorResultIOException,
      InterruptedIOException, NoSuchElementException
  {
    if (isReference())
    {
      final SearchResultReference reference = (SearchResultReference) nextResponse;
      nextResponse = null;
      return reference;
    }
    else
    {
      return null;
    }
  }
  private Response getNextResponse() throws InterruptedIOException
  {
    while (nextResponse == null)
    {
      try
      {
        nextResponse = buffer.responses.poll(50, TimeUnit.MILLISECONDS);
      }
      catch (final InterruptedException e)
      {
        throw new InterruptedIOException(e.getMessage());
      }
      if (nextResponse == null && buffer.isInterrupted)
      {
        // The worker thread processing the result was interrupted so no
        // result will ever arrive. We don't want to hang this thread
        // forever while we wait, so terminate now.
        nextResponse = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR);
        break;
      }
    }
    return nextResponse;
  }
}
sdk/src/org/opends/sdk/ldif/EntryReader.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.sdk.ldif;
@@ -31,8 +31,8 @@
import java.io.Closeable;
import java.io.IOException;
import java.util.NoSuchElementException;
import org.opends.sdk.DecodeException;
import org.opends.sdk.Entry;
@@ -46,10 +46,6 @@
 * malformed change records and, if it is possible, how they are handled.
 * <li>Any synchronization limitations.
 * </ul>
 * <p>
 * TODO: LDIFInputStreamReader
 * <p>
 * TODO: SearchResultEntryReader
 */
public interface EntryReader extends Closeable
{
@@ -62,19 +58,32 @@
   * @throws IOException
   *           If an unexpected IO error occurred while closing.
   */
  @Override
  void close() throws IOException;
  /**
   * Returns {@code true} if this reader contains another entry, blocking if
   * necessary until either the next entry is available or the end of the stream
   * is reached.
   *
   * @return {@code true} if this reader contains another entry.
   * @throws IOException
   *           If an unexpected IO error occurred.
   */
  boolean hasNext() throws IOException;
  /**
   * Reads the next entry, blocking if necessary until an entry is available.
   *
   * @return The next entry or {@code null} if there are no more entries to be
   *         read.
   * @throws DecodeException
   *           If the entry could not be decoded because it was malformed.
   * @return The next entry.
   * @throws IOException
   *           If an unexpected IO error occurred while reading the entry.
   * @throws NoSuchElementException
   *           If this reader does not contain any more entries.
   */
  Entry readEntry() throws DecodeException, IOException;
  Entry readEntry() throws IOException, NoSuchElementException;
}
sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.sdk.ldif;
@@ -37,6 +37,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import org.opends.sdk.*;
import org.opends.sdk.requests.ModifyDNRequest;
@@ -79,9 +80,7 @@
    final LDIFChangeRecordReader reader = new LDIFChangeRecordReader(ldifLines);
    try
    {
      final ChangeRecord record = reader.readChangeRecord();
      if (record == null)
      if (!reader.hasNext())
      {
        // No change record found.
        final LocalizableMessage message = WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND
@@ -89,7 +88,9 @@
        throw new LocalizedIllegalArgumentException(message);
      }
      if (reader.readChangeRecord() != null)
      final ChangeRecord record = reader.readChangeRecord();
      if (reader.hasNext())
      {
        // Multiple change records found.
        final LocalizableMessage message = WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND
@@ -115,6 +116,13 @@
  private ChangeRecord nextChangeRecord = null;
  // Poison used to indicate end of LDIF.
  private static final ChangeRecord EOF = Requests.newAddRequest(DN.rootDN());
  /**
   * Creates a new LDIF change record reader whose source is the provided input
   * stream.
@@ -169,6 +177,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public void close() throws IOException
  {
    close0();
@@ -178,111 +187,38 @@
  /**
   * {@inheritDoc}
   *
   * @throws DecodeException
   *           If the change record could not be decoded because it was
   *           malformed.
   */
  @Override
  public boolean hasNext() throws DecodeException, IOException
  {
    return getNextChangeRecord() != EOF;
  }
  /**
   * {@inheritDoc}
   *
   * @throws DecodeException
   *           If the entry could not be decoded because it was malformed.
   */
  @Override
  public ChangeRecord readChangeRecord() throws DecodeException, IOException
  {
    // Continue until an unfiltered entry is obtained.
    while (true)
    if (!hasNext())
    {
      LDIFRecord record = null;
      // Read the set of lines that make up the next entry.
      record = readLDIFRecord();
      if (record == null)
      {
        return null;
      // LDIF reader has completed successfully.
      throw new NoSuchElementException();
      }
      // 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);
        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;
      }
      ChangeRecord changeRecord = null;
      try
      {
        if (!record.iterator.hasNext())
        {
          // FIXME: improve error.
          final LocalizableMessage message = LocalizableMessage
              .raw("Missing changetype");
          throw DecodeException.error(message);
        }
        final KeyValuePair pair = new KeyValuePair();
        final String ldifLine = readLDIFRecordKeyValuePair(record, pair, false);
        if (!toLowerCase(pair.key).equals("changetype"))
        {
          // Default to add change record.
          changeRecord = parseAddChangeRecordEntry(entryDN, ldifLine, record);
        }
        else
        {
          final String changeType = toLowerCase(pair.value);
          if (changeType.equals("add"))
          {
            changeRecord = parseAddChangeRecordEntry(entryDN, null, record);
          }
          else if (changeType.equals("delete"))
          {
            changeRecord = parseDeleteChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("modify"))
          {
            changeRecord = parseModifyChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("modrdn"))
          {
            changeRecord = parseModifyDNChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("moddn"))
          {
            changeRecord = parseModifyDNChangeRecordEntry(entryDN, record);
          }
          else
          {
            // FIXME: improve error.
            final LocalizableMessage message = ERR_LDIF_INVALID_CHANGETYPE_ATTRIBUTE
                .get(pair.value, "add, delete, modify, moddn, modrdn");
            throw DecodeException.error(message);
          }
        }
      }
      catch (final DecodeException e)
      {
        rejectLDIFRecord(record, e.getMessageObject());
        continue;
      }
      if (changeRecord != null)
      {
    final ChangeRecord changeRecord = nextChangeRecord;
    nextChangeRecord = null;
        return changeRecord;
      }
    }
  }
@@ -309,8 +245,8 @@
   * change records that are read from LDIF. The default is {@code false}.
   *
   * @param excludeUserAttributes
   *          {@code true} if all user attributes should be excluded, or {@code
   *          false} otherwise.
   *          {@code true} if all user attributes should be excluded, or
   *          {@code false} otherwise.
   * @return A reference to this {@code LDIFChangeRecordReader}.
   */
  public LDIFChangeRecordReader setExcludeAllUserAttributes(
@@ -418,8 +354,8 @@
   * records that are read from LDIF. The default is {@code true} .
   *
   * @param validateSchema
   *          {@code true} if schema validation should be performed, or {@code
   *          false} otherwise.
   *          {@code true} if schema validation should be performed, or
   *          {@code false} otherwise.
   * @return A reference to this {@code LDIFChangeRecordReader}.
   */
  public LDIFChangeRecordReader setValidateSchema(final boolean validateSchema)
@@ -430,6 +366,115 @@
  private ChangeRecord getNextChangeRecord() throws DecodeException,
      IOException
  {
    while (nextChangeRecord == null)
    {
      LDIFRecord record = null;
      // Read the set of lines that make up the next entry.
      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);
        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;
      }
      ChangeRecord changeRecord = null;
      try
      {
        if (!record.iterator.hasNext())
        {
          // FIXME: improve error.
          final LocalizableMessage message = LocalizableMessage
              .raw("Missing changetype");
          throw DecodeException.error(message);
        }
        final KeyValuePair pair = new KeyValuePair();
        final String ldifLine = readLDIFRecordKeyValuePair(record, pair, false);
        if (!toLowerCase(pair.key).equals("changetype"))
        {
          // Default to add change record.
          changeRecord = parseAddChangeRecordEntry(entryDN, ldifLine, record);
        }
        else
        {
          final String changeType = toLowerCase(pair.value);
          if (changeType.equals("add"))
          {
            changeRecord = parseAddChangeRecordEntry(entryDN, null, record);
          }
          else if (changeType.equals("delete"))
          {
            changeRecord = parseDeleteChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("modify"))
          {
            changeRecord = parseModifyChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("modrdn"))
          {
            changeRecord = parseModifyDNChangeRecordEntry(entryDN, record);
          }
          else if (changeType.equals("moddn"))
          {
            changeRecord = parseModifyDNChangeRecordEntry(entryDN, record);
          }
          else
          {
            // FIXME: improve error.
            final LocalizableMessage message = ERR_LDIF_INVALID_CHANGETYPE_ATTRIBUTE
                .get(pair.value, "add, delete, modify, moddn, modrdn");
            throw DecodeException.error(message);
          }
        }
      }
      catch (final DecodeException e)
      {
        rejectLDIFRecord(record, e.getMessageObject());
        continue;
      }
      if (changeRecord != null)
      {
        nextChangeRecord = changeRecord;
      }
    }
    return nextChangeRecord;
  }
  private ChangeRecord parseAddChangeRecordEntry(final DN entryDN,
      final String lastLDIFLine, final LDIFRecord record)
      throws DecodeException
@@ -581,8 +626,8 @@
        {
          // TODO: include line number.
          final LocalizableMessage message = ERR_LDIF_INVALID_CHANGERECORD_ATTRIBUTE
              .get(attributeDescription2.toString(), attributeDescription
                  .toString());
              .get(attributeDescription2.toString(),
                  attributeDescription.toString());
          throw DecodeException.error(message);
        }
sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java
@@ -22,24 +22,20 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.sdk.ldif;
import static com.sun.opends.sdk.messages.Messages.
  WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND;
import static com.sun.opends.sdk.messages.Messages.
  WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND;
import static com.sun.opends.sdk.messages.Messages.
  WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR;
import static com.sun.opends.sdk.messages.Messages.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import org.opends.sdk.*;
import org.opends.sdk.schema.Schema;
@@ -58,6 +54,11 @@
public final class LDIFEntryReader extends AbstractLDIFReader implements
    EntryReader
{
  // Poison used to indicate end of LDIF.
  private static final Entry EOF = new LinkedHashMapEntry();
  /**
   * Parses the provided array of LDIF lines as a single LDIF entry.
   *
@@ -77,9 +78,7 @@
    final LDIFEntryReader reader = new LDIFEntryReader(ldifLines);
    try
    {
      final Entry entry = reader.readEntry();
      if (entry == null)
      if (!reader.hasNext())
      {
        // No change record found.
        final LocalizableMessage message = WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND
@@ -87,7 +86,9 @@
        throw new LocalizedIllegalArgumentException(message);
      }
      if (reader.readEntry() != null)
      final Entry entry = reader.readEntry();
      if (reader.hasNext())
      {
        // Multiple change records found.
        final LocalizableMessage message = WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND
@@ -113,6 +114,10 @@
  private Entry nextEntry = null;
  /**
   * Creates a new LDIF entry reader whose source is the provided input stream.
   *
@@ -164,6 +169,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public void close() throws IOException
  {
    close0();
@@ -173,76 +179,37 @@
  /**
   * {@inheritDoc}
   *
   * @throws DecodeException
   *           If the entry could not be decoded because it was malformed.
   */
  @Override
  public boolean hasNext() throws DecodeException, IOException
  {
    return getNextEntry() != EOF;
  }
  /**
   * {@inheritDoc}
   *
   * @throws DecodeException
   *           If the entry could not be decoded because it was malformed.
   */
  @Override
  public Entry readEntry() throws DecodeException, IOException
  {
    // Continue until an unfiltered entry is obtained.
    while (true)
    if (!hasNext())
    {
      LDIFRecord record = null;
      // Read the set of lines that make up the next entry.
      record = readLDIFRecord();
      if (record == null)
      {
        return null;
      // LDIF reader has completed successfully.
      throw new NoSuchElementException();
      }
      // 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);
        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;
      }
      // Use an Entry for the AttributeSequence.
      final Entry entry = new LinkedHashMapEntry(entryDN);
      try
      {
        while (record.iterator.hasNext())
        {
          final String ldifLine = record.iterator.next();
          readLDIFRecordAttributeValue(record, ldifLine, entry);
        }
      }
      catch (final DecodeException e)
      {
        rejectLDIFRecord(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;
      }
    final Entry entry = nextEntry;
    nextEntry = null;
      return entry;
    }
  }
@@ -269,8 +236,8 @@
   * entries that are read from LDIF. The default is {@code false}.
   *
   * @param excludeUserAttributes
   *          {@code true} if all user attributes should be excluded, or {@code
   *          false} otherwise.
   *          {@code true} if all user attributes should be excluded, or
   *          {@code false} otherwise.
   * @return A reference to this {@code LDIFEntryReader}.
   */
  public LDIFEntryReader setExcludeAllUserAttributes(
@@ -414,8 +381,8 @@
   * that are read from LDIF. The default is {@code true}.
   *
   * @param validateSchema
   *          {@code true} if schema validation should be performed, or {@code
   *          false} otherwise.
   *          {@code true} if schema validation should be performed, or
   *          {@code false} otherwise.
   * @return A reference to this {@code LDIFEntryReader}.
   */
  public LDIFEntryReader setValidateSchema(final boolean validateSchema)
@@ -424,4 +391,78 @@
    return this;
  }
  private Entry getNextEntry() throws DecodeException, IOException
  {
    while (nextEntry == null)
    {
      LDIFRecord record = null;
      // Read the set of lines that make up the next entry.
      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);
        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;
      }
      // Use an Entry for the AttributeSequence.
      final Entry entry = new LinkedHashMapEntry(entryDN);
      try
      {
        while (record.iterator.hasNext())
        {
          final String ldifLine = record.iterator.next();
          readLDIFRecordAttributeValue(record, ldifLine, entry);
        }
      }
      catch (final DecodeException e)
      {
        rejectLDIFRecord(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;
  }
}
sdk/tests/unit-tests-testng/src/org/opends/sdk/SynchronousConnectionTestCase.java
@@ -29,17 +29,18 @@
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.util.List;
import java.util.NoSuchElementException;
import org.opends.sdk.ldif.EntryReader;
import org.opends.sdk.ldif.ConnectionEntryReader;
import org.opends.sdk.requests.Requests;
import org.opends.sdk.responses.BindResult;
import org.opends.sdk.responses.CompareResult;
import org.opends.sdk.responses.Result;
import org.opends.sdk.responses.SearchResultEntry;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
@@ -155,11 +156,27 @@
  public void testSearchRequest() throws Exception
  {
    final SynchronousConnection con = new SynchronousConnection(asyncCon);
    final EntryReader reader = con.search(
    final ConnectionEntryReader reader = con.search(
        "uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT,
        "objectclass=*", "cn");
    Assert.assertTrue(reader.hasNext());
    Assert.assertFalse(reader.isReference());
    Assert.assertTrue(reader.hasNext());
    SearchResultEntry entry = reader.readEntry();
    Assert.assertEquals(entry.getName(),
        DN.valueOf("uid=user.0,ou=people,o=test"));
    Assert.assertFalse(reader.hasNext());
    try
    {
    reader.readEntry();
    assertNull(reader.readEntry());
      Assert
          .fail("reader.readEntry() should have thrown NoSuchElementException");
    }
    catch (NoSuchElementException e)
    {
      // This is expected.
    }
    Assert.assertFalse(reader.hasNext());
  }
  // TODO: add more tests.
}
sdk/tests/unit-tests-testng/src/org/opends/sdk/ldif/LDIFEntryReaderTestCase.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.sdk.ldif;
@@ -32,9 +32,12 @@
import static org.testng.Assert.assertNotNull;
import java.io.FileInputStream;
import java.util.NoSuchElementException;
import org.opends.sdk.AbstractEntry;
import org.opends.sdk.DN;
import org.opends.sdk.Entry;
import org.opends.sdk.TestCaseUtils;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -52,6 +55,45 @@
   *           If the test failed unexpectedly.
   */
  @Test()
  public void testEmpty() throws Exception
  {
    final String path = TestCaseUtils.createTempFile("");
    final FileInputStream in = new FileInputStream(path);
    final LDIFEntryReader reader = new LDIFEntryReader(in);
    try
    {
      reader.setValidateSchema(false);
      Assert.assertFalse(reader.hasNext());
      Assert.assertFalse(reader.hasNext());
      try
      {
        reader.readEntry();
        Assert
            .fail("reader.readEntry() should have thrown NoSuchElementException");
      }
      catch (NoSuchElementException e)
      {
        // This is expected.
      }
      Assert.assertFalse(reader.hasNext());
    }
    finally
    {
      reader.close();
    }
  }
  /**
   * Tests readEntry method of LDIFEntryReader class.See
   * https://opends.dev.java.net/issues/show_bug.cgi?id=4545 for more details.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test()
  public void testReadEntry() throws Exception
  {
    final String path = TestCaseUtils
@@ -80,9 +122,31 @@
            "postalAddress: Aaccf Amar$01251 Chestnut Street$Panama City, DE  50369",
            "description: This is the description for Aaccf Amar.");
    final FileInputStream in = new FileInputStream(path);
    final LDIFEntryReader entryReader = new LDIFEntryReader(in);
    entryReader.setValidateSchema(false);
    final AbstractEntry entry = (AbstractEntry) entryReader.readEntry();
    final LDIFEntryReader reader = new LDIFEntryReader(in);
    try
    {
      reader.setValidateSchema(false);
      Assert.assertTrue(reader.hasNext());
      final Entry entry = reader.readEntry();
    assertNotNull(entry);
      Assert.assertEquals(entry.getName(),
          DN.valueOf("uid=1,ou=people,dc=ucsf,dc=edu"));
      Assert.assertFalse(reader.hasNext());
      try
      {
        reader.readEntry();
        Assert
            .fail("reader.readEntry() should have thrown NoSuchElementException");
      }
      catch (NoSuchElementException e)
      {
        // This is expected.
      }
    }
    finally
    {
      reader.close();
    }
  }
}