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

neil_a_wilson
30.24.2007 4b976b00d90e98ae2807d7bdc9efb79a4ce75751
Implement support for the virtual list view (VLV) control as defined in
draft-ietf-ldapext-ldapv3-vlv. This can be used to retrieve a specified page
of a search result set. Any result set that can be used with server-side
sorting can also be used with VLV. The ldapsearch tool has also been updated
to support this control.

OpenDS Issue Number: 80
3 files added
13 files modified
3138 ■■■■■ changed files
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java 1 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java 41 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java 181 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValues.java 35 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/controls/VLVRequestControl.java 584 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/controls/VLVResponseControl.java 345 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java 34 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/messages/JebMessages.java 48 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java 98 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java 124 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPResultCode.java 33 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/tools/LDAPSearch.java 135 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/types/ResultCode.java 34 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java 16 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java 1164 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java 265 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -111,6 +111,7 @@
    supportedControls.add(OID_PAGED_RESULTS_CONTROL);
    supportedControls.add(OID_MANAGE_DSAIT_CONTROL);
    supportedControls.add(OID_SERVER_SIDE_SORT_REQUEST_CONTROL);
    supportedControls.add(OID_VLV_REQUEST_CONTROL);
  }
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -42,6 +42,7 @@
import org.opends.server.controls.PagedResultsControl;
import org.opends.server.controls.ServerSideSortRequestControl;
import org.opends.server.controls.ServerSideSortResponseControl;
import org.opends.server.controls.VLVRequestControl;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
@@ -552,6 +553,7 @@
    List<Control> controls = searchOperation.getRequestControls();
    PagedResultsControl pageRequest = null;
    ServerSideSortRequestControl sortRequest = null;
    VLVRequestControl vlvRequest = null;
    if (controls != null)
    {
      for (Control control : controls)
@@ -575,6 +577,14 @@
              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                                           e.getMessage(), e.getMessageID(), e);
            }
            if (vlvRequest != null)
            {
              int    msgID   = MSGID_JEB_SEARCH_CANNOT_MIX_PAGEDRESULTS_AND_VLV;
              String message = getMessage(msgID);
              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                                           message, msgID);
            }
          }
        }
        else if (control.getOID().equals(OID_SERVER_SIDE_SORT_REQUEST_CONTROL))
@@ -597,6 +607,34 @@
            }
          }
        }
        else if (control.getOID().equals(OID_VLV_REQUEST_CONTROL))
        {
          // Ignore all but the first VLV request control.
          if (vlvRequest == null)
          {
            try
            {
              vlvRequest = VLVRequestControl.decodeControl(control);
            }
            catch (LDAPException e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                                           e.getMessage(), e.getMessageID(), e);
            }
            if (pageRequest != null)
            {
              int    msgID   = MSGID_JEB_SEARCH_CANNOT_MIX_PAGEDRESULTS_AND_VLV;
              String message = getMessage(msgID);
              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                                           message, msgID);
            }
          }
        }
      }
    }
@@ -759,7 +797,8 @@
        {
          entryIDList = EntryIDSetSorter.sort(this, entryIDList,
                                              searchOperation,
                                              sortRequest.getSortOrder());
                                              sortRequest.getSortOrder(),
                                              vlvRequest);
          searchOperation.addResponseControl(
               new ServerSideSortResponseControl(LDAPResultCode.SUCCESS, null));
        }
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java
@@ -28,13 +28,21 @@
import java.util.Map;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeMap;
import org.opends.server.controls.VLVRequestControl;
import org.opends.server.controls.VLVResponseControl;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SearchOperation;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.SortOrder;
@@ -60,6 +68,8 @@
   * @param  entryIDSet       The entry ID set to be sorted.
   * @param  searchOperation  The search operation being processed.
   * @param  sortOrder        The sort order to use for the entry ID set.
   * @param  vlvRequest       The VLV request control included in the search
   *                          request, or {@code null} if there was none.
   *
   * @return  A new entry ID set which is a sorted representation of the
   *          provided set using the given sort order.
@@ -69,7 +79,8 @@
  public static EntryIDSet sort(EntryContainer entryContainer,
                                EntryIDSet entryIDSet,
                                SearchOperation searchOperation,
                                SortOrder sortOrder)
                                SortOrder sortOrder,
                                VLVRequestControl vlvRequest)
         throws DirectoryException
  {
    if (! entryIDSet.isDefined())
@@ -107,11 +118,171 @@
      }
    }
    long[] sortedIDs = new long[sortMap.size()];
    int i=0;
    for (EntryID id : sortMap.values())
    // See if there is a VLV request to further pare down the set of results,
    // and if there is where it should be processed by offset or assertion
    // value.
    long[] sortedIDs;
    if (vlvRequest != null)
    {
      sortedIDs[i++] = id.longValue();
      int beforeCount = vlvRequest.getBeforeCount();
      int afterCount  = vlvRequest.getAfterCount();
      if (vlvRequest.getTargetType() == VLVRequestControl.TYPE_TARGET_BYOFFSET)
      {
        int targetOffset = vlvRequest.getOffset();
        int listOffset = targetOffset - 1; // VLV offsets start at 1, not 0.
        int startPos = listOffset - beforeCount;
        if (startPos < 0)
        {
          searchOperation.addResponseControl(
               new VLVResponseControl(targetOffset, sortMap.size(),
                                      LDAPResultCode.OFFSET_RANGE_ERROR));
          int    msgID   = MSGID_ENTRYIDSORTER_NEGATIVE_START_POS;
          String message = getMessage(msgID);
          throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR,
                                       message, msgID);
        }
        else if (startPos >= sortMap.size())
        {
          searchOperation.addResponseControl(
               new VLVResponseControl(targetOffset, sortMap.size(),
                                      LDAPResultCode.OFFSET_RANGE_ERROR));
          int    msgID   = MSGID_ENTRYIDSORTER_OFFSET_TOO_LARGE;
          String message = getMessage(msgID, vlvRequest.getOffset(),
                                      sortMap.size());
          throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR,
                                       message, msgID);
        }
        int count = 1 + beforeCount + afterCount;
        sortedIDs = new long[count];
        int treePos = 0;
        int arrayPos = 0;
        Iterator<EntryID> idIterator = sortMap.values().iterator();
        while (idIterator.hasNext())
        {
          EntryID id = idIterator.next();
          if (treePos++ < startPos)
          {
            continue;
          }
          sortedIDs[arrayPos++] = id.longValue();
          if (arrayPos >= count)
          {
            break;
          }
        }
        if (arrayPos < count)
        {
          // We don't have enough entries in the set to meet the requested
          // page size, so we'll need to shorten the array.
          long[] newIDArray = new long[arrayPos];
          System.arraycopy(sortedIDs, 0, newIDArray, 0, arrayPos);
          sortedIDs = newIDArray;
        }
        searchOperation.addResponseControl(
             new VLVResponseControl(targetOffset, sortMap.size(),
                                    LDAPResultCode.SUCCESS));
      }
      else
      {
        AttributeValue assertionValue = new
             AttributeValue(sortOrder.getSortKeys()[0].getAttributeType(),
                            vlvRequest.getGreaterThanOrEqualAssertion());
        boolean targetFound     = false;
        int targetOffset        = 0;
        int includedBeforeCount = 0;
        int includedAfterCount  = 0;
        int listSize            = 0;
        LinkedList<EntryID> idList = new LinkedList<EntryID>();
        Iterator<Map.Entry<SortValues,EntryID>> mapIterator =
             sortMap.entrySet().iterator();
        while (mapIterator.hasNext())
        {
          Map.Entry<SortValues,EntryID> entry = mapIterator.next();
          SortValues sortValues = entry.getKey();
          EntryID id = entry.getValue();
          if (targetFound)
          {
            idList.add(id);
            listSize++;
            includedAfterCount++;
            if (includedAfterCount >= afterCount)
            {
              break;
            }
          }
          else
          {
            targetFound = (sortValues.compareTo(assertionValue) >= 0);
            targetOffset++;
            if (targetFound)
            {
              idList.add(id);
              listSize++;
            }
            else if (beforeCount > 0)
            {
              if (beforeCount > 0)
              {
                idList.add(id);
                includedBeforeCount++;
                if (includedBeforeCount > beforeCount)
                {
                  idList.removeFirst();
                  includedBeforeCount--;
                }
                else
                {
                  listSize++;
                }
              }
            }
          }
        }
        if (! targetFound)
        {
          searchOperation.addResponseControl(
               new VLVResponseControl(sortMap.size(), sortMap.size(),
                                      LDAPResultCode.OFFSET_RANGE_ERROR));
          int    msgID   = MSGID_ENTRYIDSORTER_TARGET_VALUE_NOT_FOUND;
          String message = getMessage(msgID);
          throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR,
                                       message, msgID);
        }
        sortedIDs = new long[listSize];
        Iterator<EntryID> idIterator = idList.iterator();
        for (int i=0; i < listSize; i++)
        {
          sortedIDs[i] = idIterator.next().longValue();
        }
        searchOperation.addResponseControl(
             new VLVResponseControl(targetOffset, sortMap.size(),
                                    LDAPResultCode.SUCCESS));
      }
    }
    else
    {
      sortedIDs = new long[sortMap.size()];
      int i=0;
      for (EntryID id : sortMap.values())
      {
        sortedIDs[i++] = id.longValue();
      }
    }
    return new EntryIDSet(sortedIDs, 0, sortedIDs.length);
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValues.java
@@ -165,6 +165,28 @@
  /**
   * Compares the first element in this set of sort values with the provided
   * assertion value to determine whether the assertion value is greater than or
   * equal to the initial sort value.  This is used during VLV processing to
   * find the offset by assertion value.
   *
   * @param  assertionValue  The assertion value to compare against the first
   *                         sort value.
   *
   * @return  A negative value if the provided assertion value should come
   *          before the first sort value, zero if the provided assertion value
   *          is equal to the first sort value, or a positive value if the
   *          provided assertion value should come after the first sort value.
   */
  public int compareTo(AttributeValue assertionValue)
  {
    SortKey sortKey = sortOrder.getSortKeys()[0];
    return sortKey.compareValues(values[0], assertionValue);
  }
  /**
   * Retrieves a string representation of this sort values object.
   *
   * @return  A string representation of this sort values object.
@@ -193,7 +215,7 @@
    {
      if (i > 0)
      {
        buffer.append(", ");
        buffer.append(",");
      }
      if (sortKeys[i].ascending())
@@ -207,9 +229,18 @@
      buffer.append(sortKeys[i].getAttributeType().getNameOrOID());
      buffer.append("=");
      buffer.append(values[i].getStringValue());
      if (values[i] == null)
      {
        buffer.append("null");
      }
      else
      {
        buffer.append(values[i].getStringValue());
      }
    }
    buffer.append(", id=");
    buffer.append(entryID.toString());
    buffer.append(")");
  }
}
opendj-sdk/opends/src/server/org/opends/server/controls/VLVRequestControl.java
New file
@@ -0,0 +1,584 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.controls;
import java.util.ArrayList;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Integer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.types.ByteString;
import org.opends.server.types.Control;
import org.opends.server.types.LDAPException;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.ProtocolMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class implements the virtual list view request controls as defined in
 * draft-ietf-ldapext-ldapv3-vlv.  The ASN.1 description for the control value
 * is:
 * <BR><BR>
 * <PRE>
 * VirtualListViewRequest ::= SEQUENCE {
 *       beforeCount    INTEGER (0..maxInt),
 *       afterCount     INTEGER (0..maxInt),
 *       target       CHOICE {
 *                      byOffset        [0] SEQUENCE {
 *                           offset          INTEGER (1 .. maxInt),
 *                           contentCount    INTEGER (0 .. maxInt) },
 *                      greaterThanOrEqual [1] AssertionValue },
 *       contextID     OCTET STRING OPTIONAL }
 * </PRE>
 */
