/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Simple bean representing an ldap search result. Contains a map of entry DN to ldap entry.
*
* @author Middleware Services
*/
public class SearchResult extends AbstractLdapBean
{
/** hash code seed. */
private static final int HASH_CODE_SEED = 337;
/** serial version uid. */
private static final long serialVersionUID = -4686725717997623766L;
/** Entries contained in this result. */
private final Map<String, LdapEntry> resultEntries;
/** References contained in this result. */
private final Collection<SearchReference> searchReferences;
/** Default constructor. */
public SearchResult()
{
this(SortBehavior.getDefaultSortBehavior());
}
/**
* Creates a new search result.
*
* @param sb sort behavior of the results
*/
public SearchResult(final SortBehavior sb)
{
super(sb);
if (SortBehavior.UNORDERED == sb) {
resultEntries = new HashMap<>();
searchReferences = new HashSet<>();
} else if (SortBehavior.ORDERED == sb) {
resultEntries = new LinkedHashMap<>();
searchReferences = new LinkedHashSet<>();
} else if (SortBehavior.SORTED == sb) {
resultEntries = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
searchReferences = new TreeSet<>(
(ref1, ref2) -> {
return Arrays.toString(ref1.getReferralUrls()).compareTo(Arrays.toString(ref2.getReferralUrls()));
});
} else {
throw new IllegalArgumentException("Unknown sort behavior: " + sb);
}
}
/**
* Creates a new search result.
*
* @param entry ldap entry
*/
public SearchResult(final LdapEntry... entry)
{
this();
for (LdapEntry e : entry) {
addEntry(e);
}
}
/**
* Creates a new search result.
*
* @param entries collection of ldap entries
*/
public SearchResult(final Collection<LdapEntry> entries)
{
this();
addEntries(entries);
}
/**
* Returns a collection of ldap entry.
*
* @return collection of ldap entry
*/
public Collection<LdapEntry> getEntries()
{
return resultEntries.values();
}
/**
* Returns a single entry of this result. If multiple entries exist the first entry returned by the underlying
* iterator is used. If no entries exist null is returned.
*
* @return single entry
*/
public LdapEntry getEntry()
{
if (resultEntries.isEmpty()) {
return null;
}
return resultEntries.values().iterator().next();
}
/**
* Returns the ldap in this result with the supplied DN.
*
* @param dn of the entry to return
*
* @return ldap entry
*/
public LdapEntry getEntry(final String dn)
{
return resultEntries.get(dn.toLowerCase());
}
/**
* Returns the entry DNs in this result.
*
* @return string array of entry DNs
*/
public String[] getEntryDns()
{
return resultEntries.keySet().toArray(new String[resultEntries.keySet().size()]);
}
/**
* Adds an entry to this search result.
*
* @param entry entry to add
*/
public void addEntry(final LdapEntry... entry)
{
for (LdapEntry e : entry) {
resultEntries.put(e.getDn().toLowerCase(), e);
}
}
/**
* Adds entry(s) to this search result.
*
* @param entries collection of entries to add
*/
public void addEntries(final Collection<LdapEntry> entries)
{
entries.forEach(this::addEntry);
}
/**
* Removes an entry from this search result.
*
* @param entry entry to remove
*/
public void removeEntry(final LdapEntry... entry)
{
for (LdapEntry e : entry) {
resultEntries.remove(e.getDn().toLowerCase());
}
}
/**
* Removes the entry with the supplied dn from this search result.
*
* @param dn of entry to remove
*/
public void removeEntry(final String dn)
{
resultEntries.remove(dn.toLowerCase());
}
/**
* Removes the entry(s) from this search result.
*
* @param entries collection of ldap entries to remove
*/
public void removeEntries(final Collection<LdapEntry> entries)
{
entries.forEach(this::removeEntry);
}
/**
* Returns a collection of ldap entry.
*
* @return collection of ldap entry
*/
public Collection<SearchReference> getReferences()
{
return searchReferences;
}
/**
* Returns a single search reference of this result. If multiple references exist the first references returned by the
* underlying iterator is used. If no references exist null is returned.
*
* @return single search references
*/
public SearchReference getReference()
{
if (searchReferences.isEmpty()) {
return null;
}
return searchReferences.iterator().next();
}
/**
* Adds a reference to this search result.
*
* @param reference reference to add
*/
public void addReference(final SearchReference... reference)
{
Collections.addAll(searchReferences, reference);
}
/**
* Adds references(s) to this search result.
*
* @param references collection of references to add
*/
public void addReferences(final Collection<SearchReference> references)
{
references.forEach(this::addReference);
}
/**
* Removes a reference from this search result.
*
* @param reference reference to remove
*/
public void removeReference(final SearchReference... reference)
{
for (SearchReference r : reference) {
searchReferences.remove(r);
}
}
/**
* Removes the references(s) from this search result.
*
* @param references collection of search references to remove
*/
public void removeReferences(final Collection<SearchReference> references)
{
references.forEach(this::removeReference);
}
/**
* Returns a portion of this result between the specified fromIndex, inclusive, and toIndex, exclusive. If fromIndex
* and toIndex are equal, the return result is empty. The result of this method is undefined for unordered results.
*
* @param fromIndex low endpoint of the search result (inclusive)
* @param toIndex high endpoint of the search result (exclusive)
*
* @return portion of this search result
*
* @throws IndexOutOfBoundsException for illegal index values
*/
public SearchResult subResult(final int fromIndex, final int toIndex)
{
if (fromIndex < 0 || toIndex > resultEntries.size() || fromIndex > toIndex) {
throw new IndexOutOfBoundsException("Illegal index value");
}
final SearchResult result = new SearchResult(getSortBehavior());
if (resultEntries.isEmpty() || fromIndex == toIndex) {
return result;
}
int i = 0;
for (Map.Entry<String, LdapEntry> e : resultEntries.entrySet()) {
if (i >= fromIndex && i < toIndex) {
result.addEntry(e.getValue());
}
i++;
}
return result;
}
/**
* Returns the number of entries in this search result.
*
* @return number of entries in this search result
*/
public int size()
{
return resultEntries.size();
}
/** Removes all the entries in this search result. */
public void clear()
{
resultEntries.clear();
}
@Override
public boolean equals(final Object o)
{
if (o == this) {
return true;
}
if (o instanceof SearchResult) {
final SearchResult v = (SearchResult) o;
return LdapUtils.areEqual(resultEntries, v.resultEntries) &&
LdapUtils.areEqual(searchReferences, v.searchReferences);
}
return false;
}
@Override
public int hashCode()
{
return LdapUtils.computeHashCode(HASH_CODE_SEED, resultEntries.values(), searchReferences);
}
@Override
public String toString()
{
return
String.format(
"[%s@%d::entries=%s, references=%s]",
getClass().getName(),
hashCode(),
resultEntries.values(),
searchReferences);
}
/**
* Merges the entries in the supplied result into a single entry. This method always returns a search result of size
* zero or one.
*
* @param result search result containing entries to merge
*
* @return search result containing a single merged entry
*/
public static SearchResult mergeEntries(final SearchResult result)
{
LdapEntry mergedEntry = null;
if (result != null) {
for (LdapEntry le : result.getEntries()) {
if (mergedEntry == null) {
mergedEntry = le;
} else {
for (LdapAttribute la : le.getAttributes()) {
final LdapAttribute oldAttr = mergedEntry.getAttribute(la.getName());
if (oldAttr == null) {
mergedEntry.addAttribute(la);
} else {
if (oldAttr.isBinary()) {
oldAttr.addBinaryValues(la.getBinaryValues());
} else {
oldAttr.addStringValues(la.getStringValues());
}
}
}
}
}
}
return mergedEntry != null ? new SearchResult(mergedEntry) : new SearchResult();
}
}