org.springframework.ldap.query.LdapQueryBuilder Maven / Gradle / Ivy
/*
* Copyright 2005-2021 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.query;
import java.text.MessageFormat;
import javax.naming.Name;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.filter.HardcodedFilter;
import org.springframework.ldap.support.LdapEncoder;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.Assert;
/**
* 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:
* 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");
*
*
* 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.
*
*
* @author Mattias Hellborg Arthursson
* @since 2.0
* @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 final 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;
private boolean isFilterStarted = false;
/**
* 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();
}
/**
* Construct a new {@link LdapQueryBuilder} based on an existing {@link LdapQuery} All
* fields are copied, including giving the query a default filter.
*
*
* Note that all filter invariants are still enforced; an application cannot specify
* any non-filter values after it specifies a filter.
* @return a new instance.
* @since 3.0
*/
public static LdapQueryBuilder fromQuery(LdapQuery query) {
LdapQueryBuilder builder = new LdapQueryBuilder();
builder.rootContainer = new DefaultContainerCriteria(builder).append(query.filter());
builder.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()}.
* @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.
* @throws IllegalStateException if a filter has already been specified.
*/
public ConditionCriteria where(String attribute) {
initRootContainer();
return new DefaultConditionCriteria(this.rootContainer, attribute);
}
private void initRootContainer() {
assertFilterNotStarted();
this.rootContainer = new DefaultContainerCriteria(this);
this.isFilterStarted = true;
}
/**
* Specify a hardcoded filter. Please note that using this method, the filter string
* will not be validated or escaped in any way. Never use direct user input and
* use it concatenating strings to use as LDAP filters. Doing so opens up for
* "LDAP injection", where malicious user may inject specifically
* constructed data to form filters at their convenience. When user input is used
* consider using {@link #where(String)}, {@link #filter(String, Object...)}, or
* {@link #filter(Filter)} instead.
* @param hardcodedFilter The hardcoded filter string to use in the search.
* @return this instance.
* @throws IllegalStateException if a filter has already been specified.
*/
public LdapQuery filter(String hardcodedFilter) {
initRootContainer();
this.rootContainer.append(new HardcodedFilter(hardcodedFilter));
return this;
}
/**
* Specify the filter to use.
* @param filter The filter to use in the search.
* @return this instance.
* @throws IllegalStateException if a filter has already been specified.
*/
public LdapQuery filter(Filter filter) {
initRootContainer();
this.rootContainer.append(filter);
return this;
}
/**
* Specify a hardcoded filter using the specified parameters. The parameters will be
* properly encoded using {@link LdapEncoder#filterEncode(String)} to make sure no
* malicious data gets through. The filterFormat
String should be
* formatted for input to {@link MessageFormat#format(String, Object...)}.
* @param filterFormat the filter format string, formatted for input to
* {@link MessageFormat#format(String, Object...)}.
* @param params the parameters that will be used for building the final filter. All
* parameters will be properly encoded.
* @return this instance.
* @throws IllegalStateException if a filter has already been specified.
*/
public LdapQuery filter(String filterFormat, Object... params) {
Object[] encodedParams = new String[params.length];
for (int i = 0; i < params.length; i++) {
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
}
return filter(MessageFormat.format(filterFormat, encodedParams));
}
private void assertFilterNotStarted() {
Assert.state(!this.isFilterStarted, "Invalid operation - filter condition specification already started");
}
@Override
public Name base() {
return this.base;
}
@Override
public SearchScope searchScope() {
return this.searchScope;
}
@Override
public Integer countLimit() {
return this.countLimit;
}
@Override
public Integer timeLimit() {
return this.timeLimit;
}
@Override
public String[] attributes() {
return this.attributes;
}
@Override
public Filter filter() {
if (this.rootContainer == null) {
throw new IllegalStateException("No filter conditions have been specified");
}
return this.rootContainer.filter();
}
}