public class VLVRequestControl
       extends Control
{
  /**
   * The BER type to use when encoding the byOffset target element.
   */
  public static final byte TYPE_TARGET_BYOFFSET = (byte) 0xA0;
  /**
   * The BER type to use when encoding the greaterThanOrEqual target element.
   */
  public static final byte TYPE_TARGET_GREATERTHANOREQUAL = (byte) 0x81;
  // The target type for this VLV request control.
  private byte targetType;
  // The context ID for this VLV request control.
  private ByteString contextID;
  // The greaterThanOrEqual target assertion value for this VLV request control.
  private ByteString greaterThanOrEqual;
  // The after count for this VLV request control.
  private int afterCount;
  // The before count for this VLV request control.
  private int beforeCount;
  // The content count for the byOffset target of this VLV request control.
  private int contentCount;
  // The offset for the byOffset target of this VLV request control.
  private int offset;
  /**
   * Creates a new VLV request control with the provided information.
   *
   * @param  beforeCount   The number of entries before the target offset to
   *                       retrieve in the results page.
   * @param  afterCount    The number of entries after the target offset to
   *                       retrieve in the results page.
   * @param  offset        The offset in the result set to target for the
   *                       beginning of the page of results.
   * @param  contentCount  The content count returned by the server in the last
   *                       phase of the VLV request, or zero for a new VLV
   *                       request session.
   */
  public VLVRequestControl(int beforeCount, int afterCount, int offset,
                           int contentCount)
  {
    this(beforeCount, afterCount, offset, contentCount, null);
  }
  /**
   * Creates a new VLV request control with the provided information.
   *
   * @param  beforeCount   The number of entries before the target offset to
   *                       retrieve in the results page.
   * @param  afterCount    The number of entries after the target offset to
   *                       retrieve in the results page.
   * @param  offset        The offset in the result set to target for the
   *                       beginning of the page of results.
   * @param  contentCount  The content count returned by the server in the last
   *                       phase of the VLV request, or zero for a new VLV
   *                       request session.
   * @param  contextID     The context ID provided by the server in the last
   *                       VLV response for the same set of criteria, or
   *                       {@code null} if there was no previous VLV response or
   *                       the server did not include a context ID in the
   *                       last response.
   */
  public VLVRequestControl(int beforeCount, int afterCount, int offset,
                           int contentCount, ByteString contextID)
  {
    super(OID_VLV_REQUEST_CONTROL, false,
          encodeControlValue(beforeCount, afterCount, offset, contentCount,
                             contextID));
    this.beforeCount  = beforeCount;
    this.afterCount   = afterCount;
    this.offset       = offset;
    this.contentCount = contentCount;
    this.contextID    = contextID;
    targetType = TYPE_TARGET_BYOFFSET;
  }
  /**
   * Creates a new VLV request control with the provided information.
   *
   * @param  beforeCount         The number of entries before the target offset
   *                             to retrieve in the results page.
   * @param  afterCount          The number of entries after the target offset
   *                             to retrieve in the results page.
   * @param  greaterThanOrEqual  The greaterThanOrEqual target assertion value
   *                             that indicates where to start the page of
   *                             results.
   */
  public VLVRequestControl(int beforeCount, int afterCount,
                           ByteString greaterThanOrEqual)
  {
    this(beforeCount, afterCount, greaterThanOrEqual, null);
  }
  /**
   * Creates a new VLV request control with the provided information.
   *
   * @param  beforeCount         The number of entries before the target
   *                             assertion value.
   * @param  afterCount          The number of entries after the target
   *                             assertion value.
   * @param  greaterThanOrEqual  The greaterThanOrEqual target assertion value
   *                             that indicates where to start the page of
   *                             results.
   * @param  contextID           The context ID provided by the server in the
   *                             last VLV response for the same set of criteria,
   *                             or {@code null} if there was no previous VLV
   *                             response or the server did not include a
   *                             context ID in the last response.
   */
  public VLVRequestControl(int beforeCount, int afterCount,
                           ByteString greaterThanOrEqual,
                           ByteString contextID)
  {
    super(OID_VLV_REQUEST_CONTROL, false,
          encodeControlValue(beforeCount, afterCount, greaterThanOrEqual,
                             contextID));
    this.beforeCount        = beforeCount;
    this.afterCount         = afterCount;
    this.greaterThanOrEqual = greaterThanOrEqual;
    this.contextID          = contextID;
    targetType = TYPE_TARGET_GREATERTHANOREQUAL;
  }
  /**
   * Creates a new VLV request control with the provided information.
   *
   * @param  oid                 The OID for the control.
   * @param  isCritical          Indicates whether the control should be
   *                             considered critical.
   * @param  controlValue        The pre-encoded value for the control.
   * @param  beforeCount         The number of entries before the target
   *                             assertion value.
   * @param  afterCount          The number of entries after the target
   *                             assertion value.
   * @param  greaterThanOrEqual  The greaterThanOrEqual target assertion value
   *                             that indicates where to start the page of
   *                             results.
   * @param  contextID           The context ID provided by the server in the
   *                             last VLV response for the same set of criteria,
   *                             or {@code null} if there was no previous VLV
   *                             response or the server did not include a
   *                             context ID in the last response.
   */
  private VLVRequestControl(String oid, boolean isCritical,
                            ASN1OctetString controlValue, int beforeCount,
                            int afterCount, byte targetType,
                            int offset, int contentCount,
                            ByteString greaterThanOrEqual,
                            ByteString contextID)
  {
    super(oid, isCritical, controlValue);
    this.beforeCount        = beforeCount;
    this.afterCount         = afterCount;
    this.targetType         = targetType;
    this.offset             = offset;
    this.contentCount       = contentCount;
    this.greaterThanOrEqual = greaterThanOrEqual;
    this.contextID          = contextID;
  }
  /**
   * Retrieves the number of entries before the target offset or assertion value
   * to include in the results page.
   *
   * @return  The number of entries before the target offset to include in the
   *          results page.
   */
  public int getBeforeCount()
  {
    return beforeCount;
  }
  /**
   * Retrieves the number of entries after the target offset or assertion value
   * to include in the results page.
   *
   * @return  The number of entries after the target offset to include in the
   *          results page.
   */
  public int getAfterCount()
  {
    return afterCount;
  }
  /**
   * Retrieves the BER type for the target that specifies the beginning of the
   * results page.
   *
   * @return  {@code TYPE_TARGET_BYOFFSET} if the beginning of the results page
   *          should be specified as a nuemric offset, or
   *          {@code TYPE_TARGET_GREATERTHANOREQUAL} if it should be specified
   *          by an assertion value.
   */
  public byte getTargetType()
  {
    return targetType;
  }
  /**
   * Retrieves the offset that indicates the beginning of the results page.  The
   * return value will only be applicable if the {@code getTargetType} method
   * returns {@code TYPE_TARGET_BYOFFSET}.
   *
   * @return  The offset that indicates the beginning of the results page.
   */
  public int getOffset()
  {
    return offset;
  }
  /**
   * Retrieves the content count indicating the estimated number of entries in
   * the complete result set.  The return value will only be applicable if the
   * {@code getTargetType} method returns {@code TYPE_TARGET_BYOFFSET}.
   *
   * @return  The content count indicating the estimated number of entries in
   *          the complete result set.
   */
  public int getContentCount()
  {
    return contentCount;
  }
  /**
   * Retrieves the assertion value that will be used to locate the beginning of
   * the results page.  This will only be applicable if the
   * {@code getTargetType} method returns
   * {@code TYPE_TARGET_GREATERTHANOREQUAL}.
   *
   * @return  The assertion value that will be used to locate the beginning of
   *          the results page, or {@code null} if the beginning of the results
   *          page is to be specified using an offset.
   */
  public ByteString getGreaterThanOrEqualAssertion()
  {
    return greaterThanOrEqual;
  }
  /**
   * Retrieves a context ID value that should be used to resume a previous VLV
   * results session.
   *
   * @return  A context ID value that should be used to resume a previous VLV
   *          results session, or {@code null} if none is available.
   */
  public ByteString getContextID()
  {
    return contextID;
  }
  /**
   * Encodes the provided information in a manner suitable for use as the value
   * of this control.
   *
   * @param  beforeCount   The number of entries before the target offset to
   *                       retrieve in the results page.
   * @param  afterCount    The number of entries after the target offset to
   *                       retrieve in the results page.
   * @param  offset        The offset in the result set to target for the
   *                       beginning of the page of results.
   * @param  contentCount  The content count returned by the server in the last
   *                       phase of the VLV request, or zero for a new VLV
   *                       request session.
   * @param  contextID     The context ID provided by the server in the last
   *                       VLV response for the same set of criteria, or
   *                       {@code null} if there was no previous VLV response or
   *                       the server did not include a context ID in the
   *                       last response.
   *
   * @return  The ASN.1 octet string containing the encoded sort order.
   */
  private static ASN1OctetString encodeControlValue(int beforeCount,
                                      int afterCount, int offset,
                                      int contentCount, ByteString contextID)
  {
    ArrayList<ASN1Element> vlvElements = new ArrayList<ASN1Element>(4);
    vlvElements.add(new ASN1Integer(beforeCount));
    vlvElements.add(new ASN1Integer(afterCount));
    ArrayList<ASN1Element> targetElements = new ArrayList<ASN1Element>(2);
    targetElements.add(new ASN1Integer(offset));
    targetElements.add(new ASN1Integer(contentCount));
    vlvElements.add(new ASN1Sequence(TYPE_TARGET_BYOFFSET, targetElements));
    if (contextID != null)
    {
      vlvElements.add(contextID.toASN1OctetString());
    }
    return new ASN1OctetString(new ASN1Sequence(vlvElements).encode());
  }
  /**
   * Encodes the provided information in a manner suitable for use as the value
   * of this control.
   *
   * @param  beforeCount         The number of entries before the target
   *                             assertion value.
   * @param  afterCount          The number of entries after the target
   *                             assertion value.
   * @param  greaterThanOrEqual  The greaterThanOrEqual target assertion value
   *                             that indicates where to start the page of
   *                             results.
   * @param  contextID           The context ID provided by the server in the
   *                             last VLV response for the same set of criteria,
   *                             or {@code null} if there was no previous VLV
   *                             response or the server did not include a
   *                             context ID in the last response.
   *
   * @return  The ASN.1 octet string containing the encoded sort order.
   */
  private static ASN1OctetString encodeControlValue(int beforeCount,
                                      int afterCount,
                                      ByteString greaterThanOrEqual,
                                      ByteString contextID)
  {
    ArrayList<ASN1Element> vlvElements = new ArrayList<ASN1Element>(4);
    vlvElements.add(new ASN1Integer(beforeCount));
    vlvElements.add(new ASN1Integer(afterCount));
    vlvElements.add(new ASN1OctetString(TYPE_TARGET_GREATERTHANOREQUAL,
                                        greaterThanOrEqual.value()));
    if (contextID != null)
    {
      vlvElements.add(contextID.toASN1OctetString());
    }
    return new ASN1OctetString(new ASN1Sequence(vlvElements).encode());
  }
  /**
   * Creates a new VLV request control from the contents of the provided
   * control.
   *
   * @param  control  The generic control containing the information to use to
   *                  create this VLV request control.  It must not be
   *                  {@code null}.
   *
   * @return  The VLV request control decoded from the provided control.
   *
   * @throws  LDAPException  If this control cannot be decoded as a valid VLV
   *                         request control.
   */
  public static VLVRequestControl decodeControl(Control control)
         throws LDAPException
  {
    ASN1OctetString controlValue = control.getValue();
    if (controlValue == null)
    {
      int    msgID   = MSGID_VLVREQ_CONTROL_NO_VALUE;
      String message = getMessage(msgID);
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
    }
    try
    {
      ASN1Sequence vlvSequence =
           ASN1Sequence.decodeAsSequence(controlValue.value());
      ArrayList<ASN1Element> elements = vlvSequence.elements();
      if ((elements.size() < 3) || (elements.size() > 4))
      {
        int    msgID   = MSGID_VLVREQ_CONTROL_INVALID_ELEMENT_COUNT;
        String message = getMessage(msgID, elements.size());
        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
      }
      int beforeCount = elements.get(0).decodeAsInteger().intValue();
      int afterCount  = elements.get(1).decodeAsInteger().intValue();
      ASN1Element targetElement = elements.get(2);
      int offset = 0;
      int contentCount = 0;
      ASN1OctetString greaterThanOrEqual = null;
      byte targetType = targetElement.getType();
      switch (targetType)
      {
        case TYPE_TARGET_BYOFFSET:
          ArrayList<ASN1Element> targetElements =
               targetElement.decodeAsSequence().elements();
          offset = targetElements.get(0).decodeAsInteger().intValue();
          contentCount = targetElements.get(1).decodeAsInteger().intValue();
          break;
        case TYPE_TARGET_GREATERTHANOREQUAL:
          greaterThanOrEqual = targetElement.decodeAsOctetString();
          break;
        default:
          int    msgID   = MSGID_VLVREQ_CONTROL_INVALID_TARGET_TYPE;
          String message = getMessage(msgID, byteToHex(targetType));
          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID,
                                  message);
      }
      ASN1OctetString contextID = null;
      if (elements.size() == 4)
      {
        contextID = elements.get(3).decodeAsOctetString();
      }
      return new VLVRequestControl(control.getOID(), control.isCritical(),
                                   controlValue, beforeCount, afterCount,
                                   targetType, offset, contentCount,
                                   greaterThanOrEqual, contextID);
    }
    catch (LDAPException le)
    {
      throw le;
    }
    catch (Exception e)
    {
      int    msgID   = MSGID_VLVREQ_CONTROL_CANNOT_DECODE_VALUE;
      String message = getMessage(msgID, getExceptionMessage(e));
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message, e);
    }
  }
  /**
   * Retrieves a string representation of this VLV request control.
   *
   * @return  A string representation of this VLV request control.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this VLV request control to the provided
   * buffer.
   *
   * @param  buffer  The buffer to which the information should be appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("VLVRequestControl(beforeCount=");
    buffer.append(beforeCount);
    buffer.append(", afterCount=");
    buffer.append(afterCount);
    if (targetType == TYPE_TARGET_BYOFFSET)
    {
      buffer.append(", offset=");
      buffer.append(offset);
      buffer.append(", contentCount=");
      buffer.append(contentCount);
    }
    else
    {
      buffer.append(", greaterThanOrEqual=");
      buffer.append(greaterThanOrEqual);
    }
    if (contextID != null)
    {
      buffer.append(", contextID=");
      buffer.append(contextID);
    }
    buffer.append(")");
  }
}
opendj-sdk/opends/src/server/org/opends/server/controls/VLVResponseControl.java
New file
@@ -0,0 +1,345 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.controls;
import java.util.ArrayList;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Enumerated;
import org.opends.server.protocols.asn1.ASN1Integer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.types.ByteString;
import org.opends.server.types.Control;
import org.opends.server.types.LDAPException;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.ProtocolMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class implements the virtual list view response controls as defined in
 * draft-ietf-ldapext-ldapv3-vlv.  The ASN.1 description for the control value
 * is:
 * <BR><BR>
 * <PRE>
 * VirtualListViewResponse ::= SEQUENCE {
 *       targetPosition    INTEGER (0 .. maxInt),
 *       contentCount     INTEGER (0 .. maxInt),
 *       virtualListViewResult ENUMERATED {
 *            success (0),
 *            operationsError (1),
 *            protocolError (3),
 *            unwillingToPerform (53),
 *            insufficientAccessRights (50),
 *            timeLimitExceeded (3),
 *            adminLimitExceeded (11),
 *            innapropriateMatching (18),
 *            sortControlMissing (60),
 *            offsetRangeError (61),
 *            other(80),
 *            ... },
 *       contextID     OCTET STRING OPTIONAL }
 * </PRE>
 */
