/* * Copyright 2009-2013 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.jdal.dao.jpa; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.persistence.EntityManager; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.PersistenceUnitUtil; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Fetch; 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.criteria.Selection; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.Attribute.PersistentAttributeType; import javax.persistence.metamodel.CollectionAttribute; import javax.persistence.metamodel.EntityType; import org.apache.commons.lang.StringUtils; import org.jdal.beans.PropertyUtils; import org.springframework.beans.PropertyAccessor; import org.springframework.beans.PropertyAccessorFactory; /** * Utility class for dealing with JPA API * * @author Jose Luis Martin * @since 1.1 */ public abstract class JpaUtils { private static String ALIAS_PATTERN_STRING = "(?<=from)\\s+(?:\\S+)\\s+(?:as\\s+)*(\\w*)"; private static Pattern ALIAS_PATTERN = Pattern.compile(ALIAS_PATTERN_STRING, Pattern.CASE_INSENSITIVE); private static String FROM_PATTERN_STRING = "(from.*+)"; private static Pattern FROM_PATTERN = Pattern.compile(FROM_PATTERN_STRING, Pattern.CASE_INSENSITIVE); private static volatile int aliasCount = 0; /** * Result count from a CriteriaQuery * @param em Entity Manager * @param criteria Criteria Query to count results * @return row count */ public static <T> Long count(EntityManager em, CriteriaQuery<T> criteria) { return em.createQuery(countCriteria(em, criteria)).getSingleResult(); } /** * Create a row count CriteriaQuery from a CriteriaQuery * @param em entity manager * @param criteria source criteria * @return row count CriteriaQuery */ public static <T> CriteriaQuery<Long> countCriteria(EntityManager em, CriteriaQuery<T> criteria) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<Long> countCriteria = builder.createQuery(Long.class); copyCriteriaWithoutSelectionAndOrder(criteria, countCriteria, false); Expression<Long> countExpression; if (criteria.isDistinct()) { countExpression = builder.countDistinct(findRoot(countCriteria, criteria.getResultType())); } else { countExpression = builder.count(findRoot(countCriteria, criteria.getResultType())); } return countCriteria.select(countExpression); } /** * Gets The result alias, if none set a default one and return it * @param selection * @return root alias or generated one */ public static synchronized <T> String getOrCreateAlias(Selection<T> selection) { // reset alias count if (aliasCount > 1000) aliasCount = 0; String alias = selection.getAlias(); if (alias == null) { alias = "JDAL_generatedAlias" + aliasCount++; selection.alias(alias); } return alias; } /** * Find Root of result type * @param query criteria query * @return the root of result type or null if none */ public static <T> Root<T> findRoot(CriteriaQuery<T> query) { return findRoot(query, query.getResultType()); } /** * Find the Root with type class on CriteriaQuery Root Set * @param <T> root type * @param query criteria query * @param clazz root type * @return Root<T> of null if none */ @SuppressWarnings("unchecked") public static <T> Root<T> findRoot(CriteriaQuery<?> query, Class<T> clazz) { for (Root<?> r : query.getRoots()) { if (clazz.equals(r.getJavaType())) { return (Root<T>) r.as(clazz); } } return null; } /** * Find the Root with type class on CriteriaQuery Root Set or creating new one if Root is not present * @param <T> root type * @param query criteria query * @param clazz root type * @return Root<T> */ public static <T> Root<T> findOrCreateRoot(CriteriaQuery<?> query, Class<T> clazz){ Root<T> root = findRoot(query, clazz); return root != null ? root : query.from(clazz); } /** * Find Joined Root of type clazz * @param <T> * @param query the criteria query * @param rootClass the root class * @param joinClass the join class * @return the Join */ @SuppressWarnings("unchecked") public static <T, K> Join<T,K> findJoinedType(CriteriaQuery<T> query, Class<T> rootClass, Class<K> joinClass) { Root<T> root = findRoot(query, rootClass); Join<T,K> join = null; for (Join<T,?> j : root.getJoins()) { if (j.getJoinType().equals(joinClass)) { join = (Join<T, K>) j; } } return join; } /** * Gets a Path from Path using property path * @param path the base path * @param propertyPath property path String like "customer.order.price" * @return a new Path for property */ @SuppressWarnings("unchecked") public static <T> Path<T> getPath(Path<?> path, String propertyPath) { if (StringUtils.isEmpty(propertyPath)) return (Path<T>) path; String name = StringUtils.substringBefore(propertyPath, PropertyUtils.PROPERTY_SEPARATOR); Path<?> p = path.get(name); return getPath(p, StringUtils.substringAfter(propertyPath, PropertyUtils.PROPERTY_SEPARATOR)); } /** * Create a count query string from a query string * @param queryString string to parse * @return the count query string */ public static String createCountQueryString(String queryString) { return queryString.replaceFirst("^.*(?i)from", "select count (*) from "); } /** * Gets the alias of root entity of JQL query * @param queryString JQL query * @return alias of root entity. */ public static String getAlias(String queryString) { Matcher m = ALIAS_PATTERN.matcher(queryString); return m.find() ? m.group(1) : null; } /** * Add order by clause to queryString * @param queryString JPL Query String * @param propertyPath Order properti * @param asc true if ascending * @return JQL Query String with Order clause appened. */ public static String addOrder(String queryString, String propertyPath, boolean asc ) { if (StringUtils.containsIgnoreCase(queryString, "order by")) { return queryString; } StringBuilder sb = new StringBuilder(queryString); sb.append(" ORDER BY "); sb.append(getAlias(queryString)); sb.append("."); sb.append(propertyPath); sb.append(" "); sb.append(asc ? "ASC" : "DESC"); return sb.toString(); } /** * Gets Query String for selecting primary keys * @param queryString the original query * @param name primary key name * @return query string */ public static String getKeyQuery(String queryString, String name) { Matcher m = FROM_PATTERN.matcher(queryString); if (m.find()) { StringBuilder sb = new StringBuilder("SELECT "); sb.append(getAlias(queryString)); sb.append("."); sb.append(name); sb.append(" "); sb.append(m.group()); return sb.toString(); } return null; } /** * Copy Criteria without Selection. * @param from source Criteria. * @param to destination Criteria. */ public static void copyCriteriaNoSelection(CriteriaQuery<?> from, CriteriaQuery<?> to) { copyCriteriaWithoutSelectionAndOrder(from, to, true); to.orderBy(from.getOrderList()); } /** * Copy criteria without selection and order. * @param from source Criteria. * @param to destination Criteria. */ private static void copyCriteriaWithoutSelectionAndOrder( CriteriaQuery<?> from, CriteriaQuery<?> to, boolean copyFetches) { if (isEclipseLink(from) && from.getRestriction() != null) { // EclipseLink adds roots from predicate paths to critera. Skip copying // roots as workaround. } else { // Copy Roots for (Root<?> root : from.getRoots()) { Root<?> dest = to.from(root.getJavaType()); dest.alias(getOrCreateAlias(root)); copyJoins(root, dest); if (copyFetches) copyFetches(root, dest); } } to.groupBy(from.getGroupList()); to.distinct(from.isDistinct()); if (from.getGroupRestriction() != null) to.having(from.getGroupRestriction()); Predicate predicate = from.getRestriction(); if (predicate != null) to.where(predicate); } private static boolean isEclipseLink(CriteriaQuery<?> from) { return from.getClass().getName().contains("org.eclipse.persistence"); } public static <T> void copyCriteria(CriteriaQuery<T> from, CriteriaQuery<T> to) { copyCriteriaNoSelection(from, to); to.select(from.getSelection()); } /** * Copy Joins * @param from source Join * @param to destination Join */ public static void copyJoins(From<?, ?> from, From<?, ?> to) { for (Join<?, ?> j : from.getJoins()) { Join<?, ?> toJoin = to.join(j.getAttribute().getName(), j.getJoinType()); toJoin.alias(getOrCreateAlias(j)); copyJoins(j, toJoin); } } /** * Copy Fetches * @param from source From * @param to destination From */ public static void copyFetches(From<?, ?> from, From<?, ?> to) { for (Fetch<?, ?> f : from.getFetches()) { Fetch<?, ?> toFetch = to.fetch(f.getAttribute().getName()); copyFetches(f, toFetch); } } /** * Copy Fetches * @param from source Fetch * @param to dest Fetch */ public static void copyFetches(Fetch<?, ?> from, Fetch<?, ?> to) { for (Fetch<?, ?> f : from.getFetches()) { Fetch<?, ?> toFetch = to.fetch(f.getAttribute().getName()); // recursively copy fetches copyFetches(f, toFetch); } } /** * Test if the path exists * @param path path to test on * @param propertyPath path to test * @return true if path exists */ public static boolean hasPath(Path<?> path, String propertyPath) { try { getPath(path, propertyPath); return true; } catch (Exception e) { // Hibernate throws NPE here. return false; } } /** * Initialize a entity. * @param em entity manager to use * @param entity entity to initialize * @param depth max depth on recursion */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static void initialize(EntityManager em, Object entity, int depth) { // return on nulls, depth = 0 or already initialized objects if (entity == null || depth == 0) { return; } PersistenceUnitUtil unitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil(); EntityType entityType = em.getMetamodel().entity(entity.getClass()); Set<Attribute> attributes = entityType.getDeclaredAttributes(); Object id = unitUtil.getIdentifier(entity); if (id != null) { Object attached = em.find(entity.getClass(), unitUtil.getIdentifier(entity)); for (Attribute a : attributes) { if (!unitUtil.isLoaded(entity, a.getName())) { if (a.isCollection()) { intializeCollection(em, entity, attached, a, depth); } else if(a.isAssociation()) { intialize(em, entity, attached, a, depth); } } } } } /** * Initialize entity attribute * @param em * @param entity * @param a * @param depth */ @SuppressWarnings("rawtypes") private static void intialize(EntityManager em, Object entity, Object attached, Attribute a, int depth) { Object value = PropertyAccessorFactory.forDirectFieldAccess(attached).getPropertyValue(a.getName()); if (!em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(value)) { em.refresh(value); } PropertyAccessorFactory.forDirectFieldAccess(entity).setPropertyValue(a.getName(), value); initialize(em, value, depth - 1); } /** * Initialize collection * @param em * @param entity * @param a * @param i */ @SuppressWarnings("rawtypes") private static void intializeCollection(EntityManager em, Object entity, Object attached, Attribute a, int depth) { PropertyAccessor accessor = PropertyAccessorFactory.forDirectFieldAccess(attached); Collection c = (Collection) accessor.getPropertyValue(a.getName()); for (Object o : c) initialize(em, o, depth -1); PropertyAccessorFactory.forDirectFieldAccess(entity).setPropertyValue(a.getName(), c); } /** * Get all attributes where type or element type is assignable from class and has persistent type * @param type entity type * @param persistentType persistentType * @param clazz class * @return Set with matching attributes */ public static Set<Attribute<?, ?>> getAttributes(EntityType<?> type, PersistentAttributeType persistentType, Class<?> clazz) { Set<Attribute<?, ?>> attributes = new HashSet<Attribute<?, ?>>(); for (Attribute<?, ?> a : type.getAttributes()) { if (a.getPersistentAttributeType() == persistentType && isTypeOrElementType(a, clazz)) { attributes.add(a); } } return attributes; } /** * Get all attributes of type by persistent type * @param type * @param persistentType * @return a set with all attributes of type with persistent type persistentType. */ public static Set<Attribute<?, ?>> getAttributes(EntityType<?> type, PersistentAttributeType persistentType) { return getAttributes(type, persistentType, Object.class); } /** * Test if attribute is type or in collections has element type * @param attribute attribute to test * @param clazz Class to test * @return true if clazz is asignable from type or element type */ public static boolean isTypeOrElementType(Attribute<?, ?> attribute, Class<?> clazz) { if (attribute.isCollection()) { return clazz.isAssignableFrom(((CollectionAttribute<?, ?>) attribute).getBindableJavaType()); } return clazz.isAssignableFrom(attribute.getJavaType()); } /** * Gets the mappedBy value from an attribute * @param attribute attribute * @return mappedBy value or null if none. */ public static String getMappedBy(Attribute<?, ?> attribute) { String mappedBy = null; if (attribute.isAssociation()) { Annotation[] annotations = null; Member member = attribute.getJavaMember(); if (member instanceof Field) { annotations = ((Field) member).getAnnotations(); } else if (member instanceof Method) { annotations = ((Method) member).getAnnotations(); } for (Annotation a : annotations) { if (a.annotationType().equals(OneToMany.class)) { mappedBy = ((OneToMany) a).mappedBy(); break; } else if (a.annotationType().equals(ManyToMany.class)) { mappedBy = ((ManyToMany) a).mappedBy(); break; } else if (a.annotationType().equals(OneToOne.class)) { mappedBy = ((OneToOne) a).mappedBy(); break; } } } return "".equals(mappedBy) ? null : mappedBy; } }