LDAP-263: Fluent API for building LDAP queries.

This commit is contained in:
Mattias Hellborg Arthursson
2013-09-23 15:53:26 +02:00
parent 96429d00c6
commit b141cfd4cb
13 changed files with 1229 additions and 14 deletions

View File

@@ -20,6 +20,7 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.ContextNotEmptyException;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.support.AbstractContextSource;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.Binding;
@@ -1503,7 +1504,39 @@ public interface LdapOperations {
*/
<T> T searchForObject(Name base, String filter, ContextMapper<T> mapper);
/**
/**
* Perform a search for a unique entry matching the specified search
* criteria and return the found object. If no entry is found or if there
* are more than one matching entry, an
* {@link IncorrectResultSizeDataAccessException} is thrown.
* @param base the DN to use as the base of the search.
* @param filter the search filter.
* @param searchControls the searchControls to use for the search.
* @param mapper the mapper to use for the search.
* @return the single object returned by the mapper that matches the search
* criteria.
* @throws IncorrectResultSizeDataAccessException if the result is not one unique entry
* @since 2.0
*/
<T> T searchForObject(Name base, String filter, SearchControls searchControls, ContextMapper<T> mapper);
/**
* Perform a search for a unique entry matching the specified search
* criteria and return the found object. If no entry is found or if there
* are more than one matching entry, an
* {@link IncorrectResultSizeDataAccessException} is thrown.
* @param base the DN to use as the base of the search.
* @param filter the search filter.
* @param searchControls the searchControls to use for the search.
* @param mapper the mapper to use for the search.
* @return the single object returned by the mapper that matches the search
* criteria.
* @throws IncorrectResultSizeDataAccessException if the result is not one unique entry
* @since 2.0
*/
<T> T searchForObject(String base, String filter, SearchControls searchControls, ContextMapper<T> mapper);
/**
* Perform a search for a unique entry matching the specified search
* criteria and return the found object. If no entry is found or if there
* are more than one matching entry, an
@@ -1517,4 +1550,57 @@ public interface LdapOperations {
* @since 1.3
*/
<T> T searchForObject(String base, String filter, ContextMapper<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>List</code> containing all entries received from the
* <code>ContextMapper</code>.
*
* @throws NamingException if any error occurs.
* @since 2.0
*/
<T> List<T> search(LdapQuery query, ContextMapper<T> mapper);
/**
* Perform a search with parameters from the specified LdapQuery. The Attributes of the found entrieswill 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>List</code> containing all entries received from the
* <code>Attributes</code>.
*
* @throws NamingException if any error occurs.
* @since 2.0
*/
<T> List<T> search(LdapQuery query, AttributesMapper<T> mapper);
/**
* Perform a search for a unique entry matching the specified LDAP
* query and return the found entry as a DirContextOperation instance. If no entry is found or if there
* are more than one matching entry, an
* {@link IncorrectResultSizeDataAccessException} is thrown.
* @param query the DN to use as the base of the search.
* @return the single entry matching the query as a DirContextOperations instance.
* @throws IncorrectResultSizeDataAccessException if the result is not one unique entry
* @since 2.0
*/
DirContextOperations searchForContext(LdapQuery query);
/**
* Perform a search for a unique entry matching the specified LDAP
* query and return the found object. If no entry is found or if there
* are more than one matching entry, an
* {@link IncorrectResultSizeDataAccessException} is thrown.
* @param query the LDAP query specification.
* @return the single object returned by the mapper that matches the search
* criteria.
* @throws IncorrectResultSizeDataAccessException if the result is not one unique entry
* @since 2.0
*/
<T> T searchForObject(LdapQuery query, ContextMapper<T> mapper);
}

View File

@@ -21,6 +21,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.Assert;
@@ -1527,15 +1528,10 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
* .Name, java.lang.String, org.springframework.ldap.core.ContextMapper)
*/
public <T> T searchForObject(Name base, String filter, ContextMapper<T> mapper) {
List<T> result = search(base, filter, mapper);
if (result.size() == 0) {
throw new EmptyResultDataAccessException(1);
}
else if (result.size() != 1) {
throw new IncorrectResultSizeDataAccessException(1, result.size());
}
return result.get(0);
return searchForObject(base,
filter,
getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, ALL_ATTRIBUTES),
mapper);
}
/*
@@ -1549,7 +1545,25 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
return searchForObject(LdapUtils.newLdapName(base), filter, mapper);
}
private static final class NullAuthenticatedLdapEntryContextCallback
public <T> T searchForObject (Name base, String filter, SearchControls searchControls, ContextMapper<T> mapper) {
List<T> 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);
}
@Override
public <T> T searchForObject(String base, String filter, SearchControls searchControls, ContextMapper<T> mapper) {
return searchForObject(LdapUtils.newLdapName(base), filter, searchControls, mapper);
}
private static final class NullAuthenticatedLdapEntryContextCallback
implements AuthenticatedLdapEntryContextCallback {
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
@@ -1563,4 +1577,65 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
// Do nothing
}
}
@Override
public <T> List<T> search(LdapQuery query, ContextMapper<T> 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(
defaultSearchScope,
returnObjFlag,
query.attributes());
if(query.searchScope() != null) {
searchControls.setSearchScope(query.searchScope().getId());
}
if(query.countLimit() != null) {
searchControls.setCountLimit(query.countLimit());
}
if(query.countLimit() != null) {
searchControls.setCountLimit(query.timeLimit());
}
return searchControls;
}
@Override
public <T> List<T> search(LdapQuery query, AttributesMapper<T> mapper) {
SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG);
return search(query.base(),
query.filter().encode(),
searchControls,
mapper);
}
@Override
public DirContextOperations searchForContext(LdapQuery query) {
return searchForObject(query, new ContextMapper<DirContextOperations>() {
@Override
public DirContextOperations mapFromContext(Object ctx) throws javax.naming.NamingException {
return (DirContextOperations) ctx;
}
});
}
@Override
public <T> T searchForObject(LdapQuery query, ContextMapper<T> mapper) {
SearchControls searchControls = searchControlsForQuery(query, DONT_RETURN_OBJ_FLAG);
return searchForObject(query.base(),
query.filter().encode(),
searchControls,
mapper);
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.ldap.filter;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -90,4 +91,9 @@ public abstract class BinaryLogicalFilter extends AbstractFilter {
queryList.add(query);
return this;
}
public final BinaryLogicalFilter appendAll(Collection<Filter> subQueries) {
queryList.addAll(subQueries);
return this;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2005-2013 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
*
* http://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.query;
/**
* Constructs a conditional LDAP filter based on the attribute specified in the previous builder step.
*
* @author Mattias Hellborg Arthursson
*
* @see LdapQueryBuilder#where(String)
* @see ContainerCriteria#and(String)
* @see ContainerCriteria#or(String)
* @since 2.0
*/
public interface ConditionCriteria {
/**
* Appends an {@link org.springframework.ldap.filter.EqualsFilter}.
*
* @param value the value to compare with.
* @return an ContainerCriteria instance that can be used to continue append more criteria
* or as the LdapQuery instance to be used as instance to e.g.
* {@link org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)}.
*
* @see org.springframework.ldap.filter.EqualsFilter
*/
ContainerCriteria is(String value);
/**
* Appends an {@link org.springframework.ldap.filter.GreaterThanOrEqualsFilter}.
*
* @param value the value to compare with.
* @return an ContainerCriteria instance that can be used to continue append more criteria
* or as the LdapQuery instance to be used as instance to e.g.
* {@link org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)}.
*
* @see org.springframework.ldap.filter.GreaterThanOrEqualsFilter
*/
ContainerCriteria gte(String value);
/**
* Appends a {@link org.springframework.ldap.filter.LessThanOrEqualsFilter}.
*
* @param value the value to compare with.
* @return an ContainerCriteria instance that can be used to continue append more criteria
* or as the LdapQuery instance to be used as instance to e.g.
* {@link org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)}.
*
* @see org.springframework.ldap.filter.LessThanOrEqualsFilter
*/
ContainerCriteria lte(String value);
/**
* Appends a {@link org.springframework.ldap.filter.LikeFilter}.
*
* @param value the value to compare with.
* @return an ContainerCriteria instance that can be used to continue append more criteria
* or as the LdapQuery instance to be used as instance to e.g.
* {@link org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)}.
*
* @see org.springframework.ldap.filter.LikeFilter
*/
ContainerCriteria like(String value);
/**
* Appends a {@link org.springframework.ldap.filter.WhitespaceWildcardsFilter}.
*
* @param value the value to compare with.
* @return an ContainerCriteria instance that can be used to continue append more criteria
* or as the LdapQuery instance to be used as instance to e.g.
* {@link org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)}.
*
* @see org.springframework.ldap.filter.WhitespaceWildcardsFilter
*/
ContainerCriteria whitespaceWildcardsLike(String value);
/**
* Appends a {@link org.springframework.ldap.filter.PresentFilter}.
*
* @return an ContainerCriteria instance that can be used to continue append more criteria
* or as the LdapQuery instance to be used as instance to e.g.
* {@link org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)}.
*
* @see org.springframework.ldap.filter.PresentFilter
*/
ContainerCriteria isPresent();
/**
* Negates the currently constructed operation. In effect this means that the resulting filter will be
* wrapped in a {@link org.springframework.ldap.filter.NotFilter}.
*
* @return an ContainerCriteria instance that can be used to continue append more criteria
* or as the LdapQuery instance to be used as instance to e.g.
* {@link org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)}.
*
* @see org.springframework.ldap.filter.NotFilter
*/
ConditionCriteria not();
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2005-2013 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
*
* http://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.query;
/**
* Builder for and/or filters.
*
* @author Mattias Hellborg Arthursson
* @since 2.0
*/
public interface ContainerCriteria extends LdapQuery {
/**
* Append a logical And condition to the currently built filter.
*
* @param attribute Name of the attribute to specify a condition for.
* @return A ConditionCriteria instance for specifying the compare operation.
* @throws IllegalStateException if {@link #or(String)} has previously been called on this instance.
*/
ConditionCriteria and(String attribute);
/**
* Append a logical Or condition to the currently built filter.
*
* @param attribute Name of the attribute to specify a condition for.
* @return A ConditionCriteria instance for specifying the compare operation.
* @throws IllegalStateException if {@link #and(String)} has previously been called on this instance.
*/
ConditionCriteria or(String attribute);
/**
* Append an And condition for a nested criterion. Use {@link org.springframework.ldap.query.LdapQueryBuilder#query()}
* to start the nested condition. Any base query information on the nested builder instance will not be considered.
*
* @param nested the nested criterion.
*
* @return A ConditionCriteria instance for specifying the compare operation.
*/
ContainerCriteria and(ContainerCriteria nested);
/**
* Append an Or condition for a nested criterion. Use {@link org.springframework.ldap.query.LdapQueryBuilder#query()}
* to start the nested condition. Any base query information on the nested builder instance will not be considered.
*
* @param nested the nested criterion.
*
* @return A ConditionCriteria instance for specifying the compare operation.
*/
ContainerCriteria or(ContainerCriteria nested);
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2005-2013 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
*
* http://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.query;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.BinaryLogicalFilter;
import org.springframework.ldap.filter.OrFilter;
/**
* @author Mattias Hellborg Arthursson
* @since 2.0
*/
enum CriteriaContainerType {
AND {
@Override
public BinaryLogicalFilter constructFilter() {
return new AndFilter();
}
}, OR {
@Override
public BinaryLogicalFilter constructFilter() {
return new OrFilter();
}
};
public void validateSameType(CriteriaContainerType oldType) {
if (oldType != null && oldType != this) {
throw new IllegalStateException(
String.format("Container type has already been specified as %s, cannot change it to %s",
oldType.toString(),
this.toString()));
}
}
public abstract BinaryLogicalFilter constructFilter();
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2005-2013 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
*
* http://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.query;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.filter.GreaterThanOrEqualsFilter;
import org.springframework.ldap.filter.LessThanOrEqualsFilter;
import org.springframework.ldap.filter.LikeFilter;
import org.springframework.ldap.filter.NotFilter;
import org.springframework.ldap.filter.PresentFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter;
/**
* @author Mattias Hellborg Arthursson
* @since 2.0
*/
class DefaultConditionCriteria implements ConditionCriteria {
private final DefaultContainerCriteria parent;
private final String attribute;
private boolean negated = false;
DefaultConditionCriteria(DefaultContainerCriteria parent, String attribute) {
this.parent = parent;
this.attribute = attribute;
}
@Override
public ContainerCriteria is(String value) {
return appendToParent(new EqualsFilter(attribute, value));
}
@Override
public ContainerCriteria gte(String value) {
return appendToParent(new GreaterThanOrEqualsFilter(attribute, value));
}
@Override
public ContainerCriteria lte(String value) {
return appendToParent(new LessThanOrEqualsFilter(attribute, value));
}
@Override
public ContainerCriteria like(String value) {
return appendToParent(new LikeFilter(attribute, value));
}
@Override
public ContainerCriteria whitespaceWildcardsLike(String value) {
return appendToParent(new WhitespaceWildcardsFilter(attribute, value));
}
@Override
public ContainerCriteria isPresent() {
return appendToParent(new PresentFilter(attribute));
}
private ContainerCriteria appendToParent(Filter filter) {
return parent.append(negateIfApplicable(filter));
}
private Filter negateIfApplicable(Filter myFilter) {
if (negated) {
return new NotFilter(myFilter);
}
return myFilter;
}
@Override
public DefaultConditionCriteria not() {
negated = !negated;
return this;
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2005-2013 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
*
* http://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.query;
import org.springframework.ldap.filter.Filter;
import javax.naming.Name;
import java.util.LinkedHashSet;
import java.util.Set;
import static org.springframework.ldap.query.CriteriaContainerType.AND;
import static org.springframework.ldap.query.CriteriaContainerType.OR;
/**
* @author Mattias Hellborg Arthursson
* @since 2.0
*/
class DefaultContainerCriteria implements ContainerCriteria {
private final Set<Filter> filters = new LinkedHashSet<Filter>();
private final LdapQuery topQuery;
private CriteriaContainerType type;
DefaultContainerCriteria(LdapQuery topQuery) {
this.topQuery = topQuery;
}
DefaultContainerCriteria append(Filter filter) {
this.filters.add(filter);
return this;
}
DefaultContainerCriteria withType(CriteriaContainerType newType) {
this.type = newType;
return this;
}
@Override
public ConditionCriteria and(String attribute) {
AND.validateSameType(type);
type = AND;
return new DefaultConditionCriteria(this, attribute);
}
@Override
public ConditionCriteria or(String attribute) {
OR.validateSameType(type);
type = OR;
return new DefaultConditionCriteria(this, attribute);
}
@Override
public ContainerCriteria and(ContainerCriteria nested) {
if(type == OR) {
return new DefaultContainerCriteria(topQuery)
.withType(AND)
.append(this.filter())
.append(nested.filter());
} else {
type = AND;
this.filters.add(nested.filter());
return this;
}
}
@Override
public ContainerCriteria or(ContainerCriteria nested) {
if (type == AND) {
return new DefaultContainerCriteria(topQuery)
.withType(OR)
.append(this.filter())
.append(nested.filter());
} else {
type = OR;
this.filters.add(nested.filter());
return this;
}
}
@Override
public Filter filter() {
if(filters.size() == 1) {
return filters.iterator().next();
}
return type.constructFilter().appendAll(filters);
}
@Override
public Name base() {
return topQuery.base();
}
@Override
public SearchScope searchScope() {
return topQuery.searchScope();
}
@Override
public Integer timeLimit() {
return topQuery.timeLimit();
}
@Override
public Integer countLimit() {
return topQuery.countLimit();
}
@Override
public String[] attributes() {
return topQuery.attributes();
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2005-2013 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
*
* http://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.query;
import org.springframework.ldap.filter.Filter;
import javax.naming.Name;
/**
* Holds all information regarding a Ldap query to be performed. Contains information regarding search base,
* search scope, time and count limits, and search filter.
*
* @author Mattias Hellborg Arthursson
* @since 2.0
* @see LdapQueryBuilder
*
* @see org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.AttributesMapper)
* @see org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)
* @see org.springframework.ldap.core.LdapOperations#searchForObject(LdapQuery, org.springframework.ldap.core.ContextMapper)
* @see org.springframework.ldap.core.LdapOperations#searchForContext(LdapQuery)
*/
public interface LdapQuery {
/**
* Get the search base. Default is {@link org.springframework.ldap.support.LdapUtils#emptyLdapName()}.
*
* @return the search base.
*/
Name base();
/**
* Get the search scope. Default is <code>null</code>, indicating that the LdapTemplate default should be used.
*
* @return the search scope.
*/
SearchScope searchScope();
/**
* Get the time limit. Default is <code>null</code>, indicating that the LdapTemplate default should be used.
*
* @return the time limit.
*/
Integer timeLimit();
/**
* Get the count limit. Default is <code>null</code>, indicating that the LdapTemplate default should be used.
* @return the count limit.
*/
Integer countLimit();
/**
* Get the attributes to return. Default is <code>null</code>, indicating that all attributes should be returned.
*
* @return the attributes to return.
*/
String[] attributes();
/**
* Get the filter.
*
* @return the filter.
*/
Filter filter();
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2005-2013 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
*
* http://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.query;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.Name;
/**
* Builder of LdapQueries. Start with a call to {@link #query()}, proceed with specifying the
* basic search configuration (e.g. search base, time limit, etc.), finally specify the actual query.
* Example:
* <pre>
* import static org.springframework.ldap.query.LdapQueryBuilder.query;
* ...
*
* LdapQuery query = query()
* .base("dc=261consulting, dc=com")
* .searchScope(SearchScope.ONELEVEL)
* .timeLimit(200)
* .countLimit(221)
* .where("objectclass").is("person").and("cn").is("John Doe");
* </pre>
* <p>
* Default configuration is that base path is {@link org.springframework.ldap.support.LdapUtils#emptyLdapName()}.
* All other parameters are undefined, meaning that (in the case of base search parameters), the LdapTemplate
* defaults will be used. Filter conditions must always be specified.
* </p>
* @author Mattias Hellborg Arthursson
*
* @see javax.naming.directory.SearchControls
* @see org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.AttributesMapper)
* @see org.springframework.ldap.core.LdapOperations#search(LdapQuery, org.springframework.ldap.core.ContextMapper)
* @see org.springframework.ldap.core.LdapOperations#searchForObject(LdapQuery, org.springframework.ldap.core.ContextMapper)
* @see org.springframework.ldap.core.LdapOperations#searchForContext(LdapQuery)
*/
public class LdapQueryBuilder implements LdapQuery {
private Name base = LdapUtils.emptyLdapName();
private SearchScope searchScope = null;
private Integer countLimit = null;
private Integer timeLimit = null;
private String[] attributes = null;
private DefaultContainerCriteria rootContainer = null;
/**
* Not to be instantiated directly - use static query() method.
*/
private LdapQueryBuilder() {
}
/**
* Construct a new LdapQueryBuilder.
*
* @return a new instance.
*/
public static LdapQueryBuilder query() {
return new LdapQueryBuilder();
}
/**
* Set the base search path for the query.
* Default is {@link org.springframework.ldap.support.LdapUtils#emptyLdapName()}.
*
* @param baseDn the base search path.
* @return this instance.
*/
public LdapQueryBuilder base(String baseDn) {
assertFilterNotStarted();
this.base = LdapUtils.newLdapName(baseDn);
return this;
}
/**
* Set the base search path for the query.
* Default is {@link org.springframework.ldap.support.LdapUtils#emptyLdapName()}.
*
* @param baseDn the base search path.
* @return this instance.
*/
public LdapQueryBuilder base(Name baseDn) {
assertFilterNotStarted();
this.base = LdapUtils.newLdapName(baseDn);
return this;
}
/**
* Set the search scope for the query.
* Default is {@link SearchScope#SUBTREE}.
*
* @param searchScope the search scope.
* @return this instance.
*/
public LdapQueryBuilder searchScope(SearchScope searchScope) {
assertFilterNotStarted();
this.searchScope = searchScope;
return this;
}
/**
* Set the count limit for the query.
* Default is 0 (no limit).
*
* @param countLimit the count limit.
* @return this instance.
*/
public LdapQueryBuilder countLimit(int countLimit) {
assertFilterNotStarted();
this.countLimit = countLimit;
return this;
}
public LdapQueryBuilder attributes(String... attributesToReturn) {
assertFilterNotStarted();
this.attributes = attributesToReturn;
return this;
}
/**
* Set the time limit for the query.
* Default is 0 (no limit).
*
* @param timeLimit the time limit.
* @return this instance.
*/
public LdapQueryBuilder timeLimit(int timeLimit) {
assertFilterNotStarted();
this.timeLimit = timeLimit;
return this;
}
/**
* Start specifying the filter conditions in this query.
*
* @param attribute The attribute that the first part of the filter should test against.
* @return A ConditionCriteria instance for specifying the compare operation.
*/
public ConditionCriteria where(String attribute) {
assertFilterNotStarted();
rootContainer = new DefaultContainerCriteria(this);
return new DefaultConditionCriteria(rootContainer, attribute);
}
private void assertFilterNotStarted() {
if(rootContainer != null) {
throw new IllegalStateException("Invalid operation - filter condition specification already started");
}
}
@Override
public Name base() {
return base;
}
@Override
public SearchScope searchScope() {
return searchScope;
}
@Override
public Integer countLimit() {
return countLimit;
}
@Override
public Integer timeLimit() {
return timeLimit;
}
@Override
public String[] attributes() {
return attributes;
}
@Override
public Filter filter() {
if(rootContainer == null) {
throw new IllegalStateException("No filter conditions have been specified specified");
}
return rootContainer.filter();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2005-2013 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
*
* http://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.query;
import javax.naming.directory.SearchControls;
/**
* Type safe definitions of search scopes.
*
* @author Mattias Hellborg Arthursson
*/
public enum SearchScope {
/**
* Corresponds to {@link SearchControls#OBJECT_SCOPE}
*/
OBJECT(SearchControls.OBJECT_SCOPE),
/**
* Corresponds to {@link SearchControls#ONELEVEL_SCOPE}
*/
ONELEVEL(SearchControls.ONELEVEL_SCOPE),
/**
* Corresponds to {@link SearchControls#SUBTREE_SCOPE}
*/
SUBTREE(SearchControls.SUBTREE_SCOPE);
private final int id;
private SearchScope(int id) {
this.id = id;
}
public int getId() {
return id;
}
}

View File

@@ -0,0 +1,129 @@
package org.springframework.ldap.query;
import org.junit.Test;
import org.springframework.ldap.support.LdapUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
/**
* @author Mattias Hellborg Arthursson
*/
public class LdapQueryBuilderTest {
@Test
public void buildSimpleWithDefaults() {
LdapQuery result = query().where("cn").is("John Doe");
assertEquals(LdapUtils.emptyLdapName(), result.base());
assertNull(result.searchScope());
assertNull(result.timeLimit());
assertNull(result.countLimit());
assertEquals("(cn=John Doe)", result.filter().encode());
}
@Test
public void buildGreaterThanOrEquals() {
LdapQuery result = query().where("cn").gte("John Doe");
assertEquals("(cn>=John Doe)", result.filter().encode());
}
@Test
public void buildLessThanOrEquals() {
LdapQuery result = query().where("cn").lte("John Doe");
assertEquals("(cn<=John Doe)", result.filter().encode());
}
@Test
public void buildLike() {
LdapQuery result = query().where("cn").like("J*hn Doe");
assertEquals("(cn=J*hn Doe)", result.filter().encode());
}
@Test
public void buildWhitespaceWildcards() {
LdapQuery result = query().where("cn").whitespaceWildcardsLike("John Doe");
assertEquals("(cn=*John*Doe*)", result.filter().encode());
}
@Test
public void buildPresent() {
LdapQuery result = query().where("cn").isPresent();
assertEquals("(cn=*)", result.filter().encode());
}
@Test
public void testBuildSimpleAnd() {
LdapQuery query = query()
.base("dc=261consulting, dc=com")
.searchScope(SearchScope.ONELEVEL)
.timeLimit(200)
.countLimit(221)
.where("objectclass").is("person").and("cn").is("John Doe");
assertEquals(LdapUtils.newLdapName("dc=261consulting, dc=com"), query.base());
assertEquals(SearchScope.ONELEVEL, query.searchScope());
assertEquals(Integer.valueOf(200), query.timeLimit());
assertEquals(Integer.valueOf(221), query.countLimit());
assertEquals("(&(objectclass=person)(cn=John Doe))", query.filter().encode());
}
@Test
public void buildSimpleOr() {
LdapQuery result = query().where("objectclass").is("person").or("cn").is("John Doe");
assertEquals("(|(objectclass=person)(cn=John Doe))", result.filter().encode());
}
@Test
public void buildAndOrPrecedence() {
LdapQuery result = query().where("objectclass").is("person")
.and("cn").is("John Doe")
.or(query().where("sn").is("Doe"));
assertEquals("(|(&(objectclass=person)(cn=John Doe))(sn=Doe))", result.filter().encode());
}
@Test
public void buildOrNegatedSubQueries() {
LdapQuery result = query().where("objectclass").not().is("person").or("sn").not().is("Doe");
assertEquals("(|(!(objectclass=person))(!(sn=Doe)))", result.filter().encode());
}
@Test
public void buildNestedAnd() {
LdapQuery result = query()
.where("objectclass").is("person")
.and(query()
.where("sn").is("Doe")
.or("sn").like("Die"));
assertEquals("(&(objectclass=person)(|(sn=Doe)(sn=Die)))", result.filter().encode());
}
@Test(expected = IllegalStateException.class)
public void verifyEmptyFilterThrowsIllegalState() {
query().filter();
}
@Test(expected = IllegalStateException.class)
public void verifyThatNewAttemptToStartSpecifyingFilterThrowsIllegalState() {
LdapQueryBuilder query = query();
query.where("sn").is("Doe");
query.where("cn").is("John Doe");
}
@Test(expected = IllegalStateException.class)
public void verifyThatAttemptToStartSpecifyingBasePropertiesThrowsIllegalStateWhenFilterStarted() {
LdapQueryBuilder query = query();
query.where("sn").is("Doe");
query.base("dc=261consulting,dc=com");
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.query.SearchScope;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.ldap.test.AttributeCheckAttributesMapper;
import org.springframework.ldap.test.AttributeCheckContextMapper;
@@ -39,6 +40,7 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
/**
* Tests for LdapTemplate's search methods. This test class tests all the
@@ -86,7 +88,7 @@ public class LdapTemplateSearchResultITest extends AbstractLdapTemplateIntegrati
contextMapper = null;
}
@Test
@Test
public void testSearch_AttributesMapper() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
@@ -94,7 +96,81 @@ public class LdapTemplateSearchResultITest extends AbstractLdapTemplateIntegrati
assertEquals(1, list.size());
}
@Test
@Test
public void testSearch_LdapQuery_AttributesMapper() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List<Object> list = tested.search(query()
.base(BASE_STRING)
.where("objectclass").is("person").and("sn").is("Person2"),
attributesMapper);
assertEquals(1, list.size());
}
@Test
public void testSearch_LdapQuery_AttributesMapper_FewerAttributes() {
attributesMapper.setExpectedAttributes(new String[] {"cn"});
attributesMapper.setExpectedValues(new String[]{"Some Person2"});
List<Object> list = tested.search(query()
.base(BASE_STRING)
.attributes("cn")
.where("objectclass").is("person").and("sn").is("Person2"),
attributesMapper);
assertEquals(1, list.size());
}
@Test
public void testSearch_LdapQuery_AttributesMapper_SearchScope() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List<Object> list = tested.search(query()
.base(BASE_STRING)
.searchScope(SearchScope.ONELEVEL)
.where("objectclass").is("person").and("sn").is("Person2"),
attributesMapper);
assertEquals(0, list.size());
}
@Test
public void testSearch_LdapQuery_AttributesMapper_SearchScope_CorrectBase() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List<Object> list = tested.search(query()
.base("ou=company1,c=Sweden")
.searchScope(SearchScope.ONELEVEL)
.where("objectclass").is("person").and("sn").is("Person2"),
attributesMapper);
assertEquals(1, list.size());
}
@Test
public void testSearch_LdapQuery_AttributesMapper_NoBase() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List<Object> list = tested.search(query()
.where("objectclass").is("person").and("sn").is("Person2"),
attributesMapper);
assertEquals(1, list.size());
}
@Test
public void testSearch_LdapQuery_AttributesMapper_DifferentBase() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List<Object> list = tested.search(query()
.base("c=Norway")
.where("objectclass").is("person").and("sn").is("Person2"),
attributesMapper);
assertEquals(0, list.size());
}
@Test
public void testSearch_SearchScope_AttributesMapper() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
@@ -200,7 +276,80 @@ public class LdapTemplateSearchResultITest extends AbstractLdapTemplateIntegrati
assertEquals(1, list.size());
}
@Test
@Test
public void testSearch_ContextMapper_LdapQuery() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);
List<DirContextAdapter> list = tested.search(query()
.base(BASE_NAME)
.where("objectclass").is("person").and("sn").is("Person2"),
contextMapper);
assertEquals(1, list.size());
}
@Test
public void testSearch_ContextMapper_LdapQuery_NoBase() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);
List<DirContextAdapter> list = tested.search(query()
.where("objectclass").is("person").and("sn").is("Person2"),
contextMapper);
assertEquals(1, list.size());
}
@Test
public void testSearch_ContextMapper_LdapQuery_SearchScope() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);
List<DirContextAdapter> list = tested.search(query()
.base(BASE_NAME)
.searchScope(SearchScope.ONELEVEL)
.where("objectclass").is("person").and("sn").is("Person2"),
contextMapper);
assertEquals(0, list.size());
}
@Test
public void testSearch_ContextMapper_LdapQuery_SearchScope_CorrectBase() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);
List<DirContextAdapter> list = tested.search(query()
.base("ou=company1,c=Sweden")
.searchScope(SearchScope.ONELEVEL)
.where("objectclass").is("person").and("sn").is("Person2"),
contextMapper);
assertEquals(1, list.size());
}
@Test
public void testSearchForContext_LdapQuery() {
DirContextOperations result = tested.searchForContext(query()
.where("objectclass").is("person").and("sn").is("Person2"));
assertNotNull(result);
assertEquals("Person2", result.getStringAttribute("sn"));
}
@Test(expected = EmptyResultDataAccessException.class)
public void testSearchForContext_LdapQuery_SearchScopeNotFound() {
tested.searchForContext(query()
.searchScope(SearchScope.ONELEVEL)
.where("objectclass").is("person").and("sn").is("Person2"));
}
@Test
public void testSearchForContext_LdapQuery_SearchScope_CorrectBase() {
DirContextOperations result =
tested.searchForContext(query()
.searchScope(SearchScope.ONELEVEL)
.base("ou=company1,c=Sweden")
.where("objectclass").is("person").and("sn").is("Person2"));
assertNotNull(result);
assertEquals("Person2", result.getStringAttribute("sn"));
}
@Test
public void testSearch_SearchScope_ContextMapper_Name() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);