public class VLVResponseControl
       extends Control
{
  // The context ID for this VLV response control.
  private ByteString contextID;
  // The content count estimating the total number of entries in the result set.
  private int contentCount;
  // The offset of the target entry in the result set.
  private int targetPosition;
  // The result code for the VLV operation.
  private int vlvResultCode;
  /**
   * Creates a new VLV response control with the provided information.
   *
   * @param  targetPosition  The position of the target entry in the result set.
   * @param  contentCount    The content count estimating the total number of
   *                         entries in the result set.
   * @param  vlvResultCode   The result code for the VLV operation.
   */
  public VLVResponseControl(int targetPosition, int contentCount,
                            int vlvResultCode)
  {
    this(targetPosition, contentCount, vlvResultCode, null);
  }
  /**
   * Creates a new VLV response control with the provided information.
   *
   * @param  targetPosition  The position of the target entry in the result set.
   * @param  contentCount    The content count estimating the total number of
   *                         entries in the result set.
   * @param  vlvResultCode   The result code for the VLV operation.
   * @param  contextID       The context ID for this VLV response control.
   */
  public VLVResponseControl(int targetPosition, int contentCount,
                            int vlvResultCode, ByteString contextID)
  {
    super(OID_VLV_RESPONSE_CONTROL, false,
          encodeControlValue(targetPosition, contentCount, vlvResultCode,
                             contextID));
    this.targetPosition = targetPosition;
    this.contentCount   = contentCount;
    this.vlvResultCode  = vlvResultCode;
    this.contextID      = contextID;
  }
  /**
   * Creates a new VLV response control with the provided information.
   *
   * @param  oid             The OID for the control.
   * @param  isCritical      Indicates whether the control should be considered
   *                         critical.
   * @param  controlValue    The pre-encoded value for the control.
   * @param  targetPosition  The position of the target entry in the result set.
   * @param  contentCount    The content count estimating the total number of
   *                         entries in the result set.
   * @param  vlvResultCode   The result code for the VLV operation.
   * @param  contextID       The context ID for this VLV response control.
   */
  private VLVResponseControl(String oid, boolean isCritical,
                             ASN1OctetString controlValue, int targetPosition,
                             int contentCount, int vlvResultCode,
                             ByteString contextID)
  {
    super(oid, isCritical, controlValue);
    this.targetPosition = targetPosition;
    this.contentCount   = contentCount;
    this.vlvResultCode  = vlvResultCode;
    this.contextID      = contextID;
  }
  /**
   * Retrieves the position of the target entry in the result set.
   *
   * @return  The position of the target entry in the result set.
   */
  public int getTargetPosition()
  {
    return targetPosition;
  }
  /**
   * Retrieves the estimated total number of entries in the result set.
   *
   * @return  The estimated total number of entries in the result set.
   */
  public int getContentCount()
  {
    return contentCount;
  }
  /**
   * Retrieves the result code for the VLV operation.
   *
   * @return  The result code for the VLV operation.
   */
  public int getVLVResultCode()
  {
    return vlvResultCode;
  }
  /**
   * Retrieves a context ID value that should be included in the next request
   * to retrieve a page of the same result set.
   *
   * @return  A context ID value that should be included in the next request to
   *          retrieve a page of the same result set, or {@code null} if there
   *          is no context ID.
   */
  public ByteString getContextID()
  {
    return contextID;
  }
  /**
   * Encodes the provided information in a manner suitable for use as the value
   * of this control.
   *
   * @param  targetPosition  The position of the target entry in the result set.
   * @param  contentCount    The content count estimating the total number of
   *                         entries in the result set.
   * @param  vlvResultCode   The result code for the VLV operation.
   * @param  contextID       The context ID for this VLV response control.
   *
   * @return  The ASN.1 octet string containing the encoded sort order.
   */
  private static ASN1OctetString encodeControlValue(int targetPosition,
                                      int contentCount, int vlvResultCode,
                                      ByteString contextID)
  {
    ArrayList<ASN1Element> vlvElements = new ArrayList<ASN1Element>(4);
    vlvElements.add(new ASN1Integer(targetPosition));
    vlvElements.add(new ASN1Integer(contentCount));
    vlvElements.add(new ASN1Enumerated(vlvResultCode));
    if (contextID != null)
    {
      vlvElements.add(contextID.toASN1OctetString());
    }
    return new ASN1OctetString(new ASN1Sequence(vlvElements).encode());
  }
  /**
   * Creates a new VLV response control from the contents of the provided
   * control.
   *
   * @param  control  The generic control containing the information to use to
   *                  create this VLV response control.  It must not be
   *                  {@code null}.
   *
   * @return  The VLV response control decoded from the provided control.
   *
   * @throws  LDAPException  If this control cannot be decoded as a valid VLV
   *                         response control.
   */
  public static VLVResponseControl decodeControl(Control control)
         throws LDAPException
  {
    ASN1OctetString controlValue = control.getValue();
    if (controlValue == null)
    {
      int    msgID   = MSGID_VLVRES_CONTROL_NO_VALUE;
      String message = getMessage(msgID);
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
    }
    try
    {
      ASN1Sequence vlvSequence =
           ASN1Sequence.decodeAsSequence(controlValue.value());
      ArrayList<ASN1Element> elements = vlvSequence.elements();
      if ((elements.size() < 3) || (elements.size() > 4))
      {
        int    msgID   = MSGID_VLVRES_CONTROL_INVALID_ELEMENT_COUNT;
        String message = getMessage(msgID, elements.size());
        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
      }
      int targetPosition = elements.get(0).decodeAsInteger().intValue();
      int contentCount   = elements.get(1).decodeAsInteger().intValue();
      int vlvResultCode  = elements.get(2).decodeAsEnumerated().intValue();
      ASN1OctetString contextID = null;
      if (elements.size() == 4)
      {
        contextID = elements.get(3).decodeAsOctetString();
      }
      return new VLVResponseControl(control.getOID(), control.isCritical(),
                                    controlValue, targetPosition, contentCount,
                                    vlvResultCode, contextID);
    }
    catch (LDAPException le)
    {
      throw le;
    }
    catch (Exception e)
    {
      int    msgID   = MSGID_VLVRES_CONTROL_CANNOT_DECODE_VALUE;
      String message = getMessage(msgID, getExceptionMessage(e));
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message, e);
    }
  }
  /**
   * Retrieves a string representation of this VLV request control.
   *
   * @return  A string representation of this VLV request control.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this VLV request control to the provided
   * buffer.
   *
   * @param  buffer  The buffer to which the information should be appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("VLVResponseControl(targetPosition=");
    buffer.append(targetPosition);
    buffer.append(", contentCount=");
    buffer.append(contentCount);
    buffer.append(", vlvResultCode=");
    buffer.append(vlvResultCode);
    if (contextID != null)
    {
      buffer.append(", contextID=");
      buffer.append(contextID);
    }
    buffer.append(")");
  }
}
opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6092,6 +6092,36 @@
  /**
   * The message ID for the string representation of the result code that will
   * be used for search operations containing the VLV request control that do
   * not also include the server-side sort control.
   */
  public static final int MSGID_RESULT_SORT_CONTROL_MISSING =
       CATEGORY_MASK_CORE | SEVERITY_MASK_INFORMATIONAL | 608;
  /**
   * The message ID for the string representation of the result code that will
   * be used for search operations containing the VLV request control with an
   * invalid offset or target count.
   */
  public static final int MSGID_RESULT_OFFSET_RANGE_ERROR =
       CATEGORY_MASK_CORE | SEVERITY_MASK_INFORMATIONAL | 609;
  /**
   * The message ID for the string representation of the result code that will
   * be used for operations that failed because the request would have impacted
   * information in multiple servers or repositories.
   */
  public static final int MSGID_RESULT_VIRTUAL_LIST_VIEW_ERROR =
       CATEGORY_MASK_CORE | SEVERITY_MASK_INFORMATIONAL | 610;
  /**
   * Associates a set of generic messages with the message IDs defined
   * in this class.
   */
