/*******************************************************************************
* Copyright (c) 2014 antoniomariasanchez at gmail.com.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* antoniomaria - initial API and implementation
******************************************************************************/
/*
* Copyright 2012 JAXIO http://www.jaxio.com 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 net.sf.gazpachoquest.qbe;
import static net.sf.gazpachoquest.qbe.Ranges.RangeDate.rangeDate;
import static net.sf.gazpachoquest.qbe.Ranges.RangeLocalDate.rangeLocalDate;
import static net.sf.gazpachoquest.qbe.Ranges.RangeLocalDateTime.rangeLocalDateTime;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.criteria.JoinType;
import javax.persistence.metamodel.SingularAttribute;
import net.sf.gazpachoquest.domain.support.Persistable;
import net.sf.gazpachoquest.qbe.Ranges.RangeDate;
import net.sf.gazpachoquest.qbe.Ranges.RangeInteger;
import net.sf.gazpachoquest.qbe.Ranges.RangeLocalDate;
import net.sf.gazpachoquest.qbe.Ranges.RangeLocalDateTime;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
/**
* The SearchParameters is used to pass search parameters to the DAO layer.
*
* Its usage keeps 'find' method signatures in the DAO/Service layer simple.
*
* A SearchParameters helps you drive your search in the following areas:
* <ul>
* <li>Configure the search mode (EQUALS, LIKE, ...)</li>
* <li>Pagination: it allows you to limit your search results to a specific
* range.</li>
* <li>Allow you to specify ORDER BY and ASC/DESC</li>
* <li>Enable/disable case sensitivity</li>
* <li>Enable/disable 2d level cache</li>
* <li>LIKE search against all string values: simply set the searchPattern
* property</li>
* <li>Named query: if you set a named query it will be executed. Named queries
* can be defined in annotation or src/main/resources/META-INF/orm.xml</li>
* </ul>
*
* Note : All requests are limited to a maximum number of elements to prevent
* resource exhaustion.
*
* @see ByExampleSpecification
* @see SearchMode
* @see OrderBy
* @see Range
* @see NamedQueryUtil
* @see PropertySelector
* @see EntitySelector
*/
public class SearchParameters {
private boolean andMode = true;
private SearchMode searchMode = SearchMode.EQUALS;
// cache
private Boolean cacheable = true;
private String cacheRegion;
// technical parameters
private boolean caseSensitive = false;
// distinct
private Boolean distinct = false;
// entity selectors
private final List<EntitySelector<?, ? extends Persistable, ?>> entities = new ArrayList<EntitySelector<?, ? extends Persistable, ?>>();
private int firstResult = 0;
// Joins
private final Map<JoinType, List<SingularAttribute<?, ?>>> joinAttributes = new HashMap<JoinType, List<SingularAttribute<?, ?>>>();
// Pagination
private int maxResults = 500;
// named query related
private String namedQuery;
private final List<OrderBy> orders = new ArrayList<OrderBy>();
private Map<String, Object> parameters = new HashMap<String, Object>();
// property selectors
private final List<PropertySelector<?, ?>> properties = new ArrayList<PropertySelector<?, ?>>();
// ranges
private final List<Range<?, ?>> ranges = new ArrayList<Range<?, ?>>();
// pattern to match against all strings.
private String searchPattern;
public SearchParameters() {
}
// -----------------------------------
// Predicate mode
// -----------------------------------
public boolean isAndMode() {
return andMode;
}
public void setAndMode(boolean andMode) {
this.andMode = andMode;
}
// -----------------------------------
// SearchMode
// -----------------------------------
public SearchParameters(final EntitySelector<?, ? extends Persistable, ?> entitySelector) {
addEntity(entitySelector);
}
// -----------------------------------
// Search by property selector support
// -----------------------------------
public List<PropertySelector<?, ?>> getProperties() {
return properties;
}
public void addProperty(PropertySelector<?, ?> propertySelector) {
properties.add(propertySelector);
}
public boolean hasProperties() {
return !properties.isEmpty();
}
// -----------------------------------
// Search by range support
// -----------------------------------
public SearchParameters(final Range<?, ?> range) {
addRange(range);
}
public SearchParameters(final SearchMode searchMode) {
setSearchMode(searchMode);
}
public void addEntity(final EntitySelector<?, ? extends Persistable, ?> entitySelector) {
entities.add(entitySelector);
}
public void addNamedQueryParameter(final String name, final Object value) {
Validate.notNull(name, "name must not be null");
Validate.notNull(value, "value must not be null");
parameters.put(name, value);
}
public void addOrderBy(final OrderBy orderBy) {
Validate.notNull(orderBy, "orderBy must not be null");
orders.add(orderBy);
}
public void addOrderBy(final SingularAttribute<? extends Persistable, ? extends Serializable> attribute) {
Validate.notNull(attribute, "attribute must not be null");
orders.add(new OrderBy(attribute));
}
// -----------------------------------
// Named query support
// -----------------------------------
public void addOrderBy(final SingularAttribute<? extends Persistable, ? extends Serializable> attribute,
final OrderByDirection direction) {
Validate.notNull(attribute, "fieldName must not be null");
Validate.notNull(direction, "direction must not be null");
orders.add(new OrderBy(attribute, direction));
}
public void addOrderBy(final String fieldName) {
Validate.notNull(fieldName, "fieldName must not be null");
orders.add(new OrderBy(fieldName));
}
public void addOrderBy(final String fieldName, final OrderByDirection direction) {
Validate.notNull(fieldName, "fieldName must not be null");
Validate.notNull(direction, "direction must not be null");
orders.add(new OrderBy(fieldName, direction));
}
public void addRange(final Range<?, ?> range) {
ranges.add(range);
}
public SearchParameters after(final SingularAttribute<?, Date> field, final Date from) {
RangeDate<?> rangeDate = rangeDate(field);
rangeDate.setFrom(from);
addRange(rangeDate);
return this;
}
public SearchParameters after(final SingularAttribute<?, LocalDate> field, final LocalDate from) {
RangeLocalDate<?> rangeLocalDate = rangeLocalDate(field);
rangeLocalDate.setFrom(from);
addRange(rangeLocalDate);
return this;
}
public SearchParameters after(final SingularAttribute<?, LocalDateTime> field, final LocalDateTime from) {
RangeLocalDateTime<?> rangeLocalDateTime = rangeLocalDateTime(field);
rangeLocalDateTime.setFrom(from);
addRange(rangeLocalDateTime);
return this;
}
public SearchParameters anywhere() {
return searchMode(SearchMode.ANYWHERE);
}
public SearchParameters before(final SingularAttribute<?, Date> field, final Date setToto) {
RangeDate<?> rangeDate = rangeDate(field);
rangeDate.setTo(setToto);
addRange(rangeDate);
return this;
}
// -----------------------------------
// Search pattern support
// -----------------------------------
public SearchParameters before(final SingularAttribute<?, LocalDate> field, final LocalDate setToto) {
RangeLocalDate<?> rangeLocalDate = rangeLocalDate(field);
rangeLocalDate.setTo(setToto);
addRange(rangeLocalDate);
return this;
}
public SearchParameters before(final SingularAttribute<?, LocalDateTime> field, final LocalDateTime setToto) {
RangeLocalDateTime<?> rangeLocalDateTime = rangeLocalDateTime(field);
rangeLocalDateTime.setTo(setToto);
addRange(rangeLocalDateTime);
return this;
}
public SearchParameters cacheable(final boolean cacheable) {
setCacheable(cacheable);
return this;
}
public SearchParameters cacheRegion(final String cacheRegion) {
setCacheRegion(cacheRegion);
return this;
}
// -----------------------------------
// Case sensitiveness support
// -----------------------------------
/**
* Fluently set the case sensitiveness to false.
* @return SearchParameters
*/
public SearchParameters caseInsensitive() {
setCaseSensitive(false);
return this;
}
/**
* Fluently set the case sensitiveness to true.
*
* @return SearchParameters
*/
public SearchParameters caseSensitive() {
setCaseSensitive(true);
return this;
}
/**
* Fluently set the case sensitiveness. Defaults to false.
*
* @param caseSensitive
* @return SearchParameters
*/
public SearchParameters caseSensitive(final boolean caseSensitive) {
setCaseSensitive(caseSensitive);
return this;
}
public void clearEntity() {
entities.clear();
}
public void clearOrders() {
orders.clear();
}
public void clearProperties() {
properties.clear();
}
// -----------------------------------
// Order by support
// -----------------------------------
public void clearRanges() {
ranges.clear();
}
public SearchParameters disableCache() {
setCacheable(false);
return this;
}
public SearchParameters distinct() {
distinct = true;
return this;
}
public SearchParameters distinct(final boolean value) {
distinct = value;
return this;
}
public SearchParameters enableCache() {
setCacheable(true);
return this;
}
/**
* Use the ENDING_LIKE
*
* @return SearchParameters
*/
public SearchParameters endingLike() {
return searchMode(SearchMode.ENDING_LIKE);
}
/**
* Add the passed {@link EntitySelector} in order to construct an OR
* predicate for the underlying foreign key.
*
* @return SearchParameters
*/
public SearchParameters entity(final EntitySelector<?, ? extends Persistable, ?> entitySelector) {
addEntity(entitySelector);
return this;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public SearchParameters entity(final SingularAttribute<?, ?> field, final Persistable... values) {
return entity(new EntitySelector(field, values));
}
/**
* Use the EQUALS @{link SearchMode}.
*
* @see SearchMode#EQUALS
*
* @return SearchParameters
*/
public SearchParameters equals() {
return searchMode(SearchMode.EQUALS);
}
public SearchParameters firstResult(final int firstResult) {
setFirstResult(firstResult);
return this;
}
public String getCacheRegion() {
return cacheRegion;
}
public List<EntitySelector<?, ? extends Persistable, ?>> getEntities() {
return entities;
}
public int getFirstResult() {
return firstResult;
}
/**
* Returns the attribute (x-to-one association) which must be fetched with
* an inner join.
* @return List
*/
public List<SingularAttribute<?, ?>> getInnerJoinAttributes() {
return getJoinAttributes(JoinType.INNER);
}
public Map<JoinType, List<SingularAttribute<?, ?>>> getJoinAttributes() {
return joinAttributes;
}
/**
* Returns the attribute (x-to-one association) which must be fetched with a
* left join.
* @return List
*/
public List<SingularAttribute<?, ?>> getLeftJoinAttributes() {
return getJoinAttributes(JoinType.LEFT);
}
public int getMaxResults() {
return maxResults;
}
/**
* Return the name of the named query to be used by the DAO layer.
* @return String
*/
public String getNamedQuery() {
return namedQuery;
}
/**
* Return the value of the passed parameter name.
* @return value
*/
public Object getNamedQueryParameter(final String parameterName) {
return parameters.get(parameterName);
}
/**
* The parameters associated with the named query, if any.
* @return Map
*/
public Map<String, Object> getNamedQueryParameters() {
return parameters;
}
public List<OrderBy> getOrders() {
return orders;
}
public List<Range<?, ?>> getRanges() {
return ranges;
}
/**
* Return the @{link SearchMode}. It defaults to EQUALS.
*
* @see SearchMode#EQUALS
* @return SearchMode
*/
public SearchMode getSearchMode() {
return searchMode;
}
/**
* Returns the search pattern to be used by the DAO layer.
* @return String
*/
public String getSearchPattern() {
return searchPattern;
}
public SearchParameters greather(final SingularAttribute<?, Integer> field, final Integer value) {
RangeInteger<?> rangeInteger = RangeInteger.rangeInteger(field);
rangeInteger.setFrom(value);
addRange(rangeInteger);
return this;
}
public boolean hasCacheRegion() {
return isNotBlank(cacheRegion);
}
/**
* Returns true if a named query has been set, false otherwise. When it
* returns true, the DAO layer will call the
* namedQuery.
* @return boolean
*/
public boolean hasNamedQuery() {
return isNotBlank(namedQuery);
}
public boolean hasOrders() {
return !orders.isEmpty();
}
/**
* When it returns true, it indicates to the DAO layer to use the passed
* searchPattern on all string properties.
* @return boolean
*/
public boolean hasSearchPattern() {
return isNotBlank(searchPattern);
}
/**
* The passed attribute (x-to-one association) will be fetched with a inner
* join.
* @return SearchParameters
*/
public SearchParameters innerJoin(final SingularAttribute<?, ?> xToOneAttribute) {
getInnerJoinAttributes().add(xToOneAttribute);
return this;
}
public boolean isCacheable() {
return cacheable;
}
public boolean isCaseInsensitive() {
return !caseSensitive;
}
public boolean isCaseSensitive() {
return caseSensitive;
}
public boolean isDistinct() {
return distinct;
}
/**
* The passed attribute (x-to-one association) will be fetched with a left
* join.
* @return SearchParameters
*/
public SearchParameters leftJoin(final SingularAttribute<?, ?> xToOneAttribute) {
getLeftJoinAttributes().add(xToOneAttribute);
return this;
}
// -----------------------------------
// Search by entity selector support
// -----------------------------------
/**
* Use the LIKE @{link SearchMode}.
*
* @see SearchMode#LIKE
* @return SearchParameters
*/
public SearchParameters like() {
return searchMode(SearchMode.LIKE);
}
public SearchParameters lower(final SingularAttribute<?, Integer> field, final Integer value) {
RangeInteger<?> rangeInteger = RangeInteger.rangeInteger(field);
rangeInteger.setTo(value);
addRange(rangeInteger);
return this;
}
public SearchParameters maxResults(final int maxResults) {
setMaxResults(maxResults);
return this;
}
/**
* Fluently set the named query to be used by the DAO layer. Null by
* default.
* @return SearchParameters
*/
public SearchParameters namedQuery(final String namedQuery) {
setNamedQuery(namedQuery);
return this;
}
/**
* Fluently set the parameters for the named query.
* @return SearchParameters
*/
public SearchParameters namedQueryParameter(final String name, final Object value) {
addNamedQueryParameter(name, value);
return this;
}
/**
* Fluently set the parameters for the named query.
* @return SearchParameters
*/
public SearchParameters namedQueryParameters(final Map<String, Object> parameters) {
setNamedQueryParameters(parameters);
return this;
}
// -----------------------------------
// Pagination support
// -----------------------------------
public SearchParameters noLimit() {
setMaxResults(-1);
return this;
}
public SearchParameters orderBy(final OrderBy orderBy) {
addOrderBy(orderBy);
return this;
}
public SearchParameters orderBy(final SingularAttribute<? extends Persistable, ? extends Serializable> attribute) {
addOrderBy(attribute);
return this;
}
public SearchParameters orderBy(final SingularAttribute<? extends Persistable, ? extends Serializable> attribute,
final OrderByDirection direction) {
addOrderBy(attribute, direction);
return this;
}
public SearchParameters orderBy(final String fieldName) {
addOrderBy(fieldName);
return this;
}
public SearchParameters orderBy(final String fieldName, final OrderByDirection direction) {
addOrderBy(fieldName, direction);
return this;
}
/**
* Add the passed {@link PropertySelector} in order to construct an OR
* predicate for the corresponding property.
* @return SearchParameters
*/
public SearchParameters property(final PropertySelector<?, ?> propertySelector) {
addProperty(propertySelector);
return this;
}
/**
* Add the passed {@link Range} in order to create a 'range' predicate on
* the corresponding property.
* @return SearchParameters
*/
public SearchParameters range(final Range<?, ?> range) {
addRange(range);
return this;
}
public SearchParameters range(final SingularAttribute<?, Date> field, final Date from, final Date to) {
addRange(rangeDate(field, from, to));
return this;
}
public SearchParameters range(final SingularAttribute<?, LocalDate> field, final LocalDate from, final LocalDate to) {
addRange(rangeLocalDate(field, from, to));
return this;
}
public SearchParameters range(final SingularAttribute<?, LocalDateTime> field, final LocalDateTime from,
final LocalDateTime to) {
addRange(rangeLocalDateTime(field, from, to));
return this;
}
/**
* Fluently set the @{link SearchMode}. It defaults to EQUALS.
*
* @see SearchMode#EQUALS
* @return SearchParameters
*/
public SearchParameters searchMode(final SearchMode searchMode) {
setSearchMode(searchMode);
return this;
}
// -----------------------------------
// Caching support
// -----------------------------------
/**
* Fluently set the pattern which may contains wildcards (ex: "e%r%ka" ).
* The passed searchPattern is used by the
* DAO layer on all string properties. Null by default.
* @return SearchParameters
*/
public SearchParameters searchPattern(final String searchPattern) {
setSearchPattern(searchPattern);
return this;
}
/**
* Default to true.
*/
public void setCacheable(final boolean cacheable) {
this.cacheable = cacheable;
}
public void setCacheRegion(final String cacheRegion) {
this.cacheRegion = cacheRegion;
}
/**
* Set the case sensitiveness. Defaults to false.
*
* @param caseSensitive
*/
public void setCaseSensitive(final boolean caseSensitive) {
this.caseSensitive = caseSensitive;
}
public void setFirstResult(final int firstResult) {
this.firstResult = firstResult;
}
/**
* Set the maximum number of results to retrieve. Pass -1 for no limits.
*/
public void setMaxResults(final int maxResults) {
this.maxResults = maxResults;
}
/**
* Set the named query to be used by the DAO layer. Null by default.
*/
public void setNamedQuery(final String namedQuery) {
this.namedQuery = namedQuery;
}
/**
* Set the parameters for the named query.
*/
public void setNamedQueryParameters(final Map<String, Object> parameters) {
Validate.notNull(parameters, "parameters must not be null");
this.parameters = parameters;
}
/**
* Fluently set the @{link SearchMode}. It defaults to EQUALS.
*
* @see SearchMode#EQUALS
*/
public void setSearchMode(final SearchMode searchMode) {
Validate.notNull(searchMode, "searchMode must not be null");
this.searchMode = searchMode;
}
// -----------------------------------
// Distinct
// -----------------------------------
/**
* Set the pattern which may contains wildcards (ex: "e%r%ka" ). The passed
* searchPattern is used by the DAO layer
* on all string properties. Null by default.
*/
public void setSearchPattern(final String searchPattern) {
this.searchPattern = searchPattern;
}
/**
* Use the STARTING_LIKE @{link SearchMode}.
*
* @see SearchMode#STARTING_LIKE
* @return SearchParameters
*/
public SearchParameters startingLike() {
return searchMode(SearchMode.STARTING_LIKE);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
private List<SingularAttribute<?, ?>> getJoinAttributes(final JoinType inner) {
List<SingularAttribute<?, ?>> left = joinAttributes.get(inner);
if (left == null) {
left = new ArrayList<SingularAttribute<?, ?>>();
joinAttributes.put(inner, left);
}
return left;
}
}