All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.ldap.core.LdapTemplate Maven / Gradle / Ivy

/*
 * Copyright 2005-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.ldap.core;

import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.naming.Binding;
import javax.naming.Name;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.PartialResultException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.UncategorizedLdapException;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
import org.springframework.ldap.odm.core.OdmException;
import org.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

/**
 * Executes core LDAP functionality and helps to avoid common errors, relieving the user
 * of the burden of looking up contexts, looping through NamingEnumerations and closing
 * contexts.
 * 

* Note for Active Directory (AD) users: AD servers are apparently unable to handle * referrals automatically, which causes a PartialResultException to be * thrown whenever a referral is encountered in a search. To avoid this, set the * ignorePartialResultException property to true. There is * currently no way of manually handling these referrals in the form of * ReferralException, i.e. either you get the exception (and your results are * lost) or all referrals are ignored (if the server is unable to handle them properly. * Neither is there any simple way to get notified that a * PartialResultException has been ignored (other than in the log). * * @author Mattias Hellborg Arthursson * @author Ulrik Sandberg * @see org.springframework.ldap.core.ContextSource */ public class LdapTemplate implements LdapOperations, InitializingBean { private static final Logger LOG = LoggerFactory.getLogger(LdapTemplate.class); private static final boolean DONT_RETURN_OBJ_FLAG = false; private static final boolean RETURN_OBJ_FLAG = true; private static final String[] ALL_ATTRIBUTES = null; private ContextSource contextSource; private boolean ignorePartialResultException = false; private boolean ignoreNameNotFoundException = false; private boolean ignoreSizeLimitExceededException = true; private int defaultSearchScope = SearchControls.SUBTREE_SCOPE; private int defaultTimeLimit = 0; private int defaultCountLimit = 0; private ObjectDirectoryMapper odm = new DefaultObjectDirectoryMapper(); /** * Constructor for bean usage. */ public LdapTemplate() { } /** * Constructor to setup instance directly. * @param contextSource the ContextSource to use. */ public LdapTemplate(ContextSource contextSource) { this.contextSource = contextSource; } /** * Set the ContextSource. Call this method when the default constructor has been used. * @param contextSource the ContextSource. */ public void setContextSource(ContextSource contextSource) { this.contextSource = contextSource; } /** * {@inheritDoc} */ @Override public ObjectDirectoryMapper getObjectDirectoryMapper() { return this.odm; } /** * Set the ObjectDirectoryMapper instance to use. * @param odm the ObejctDirectoryMapper to use. * @since 2.0 */ public void setObjectDirectoryMapper(ObjectDirectoryMapper odm) { this.odm = odm; } /** * Get the ContextSource. * @return the ContextSource. */ public ContextSource getContextSource() { return this.contextSource; } /** * Specify whether NameNotFoundException should be ignored in searches. * In previous version, NameNotFoundException caused by the search base * not being found was silently ignored. The default behavior is now to treat this as * an error (as it should), and to convert and re-throw the exception. The ability to * revert to the previous behavior still exists. The only difference is that the * incident is in that case no longer silently ignored, but logged as a warning. * @param ignore true if NameNotFoundException should be * ignored in searches, false otherwise. Default is false. * * @since 1.3 */ public void setIgnoreNameNotFoundException(boolean ignore) { this.ignoreNameNotFoundException = ignore; } /** * Specify whether PartialResultException should be ignored in searches. * AD servers typically have a problem with referrals. Normally a referral should be * followed automatically, but this does not seem to work with AD servers. The problem * manifests itself with a PartialResultException being thrown when a * referral is encountered by the server. Setting this property to true * presents a workaround to this problem by causing * PartialResultException to be ignored, so that the search method * returns normally. Default value of this parameter is false. * @param ignore true if PartialResultException should be * ignored in searches, false otherwise. Default is false. */ public void setIgnorePartialResultException(boolean ignore) { this.ignorePartialResultException = ignore; } /** * Specify whether SizeLimitExceededException should be ignored in * searches. This is typically what you want if you specify count limit in your search * controls. * @param ignore true if SizeLimitExceededException should * be ignored in searches, false otherwise. Default is true. * @since 2.0 */ public void setIgnoreSizeLimitExceededException(boolean ignore) { this.ignoreSizeLimitExceededException = ignore; } /** * Set the default scope to be used in searches if not explicitly specified. Default * is {@link javax.naming.directory.SearchControls#SUBTREE_SCOPE}. * @param defaultSearchScope the default search scope to use in searches. One of * {@link SearchControls#OBJECT_SCOPE}, {@link SearchControls#ONELEVEL_SCOPE}, or * {@link SearchControls#SUBTREE_SCOPE} * @since 2.0 */ public void setDefaultSearchScope(int defaultSearchScope) { this.defaultSearchScope = defaultSearchScope; } /** * Set the default time limit be used in searches if not explicitly specified. Default * is 0, indicating no time limit. * @param defaultTimeLimit the default time limit to use in searches. * @since 2.0 */ public void setDefaultTimeLimit(int defaultTimeLimit) { this.defaultTimeLimit = defaultTimeLimit; } /** * Set the default count limit be used in searches if not explicitly specified. * Default is 0, indicating no count limit. * @param defaultCountLimit the default count limit to use in searches. * @since 2.0 */ public void setDefaultCountLimit(int defaultCountLimit) { this.defaultCountLimit = defaultCountLimit; } /** * {@inheritDoc} */ @Override public void search(Name base, String filter, int searchScope, boolean returningObjFlag, NameClassPairCallbackHandler handler) { search(base, filter, getDefaultSearchControls(searchScope, returningObjFlag, ALL_ATTRIBUTES), handler); } /** * {@inheritDoc} */ @Override public void search(String base, String filter, int searchScope, boolean returningObjFlag, NameClassPairCallbackHandler handler) { search(base, filter, getDefaultSearchControls(searchScope, returningObjFlag, ALL_ATTRIBUTES), handler); } /** * {@inheritDoc} */ @Override public void search(final Name base, final String filter, final SearchControls controls, NameClassPairCallbackHandler handler) { // Create a SearchExecutor to perform the search. SearchExecutor se = new SearchExecutor() { public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { return ctx.search(base, filter, controls); } }; if (handler instanceof ContextMapperCallbackHandler) { assureReturnObjFlagSet(controls); } search(se, handler); } /** * {@inheritDoc} */ @Override public void search(final String base, final String filter, final SearchControls controls, NameClassPairCallbackHandler handler) { // Create a SearchExecutor to perform the search. SearchExecutor se = new SearchExecutor() { public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { return ctx.search(base, filter, controls); } }; if (handler instanceof ContextMapperCallbackHandler) { assureReturnObjFlagSet(controls); } search(se, handler); } /** * {@inheritDoc} */ @Override public void search(final Name base, final String filter, final SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor) { // Create a SearchExecutor to perform the search. SearchExecutor se = new SearchExecutor() { public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { return ctx.search(base, filter, controls); } }; if (handler instanceof ContextMapperCallbackHandler) { assureReturnObjFlagSet(controls); } search(se, handler, processor); } /** * {@inheritDoc} */ @Override public void search(final String base, final String filter, final SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor) { // Create a SearchExecutor to perform the search. SearchExecutor se = new SearchExecutor() { public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { return ctx.search(base, filter, controls); } }; if (handler instanceof ContextMapperCallbackHandler) { assureReturnObjFlagSet(controls); } search(se, handler, processor); } /** * Perform a search operation, such as a search(), list() or listBindings(). This * method handles all the plumbing; getting a readonly context; looping through the * NamingEnumeration and closing the context and enumeration. It also calls the * supplied DirContextProcessor before and after the search, respectively. This * enables custom pre-processing and post-processing, like for example when handling * paged results or other search controls. *

* The actual list is delegated to the {@link SearchExecutor} and each * {@link NameClassPair} (this might be a NameClassPair or a subclass thereof) is * passed to the CallbackHandler. Any encountered NamingException will be translated * using the NamingExceptionTranslator. * @param se the SearchExecutor to use for performing the actual list. * @param handler the NameClassPairCallbackHandler to which each found entry will be * passed. * @param processor DirContextProcessor for custom pre- and post-processing. Must not * be null. If no custom processing should take place, please use e.g. * {@link #search(SearchExecutor, NameClassPairCallbackHandler)}. * @throws NamingException if any error occurs. Note that a NameNotFoundException will * be ignored. Instead this is interpreted that no entries were found. */ @Override public void search(SearchExecutor se, NameClassPairCallbackHandler handler, DirContextProcessor processor) { DirContext ctx = this.contextSource.getReadOnlyContext(); NamingEnumeration results = null; RuntimeException exception = null; try { processor.preProcess(ctx); results = se.executeSearch(ctx); while (results.hasMore()) { NameClassPair result = (NameClassPair) results.next(); handler.handleNameClassPair(result); } } catch (NameNotFoundException ex) { // It is possible to ignore errors caused by base not found if (this.ignoreNameNotFoundException) { LOG.warn("Base context not found, ignoring: " + ex.getMessage()); } else { exception = LdapUtils.convertLdapException(ex); } } catch (PartialResultException ex) { // Workaround for AD servers not handling referrals correctly. if (this.ignorePartialResultException) { LOG.debug("PartialResultException encountered and ignored", ex); } else { exception = LdapUtils.convertLdapException(ex); } } catch (SizeLimitExceededException ex) { if (this.ignoreSizeLimitExceededException) { LOG.debug("SizeLimitExceededException encountered and ignored", ex); } else { exception = LdapUtils.convertLdapException(ex); } } catch (javax.naming.NamingException ex) { exception = LdapUtils.convertLdapException(ex); } finally { try { processor.postProcess(ctx); } catch (javax.naming.NamingException ex) { if (exception == null) { exception = LdapUtils.convertLdapException(ex); } else { // We already had an exception from above and should ignore // this one. LOG.debug("Ignoring Exception from postProcess, " + "main exception thrown instead", ex); } } closeContextAndNamingEnumeration(ctx, results); // If we got an exception it should be thrown. if (exception != null) { throw exception; } } } /** * Perform a search operation, such as a search(), list() or listBindings(). This * method handles all the plumbing; getting a readonly context; looping through the * NamingEnumeration and closing the context and enumeration. *

* The actual list is delegated to the {@link SearchExecutor} and each * {@link NameClassPair} (this might be a NameClassPair or a subclass thereof) is * passed to the CallbackHandler. Any encountered NamingException will be translated * using the NamingExceptionTranslator. * @param se the SearchExecutor to use for performing the actual list. * @param handler the NameClassPairCallbackHandler to which each found entry will be * passed. * @throws NamingException if any error occurs. Note that a NameNotFoundException will * be ignored. Instead this is interpreted that no entries were found. */ @Override public void search(SearchExecutor se, NameClassPairCallbackHandler handler) { search(se, handler, new NullDirContextProcessor()); } /** * {@inheritDoc} */ @Override public void search(Name base, String filter, NameClassPairCallbackHandler handler) { SearchControls controls = getDefaultSearchControls(this.defaultSearchScope, DONT_RETURN_OBJ_FLAG, ALL_ATTRIBUTES); if (handler instanceof ContextMapperCallbackHandler) { assureReturnObjFlagSet(controls); } search(base, filter, controls, handler); } /** * {@inheritDoc} */ @Override public void search(String base, String filter, NameClassPairCallbackHandler handler) { SearchControls controls = getDefaultSearchControls(this.defaultSearchScope, DONT_RETURN_OBJ_FLAG, ALL_ATTRIBUTES); if (handler instanceof ContextMapperCallbackHandler) { assureReturnObjFlagSet(controls); } search(base, filter, controls, handler); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, int searchScope, String[] attrs, AttributesMapper mapper) { return search(base, filter, getDefaultSearchControls(searchScope, DONT_RETURN_OBJ_FLAG, attrs), mapper); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, int searchScope, String[] attrs, AttributesMapper mapper) { return search(base, filter, getDefaultSearchControls(searchScope, DONT_RETURN_OBJ_FLAG, attrs), mapper); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, int searchScope, AttributesMapper mapper) { return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, int searchScope, AttributesMapper mapper) { return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, AttributesMapper mapper) { return search(base, filter, this.defaultSearchScope, mapper); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, AttributesMapper mapper) { return search(base, filter, this.defaultSearchScope, mapper); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, int searchScope, String[] attrs, ContextMapper mapper) { return search(base, filter, getDefaultSearchControls(searchScope, RETURN_OBJ_FLAG, attrs), mapper); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, int searchScope, String[] attrs, ContextMapper mapper) { return search(base, filter, getDefaultSearchControls(searchScope, RETURN_OBJ_FLAG, attrs), mapper); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, int searchScope, ContextMapper mapper) { return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, int searchScope, ContextMapper mapper) { return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, ContextMapper mapper) { return search(base, filter, this.defaultSearchScope, mapper); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, ContextMapper mapper) { return search(base, filter, this.defaultSearchScope, mapper); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, SearchControls controls, ContextMapper mapper) { return search(base, filter, controls, mapper, new NullDirContextProcessor()); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, SearchControls controls, ContextMapper mapper) { return search(base, filter, controls, mapper, new NullDirContextProcessor()); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, SearchControls controls, AttributesMapper mapper) { return search(base, filter, controls, mapper, new NullDirContextProcessor()); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, SearchControls controls, AttributesMapper mapper) { return search(base, filter, controls, mapper, new NullDirContextProcessor()); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, SearchControls controls, AttributesMapper mapper, DirContextProcessor processor) { AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(mapper); search(base, filter, controls, handler, processor); return handler.getList(); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, SearchControls controls, AttributesMapper mapper, DirContextProcessor processor) { AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(mapper); search(base, filter, controls, handler, processor); return handler.getList(); } /** * {@inheritDoc} */ @Override public List search(String base, String filter, SearchControls controls, ContextMapper mapper, DirContextProcessor processor) { assureReturnObjFlagSet(controls); ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(mapper); search(base, filter, controls, handler, processor); return handler.getList(); } /** * {@inheritDoc} */ @Override public List search(Name base, String filter, SearchControls controls, ContextMapper mapper, DirContextProcessor processor) { assureReturnObjFlagSet(controls); ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(mapper); search(base, filter, controls, handler, processor); return handler.getList(); } /** * {@inheritDoc} */ @Override public void list(final String base, NameClassPairCallbackHandler handler) { SearchExecutor searchExecutor = new SearchExecutor() { public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { return ctx.list(base); } }; search(searchExecutor, handler); } /** * {@inheritDoc} */ @Override public void list(final Name base, NameClassPairCallbackHandler handler) { SearchExecutor searchExecutor = new SearchExecutor() { public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { return ctx.list(base); } }; search(searchExecutor, handler); } /** * {@inheritDoc} */ @Override public List list(String base, NameClassPairMapper mapper) { CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler( mapper); list(base, handler); return handler.getList(); } /** * {@inheritDoc} */ @Override public List list(Name base, NameClassPairMapper mapper) { CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler( mapper); list(base, handler); return handler.getList(); } /** * {@inheritDoc} */ @Override public List list(final Name base) { return list(base, new DefaultNameClassPairMapper()); } /** * {@inheritDoc} */ @Override public List list(final String base) { return list(base, new DefaultNameClassPairMapper()); } /** * {@inheritDoc} */ @Override public void listBindings(final String base, NameClassPairCallbackHandler handler) { SearchExecutor searchExecutor = new SearchExecutor() { public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { return ctx.listBindings(base); } }; search(searchExecutor, handler); } /** * {@inheritDoc} */ @Override public void listBindings(final Name base, NameClassPairCallbackHandler handler) { SearchExecutor searchExecutor = new SearchExecutor() { public NamingEnumeration executeSearch(DirContext ctx) throws javax.naming.NamingException { return ctx.listBindings(base); } }; search(searchExecutor, handler); } /** * {@inheritDoc} */ @Override public List listBindings(String base, NameClassPairMapper mapper) { CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler( mapper); listBindings(base, handler); return handler.getList(); } /** * {@inheritDoc} */ @Override public List listBindings(Name base, NameClassPairMapper mapper) { CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler( mapper); listBindings(base, handler); return handler.getList(); } /** * {@inheritDoc} */ @Override public List listBindings(final String base) { return listBindings(base, new DefaultNameClassPairMapper()); } /** * {@inheritDoc} */ @Override public List listBindings(final Name base) { return listBindings(base, new DefaultNameClassPairMapper()); } /** * {@inheritDoc} */ @Override public List listBindings(String base, ContextMapper mapper) { ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(mapper); listBindings(base, handler); return handler.getList(); } /** * {@inheritDoc} */ @Override public List listBindings(Name base, ContextMapper mapper) { ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(mapper); listBindings(base, handler); return handler.getList(); } /** * {@inheritDoc} */ @Override public T executeReadOnly(ContextExecutor ce) { DirContext ctx = this.contextSource.getReadOnlyContext(); return executeWithContext(ce, ctx); } /** * {@inheritDoc} */ @Override public T executeReadWrite(ContextExecutor ce) { DirContext ctx = this.contextSource.getReadWriteContext(); return executeWithContext(ce, ctx); } private T executeWithContext(ContextExecutor ce, DirContext ctx) { try { return ce.executeWithContext(ctx); } catch (javax.naming.NamingException ex) { throw LdapUtils.convertLdapException(ex); } finally { closeContext(ctx); } } /** * {@inheritDoc} */ @Override public Object lookup(final Name dn) { return executeReadOnly(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { return ctx.lookup(dn); } }); } /** * {@inheritDoc} */ @Override public Object lookup(final String dn) { return executeReadOnly(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { return ctx.lookup(dn); } }); } /** * {@inheritDoc} */ @Override public T lookup(final Name dn, final AttributesMapper mapper) { return executeReadOnly(new ContextExecutor() { public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { Attributes attributes = ctx.getAttributes(dn); return mapper.mapFromAttributes(attributes); } }); } /** * {@inheritDoc} */ @Override public T lookup(final String dn, final AttributesMapper mapper) { return executeReadOnly(new ContextExecutor() { public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { Attributes attributes = ctx.getAttributes(dn); return mapper.mapFromAttributes(attributes); } }); } /** * {@inheritDoc} */ @Override public T lookup(final Name dn, final ContextMapper mapper) { return executeReadOnly(new ContextExecutor() { public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { Object object = ctx.lookup(dn); return mapper.mapFromContext(object); } }); } /** * {@inheritDoc} */ @Override public T lookup(final String dn, final ContextMapper mapper) { return executeReadOnly(new ContextExecutor() { public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { Object object = ctx.lookup(dn); return mapper.mapFromContext(object); } }); } /** * {@inheritDoc} */ @Override public T lookup(final Name dn, final String[] attributes, final AttributesMapper mapper) { return executeReadOnly(new ContextExecutor() { public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { Attributes filteredAttributes = ctx.getAttributes(dn, attributes); return mapper.mapFromAttributes(filteredAttributes); } }); } /** * {@inheritDoc} */ @Override public T lookup(final String dn, final String[] attributes, final AttributesMapper mapper) { return executeReadOnly(new ContextExecutor() { public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { Attributes filteredAttributes = ctx.getAttributes(dn, attributes); return mapper.mapFromAttributes(filteredAttributes); } }); } /** * {@inheritDoc} */ @Override public T lookup(final Name dn, final String[] attributes, final ContextMapper mapper) { return executeReadOnly(new ContextExecutor() { public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { Attributes filteredAttributes = ctx.getAttributes(dn, attributes); DirContextAdapter contextAdapter = new DirContextAdapter(filteredAttributes, dn); return mapper.mapFromContext(contextAdapter); } }); } /** * {@inheritDoc} */ @Override public T lookup(final String dn, final String[] attributes, final ContextMapper mapper) { return executeReadOnly(new ContextExecutor() { public T executeWithContext(DirContext ctx) throws javax.naming.NamingException { Attributes filteredAttributes = ctx.getAttributes(dn, attributes); LdapName name = LdapUtils.newLdapName(dn); DirContextAdapter contextAdapter = new DirContextAdapter(filteredAttributes, name); return mapper.mapFromContext(contextAdapter); } }); } /** * {@inheritDoc} */ @Override public void modifyAttributes(final Name dn, final ModificationItem[] mods) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.modifyAttributes(dn, mods); return null; } }); } /** * {@inheritDoc} */ @Override public void modifyAttributes(final String dn, final ModificationItem[] mods) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.modifyAttributes(dn, mods); return null; } }); } /** * {@inheritDoc} */ @Override public void bind(final Name dn, final Object obj, final Attributes attributes) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.bind(dn, obj, attributes); return null; } }); } /** * {@inheritDoc} */ @Override public void bind(final String dn, final Object obj, final Attributes attributes) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.bind(dn, obj, attributes); return null; } }); } /** * {@inheritDoc} */ @Override public void unbind(final Name dn) { doUnbind(dn); } /** * {@inheritDoc} */ @Override public void unbind(final String dn) { doUnbind(dn); } /** * {@inheritDoc} */ @Override public void unbind(final Name dn, boolean recursive) { if (!recursive) { doUnbind(dn); } else { doUnbindRecursively(dn); } } /** * {@inheritDoc} */ @Override public void unbind(final String dn, boolean recursive) { if (!recursive) { doUnbind(dn); } else { doUnbindRecursively(dn); } } private void doUnbind(final Name dn) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.unbind(dn); return null; } }); } private void doUnbind(final String dn) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.unbind(dn); return null; } }); } private void doUnbindRecursively(final Name dn) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) { deleteRecursively(ctx, LdapUtils.newLdapName(dn)); return null; } }); } private void doUnbindRecursively(final String dn) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { deleteRecursively(ctx, LdapUtils.newLdapName(dn)); return null; } }); } /** * Delete all subcontexts including the current one recursively. * @param ctx The context to use for deleting. * @param name The starting point to delete recursively. * @throws NamingException if any error occurs */ protected void deleteRecursively(DirContext ctx, Name name) { NamingEnumeration enumeration = null; try { enumeration = ctx.listBindings(name); while (enumeration.hasMore()) { Binding binding = (Binding) enumeration.next(); LdapName childName = LdapUtils.newLdapName(binding.getName()); childName.addAll(0, name); deleteRecursively(ctx, childName); } ctx.unbind(name); if (LOG.isDebugEnabled()) { LOG.debug("Entry " + name + " deleted"); } } catch (javax.naming.NamingException ex) { throw LdapUtils.convertLdapException(ex); } finally { try { enumeration.close(); } catch (Exception ex) { // Never mind this } } } /** * {@inheritDoc} */ @Override public void rebind(final Name dn, final Object obj, final Attributes attributes) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.rebind(dn, obj, attributes); return null; } }); } /** * {@inheritDoc} */ @Override public void rebind(final String dn, final Object obj, final Attributes attributes) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.rebind(dn, obj, attributes); return null; } }); } /** * {@inheritDoc} */ @Override public void rename(final Name oldDn, final Name newDn) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.rename(oldDn, newDn); return null; } }); } /** * {@inheritDoc} */ @Override public void rename(final String oldDn, final String newDn) { executeReadWrite(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { ctx.rename(oldDn, newDn); return null; } }); } /** * {@inheritDoc} */ @Override public void afterPropertiesSet() throws Exception { Assert.notNull(this.contextSource, "Property 'contextSource' must be set."); } private void closeContextAndNamingEnumeration(DirContext ctx, NamingEnumeration results) { closeNamingEnumeration(results); closeContext(ctx); } /** * Close the supplied DirContext if it is not null. Swallow any exceptions, as this is * only for cleanup. * @param ctx the context to close. */ private void closeContext(DirContext ctx) { if (ctx != null) { try { ctx.close(); } catch (Exception ex) { // Never mind this. } } } /** * Close the supplied NamingEnumeration if it is not null. Swallow any exceptions, as * this is only for cleanup. * @param results the NamingEnumeration to close. */ private void closeNamingEnumeration(NamingEnumeration results) { if (results != null) { try { results.close(); } catch (Exception ex) { // Never mind this. } } } private SearchControls getDefaultSearchControls(int searchScope, boolean returningObjFlag, String[] attrs) { SearchControls controls = new SearchControls(); controls.setSearchScope(searchScope); controls.setTimeLimit(this.defaultTimeLimit); controls.setCountLimit(this.defaultCountLimit); controls.setReturningObjFlag(returningObjFlag); controls.setReturningAttributes(attrs); return controls; } /** * Make sure the returnObjFlag is set in the supplied SearchControls. Set it and log * if it's not set. * @param controls the SearchControls to check. */ private void assureReturnObjFlagSet(SearchControls controls) { Assert.notNull(controls, "controls must not be null"); if (!controls.getReturningObjFlag()) { LOG.debug("The returnObjFlag of supplied SearchControls is not set" + " but a ContextMapper is used - setting flag to true"); controls.setReturningObjFlag(true); } } /** * {@inheritDoc} */ @Override public DirContextOperations lookupContext(Name dn) { return (DirContextOperations) lookup(dn); } /** * {@inheritDoc} */ @Override public DirContextOperations lookupContext(String dn) { return (DirContextOperations) lookup(dn); } /** * {@inheritDoc} */ @Override public void modifyAttributes(DirContextOperations ctx) { Name dn = ctx.getDn(); if (dn != null && ctx.isUpdateMode()) { modifyAttributes(dn, ctx.getModificationItems()); } else { throw new IllegalStateException("The DirContextOperations instance needs to be properly initialized."); } } /** * {@inheritDoc} */ @Override public void bind(DirContextOperations ctx) { Name dn = ctx.getDn(); if (dn != null && !ctx.isUpdateMode()) { bind(dn, ctx, null); } else { throw new IllegalStateException("The DirContextOperations instance needs to be properly initialized."); } } /** * {@inheritDoc} */ @Override public void rebind(DirContextOperations ctx) { Name dn = ctx.getDn(); if (dn != null && !ctx.isUpdateMode()) { rebind(dn, ctx, null); } else { throw new IllegalStateException("The DirContextOperations instance needs to be properly initialized."); } } /** * {@inheritDoc} */ @Override public boolean authenticate(Name base, String filter, String password) { return authenticate(base, filter, password, new NullAuthenticatedLdapEntryContextCallback(), new NullAuthenticationErrorCallback()); } /** * {@inheritDoc} */ @Override public boolean authenticate(String base, String filter, String password) { return authenticate(LdapUtils.newLdapName(base), filter, password, new NullAuthenticatedLdapEntryContextCallback(), new NullAuthenticationErrorCallback()); } /** * {@inheritDoc} */ @Override public boolean authenticate(String base, String filter, String password, AuthenticatedLdapEntryContextCallback callback) { return authenticate(LdapUtils.newLdapName(base), filter, password, callback, new NullAuthenticationErrorCallback()); } /** * {@inheritDoc} */ @Override public boolean authenticate(Name base, String filter, String password, final AuthenticatedLdapEntryContextCallback callback) { return authenticate(base, filter, password, callback, new NullAuthenticationErrorCallback()); } /** * {@inheritDoc} */ @Override public boolean authenticate(String base, String filter, String password, AuthenticationErrorCallback errorCallback) { return authenticate(LdapUtils.newLdapName(base), filter, password, new NullAuthenticatedLdapEntryContextCallback(), errorCallback); } /** * {@inheritDoc} */ @Override public boolean authenticate(Name base, String filter, String password, final AuthenticationErrorCallback errorCallback) { return authenticate(base, filter, password, new NullAuthenticatedLdapEntryContextCallback(), errorCallback); } /** * {@inheritDoc} */ @Override public boolean authenticate(String base, String filter, String password, final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) { return authenticate(LdapUtils.newLdapName(base), filter, password, callback, errorCallback); } /** * {@inheritDoc} */ @Override public boolean authenticate(Name base, String filter, String password, final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) { return authenticate(base, filter, password, getDefaultSearchControls(this.defaultSearchScope, RETURN_OBJ_FLAG, null), callback, errorCallback) .isSuccess(); } private AuthenticationStatus authenticate(Name base, String filter, String password, SearchControls searchControls, final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) { List result = search(base, filter, searchControls, new LdapEntryIdentificationContextMapper()); if (result.size() == 0) { String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'."; LOG.info(msg); return AuthenticationStatus.EMPTYRESULT; } else if (result.size() > 1) { String msg = "base: '" + base + "'; filter: '" + filter + "'."; throw new IncorrectResultSizeDataAccessException(msg, 1, result.size()); } final LdapEntryIdentification entryIdentification = result.get(0); try { DirContext ctx = this.contextSource.getContext(entryIdentification.getAbsoluteName().toString(), password); executeWithContext(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { callback.executeWithContext(ctx, entryIdentification); return null; } }, ctx); return AuthenticationStatus.SUCCESS; } catch (Exception ex) { LOG.debug("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteName() + "'", ex); errorCallback.execute(ex); return AuthenticationStatus.UNDEFINED_FAILURE; } } /** * {@inheritDoc} */ @Override public T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper mapper) { SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); ReturningAuthenticatedLdapEntryContext mapperCallback = new ReturningAuthenticatedLdapEntryContext( mapper); CollectingAuthenticationErrorCallback errorCallback = new CollectingAuthenticationErrorCallback(); AuthenticationStatus authenticationStatus = authenticate(query.base(), query.filter().encode(), password, searchControls, mapperCallback, errorCallback); if (errorCallback.hasError()) { Exception error = errorCallback.getError(); if (error instanceof NamingException) { throw (NamingException) error; } else { throw new UncategorizedLdapException(error); } } else if (AuthenticationStatus.EMPTYRESULT == authenticationStatus) { throw new EmptyResultDataAccessException(1); } else if (!authenticationStatus.isSuccess()) { throw new AuthenticationException(); } return mapperCallback.collectedObject; } /** * {@inheritDoc} */ @Override public void authenticate(LdapQuery query, String password) { authenticate(query, password, new NullAuthenticatedLdapEntryContextCallback()); } /** * {@inheritDoc} */ @Override public T searchForObject(Name base, String filter, ContextMapper mapper) { return searchForObject(base, filter, getDefaultSearchControls(this.defaultSearchScope, RETURN_OBJ_FLAG, ALL_ATTRIBUTES), mapper); } /** * {@inheritDoc} */ @Override public T searchForObject(String base, String filter, ContextMapper mapper) { return searchForObject(LdapUtils.newLdapName(base), filter, mapper); } /** * {@inheritDoc} */ @Override public T searchForObject(Name base, String filter, SearchControls searchControls, ContextMapper mapper) { List result = search(base, filter, searchControls, mapper); if (result.size() == 0) { throw new EmptyResultDataAccessException(1); } else if (result.size() != 1) { throw new IncorrectResultSizeDataAccessException(1, result.size()); } return result.get(0); } /** * {@inheritDoc} */ @Override public T searchForObject(String base, String filter, SearchControls searchControls, ContextMapper mapper) { return searchForObject(LdapUtils.newLdapName(base), filter, searchControls, mapper); } /** * {@inheritDoc} */ @Override public void search(LdapQuery query, NameClassPairCallbackHandler callbackHandler) { SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG); search(query.base(), query.filter().encode(), searchControls, callbackHandler); } /** * {@inheritDoc} */ @Override public List search(LdapQuery query, ContextMapper mapper) { SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); return search(query.base(), query.filter().encode(), searchControls, mapper); } private SearchControls searchControlsForQuery(LdapQuery query, boolean returnObjFlag) { SearchControls searchControls = getDefaultSearchControls(this.defaultSearchScope, returnObjFlag, query.attributes()); if (query.searchScope() != null) { searchControls.setSearchScope(query.searchScope().getId()); } if (query.countLimit() != null) { searchControls.setCountLimit(query.countLimit()); } if (query.timeLimit() != null) { searchControls.setTimeLimit(query.timeLimit()); } return searchControls; } /** * {@inheritDoc} */ @Override public List search(LdapQuery query, AttributesMapper mapper) { SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG); return search(query.base(), query.filter().encode(), searchControls, mapper); } /** * {@inheritDoc} */ @Override public DirContextOperations searchForContext(LdapQuery query) { return searchForObject(query, new ContextMapper() { @Override public DirContextOperations mapFromContext(Object ctx) throws javax.naming.NamingException { return (DirContextOperations) ctx; } }); } /** * {@inheritDoc} */ @Override public T searchForObject(LdapQuery query, ContextMapper mapper) { SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG); return searchForObject(query.base(), query.filter().encode(), searchControls, mapper); } /** * {@inheritDoc} */ @Override public Stream searchForStream(LdapQuery query, AttributesMapper attributesMapper) { return searchForStream(query, (SearchResult result) -> { Attributes attributes = result.getAttributes(); return unchecked(() -> attributesMapper.mapFromAttributes(attributes)); }); } /** * {@inheritDoc} */ @Override public Stream searchForStream(LdapQuery query, ContextMapper mapper) { return searchForStream(query, (SearchResult result) -> { Object object = result.getObject(); if (object == null) { throw new ObjectRetrievalException("Binding did not contain any object."); } return unchecked(() -> mapper.mapFromContext(object)); }); } Stream searchForStream(LdapQuery query, Function mapper) { Name base = query.base(); Filter filter = query.filter(); SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); DirContext ctx = this.contextSource.getReadOnlyContext(); String encodedFilter = filter.encode(); if (LOG.isDebugEnabled()) { LOG.debug( String.format("Searching - base=%1$s, finalFilter=%2$s, scope=%3$s", base, filter, searchControls)); } assureReturnObjFlagSet(searchControls); NamingEnumeration results = unchecked(() -> ctx.search(base, encodedFilter, searchControls)); if (results == null) { return Stream.empty(); } return StreamSupport .stream(Spliterators.spliteratorUnknownSize(CollectionUtils.toIterator(results), Spliterator.ORDERED), false) .map((nameClassPair) -> unchecked(() -> mapper.apply(nameClassPair))).filter(Objects::nonNull) .onClose(() -> closeContextAndNamingEnumeration(ctx, results)); } /** * {@inheritDoc} */ @Override public T findByDn(Name dn, final Class clazz) { if (LOG.isDebugEnabled()) { LOG.debug(String.format("Reading Entry at - %1$s", dn)); } // Make sure the class is OK before doing the lookup String[] attributes = this.odm.manageClass(clazz); T result = lookup(dn, attributes, new ContextMapper() { @Override public T mapFromContext(Object ctx) throws javax.naming.NamingException { return LdapTemplate.this.odm.mapFromLdapDataEntry((DirContextOperations) ctx, clazz); } }); if (result == null) { throw new OdmException(String.format("Entry %1$s does not have the required objectclasses ", dn)); } if (LOG.isDebugEnabled()) { LOG.debug(String.format("Found entry - %1$s", result)); } return result; } /** * {@inheritDoc} */ @Override public void create(Object entry) { Assert.notNull(entry, "Entry must not be null"); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Creating entry - %1$s", entry)); } Name id = this.odm.getId(entry); if (id == null) { id = this.odm.getCalculatedId(entry); this.odm.setId(entry, id); } Assert.notNull(id, String.format("Unable to determine id for entry %s", entry.toString())); DirContextAdapter context = new DirContextAdapter(id); this.odm.mapToLdapDataEntry(entry, context); bind(context); } /** * {@inheritDoc} */ @Override public void update(Object entry) { Assert.notNull(entry, "Entry must not be null"); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Updating entry - %1$s", entry)); } Name originalId = this.odm.getId(entry); Name calculatedId = this.odm.getCalculatedId(entry); if (originalId != null && calculatedId != null && !originalId.equals(calculatedId)) { // The DN has changed - remove the original entry and bind the new one // (because other data may have changed as well if (LOG.isDebugEnabled()) { LOG.debug(String.format( "Calculated DN of %s; of entry %s differs from explicitly specified one; %s - moving", calculatedId, entry, originalId)); } unbind(originalId); DirContextAdapter context = new DirContextAdapter(calculatedId); this.odm.mapToLdapDataEntry(entry, context); bind(context); this.odm.setId(entry, calculatedId); } else { // DN is the same, just modify the attributes Name id = originalId; if (id == null) { id = calculatedId; this.odm.setId(entry, calculatedId); } Assert.notNull(id, String.format("Unable to determine id for entry %s", entry.toString())); DirContextOperations context = lookupContext(id); this.odm.mapToLdapDataEntry(entry, context); modifyAttributes(context); } } /** * {@inheritDoc} */ @Override public void delete(Object entry) { Assert.notNull(entry, "Entry must not be null"); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Deleting %1$s", entry)); } Name id = this.odm.getId(entry); if (id == null) { id = this.odm.getCalculatedId(entry); } Assert.notNull(id, String.format("Unable to determine id for entry %s", entry.toString())); unbind(id); } /** * {@inheritDoc} */ @Override public List findAll(Name base, SearchControls searchControls, final Class clazz) { return find(base, null, searchControls, clazz); } /** * {@inheritDoc} */ @Override public List findAll(Class clazz) { return findAll(LdapUtils.emptyLdapName(), getDefaultSearchControls(this.defaultSearchScope, RETURN_OBJ_FLAG, ALL_ATTRIBUTES), clazz); } /** * {@inheritDoc} */ @Override public List find(Name base, Filter filter, SearchControls searchControls, final Class clazz) { Filter finalFilter = this.odm.filterFor(clazz, filter); // Search from the root if we are not told where to search from Name localBase = base; if (base == null || base.size() == 0) { localBase = LdapUtils.emptyLdapName(); } // extend search controls with the attributes to return if (searchControls.getReturningAttributes() == null) { String[] attributes = this.odm.manageClass(clazz); searchControls.setReturningAttributes(attributes); } if (LOG.isDebugEnabled()) { LOG.debug(String.format("Searching - base=%1$s, finalFilter=%2$s, scope=%3$s", base, finalFilter, searchControls)); } List result = search(localBase, finalFilter.encode(), searchControls, new ContextMapper() { @Override public T mapFromContext(Object ctx) throws javax.naming.NamingException { return LdapTemplate.this.odm.mapFromLdapDataEntry((DirContextOperations) ctx, clazz); } }); result.remove(null); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Found %1$s Entries - %2$s", result.size(), result)); } return result; } /** * {@inheritDoc} */ @Override public List find(LdapQuery query, Class clazz) { SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); return find(query.base(), query.filter(), searchControls, clazz); } /** * {@inheritDoc} */ @Override public T findOne(LdapQuery query, Class clazz) { List result = find(query, clazz); if (result.size() == 0) { throw new EmptyResultDataAccessException(1); } else if (result.size() != 1) { throw new IncorrectResultSizeDataAccessException(1, result.size()); } return result.get(0); } /** * {@inheritDoc} */ @Override public Stream findForStream(LdapQuery query, Class clazz) { LdapQueryBuilder builder = LdapQueryBuilder.fromQuery(query); if (query.attributes() == null) { String[] attributes = this.odm.manageClass(clazz); builder.attributes(attributes); } Filter includeClass = this.odm.filterFor(clazz, query.filter()); ContextMapper contextMapper = (object) -> this.odm.mapFromLdapDataEntry((DirContextOperations) object, clazz); return searchForStream(builder.filter(includeClass), contextMapper); } private T unchecked(CheckedSupplier supplier) { try { return supplier.get(); } catch (NameNotFoundException ex) { // It is possible to ignore errors caused by base not found if (!this.ignoreNameNotFoundException) { throw LdapUtils.convertLdapException(ex); } LOG.warn("Base context not found, ignoring: " + ex.getMessage()); } catch (PartialResultException ex) { // Workaround for AD servers not handling referrals correctly. if (!this.ignorePartialResultException) { throw LdapUtils.convertLdapException(ex); } LOG.debug("PartialResultException encountered and ignored", ex); } catch (SizeLimitExceededException ex) { if (!this.ignoreSizeLimitExceededException) { throw LdapUtils.convertLdapException(ex); } LOG.debug("SizeLimitExceededException encountered and ignored", ex); } catch (javax.naming.NamingException ex) { throw LdapUtils.convertLdapException(ex); } return null; } private interface CheckedSupplier { T get() throws javax.naming.NamingException; } /** * The status of an authentication attempt. * * @author Rob Winch */ private enum AuthenticationStatus { /** * Authentication was successful */ SUCCESS(true), /** * The user was not found */ EMPTYRESULT(false), /** * Authentication failed for other reason */ UNDEFINED_FAILURE(false); private boolean success; AuthenticationStatus(boolean success) { this.success = success; } /** * Return true if the authentication attempt was successful * @return */ public boolean isSuccess() { return this.success; } } private static final class NullAuthenticatedLdapEntryContextCallback implements AuthenticatedLdapEntryContextCallback, AuthenticatedLdapEntryContextMapper { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { // Do nothing } @Override public Object mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { return null; } } /** * Do-nothing implementation of {@link DirContextProcessor}. * * @author Mattias Hellborg Arthursson * @since 2.0 */ public static final class NullDirContextProcessor implements DirContextProcessor { @Override public void postProcess(DirContext ctx) { // Do nothing } @Override public void preProcess(DirContext ctx) { // Do nothing } } /** * A {@link NameClassPairCallbackHandler} that passes the NameClassPairs found to a * NameClassPairMapper and collects the results in a list. * * @author Mattias Hellborg Arthursson */ public static final class MappingCollectingNameClassPairCallbackHandler extends CollectingNameClassPairCallbackHandler { private NameClassPairMapper mapper; public MappingCollectingNameClassPairCallbackHandler(NameClassPairMapper mapper) { this.mapper = mapper; } /** * {@inheritDoc} */ @Override public T getObjectFromNameClassPair(NameClassPair nameClassPair) { try { return this.mapper.mapFromNameClassPair(nameClassPair); } catch (javax.naming.NamingException ex) { throw LdapUtils.convertLdapException(ex); } } } private static final class NullAuthenticationErrorCallback implements AuthenticationErrorCallback { @Override public void execute(Exception ex) { // Do nothing } } private static final class ReturningAuthenticatedLdapEntryContext implements AuthenticatedLdapEntryContextCallback { private final AuthenticatedLdapEntryContextMapper mapper; private T collectedObject; private ReturningAuthenticatedLdapEntryContext(AuthenticatedLdapEntryContextMapper mapper) { this.mapper = mapper; } /** * {@inheritDoc} */ @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { this.collectedObject = this.mapper.mapWithContext(ctx, ldapEntryIdentification); } } }