/*
* Copyright 2007 The Fornax Project Team, including 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.sculptor.framework.accessimpl.jpa;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import javax.persistence.metamodel.ManagedType;
import org.sculptor.framework.accessimpl.jpa.QueryPropertyRestriction.Operator;
/**
* <p>
* Implementation of Access command FindByQueryAccess.
* </p>
* <p>
* Command design pattern.
* </p>
*/
public abstract class JpaCriteriaQueryExpressionAccessBase<T,R>
extends JpaCriteriaQueryAccessBase<T,R> {
private QueryExpressions<T> expressions = new QueryExpressions<T>();
public JpaCriteriaQueryExpressionAccessBase() {
super();
}
public JpaCriteriaQueryExpressionAccessBase(Class<T> type) {
super(type);
}
public JpaCriteriaQueryExpressionAccessBase(Class<T> type, Class<R> resultType) {
super(type, resultType);
}
protected QueryExpressions<T> getExpressions() {
return expressions;
}
protected void setExpressions(QueryExpressions<T> expressions) {
this.expressions = expressions;
}
public String getOrderBy() {
return expressions.getOrdersAsString();
}
public void setOrderBy(String orderBy) {
expressions.addOrders(orderBy);
}
public void setSelections(String selections) {
expressions.addSelections(selections);
}
public void setGroupBy(String groupBy) {
expressions.addGroups(groupBy);
}
@SuppressWarnings("unchecked")
protected void prepareSelect(CriteriaQuery<R> criteriaQuery, Root<T> root, QueryConfig config) {
if (expressions.hasSelections()) {
List<Selection<?>> selections = mapSelections(getCriteriaBuilder(), root, expressions.getSelections());
if (selections.size() == 1) {
criteriaQuery.select((Selection<? extends R>) selections.get(0));
} else {
criteriaQuery.multiselect(selections);
}
}
}
protected void prepareGroupBy(CriteriaQuery<R> criteriaQuery, Root<T> root, QueryConfig config) {
if (expressions.hasGroups()) {
criteriaQuery.groupBy(mapExpressions(getCriteriaBuilder(), root, expressions.getGroups()));
}
}
protected void prepareOrderBy(CriteriaQuery<R> criteriaQuery, Root<T> root, QueryConfig config) {
if (config.isSingleResult() && expressions.hasOrders()) {
if (config.throwExceptionOnConfigurationError()) {
throw new QueryConfigException(
"Query returns a single result, 'order by' not allowed.");
}
return;
}
if (expressions.hasOrders()) {
criteriaQuery.orderBy(mapOrders(getCriteriaBuilder(), root, expressions.getOrders()));
}
}
protected List<Predicate> preparePredicates() {
return null;
}
protected void prepareFetch(Root<T> root, QueryConfig config) {
}
@Override
protected TypedQuery<R> prepareTypedQuery(QueryConfig config) {
return getEntityManager().createQuery(getCriteriaQuery());
}
/**
*
* @param path
* @param property
* @param operator
* @param value
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes", "unused" })
private Predicate preparePredicate(Path<?> path, String property, Operator operator, Object value) {
path = getPath(path, property);
if (Operator.Equal.equals(operator)) {
return getCriteriaBuilder().equal(path, value);
} else if (Operator.NotEqual.equals(operator)) {
return getCriteriaBuilder().notEqual(
getCriteriaBuilder().upper(path.as(String.class)),
((String) value).toUpperCase());
} else if (Operator.IgnoreCaseEqual.equals(operator)) {
return getCriteriaBuilder().equal(
getCriteriaBuilder().upper(path.as(String.class)),
((String) value).toUpperCase());
} else if (Operator.LessThan.equals(operator)) {
return getCriteriaBuilder().lessThan((Expression<Comparable>) path,
(Comparable) value);
} else if (Operator.LessThanOrEqual.equals(operator)) {
return getCriteriaBuilder().lessThanOrEqualTo(
(Expression<Comparable>) path, (Comparable) value);
} else if (Operator.GreaterThan.equals(operator)) {
return getCriteriaBuilder().greaterThan((Expression<Comparable>) path,
(Comparable) value);
} else if (Operator.GreaterThanOrEqual.equals(operator)) {
return getCriteriaBuilder().greaterThanOrEqualTo(
(Expression<Comparable>) path, (Comparable) value);
} else if (Operator.NotLike.equals(operator)) {
return getCriteriaBuilder().notLike(path.as(String.class),
(String) value);
} else if (Operator.Like.equals(operator)) {
return getCriteriaBuilder().like(path.as(String.class), (String) value);
} else if (Operator.IgnoreCaseLike.equals(operator)) {
return getCriteriaBuilder().like(
getCriteriaBuilder().upper(path.as(String.class)),
((String) value).toUpperCase());
} else if (Operator.IsNull.equals(operator)) {
return getCriteriaBuilder().isNull(path);
} else if (Operator.IsNotNull.equals(operator)) {
return getCriteriaBuilder().isNotNull(path);
} else if (Operator.IsEmpty.equals(operator)) {
// TODO: support additional types like Map,...
if (getAttribute(getRoot().getModel(), property).isCollection()) {
return getCriteriaBuilder().isEmpty(path.as(Collection.class));
} else {
return null;
}
} else if (Operator.IsNotEmpty.equals(operator)) {
// TODO: support additional types like Map,...
if (getAttribute(getRoot().getModel(), property).isCollection()) {
return getCriteriaBuilder().isNotEmpty(path.as(Collection.class));
} else {
return null;
}
} else if (Operator.In.equals(operator)) {
if (value instanceof Collection<?>) {
return path.in((Collection<?>) value);
} else {
return path.in((Object[]) value);
}
} else if (Operator.NotIn.equals(operator)) {
if (value instanceof Collection<?>) {
return getCriteriaBuilder().not(path.in((Collection<?>) value));
} else {
return getCriteriaBuilder().not(path.in((Object[]) value));
}
}
// openjpa does not support embeddables as restriction directly
// TODO: verify whether this is working now
if (JpaHelper.isJpaProviderOpenJpa(getEntityManager())) {
for (ManagedType<?> embeddableType : getMetaModel().getEmbeddables()) {
if (embeddableType.getJavaType().equals(value.getClass())) {
return andPredicates(preparePredicates(path, embeddableType, value));
}
}
}
return preparePredicate(path, value);
}
/**
*
* @param path
* @param builder
* @param selections
* @return
*/
private List<Selection<?>> mapSelections(CriteriaBuilder builder, Path<?> root, List<String> selections) {
List<Selection<?>> list = new ArrayList<Selection<?>>();
list.addAll(mapExpressions(builder, root, selections));
return list;
}
/**
*
* @param path
* @param builder
* @param selections
* @return
*/
private List<Order> mapOrders(CriteriaBuilder builder, Path<?> root, List<String> orders) {
if (orders == null) {
return null;
}
List<Order> list = new ArrayList<Order>();
for (String order : orders) {
Path<?> path = getPath(root, getPropertyName(order));
if ("desc".equalsIgnoreCase(getFunction(order))) {
list.add(builder.desc(path));
} else {
list.add(builder.asc(path));
}
}
return list;
}
/**
*
* @param path
* @param builder
* @param expressions
* @return
*/
@SuppressWarnings("unchecked")
private List<Expression<?>> mapExpressions(CriteriaBuilder builder,
Path<?> root, List<String> expressions) {
if (expressions == null) {
return null;
}
List<Expression<?>> list = new ArrayList<Expression<?>>();
for (String expression : expressions) {
String propertyName = getPropertyName(expression);
String alias = getPropertyAlias(expression);
String function = getFunction(expression);
Path<?> path = getPath(root, propertyName);
if (alias != null) {
path.alias(alias);
}
if ("max".equalsIgnoreCase(function)) {
list.add(builder.max((Expression<? extends Number>) path));
} else if ("min".equalsIgnoreCase(function)) {
list.add(builder.min((Expression<? extends Number>) path));
} else if ("avg".equalsIgnoreCase(function)) {
list.add(builder.avg((Expression<? extends Number>) path));
} else if ("sum".equalsIgnoreCase(function)) {
list.add(builder.sum((Expression<? extends Number>) path));
} else if ("sumAsLong".equalsIgnoreCase(function)) {
list.add(builder.sumAsLong(path.as(Integer.class)));
} else if ("sumAsDouble".equalsIgnoreCase(function)) {
list.add(builder.sumAsDouble(path.as(Float.class)));
} else if ("count".equalsIgnoreCase(function)) {
list.add(builder.count(path.as(Long.class)));
} else if ("countDistinct".equalsIgnoreCase(function)) {
list.add(builder.countDistinct(path.as(Long.class)));
} else {
list.add(path);
}
}
return list;
}
/**
*
* @param expression
* @return
*/
private String getPropertyAlias(String expression) {
if (expression.contains(" as ")) {
return expression.split(" as ")[1].trim();
}
return null;
}
/**
*
* @param expression
* @return
*/
private String getPropertyName(String expression) {
String propertyName = expression.trim();
if (expression.contains(" as ")) {
propertyName = expression.split(" as ")[0].trim();
}
if (propertyName.contains("(") && propertyName.contains(")")) {
propertyName = propertyName.split("\\(|\\)")[1].trim();
}
return propertyName;
}
/**
*
* @param expression
* @return
*/
private String getFunction(String expression) {
String expr = expression.trim();
if (expr.contains(" asc")) {
return "asc";
} else if (expr.contains(" desc")) {
return "desc";
} else if (expr.contains("(")) {
return expr.split("\\(")[0].trim();
}
return null;
}
}