@@ -6219,6 +6249,8 @@
    registerMessage(MSGID_RESULT_UNAVAILABLE, "Unavailable");
    registerMessage(MSGID_RESULT_UNWILLING_TO_PERFORM, "Unwilling to Perform");
    registerMessage(MSGID_RESULT_LOOP_DETECT, "Loop Detected");
    registerMessage(MSGID_RESULT_SORT_CONTROL_MISSING, "Sort Control Missing");
    registerMessage(MSGID_RESULT_OFFSET_RANGE_ERROR, "Offset Range Error");
    registerMessage(MSGID_RESULT_NAMING_VIOLATION, "Naming Violation");
    registerMessage(MSGID_RESULT_OBJECTCLASS_VIOLATION,
                    "ObjectClass Violation");
@@ -6230,6 +6262,8 @@
                    "ObjectClass Modifications Prohibited");
    registerMessage(MSGID_RESULT_AFFECTS_MULTIPLE_DSAS,
                    "Affects Multiple DSAs");
    registerMessage(MSGID_RESULT_VIRTUAL_LIST_VIEW_ERROR,
                    "Virtual List View Error");
    registerMessage(MSGID_RESULT_OTHER, "Other");
    registerMessage(MSGID_RESULT_CLIENT_SIDE_SERVER_DOWN, "Server Down");
    registerMessage(MSGID_RESULT_CLIENT_SIDE_LOCAL_ERROR, "Local Error");
