use of com.unboundid.ldap.sdk.EntrySorter in project ldapsdk by pingidentity.
the class InMemoryRequestHandler method processSearchRequest.
/**
* Attempts to process the provided search request. The attempt will fail
* if any of the following conditions is true:
* <UL>
* <LI>There is a problem with any of the request controls.</LI>
* <LI>The modify DN request contains a malformed target DN, new RDN, or
* new superior DN.</LI>
* <LI>The new DN of the entry would conflict with the DN of an existing
* entry.</LI>
* <LI>The new DN of the entry would exist outside the set of defined
* base DNs.</LI>
* <LI>The new DN of the entry is not a defined base DN and does not exist
* immediately below an existing entry.</LI>
* </UL>
*
* @param messageID The message ID of the LDAP message containing the
* search request.
* @param request The search request that was included in the LDAP
* message that was received.
* @param controls The set of controls included in the LDAP message.
* It may be empty if there were no controls, but will
* not be {@code null}.
* @param entryList A list to which to add search result entries
* intended for return to the client. It must not be
* {@code null}.
* @param referenceList A list to which to add search result references
* intended for return to the client. It must not be
* {@code null}.
*
* @return The {@link LDAPMessage} containing the response to send to the
* client. The protocol op in the {@code LDAPMessage} must be an
* {@code SearchResultDoneProtocolOp}.
*/
@NotNull()
LDAPMessage processSearchRequest(final int messageID, @NotNull final SearchRequestProtocolOp request, @NotNull final List<Control> controls, @NotNull final List<SearchResultEntry> entryList, @NotNull final List<SearchResultReference> referenceList) {
synchronized (entryMap) {
// Sleep before processing, if appropriate.
final long processingStartTime = System.currentTimeMillis();
sleepBeforeProcessing();
// Look at the filter and see if it contains any unsupported elements.
try {
ensureFilterSupported(request.getFilter());
} catch (final LDAPException le) {
Debug.debugException(le);
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(le.getResultCode().intValue(), null, le.getMessage(), null));
}
// Look at the time limit for the search request and see if sleeping
// would have caused us to exceed that time limit. It's extremely
// unlikely that any search in the in-memory directory server would take
// a second or more to complete, and that's the minimum time limit that
// can be requested, so there's no need to check the time limit in most
// cases. However, someone may want to force a "time limit exceeded"
// response by configuring a delay that is greater than the requested time
// limit, so we should check now to see if that's been exceeded.
final long timeLimitMillis = 1000L * request.getTimeLimit();
if (timeLimitMillis > 0L) {
final long timeLimitExpirationTime = processingStartTime + timeLimitMillis;
if (System.currentTimeMillis() >= timeLimitExpirationTime) {
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.TIME_LIMIT_EXCEEDED_INT_VALUE, null, ERR_MEM_HANDLER_TIME_LIMIT_EXCEEDED.get(), null));
}
}
// Process the provided request controls.
final Map<String, Control> controlMap;
try {
controlMap = RequestControlPreProcessor.processControls(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST, controls);
} catch (final LDAPException le) {
Debug.debugException(le);
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(le.getResultCode().intValue(), null, le.getMessage(), null));
}
final ArrayList<Control> responseControls = new ArrayList<>(1);
// If this operation type is not allowed, then reject it.
final boolean isInternalOp = controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
if ((!isInternalOp) && (!config.getAllowedOperationTypes().contains(OperationType.SEARCH))) {
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, ERR_MEM_HANDLER_SEARCH_NOT_ALLOWED.get(), null));
}
// client is authenticated.
if ((authenticatedDN.isNullDN() && config.getAuthenticationRequiredOperationTypes().contains(OperationType.SEARCH))) {
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null, ERR_MEM_HANDLER_SEARCH_REQUIRES_AUTH.get(), null));
}
// Get the parsed base DN.
final DN baseDN;
final Schema schema = schemaRef.get();
try {
baseDN = new DN(request.getBaseDN(), schema);
} catch (final LDAPException le) {
Debug.debugException(le);
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(request.getBaseDN(), le.getMessage()), null));
}
// See if the search base or one of its superiors is a smart referral.
final boolean hasManageDsaIT = controlMap.containsKey(ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
if (!hasManageDsaIT) {
final Entry referralEntry = findNearestReferral(baseDN);
if (referralEntry != null) {
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(), INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(), getReferralURLs(baseDN, referralEntry)));
}
}
// Make sure that the base entry exists. It may be the root DSE or
// subschema subentry.
final Entry baseEntry;
boolean includeChangeLog = true;
if (baseDN.isNullDN()) {
baseEntry = generateRootDSE();
includeChangeLog = false;
} else if (baseDN.equals(subschemaSubentryDN)) {
baseEntry = subschemaSubentryRef.get();
} else {
baseEntry = entryMap.get(baseDN);
}
if (baseEntry == null) {
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(baseDN), ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(request.getBaseDN()), null));
}
// controls.
try {
handleAssertionRequestControl(controlMap, baseEntry);
handleProxiedAuthControl(controlMap);
} catch (final LDAPException le) {
Debug.debugException(le);
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(le.getResultCode().intValue(), null, le.getMessage(), null));
}
// Determine whether to include subentries in search results.
final boolean includeSubEntries;
final boolean includeNonSubEntries;
final SearchScope scope = request.getScope();
if (scope == SearchScope.BASE) {
includeSubEntries = true;
includeNonSubEntries = true;
} else if (controlMap.containsKey(DraftLDUPSubentriesRequestControl.SUBENTRIES_REQUEST_OID)) {
includeSubEntries = true;
includeNonSubEntries = false;
} else if (controlMap.containsKey(RFC3672SubentriesRequestControl.SUBENTRIES_REQUEST_OID)) {
includeSubEntries = true;
final RFC3672SubentriesRequestControl c = (RFC3672SubentriesRequestControl) controlMap.get(RFC3672SubentriesRequestControl.SUBENTRIES_REQUEST_OID);
includeNonSubEntries = (!c.returnOnlySubEntries());
} else if (baseEntry.hasObjectClass("ldapSubEntry") || baseEntry.hasObjectClass("inheritableLDAPSubEntry")) {
includeSubEntries = true;
includeNonSubEntries = true;
} else if (filterIncludesLDAPSubEntry(request.getFilter())) {
includeSubEntries = true;
includeNonSubEntries = true;
} else {
includeSubEntries = false;
includeNonSubEntries = true;
}
// Create a temporary list to hold all of the entries to be returned.
// These entries will not have been pared down based on the requested
// attributes.
final List<Entry> fullEntryList = new ArrayList<>(entryMap.size());
findEntriesAndRefs: {
// Check the scope. If it is a base-level search, then we only need to
// examine the base entry. Otherwise, we'll have to scan the entire
// entry map.
final Filter filter = request.getFilter();
if (scope == SearchScope.BASE) {
try {
if (filter.matchesEntry(baseEntry, schema)) {
processSearchEntry(baseEntry, includeSubEntries, includeNonSubEntries, includeChangeLog, hasManageDsaIT, fullEntryList, referenceList);
}
} catch (final Exception e) {
Debug.debugException(e);
}
break findEntriesAndRefs;
}
// set.
if ((scope == SearchScope.ONE) && baseDN.isNullDN()) {
for (final DN dn : baseDNs) {
final Entry e = entryMap.get(dn);
if (e != null) {
try {
if (filter.matchesEntry(e, schema)) {
processSearchEntry(e, includeSubEntries, includeNonSubEntries, includeChangeLog, hasManageDsaIT, fullEntryList, referenceList);
}
} catch (final Exception ex) {
Debug.debugException(ex);
}
}
}
break findEntriesAndRefs;
}
// Try to use indexes to process the request. If we can't use any
// indexes to get a candidate list, then just iterate over all the
// entries. It's not necessary to consider the root DSE for non-base
// scopes.
final Set<DN> candidateDNs = indexSearch(filter);
if (candidateDNs == null) {
for (final Map.Entry<DN, ReadOnlyEntry> me : entryMap.entrySet()) {
final DN dn = me.getKey();
final Entry entry = me.getValue();
try {
if (dn.matchesBaseAndScope(baseDN, scope)) {
if (filter.matchesEntry(entry, schema) || (((!hasManageDsaIT) && entry.hasObjectClass("referral") && entry.hasAttribute("ref")))) {
processSearchEntry(entry, includeSubEntries, includeNonSubEntries, includeChangeLog, hasManageDsaIT, fullEntryList, referenceList);
}
}
} catch (final Exception e) {
Debug.debugException(e);
}
}
} else {
for (final DN dn : candidateDNs) {
try {
if (!dn.matchesBaseAndScope(baseDN, scope)) {
continue;
}
final Entry entry = entryMap.get(dn);
if (filter.matchesEntry(entry, schema) || (((!hasManageDsaIT) && entry.hasObjectClass("referral") && entry.hasAttribute("ref")))) {
processSearchEntry(entry, includeSubEntries, includeNonSubEntries, includeChangeLog, hasManageDsaIT, fullEntryList, referenceList);
}
} catch (final Exception e) {
Debug.debugException(e);
}
}
}
}
// If the request included the server-side sort request control, then sort
// the matching entries appropriately.
final ServerSideSortRequestControl sortRequestControl = (ServerSideSortRequestControl) controlMap.get(ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID);
if (sortRequestControl != null) {
final EntrySorter entrySorter = new EntrySorter(false, schema, sortRequestControl.getSortKeys());
final SortedSet<Entry> sortedEntrySet = entrySorter.sort(fullEntryList);
fullEntryList.clear();
fullEntryList.addAll(sortedEntrySet);
responseControls.add(new ServerSideSortResponseControl(ResultCode.SUCCESS, null));
}
// If the request included the simple paged results control, then handle
// it.
final SimplePagedResultsControl pagedResultsControl = (SimplePagedResultsControl) controlMap.get(SimplePagedResultsControl.PAGED_RESULTS_OID);
if (pagedResultsControl != null) {
final int totalSize = fullEntryList.size();
final int pageSize = pagedResultsControl.getSize();
final ASN1OctetString cookie = pagedResultsControl.getCookie();
final int offset;
if ((cookie == null) || (cookie.getValueLength() == 0)) {
// This is the first request in the series, so start at the beginning
// of the list.
offset = 0;
} else {
// offset within the result list at which to start the next batch.
try {
final ASN1Integer offsetInteger = ASN1Integer.decodeAsInteger(cookie.getValue());
offset = offsetInteger.intValue();
} catch (final Exception e) {
Debug.debugException(e);
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.PROTOCOL_ERROR_INT_VALUE, null, ERR_MEM_HANDLER_MALFORMED_PAGED_RESULTS_COOKIE.get(), null), responseControls);
}
}
// Create an iterator that will be used to remove entries from the
// result set that are outside of the requested page of results.
int pos = 0;
final Iterator<Entry> iterator = fullEntryList.iterator();
// offset.
while (iterator.hasNext() && (pos < offset)) {
iterator.next();
iterator.remove();
pos++;
}
// Next, skip over the entries that should be returned.
int keptEntries = 0;
while (iterator.hasNext() && (keptEntries < pageSize)) {
iterator.next();
pos++;
keptEntries++;
}
// to include in the response. Otherwise, use an empty cookie.
if (iterator.hasNext()) {
responseControls.add(new SimplePagedResultsControl(totalSize, new ASN1OctetString(new ASN1Integer(pos).encode()), false));
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
} else {
responseControls.add(new SimplePagedResultsControl(totalSize, new ASN1OctetString(), false));
}
}
// If the request includes the virtual list view request control, then
// handle it.
final VirtualListViewRequestControl vlvRequest = (VirtualListViewRequestControl) controlMap.get(VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID);
if (vlvRequest != null) {
final int totalEntries = fullEntryList.size();
final ASN1OctetString assertionValue = vlvRequest.getAssertionValue();
// Figure out the position of the target entry in the list.
int offset = vlvRequest.getTargetOffset();
if (assertionValue == null) {
// The offset is one-based, so we need to adjust it for the list's
// zero-based offset. Also, make sure to put it within the bounds of
// the list.
offset--;
offset = Math.max(0, offset);
offset = Math.min(fullEntryList.size(), offset);
} else {
final SortKey primarySortKey = sortRequestControl.getSortKeys()[0];
final Entry testEntry = new Entry("cn=test", schema, new Attribute(primarySortKey.getAttributeName(), assertionValue));
final EntrySorter entrySorter = new EntrySorter(false, schema, primarySortKey);
offset = fullEntryList.size();
for (int i = 0; i < fullEntryList.size(); i++) {
if (entrySorter.compare(fullEntryList.get(i), testEntry) >= 0) {
offset = i;
break;
}
}
}
// Get the start and end positions based on the before and after counts.
final int beforeCount = Math.max(0, vlvRequest.getBeforeCount());
final int afterCount = Math.max(0, vlvRequest.getAfterCount());
final int start = Math.max(0, (offset - beforeCount));
final int end = Math.min(fullEntryList.size(), (offset + afterCount + 1));
// Create an iterator to use to alter the list so that it only contains
// the appropriate set of entries.
int pos = 0;
final Iterator<Entry> iterator = fullEntryList.iterator();
while (iterator.hasNext()) {
iterator.next();
if ((pos < start) || (pos >= end)) {
iterator.remove();
}
pos++;
}
// Create the appropriate response control.
responseControls.add(new VirtualListViewResponseControl((offset + 1), totalEntries, ResultCode.SUCCESS, null));
}
// Process the set of requested attributes so that we can pare down the
// entries.
final SearchEntryParer parer = new SearchEntryParer(request.getAttributes(), schema);
final int sizeLimit;
if (request.getSizeLimit() > 0) {
sizeLimit = Math.min(request.getSizeLimit(), maxSizeLimit);
} else {
sizeLimit = maxSizeLimit;
}
int entryCount = 0;
for (final Entry e : fullEntryList) {
entryCount++;
if (entryCount > sizeLimit) {
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.SIZE_LIMIT_EXCEEDED_INT_VALUE, null, ERR_MEM_HANDLER_SEARCH_SIZE_LIMIT_EXCEEDED.get(), null), responseControls);
}
final Entry trimmedEntry = parer.pareEntry(e);
if (request.typesOnly()) {
final Entry typesOnlyEntry = new Entry(trimmedEntry.getDN(), schema);
for (final Attribute a : trimmedEntry.getAttributes()) {
typesOnlyEntry.addAttribute(new Attribute(a.getName()));
}
entryList.add(new SearchResultEntry(typesOnlyEntry));
} else {
entryList.add(new SearchResultEntry(trimmedEntry));
}
}
return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null, null), responseControls);
}
}
Aggregations