| | |
| | | */ |
| | | package org.opends.server.core; |
| | | |
| | | |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashSet; |
| | | import java.util.Iterator; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | import org.opends.server.api.Backend; |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.api.plugin.PostOperationPluginResult; |
| | | import org.opends.server.api.plugin.PreOperationPluginResult; |
| | | import org.opends.server.api.plugin.PreParsePluginResult; |
| | | import org.opends.server.api.plugin.SearchEntryPluginResult; |
| | | import org.opends.server.api.plugin.SearchReferencePluginResult; |
| | | import org.opends.server.controls.*; |
| | | import org.opends.server.protocols.asn1.ASN1OctetString; |
| | | import org.opends.server.protocols.ldap.LDAPFilter; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.AttributeValue; |
| | | import org.opends.server.controls.MatchedValuesControl; |
| | | import org.opends.server.types.ByteString; |
| | | import org.opends.server.types.CancelledOperationException; |
| | | import org.opends.server.types.CancelRequest; |
| | | import org.opends.server.types.CancelResult; |
| | | import org.opends.server.types.Control; |
| | | import org.opends.server.types.DebugLogLevel; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DereferencePolicy; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.DisconnectReason; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.FilterType; |
| | | import org.opends.server.types.LDAPException; |
| | | import org.opends.server.types.Operation; |
| | | import org.opends.server.types.OperationType; |
| | | import org.opends.server.types.Privilege; |
| | | import org.opends.server.types.RawFilter; |
| | | import org.opends.server.types.ResultCode; |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.types.SearchResultEntry; |
| | | import org.opends.server.types.SearchResultReference; |
| | | import org.opends.server.types.SearchScope; |
| | | import org.opends.server.types.operation.PostOperationSearchOperation; |
| | | import org.opends.server.types.operation.PostResponseSearchOperation; |
| | | import org.opends.server.types.operation.PreOperationSearchOperation; |
| | | import org.opends.server.types.operation.PreParseSearchOperation; |
| | | import org.opends.server.types.operation.SearchEntrySearchOperation; |
| | | import org.opends.server.types.operation.SearchReferenceSearchOperation; |
| | | import org.opends.server.util.TimeThread; |
| | | |
| | | import static org.opends.server.core.CoreConstants.*; |
| | | import static org.opends.server.loggers.AccessLogger.*; |
| | | import static org.opends.server.loggers.debug.DebugLogger.*; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import static org.opends.server.messages.CoreMessages.*; |
| | | import static org.opends.server.messages.MessageHandler.*; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class defines an operation that may be used to locate entries in the |
| | | * Directory Server based on a given set of criteria. |
| | | * This interface defines an operation used to search for entries |
| | | * in the Directory Server. |
| | | */ |
| | | public class SearchOperation |
| | | extends Operation |
| | | implements PreParseSearchOperation, PreOperationSearchOperation, |
| | | PostOperationSearchOperation, PostResponseSearchOperation, |
| | | SearchEntrySearchOperation, SearchReferenceSearchOperation |
| | | public interface SearchOperation extends Operation |
| | | { |
| | | /** |
| | | * The tracer object for the debug logger. |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | // Indicates whether a search result done response has been sent to the |
| | | // client. |
| | | private AtomicBoolean responseSent; |
| | | |
| | | // Indicates whether the client is able to handle referrals. |
| | | private boolean clientAcceptsReferrals; |
| | | |
| | | // Indicates whether to include the account usable control with search result |
| | | // entries. |
| | | private boolean includeUsableControl; |
| | | |
| | | // Indicates whether to only real attributes should be returned. |
| | | private boolean realAttributesOnly; |
| | | |
| | | // Indicates whether LDAP subentries should be returned. |
| | | private boolean returnLDAPSubentries; |
| | | |
| | | // Indicates whether to include attribute types only or both types and values. |
| | | private boolean typesOnly; |
| | | |
| | | // Indicates whether to only virtual attributes should be returned. |
| | | private boolean virtualAttributesOnly; |
| | | |
| | | // The raw, unprocessed base DN as included in the request from the client. |
| | | private ByteString rawBaseDN; |
| | | |
| | | // The cancel request that has been issued for this search operation. |
| | | private CancelRequest cancelRequest; |
| | | |
| | | // The dereferencing policy for the search operation. |
| | | private DereferencePolicy derefPolicy; |
| | | |
| | | // The base DN for the search operation. |
| | | private DN baseDN; |
| | | |
| | | // The proxied authorization target DN for this operation. |
| | | private DN proxiedAuthorizationDN; |
| | | |
| | | // The number of entries that have been sent to the client. |
| | | private int entriesSent; |
| | | |
| | | // The number of search result references that have been sent to the client. |
| | | private int referencesSent; |
| | | |
| | | // The size limit for the search operation. |
| | | private int sizeLimit; |
| | | |
| | | // The time limit for the search operation. |
| | | private int timeLimit; |
| | | |
| | | // The set of attributes that should be returned in matching entries. |
| | | private LinkedHashSet<String> attributes; |
| | | |
| | | // The set of response controls for this search operation. |
| | | private List<Control> responseControls; |
| | | |
| | | // The time that processing started on this operation. |
| | | private long processingStartTime; |
| | | |
| | | // The time that processing ended on this operation. |
| | | private long processingStopTime; |
| | | |
| | | // The time that the search time limit has expired. |
| | | private long timeLimitExpiration; |
| | | |
| | | // The matched values control associated with this search operation. |
| | | private MatchedValuesControl matchedValuesControl; |
| | | |
| | | // The persistent search associated with this search operation. |
| | | private PersistentSearch persistentSearch; |
| | | |
| | | // The raw, unprocessed filter as included in the request from the client. |
| | | private RawFilter rawFilter; |
| | | |
| | | // The search filter for the search operation. |
| | | private SearchFilter filter; |
| | | |
| | | // The search scope for the search operation. |
| | | private SearchScope scope; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new search operation with the provided information. |
| | | * |
| | | * @param clientConnection The client connection with which this operation |
| | | * is associated. |
| | | * @param operationID The operation ID for this operation. |
| | | * @param messageID The message ID of the request with which this |
| | | * operation is associated. |
| | | * @param requestControls The set of controls included in the request. |
| | | * @param rawBaseDN The raw, unprocessed base DN as included in the |
| | | * request from the client. |
| | | * @param scope The scope for this search operation. |
| | | * @param derefPolicy The alias dereferencing policy for this search |
| | | * operation. |
| | | * @param sizeLimit The size limit for this search operation. |
| | | * @param timeLimit The time limit for this search operation. |
| | | * @param typesOnly The typesOnly flag for this search operation. |
| | | * @param rawFilter the raw, unprocessed filter as included in the |
| | | * request from the client. |
| | | * @param attributes The requested attributes for this search |
| | | * operation. |
| | | */ |
| | | public SearchOperation(ClientConnection clientConnection, long operationID, |
| | | int messageID, List<Control> requestControls, |
| | | ByteString rawBaseDN, SearchScope scope, |
| | | DereferencePolicy derefPolicy, int sizeLimit, |
| | | int timeLimit, boolean typesOnly, RawFilter rawFilter, |
| | | LinkedHashSet<String> attributes) |
| | | { |
| | | super(clientConnection, operationID, messageID, requestControls); |
| | | |
| | | |
| | | this.rawBaseDN = rawBaseDN; |
| | | this.scope = scope; |
| | | this.derefPolicy = derefPolicy; |
| | | this.sizeLimit = sizeLimit; |
| | | this.timeLimit = timeLimit; |
| | | this.typesOnly = typesOnly; |
| | | this.rawFilter = rawFilter; |
| | | |
| | | if (attributes == null) |
| | | { |
| | | this.attributes = new LinkedHashSet<String>(0); |
| | | } |
| | | else |
| | | { |
| | | this.attributes = attributes; |
| | | } |
| | | |
| | | |
| | | if (clientConnection.getSizeLimit() <= 0) |
| | | { |
| | | this.sizeLimit = sizeLimit; |
| | | } |
| | | else |
| | | { |
| | | if (sizeLimit <= 0) |
| | | { |
| | | this.sizeLimit = clientConnection.getSizeLimit(); |
| | | } |
| | | else |
| | | { |
| | | this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit()); |
| | | } |
| | | } |
| | | |
| | | |
| | | if (clientConnection.getTimeLimit() <= 0) |
| | | { |
| | | this.timeLimit = timeLimit; |
| | | } |
| | | else |
| | | { |
| | | if (timeLimit <= 0) |
| | | { |
| | | this.timeLimit = clientConnection.getTimeLimit(); |
| | | } |
| | | else |
| | | { |
| | | this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit()); |
| | | } |
| | | } |
| | | |
| | | |
| | | baseDN = null; |
| | | filter = null; |
| | | entriesSent = 0; |
| | | referencesSent = 0; |
| | | responseControls = new ArrayList<Control>(); |
| | | cancelRequest = null; |
| | | clientAcceptsReferrals = true; |
| | | includeUsableControl = false; |
| | | responseSent = new AtomicBoolean(false); |
| | | persistentSearch = null; |
| | | returnLDAPSubentries = false; |
| | | matchedValuesControl = null; |
| | | realAttributesOnly = false; |
| | | virtualAttributesOnly = false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new search operation with the provided information. |
| | | * |
| | | * @param clientConnection The client connection with which this operation |
| | | * is associated. |
| | | * @param operationID The operation ID for this operation. |
| | | * @param messageID The message ID of the request with which this |
| | | * operation is associated. |
| | | * @param requestControls The set of controls included in the request. |
| | | * @param baseDN The base DN for this search operation. |
| | | * @param scope The scope for this search operation. |
| | | * @param derefPolicy The alias dereferencing policy for this search |
| | | * operation. |
| | | * @param sizeLimit The size limit for this search operation. |
| | | * @param timeLimit The time limit for this search operation. |
| | | * @param typesOnly The typesOnly flag for this search operation. |
| | | * @param filter The filter for this search operation. |
| | | * @param attributes The attributes for this search operation. |
| | | */ |
| | | public SearchOperation(ClientConnection clientConnection, long operationID, |
| | | int messageID, List<Control> requestControls, |
| | | DN baseDN, SearchScope scope, |
| | | DereferencePolicy derefPolicy, int sizeLimit, |
| | | int timeLimit, boolean typesOnly, SearchFilter filter, |
| | | LinkedHashSet<String> attributes) |
| | | { |
| | | super(clientConnection, operationID, messageID, requestControls); |
| | | |
| | | |
| | | this.baseDN = baseDN; |
| | | this.scope = scope; |
| | | this.derefPolicy = derefPolicy; |
| | | this.sizeLimit = sizeLimit; |
| | | this.timeLimit = timeLimit; |
| | | this.typesOnly = typesOnly; |
| | | this.filter = filter; |
| | | |
| | | if (attributes == null) |
| | | { |
| | | this.attributes = new LinkedHashSet<String>(0); |
| | | } |
| | | else |
| | | { |
| | | this.attributes = attributes; |
| | | } |
| | | |
| | | rawBaseDN = new ASN1OctetString(baseDN.toString()); |
| | | rawFilter = new LDAPFilter(filter); |
| | | |
| | | |
| | | if (clientConnection.getSizeLimit() <= 0) |
| | | { |
| | | this.sizeLimit = sizeLimit; |
| | | } |
| | | else |
| | | { |
| | | if (sizeLimit <= 0) |
| | | { |
| | | this.sizeLimit = clientConnection.getSizeLimit(); |
| | | } |
| | | else |
| | | { |
| | | this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit()); |
| | | } |
| | | } |
| | | |
| | | |
| | | if (clientConnection.getTimeLimit() <= 0) |
| | | { |
| | | this.timeLimit = timeLimit; |
| | | } |
| | | else |
| | | { |
| | | if (timeLimit <= 0) |
| | | { |
| | | this.timeLimit = clientConnection.getTimeLimit(); |
| | | } |
| | | else |
| | | { |
| | | this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit()); |
| | | } |
| | | } |
| | | |
| | | |
| | | entriesSent = 0; |
| | | referencesSent = 0; |
| | | responseControls = new ArrayList<Control>(); |
| | | cancelRequest = null; |
| | | clientAcceptsReferrals = true; |
| | | includeUsableControl = false; |
| | | responseSent = new AtomicBoolean(false); |
| | | persistentSearch = null; |
| | | returnLDAPSubentries = false; |
| | | matchedValuesControl = null; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Retrieves the raw, unprocessed base DN as included in the request from the |
| | |
| | | * @return The raw, unprocessed base DN as included in the request from the |
| | | * client. |
| | | */ |
| | | public final ByteString getRawBaseDN() |
| | | { |
| | | return rawBaseDN; |
| | | } |
| | | |
| | | |
| | | public abstract ByteString getRawBaseDN(); |
| | | |
| | | /** |
| | | * Specifies the raw, unprocessed base DN as included in the request from the |
| | |
| | | * @param rawBaseDN The raw, unprocessed base DN as included in the request |
| | | * from the client. |
| | | */ |
| | | public final void setRawBaseDN(ByteString rawBaseDN) |
| | | { |
| | | this.rawBaseDN = rawBaseDN; |
| | | |
| | | baseDN = null; |
| | | } |
| | | |
| | | |
| | | public abstract void setRawBaseDN(ByteString rawBaseDN); |
| | | |
| | | /** |
| | | * Retrieves the base DN for this search operation. This should not be called |
| | |
| | | * @return The base DN for this search operation, or <CODE>null</CODE> if the |
| | | * raw base DN has not yet been processed. |
| | | */ |
| | | public final DN getBaseDN() |
| | | { |
| | | return baseDN; |
| | | } |
| | | |
| | | |
| | | public abstract DN getBaseDN(); |
| | | |
| | | /** |
| | | * Specifies the base DN for this search operation. This method is only |
| | |
| | | * |
| | | * @param baseDN The base DN for this search operation. |
| | | */ |
| | | public final void setBaseDN(DN baseDN) |
| | | { |
| | | this.baseDN = baseDN; |
| | | } |
| | | |
| | | |
| | | public abstract void setBaseDN(DN baseDN); |
| | | |
| | | /** |
| | | * Retrieves the scope for this search operation. |
| | | * |
| | | * @return The scope for this search operation. |
| | | */ |
| | | public final SearchScope getScope() |
| | | { |
| | | return scope; |
| | | } |
| | | |
| | | |
| | | public abstract SearchScope getScope(); |
| | | |
| | | /** |
| | | * Specifies the scope for this search operation. This should only be called |
| | |
| | | * |
| | | * @param scope The scope for this search operation. |
| | | */ |
| | | public final void setScope(SearchScope scope) |
| | | { |
| | | this.scope = scope; |
| | | } |
| | | |
| | | |
| | | public abstract void setScope(SearchScope scope); |
| | | |
| | | /** |
| | | * Retrieves the alias dereferencing policy for this search operation. |
| | | * |
| | | * @return The alias dereferencing policy for this search operation. |
| | | */ |
| | | public final DereferencePolicy getDerefPolicy() |
| | | { |
| | | return derefPolicy; |
| | | } |
| | | |
| | | |
| | | public abstract DereferencePolicy getDerefPolicy(); |
| | | |
| | | /** |
| | | * Specifies the alias dereferencing policy for this search operation. This |
| | |
| | | * @param derefPolicy The alias dereferencing policy for this search |
| | | * operation. |
| | | */ |
| | | public final void setDerefPolicy(DereferencePolicy derefPolicy) |
| | | { |
| | | this.derefPolicy = derefPolicy; |
| | | } |
| | | |
| | | |
| | | public abstract void setDerefPolicy(DereferencePolicy derefPolicy); |
| | | |
| | | /** |
| | | * Retrieves the size limit for this search operation. |
| | | * |
| | | * @return The size limit for this search operation. |
| | | */ |
| | | public final int getSizeLimit() |
| | | { |
| | | return sizeLimit; |
| | | } |
| | | |
| | | |
| | | public abstract int getSizeLimit(); |
| | | |
| | | /** |
| | | * Specifies the size limit for this search operation. This should only be |
| | |
| | | * |
| | | * @param sizeLimit The size limit for this search operation. |
| | | */ |
| | | public final void setSizeLimit(int sizeLimit) |
| | | { |
| | | this.sizeLimit = sizeLimit; |
| | | } |
| | | |
| | | |
| | | public abstract void setSizeLimit(int sizeLimit); |
| | | |
| | | /** |
| | | * Retrieves the time limit for this search operation. |
| | | * |
| | | * @return The time limit for this search operation. |
| | | */ |
| | | public final int getTimeLimit() |
| | | { |
| | | return timeLimit; |
| | | } |
| | | public abstract int getTimeLimit(); |
| | | |
| | | |
| | | /** |
| | | * Get the time after which the search time limit has expired. |
| | | * |
| | | * @return the timeLimitExpiration |
| | | */ |
| | | public abstract Long getTimeLimitExpiration(); |
| | | |
| | | /** |
| | | * Specifies the time limit for this search operation. This should only be |
| | |
| | | * |
| | | * @param timeLimit The time limit for this search operation. |
| | | */ |
| | | public final void setTimeLimit(int timeLimit) |
| | | { |
| | | this.timeLimit = timeLimit; |
| | | } |
| | | |
| | | |
| | | public abstract void setTimeLimit(int timeLimit); |
| | | |
| | | /** |
| | | * Retrieves the typesOnly flag for this search operation. |
| | | * |
| | | * @return The typesOnly flag for this search operation. |
| | | */ |
| | | public final boolean getTypesOnly() |
| | | { |
| | | return typesOnly; |
| | | } |
| | | |
| | | |
| | | public abstract boolean getTypesOnly(); |
| | | |
| | | /** |
| | | * Specifies the typesOnly flag for this search operation. This should only |
| | |
| | | * |
| | | * @param typesOnly The typesOnly flag for this search operation. |
| | | */ |
| | | public final void setTypesOnly(boolean typesOnly) |
| | | { |
| | | this.typesOnly = typesOnly; |
| | | } |
| | | |
| | | |
| | | public abstract void setTypesOnly(boolean typesOnly); |
| | | |
| | | /** |
| | | * Retrieves the raw, unprocessed search filter as included in the request |
| | |
| | | * @return The raw, unprocessed search filter as included in the request from |
| | | * the client. |
| | | */ |
| | | public final RawFilter getRawFilter() |
| | | { |
| | | return rawFilter; |
| | | } |
| | | |
| | | |
| | | public abstract RawFilter getRawFilter(); |
| | | |
| | | /** |
| | | * Specifies the raw, unprocessed search filter as included in the request |
| | |
| | | * @param rawFilter The raw, unprocessed search filter as included in the |
| | | * request from the client. |
| | | */ |
| | | public final void setRawFilter(RawFilter rawFilter) |
| | | { |
| | | this.rawFilter = rawFilter; |
| | | |
| | | filter = null; |
| | | } |
| | | |
| | | |
| | | public abstract void setRawFilter(RawFilter rawFilter); |
| | | |
| | | /** |
| | | * Retrieves the filter for this search operation. This should not be called |
| | |
| | | * @return The filter for this search operation, or <CODE>null</CODE> if the |
| | | * raw filter has not yet been processed. |
| | | */ |
| | | public final SearchFilter getFilter() |
| | | { |
| | | return filter; |
| | | } |
| | | |
| | | |
| | | public abstract SearchFilter getFilter(); |
| | | |
| | | /** |
| | | * Retrieves the set of requested attributes for this search operation. Its |
| | |
| | | * |
| | | * @return The set of requested attributes for this search operation. |
| | | */ |
| | | public final LinkedHashSet<String> getAttributes() |
| | | { |
| | | return attributes; |
| | | } |
| | | |
| | | |
| | | public abstract LinkedHashSet<String> getAttributes(); |
| | | |
| | | /** |
| | | * Specifies the set of requested attributes for this search operation. It |
| | |
| | | * @param attributes The set of requested attributes for this search |
| | | * operation. |
| | | */ |
| | | public final void setAttributes(LinkedHashSet<String> attributes) |
| | | { |
| | | if (attributes == null) |
| | | { |
| | | this.attributes.clear(); |
| | | } |
| | | else |
| | | { |
| | | this.attributes = attributes; |
| | | } |
| | | } |
| | | |
| | | |
| | | public abstract void setAttributes(LinkedHashSet<String> attributes); |
| | | |
| | | /** |
| | | * Retrieves the number of entries sent to the client for this search |
| | |
| | | * @return The number of entries sent to the client for this search |
| | | * operation. |
| | | */ |
| | | public final int getEntriesSent() |
| | | { |
| | | return entriesSent; |
| | | } |
| | | |
| | | |
| | | public abstract int getEntriesSent(); |
| | | |
| | | /** |
| | | * Retrieves the number of search references sent to the client for this |
| | |
| | | * @return The number of search references sent to the client for this search |
| | | * operation. |
| | | */ |
| | | public final int getReferencesSent() |
| | | { |
| | | return referencesSent; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final long getProcessingStartTime() |
| | | { |
| | | return processingStartTime; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final long getProcessingStopTime() |
| | | { |
| | | return processingStopTime; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final long getProcessingTime() |
| | | { |
| | | return (processingStopTime - processingStartTime); |
| | | } |
| | | |
| | | |
| | | public abstract int getReferencesSent(); |
| | | |
| | | /** |
| | | * Used as a callback for backends to indicate that the provided entry matches |
| | |
| | | * <CODE>false</CODE> if not for some reason (e.g., the size limit |
| | | * has been reached or the search has been abandoned). |
| | | */ |
| | | public final boolean returnEntry(Entry entry, List<Control> controls) |
| | | { |
| | | // See if the operation has been abandoned. If so, then don't send the |
| | | // entry and indicate that the search should end. |
| | | if (cancelRequest != null) |
| | | { |
| | | setResultCode(ResultCode.CANCELED); |
| | | return false; |
| | | } |
| | | |
| | | // See if the size limit has been exceeded. If so, then don't send the |
| | | // entry and indicate that the search should end. |
| | | if ((sizeLimit > 0) && (entriesSent >= sizeLimit)) |
| | | { |
| | | setResultCode(ResultCode.SIZE_LIMIT_EXCEEDED); |
| | | appendErrorMessage(getMessage(MSGID_SEARCH_SIZE_LIMIT_EXCEEDED, |
| | | sizeLimit)); |
| | | return false; |
| | | } |
| | | |
| | | // See if the time limit has expired. If so, then don't send the entry and |
| | | // indicate that the search should end. |
| | | if ((timeLimit > 0) && (TimeThread.getTime() >= timeLimitExpiration)) |
| | | { |
| | | setResultCode(ResultCode.TIME_LIMIT_EXCEEDED); |
| | | appendErrorMessage(getMessage(MSGID_SEARCH_TIME_LIMIT_EXCEEDED, |
| | | timeLimit)); |
| | | return false; |
| | | } |
| | | |
| | | // Determine whether the provided entry is a subentry and if so whether it |
| | | // should be returned. |
| | | if ((scope != SearchScope.BASE_OBJECT) && (! returnLDAPSubentries) && |
| | | entry.isLDAPSubentry()) |
| | | { |
| | | // Check to see if the filter contains an equality element with the |
| | | // objectclass attribute type and a value of "ldapSubentry". If so, then |
| | | // we'll return it anyway. Technically, this isn't part of the |
| | | // specification so we don't need to get carried away with really in-depth |
| | | // checks. |
| | | switch (filter.getFilterType()) |
| | | { |
| | | case AND: |
| | | case OR: |
| | | for (SearchFilter f : filter.getFilterComponents()) |
| | | { |
| | | if ((f.getFilterType() == FilterType.EQUALITY) && |
| | | (f.getAttributeType().isObjectClassType())) |
| | | { |
| | | AttributeValue v = f.getAssertionValue(); |
| | | if (toLowerCase(v.getStringValue()).equals("ldapsubentry")) |
| | | { |
| | | returnLDAPSubentries = true; |
| | | } |
| | | break; |
| | | } |
| | | } |
| | | break; |
| | | case EQUALITY: |
| | | AttributeType t = filter.getAttributeType(); |
| | | if (t.isObjectClassType()) |
| | | { |
| | | AttributeValue v = filter.getAssertionValue(); |
| | | if (toLowerCase(v.getStringValue()).equals("ldapsubentry")) |
| | | { |
| | | returnLDAPSubentries = true; |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | |
| | | if (! returnLDAPSubentries) |
| | | { |
| | | // We still shouldn't return it even based on the filter. Just throw it |
| | | // away without doing anything. |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Determine whether to include the account usable control. If so, then |
| | | // create it now. |
| | | if (includeUsableControl) |
| | | { |
| | | try |
| | | { |
| | | // FIXME -- Need a way to enable PWP debugging. |
| | | PasswordPolicyState pwpState = new PasswordPolicyState(entry, false, |
| | | false); |
| | | |
| | | boolean isInactive = pwpState.isDisabled() || |
| | | pwpState.isAccountExpired(); |
| | | boolean isLocked = pwpState.lockedDueToFailures() || |
| | | pwpState.lockedDueToMaximumResetAge() || |
| | | pwpState.lockedDueToIdleInterval(); |
| | | boolean isReset = pwpState.mustChangePassword(); |
| | | boolean isExpired = pwpState.isPasswordExpired(); |
| | | |
| | | if (isInactive || isLocked || isReset || isExpired) |
| | | { |
| | | int secondsBeforeUnlock = pwpState.getSecondsUntilUnlock(); |
| | | int remainingGraceLogins = pwpState.getGraceLoginsRemaining(); |
| | | |
| | | if (controls == null) |
| | | { |
| | | controls = new ArrayList<Control>(1); |
| | | } |
| | | |
| | | controls.add(new AccountUsableResponseControl(isInactive, isReset, |
| | | isExpired, remainingGraceLogins, isLocked, |
| | | secondsBeforeUnlock)); |
| | | } |
| | | else |
| | | { |
| | | if (controls == null) |
| | | { |
| | | controls = new ArrayList<Control>(1); |
| | | } |
| | | |
| | | int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration(); |
| | | controls.add(new AccountUsableResponseControl( |
| | | secondsBeforeExpiration)); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Check to see if the entry can be read by the client. |
| | | SearchResultEntry tmpSearchEntry = new SearchResultEntry(entry, |
| | | controls); |
| | | if (AccessControlConfigManager.getInstance() |
| | | .getAccessControlHandler().maySend(this, tmpSearchEntry) == false) { |
| | | return true; |
| | | } |
| | | |
| | | // Make a copy of the entry and pare it down to only include the set |
| | | // of |
| | | // requested attributes. |
| | | Entry entryToReturn; |
| | | if ((attributes == null) || attributes.isEmpty()) |
| | | { |
| | | entryToReturn = entry.duplicateWithoutOperationalAttributes(typesOnly, |
| | | true); |
| | | } |
| | | else |
| | | { |
| | | entryToReturn = entry.duplicateWithoutAttributes(); |
| | | |
| | | for (String attrName : attributes) |
| | | { |
| | | if (attrName.equals("*")) |
| | | { |
| | | // This is a special placeholder indicating that all user attributes |
| | | // should be returned. |
| | | if (typesOnly) |
| | | { |
| | | // First, add the placeholder for the objectclass attribute. |
| | | AttributeType ocType = |
| | | DirectoryServer.getObjectClassAttributeType(); |
| | | List<Attribute> ocList = new ArrayList<Attribute>(1); |
| | | ocList.add(new Attribute(ocType)); |
| | | entryToReturn.putAttribute(ocType, ocList); |
| | | } |
| | | else |
| | | { |
| | | // First, add the objectclass attribute. |
| | | Attribute ocAttr = entry.getObjectClassAttribute(); |
| | | try |
| | | { |
| | | entryToReturn.setObjectClasses(ocAttr.getValues()); |
| | | } |
| | | catch (DirectoryException e) |
| | | { |
| | | // We cannot get this exception because the object classes have |
| | | // already been validated in the entry they came from. |
| | | } |
| | | } |
| | | |
| | | // Next iterate through all the user attributes and include them. |
| | | for (AttributeType t : entry.getUserAttributes().keySet()) |
| | | { |
| | | List<Attribute> attrList = |
| | | entry.duplicateUserAttribute(t, null, typesOnly); |
| | | entryToReturn.putAttribute(t, attrList); |
| | | } |
| | | |
| | | continue; |
| | | } |
| | | else if (attrName.equals("+")) |
| | | { |
| | | // This is a special placeholder indicating that all operational |
| | | // attributes should be returned. |
| | | for (AttributeType t : entry.getOperationalAttributes().keySet()) |
| | | { |
| | | List<Attribute> attrList = |
| | | entry.duplicateOperationalAttribute(t, null, typesOnly); |
| | | entryToReturn.putAttribute(t, attrList); |
| | | } |
| | | |
| | | continue; |
| | | } |
| | | |
| | | String lowerName; |
| | | HashSet<String> options; |
| | | int semicolonPos = attrName.indexOf(';'); |
| | | if (semicolonPos > 0) |
| | | { |
| | | lowerName = toLowerCase(attrName.substring(0, semicolonPos)); |
| | | int nextPos = attrName.indexOf(';', semicolonPos+1); |
| | | options = new HashSet<String>(); |
| | | while (nextPos > 0) |
| | | { |
| | | options.add(attrName.substring(semicolonPos+1, nextPos)); |
| | | |
| | | semicolonPos = nextPos; |
| | | nextPos = attrName.indexOf(';', semicolonPos+1); |
| | | } |
| | | |
| | | options.add(attrName.substring(semicolonPos+1)); |
| | | } |
| | | else |
| | | { |
| | | lowerName = toLowerCase(attrName); |
| | | options = null; |
| | | } |
| | | |
| | | |
| | | AttributeType attrType = DirectoryServer.getAttributeType(lowerName); |
| | | if (attrType == null) |
| | | { |
| | | boolean added = false; |
| | | for (AttributeType t : entry.getUserAttributes().keySet()) |
| | | { |
| | | if (t.hasNameOrOID(lowerName)) |
| | | { |
| | | List<Attribute> attrList = |
| | | entry.duplicateUserAttribute(t, options, typesOnly); |
| | | if (attrList != null) |
| | | { |
| | | entryToReturn.putAttribute(t, attrList); |
| | | |
| | | added = true; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (added) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | for (AttributeType t : entry.getOperationalAttributes().keySet()) |
| | | { |
| | | if (t.hasNameOrOID(lowerName)) |
| | | { |
| | | List<Attribute> attrList = |
| | | entry.duplicateOperationalAttribute(t, options, typesOnly); |
| | | if (attrList != null) |
| | | { |
| | | entryToReturn.putAttribute(t, attrList); |
| | | |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | else |
| | | { |
| | | if (attrType.isObjectClassType()) { |
| | | if (typesOnly) |
| | | { |
| | | AttributeType ocType = |
| | | DirectoryServer.getObjectClassAttributeType(); |
| | | List<Attribute> ocList = new ArrayList<Attribute>(1); |
| | | ocList.add(new Attribute(ocType)); |
| | | entryToReturn.putAttribute(ocType, ocList); |
| | | } |
| | | else |
| | | { |
| | | List<Attribute> attrList = new ArrayList<Attribute>(1); |
| | | attrList.add(entry.getObjectClassAttribute()); |
| | | entryToReturn.putAttribute(attrType, attrList); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | List<Attribute> attrList = |
| | | entry.duplicateOperationalAttribute(attrType, options, |
| | | typesOnly); |
| | | if (attrList == null) |
| | | { |
| | | attrList = entry.duplicateUserAttribute(attrType, options, |
| | | typesOnly); |
| | | } |
| | | if (attrList != null) |
| | | { |
| | | entryToReturn.putAttribute(attrType, attrList); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | if (realAttributesOnly) |
| | | { |
| | | entryToReturn.stripVirtualAttributes(); |
| | | } |
| | | else if (virtualAttributesOnly) |
| | | { |
| | | entryToReturn.stripRealAttributes(); |
| | | } |
| | | |
| | | |
| | | // If there is a matched values control, then further pare down the entry |
| | | // based on the filters that it contains. |
| | | if ((matchedValuesControl != null) && (! typesOnly)) |
| | | { |
| | | // First, look at the set of objectclasses. |
| | | AttributeType attrType = DirectoryServer.getObjectClassAttributeType(); |
| | | Iterator<String> ocIterator = |
| | | entryToReturn.getObjectClasses().values().iterator(); |
| | | while (ocIterator.hasNext()) |
| | | { |
| | | String ocName = ocIterator.next(); |
| | | AttributeValue v = new AttributeValue(attrType, |
| | | new ASN1OctetString(ocName)); |
| | | if (! matchedValuesControl.valueMatches(attrType, v)) |
| | | { |
| | | ocIterator.remove(); |
| | | } |
| | | } |
| | | |
| | | |
| | | // Next, the set of user attributes. |
| | | for (AttributeType t : entryToReturn.getUserAttributes().keySet()) |
| | | { |
| | | for (Attribute a : entryToReturn.getUserAttribute(t)) |
| | | { |
| | | Iterator<AttributeValue> valueIterator = a.getValues().iterator(); |
| | | while (valueIterator.hasNext()) |
| | | { |
| | | AttributeValue v = valueIterator.next(); |
| | | if (! matchedValuesControl.valueMatches(t, v)) |
| | | { |
| | | valueIterator.remove(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Then the set of operational attributes. |
| | | for (AttributeType t : entryToReturn.getOperationalAttributes().keySet()) |
| | | { |
| | | for (Attribute a : entryToReturn.getOperationalAttribute(t)) |
| | | { |
| | | Iterator<AttributeValue> valueIterator = a.getValues().iterator(); |
| | | while (valueIterator.hasNext()) |
| | | { |
| | | AttributeValue v = valueIterator.next(); |
| | | if (! matchedValuesControl.valueMatches(t, v)) |
| | | { |
| | | valueIterator.remove(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Convert the provided entry to a search result entry. |
| | | SearchResultEntry searchEntry = new SearchResultEntry(entryToReturn, |
| | | controls); |
| | | |
| | | // Strip out any attributes that the client does not have access to. |
| | | |
| | | // FIXME: need some way to prevent plugins from adding attributes or |
| | | // values that the client is not permitted to see. |
| | | searchEntry = AccessControlConfigManager.getInstance() |
| | | .getAccessControlHandler().filterEntry(this, searchEntry); |
| | | |
| | | // Invoke any search entry plugins that may be registered with the server. |
| | | SearchEntryPluginResult pluginResult = |
| | | DirectoryServer.getPluginConfigManager(). |
| | | invokeSearchResultEntryPlugins(this, searchEntry); |
| | | if (pluginResult.connectionTerminated()) |
| | | { |
| | | // We won't attempt to send this entry, and we won't continue with |
| | | // any processing. Just update the operation to indicate that it was |
| | | // cancelled and return false. |
| | | setResultCode(ResultCode.CANCELED); |
| | | appendErrorMessage(getMessage(MSGID_CANCELED_BY_SEARCH_ENTRY_DISCONNECT, |
| | | String.valueOf(entry.getDN()))); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // Send the entry to the client. |
| | | if (pluginResult.sendEntry()) |
| | | { |
| | | try |
| | | { |
| | | clientConnection.sendSearchEntry(this, searchEntry); |
| | | |
| | | // Log the entry sent to the client. |
| | | logSearchResultEntry(this, searchEntry); |
| | | |
| | | entriesSent++; |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResponseData(de); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return pluginResult.continueSearch(); |
| | | } |
| | | |
| | | |
| | | public abstract boolean returnEntry(Entry entry, List<Control> controls); |
| | | |
| | | /** |
| | | * Used as a callback for backends to indicate that the provided search |
| | |
| | | * <CODE>false</CODE> if not for some reason (e.g., the size limit |
| | | * has been reached or the search has been abandoned). |
| | | */ |
| | | public final boolean returnReference(SearchResultReference reference) |
| | | { |
| | | // See if the operation has been abandoned. If so, then don't send the |
| | | // reference and indicate that the search should end. |
| | | if (cancelRequest != null) |
| | | { |
| | | setResultCode(ResultCode.CANCELED); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // See if the time limit has expired. If so, then don't send the entry and |
| | | // indicate that the search should end. |
| | | if ((timeLimit > 0) && (TimeThread.getTime() >= timeLimitExpiration)) |
| | | { |
| | | setResultCode(ResultCode.TIME_LIMIT_EXCEEDED); |
| | | appendErrorMessage(getMessage(MSGID_SEARCH_TIME_LIMIT_EXCEEDED, |
| | | timeLimit)); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // See if we know that this client can't handle referrals. If so, then |
| | | // don't even try to send it. |
| | | if (! clientAcceptsReferrals) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | |
| | | // See if the client has permission to read this reference. |
| | | if (AccessControlConfigManager.getInstance() |
| | | .getAccessControlHandler().maySend(this, reference) == false) { |
| | | return true; |
| | | } |
| | | |
| | | |
| | | // Invoke any search reference plugins that may be registered with the |
| | | // server. |
| | | SearchReferencePluginResult pluginResult = |
| | | DirectoryServer.getPluginConfigManager(). |
| | | invokeSearchResultReferencePlugins(this, reference); |
| | | if (pluginResult.connectionTerminated()) |
| | | { |
| | | // We won't attempt to send this entry, and we won't continue with |
| | | // any processing. Just update the operation to indicate that it was |
| | | // cancelled and return false. |
| | | setResultCode(ResultCode.CANCELED); |
| | | appendErrorMessage(getMessage(MSGID_CANCELED_BY_SEARCH_REF_DISCONNECT, |
| | | String.valueOf(reference.getReferralURLString()))); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // Send the reference to the client. Note that this could throw an |
| | | // exception, which would indicate that the associated client can't handle |
| | | // referrals. If that't the case, then set a flag so we'll know not to try |
| | | // to send any more. |
| | | if (pluginResult.sendReference()) |
| | | { |
| | | try |
| | | { |
| | | if (clientConnection.sendSearchReference(this, reference)) |
| | | { |
| | | // Log the entry sent to the client. |
| | | logSearchResultReference(this, reference); |
| | | referencesSent++; |
| | | |
| | | // FIXME -- Should the size limit apply here? |
| | | } |
| | | else |
| | | { |
| | | // We know that the client can't handle referrals, so we won't try to |
| | | // send it any more. |
| | | clientAcceptsReferrals = false; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResponseData(de); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | |
| | | return pluginResult.continueSearch(); |
| | | } |
| | | |
| | | |
| | | public abstract boolean returnReference(SearchResultReference reference); |
| | | |
| | | /** |
| | | * Sends the search result done message to the client. Note that this method |
| | |
| | | * message should have been set for this operation before this method is |
| | | * called. |
| | | */ |
| | | public final void sendSearchResultDone() |
| | | { |
| | | // Send the search result done message to the client. We want to make sure |
| | | // that this only gets sent once, and it's possible that this could be |
| | | // multithreaded in the event of a persistent search, so do it safely. |
| | | if (responseSent.compareAndSet(false, true)) |
| | | { |
| | | // Send the response to the client. |
| | | clientConnection.sendResponse(this); |
| | | |
| | | // Log the search result. |
| | | logSearchResultDone(this); |
| | | |
| | | |
| | | // Invoke the post-response search plugins. |
| | | DirectoryServer.getPluginConfigManager(). |
| | | invokePostResponseSearchPlugins(this); |
| | | } |
| | | } |
| | | |
| | | |
| | | public abstract void sendSearchResultDone(); |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | * Set the time after which the search time limit has expired. |
| | | * |
| | | * @param timeLimitExpiration - Time after which the search has expired |
| | | */ |
| | | @Override() |
| | | public final OperationType getOperationType() |
| | | { |
| | | // Note that no debugging will be done in this method because it is a likely |
| | | // candidate for being called by the logging subsystem. |
| | | |
| | | return OperationType.SEARCH; |
| | | } |
| | | |
| | | |
| | | public abstract void setTimeLimitExpiration(Long timeLimitExpiration); |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | * Indicates whether LDAP subentries should be returned or not. |
| | | * |
| | | * @return true if the LDAP subentries should be returned, false otherwise |
| | | */ |
| | | @Override() |
| | | public final void disconnectClient(DisconnectReason disconnectReason, |
| | | boolean sendNotification, String message, |
| | | int messageID) |
| | | { |
| | | // Before calling clientConnection.disconnect, we need to mark this |
| | | // operation as cancelled so that the attempt to cancel it later won't cause |
| | | // an unnecessary delay. |
| | | setCancelResult(CancelResult.CANCELED); |
| | | |
| | | clientConnection.disconnect(disconnectReason, sendNotification, message, |
| | | messageID); |
| | | } |
| | | |
| | | |
| | | public abstract boolean isReturnLDAPSubentries(); |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | * Set the flag indicating wether the LDAP subentries should be returned. |
| | | * |
| | | * @param returnLDAPSubentries - Boolean indicating wether the LDAP |
| | | * subentries should be returned or not |
| | | */ |
| | | @Override() |
| | | public final String[][] getRequestLogElements() |
| | | { |
| | | // Note that no debugging will be done in this method because it is a likely |
| | | // candidate for being called by the logging subsystem. |
| | | |
| | | String attrs; |
| | | if ((attributes == null) || attributes.isEmpty()) |
| | | { |
| | | attrs = null; |
| | | } |
| | | else |
| | | { |
| | | StringBuilder attrBuffer = new StringBuilder(); |
| | | Iterator<String> iterator = attributes.iterator(); |
| | | attrBuffer.append(iterator.next()); |
| | | |
| | | while (iterator.hasNext()) |
| | | { |
| | | attrBuffer.append(", "); |
| | | attrBuffer.append(iterator.next()); |
| | | } |
| | | |
| | | attrs = attrBuffer.toString(); |
| | | } |
| | | |
| | | return new String[][] |
| | | { |
| | | new String[] { LOG_ELEMENT_BASE_DN, String.valueOf(rawBaseDN) }, |
| | | new String[] { LOG_ELEMENT_SCOPE, String.valueOf(scope) }, |
| | | new String[] { LOG_ELEMENT_SIZE_LIMIT, String.valueOf(sizeLimit) }, |
| | | new String[] { LOG_ELEMENT_TIME_LIMIT, String.valueOf(timeLimit) }, |
| | | new String[] { LOG_ELEMENT_FILTER, String.valueOf(rawFilter) }, |
| | | new String[] { LOG_ELEMENT_REQUESTED_ATTRIBUTES, attrs } |
| | | }; |
| | | } |
| | | |
| | | |
| | | public abstract void setReturnLDAPSubentries(boolean returnLDAPSubentries); |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | * The matched values control associated with this search operation. |
| | | * |
| | | * @return the match values control |
| | | */ |
| | | @Override() |
| | | public final String[][] getResponseLogElements() |
| | | { |
| | | // Note that no debugging will be done in this method because it is a likely |
| | | // candidate for being called by the logging subsystem. |
| | | public abstract MatchedValuesControl getMatchedValuesControl(); |
| | | |
| | | String resultCode = String.valueOf(getResultCode().getIntValue()); |
| | | /** |
| | | * Set the match values control. |
| | | * |
| | | * @param controls - The matched values control |
| | | */ |
| | | public abstract void setMatchedValuesControl(MatchedValuesControl controls); |
| | | |
| | | String errorMessage; |
| | | StringBuilder errorMessageBuffer = getErrorMessage(); |
| | | if (errorMessageBuffer == null) |
| | | { |
| | | errorMessage = null; |
| | | } |
| | | else |
| | | { |
| | | errorMessage = errorMessageBuffer.toString(); |
| | | } |
| | | /** |
| | | * Indicates whether to include the account usable response control with |
| | | * search result entries or not. |
| | | * |
| | | * @return true if the usable control has to be part of the search result |
| | | * entry |
| | | */ |
| | | public abstract boolean isIncludeUsableControl(); |
| | | |
| | | String matchedDNStr; |
| | | DN matchedDN = getMatchedDN(); |
| | | if (matchedDN == null) |
| | | { |
| | | matchedDNStr = null; |
| | | } |
| | | else |
| | | { |
| | | matchedDNStr = matchedDN.toString(); |
| | | } |
| | | /** |
| | | * Specify whether to include the account usable response control within the |
| | | * search result entries. |
| | | * |
| | | * @param includeUsableControl - True if the account usable response control |
| | | * has to be included within the search result |
| | | * entries, false otherwise |
| | | */ |
| | | public abstract void setIncludeUsableControl(boolean includeUsableControl); |
| | | |
| | | String referrals; |
| | | List<String> referralURLs = getReferralURLs(); |
| | | if ((referralURLs == null) || referralURLs.isEmpty()) |
| | | { |
| | | referrals = null; |
| | | } |
| | | else |
| | | { |
| | | StringBuilder buffer = new StringBuilder(); |
| | | Iterator<String> iterator = referralURLs.iterator(); |
| | | buffer.append(iterator.next()); |
| | | /** |
| | | * Register the psearch in the search operation. |
| | | * |
| | | * @param psearch - Persistent search associated to that operation |
| | | */ |
| | | public abstract void setPersistentSearch(PersistentSearch psearch); |
| | | |
| | | while (iterator.hasNext()) |
| | | { |
| | | buffer.append(", "); |
| | | buffer.append(iterator.next()); |
| | | } |
| | | /** |
| | | * Get the psearch from the search operation. |
| | | * |
| | | * @return the psearch, or null if no psearch was registered |
| | | */ |
| | | public abstract PersistentSearch getPersistentSearch(); |
| | | |
| | | referrals = buffer.toString(); |
| | | } |
| | | /** |
| | | * Indicates whether the client is able to handle referrals. |
| | | * |
| | | * @return true, if the client is able to handle referrals |
| | | */ |
| | | public abstract boolean isClientAcceptsReferrals(); |
| | | |
| | | String processingTime = |
| | | String.valueOf(processingStopTime - processingStartTime); |
| | | /** |
| | | * Specify whether the client is able to handle referrals. |
| | | * |
| | | * @param clientAcceptReferrals - Boolean set to true if the client |
| | | * can handle referrals |
| | | */ |
| | | public abstract void setClientAcceptsReferrals(boolean clientAcceptReferrals); |
| | | |
| | | return new String[][] |
| | | { |
| | | new String[] { LOG_ELEMENT_RESULT_CODE, resultCode }, |
| | | new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage }, |
| | | new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr }, |
| | | new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals }, |
| | | new String[] { LOG_ELEMENT_ENTRIES_SENT, String.valueOf(entriesSent) }, |
| | | new String[] { LOG_ELEMENT_REFERENCES_SENT, |
| | | String.valueOf(referencesSent ) }, |
| | | new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime } |
| | | }; |
| | | } |
| | | /** |
| | | * Increments by 1 the number of entries sent to the client for this search |
| | | * operation. |
| | | */ |
| | | public abstract void incrementEntriesSent(); |
| | | |
| | | /** |
| | | * Increments by 1 the number of search references sent to the client for this |
| | | * search operation. |
| | | */ |
| | | public abstract void incrementReferencesSent(); |
| | | |
| | | /** |
| | | * Indicates wether the search result done message has to be sent |
| | | * to the client, or not. |
| | | * |
| | | * @return true if the search result done message is to be sent to the client |
| | | */ |
| | | public abstract boolean isSendResponse(); |
| | | |
| | | /** |
| | | * Specify wether the search result done message has to be sent |
| | | * to the client, or not. |
| | | * |
| | | * @param sendResponse - boolean indicating wether the search result done |
| | | * message is to send to the client |
| | | */ |
| | | public abstract void setSendResponse(boolean sendResponse); |
| | | |
| | | /** |
| | | * Returns true if only real attributes should be returned. |
| | | * |
| | | * @return true if only real attributes should be returned, false otherwise |
| | | */ |
| | | public abstract boolean isRealAttributesOnly(); |
| | | |
| | | /** |
| | | * Specify wether to only return real attributes. |
| | | * |
| | | * @param realAttributesOnly - boolean setup to true, if only the real |
| | | * attributes should be returned |
| | | */ |
| | | public abstract void setRealAttributesOnly(boolean realAttributesOnly); |
| | | |
| | | /** |
| | | * Returns true if only virtual attributes should be returned. |
| | | * |
| | | * @return true if only virtual attributes should be returned, false |
| | | * otherwise |
| | | */ |
| | | public abstract boolean isVirtualAttributesOnly(); |
| | | |
| | | /** |
| | | * Specify wether to only return virtual attributes. |
| | | * |
| | | * @param virtualAttributesOnly - boolean setup to true, if only the virtual |
| | | * attributes should be returned |
| | | */ |
| | | public abstract void setVirtualAttributesOnly(boolean virtualAttributesOnly); |
| | | |
| | | /** |
| | | * Sends the provided search result entry to the client. |
| | | * |
| | | * @param entry The search result entry to be sent to |
| | | * the client. |
| | | * |
| | | * @throws DirectoryException If a problem occurs while attempting |
| | | * to send the entry to the client and |
| | | * the search should be terminated. |
| | | */ |
| | | public abstract void sendSearchEntry(SearchResultEntry entry) |
| | | throws DirectoryException; |
| | | |
| | | /** |
| | | * Sends the provided search result reference to the client. |
| | | * |
| | | * @param reference The search result reference to be sent |
| | | * to the client. |
| | | * |
| | | * @return <CODE>true</CODE> if the client is able to accept |
| | | * referrals, or <CODE>false</CODE> if the client cannot |
| | | * handle referrals and no more attempts should be made to |
| | | * send them for the associated search operation. |
| | | * |
| | | * @throws DirectoryException If a problem occurs while attempting |
| | | * to send the reference to the client |
| | | * and the search should be terminated. |
| | | */ |
| | | public abstract boolean sendSearchReference(SearchResultReference reference) |
| | | throws DirectoryException; |
| | | |
| | | /** |
| | | * Retrieves the proxied authorization DN for this operation if proxied |
| | |
| | | * authorization has been requested, or {@code null} if proxied |
| | | * authorization has not been requested. |
| | | */ |
| | | public DN getProxiedAuthorizationDN() |
| | | { |
| | | return proxiedAuthorizationDN; |
| | | } |
| | | |
| | | |
| | | public abstract DN getProxiedAuthorizationDN(); |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final List<Control> getResponseControls() |
| | | { |
| | | return responseControls; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final void addResponseControl(Control control) |
| | | { |
| | | responseControls.add(control); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final void removeResponseControl(Control control) |
| | | { |
| | | responseControls.remove(control); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final void run() |
| | | { |
| | | setResultCode(ResultCode.UNDEFINED); |
| | | boolean sendResponse = true; |
| | | |
| | | |
| | | // Get the plugin config manager that will be used for invoking plugins. |
| | | PluginConfigManager pluginConfigManager = |
| | | DirectoryServer.getPluginConfigManager(); |
| | | boolean skipPostOperation = false; |
| | | |
| | | |
| | | // Start the processing timer. |
| | | processingStartTime = System.currentTimeMillis(); |
| | | if (timeLimit <= 0) |
| | | { |
| | | timeLimitExpiration = Long.MAX_VALUE; |
| | | } |
| | | else |
| | | { |
| | | // FIXME -- Factor in the user's effective time limit. |
| | | timeLimitExpiration = processingStartTime + (1000L * timeLimit); |
| | | } |
| | | |
| | | |
| | | // Check for and handle a request to cancel this operation. |
| | | if (cancelRequest != null) |
| | | { |
| | | indicateCancelled(cancelRequest); |
| | | processingStopTime = System.currentTimeMillis(); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Create a labeled block of code that we can break out of if a problem is |
| | | // detected. |
| | | searchProcessing: |
| | | { |
| | | PreParsePluginResult preParseResult = |
| | | pluginConfigManager.invokePreParseSearchPlugins(this); |
| | | if (preParseResult.connectionTerminated()) |
| | | { |
| | | // There's no point in continuing with anything. Log the request and |
| | | // result and return. |
| | | setResultCode(ResultCode.CANCELED); |
| | | |
| | | int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT; |
| | | appendErrorMessage(getMessage(msgID)); |
| | | |
| | | processingStopTime = System.currentTimeMillis(); |
| | | |
| | | logSearchRequest(this); |
| | | logSearchResultDone(this); |
| | | pluginConfigManager.invokePostResponseSearchPlugins(this); |
| | | return; |
| | | } |
| | | else if (preParseResult.sendResponseImmediately()) |
| | | { |
| | | skipPostOperation = true; |
| | | logSearchRequest(this); |
| | | break searchProcessing; |
| | | } |
| | | else if (preParseResult.skipCoreProcessing()) |
| | | { |
| | | skipPostOperation = false; |
| | | break searchProcessing; |
| | | } |
| | | |
| | | |
| | | // Log the search request message. |
| | | logSearchRequest(this); |
| | | |
| | | |
| | | // Check for and handle a request to cancel this operation. |
| | | if (cancelRequest != null) |
| | | { |
| | | indicateCancelled(cancelRequest); |
| | | processingStopTime = System.currentTimeMillis(); |
| | | logSearchResultDone(this); |
| | | pluginConfigManager.invokePostResponseSearchPlugins(this); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Process the search base and filter to convert them from their raw forms |
| | | // as provided by the client to the forms required for the rest of the |
| | | // search processing. |
| | | try |
| | | { |
| | | if (baseDN == null) |
| | | { |
| | | baseDN = DN.decode(rawBaseDN); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResultCode(de.getResultCode()); |
| | | appendErrorMessage(de.getErrorMessage()); |
| | | setMatchedDN(de.getMatchedDN()); |
| | | setReferralURLs(de.getReferralURLs()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | |
| | | try |
| | | { |
| | | if (filter == null) |
| | | { |
| | | filter = rawFilter.toSearchFilter(); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResultCode(de.getResultCode()); |
| | | appendErrorMessage(de.getErrorMessage()); |
| | | setMatchedDN(de.getMatchedDN()); |
| | | setReferralURLs(de.getReferralURLs()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | |
| | | // Check to see if there are any controls in the request. If so, then |
| | | // see if there is any special processing required. |
| | | boolean processSearch = true; |
| | | List<Control> requestControls = getRequestControls(); |
| | | if ((requestControls != null) && (! requestControls.isEmpty())) |
| | | { |
| | | for (int i=0; i < requestControls.size(); i++) |
| | | { |
| | | Control c = requestControls.get(i); |
| | | String oid = c.getOID(); |
| | | |
| | | if (oid.equals(OID_LDAP_ASSERTION)) |
| | | { |
| | | LDAPAssertionRequestControl assertControl; |
| | | if (c instanceof LDAPAssertionRequestControl) |
| | | { |
| | | assertControl = (LDAPAssertionRequestControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | assertControl = LDAPAssertionRequestControl.decodeControl(c); |
| | | requestControls.set(i, assertControl); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | appendErrorMessage(le.getMessage()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | |
| | | try |
| | | { |
| | | // FIXME -- We need to determine whether the current user has |
| | | // permission to make this determination. |
| | | SearchFilter assertionFilter = assertControl.getSearchFilter(); |
| | | Entry entry; |
| | | try |
| | | { |
| | | entry = DirectoryServer.getEntry(baseDN); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResultCode(de.getResultCode()); |
| | | |
| | | int msgID = MSGID_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION; |
| | | appendErrorMessage(getMessage(msgID, de.getErrorMessage())); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | |
| | | if (entry == null) |
| | | { |
| | | setResultCode(ResultCode.NO_SUCH_OBJECT); |
| | | |
| | | int msgID = MSGID_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION; |
| | | appendErrorMessage(getMessage(msgID)); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | |
| | | |
| | | if (! assertionFilter.matchesEntry(entry)) |
| | | { |
| | | setResultCode(ResultCode.ASSERTION_FAILED); |
| | | |
| | | appendErrorMessage(getMessage(MSGID_SEARCH_ASSERTION_FAILED)); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResultCode(ResultCode.PROTOCOL_ERROR); |
| | | |
| | | int msgID = MSGID_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER; |
| | | appendErrorMessage(getMessage(msgID, de.getErrorMessage())); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | else if (oid.equals(OID_PROXIED_AUTH_V1)) |
| | | { |
| | | // The requester must have the PROXIED_AUTH privilige in order to be |
| | | // able to use this control. |
| | | if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this)) |
| | | { |
| | | int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES; |
| | | appendErrorMessage(getMessage(msgID)); |
| | | setResultCode(ResultCode.AUTHORIZATION_DENIED); |
| | | break searchProcessing; |
| | | } |
| | | |
| | | |
| | | ProxiedAuthV1Control proxyControl; |
| | | if (c instanceof ProxiedAuthV1Control) |
| | | { |
| | | proxyControl = (ProxiedAuthV1Control) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | proxyControl = ProxiedAuthV1Control.decodeControl(c); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | appendErrorMessage(le.getMessage()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | Entry authorizationEntry; |
| | | try |
| | | { |
| | | authorizationEntry = proxyControl.getAuthorizationEntry(); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResultCode(de.getResultCode()); |
| | | appendErrorMessage(de.getErrorMessage()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | |
| | | if (AccessControlConfigManager.getInstance(). |
| | | getAccessControlHandler().isProxiedAuthAllowed(this, |
| | | authorizationEntry) == false) { |
| | | setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS; |
| | | appendErrorMessage(getMessage(msgID, String.valueOf(baseDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | setAuthorizationEntry(authorizationEntry); |
| | | if (authorizationEntry == null) |
| | | { |
| | | proxiedAuthorizationDN = DN.nullDN(); |
| | | } |
| | | else |
| | | { |
| | | proxiedAuthorizationDN = authorizationEntry.getDN(); |
| | | } |
| | | } |
| | | else if (oid.equals(OID_PROXIED_AUTH_V2)) |
| | | { |
| | | // The requester must have the PROXIED_AUTH privilige in order to be |
| | | // able to use this control. |
| | | if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this)) |
| | | { |
| | | int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES; |
| | | appendErrorMessage(getMessage(msgID)); |
| | | setResultCode(ResultCode.AUTHORIZATION_DENIED); |
| | | break searchProcessing; |
| | | } |
| | | |
| | | |
| | | ProxiedAuthV2Control proxyControl; |
| | | if (c instanceof ProxiedAuthV2Control) |
| | | { |
| | | proxyControl = (ProxiedAuthV2Control) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | proxyControl = ProxiedAuthV2Control.decodeControl(c); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | appendErrorMessage(le.getMessage()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | Entry authorizationEntry; |
| | | try |
| | | { |
| | | authorizationEntry = proxyControl.getAuthorizationEntry(); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResultCode(de.getResultCode()); |
| | | appendErrorMessage(de.getErrorMessage()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | |
| | | if (AccessControlConfigManager.getInstance() |
| | | .getAccessControlHandler().isProxiedAuthAllowed(this, |
| | | authorizationEntry) == false) { |
| | | setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS; |
| | | appendErrorMessage(getMessage(msgID, String.valueOf(baseDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | |
| | | setAuthorizationEntry(authorizationEntry); |
| | | if (authorizationEntry == null) |
| | | { |
| | | proxiedAuthorizationDN = DN.nullDN(); |
| | | } |
| | | else |
| | | { |
| | | proxiedAuthorizationDN = authorizationEntry.getDN(); |
| | | } |
| | | } |
| | | else if (oid.equals(OID_PERSISTENT_SEARCH)) |
| | | { |
| | | PersistentSearchControl psearchControl; |
| | | if (c instanceof PersistentSearchControl) |
| | | { |
| | | psearchControl = (PersistentSearchControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | psearchControl = PersistentSearchControl.decodeControl(c); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | appendErrorMessage(le.getMessage()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | |
| | | persistentSearch = |
| | | new PersistentSearch(this, psearchControl.getChangeTypes(), |
| | | psearchControl.getReturnECs()); |
| | | |
| | | // If we're only interested in changes, then we don't actually want |
| | | // to process the search now. |
| | | if (psearchControl.getChangesOnly()) |
| | | { |
| | | processSearch = false; |
| | | } |
| | | } |
| | | else if (oid.equals(OID_LDAP_SUBENTRIES)) |
| | | { |
| | | returnLDAPSubentries = true; |
| | | } |
| | | else if (oid.equals(OID_MATCHED_VALUES)) |
| | | { |
| | | if (c instanceof MatchedValuesControl) |
| | | { |
| | | matchedValuesControl = (MatchedValuesControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | matchedValuesControl = MatchedValuesControl.decodeControl(c); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | appendErrorMessage(le.getMessage()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | } |
| | | else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL)) |
| | | { |
| | | includeUsableControl = true; |
| | | } |
| | | else if (oid.equals(OID_REAL_ATTRS_ONLY)) |
| | | { |
| | | realAttributesOnly = true; |
| | | } |
| | | else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY)) |
| | | { |
| | | virtualAttributesOnly = true; |
| | | } else if(oid.equals(OID_GET_EFFECTIVE_RIGHTS)) { |
| | | GetEffectiveRights effectiveRightsControl; |
| | | if (c instanceof GetEffectiveRights) |
| | | { |
| | | effectiveRightsControl = (GetEffectiveRights) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | effectiveRightsControl = GetEffectiveRights.decodeControl(c); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | appendErrorMessage(le.getMessage()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | |
| | | if (!AccessControlConfigManager.getInstance() |
| | | .getAccessControlHandler(). |
| | | isGetEffectiveRightsAllowed(this, effectiveRightsControl)) { |
| | | setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | int msgID = |
| | | MSGID_SEARCH_EFFECTIVERIGHTS_INSUFFICIENT_ACCESS_RIGHTS; |
| | | appendErrorMessage(getMessage(msgID, String.valueOf(baseDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | |
| | | // NYI -- Add support for additional controls. |
| | | else if (c.isCritical()) |
| | | { |
| | | Backend backend = DirectoryServer.getBackend(baseDN); |
| | | if ((backend == null) || (! backend.supportsControl(oid))) |
| | | { |
| | | setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION); |
| | | |
| | | int msgID = MSGID_SEARCH_UNSUPPORTED_CRITICAL_CONTROL; |
| | | appendErrorMessage(getMessage(msgID, oid)); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check to see if the client has permission to perform the |
| | | // search. |
| | | |
| | | // FIXME: for now assume that this will check all permission |
| | | // pertinent to the operation. This includes proxy authorization |
| | | // and any other controls specified. |
| | | if (AccessControlConfigManager.getInstance() |
| | | .getAccessControlHandler().isAllowed(this) == false) { |
| | | setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS; |
| | | appendErrorMessage(getMessage(msgID, String.valueOf(baseDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | |
| | | // Check for and handle a request to cancel this operation. |
| | | if (cancelRequest != null) |
| | | { |
| | | indicateCancelled(cancelRequest); |
| | | processingStopTime = System.currentTimeMillis(); |
| | | logSearchResultDone(this); |
| | | pluginConfigManager.invokePostResponseSearchPlugins(this); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Invoke the pre-operation search plugins. |
| | | PreOperationPluginResult preOpResult = |
| | | pluginConfigManager.invokePreOperationSearchPlugins(this); |
| | | if (preOpResult.connectionTerminated()) |
| | | { |
| | | // There's no point in continuing with anything. Log the request and |
| | | // result and return. |
| | | setResultCode(ResultCode.CANCELED); |
| | | |
| | | int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT; |
| | | appendErrorMessage(getMessage(msgID)); |
| | | |
| | | processingStopTime = System.currentTimeMillis(); |
| | | logSearchResultDone(this); |
| | | pluginConfigManager.invokePostResponseSearchPlugins(this); |
| | | return; |
| | | } |
| | | else if (preOpResult.sendResponseImmediately()) |
| | | { |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | else if (preOpResult.skipCoreProcessing()) |
| | | { |
| | | skipPostOperation = false; |
| | | break searchProcessing; |
| | | } |
| | | |
| | | |
| | | // Check for and handle a request to cancel this operation. |
| | | if (cancelRequest != null) |
| | | { |
| | | indicateCancelled(cancelRequest); |
| | | processingStopTime = System.currentTimeMillis(); |
| | | logSearchResultDone(this); |
| | | pluginConfigManager.invokePostResponseSearchPlugins(this); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Get the backend that should hold the search base. If there is none, |
| | | // then fail. |
| | | Backend backend = DirectoryServer.getBackend(baseDN); |
| | | if (backend == null) |
| | | { |
| | | setResultCode(ResultCode.NO_SUCH_OBJECT); |
| | | appendErrorMessage(getMessage(MSGID_SEARCH_BASE_DOESNT_EXIST, |
| | | String.valueOf(baseDN))); |
| | | break searchProcessing; |
| | | } |
| | | |
| | | |
| | | // We'll set the result code to "success". If a problem occurs, then it |
| | | // will be overwritten. |
| | | setResultCode(ResultCode.SUCCESS); |
| | | |
| | | |
| | | // If there's a persistent search, then register it with the server. |
| | | if (persistentSearch != null) |
| | | { |
| | | DirectoryServer.registerPersistentSearch(persistentSearch); |
| | | sendResponse = false; |
| | | } |
| | | |
| | | |
| | | // Process the search in the backend and all its subordinates. |
| | | try |
| | | { |
| | | if (processSearch) |
| | | { |
| | | searchBackend(backend); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | setResultCode(de.getResultCode()); |
| | | appendErrorMessage(de.getErrorMessage()); |
| | | setMatchedDN(de.getMatchedDN()); |
| | | setReferralURLs(de.getReferralURLs()); |
| | | |
| | | if (persistentSearch != null) |
| | | { |
| | | DirectoryServer.deregisterPersistentSearch(persistentSearch); |
| | | sendResponse = true; |
| | | } |
| | | |
| | | break searchProcessing; |
| | | } |
| | | catch (CancelledOperationException coe) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, coe); |
| | | } |
| | | |
| | | CancelResult cancelResult = coe.getCancelResult(); |
| | | |
| | | setCancelResult(cancelResult); |
| | | setResultCode(cancelResult.getResultCode()); |
| | | |
| | | String message = coe.getMessage(); |
| | | if ((message != null) && (message.length() > 0)) |
| | | { |
| | | appendErrorMessage(message); |
| | | } |
| | | |
| | | if (persistentSearch != null) |
| | | { |
| | | DirectoryServer.deregisterPersistentSearch(persistentSearch); |
| | | sendResponse = true; |
| | | } |
| | | |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | |
| | | int msgID = MSGID_SEARCH_BACKEND_EXCEPTION; |
| | | appendErrorMessage(getMessage(msgID, getExceptionMessage(e))); |
| | | |
| | | if (persistentSearch != null) |
| | | { |
| | | DirectoryServer.deregisterPersistentSearch(persistentSearch); |
| | | sendResponse = true; |
| | | } |
| | | |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for and handle a request to cancel this operation. |
| | | if (cancelRequest != null) |
| | | { |
| | | indicateCancelled(cancelRequest); |
| | | processingStopTime = System.currentTimeMillis(); |
| | | logSearchResultDone(this); |
| | | pluginConfigManager.invokePostResponseSearchPlugins(this); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Invoke the post-operation search plugins. |
| | | if (! skipPostOperation) |
| | | { |
| | | PostOperationPluginResult postOperationResult = |
| | | pluginConfigManager.invokePostOperationSearchPlugins(this); |
| | | if (postOperationResult.connectionTerminated()) |
| | | { |
| | | setResultCode(ResultCode.CANCELED); |
| | | |
| | | int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT; |
| | | appendErrorMessage(getMessage(msgID)); |
| | | |
| | | processingStopTime = System.currentTimeMillis(); |
| | | logSearchResultDone(this); |
| | | pluginConfigManager.invokePostResponseSearchPlugins(this); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Indicate that it is now too late to attempt to cancel the operation. |
| | | setCancelResult(CancelResult.TOO_LATE); |
| | | |
| | | |
| | | // Stop the processing timer. |
| | | processingStopTime = System.currentTimeMillis(); |
| | | |
| | | |
| | | // If everything is successful to this point and it is not a persistent |
| | | // search, then send the search result done message to the client. |
| | | // Otherwise, we'll want to make the size and time limit values unlimited |
| | | // to ensure that the remainder of the persistent search isn't subject to |
| | | // those restrictions. |
| | | if (sendResponse) |
| | | { |
| | | sendSearchResultDone(); |
| | | } |
| | | else |
| | | { |
| | | sizeLimit = 0; |
| | | timeLimit = 0; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Processes the search in the provided backend and recursively through its |
| | | * subordinate backends. |
| | | * Set the proxied authorization DN for this operation if proxied |
| | | * authorization has been requested. |
| | | * |
| | | * @param backend The backend in which to process the search. |
| | | * |
| | | * @throws DirectoryException If a problem occurs while processing the |
| | | * search. |
| | | * |
| | | * @throws CancelledOperationException If the backend noticed and reacted |
| | | * to a request to cancel or abandon the |
| | | * search operation. |
| | | * @param proxiedAuthorizationDN |
| | | * The proxied authorization DN for this operation if proxied |
| | | * authorization has been requested, or {@code null} if proxied |
| | | * authorization has not been requested. |
| | | */ |
| | | private final void searchBackend(Backend backend) |
| | | throws DirectoryException, CancelledOperationException |
| | | { |
| | | // Check for and handle a request to cancel this operation. |
| | | if (cancelRequest != null) |
| | | { |
| | | setCancelResult(CancelResult.CANCELED); |
| | | processingStopTime = System.currentTimeMillis(); |
| | | return; |
| | | } |
| | | public abstract void setProxiedAuthorizationDN(DN proxiedAuthorizationDN); |
| | | |
| | | |
| | | // Perform the search in the provided backend. |
| | | backend.search(this); |
| | | |
| | | |
| | | // If there are any subordinate backends, then process the search there as |
| | | // well. |
| | | Backend[] subBackends = backend.getSubordinateBackends(); |
| | | for (Backend b : subBackends) |
| | | { |
| | | DN[] baseDNs = b.getBaseDNs(); |
| | | for (DN dn : baseDNs) |
| | | { |
| | | if (dn.isDescendantOf(baseDN)) |
| | | { |
| | | searchBackend(b); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final CancelResult cancel(CancelRequest cancelRequest) |
| | | { |
| | | this.cancelRequest = cancelRequest; |
| | | |
| | | if (persistentSearch != null) |
| | | { |
| | | DirectoryServer.deregisterPersistentSearch(persistentSearch); |
| | | persistentSearch = null; |
| | | } |
| | | |
| | | CancelResult cancelResult = getCancelResult(); |
| | | long stopWaitingTime = System.currentTimeMillis() + 5000; |
| | | while ((cancelResult == null) && |
| | | (System.currentTimeMillis() < stopWaitingTime)) |
| | | { |
| | | try |
| | | { |
| | | Thread.sleep(50); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | } |
| | | |
| | | cancelResult = getCancelResult(); |
| | | } |
| | | |
| | | if (cancelResult == null) |
| | | { |
| | | // This can happen in some rare cases (e.g., if a client disconnects and |
| | | // there is still a lot of data to send to that client), and in this case |
| | | // we'll prevent the cancel thread from blocking for a long period of |
| | | // time. |
| | | cancelResult = CancelResult.CANNOT_CANCEL; |
| | | } |
| | | |
| | | return cancelResult; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final CancelRequest getCancelRequest() |
| | | { |
| | | return cancelRequest; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | protected boolean setCancelRequest(CancelRequest cancelRequest) |
| | | { |
| | | this.cancelRequest = cancelRequest; |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final void toString(StringBuilder buffer) |
| | | { |
| | | buffer.append("SearchOperation(connID="); |
| | | buffer.append(clientConnection.getConnectionID()); |
| | | buffer.append(", opID="); |
| | | buffer.append(operationID); |
| | | buffer.append(", baseDN="); |
| | | buffer.append(rawBaseDN); |
| | | buffer.append(", scope="); |
| | | buffer.append(scope.toString()); |
| | | buffer.append(", filter="); |
| | | buffer.append(rawFilter.toString()); |
| | | buffer.append(")"); |
| | | } |
| | | } |
| | | |
| | | } |