/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive.handler; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.ldaptive.Connection; import org.ldaptive.LdapAttribute; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.LdapUtils; import org.ldaptive.SearchEntry; import org.ldaptive.SearchOperation; import org.ldaptive.SearchRequest; import org.ldaptive.SearchResult; /** * This recursively searches based on a supplied attribute and merges those results into the original entry. For the * following LDIF: * * <pre> dn: uugid=group1,ou=groups,dc=ldaptive,dc=org uugid: group1 member: uugid=group2,ou=groups,dc=ldaptive,dc=org dn: uugid=group2,ou=groups,dc=ldaptive,dc=org uugid: group2 * </pre> * * <p>With the following code:</p> * * <pre> RecursiveEntryHandler reh = new RecursiveEntryHandler("member", "uugid"); * </pre> * * <p>Will produce this result for the query (uugid=group1):</p> * * <pre> dn: uugid=group1,ou=groups,dc=ldaptive,dc=org uugid: group1 uugid: group2 member: uugid=group2,ou=groups,dc=ldaptive,dc=org * </pre> * * @author Middleware Services */ public class RecursiveEntryHandler extends AbstractSearchEntryHandler { /** hash code seed. */ private static final int HASH_CODE_SEED = 829; /** Attribute to recursively search on. */ private String searchAttribute; /** Attribute(s) to merge. */ private String[] mergeAttributes; /** Attributes to return when searching, mergeAttributes + searchAttribute. */ private String[] retAttrs; /** Default constructor. */ public RecursiveEntryHandler() {} /** * Creates a new recursive entry handler. * * @param searchAttr attribute to search on * @param mergeAttrs attribute names to merge */ public RecursiveEntryHandler(final String searchAttr, final String... mergeAttrs) { searchAttribute = searchAttr; mergeAttributes = mergeAttrs; initalizeReturnAttributes(); } /** * Returns the attribute name that will be recursively searched on. * * @return attribute name */ public String getSearchAttribute() { return searchAttribute; } /** * Sets the attribute name that will be recursively searched on. * * @param name of the search attribute */ public void setSearchAttribute(final String name) { searchAttribute = name; initalizeReturnAttributes(); } /** * Returns the attribute names that will be merged by the recursive search. * * @return attribute names */ public String[] getMergeAttributes() { return mergeAttributes; } /** * Sets the attribute name that will be merged by the recursive search. * * @param mergeAttrs attribute names to merge */ public void setMergeAttributes(final String... mergeAttrs) { mergeAttributes = mergeAttrs; initalizeReturnAttributes(); } /** * Initializes the return attributes array. Must be called after both searchAttribute and mergeAttributes have been * set. */ protected void initalizeReturnAttributes() { if (mergeAttributes != null && searchAttribute != null) { // return attributes must include the search attribute retAttrs = new String[mergeAttributes.length + 1]; System.arraycopy(mergeAttributes, 0, retAttrs, 0, mergeAttributes.length); retAttrs[retAttrs.length - 1] = searchAttribute; } } @Override public HandlerResult<SearchEntry> handle(final Connection conn, final SearchRequest request, final SearchEntry entry) throws LdapException { // Recursively searches a list of attributes and merges those results with // the existing entry. final List<String> searchedDns = new ArrayList<>(); if (entry.getAttribute(searchAttribute) != null) { searchedDns.add(entry.getDn()); readSearchAttribute(conn, entry, searchedDns); } else { recursiveSearch(conn, entry.getDn(), entry, searchedDns); } return new HandlerResult<>(entry); } /** * Reads the values of {@link #searchAttribute} from the supplied attributes and calls {@link #recursiveSearch} for * each. * * @param conn to perform search operation on * @param entry to read * @param searchedDns list of DNs whose attributes have been read * * @throws LdapException if a search error occurs */ private void readSearchAttribute(final Connection conn, final LdapEntry entry, final List<String> searchedDns) throws LdapException { if (entry != null) { final LdapAttribute attr = entry.getAttribute(searchAttribute); if (attr != null && !attr.isBinary()) { final Set<String> values = new HashSet<>(attr.getStringValues()); for (String s : values) { recursiveSearch(conn, s, entry, searchedDns); } } } } /** * Recursively gets the attribute(s) {@link #mergeAttributes} for the supplied dn and adds the values to the supplied * attributes. * * @param conn to perform search operation on * @param dn to get attribute(s) for * @param entry to merge with * @param searchedDns list of DNs that have been searched for * * @throws LdapException if a search error occurs */ private void recursiveSearch( final Connection conn, final String dn, final LdapEntry entry, final List<String> searchedDns) throws LdapException { if (!searchedDns.contains(dn)) { LdapEntry newEntry = null; try { final SearchOperation search = new SearchOperation(conn); final SearchRequest sr = SearchRequest.newObjectScopeSearchRequest(dn, retAttrs); final SearchResult result = search.execute(sr).getResult(); newEntry = result.getEntry(dn); } catch (LdapException e) { logger.warn("Error retrieving attribute(s): {}", Arrays.toString(retAttrs), e); } searchedDns.add(dn); if (newEntry != null) { // recursively search new attributes readSearchAttribute(conn, newEntry, searchedDns); // merge new attribute values for (String s : mergeAttributes) { final LdapAttribute newAttr = newEntry.getAttribute(s); if (newAttr != null) { final LdapAttribute oldAttr = entry.getAttribute(s); if (oldAttr == null) { entry.addAttribute(newAttr); } else { if (newAttr.isBinary()) { newAttr.getBinaryValues().forEach(oldAttr::addBinaryValue); } else { newAttr.getStringValues().forEach(oldAttr::addStringValue); } } } } } } } @Override public boolean equals(final Object o) { if (o == this) { return true; } if (o instanceof RecursiveEntryHandler) { final RecursiveEntryHandler v = (RecursiveEntryHandler) o; return LdapUtils.areEqual(mergeAttributes, v.mergeAttributes) && LdapUtils.areEqual(retAttrs, v.retAttrs) && LdapUtils.areEqual(searchAttribute, v.searchAttribute); } return false; } @Override public int hashCode() { return LdapUtils.computeHashCode(HASH_CODE_SEED, mergeAttributes, retAttrs, searchAttribute); } @Override public String toString() { return String.format( "[%s@%d::searchAttribute=%s, mergeAttributes=%s, retAttrs=%s]", getClass().getName(), hashCode(), searchAttribute, Arrays.toString(mergeAttributes), Arrays.toString(retAttrs)); } }