Add Stream Support

Closes gh-586
This commit is contained in:
Josh Cummings
2022-07-21 16:04:30 -06:00
parent a51c9e11c6
commit 092b22c200
9 changed files with 471 additions and 10 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2005-2010 the original author or authors.
* 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.
@@ -32,6 +32,7 @@ import javax.naming.directory.Attributes;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import java.util.List;
import java.util.stream.Stream;
/**
* Interface that specifies a basic set of LDAP operations. Implemented by
@@ -1693,6 +1694,37 @@ public interface LdapOperations {
*/
<T> T searchForObject(LdapQuery query, ContextMapper<T> mapper);
/**
* Perform a search with parameters from the specified LdapQuery. The Attributes of the found entries will be
* supplied to the <code>AttributesMapper</code> for processing, and all
* returned objects will be collected in a list to be returned.
*
* @param query the LDAP query specification.
* @param mapper the <code>Attributes</code> to supply all found Attributes to.
* @return a <code>Stream</code> of all entries received from the
* <code>Attributes</code>.
*
* @throws NamingException if any error occurs.
* @since 3.0
* @see org.springframework.ldap.query.LdapQueryBuilder
*/
<T> Stream<T> searchForStream(LdapQuery query, AttributesMapper<T> mapper);
/**
* Perform a search with parameters from the specified LdapQuery. All found objects will be supplied to the
* <code>ContextMapper</code> for processing, and all returned objects will be collected in a list to be returned.
*
* @param query the LDAP query specification.
* @param mapper the <code>ContextMapper</code> to supply all found entries to.
* @return a <code>Stream</code> of all entries received from the
* <code>ContextMapper</code>.
*
* @throws NamingException if any error occurs.
* @since 3.0
* @see org.springframework.ldap.query.LdapQueryBuilder
*/
<T> Stream<T> searchForStream(LdapQuery query, ContextMapper<T> mapper);
/**
* Read a named entry from the LDAP directory. The referenced class must have object-directory mapping metadata
* specified using {@link org.springframework.ldap.odm.annotations.Entry} and associated annotations.
@@ -1844,6 +1876,24 @@ public interface LdapOperations {
*/
<T> T findOne(LdapQuery query, Class<T> clazz);
/**
* Search for entries in the LDAP directory. The referenced class must have object-directory
* mapping metadata specified using {@link org.springframework.ldap.odm.annotations.Entry} and associated annotations.
* <p>
* Only those entries that both match the query search filter and
* are represented by the given Java class are returned.
*
* @param <T> The Java type to return
* @param query the LDAP query specification
* @param clazz The Java type to return
* @return All matching entries.
*
* @throws org.springframework.ldap.NamingException on error.
* @see org.springframework.ldap.query.LdapQueryBuilder
* @since 3.0
*/
<T> Stream<T> findForStream(LdapQuery query, Class<T> clazz);
/**
* Get the configured ObjectDirectoryMapper. For internal use.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2005-2013 the original author or authors.
* 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.
@@ -28,10 +28,10 @@ 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;
import org.springframework.util.ObjectUtils;
import javax.naming.Binding;
import javax.naming.Name;
@@ -44,10 +44,16 @@ 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 java.util.Arrays;
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;
/**
* Executes core LDAP functionality and helps to avoid common errors, relieving
@@ -1684,6 +1690,53 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
mapper);
}
/**
* {@inheritDoc}
*/
@Override
public <T> Stream<T> searchForStream(LdapQuery query, AttributesMapper<T> attributesMapper) {
return searchForStream(query, (SearchResult result) -> {
Attributes attributes = result.getAttributes();
return unchecked(() -> attributesMapper.mapFromAttributes(attributes));
});
}
/**
* {@inheritDoc}
*/
@Override
public <T> Stream<T> searchForStream(LdapQuery query, ContextMapper<T> 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));
});
}
<T> Stream<T> searchForStream(LdapQuery query, Function<SearchResult, T> mapper) {
Name base = query.base();
Filter filter = query.filter();
SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG);
DirContext ctx = 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<SearchResult> 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}
*/
@@ -1884,6 +1937,51 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
return result.get(0);
}
/**
* {@inheritDoc}
*/
@Override
public <T> Stream<T> findForStream(LdapQuery query, Class<T> clazz) {
LdapQueryBuilder builder = LdapQueryBuilder.fromQuery(query);
if (query.attributes() == null) {
String[] attributes = odm.manageClass(clazz);
builder.attributes(attributes);
}
Filter includeClass = odm.filterFor(clazz, query.filter());
ContextMapper<T> contextMapper = (object) -> odm.mapFromLdapDataEntry((DirContextOperations) object, clazz);
return searchForStream(builder.filter(includeClass), contextMapper);
}
private <T> T unchecked(CheckedSupplier<T> supplier) {
try {
return supplier.get();
} catch (NameNotFoundException e) {
// It is possible to ignore errors caused by base not found
if (!ignoreNameNotFoundException) {
throw LdapUtils.convertLdapException(e);
}
LOG.warn("Base context not found, ignoring: " + e.getMessage());
} catch (PartialResultException e) {
// Workaround for AD servers not handling referrals correctly.
if (!ignorePartialResultException) {
throw LdapUtils.convertLdapException(e);
}
LOG.debug("PartialResultException encountered and ignored", e);
} catch(SizeLimitExceededException e) {
if(!ignoreSizeLimitExceededException) {
throw LdapUtils.convertLdapException(e);
}
LOG.debug("SizeLimitExceededException encountered and ignored", e);
} catch (javax.naming.NamingException e) {
throw LdapUtils.convertLdapException(e);
}
return null;
}
private interface CheckedSupplier<T> {
T get() throws javax.naming.NamingException;
}
/**
* The status of an authentication attempt.
*

View File

@@ -78,6 +78,26 @@ public final class LdapQueryBuilder implements LdapQuery {
return new LdapQueryBuilder();
}
/**
* Construct a new LdapQueryBuilder based on an existing {@link LdapQuery}
* All non-filter fields are copied.
* @return a new instance.
* @since 3.0
*/
public static LdapQueryBuilder fromQuery(LdapQuery query) {
LdapQueryBuilder builder = LdapQueryBuilder.query()
.attributes(query.attributes())
.base(query.base());
if (query.countLimit() != null) {
builder.countLimit(query.countLimit());
}
builder.searchScope(query.searchScope());
if (query.timeLimit() != null) {
builder.timeLimit(query.timeLimit());
}
return builder;
}
/**
* Set the base search path for the query.
* Default is {@link org.springframework.ldap.support.LdapUtils#emptyLdapName()}.