Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2011-2018 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.appng.persistence.repository;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
/**
* A {@link SearchQuery} can be used to perform JPA-queries for JPA entities. Therefore multiple criteria can be added,
* which are being used in the WHERE-clause of the query (with an and-concatenation).
* Example:
* To retrieve all male users aged over 30 whose name starts with "John", the following query could be build:
*
*
*
* This would result in a JPA-query like this:
*
*
* from User e where e.name like ?1 and e.gender = ?2 and e.age > ?3
*
*
* The query can then be executed by calling {@link #execute(javax.persistence.EntityManager)} or
* {@link #execute(org.springframework.data.domain.Pageable, javax.persistence.EntityManager)} .
*
* Note that the methods which are used to add a criteria are null-safe, which means the criteria is ignored if the
* given value is {@code null}.
*
*
* @author Matthias Müller
* @param T
* the JPA {@link Entity}-type
*
* @see SearchRepository#search(SearchQuery, Pageable)
*/
public class SearchQuery {
private List criteria = new ArrayList();
private Class domainClass;
private boolean distinct;
private String joinQuery;
private boolean appendEntityAlias = true;
private List andClauses = new ArrayList.Clause>();
/**
* Creates a new {@link SearchQuery} for the given type.
*
* @param domainClass
* the type of the JPA {@link Entity}
*/
public SearchQuery(Class domainClass) {
this.domainClass = domainClass;
}
/**
* Checks whether or not to add the entity alias {@code e.} for each criteria in the JPQL query. Default is
* {@code true} .
*
* @return {@code true} when entity alias gets appended, {@code false} otherwise
*/
public boolean isAppendEntityAlias() {
return appendEntityAlias;
}
/**
* Adds a parameterized JPQL AND-clause to this query. Avoid using the same parameter names for different calls of
* this method.
* Usage example:
*
*
* SearchQuery query= repository.createSearchQuery();
* Map<String, Object> params = new HashMap<String, Object>();
* params.put("date", new Date());
* query.and("(e.valid_from before :date or e.valid_from is null)", params);
*
*
* @param clause
* the AND clause
* @param params
* the named parameters for the AND-clause
*
* @see #and(String)
* @see #setAppendEntityAlias(boolean)
*/
public void and(String clause, Map params) {
andClauses.add(new Clause(clause, params));
}
/**
* Adds a parameterless JPQL AND-clause to this query.
* Usage example:
*
*
* SearchQuery query = repository.createSearchQuery();
* query.and("(e.valid_from before now() or e.valid_from is null)");
*
*
* @param clause
* the AND-clause
* @see #and(String, Map)
* @see #setAppendEntityAlias(boolean)
*/
public void and(String clause) {
and(clause, new HashMap());
}
/**
* Set to {@code false} to avoid adding the entity alias {@code e.} for each criteria in the JPQL query.
*
* @param appendEntityAlias
* @see #isAppendEntityAlias()
*/
public void setAppendEntityAlias(boolean appendEntityAlias) {
this.appendEntityAlias = appendEntityAlias;
}
/**
* Checks if the attribute equals the given value.
* JPQL-fragment:
*
*
* e.name = :value
*
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery equals(String name, Object value) {
add(name, value, Operand.EQ, true);
return this;
}
/**
* Checks if the attribute not equals the given value.
* JPQL-fragment:
*
*
* e.name != :value
*
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery notEquals(String name, Object value) {
add(name, value, Operand.NE, true);
return this;
}
/**
* Checks if the attribute is greater than the given value.
* JPQL-fragment:
*
*
* e.name > :value
*
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery greaterThan(String name, Object value) {
add(name, value, Operand.GT, true);
return this;
}
/**
* Checks if the attribute is greater or equals the given value.
* JPQL-fragment:
*
*
* e.name >= :value
*
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery greaterEquals(String name, Object value) {
add(name, value, Operand.GE, true);
return this;
}
/**
* Checks if the attribute is less than the given value.
* JPQL-fragment:
*
*
* e.name < :value
*
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery lessThan(String name, Object value) {
add(name, value, Operand.LT, true);
return this;
}
/**
* Checks if the attribute is less or equals the given value.
* JPQL-fragment:
*
*
* e.name <= :value
*
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery lessEquals(String name, Object value) {
add(name, value, Operand.LE, true);
return this;
}
/**
* Checks if the attribute is in the given values.
* JPQL-fragment:
*
*
* e.name in :value
*
*
* @param name
* the name of the attribute
* @param values
* the values to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery in(String name, Collection> values) {
add(name, values == null ? null : (values.isEmpty() ? null : values), Operand.IN, true);
return this;
}
/**
* Checks if the attribute is not in the given values.
* JPQL-fragment:
*
*
* e.name not in :value
*
*
* @param name
* the name of the attribute
* @param values
* the values to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery notIn(String name, Collection> values) {
add(name, values == null ? null : (values.isEmpty() ? null : values), Operand.NOT_IN, true);
return this;
}
/**
* Checks if the attribute is contains the given value.
* JPQL-fragment:
*
*
* e.name like :value
*
*
* You don't have to add the wildcard '{@code %}' before and after the {@code value}, this is done
* internally!
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
*/
public final SearchQuery contains(String name, Object value) {
add(name, value == null ? null : ("%" + value + "%"), Operand.LIKE, true);
return this;
}
/**
* Checks if the attribute is like the given value.
* JPQL-fragment:
*
*
* e.name like :value
*
*
* The {@code value} should contain some wildcards ('{@code %}').
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
* @see #startsWith(String, Object)
* @see #endsWith(String, Object)
*/
public final SearchQuery like(String name, Object value) {
add(name, value == null ? null : (value), Operand.LIKE, true);
return this;
}
/**
* Checks if the attribute is not like the given value.
* JPQL-fragment:
*
*
* e.name not like :value
*
*
* The {@code value} should contain some wildcards ('{@code %}').
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
* @see #startsWith(String, Object)
* @see #endsWith(String, Object)
*/
public final SearchQuery notLike(String name, Object value) {
add(name, value == null ? null : (value), Operand.NOT_LIKE, true);
return this;
}
/**
* Checks if the attribute is starts with the given value.
* JPQL-fragment:
*
*
* e.name like :value
*
*
* You don't have to add the wildcard '{@code %}' after the {@code value}, this is done internally!
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
* @see #like(String, Object)
*/
public final SearchQuery startsWith(String name, Object value) {
add(name, value == null ? null : (value + "%"), Operand.LIKE, true);
return this;
}
/**
* Checks if the attribute is ends with the given value.
* JPQL-fragment:
*
*
* e.name like :value
*
*
* You don't have to add the wildcard '{@code %}' before the {@code value}, this is done internally!
*
* @param name
* the name of the attribute
* @param value
* the value to check
* @return the current {@link SearchQuery}
* @see #like(String, Object)
*/
public final SearchQuery endsWith(String name, Object value) {
add(name, value == null ? null : ("%" + value), Operand.LIKE, true);
return this;
}
/**
* Checks if the attribute is not null.
* JPQL-fragment:
*
*
* e.name is not null
*
*
* @param name
* the name of the attribute
* @return the current {@link SearchQuery}
*/
public final SearchQuery isNotNull(String name) {
add(name, null, Operand.NOT_NULL, false);
return this;
}
/**
* Checks if the attribute is null.
* JPQL-fragment:
*
*
* e.name is null
*
*
* @param name
* the name of the attribute
* @return the current {@link SearchQuery}
*/
public final SearchQuery isNull(String name) {
add(name, null, Operand.NULL, false);
return this;
}
private void add(String name, Object value, Operand operand, boolean valueMandatory) {
SearchCriteria sc = new SearchCriteria(name, value, operand, valueMandatory);
if (sc.isValid()) {
criteria.add(sc);
}
}
private void appendOrder(Pageable pageable, StringBuilder queryBuilder, String entityName) {
if (null != pageable) {
Sort pageSort = pageable.getSort();
if (null != pageSort) {
boolean firstOrder = true;
for (Order order : pageSort) {
queryBuilder.append(firstOrder ? " order by " : ", ");
queryBuilder.append(StringUtils.isBlank(entityName) ? entityName : entityName + ".");
queryBuilder.append(order.getProperty() + " " + order.getDirection().name());
firstOrder = false;
}
}
}
}
/**
* Adds one or more joins to the resulting JPQL-query.
* The alias for the entity is {@code e}, so you could add a join like
*
*
* join e.addresses a
*
*
* @param joinQuery
* the join-part of a JPQL query
*/
public void join(String joinQuery) {
this.joinQuery = joinQuery;
}
/**
* Causes the JPQL-query to select {@code distinct} entities only.
*/
public void distinct() {
this.distinct = true;
}
@Override
public String toString() {
return criteria.toString() + " " + andClauses.toString();
}
/**
* Executes this {@link SearchQuery} with the given {@link EntityManager}.
*
* @param entityManager
* the {@link EntityManager}
* @return the result-list
*/
public final List execute(EntityManager entityManager) {
return execute(null, entityManager).getContent();
}
/**
* Executes this {@link SearchQuery} with the given {@link EntityManager}, applying the given {@link Pageable} (if
* any) for paging and sorting the results.
*
* @param pageable
* a {@link Pageable} (optional)
* @param entityManager
* the {@link EntityManager}
* @return the result-{@link Page}
*
* @see SearchRepository#search(SearchQuery, Pageable)
*/
public final Page execute(Pageable pageable, EntityManager entityManager) {
StringBuilder sb = new StringBuilder();
sb.append("from " + domainClass.getSimpleName() + " e");
if (StringUtils.isNotBlank(joinQuery)) {
sb.append(" " + joinQuery.trim() + " ");
}
for (int i = 0; i < criteria.size(); i++) {
SearchCriteria criterion = criteria.get(i);
sb.append(i == 0 ? " where " : " and ");
if (appendEntityAlias) {
sb.append("e.");
}
sb.append(criterion.getName() + " " + criterion.getOperand().getPresentation());
if (null != criterion.getValue()) {
sb.append(" ?" + i);
}
}
boolean addWhere = criteria.size() == 0;
for (Clause clause : andClauses) {
sb.append(addWhere ? " where " : " and ");
sb.append(" " + clause.clause + " ");
addWhere = false;
}
String distinctPart = distinct ? "distinct" : "";
TypedQuery countQuery = entityManager.createQuery("select count(" + distinctPart + " e) " + sb.toString(),
Long.class);
appendOrder(pageable, sb, appendEntityAlias ? "e" : "");
TypedQuery query = entityManager.createQuery("select " + distinctPart + " e " + sb.toString(), domainClass);
for (int i = 0; i < criteria.size(); i++) {
SearchCriteria criterion = criteria.get(i);
Object value = criterion.getValue();
if (null != value) {
countQuery.setParameter(i, value);
query.setParameter(i, value);
}
}
for (Clause clause : andClauses) {
for (Entry entry : clause.params.entrySet()) {
countQuery.setParameter(entry.getKey(), entry.getValue());
query.setParameter(entry.getKey(), entry.getValue());
}
}
Long total = null;
if (null != pageable) {
total = countQuery.getSingleResult();
if (pageable.getOffset() >= total) {
pageable = new PageRequest(0, pageable.getPageSize(), pageable.getSort());
}
query.setFirstResult(pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
}
List content = query.getResultList();
if (null == total) {
total = new Integer(content.size()).longValue();
}
Page page = new PageImpl(content, pageable, total);
return page;
}
enum Operand {
EQ("="), NE("!="), LE("<="), GE(">="), LT("<"), GT(">"), IN("in"), NOT_IN("not in"), LIKE("like"), NOT_LIKE(
"not like"), NOT_NULL("is not null"), NULL("is null");
private final String presentation;
private Operand(String presentation) {
this.presentation = presentation;
}
String getPresentation() {
return presentation;
}
}
private class Clause {
String clause;
Map params;
Clause(String clause, Map params) {
this.clause = clause.trim();
this.params = params;
}
@Override
public String toString() {
return clause;
}
}
private class SearchCriteria {
private final String name;
private final Object value;
private final Operand operand;
private final boolean valueMandatory;
SearchCriteria(String name, Object value, Operand operand, boolean valueMandatory) {
this.name = name;
this.value = value;
this.operand = operand;
this.valueMandatory = valueMandatory;
}
String getName() {
return name;
}
Object getValue() {
return value;
}
Operand getOperand() {
return operand;
}
boolean isValueMandatory() {
return valueMandatory;
}
boolean isValid() {
return null != value || !isValueMandatory();
}
@Override
public String toString() {
return "e." + getName() + " " + getOperand().getPresentation() + " "
+ (isValueMandatory() ? getValue() : "");
}
}
}