opendj-sdk/opends/src/server/org/opends/server/messages/JebMessages.java
@@ -1112,6 +1112,37 @@
       CATEGORY_MASK_JEB | SEVERITY_MASK_MILD_ERROR | 140;
  /**
   * The message ID to use if a VLV request has a negative start position.  This
   * does not take any arguments.
   */
  public static final int MSGID_ENTRYIDSORTER_NEGATIVE_START_POS =
       CATEGORY_MASK_JEB | SEVERITY_MASK_MILD_ERROR | 141;
  /**
   * The message ID to use if a VLV request has an offset beyond the end of the
   * entry set.  This takes two arguments, which are the provided offset and the
   * list size.
   */
  public static final int MSGID_ENTRYIDSORTER_OFFSET_TOO_LARGE =
       CATEGORY_MASK_JEB | SEVERITY_MASK_MILD_ERROR | 142;
  /**
   * The message ID to use if a VLV request specifies a target value that is
   * larger than all values in the sort list.  This does not take any arguments.
   */
  public static final int MSGID_ENTRYIDSORTER_TARGET_VALUE_NOT_FOUND =
       CATEGORY_MASK_JEB | SEVERITY_MASK_MILD_ERROR | 143;
  /**
   * The message ID of an error indicating that the search request included both
   * the paged results control and the VLV control.  This does not take any
   * arguments.
   */
  public static final int MSGID_JEB_SEARCH_CANNOT_MIX_PAGEDRESULTS_AND_VLV =
       CATEGORY_MASK_JEB | SEVERITY_MASK_MILD_ERROR | 144;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -1208,6 +1239,11 @@
                    "allow it to be replaced");
    registerMessage(MSGID_JEB_ATTRIBUTE_INDEX_NOT_CONFIGURED,
                    "There is no index configured for attribute type '%s'");
    registerMessage(MSGID_JEB_SEARCH_CANNOT_MIX_PAGEDRESULTS_AND_VLV,
                    "The requested search operation included both the simple " +
                    "paged results control and the virtual list view " +
                    "control.  These controls are mutually exclusive and " +
                    "cannot be used together");
    registerMessage(MSGID_JEB_SEARCH_NO_SUCH_OBJECT,
                    "The search base entry '%s' does not exist");
    registerMessage(MSGID_JEB_SEARCH_CANNOT_SORT_UNINDEXED,
@@ -1419,5 +1455,17 @@
    registerMessage(MSGID_ENTRYIDSORTER_CANNOT_EXAMINE_ENTRY,
                    "Unable to examine the entry with ID %s for sorting " +
                    "purposes:  %s");
    registerMessage(MSGID_ENTRYIDSORTER_NEGATIVE_START_POS,
                    "Unable to process the virtual list view request because " +
                    "the target start position was before the beginning of " +
                    "the result set");
    registerMessage(MSGID_ENTRYIDSORTER_OFFSET_TOO_LARGE,
                    "Unable to process the virtual list view request because " +
                    "the target offset %d was greater than the total number " +
                    "of results in the list (%d)");
    registerMessage(MSGID_ENTRYIDSORTER_TARGET_VALUE_NOT_FOUND,
                    "Unable to prcess the virtual list view request because " +
                    "no entry was found in the result set with a sort value " +
                    "greater than or equal to the provided assertion value");
  }
}
opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java
@@ -4510,6 +4510,74 @@
  /**
   * The message ID for the message that will be used if the VLV request control
   * does not have a value.  It does not take any arguments.
   */
  public static final int MSGID_VLVREQ_CONTROL_NO_VALUE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 417;
  /**
   * The message ID for the message that will be used if the VLV request control
   * has an invalid number of elements.  This takes a single argument, which is
   * the number of elements contained in the control sequence.
   */
  public static final int MSGID_VLVREQ_CONTROL_INVALID_ELEMENT_COUNT =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 418;
  /**
   * The message ID for the message that will be used if the VLV request control
   * has an invalid BER target type.  It takes a single argument, which is the
   * hex representation of the BER target type.
   */
  public static final int MSGID_VLVREQ_CONTROL_INVALID_TARGET_TYPE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 419;
  /**
   * The message ID for the message that will be used if the VLV request control
   * value cannot be decoded.  It takes a single argument, which is a message
   * explaining the problem that occurred.
   */
  public static final int MSGID_VLVREQ_CONTROL_CANNOT_DECODE_VALUE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 420;
  /**
   * The message ID for the message that will be used if the VLV response
   * control does not have a value.  It does not take any arguments.
   */
  public static final int MSGID_VLVRES_CONTROL_NO_VALUE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 421;
  /**
   * The message ID for the message that will be used if the VLV response
   * control has an invalid number of elements.  This takes a single argument,
   * which is the number of elements contained in the control sequence.
   */
  public static final int MSGID_VLVRES_CONTROL_INVALID_ELEMENT_COUNT =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 422;
  /**
   * The message ID for the message that will be used if the VLV response
   * control value cannot be decoded.  It takes a single argument, which is a
   * message explaining the problem that occurred.
   */
  public static final int MSGID_VLVRES_CONTROL_CANNOT_DECODE_VALUE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 423;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -6444,6 +6512,34 @@
                    "Unable to process the provided server-side sort " +
                    "response control because an error occurred while " +
                    "attempting to decode the control value:  %s");
  }
    registerMessage(MSGID_VLVREQ_CONTROL_NO_VALUE,
                    "Unable to decode the provided control as a VLV request " +
                    "control because it does not include a control value");
    registerMessage(MSGID_VLVREQ_CONTROL_INVALID_ELEMENT_COUNT,
                    "Unable to decode the provided control as a VLV request " +
                    "control because it contains an invalid number of " +
                    "elements:  %d");
    registerMessage(MSGID_VLVREQ_CONTROL_INVALID_TARGET_TYPE,
                    "Unable to decode the provided control as a VLV request " +
                    "control because the target element type %s is invalid");
    registerMessage(MSGID_VLVREQ_CONTROL_CANNOT_DECODE_VALUE,
                    "Unable to process the provided VLV request control " +
                    "because an error occurred while attempting to decode " +
                    "the control value:  %s");
    registerMessage(MSGID_VLVRES_CONTROL_NO_VALUE,
                    "Unable to decode the provided control as a VLV response " +
                    "control because it does not include a control value");
    registerMessage(MSGID_VLVRES_CONTROL_INVALID_ELEMENT_COUNT,
                    "Unable to decode the provided control as a VLV response " +
                    "control because it contains an invalid number of " +
                    "elements:  %d");
    registerMessage(MSGID_VLVRES_CONTROL_CANNOT_DECODE_VALUE,
                    "Unable to process the provided VLV response control " +
                    "because an error occurred while attempting to decode " +
                    "the control value:  %s");  }
}
opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java
@@ -6968,6 +6968,98 @@
  /**
   * The message ID for the message that will be used as the description of the
   * virtualListView option for the ldapsearch tool.  It does not take any
   * arguments.
   */
  public static final int MSGID_DESCRIPTION_VLV =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 878;
  /**
   * The message ID for the message that will be used if the user requests a VLV
   * operation without also requesting a server-side sort.  This takes two
   * arguments, which are the long identifiers for the virtualListView and
   * sortOrder arguments.
   */
  public static final int MSGID_LDAPSEARCH_VLV_REQUIRES_SORT =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 879;
  /**
   * The message ID for the message that will be used if the user requests a VLV
   * operation with an invalid descriptor.  This does not take any arguments.
   */
  public static final int MSGID_LDAPSEARCH_VLV_INVALID_DESCRIPTOR =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 880;
  /**
   * The message ID for the message that will be used to indicate that the
   * requested server-side sort operation was not successful.  This takes a
   * single argument, which is a string representation of the associated sort
   * control result code.
   */
  public static final int MSGID_LDAPSEARCH_SORT_ERROR =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_WARNING | 881;
  /**
   * The message ID for the message that will be used to indicate that the
   * server-side sort response control could not be decoded.  This takes a
   * single argument, which is a message explaining the problem that occurred.
   */
  public static final int MSGID_LDAPSEARCH_CANNOT_DECODE_SORT_RESPONSE =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_WARNING | 882;
  /**
   * The message ID for the message that will be used display the target offset
   * for the VLV result set.  This takes a single argument, which is the target
   * offset.
   */
  public static final int MSGID_LDAPSEARCH_VLV_TARGET_OFFSET =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 883;
  /**
   * The message ID for the message that will be used display the content count
   * for the VLV result set.  This takes a single argument, which is the content
   * count.
   */
  public static final int MSGID_LDAPSEARCH_VLV_CONTENT_COUNT =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 884;
  /**
   * The message ID for the message that will be used to indicate that the
   * requested virtual list view operation was not successful.  This takes a
   * single argument, which is a string representation of the associated sort
   * control result code.
   */
  public static final int MSGID_LDAPSEARCH_VLV_ERROR =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_WARNING | 885;
  /**
   * The message ID for the message that will be used to indicate that the
   * virtual list view response control could not be decoded.  This takes a
   * single argument, which is a message explaining the problem that occurred.
   */
  public static final int MSGID_LDAPSEARCH_CANNOT_DECODE_VLV_RESPONSE =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_WARNING | 886;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -7389,6 +7481,9 @@
                    "filter");
    registerMessage(MSGID_DESCRIPTION_SORT_ORDER,
                    "Sort the results using the provided sort order");
    registerMessage(MSGID_DESCRIPTION_VLV,
                    "Use the virtual list view control to retrieve the " +
                    "specified results page");
    registerMessage(MSGID_COMPARE_CANNOT_BASE64_DECODE_ASSERTION_VALUE,
                    "The assertion value was indicated to be base64-encoded, " +
                    "but an error occurred while trying to decode the value");
