package com.hwlcn.ldap.ldap.sdk.controls;
import com.hwlcn.ldap.asn1.ASN1Element;
import com.hwlcn.ldap.asn1.ASN1Integer;
import com.hwlcn.ldap.asn1.ASN1OctetString;
import com.hwlcn.ldap.asn1.ASN1Sequence;
import com.hwlcn.ldap.ldap.sdk.Control;
import com.hwlcn.ldap.ldap.sdk.LDAPException;
import com.hwlcn.ldap.ldap.sdk.ResultCode;
import com.hwlcn.core.annotation.NotMutable;
import com.hwlcn.core.annotation.ThreadSafety;
import com.hwlcn.ldap.util.ThreadSafetyLevel;
import static com.hwlcn.ldap.ldap.sdk.controls.ControlMessages.*;
import static com.hwlcn.ldap.util.Debug.*;
import static com.hwlcn.ldap.util.StaticUtils.*;
import static com.hwlcn.ldap.util.Validator.*;
/**
* This class provides an implementation of the LDAP virtual list view (VLV)
* request control as defined in draft-ietf-ldapext-ldapv3-vlv. This control
* may be used to retrieve arbitrary "pages" of entries from the complete set of
* search results. It is similar to the {@link com.hwlcn.ldap.ldap.sdk.controls.SimplePagedResultsControl}, with
* the exception that the simple paged results control requires scrolling
* through the results in sequential order, while the VLV control allows
* starting and resuming at any arbitrary point in the result set. The starting
* point may be specified using either a positional offset, or based on the
* first entry with a value that is greater than or equal to a specified value.
* <BR><BR>
* When the start of the result set is to be specified using an offset, then the
* virtual list view request control should include the following elements:
* <UL>
* <LI>{@code targetOffset} -- The position in the result set of the entry to
* target for the next page of results to return. Note that the offset is
* one-based (so the first entry has offset 1, the second entry has offset
* 2, etc.).</LI>
* <LI>{@code beforeCount} -- The number of entries before the entry specified
* as the target offset that should be retrieved.</LI>
* <LI>{@code afterCount} -- The number of entries after the entry specified
* as the target offset that should be retrieved.</LI>
* <LI>{@code contentCount} -- The estimated total number of entries that
* are in the total result set. This should be zero for the first request
* in a VLV search sequence, but should be the value returned by the
* server in the corresponding response control for subsequent searches as
* part of the VLV sequence.</LI>
* <LI>{@code contextID} -- This is an optional cookie that may be used to
* help the server resume processing on a VLV search. It should be absent
* from the initial request, but for subsequent requests should be the
* value returned in the previous VLV response control.</LI>
* </UL>
* When the start of the result set is to be specified using a search string,
* then the virtual list view request control should include the following
* elements:
* <UL>
* <LI>{@code assertionValue} -- The value that specifies the start of the
* page of results to retrieve. The target entry will be the first entry
* in which the value for the primary sort attribute is greater than or
* equal to this assertion value.</LI>
* <LI>{@code beforeCount} -- The number of entries before the entry specified
* by the assertion value that should be retrieved.</LI>
* <LI>{@code afterCount} -- The number of entries after the entry specified
* by the assertion value that should be retrieved.</LI>
* <LI>{@code contentCount} -- The estimated total number of entries that
* are in the total result set. This should be zero for the first request
* in a VLV search sequence, but should be the value returned by the
* server in the corresponding response control for subsequent searches as
* part of the VLV sequence.</LI>
* <LI>{@code contextID} -- This is an optional cookie that may be used to
* help the server resume processing on a VLV search. It should be absent
* from the initial request, but for subsequent requests should be the
* value returned in the previous VLV response control.</LI>
* </UL>
* Note that the virtual list view request control may only be included in a
* search request if that search request also includes the
* {@link com.hwlcn.ldap.ldap.sdk.controls.ServerSideSortRequestControl}. This is necessary to ensure that a
* consistent order is used for the resulting entries.
* <BR><BR>
* If the search is successful, then the search result done response may include
* a {@link VirtualListViewResponseControl} to provide information about the
* state of the virtual list view processing.
* <BR><BR>
* <H2>Example</H2>
* The following example demonstrates the use of the virtual list view request
* control to iterate through all users in the "Sales" department, retrieving
* up to 10 entries at a time:
* <PRE>
* ServerSideSortRequestControl sortRequest =
* new ServerSideSortRequestControl(new SortKey("sn"),
* new SortKey("givenName"));
* SearchRequest searchRequest =
* new SearchRequest("dc=example,dc=com", SearchScope.SUB, "(ou=Sales)");
*
* int offset = 1;
* int contentCount = 0;
* ASN1OctetString contextID = null;
* do
* {
* VirtualListViewRequestControl vlvRequest =
* new VirtualListViewRequestControl(offset, 0, 9, contentCount,
* contextID);
* searchRequest.setControls(new Control[] { sortRequest, vlvRequest });
* SearchResult searchResult = connection.search();
*
* // Do something with the entries that are returned.
*
* contentCount = -1;
* VirtualListViewResponseControl c =
* VirtualListViewResponseControl.get(searchResult);
* if (c != null)
* {
* contentCount = c.getContentCount();
* contextID = c.getContextID();
* }
*
* offset += 10;
* } while (offset <= contentCount);
* </PRE>
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class VirtualListViewRequestControl
extends Control
{
public static final String VIRTUAL_LIST_VIEW_REQUEST_OID =
"2.16.840.1.113730.3.4.9";
private static final byte TARGET_TYPE_OFFSET = (byte) 0xA0;
private static final byte TARGET_TYPE_GREATER_OR_EQUAL = (byte) 0x81;
private static final long serialVersionUID = 4348423177859960815L;
private final ASN1OctetString assertionValue;
private final ASN1OctetString contextID;
private final int afterCount;
private final int beforeCount;
private final int contentCount;
private final int targetOffset;
public VirtualListViewRequestControl(final int targetOffset,
final int beforeCount, final int afterCount,
final int contentCount, final ASN1OctetString contextID)
{
this(targetOffset, beforeCount, afterCount, contentCount, contextID, true);
}
public VirtualListViewRequestControl(final String assertionValue,
final int beforeCount, final int afterCount,
final ASN1OctetString contextID)
{
this(new ASN1OctetString(assertionValue), beforeCount, afterCount,
contextID, true);
}
public VirtualListViewRequestControl(final byte[] assertionValue,
final int beforeCount, final int afterCount,
final ASN1OctetString contextID)
{
this(new ASN1OctetString(assertionValue), beforeCount, afterCount,
contextID, true);
}
public VirtualListViewRequestControl(final ASN1OctetString assertionValue,
final int beforeCount, final int afterCount,
final ASN1OctetString contextID)
{
this(assertionValue, beforeCount, afterCount, contextID, true);
}
public VirtualListViewRequestControl(final int targetOffset,
final int beforeCount, final int afterCount,
final int contentCount, final ASN1OctetString contextID,
final boolean isCritical)
{
super(VIRTUAL_LIST_VIEW_REQUEST_OID, isCritical,
encodeValue(targetOffset, beforeCount, afterCount, contentCount,
contextID));
this.targetOffset = targetOffset;
this.beforeCount = beforeCount;
this.afterCount = afterCount;
this.contentCount = contentCount;
this.contextID = contextID;
assertionValue = null;
}
public VirtualListViewRequestControl(final String assertionValue,
final int beforeCount, final int afterCount,
final ASN1OctetString contextID, final boolean isCritical)
{
this(new ASN1OctetString(assertionValue), beforeCount, afterCount,
contextID, isCritical);
}
public VirtualListViewRequestControl(final byte[] assertionValue,
final int beforeCount, final int afterCount,
final ASN1OctetString contextID, final boolean isCritical)
{
this(new ASN1OctetString(assertionValue), beforeCount, afterCount,
contextID, isCritical);
}
public VirtualListViewRequestControl(final ASN1OctetString assertionValue,
final int beforeCount, final int afterCount,
final ASN1OctetString contextID, final boolean isCritical)
{
super(VIRTUAL_LIST_VIEW_REQUEST_OID, isCritical,
encodeValue(assertionValue, beforeCount, afterCount, contextID));
this.assertionValue = assertionValue;
this.beforeCount = beforeCount;
this.afterCount = afterCount;
this.contextID = contextID;
targetOffset = -1;
contentCount = -1;
}
public VirtualListViewRequestControl(final Control control)
throws LDAPException
{
super(control);
final ASN1OctetString value = control.getValue();
if (value == null)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_VLV_REQUEST_NO_VALUE.get());
}
try
{
final ASN1Element valueElement = ASN1Element.decode(value.getValue());
final ASN1Element[] elements =
ASN1Sequence.decodeAsSequence(valueElement).elements();
beforeCount = ASN1Integer.decodeAsInteger(elements[0]).intValue();
afterCount = ASN1Integer.decodeAsInteger(elements[1]).intValue();
switch (elements[2].getType())
{
case TARGET_TYPE_OFFSET:
assertionValue = null;
final ASN1Element[] offsetElements =
ASN1Sequence.decodeAsSequence(elements[2]).elements();
targetOffset =
ASN1Integer.decodeAsInteger(offsetElements[0]).intValue();
contentCount =
ASN1Integer.decodeAsInteger(offsetElements[1]).intValue();
break;
case TARGET_TYPE_GREATER_OR_EQUAL:
assertionValue = ASN1OctetString.decodeAsOctetString(elements[2]);
targetOffset = -1;
contentCount = -1;
break;
default:
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_VLV_REQUEST_INVALID_ELEMENT_TYPE.get(
toHex(elements[2].getType())));
}
if (elements.length == 4)
{
contextID = ASN1OctetString.decodeAsOctetString(elements[3]);
}
else
{
contextID = null;
}
}
catch (LDAPException le)
{
debugException(le);
throw le;
}
catch (Exception e)
{
debugException(e);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_VLV_REQUEST_CANNOT_DECODE.get(e), e);
}
}
private static ASN1OctetString encodeValue(final int targetOffset,
final int beforeCount,
final int afterCount,
final int contentCount,
final ASN1OctetString contextID)
{
final ASN1Element[] targetElements =
{
new ASN1Integer(targetOffset),
new ASN1Integer(contentCount)
};
final ASN1Element[] vlvElements;
if (contextID == null)
{
vlvElements = new ASN1Element[]
{
new ASN1Integer(beforeCount),
new ASN1Integer(afterCount),
new ASN1Sequence(TARGET_TYPE_OFFSET, targetElements)
};
}
else
{
vlvElements = new ASN1Element[]
{
new ASN1Integer(beforeCount),
new ASN1Integer(afterCount),
new ASN1Sequence(TARGET_TYPE_OFFSET, targetElements),
contextID
};
}
return new ASN1OctetString(new ASN1Sequence(vlvElements).encode());
}
private static ASN1OctetString encodeValue(
final ASN1OctetString assertionValue,
final int beforeCount,
final int afterCount,
final ASN1OctetString contextID)
{
ensureNotNull(assertionValue);
final ASN1Element[] vlvElements;
if (contextID == null)
{
vlvElements = new ASN1Element[]
{
new ASN1Integer(beforeCount),
new ASN1Integer(afterCount),
new ASN1OctetString(TARGET_TYPE_GREATER_OR_EQUAL,
assertionValue.getValue())
};
}
else
{
vlvElements = new ASN1Element[]
{
new ASN1Integer(beforeCount),
new ASN1Integer(afterCount),
new ASN1OctetString(TARGET_TYPE_GREATER_OR_EQUAL,
assertionValue.getValue()),
contextID
};
}
return new ASN1OctetString(new ASN1Sequence(vlvElements).encode());
}
public int getTargetOffset()
{
return targetOffset;
}
public String getAssertionValueString()
{
if (assertionValue == null)
{
return null;
}
else
{
return assertionValue.stringValue();
}
}
public byte[] getAssertionValueBytes()
{
if (assertionValue == null)
{
return null;
}
else
{
return assertionValue.getValue();
}
}
public ASN1OctetString getAssertionValue()
{
return assertionValue;
}
public int getBeforeCount()
{
return beforeCount;
}
public int getAfterCount()
{
return afterCount;
}
public int getContentCount()
{
return contentCount;
}
public ASN1OctetString getContextID()
{
return contextID;
}
@Override()
public String getControlName()
{
return INFO_CONTROL_NAME_VLV_REQUEST.get();
}
@Override()
public void toString(final StringBuilder buffer)
{
buffer.append("VirtualListViewRequestControl(beforeCount=");
buffer.append(beforeCount);
buffer.append(", afterCount=");
buffer.append(afterCount);
if (assertionValue == null)
{
buffer.append(", targetOffset=");
buffer.append(targetOffset);
buffer.append(", contentCount=");
buffer.append(contentCount);
}
else
{
buffer.append(", assertionValue='");
buffer.append(assertionValue.stringValue());
buffer.append('\'');
}
buffer.append(", isCritical=");
buffer.append(isCritical());
buffer.append(')');
}
}