package com.hwlcn.ldap.ldap.sdk; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Timer; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import com.hwlcn.ldap.asn1.ASN1Boolean; import com.hwlcn.ldap.asn1.ASN1Buffer; import com.hwlcn.ldap.asn1.ASN1BufferSequence; import com.hwlcn.ldap.asn1.ASN1Element; import com.hwlcn.ldap.asn1.ASN1Enumerated; import com.hwlcn.ldap.asn1.ASN1Integer; import com.hwlcn.ldap.asn1.ASN1OctetString; import com.hwlcn.ldap.asn1.ASN1Sequence; import com.hwlcn.ldap.ldap.protocol.LDAPMessage; import com.hwlcn.ldap.ldap.protocol.LDAPResponse; import com.hwlcn.ldap.ldap.protocol.ProtocolOp; import com.hwlcn.core.annotation.InternalUseOnly; import com.hwlcn.core.annotation.Mutable; import com.hwlcn.core.annotation.ThreadSafety; import com.hwlcn.ldap.util.ThreadSafetyLevel; import static com.hwlcn.ldap.ldap.sdk.LDAPMessages.*; import static com.hwlcn.ldap.util.Debug.*; import static com.hwlcn.ldap.util.StaticUtils.*; import static com.hwlcn.ldap.util.Validator.*; /** * This class implements the processing necessary to perform an LDAPv3 search * operation, which can be used to retrieve entries that match a given set of * criteria. A search request may include the following elements: * <UL> * <LI>Base DN -- Specifies the base DN for the search. Only entries at or * below this location in the server (based on the scope) will be * considered potential matches.</LI> * <LI>Scope -- Specifies the range of entries relative to the base DN that * may be considered potential matches.</LI> * <LI>Dereference Policy -- Specifies the behavior that the server should * exhibit if any alias entries are encountered while processing the * search. If no dereference policy is provided, then a default of * {@code DereferencePolicy.NEVER} will be used.</LI> * <LI>Size Limit -- Specifies the maximum number of entries that should be * returned from the search. A value of zero indicates that there should * not be any limit enforced. Note that the directory server may also * be configured with a server-side size limit which can also limit the * number of entries that may be returned to the client and in that case * the smaller of the client-side and server-side limits will be * used. If no size limit is provided, then a default of zero (unlimited) * will be used.</LI> * <LI>Time Limit -- Specifies the maximum length of time in seconds that the * server should spend processing the search. A value of zero indicates * that there should not be any limit enforced. Note that the directory * server may also be configured with a server-side time limit which can * also limit the processing time, and in that case the smaller of the * client-side and server-side limits will be used. If no time limit is * provided, then a default of zero (unlimited) will be used.</LI> * <LI>Types Only -- Indicates whether matching entries should include only * attribute names, or both attribute names and values. If no value is * provided, then a default of {@code false} will be used.</LI> * <LI>Filter -- Specifies the criteria for determining which entries should * be returned. See the {@link com.hwlcn.ldap.ldap.sdk.Filter} class for the types of filters * that may be used. * <BR><BR> * Note that filters can be specified using either their string * representations or as {@link com.hwlcn.ldap.ldap.sdk.Filter} objects. As noted in the * documentation for the {@link com.hwlcn.ldap.ldap.sdk.Filter} class, using the string * representation may be somewhat dangerous if the data is not properly * sanitized because special characters contained in the filter may cause * it to be invalid or worse expose a vulnerability that could cause the * filter to request more information than was intended. As a result, if * the filter may include special characters or user-provided strings, * then it is recommended that you use {@link com.hwlcn.ldap.ldap.sdk.Filter} objects created from * their individual components rather than their string representations. * </LI> * <LI>Attributes -- Specifies the set of attributes that should be included * in matching entries. If no attributes are provided, then the server * will default to returning all user attributes. If a specified set of * attributes is given, then only those attributes will be included. * Values that may be included to indicate a special meaning include: * <UL> * <LI>{@code NO_ATTRIBUTES} -- Indicates that no attributes should be * returned. That is, only the DNs of matching entries will be * returned.</LI> * <LI>{@code ALL_USER_ATTRIBUTES} -- Indicates that all user attributes * should be included in matching entries. This is the default if * no attributes are provided, but this special value may be * included if a specific set of operational attributes should be * included along with all user attributes.</LI> * <LI>{@code ALL_OPERATIONAL_ATTRIBUTES} -- Indicates that all * operational attributes should be included in matching * entries.</LI> * </UL> * These special values may be used alone or in conjunction with each * other and/or any specific attribute names or OIDs.</LI> * <LI>An optional set of controls to include in the request to send to the * server.</LI> * <LI>An optional {@link SearchResultListener} which may be used to process * search result entries and search result references returned by the * server in the course of processing the request. If this is * {@code null}, then the entries and references will be collected and * returned in the {@link com.hwlcn.ldap.ldap.sdk.SearchResult} object that is returned.</LI> * </UL> * When processing a search operation, there are three ways that the returned * entries and references may be accessed: * <UL> * <LI>If the {@link com.hwlcn.ldap.ldap.sdk.LDAPInterface#search(com.hwlcn.ldap.ldap.sdk.SearchRequest)} method is used and * the provided search request does not include a * {@link SearchResultListener} object, then the entries and references * will be collected internally and made available in the * {@link com.hwlcn.ldap.ldap.sdk.SearchResult} object that is returned.</LI> * <LI>If the {@link com.hwlcn.ldap.ldap.sdk.LDAPInterface#search(com.hwlcn.ldap.ldap.sdk.SearchRequest)} method is used and * the provided search request does include a {@link SearchResultListener} * object, then that listener will be used to provide access to the * entries and references, and they will not be present in the * {@link com.hwlcn.ldap.ldap.sdk.SearchResult} object (although the number of entries and * references returned will still be available).</LI> * <LI>The {@link LDAPEntrySource} object may be used to access the entries * and references returned from the search. It uses an * {@code Iterator}-like API to provide access to the entries that are * returned, and any references returned will be included in the * {@link EntrySourceException} thrown on the appropriate call to * {@link LDAPEntrySource#nextEntry()}.</LI> * </UL> * <BR><BR> * {@code SearchRequest} objects are mutable and therefore can be altered and * re-used for multiple requests. Note, however, that {@code SearchRequest} * objects are not threadsafe and therefore a single {@code SearchRequest} * object instance should not be used to process multiple requests at the same * time. * <BR><BR> * <H2>Example</H2> * The following example demonstrates a simple search operation in which the * client performs a search to find all users in the "Sales" department and then * prints out the name and e-mail address for each matching user: * <PRE> * Filter filter = Filter.createEqualityFilter("ou", "Sales"); * * SearchRequest searchRequest = * new SearchRequest("dc=example,dc=com", SearchScope.SUB, filter, * "cn", "mail"); * * try * { * SearchResult searchResult = connection.search(searchRequest); * * for (SearchResultEntry entry : searchResult.getSearchEntries()) * { * String name = entry.getAttributeValue("cn"); * String mail = entry.getAttributeValue("mail"); * System.out.println(name + "\t" + mail); * } * } * catch (LDAPSearchException lse) * { * System.err.println("The search failed."); * } * </PRE> */ @Mutable() @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) public final class SearchRequest extends UpdatableLDAPRequest implements ReadOnlySearchRequest, ResponseAcceptor, ProtocolOp { public static final String ALL_USER_ATTRIBUTES = "*"; public static final String ALL_OPERATIONAL_ATTRIBUTES = "+"; public static final String NO_ATTRIBUTES = "1.1"; public static final String[] REQUEST_ATTRS_DEFAULT = NO_STRINGS; private static final long serialVersionUID = 1500219434086474893L; private String[] attributes; private boolean typesOnly; private DereferencePolicy derefPolicy; private int messageID = -1; private int sizeLimit; private int timeLimit; private Filter filter; private final LinkedBlockingQueue<LDAPResponse> responseQueue = new LinkedBlockingQueue<LDAPResponse>(50); private final SearchResultListener searchResultListener; private SearchScope scope; private String baseDN; public SearchRequest(final String baseDN, final SearchScope scope, final String filter, final String... attributes) throws LDAPException { this(null, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false, Filter.create(filter), attributes); } public SearchRequest(final String baseDN, final SearchScope scope, final Filter filter, final String... attributes) { this(null, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false, filter, attributes); } public SearchRequest(final SearchResultListener searchResultListener, final String baseDN, final SearchScope scope, final String filter, final String... attributes) throws LDAPException { this(searchResultListener, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false, Filter.create(filter), attributes); } public SearchRequest(final SearchResultListener searchResultListener, final String baseDN, final SearchScope scope, final Filter filter, final String... attributes) { this(searchResultListener, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false, filter, attributes); } public SearchRequest(final String baseDN, final SearchScope scope, final DereferencePolicy derefPolicy, final int sizeLimit, final int timeLimit, final boolean typesOnly, final String filter, final String... attributes) throws LDAPException { this(null, null, baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, Filter.create(filter), attributes); } public SearchRequest(final String baseDN, final SearchScope scope, final DereferencePolicy derefPolicy, final int sizeLimit, final int timeLimit, final boolean typesOnly, final Filter filter, final String... attributes) { this(null, null, baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter, attributes); } public SearchRequest(final SearchResultListener searchResultListener, final String baseDN, final SearchScope scope, final DereferencePolicy derefPolicy, final int sizeLimit, final int timeLimit, final boolean typesOnly, final String filter, final String... attributes) throws LDAPException { this(searchResultListener, null, baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, Filter.create(filter), attributes); } public SearchRequest(final SearchResultListener searchResultListener, final String baseDN, final SearchScope scope, final DereferencePolicy derefPolicy, final int sizeLimit, final int timeLimit, final boolean typesOnly, final Filter filter, final String... attributes) { this(searchResultListener, null, baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter, attributes); } public SearchRequest(final SearchResultListener searchResultListener, final Control[] controls, final String baseDN, final SearchScope scope, final DereferencePolicy derefPolicy, final int sizeLimit, final int timeLimit, final boolean typesOnly, final String filter, final String... attributes) throws LDAPException { this(searchResultListener, controls, baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, Filter.create(filter), attributes); } public SearchRequest(final SearchResultListener searchResultListener, final Control[] controls, final String baseDN, final SearchScope scope, final DereferencePolicy derefPolicy, final int sizeLimit, final int timeLimit, final boolean typesOnly, final Filter filter, final String... attributes) { super(controls); ensureNotNull(baseDN, filter); this.baseDN = baseDN; this.scope = scope; this.derefPolicy = derefPolicy; this.typesOnly = typesOnly; this.filter = filter; this.searchResultListener = searchResultListener; if (sizeLimit < 0) { this.sizeLimit = 0; } else { this.sizeLimit = sizeLimit; } if (timeLimit < 0) { this.timeLimit = 0; } else { this.timeLimit = timeLimit; } if (attributes == null) { this.attributes = REQUEST_ATTRS_DEFAULT; } else { this.attributes = attributes; } } public String getBaseDN() { return baseDN; } public void setBaseDN(final String baseDN) { ensureNotNull(baseDN); this.baseDN = baseDN; } public void setBaseDN(final DN baseDN) { ensureNotNull(baseDN); this.baseDN = baseDN.toString(); } public SearchScope getScope() { return scope; } public void setScope(final SearchScope scope) { this.scope = scope; } public DereferencePolicy getDereferencePolicy() { return derefPolicy; } public void setDerefPolicy(final DereferencePolicy derefPolicy) { this.derefPolicy = derefPolicy; } public int getSizeLimit() { return sizeLimit; } public void setSizeLimit(final int sizeLimit) { if (sizeLimit < 0) { this.sizeLimit = 0; } else { this.sizeLimit = sizeLimit; } } public int getTimeLimitSeconds() { return timeLimit; } public void setTimeLimitSeconds(final int timeLimit) { if (timeLimit < 0) { this.timeLimit = 0; } else { this.timeLimit = timeLimit; } } public boolean typesOnly() { return typesOnly; } public void setTypesOnly(final boolean typesOnly) { this.typesOnly = typesOnly; } public Filter getFilter() { return filter; } public void setFilter(final String filter) throws LDAPException { ensureNotNull(filter); this.filter = Filter.create(filter); } public void setFilter(final Filter filter) { ensureNotNull(filter); this.filter = filter; } public String[] getAttributes() { return attributes; } public List<String> getAttributeList() { return Collections.unmodifiableList(Arrays.asList(attributes)); } public void setAttributes(final String... attributes) { if (attributes == null) { this.attributes = REQUEST_ATTRS_DEFAULT; } else { this.attributes = attributes; } } public void setAttributes(final List<String> attributes) { if (attributes == null) { this.attributes = REQUEST_ATTRS_DEFAULT; } else { this.attributes = new String[attributes.size()]; for (int i=0; i < this.attributes.length; i++) { this.attributes[i] = attributes.get(i); } } } public SearchResultListener getSearchResultListener() { return searchResultListener; } public byte getProtocolOpType() { return LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST; } public void writeTo(final ASN1Buffer writer) { final ASN1BufferSequence requestSequence = writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST); writer.addOctetString(baseDN); writer.addEnumerated(scope.intValue()); writer.addEnumerated(derefPolicy.intValue()); writer.addInteger(sizeLimit); writer.addInteger(timeLimit); writer.addBoolean(typesOnly); filter.writeTo(writer); final ASN1BufferSequence attrSequence = writer.beginSequence(); for (final String s : attributes) { writer.addOctetString(s); } attrSequence.end(); requestSequence.end(); } public ASN1Element encodeProtocolOp() { final ASN1Element[] attrElements = new ASN1Element[attributes.length]; for (int i=0; i < attrElements.length; i++) { attrElements[i] = new ASN1OctetString(attributes[i]); } final ASN1Element[] protocolOpElements = { new ASN1OctetString(baseDN), new ASN1Enumerated(scope.intValue()), new ASN1Enumerated(derefPolicy.intValue()), new ASN1Integer(sizeLimit), new ASN1Integer(timeLimit), new ASN1Boolean(typesOnly), filter.encode(), new ASN1Sequence(attrElements) }; return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST, protocolOpElements); } @Override() protected SearchResult process(final LDAPConnection connection, final int depth) throws LDAPException { if (connection.synchronousMode()) { return processSync(connection, depth, connection.getConnectionOptions().autoReconnect()); } final long requestTime = System.nanoTime(); processAsync(connection, null); try { final ArrayList<SearchResultEntry> entryList; final ArrayList<SearchResultReference> referenceList; if (searchResultListener == null) { entryList = new ArrayList<SearchResultEntry>(5); referenceList = new ArrayList<SearchResultReference>(5); } else { entryList = null; referenceList = null; } int numEntries = 0; int numReferences = 0; ResultCode intermediateResultCode = ResultCode.SUCCESS; final long responseTimeout = getResponseTimeoutMillis(connection); while (true) { final LDAPResponse response; try { if (responseTimeout > 0) { response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); } else { response = responseQueue.take(); } } catch (InterruptedException ie) { debugException(ie); throw new LDAPException(ResultCode.LOCAL_ERROR, ERR_SEARCH_INTERRUPTED.get(connection.getHostPort()), ie); } if (response == null) { if (connection.getConnectionOptions().abandonOnTimeout()) { connection.abandon(messageID); } final SearchResult searchResult = new SearchResult(messageID, ResultCode.TIMEOUT, ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, connection.getHostPort()), null, null, entryList, referenceList, numEntries, numReferences, null); throw new LDAPSearchException(searchResult); } if (response instanceof ConnectionClosedResponse) { final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; final String message = ccr.getMessage(); if (message == null) { final SearchResult searchResult = new SearchResult(messageID, ccr.getResultCode(), ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get( connection.getHostPort(), toString()), null, null, entryList, referenceList, numEntries, numReferences, null); throw new LDAPSearchException(searchResult); } else { final SearchResult searchResult = new SearchResult(messageID, ccr.getResultCode(), ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE. get(connection.getHostPort(), toString(), message), null, null, entryList, referenceList, numEntries, numReferences, null); throw new LDAPSearchException(searchResult); } } else if (response instanceof SearchResultEntry) { final SearchResultEntry searchEntry = (SearchResultEntry) response; numEntries++; if (searchResultListener == null) { entryList.add(searchEntry); } else { searchResultListener.searchEntryReturned(searchEntry); } } else if (response instanceof SearchResultReference) { final SearchResultReference searchReference = (SearchResultReference) response; if (followReferrals(connection)) { final LDAPResult result = followSearchReference(messageID, searchReference, connection, depth); if (! result.getResultCode().equals(ResultCode.SUCCESS)) { numReferences++; if (searchResultListener == null) { referenceList.add(searchReference); } else { searchResultListener.searchReferenceReturned(searchReference); } if (intermediateResultCode.equals(ResultCode.SUCCESS)) { intermediateResultCode = result.getResultCode(); } } else if (result instanceof SearchResult) { final SearchResult searchResult = (SearchResult) result; numEntries += searchResult.getEntryCount(); if (searchResultListener == null) { entryList.addAll(searchResult.getSearchEntries()); } } } else { numReferences++; if (searchResultListener == null) { referenceList.add(searchReference); } else { searchResultListener.searchReferenceReturned(searchReference); } } } else { connection.getConnectionStatistics().incrementNumSearchResponses( numEntries, numReferences, (System.nanoTime() - requestTime)); SearchResult result = (SearchResult) response; result.setCounts(numEntries, entryList, numReferences, referenceList); if ((result.getResultCode().equals(ResultCode.REFERRAL)) && followReferrals(connection)) { if (depth >= connection.getConnectionOptions().getReferralHopLimit()) { return new SearchResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, ERR_TOO_MANY_REFERRALS.get(), result.getMatchedDN(), result.getReferralURLs(), entryList, referenceList, numEntries, numReferences, result.getResponseControls()); } result = followReferral(result, connection, depth); } if ((result.getResultCode().equals(ResultCode.SUCCESS)) && (! intermediateResultCode.equals(ResultCode.SUCCESS))) { return new SearchResult(messageID, intermediateResultCode, result.getDiagnosticMessage(), result.getMatchedDN(), result.getReferralURLs(), entryList, referenceList, numEntries, numReferences, result.getResponseControls()); } return result; } } } finally { connection.deregisterResponseAcceptor(messageID); } } AsyncRequestID processAsync(final LDAPConnection connection, final AsyncSearchResultListener resultListener) throws LDAPException { messageID = connection.nextMessageID(); final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); final AsyncRequestID asyncRequestID; if (resultListener == null) { asyncRequestID = null; connection.registerResponseAcceptor(messageID, this); } else { final AsyncSearchHelper helper = new AsyncSearchHelper(connection, messageID, resultListener, getIntermediateResponseListener()); connection.registerResponseAcceptor(messageID, helper); asyncRequestID = helper.getAsyncRequestID(); final long timeout = getResponseTimeoutMillis(connection); if (timeout > 0L) { final Timer timer = connection.getTimer(); final AsyncTimeoutTimerTask timerTask = new AsyncTimeoutTimerTask(helper); timer.schedule(timerTask, timeout); asyncRequestID.setTimerTask(timerTask); } } try { debugLDAPRequest(this); connection.getConnectionStatistics().incrementNumSearchRequests(); connection.sendMessage(message); return asyncRequestID; } catch (LDAPException le) { debugException(le); connection.deregisterResponseAcceptor(messageID); throw le; } } private SearchResult processSync(final LDAPConnection connection, final int depth, final boolean allowRetry) throws LDAPException { messageID = connection.nextMessageID(); final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); final long responseTimeout = getResponseTimeoutMillis(connection); try { connection.getConnectionInternals(true).getSocket().setSoTimeout( (int) responseTimeout); } catch (Exception e) { debugException(e); } final long requestTime = System.nanoTime(); debugLDAPRequest(this); connection.getConnectionStatistics().incrementNumSearchRequests(); try { connection.sendMessage(message); } catch (final LDAPException le) { debugException(le); if (allowRetry) { final SearchResult retryResult = reconnectAndRetry(connection, depth, le.getResultCode(), 0, 0); if (retryResult != null) { return retryResult; } } throw le; } final ArrayList<SearchResultEntry> entryList; final ArrayList<SearchResultReference> referenceList; if (searchResultListener == null) { entryList = new ArrayList<SearchResultEntry>(5); referenceList = new ArrayList<SearchResultReference>(5); } else { entryList = null; referenceList = null; } int numEntries = 0; int numReferences = 0; ResultCode intermediateResultCode = ResultCode.SUCCESS; while (true) { final LDAPResponse response; try { response = connection.readResponse(messageID); } catch (final LDAPException le) { debugException(le); if ((le.getResultCode() == ResultCode.TIMEOUT) && connection.getConnectionOptions().abandonOnTimeout()) { connection.abandon(messageID); } if (allowRetry) { final SearchResult retryResult = reconnectAndRetry(connection, depth, le.getResultCode(), numEntries, numReferences); if (retryResult != null) { return retryResult; } } throw le; } if (response == null) { if (connection.getConnectionOptions().abandonOnTimeout()) { connection.abandon(messageID); } throw new LDAPException(ResultCode.TIMEOUT, ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, connection.getHostPort())); } else if (response instanceof ConnectionClosedResponse) { if (allowRetry) { final SearchResult retryResult = reconnectAndRetry(connection, depth, ResultCode.SERVER_DOWN, numEntries, numReferences); if (retryResult != null) { return retryResult; } } final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; final String msg = ccr.getMessage(); if (msg == null) { final SearchResult searchResult = new SearchResult(messageID, ccr.getResultCode(), ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get( connection.getHostPort(), toString()), null, null, entryList, referenceList, numEntries, numReferences, null); throw new LDAPSearchException(searchResult); } else { final SearchResult searchResult = new SearchResult(messageID, ccr.getResultCode(), ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE. get(connection.getHostPort(), toString(), msg), null, null, entryList, referenceList, numEntries, numReferences, null); throw new LDAPSearchException(searchResult); } } else if (response instanceof IntermediateResponse) { final IntermediateResponseListener listener = getIntermediateResponseListener(); if (listener != null) { listener.intermediateResponseReturned( (IntermediateResponse) response); } } else if (response instanceof SearchResultEntry) { final SearchResultEntry searchEntry = (SearchResultEntry) response; numEntries++; if (searchResultListener == null) { entryList.add(searchEntry); } else { searchResultListener.searchEntryReturned(searchEntry); } } else if (response instanceof SearchResultReference) { final SearchResultReference searchReference = (SearchResultReference) response; if (followReferrals(connection)) { final LDAPResult result = followSearchReference(messageID, searchReference, connection, depth); if (! result.getResultCode().equals(ResultCode.SUCCESS)) { numReferences++; if (searchResultListener == null) { referenceList.add(searchReference); } else { searchResultListener.searchReferenceReturned(searchReference); } if (intermediateResultCode.equals(ResultCode.SUCCESS)) { intermediateResultCode = result.getResultCode(); } } else if (result instanceof SearchResult) { final SearchResult searchResult = (SearchResult) result; numEntries += searchResult.getEntryCount(); if (searchResultListener == null) { entryList.addAll(searchResult.getSearchEntries()); } } } else { numReferences++; if (searchResultListener == null) { referenceList.add(searchReference); } else { searchResultListener.searchReferenceReturned(searchReference); } } } else { final SearchResult result = (SearchResult) response; if (allowRetry) { final SearchResult retryResult = reconnectAndRetry(connection, depth, result.getResultCode(), numEntries, numReferences); if (retryResult != null) { return retryResult; } } return handleResponse(connection, response, requestTime, depth, numEntries, numReferences, entryList, referenceList, intermediateResultCode); } } } private SearchResult reconnectAndRetry(final LDAPConnection connection, final int depth, final ResultCode resultCode, final int numEntries, final int numReferences) { try { switch (resultCode.intValue()) { case ResultCode.SERVER_DOWN_INT_VALUE: case ResultCode.DECODING_ERROR_INT_VALUE: case ResultCode.CONNECT_ERROR_INT_VALUE: connection.reconnect(); if ((numEntries == 0) && (numReferences == 0)) { return processSync(connection, depth, false); } break; } } catch (final Exception e) { debugException(e); } return null; } private SearchResult handleResponse(final LDAPConnection connection, final LDAPResponse response, final long requestTime, final int depth, final int numEntries, final int numReferences, final List<SearchResultEntry> entryList, final List<SearchResultReference> referenceList, final ResultCode intermediateResultCode) throws LDAPException { connection.getConnectionStatistics().incrementNumSearchResponses( numEntries, numReferences, (System.nanoTime() - requestTime)); SearchResult result = (SearchResult) response; result.setCounts(numEntries, entryList, numReferences, referenceList); if ((result.getResultCode().equals(ResultCode.REFERRAL)) && followReferrals(connection)) { if (depth >= connection.getConnectionOptions().getReferralHopLimit()) { return new SearchResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, ERR_TOO_MANY_REFERRALS.get(), result.getMatchedDN(), result.getReferralURLs(), entryList, referenceList, numEntries, numReferences, result.getResponseControls()); } result = followReferral(result, connection, depth); } if ((result.getResultCode().equals(ResultCode.SUCCESS)) && (! intermediateResultCode.equals(ResultCode.SUCCESS))) { return new SearchResult(messageID, intermediateResultCode, result.getDiagnosticMessage(), result.getMatchedDN(), result.getReferralURLs(), entryList, referenceList, numEntries, numReferences, result.getResponseControls()); } return result; } private LDAPResult followSearchReference(final int messageID, final SearchResultReference searchReference, final LDAPConnection connection, final int depth) throws LDAPException { for (final String urlString : searchReference.getReferralURLs()) { try { final LDAPURL referralURL = new LDAPURL(urlString); final String host = referralURL.getHost(); if (host == null) { continue; } final String requestBaseDN; if (referralURL.baseDNProvided()) { requestBaseDN = referralURL.getBaseDN().toString(); } else { requestBaseDN = baseDN; } final SearchScope requestScope; if (referralURL.scopeProvided()) { requestScope = referralURL.getScope(); } else { requestScope = scope; } final Filter requestFilter; if (referralURL.filterProvided()) { requestFilter = referralURL.getFilter(); } else { requestFilter = filter; } final SearchRequest searchRequest = new SearchRequest(searchResultListener, getControls(), requestBaseDN, requestScope, derefPolicy, sizeLimit, timeLimit, typesOnly, requestFilter, attributes); final LDAPConnection referralConn = connection.getReferralConnector(). getReferralConnection(referralURL, connection); try { return searchRequest.process(referralConn, depth+1); } finally { referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); referralConn.close(); } } catch (LDAPException le) { debugException(le); if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED)) { throw le; } } } return new SearchResult(messageID, ResultCode.REFERRAL, null, null, searchReference.getReferralURLs(), 0, 0, null); } private SearchResult followReferral(final SearchResult referralResult, final LDAPConnection connection, final int depth) throws LDAPException { for (final String urlString : referralResult.getReferralURLs()) { try { final LDAPURL referralURL = new LDAPURL(urlString); final String host = referralURL.getHost(); if (host == null) { continue; } final String requestBaseDN; if (referralURL.baseDNProvided()) { requestBaseDN = referralURL.getBaseDN().toString(); } else { requestBaseDN = baseDN; } final SearchScope requestScope; if (referralURL.scopeProvided()) { requestScope = referralURL.getScope(); } else { requestScope = scope; } final Filter requestFilter; if (referralURL.filterProvided()) { requestFilter = referralURL.getFilter(); } else { requestFilter = filter; } final SearchRequest searchRequest = new SearchRequest(searchResultListener, getControls(), requestBaseDN, requestScope, derefPolicy, sizeLimit, timeLimit, typesOnly, requestFilter, attributes); final LDAPConnection referralConn = connection.getReferralConnector(). getReferralConnection(referralURL, connection); try { return searchRequest.process(referralConn, depth+1); } finally { referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); referralConn.close(); } } catch (LDAPException le) { debugException(le); if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED)) { throw le; } } } return referralResult; } @InternalUseOnly() public void responseReceived(final LDAPResponse response) throws LDAPException { try { responseQueue.put(response); } catch (Exception e) { debugException(e); throw new LDAPException(ResultCode.LOCAL_ERROR, ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); } } @Override() public int getLastMessageID() { return messageID; } @Override() public OperationType getOperationType() { return OperationType.SEARCH; } public SearchRequest duplicate() { return duplicate(getControls()); } public SearchRequest duplicate(final Control[] controls) { final SearchRequest r = new SearchRequest(searchResultListener, controls, baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter, attributes); if (followReferralsInternal() != null) { r.setFollowReferrals(followReferralsInternal()); } r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); return r; } @Override() public void toString(final StringBuilder buffer) { buffer.append("SearchRequest(baseDN='"); buffer.append(baseDN); buffer.append("', scope="); buffer.append(scope); buffer.append(", deref="); buffer.append(derefPolicy); buffer.append(", sizeLimit="); buffer.append(sizeLimit); buffer.append(", timeLimit="); buffer.append(timeLimit); buffer.append(", filter='"); buffer.append(filter); buffer.append("', attrs={"); for (int i=0; i < attributes.length; i++) { if (i > 0) { buffer.append(", "); } buffer.append(attributes[i]); } buffer.append('}'); final Control[] controls = getControls(); if (controls.length > 0) { buffer.append(", controls={"); for (int i=0; i < controls.length; i++) { if (i > 0) { buffer.append(", "); } buffer.append(controls[i]); } buffer.append('}'); } buffer.append(')'); } }