@@ -7682,6 +7777,23 @@
                    "The provided matched values filter was invalid:  %s");
    registerMessage(MSGID_LDAP_SORTCONTROL_INVALID_ORDER,
                    "The provided sort order was invalid:  %s");
    registerMessage(MSGID_LDAPSEARCH_VLV_REQUIRES_SORT,
                    "If the --%s argument is provided, then the --%s " +
                    "argument must also be given");
    registerMessage(MSGID_LDAPSEARCH_VLV_INVALID_DESCRIPTOR,
                    "The provided virtual list view descriptor was invalid.  " +
                    "It must be a value in the form " +
                    "'beforeCount:afterCount:offset:contentCount' (where " +
                    "offset specifies the index of the target entry and " +
                    "contentCount specifies the estimated total number of " +
                    "results or zero if it is not known), or " +
                    "'beforeCount:afterCount:assertionValue' (where the " +
                    "entry should be the first entry whose primary sort " +
                    "value is greater than or equal to the provided " +
                    "assertionValue).  In either case, beforeCount is the " +
                    "number of entries to return before the target value and " +
                    "afterCount is the number of entries to return after " +
                    "the target value");
    registerMessage(MSGID_LDAPMODIFY_PREREAD_NO_VALUE,
                    "The pre-read response control did not include a value");
    registerMessage(MSGID_LDAPMODIFY_PREREAD_CANNOT_DECODE_VALUE,
@@ -8886,6 +8998,18 @@
                    "#   The account is locked");
    registerMessage(MSGID_LDAPSEARCH_ACCTUSABLE_TIME_UNTIL_UNLOCK,
                    "#   Time until the account is unlocked:  %s");
    registerMessage(MSGID_LDAPSEARCH_SORT_ERROR,
                    "# Server-side sort failed:  %s");
    registerMessage(MSGID_LDAPSEARCH_CANNOT_DECODE_SORT_RESPONSE,
                    "# Unable to decode the server-side sort response:  %s");
    registerMessage(MSGID_LDAPSEARCH_VLV_TARGET_OFFSET,
                    "# VLV Target Offset:  %d");
    registerMessage(MSGID_LDAPSEARCH_VLV_CONTENT_COUNT,
                    "# VLV Content Count:  %d");
    registerMessage(MSGID_LDAPSEARCH_VLV_ERROR,
                    "# Virtual list view processing failed:  %s");
    registerMessage(MSGID_LDAPSEARCH_CANNOT_DECODE_VLV_RESPONSE,
                    "# Unable to decode the virtual list view response:  %s");
    registerMessage(MSGID_LDAPSEARCH_MATCHING_ENTRY_COUNT,
                    "# Total number of matching entries:  %d");
opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPResultCode.java
@@ -287,6 +287,22 @@
  /**
   * The LDAP result code for operations that fail because the request included
   * a VLV request control without a server-side sort control.
   */
  public static final int SORT_CONTROL_MISSING = 60;
  /**
   * The LDAP result code for operations that fail because the request included
   * a VLV request control with an invalid offset.
   */
  public static final int OFFSET_RANGE_ERROR = 61;
  /**
   * The LDAP result code for operations that fail due to a naming violation.
   */
  public static final int NAMING_VIOLATION = 64;
@@ -343,6 +359,14 @@
  /**
   * The LDAP result code for operations that fail due to an error in
   * virtual list view processing.
   */
  public static final int VIRTUAL_LIST_VIEW_ERROR = 76;
  /**
   * The LDAP result code for use in cases in which none of the other defined
   * result codes are appropriate.
   */
@@ -664,6 +688,12 @@
      case LOOP_DETECT:
        msgID = MSGID_RESULT_LOOP_DETECT;
        break;
      case SORT_CONTROL_MISSING:
        msgID = MSGID_RESULT_SORT_CONTROL_MISSING;
        break;
      case OFFSET_RANGE_ERROR:
        msgID = MSGID_RESULT_OFFSET_RANGE_ERROR;
        break;
      case NAMING_VIOLATION:
        msgID = MSGID_RESULT_NAMING_VIOLATION;
        break;
@@ -685,6 +715,9 @@
      case AFFECTS_MULTIPLE_DSAS:
        msgID = MSGID_RESULT_AFFECTS_MULTIPLE_DSAS;
        break;
      case VIRTUAL_LIST_VIEW_ERROR:
        msgID = MSGID_RESULT_VIRTUAL_LIST_VIEW_ERROR;
        break;
      case CLIENT_SIDE_SERVER_DOWN:
        msgID = MSGID_RESULT_CLIENT_SIDE_SERVER_DOWN;
        break;
opendj-sdk/opends/src/server/org/opends/server/tools/LDAPSearch.java
@@ -46,6 +46,9 @@
import org.opends.server.controls.PersistentSearchChangeType;
import org.opends.server.controls.PersistentSearchControl;
import org.opends.server.controls.ServerSideSortRequestControl;
import org.opends.server.controls.ServerSideSortResponseControl;
import org.opends.server.controls.VLVRequestControl;
import org.opends.server.controls.VLVResponseControl;
import org.opends.server.core.DirectoryServer;
import org.opends.server.util.Base64;
import org.opends.server.util.PasswordReader;
@@ -63,6 +66,7 @@
import org.opends.server.protocols.ldap.LDAPControl;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
@@ -301,6 +305,66 @@
                errorMessage = searchOp.getErrorMessage();
                matchedDN = searchOp.getMatchedDN();
                for (LDAPControl c : responseMessage.getControls())
                {
                  if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
                  {
                    try
                    {
                      ServerSideSortResponseControl sortResponse =
                           ServerSideSortResponseControl.decodeControl(
                                c.getControl());
                      int rc = sortResponse.getResultCode();
                      if (rc != LDAPResultCode.SUCCESS)
                      {
                        int    msgID = MSGID_LDAPSEARCH_SORT_ERROR;
                        String msg   = getMessage(msgID,
                                                  LDAPResultCode.toString(rc));
                        err.println(msg);
                      }
                    }
                    catch (Exception e)
                    {
                      int msgID = MSGID_LDAPSEARCH_CANNOT_DECODE_SORT_RESPONSE;
                      String msg   = getMessage(msgID, getExceptionMessage(e));
                      err.println(msg);
                    }
                  }
                  else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
                  {
                    try
                    {
                      VLVResponseControl vlvResponse =
                           VLVResponseControl.decodeControl(c.getControl());
                      int rc = vlvResponse.getVLVResultCode();
                      if (rc == LDAPResultCode.SUCCESS)
                      {
                        int msgID = MSGID_LDAPSEARCH_VLV_TARGET_OFFSET;
                        String msg = getMessage(msgID,
                                          vlvResponse.getTargetPosition());
                        out.println(msg);
                        msgID = MSGID_LDAPSEARCH_VLV_CONTENT_COUNT;
                        msg = getMessage(msgID, vlvResponse.getContentCount());
                        out.println(msg);
                      }
                      else
                      {
                        int msgID = MSGID_LDAPSEARCH_VLV_ERROR;
                        String msg = getMessage(msgID,
                                                LDAPResultCode.toString(rc));
                        err.println(msg);
                      }
                    }
                    catch (Exception e)
                    {
                      int msgID = MSGID_LDAPSEARCH_CANNOT_DECODE_VLV_RESPONSE;
                      String msg   = getMessage(msgID, getExceptionMessage(e));
                      err.println(msg);
                    }
                  }
                }
                break;
              default:
                // FIXME - throw exception?
@@ -606,6 +670,7 @@
    StringArgument    sortOrder                = null;
    StringArgument    trustStorePath           = null;
    StringArgument    trustStorePassword       = null;
    StringArgument    vlvDescriptor            = null;
    // Create the command-line argument parser for use with this program.
