/*
* 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.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Attribute.PersistentAttributeType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import org.sculptor.framework.domain.Property;
/**
* <p>
* Implementation of Access command FindByQueryAccess.
* </p>
* <p>
* Command design pattern.
* </p>
*/
public abstract class JpaCriteriaQueryAccessBase<T, R> extends JpaQueryAccessBase<T, R> {
private CriteriaBuilder criteriaBuilder = null;
private Metamodel metaModel = null;
private CriteriaQuery<R> criteriaQuery = null;
private Root<T> root = null;
private QueryExpressions<T> expressions = new QueryExpressions<T>();
private TypedQuery<Long> resultCountQuery = null;
private Property<?>[] fetchEager;
public JpaCriteriaQueryAccessBase() {
super();
}
public JpaCriteriaQueryAccessBase(Class<T> type) {
super(type);
}
public JpaCriteriaQueryAccessBase(Class<T> type, Class<R> resultType) {
super(type, resultType);
}
protected CriteriaBuilder getCriteriaBuilder() {
return criteriaBuilder;
}
protected void setCriteriaBuilder(CriteriaBuilder builder) {
this.criteriaBuilder = builder;
}
protected CriteriaQuery<R> getCriteriaQuery() {
return criteriaQuery;
}
protected void setCriteriaQuery(CriteriaQuery<R> query) {
this.criteriaQuery = query;
}
protected Root<T> getRoot() {
return root;
}
protected void setRoot(Root<T> root) {
this.root = root;
}
protected Metamodel getMetaModel() {
return metaModel;
}
protected void setMetaModel(Metamodel metaModel) {
this.metaModel = metaModel;
}
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 setFetchEager(Property<?>[] fetchEager) {
this.fetchEager = fetchEager;
}
public Property<?>[] getFetchEager() {
return fetchEager;
}
public void setSelections(String selections) {
expressions.addSelections(selections);
}
public void setGroupBy(String groupBy) {
expressions.addGroups(groupBy);
}
@Override
protected void init() {
criteriaBuilder = getEntityManager().getCriteriaBuilder();
metaModel = getEntityManager().getMetamodel();
}
@Override
final protected void prepareQuery(QueryConfig config) {
if (criteriaQuery == null) {
criteriaQuery = criteriaBuilder.createQuery(getResultType());
}
root = criteriaQuery.from(getType());
prepareSelect(criteriaQuery, root, config);
prepareDistinct(config);
prepareGroupBy(criteriaQuery, root, config);
prepareFetch(root, config);
List<Predicate> predicates = preparePredicates();
if (predicates != null && predicates.size() > 0) {
criteriaQuery.where((config.isDisjunction()) ? orPredicates(predicates) : andPredicates(predicates));
}
}
protected void prepareSelect(CriteriaQuery<R> criteriaQuery, Root<T> root, QueryConfig config) {
}
protected void prepareDistinct(QueryConfig config) {
criteriaQuery.distinct(config.isDistinct());
}
protected void prepareGroupBy(CriteriaQuery<R> criteriaQuery, Root<T> root, QueryConfig config) {
}
@Override
final protected void prepareOrderBy(QueryConfig config) {
prepareOrderBy(criteriaQuery, root, config);
}
protected void prepareOrderBy(CriteriaQuery<R> criteriaQuery, Root<T> root, QueryConfig config) {
}
protected List<Predicate> preparePredicates() {
return null;
}
protected void prepareFetch(Root<T> root, QueryConfig config) {
}
@Override
protected TypedQuery<R> prepareTypedQuery(QueryConfig config) {
return getEntityManager().createQuery(criteriaQuery);
}
@Override
protected void prepareResultCount(QueryConfig config) {
CriteriaQuery<Long> resultCountCriteriaQuery = criteriaBuilder.createQuery(Long.class);
// TODO: works only T = R
if (config.isDistinct()) {
resultCountCriteriaQuery.select(criteriaBuilder.countDistinct(resultCountCriteriaQuery.from(getType())));
} else {
resultCountCriteriaQuery.select(criteriaBuilder.count(resultCountCriteriaQuery.from(getType())));
}
if (criteriaQuery.getRestriction() != null) {
resultCountCriteriaQuery.where(criteriaQuery.getRestriction());
}
resultCountQuery = getEntityManager().createQuery(resultCountCriteriaQuery);
};
@Override
public void executeResultCount() {
if (resultCountQuery != null) {
setResultCount(resultCountQuery.getSingleResult());
}
}
/**
*
* @param path
* @param type
* @param entity
* @return
*/
protected List<Predicate> preparePredicates(Object entity) {
return preparePredicates(root, metaModel.managedType(getType()), entity);
}
/**
*
* @param path
* @param type
* @param entity
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected List<Predicate> preparePredicates(Path<?> path, ManagedType type, Object managedObject) {
List<Predicate> predicates = new ArrayList<Predicate>();
// TODO: this is a temporary workaround for datanucleus,
// getSingularAttribute is not working correctly together with
// MappedSuperclass
Set<SingularAttribute> singularAttributes = new HashSet<SingularAttribute>();
Set<Attribute> attributes = type.getAttributes();
for (Attribute attribute : attributes) {
if (attribute instanceof SingularAttribute) {
singularAttributes.add((SingularAttribute) attribute);
}
}
for (SingularAttribute attribute : singularAttributes) {
// exclude properties
if (getConfig().getExcludeProperties().contains(attribute)) {
continue;
}
Object value = JpaHelper.getValue(managedObject, attribute);
if (value == null) {
if (!getConfig().isExcludeZeroes()) {
predicates.add(path.get(attribute).isNull());
}
continue;
}
if (isAttributeSingularManagedType(attribute)) {
predicates.addAll(preparePredicates(path.get(attribute), (ManagedType) attribute.getType(), value));
} else {
predicates.add(preparePredicate(path.get(attribute), value));
}
}
return predicates;
}
/**
*
* @param restrictions
* @return
*/
protected List<Predicate> preparePredicates(Map<String, Object> restrictions) {
return preparePredicates(root, restrictions);
}
/**
* Convenience method to get a single (conjunction) predicate for
*
* @param restrictions
* @return
*/
protected Predicate preparePredicate(Map<String, Object> restrictions) {
return preparePredicate(restrictions, true);
}
/**
*
* @param restrictions
* @param disjunction
* @return
*/
protected Predicate preparePredicate(Map<String, Object> restrictions, boolean AND) {
return (AND) ? andPredicates(preparePredicates(restrictions)) : orPredicates(preparePredicates(restrictions));
}
/**
*
* @param path
* @param restrictions
* @return
*/
private List<Predicate> preparePredicates(Path<?> path, Map<String, Object> restrictions) {
List<Predicate> predicates = new ArrayList<Predicate>();
for (String property : restrictions.keySet()) {
predicates.add(preparePredicate(path, property, restrictions.get(property)));
}
return predicates;
}
/**
*
* @param path
* @param property
* @param value
* @return
*/
private Predicate preparePredicate(Path<?> path, String property, Object value) {
path = getPath(path, property);
if (value == null) {
return path.isNull();
}
// openjpa and datanucleus do not support embeddables as restriction
// directly
// TODO: verify whether this is working now
if (JpaHelper.isJpaProviderOpenJpa(getEntityManager())
|| JpaHelper.isJpaProviderDataNucleus(getEntityManager())) {
for (ManagedType<?> embeddableType : metaModel.getEmbeddables()) {
if (embeddableType.getJavaType().equals(value.getClass())) {
return andPredicates(preparePredicates(path, embeddableType, value));
}
}
}
return preparePredicate(path, value);
}
/**
*
* @param path
* @param value
* @return
*/
protected Predicate preparePredicate(Path<?> path, Object value) {
if (value instanceof String) {
return preparePredicate(path, (String) value);
}
return criteriaBuilder.equal(path, value);
}
/**
*
* @param path
* @param value
* @return
*/
@SuppressWarnings("unchecked")
private Predicate preparePredicate(Path<?> path, String value) {
// TODO: workaround for datanucleus
// use unchecked cast, because datanucleus 2.x has not implemented
// path.as
// and 3.x has still problems
if (path.getJavaType() != String.class) {
throw new QueryConfigException("Path is not of type string.");
}
Expression<String> stringExpression = (getConfig().isIgnoreCase()) ? criteriaBuilder.upper((Path<String>) path)
: (Path<String>) path;
String stringValue = (getConfig().isIgnoreCase()) ? value.toString().toUpperCase() : value.toString();
return (getConfig().isEnableLike()) ? criteriaBuilder.like(stringExpression, stringValue) : criteriaBuilder
.equal(stringExpression, stringValue);
}
/**
*
* @param predicates
* @return
*/
protected Predicate conjunctPredicates(List<Predicate> predicates) {
if (predicates == null || predicates.size() == 0)
return null;
Predicate predicate = criteriaBuilder.conjunction();
for (Predicate p : predicates) {
predicate = criteriaBuilder.and(predicate, p);
}
return predicate;
}
/**
*
* @param predicates
* @return
*/
protected Predicate disjunctPredicates(List<Predicate> predicates) {
if (predicates == null || predicates.size() == 0)
return null;
Predicate predicate = criteriaBuilder.disjunction();
for (Predicate p : predicates) {
predicate = criteriaBuilder.or(predicate, p);
}
return predicate;
}
/**
*
* @param predicates
* @return
*/
protected Predicate andPredicates(List<Predicate> predicates) {
if (predicates == null || predicates.size() == 0)
return null;
Predicate predicate = null;
for (Predicate p : predicates) {
if (predicate == null)
predicate = p;
else
predicate = criteriaBuilder.and(predicate, p);
}
return predicate;
}
/**
*
* @param predicates
* @return
*/
protected Predicate orPredicates(List<Predicate> predicates) {
if (predicates == null || predicates.size() == 0)
return null;
Predicate predicate = null;
for (Predicate p : predicates) {
if (predicate == null)
predicate = p;
else {
predicate = criteriaBuilder.or(predicate, p);
}
}
return predicate;
}
protected Path<?> getPath(Path<?> fromPath, String property) {
if (property == null) {
return null;
}
if (isNestedProperty(property)) {
return getPathForNestedProperty(fromPath, property);
}
return getPathForSimpleProperty(fromPath, property);
}
private boolean isNestedProperty(String property) {
if (property.contains(".")) {
return true;
}
return false;
}
private Path<?> getPathForSimpleProperty(Path<?> fromPath, String simpleProperty) {
return fromPath.get(simpleProperty);
}
private Path<?> getPathForNestedProperty(Path<?> fromPath, String nestedProperties) {
Path<?> path = fromPath;
List<String> properties = Arrays.asList(nestedProperties.split("\\."));
for (Iterator<String> iterator = properties.iterator(); iterator.hasNext();) {
String property = iterator.next();
path = getPathForSimpleProperty(path, property);
if (isExplicitJoinNeeded(path) && iterator.hasNext()) {
path = getExplicitJoinForPath(path, property);
}
}
return path;
}
private boolean isExplicitJoinNeeded(Path<?> path) {
// eclipselink and openjpa do not need an explicit join
if (!JpaHelper.isJpaProviderHibernate(getEntityManager())
&& !JpaHelper.isJpaProviderDataNucleus(getEntityManager())) {
return false;
}
if (isCollection(path) || isMap(path)) {
return true;
}
return false;
}
private boolean isCollection(Path<?> path) {
Class<?> type = path.getJavaType();
if (Collection.class.isAssignableFrom(type)) {
return true;
}
return false;
}
private boolean isMap(Path<?> path) {
Class<?> type = path.getJavaType();
if (Map.class.isAssignableFrom(type)) {
return true;
}
return false;
}
private Join<?, ?> getExplicitJoinForPath(Path<?> path, String property) {
Path<?> parentPath = path.getParentPath();
if (From.class.isAssignableFrom(parentPath.getClass())) {
return ((From<?, ?>) parentPath).join(property);
}
throw new QueryConfigException("Can't create a explicit join for property " + property);
}
/**
*
* @param type
* @return
*/
@SuppressWarnings("rawtypes")
protected Attribute<?, ?> getAttribute(ManagedType<?> type, String property) {
if (type == null || property == null) {
return null;
}
if (!property.contains(".")) {
return type.getAttribute(property);
}
Attribute<?, ?> attribute = type.getAttribute(property.substring(0, property.indexOf(".")));
if (!attribute.isCollection() && isAttributeSingularManagedType(attribute)) {
return getAttribute((ManagedType<?>) ((SingularAttribute) attribute).getType(),
property.substring(property.indexOf(".") + 1));
}
return attribute;
}
@SuppressWarnings({ "unused" })
private boolean isManagedType(Class<?> type) {
for (ManagedType<?> managedType : metaModel.getManagedTypes()) {
if (managedType.getJavaType().equals(type)) {
return true;
}
}
return false;
}
private boolean isAttributeSingularManagedType(Attribute<?, ?> attribute) {
if (PersistentAttributeType.EMBEDDED.equals(attribute.getPersistentAttributeType())
|| PersistentAttributeType.MANY_TO_ONE.equals(attribute.getPersistentAttributeType())
|| PersistentAttributeType.ONE_TO_ONE.equals(attribute.getPersistentAttributeType())) {
return true;
}
return false;
}
}