package com.hwlcn.ldap.ldap.sdk.controls;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Set;
import com.hwlcn.ldap.asn1.ASN1Boolean;
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.Validator.*;
/**
* This class provides an implementation of the persistent search request
* control as defined in draft-ietf-ldapext-psearch. It may be included in a
* search request to request notification for changes to entries that match the
* associated set of search criteria. It can provide a basic mechanism for
* clients to request to be notified whenever entries matching the associated
* search criteria are altered.
* <BR><BR>
* A persistent search request control may include the following elements:
* <UL>
* <LI>{@code changeTypes} -- Specifies the set of change types for which to
* receive notification. This may be any combination of one or more of
* the {@link com.hwlcn.ldap.ldap.sdk.controls.PersistentSearchChangeType} values.</LI>
* <LI>{@code changesOnly} -- Indicates whether to only return updated entries
* that match the associated search criteria. If this is {@code false},
* then the server will first return all existing entries in the server
* that match the search criteria, and will then begin returning entries
* that are updated in an operation associated with one of the
* registered {@code changeTypes}. If this is {@code true}, then the
* server will not return all matching entries that already exist in the
* server but will only return entries in response to changes that
* occur.</LI>
* <LI>{@code returnECs} -- Indicates whether search result entries returned
* as a result of a change to the directory data should include the
* {@link com.hwlcn.ldap.ldap.sdk.controls.EntryChangeNotificationControl} to provide information about
* the type of operation that occurred. If {@code changesOnly} is
* {@code false}, then entry change notification controls will not be
* included in existing entries that match the search criteria, but only
* in entries that are updated by an operation with one of the registered
* {@code changeTypes}.</LI>
* </UL>
* Note that when an entry is returned in response to a persistent search
* request, the content of the entry that is returned will reflect the updated
* entry in the server (except in the case of a delete operation, in which case
* it will be the entry as it appeared before it was removed). Other than the
* information included in the entry change notification control, the search
* result entry will not contain any information about what actually changed in
* the entry.
* <BR><BR>
* Many servers do not enforce time limit or size limit restrictions on the
* persistent search control, and because there is no defined "end" to the
* search, it may remain active until the client abandons or cancels the search
* or until the connection is closed. Because of this, it is strongly
* recommended that clients only use the persistent search request control in
* conjunction with asynchronous search operations invoked using the
* {@link com.hwlcn.ldap.ldap.sdk.LDAPConnection#asyncSearch} method.
* <BR><BR>
* <H2>Example</H2>
* The following example demonstrates the process for beginning an asynchronous
* search that includes the persistent search control in order to notify the
* client of all changes to entries within the "dc=example,dc=com" subtree.
* <PRE>
* SearchRequest searchRequest =
* new SearchRequest(myAsyncSearchListener, "dc=example,dc=com",
* SearchScope.SUB, "(objectClass=*)");
* searchRequest.addControl(new PersistentSearchRequestControl(
* PersistentSearchChangeType.allChangeTypes(), true, true));
* AsyncRequestID asyncRequestID = connection.asyncSearch(searchRequest);
* </PRE>
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class PersistentSearchRequestControl
extends Control
{
public static final String PERSISTENT_SEARCH_REQUEST_OID =
"2.16.840.1.113730.3.4.3";
private static final long serialVersionUID = 3532762682521779027L;
private final boolean changesOnly;
private final boolean returnECs;
private final EnumSet<PersistentSearchChangeType> changeTypes;
public PersistentSearchRequestControl(
final PersistentSearchChangeType changeType,
final boolean changesOnly, final boolean returnECs)
{
super(PERSISTENT_SEARCH_REQUEST_OID, true,
encodeValue(changeType, changesOnly, returnECs));
changeTypes = EnumSet.of(changeType);
this.changesOnly = changesOnly;
this.returnECs = returnECs;
}
public PersistentSearchRequestControl(
final Set<PersistentSearchChangeType> changeTypes,
final boolean changesOnly, final boolean returnECs)
{
super(PERSISTENT_SEARCH_REQUEST_OID, true,
encodeValue(changeTypes, changesOnly, returnECs));
this.changeTypes = EnumSet.copyOf(changeTypes);
this.changesOnly = changesOnly;
this.returnECs = returnECs;
}
public PersistentSearchRequestControl(
final PersistentSearchChangeType changeType,
final boolean changesOnly, final boolean returnECs,
final boolean isCritical)
{
super(PERSISTENT_SEARCH_REQUEST_OID, isCritical,
encodeValue(changeType, changesOnly, returnECs));
changeTypes = EnumSet.of(changeType);
this.changesOnly = changesOnly;
this.returnECs = returnECs;
}
public PersistentSearchRequestControl(
final Set<PersistentSearchChangeType> changeTypes,
final boolean changesOnly, final boolean returnECs,
final boolean isCritical)
{
super(PERSISTENT_SEARCH_REQUEST_OID, isCritical,
encodeValue(changeTypes, changesOnly, returnECs));
this.changeTypes = EnumSet.copyOf(changeTypes);
this.changesOnly = changesOnly;
this.returnECs = returnECs;
}
public PersistentSearchRequestControl(final Control control)
throws LDAPException
{
super(control);
final ASN1OctetString value = control.getValue();
if (value == null)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PSEARCH_NO_VALUE.get());
}
try
{
final ASN1Element valueElement = ASN1Element.decode(value.getValue());
final ASN1Element[] elements =
ASN1Sequence.decodeAsSequence(valueElement).elements();
changeTypes =
EnumSet.copyOf(PersistentSearchChangeType.decodeChangeTypes(
ASN1Integer.decodeAsInteger(elements[0]).intValue()));
changesOnly = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
returnECs = ASN1Boolean.decodeAsBoolean(elements[2]).booleanValue();
}
catch (Exception e)
{
debugException(e);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PSEARCH_CANNOT_DECODE.get(e), e);
}
}
private static ASN1OctetString encodeValue(
final PersistentSearchChangeType changeType,
final boolean changesOnly, final boolean returnECs)
{
ensureNotNull(changeType);
final ASN1Element[] elements =
{
new ASN1Integer(changeType.intValue()),
new ASN1Boolean(changesOnly),
new ASN1Boolean(returnECs)
};
return new ASN1OctetString(new ASN1Sequence(elements).encode());
}
private static ASN1OctetString encodeValue(
final Set<PersistentSearchChangeType> changeTypes,
final boolean changesOnly, final boolean returnECs)
{
ensureNotNull(changeTypes);
ensureFalse(changeTypes.isEmpty(),
"PersistentSearchRequestControl.changeTypes must not be empty.");
final ASN1Element[] elements =
{
new ASN1Integer(
PersistentSearchChangeType.encodeChangeTypes(changeTypes)),
new ASN1Boolean(changesOnly),
new ASN1Boolean(returnECs)
};
return new ASN1OctetString(new ASN1Sequence(elements).encode());
}
public Set<PersistentSearchChangeType> getChangeTypes()
{
return changeTypes;
}
public boolean changesOnly()
{
return changesOnly;
}
public boolean returnECs()
{
return returnECs;
}
@Override()
public String getControlName()
{
return INFO_CONTROL_NAME_PSEARCH_REQUEST.get();
}
@Override()
public void toString(final StringBuilder buffer)
{
buffer.append("PersistentSearchRequestControl(changeTypes={");
final Iterator<PersistentSearchChangeType> iterator =
changeTypes.iterator();
while (iterator.hasNext())
{
buffer.append(iterator.next().getName());
if (iterator.hasNext())
{
buffer.append(", ");
}
}
buffer.append("}, changesOnly=");
buffer.append(changesOnly);
buffer.append(", returnECs=");
buffer.append(returnECs);
buffer.append(", isCritical=");
buffer.append(isCritical());
buffer.append(')');
}
}