@@ -795,6 +860,13 @@
                                     MSGID_DESCRIPTION_SORT_ORDER);
      argParser.addArgument(sortOrder);
      vlvDescriptor =
           new StringArgument("vlvdescriptor", 'G', "virtualListView", false,
                              false, true,
                              "{before:after:index:count | before:after:value}",
                              null, null, MSGID_DESCRIPTION_VLV);
      argParser.addArgument(vlvDescriptor);
      controlStr =
           new StringArgument("control", 'J', "control", false, true, true,
                    "{controloid[:criticality[:value|::b64value|:<fileurl]]}",
@@ -1284,6 +1356,69 @@
      }
    }
    if (vlvDescriptor.isPresent())
    {
      if (! sortOrder.isPresent())
      {
        int    msgID   = MSGID_LDAPSEARCH_VLV_REQUIRES_SORT;
        String message = getMessage(msgID, vlvDescriptor.getLongIdentifier(),
                                    sortOrder.getLongIdentifier());
        err.println(wrapText(message, MAX_LINE_WIDTH));
        return 1;
      }
      StringTokenizer tokenizer =
           new StringTokenizer(vlvDescriptor.getValue(), ":");
      int numTokens = tokenizer.countTokens();
      if (numTokens == 3)
      {
        try
        {
          int beforeCount = Integer.parseInt(tokenizer.nextToken());
          int afterCount  = Integer.parseInt(tokenizer.nextToken());
          ASN1OctetString assertionValue =
               new ASN1OctetString(tokenizer.nextToken());
          searchOptions.getControls().add(
               new LDAPControl(new VLVRequestControl(beforeCount, afterCount,
                                                     assertionValue)));
        }
        catch (Exception e)
        {
          int    msgID   = MSGID_LDAPSEARCH_VLV_INVALID_DESCRIPTOR;
          String message = getMessage(msgID);
          err.println(wrapText(message, MAX_LINE_WIDTH));
          return 1;
        }
      }
      else if (numTokens == 4)
      {
        try
        {
          int beforeCount  = Integer.parseInt(tokenizer.nextToken());
          int afterCount   = Integer.parseInt(tokenizer.nextToken());
          int offset       = Integer.parseInt(tokenizer.nextToken());
          int contentCount = Integer.parseInt(tokenizer.nextToken());
          searchOptions.getControls().add(
               new LDAPControl(new VLVRequestControl(beforeCount, afterCount,
                                                     offset, contentCount)));
        }
        catch (Exception e)
        {
          int    msgID   = MSGID_LDAPSEARCH_VLV_INVALID_DESCRIPTOR;
          String message = getMessage(msgID);
          err.println(wrapText(message, MAX_LINE_WIDTH));
          return 1;
        }
      }
      else
      {
        int    msgID   = MSGID_LDAPSEARCH_VLV_INVALID_DESCRIPTOR;
        String message = getMessage(msgID);
        err.println(wrapText(message, MAX_LINE_WIDTH));
        return 1;
      }
    }
    // Set the connection options.
    connectionOptions.setSASLExternal(saslExternal.isPresent());
    if(saslOptions.isPresent())
opendj-sdk/opends/src/server/org/opends/server/types/ResultCode.java
@@ -341,6 +341,24 @@
  /**
   * The result code that indicates that a search request included a
   * VLV request control without a server-side sort control.
   */
  SORT_CONTROL_MISSING(LDAPResultCode.SORT_CONTROL_MISSING,
                       MSGID_RESULT_SORT_CONTROL_MISSING),
  /**
   * The result code that indicates that a search request included a
   * VLV request control with an invalid offset.
   */
  OFFSET_RANGE_ERROR(LDAPResultCode.OFFSET_RANGE_ERROR,
                     MSGID_RESULT_OFFSET_RANGE_ERROR),
  /**
   * The result code that indicates that the requested operation
   * failed because it would have violated the server's naming
   * configuration.
@@ -410,6 +428,16 @@
  /**
   * The result code that indicates that the operation could not be
   * processed because there was an error while processing the virtual
   * list view control.
   */
  VIRTUAL_LIST_VIEW_ERROR(LDAPResultCode.VIRTUAL_LIST_VIEW_ERROR,
                          MSGID_RESULT_VIRTUAL_LIST_VIEW_ERROR),
  /**
   * The result code that should be used if no other result code is
   * appropriate.
   */
@@ -761,6 +789,10 @@
        return UNWILLING_TO_PERFORM;
      case LDAPResultCode.LOOP_DETECT:
        return LOOP_DETECT;
      case LDAPResultCode.SORT_CONTROL_MISSING:
        return SORT_CONTROL_MISSING;
      case LDAPResultCode.OFFSET_RANGE_ERROR:
        return OFFSET_RANGE_ERROR;
      case LDAPResultCode.NAMING_VIOLATION:
        return NAMING_VIOLATION;
      case LDAPResultCode.OBJECTCLASS_VIOLATION:
@@ -775,6 +807,8 @@
        return OBJECTCLASS_MODS_PROHIBITED;
      case LDAPResultCode.AFFECTS_MULTIPLE_DSAS:
        return AFFECTS_MULTIPLE_DSAS;
      case LDAPResultCode.VIRTUAL_LIST_VIEW_ERROR:
        return VIRTUAL_LIST_VIEW_ERROR;
      case LDAPResultCode.CLIENT_SIDE_SERVER_DOWN:
        return CLIENT_SIDE_SERVER_DOWN;
      case LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR:
opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -1851,6 +1851,22 @@
  /**
   * The OID for the virtual list view request control.
   */
  public static final String OID_VLV_REQUEST_CONTROL =
       "2.16.840.1.113730.3.4.9";
  /**
   * The OID for the virtual list view request control.
   */
  public static final String OID_VLV_RESPONSE_CONTROL =
       "2.16.840.1.113730.3.4.10";
  /**
   * The block length in bytes used when generating an HMAC-MD5 digest.
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java
New file
@@ -0,0 +1,1164 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.controls;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.types.AttributeType;
import org.opends.server.types.Control;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.SortKey;
import org.opends.server.types.SortOrder;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class contains a number of test cases for the virtual list view request
 * and response controls.
 */
