From 30f5175327333dcfaaeafb02560697bcdce6166d Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Mon, 30 Apr 2007 16:24:15 +0000
Subject: [PATCH] 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/src/server/org/opends/server/protocols/ldap/LDAPResultCode.java | 33
opends/src/server/org/opends/server/tools/LDAPSearch.java | 135 ++
opends/src/server/org/opends/server/messages/CoreMessages.java | 34
opends/src/server/org/opends/server/util/ServerConstants.java | 16
opends/src/server/org/opends/server/messages/ToolMessages.java | 124 ++
opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java | 1164 +++++++++++++++++++++
opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java | 181 +++
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java | 41
opends/src/server/org/opends/server/controls/VLVRequestControl.java | 584 +++++++++++
opends/src/server/org/opends/server/messages/ProtocolMessages.java | 98 +
opends/src/server/org/opends/server/types/ResultCode.java | 34
opends/src/server/org/opends/server/messages/JebMessages.java | 48
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java | 1
opends/src/server/org/opends/server/backends/jeb/SortValues.java | 35
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java | 265 +++++
opends/src/server/org/opends/server/controls/VLVResponseControl.java | 345 ++++++
16 files changed, 3,129 insertions(+), 9 deletions(-)
diff --git a/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java b/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
index 6088c48..6cd7658 100644
--- a/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
+++ b/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);
}
diff --git a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java b/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
index fa8029f..18eb18b 100644
--- a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
+++ b/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));
}
diff --git a/opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java b/opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java
index 768b3b2..5b685a5 100644
--- a/opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java
+++ b/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);
diff --git a/opends/src/server/org/opends/server/backends/jeb/SortValues.java b/opends/src/server/org/opends/server/backends/jeb/SortValues.java
index 2ee7f8d..1d26ff8 100644
--- a/opends/src/server/org/opends/server/backends/jeb/SortValues.java
+++ b/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(")");
}
}
diff --git a/opends/src/server/org/opends/server/controls/VLVRequestControl.java b/opends/src/server/org/opends/server/controls/VLVRequestControl.java
new file mode 100644
index 0000000..1e0ff6f
--- /dev/null
+++ b/opends/src/server/org/opends/server/controls/VLVRequestControl.java
@@ -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(")");
+ }
+}
+
diff --git a/opends/src/server/org/opends/server/controls/VLVResponseControl.java b/opends/src/server/org/opends/server/controls/VLVResponseControl.java
new file mode 100644
index 0000000..2960500
--- /dev/null
+++ b/opends/src/server/org/opends/server/controls/VLVResponseControl.java
@@ -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(")");
+ }
+}
+
diff --git a/opends/src/server/org/opends/server/messages/CoreMessages.java b/opends/src/server/org/opends/server/messages/CoreMessages.java
index e63864a..d27a8e9 100644
--- a/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/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");
diff --git a/opends/src/server/org/opends/server/messages/JebMessages.java b/opends/src/server/org/opends/server/messages/JebMessages.java
index bff6ec0..4c68e21 100644
--- a/opends/src/server/org/opends/server/messages/JebMessages.java
+++ b/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");
}
}
diff --git a/opends/src/server/org/opends/server/messages/ProtocolMessages.java b/opends/src/server/org/opends/server/messages/ProtocolMessages.java
index b54475a..156a19c 100644
--- a/opends/src/server/org/opends/server/messages/ProtocolMessages.java
+++ b/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"); }
}
diff --git a/opends/src/server/org/opends/server/messages/ToolMessages.java b/opends/src/server/org/opends/server/messages/ToolMessages.java
index 880adbe..c7647b3 100644
--- a/opends/src/server/org/opends/server/messages/ToolMessages.java
+++ b/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");
diff --git a/opends/src/server/org/opends/server/protocols/ldap/LDAPResultCode.java b/opends/src/server/org/opends/server/protocols/ldap/LDAPResultCode.java
index 9fc0e0f..114cb18 100644
--- a/opends/src/server/org/opends/server/protocols/ldap/LDAPResultCode.java
+++ b/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;
diff --git a/opends/src/server/org/opends/server/tools/LDAPSearch.java b/opends/src/server/org/opends/server/tools/LDAPSearch.java
index adbb71c..b799511 100644
--- a/opends/src/server/org/opends/server/tools/LDAPSearch.java
+++ b/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())
diff --git a/opends/src/server/org/opends/server/types/ResultCode.java b/opends/src/server/org/opends/server/types/ResultCode.java
index 0c3ea76..e5b60c6 100644
--- a/opends/src/server/org/opends/server/types/ResultCode.java
+++ b/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:
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index 8f49118..79cc242 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/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.
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java
new file mode 100644
index 0000000..9b63163
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java
@@ -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);
+ }
+}
+
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java
index f225b16..b641d03 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java
+++ b/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()
--
Gitblit v1.10.0