public class VLVControlTestCase
    extends ControlsTestCase
{
  // The givenName attribute type.
  private AttributeType givenNameType;
  // The sn attribute type.
  private AttributeType snType;
  // The DN for "Aaccf Johnson"
  DN aaccfJohnsonDN;
  // The DN for "Aaron Zimmerman"
  DN aaronZimmermanDN;
  // The DN for "Albert Smith"
  DN albertSmithDN;
  // The DN for "Albert Zimmerman"
  DN albertZimmermanDN;
  // The DN for "lowercase mcgee"
  DN lowercaseMcGeeDN;
  // The DN for "Mararet Jones"
  DN margaretJonesDN;
  // The DN for "Mary Jones"
  DN maryJonesDN;
  // The DN for "Sam Zweck"
  DN samZweckDN;
  // The DN for "Zorro"
  DN zorroDN;
  /**
   * Make sure that the server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    givenNameType = DirectoryServer.getAttributeType("givenname", false);
    assertNotNull(givenNameType);
    snType = DirectoryServer.getAttributeType("sn", false);
    assertNotNull(snType);
    aaccfJohnsonDN    = DN.decode("uid=aaccf.johnson,dc=example,dc=com");
    aaronZimmermanDN  = DN.decode("uid=aaron.zimmerman,dc=example,dc=com");
    albertSmithDN     = DN.decode("uid=albert.smith,dc=example,dc=com");
    albertZimmermanDN = DN.decode("uid=albert.zimmerman,dc=example,dc=com");
    lowercaseMcGeeDN  = DN.decode("uid=lowercase.mcgee,dc=example,dc=com");
    margaretJonesDN   = DN.decode("uid=margaret.jones,dc=example,dc=com");
    maryJonesDN       = DN.decode("uid=mary.jones,dc=example,dc=com");
    samZweckDN        = DN.decode("uid=sam.zweck,dc=example,dc=com");
    zorroDN           = DN.decode("uid=zorro,dc=example,dc=com");
  }
  /**
   * Populates the JE DB with a set of test data.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void populateDB()
          throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman",
      "",
      "dn: uid=mary.jones,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: mary.jones",
      "givenName: Mary",
      "sn: Jones",
      "cn: Mary Jones",
      "",
      "dn: uid=margaret.jones,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: margaret.jones",
      "givenName: Margaret",
      "givenName: Maggie",
      "sn: Jones",
      "sn: Smith",
      "cn: Maggie Jones-Smith",
      "",
      "dn: uid=aaccf.johnson,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: aaccf.johnson",
      "givenName: Aaccf",
      "sn: Johnson",
      "cn: Aaccf Johnson",
      "",
      "dn: uid=sam.zweck,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: sam.zweck",
      "givenName: Sam",
      "sn: Zweck",
      "cn: Sam Zweck",
      "",
      "dn: uid=lowercase.mcgee,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: lowercase.mcgee",
      "givenName: lowercase",
      "sn: mcgee",
      "cn: lowercase mcgee",
      "",
      "dn: uid=zorro,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: zorro",
      "sn: Zorro",
      "cn: Zorro"
    );
  }
  /**
   * Tests the first constructor for the request control.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor1()
              throws Exception
  {
    VLVRequestControl vlvRequest = new VLVRequestControl(0, 9, 1, 0);
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getOffset(), 1);
    assertEquals(vlvRequest.getContentCount(), 0);
    assertNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(),
                 VLVRequestControl.TYPE_TARGET_BYOFFSET);
    assertNull(vlvRequest.getGreaterThanOrEqualAssertion());
    assertNotNull(vlvRequest.toString());
    assertNotNull(vlvRequest.decodeControl(vlvRequest));
  }
  /**
   * Tests the second constructor for the request control with a null context
   * ID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor2NullContextID()
              throws Exception
  {
    VLVRequestControl vlvRequest = new VLVRequestControl(0, 9, 1, 0, null);
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getOffset(), 1);
    assertEquals(vlvRequest.getContentCount(), 0);
    assertNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(),
                 VLVRequestControl.TYPE_TARGET_BYOFFSET);
    assertNull(vlvRequest.getGreaterThanOrEqualAssertion());
    assertNotNull(vlvRequest.toString());
    assertNotNull(vlvRequest.decodeControl(vlvRequest));
  }
  /**
   * Tests the second constructor for the request control with a non-null
   * context  ID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor2NonNullContextID()
              throws Exception
  {
    VLVRequestControl vlvRequest =
         new VLVRequestControl(0, 9, 1, 0, new ASN1OctetString("foo"));
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getOffset(), 1);
    assertEquals(vlvRequest.getContentCount(), 0);
    assertNotNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(),
                 VLVRequestControl.TYPE_TARGET_BYOFFSET);
    assertNull(vlvRequest.getGreaterThanOrEqualAssertion());
    assertNotNull(vlvRequest.toString());
    assertNotNull(vlvRequest.decodeControl(vlvRequest));
  }
  /**
   * Tests the third constructor for the request control.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor3()
              throws Exception
  {
    VLVRequestControl vlvRequest =
         new VLVRequestControl(0, 9, new ASN1OctetString("a"));
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getGreaterThanOrEqualAssertion().stringValue(),
                 "a");
    assertNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(),
                 VLVRequestControl.TYPE_TARGET_GREATERTHANOREQUAL);
    assertNotNull(vlvRequest.toString());
    assertNotNull(vlvRequest.decodeControl(vlvRequest));
  }
  /**
   * Tests the fourth constructor for the request control with a null context
   * ID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor4NullContextID()
              throws Exception
  {
    VLVRequestControl vlvRequest =
         new VLVRequestControl(0, 9, new ASN1OctetString("a"), null);
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getGreaterThanOrEqualAssertion().stringValue(),
                 "a");
    assertNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(),
                 VLVRequestControl.TYPE_TARGET_GREATERTHANOREQUAL);
    assertNotNull(vlvRequest.toString());
    assertNotNull(vlvRequest.decodeControl(vlvRequest));
  }
  /**
   * Tests the fourth constructor for the request control with a non-null
   * context ID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor4NonNullContextID()
              throws Exception
  {
    VLVRequestControl vlvRequest =
         new VLVRequestControl(0, 9, new ASN1OctetString("a"),
                               new ASN1OctetString("foo"));
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getGreaterThanOrEqualAssertion().stringValue(),
                 "a");
    assertNotNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(),
                 VLVRequestControl.TYPE_TARGET_GREATERTHANOREQUAL);
    assertNotNull(vlvRequest.toString());
    assertNotNull(vlvRequest.decodeControl(vlvRequest));
  }
  /**
   * Tests the first constructor for the response control.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testResponseConstructor1()
         throws Exception
  {
    VLVResponseControl vlvResponse = new VLVResponseControl(0, 15, 0);
    assertEquals(vlvResponse.getTargetPosition(), 0);
    assertEquals(vlvResponse.getContentCount(), 15);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertNull(vlvResponse.getContextID());
    assertNotNull(vlvResponse.toString());
    assertNotNull(vlvResponse.decodeControl(vlvResponse));
  }
  /**
   * Tests the second constructor for the response control with a null context
   * ID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testResponseConstructor2NullContextID()
         throws Exception
  {
    VLVResponseControl vlvResponse = new VLVResponseControl(0, 15, 0, null);
    assertEquals(vlvResponse.getTargetPosition(), 0);
    assertEquals(vlvResponse.getContentCount(), 15);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertNull(vlvResponse.getContextID());
    assertNotNull(vlvResponse.toString());
    assertNotNull(vlvResponse.decodeControl(vlvResponse));
  }
  /**
   * Tests the second constructor for the response control with a non-null
   * context ID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testResponseConstructor2NonNullContextID()
         throws Exception
  {
    VLVResponseControl vlvResponse =
         new VLVResponseControl(0, 15, 0, new ASN1OctetString("foo"));
    assertEquals(vlvResponse.getTargetPosition(), 0);
    assertEquals(vlvResponse.getContentCount(), 15);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertNotNull(vlvResponse.getContextID());
    assertNotNull(vlvResponse.toString());
    assertNotNull(vlvResponse.decodeControl(vlvResponse));
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using an offset of zero.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetZeroOffset()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, 1, 0));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(aaronZimmermanDN);  // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);
    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl            vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
      {
        sortResponse = ServerSideSortResponseControl.decodeControl(c);
      }
      else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
      else
      {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }
    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 1);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using a nonzero offset that still is completely
   * within the bounds of the result set.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetNonZeroOffset()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, 3, 0));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(margaretJonesDN);   // Maggie
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);
    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl            vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
      {
        sortResponse = ServerSideSortResponseControl.decodeControl(c);
      }
      else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
      else
      {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }
    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 3);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control with a negative
   * start position.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetNegativeStartPosition()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(3, 3, 1, 0));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    // It will be successful because it's not a critical control.
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    VLVResponseControl vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
    }
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(),
                 LDAPResultCode.OFFSET_RANGE_ERROR);
  }
  /**
   * Tests performing an internal search using the VLV control with a start
   * start position beyond the end of the result set.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetStartPositionTooHigh()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(3, 3, 30, 0));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    // It will be successful because it's not a critical control.
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    VLVResponseControl vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
    }
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(),
                 LDAPResultCode.OFFSET_RANGE_ERROR);
  }
  /**
   * Tests performing an internal search using the VLV control with a start
   * start position within the bounds of the list but not enough remaining
   * entries to meet the afterCount
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetIncompleteAfterCount()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, 7, 0));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(maryJonesDN);       // Mary
    expectedDNOrder.add(samZweckDN);        // Sam
    expectedDNOrder.add(zorroDN);           // No first name
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);
    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl            vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
      {
        sortResponse = ServerSideSortResponseControl.decodeControl(c);
      }
      else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
      else
      {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }
    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 7);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using an assertion value before any actual value in
   * the list.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueBeforeAll()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, new ASN1OctetString("a")));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(aaronZimmermanDN);  // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);
    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl            vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
      {
        sortResponse = ServerSideSortResponseControl.decodeControl(c);
      }
      else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
      else
      {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }
    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 1);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using an assertion value that matches the first value
   * in the list.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueMatchesFirst()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3,
                                              new ASN1OctetString("aaccf")));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(aaronZimmermanDN);  // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);
    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl            vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
      {
        sortResponse = ServerSideSortResponseControl.decodeControl(c);
      }
      else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
      else
      {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }
    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 1);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using an assertion value that matches the third value
   * in the list.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueMatchesThird()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3,
                                              new ASN1OctetString("albert")));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(margaretJonesDN);   // Maggie
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);
    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl            vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
      {
        sortResponse = ServerSideSortResponseControl.decodeControl(c);
      }
      else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
      else
      {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }
    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 3);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using an assertion value that matches the third value
   * in the list and includes a nonzero before count.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueMatchesThirdWithBeforeCount()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(1, 3,
                                              new ASN1OctetString("albert")));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaronZimmermanDN);  // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(margaretJonesDN);   // Maggie
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);
    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl            vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
      {
        sortResponse = ServerSideSortResponseControl.decodeControl(c);
      }
      else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
      else
      {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }
    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 3);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using an assertion value that is after all values in
   * the list.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueAfterAll()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("sn"));
    requestControls.add(new VLVRequestControl(0, 3, new ASN1OctetString("zz")));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    // It will be successful because the control isn't critical.
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    VLVResponseControl vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
    }
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(),
                 LDAPResultCode.OFFSET_RANGE_ERROR);
  }
}
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java
@@ -2273,6 +2273,271 @@
  /**
   * Tests the use of the virtual list view control without the server-side sort
   * control.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVLVWithoutSort()
         throws Exception
  {
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-G", "0:9:1:0",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of the server-side sort control with both the simple paged
   * results and virtual list view controls.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortWithVLVAndPagedResults()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "sn,givenName",
      "--simplePageSize", "2",
      "-G", "0:3:1:0",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of the virtual list view control with an invalid descriptor
   * with no colons.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVLVInvalidDescriptorNoColons()
         throws Exception
  {
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "sn,givenName",
      "-G", "invalid",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of the virtual list view control with an invalid descriptor
   * with two colons.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVLVInvalidDescriptorTwoColons()
         throws Exception
  {
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "sn,givenName",
      "-G", "invalid:9:invalid",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of the virtual list view control with an invalid descriptor
   * with three colons.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVLVInvalidDescriptorThreeColons()
         throws Exception
  {
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "sn,givenName",
      "-G", "invalid:9:1:0",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of both the server-side sort control and the virtual list
   * view control.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortWithVLV()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman",
      "",
      "dn: uid=mary.jones,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: mary.jones",
      "givenName: Mary",
      "sn: Jones",
      "cn: Mary Jones",
      "",
      "dn: uid=margaret.jones,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: margaret.jones",
      "givenName: Margaret",
      "givenName: Maggie",
      "sn: Jones",
      "sn: Smith",
      "cn: Maggie Jones-Smith",
      "",
      "dn: uid=aaccf.johnson,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: aaccf.johnson",
      "givenName: Aaccf",
      "sn: Johnson",
      "cn: Aaccf Johnson",
      "",
      "dn: uid=sam.zweck,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: sam.zweck",
      "givenName: Sam",
      "sn: Zweck",
      "cn: Sam Zweck",
      "",
      "dn: uid=lowercase.mcgee,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: lowercase.mcgee",
      "givenName: lowercase",
      "sn: mcgee",
      "cn: lowercase mcgee",
      "",
      "dn: uid=zorro,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: zorro",
      "sn: Zorro",
      "cn: Zorro");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "givenName",
      "-G", "1:3:1:0",
      "(objectClass=*)"
    };
    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
  }
  /**
   * Tests the LDAPSearch tool with the "--help" option.
   */
  @Test()