package org.jbpm.query.jpa.impl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import javax.persistence.Entity; import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.criteria.AbstractQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.From; import javax.persistence.criteria.Order; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SingularAttribute; import org.jbpm.query.jpa.data.QueryCriteria; import org.jbpm.query.jpa.data.QueryWhere; import org.jbpm.query.jpa.data.QueryWhere.QueryCriteriaType; import org.kie.api.runtime.manager.audit.VariableInstanceLog; import org.kie.internal.query.QueryParameterIdentifiers; public abstract class QueryCriteriaUtil { private Map<Class, Map<String, Attribute>> criteriaAttributes; private final AtomicBoolean criteriaAttributesInitialized = new AtomicBoolean(false); public QueryCriteriaUtil(Map<Class, Map<String, Attribute>> criteriaAttributes) { initialize(criteriaAttributes); } protected QueryCriteriaUtil() { // for the AbstractTaskQueryCriteriaUtil } protected void initialize(Map<Class, Map<String, Attribute>> criteriaAttributes) { this.criteriaAttributes = criteriaAttributes; } protected Map<Class, Map<String, Attribute>> getCriteriaAttributes() { if( ! criteriaAttributesInitialized.get() ) { if( initializeCriteriaAttributes() ) { criteriaAttributesInitialized.set(true); } else { throw new IllegalStateException("Queries can not be performed if no persistence unit has been initalized!"); } } return criteriaAttributes; } // List cast conversion methods ----------------------------------------------------------------------------------------------- @SuppressWarnings("unchecked") public static <C,I> List<I> convertListToInterfaceList( List<C>internalResult, Class<I> interfaceType ) { List<I> result = new ArrayList<I>(internalResult.size()); for( C element : internalResult ) { result.add((I) element); } return result; } // constructor helper methods ------------------------------------------------------------------------------------------------- public static void addCriteria( Map<Class, Map<String, Attribute>> criteriaAttributes, String listId, Attribute attr ) { Class table = attr.getJavaMember().getDeclaringClass(); addCriteria(criteriaAttributes, listId, table, attr); } public static void addCriteria( Map<Class, Map<String, Attribute>> criteriaAttributes, String listId, Class table, Attribute attr ) { Map<String, Attribute> tableAttrs = criteriaAttributes.get(table); if( tableAttrs == null ) { tableAttrs = new ConcurrentHashMap<String, Attribute>(1); criteriaAttributes.put(table, tableAttrs); } Attribute previousMapping = tableAttrs.put(listId, attr); assert previousMapping == null : "Previous mapping existed for [" + listId + "]!"; } // abstract methods ----------------------------------------------------------------------------------------------------------- /** * The implementation of this method should be synchronized! */ protected abstract boolean initializeCriteriaAttributes(); protected abstract CriteriaBuilder getCriteriaBuilder(); // @formatter:on // query logic ---------------------------------------------------------------------------------------------------------------- /** * This method takes the high-level steps needed in order to create a JPA {@link CriteriaQuery}. * <ol> * <li>A {@link CriteriaBuilder} and {@link CriteriaQuery} instance are created.</li> * <li>The tables being selected from are defined in the query.</li> * <li>The {@link CriteriaQuery} instance is filled using the criteria in the {@link QueryWhere} instance</li> * <li>A JPA {@link Query} instance is created</li> * <li>The meta criteria (max results, offset) are applied to the query</li> * <li>The results are retrieved and returned</li> * </ol> * @param queryWhere a {@link QueryWhere} instance containing the query criteria * @param queryType The type ({@link Class}) of the result * @return The result of the query, a {@link List}. */ public <T> List<T> doCriteriaQuery( QueryWhere queryWhere, Class<T> queryType ) { // 1. create builder and query instances CriteriaBuilder builder = getCriteriaBuilder(); CriteriaQuery<T> criteriaQuery = builder.createQuery(queryType); // query base; criteriaQuery.select(criteriaQuery.from(queryType)); fillCriteriaQuery(criteriaQuery, queryWhere, builder, queryType); List<T> result = createQueryAndCallApplyMetaCriteriaAndGetResult(queryWhere, criteriaQuery, builder); return result; } // query logic ---------------------------------------------------------------------------------------------------------------- /** * This is the main ("highest"? "most abstract"?) method that is used to create a {@link CriteriaQuery} from a {@link QueryWhere} instance. * * @param query The (empty) {@link CriteriaQuery} that will be filled using the {@link QueryCriteria} and other information in the {@link QueryWhere} instance * @param queryWhere The {@link QueryWhere} instance, with abstract information that should be added to the {@link CriteriaQuery} * @param builder The {@link CriteriaBuilder}, helpful when creating {@link Predicate}s to add to the {@link CriteriaQuery} * @param queryType The {@link Class} indicating the main {@link Root} of the {@link CriteriaQuery} */ protected <R,T> void fillCriteriaQuery( CriteriaQuery<R> query, QueryWhere queryWhere, CriteriaBuilder builder, Class<T> queryType ) { Predicate queryPredicate = createPredicateFromCriteriaList(query, builder, queryType, queryWhere.getCriteria(), queryWhere ); if( queryPredicate != null ) { query.where(queryPredicate); } if( queryWhere.getAscOrDesc() != null ) { String orderByListId = queryWhere.getOrderByListId(); assert orderByListId != null : "Ascending boolean is set but no order by list Id has been specified!"; Expression orderByPath = getOrderByExpression(query, queryType, orderByListId); Order order; if( queryWhere.getAscOrDesc() ) { order = builder.asc(orderByPath); } else { order = builder.desc(orderByPath); } query.orderBy(order); } } /** * This method is contains the setup steps for creating and assembling {@link Predicate} instances * from the information in a {@link List} of {@link QueryCriteria} instances. * </p> * The steps taken when assembling a {@link Predicate} are the following: * <ol> * <li>Separate the given {@link List} of {@link QueryCriteria} into an intersection and disjunction (union) list.</li> * <li>Combine separate "range" {@link QueryCriteria} that apply to the same listId</li> * <li>Call the {@link #createPredicateFromCriteriaList(CriteriaQuery, List, CriteriaBuilder, Class, boolean)} * method on disjunction criteria list and on the intersection criteria list</li> * <li>Take the result of the previous step and appropriately combine the returned {@link Predicate} instances into a * final {@link Predicate} instance that is then returned.</li> * </ol> * @param query The {@link CriteriaQuery} instance that we're assembling {@link Predicate} instances for * @param inputCriteriaList The list of {@link QueryCriteria} instances that will be processed * @param builder A {@link CriteriaBuilder} instance to help us build {@link Predicate} instances * @param resultType The {@link Class} (type) of the result, given so that later methods can use it * @return A {@link Predicate} instance based on the given {@link QueryCriteria} list */ private <R,T> Predicate createPredicateFromCriteriaList( CriteriaQuery<R> query, CriteriaBuilder builder, Class<T> resultType, List<QueryCriteria> inputCriteriaList, QueryWhere queryWhere ) { Predicate queryPredicate = null; if( inputCriteriaList.size() > 1 ) { List<Predicate> predicateList = new LinkedList<Predicate>(); QueryCriteria previousCriteria = null; QueryCriteria firstCriteria = null; List<QueryCriteria> currentIntersectingCriteriaList = new LinkedList<QueryCriteria>(); int i = 0; for( QueryCriteria criteria : inputCriteriaList ) { assert i++ != 0 || criteria.isFirst() : "First criteria is not flagged as first!"; if( criteria.isFirst() ) { firstCriteria = previousCriteria = criteria; continue; } else if( firstCriteria != null ) { if( criteria.isUnion() ) { Predicate predicate = createPredicateFromSingleOrGroupCriteria(query, builder, resultType, previousCriteria, queryWhere); predicateList.add(predicate); } else { currentIntersectingCriteriaList.add(firstCriteria); } firstCriteria = null; } if( criteria.isUnion() ) { // AND has precedence over OR: // If 'criteria' is now OR and there was a list (currentIntersectingCriteriaList) of AND criteria before 'criteria' // - create a predicate from the AND criteria if( previousCriteria != null && ! previousCriteria.isUnion() && ! currentIntersectingCriteriaList.isEmpty() ) { Predicate predicate = createPredicateFromIntersectingCriteriaList(query, builder, resultType, currentIntersectingCriteriaList, queryWhere ); assert predicate != null : "Null predicate when evaluating intersecting criteria [" + criteria.toString() + "]"; predicateList.add(predicate); // - new (empty) current intersecting criteria list currentIntersectingCriteriaList = new LinkedList<QueryCriteria>(); } // Process the current union criteria Predicate predicate = createPredicateFromSingleOrGroupCriteria(query, builder, resultType, criteria, queryWhere); assert predicate != null : "Null predicate when evaluating union criteria [" + criteria.toString() + "]"; predicateList.add(predicate); } else { currentIntersectingCriteriaList.add(criteria); } previousCriteria = criteria; } if( ! currentIntersectingCriteriaList.isEmpty() ) { Predicate predicate = createPredicateFromIntersectingCriteriaList(query, builder, resultType, currentIntersectingCriteriaList, queryWhere ); predicateList.add(predicate); } assert ! predicateList.isEmpty() : "The predicate list should not (can not?) be empty here!"; if( predicateList.size() == 1 ) { queryPredicate = predicateList.get(0); } else { Predicate [] predicates = predicateList.toArray(new Predicate[predicateList.size()]); queryPredicate = builder.or(predicates); } } else if( inputCriteriaList.size() == 1 ) { QueryCriteria singleCriteria = inputCriteriaList.get(0); queryPredicate = createPredicateFromSingleOrGroupCriteria(query, builder, resultType, singleCriteria, queryWhere); } return queryPredicate; } /** * This method is necessary because the AND operator in SQL has precedence over the OR operator. * </p> * That means that intersecting criteria should always be grouped together (and processed first, basically), which is essentially * what this method does. * * @param query The {@link CriteriaQuery} that is being built * @param intersectingCriteriaList The list of intersecting (ANDed) {@link QueryCriteria} * @param builder The {@link CriteriaBuilder} builder instance * @param queryType The (persistent {@link Entity}) {@link Class} that we are querying on * @return A {@link Predicate} created on the basis of the given {@link List} of {@link QueryCriteria} */ private <R,T> Predicate createPredicateFromIntersectingCriteriaList(CriteriaQuery<R> query, CriteriaBuilder builder, Class<T> queryType, List<QueryCriteria> intersectingCriteriaList, QueryWhere queryWhere ) { combineIntersectingRangeCriteria(intersectingCriteriaList); assert intersectingCriteriaList.size() > 0 : "Empty list of currently intersecting criteria!"; Predicate [] intersectingPredicates = new Predicate[intersectingCriteriaList.size()]; int i = 0; for( QueryCriteria intersectingCriteria : intersectingCriteriaList ) { Predicate predicate = createPredicateFromSingleOrGroupCriteria(query, builder, queryType, intersectingCriteria, queryWhere ); assert predicate != null : "Null predicate when evaluating individual intersecting criteria [" + intersectingCriteria.toString() + "]"; intersectingPredicates[i++] = predicate; } Predicate predicate; if( intersectingPredicates.length > 1 ) { predicate = builder.and(intersectingPredicates); } else { predicate = intersectingPredicates[0]; } return predicate; } /** * When there are multiple range criteria in a query (in the same group), it is more efficient to * submit a JPA "between" criteria than 2 different criteria. * * @param intersectionCriteria A {@link List} of {@link QueryCriteria} instances that are range criteria */ @SuppressWarnings("unchecked") private void combineIntersectingRangeCriteria(List<QueryCriteria> intersectionCriteria) { Map<String, QueryCriteria> intersectingRangeCriteria = new HashMap<String, QueryCriteria>(); Iterator<QueryCriteria> iter = intersectionCriteria.iterator(); while( iter.hasNext() ) { QueryCriteria criteria = iter.next(); if( QueryCriteriaType.RANGE.equals(criteria.getType()) ) { QueryCriteria previousCriteria = intersectingRangeCriteria.put(criteria.getListId(), criteria); if( previousCriteria != null ) { Object [] prevCritValues, thisCritValues; assert previousCriteria.hasValues() || previousCriteria.hasDateValues() : "Previous criteria has neither values nor date values!"; assert !(previousCriteria.hasValues() && previousCriteria.hasDateValues()) : "Previous criteria has BOTH values and date values!"; assert (previousCriteria.hasValues() && criteria.hasValues()) || (previousCriteria.hasDateValues() && criteria.hasDateValues()) : "Previous and current criteria should have either both have values or both have date values!"; boolean dateValues = false; if( previousCriteria.hasValues() ) { prevCritValues = previousCriteria.getValues().toArray(); thisCritValues = criteria.getValues().toArray(); } else { dateValues = true; prevCritValues = previousCriteria.getDateValues().toArray(); thisCritValues = criteria.getDateValues().toArray(); } List values = dateValues ? previousCriteria.getDateValues() : previousCriteria.getValues(); if( prevCritValues[0] == null && thisCritValues[1] == null ) { values.set(0, thisCritValues[0]); intersectingRangeCriteria.put(previousCriteria.getListId(), previousCriteria); iter.remove(); } else if( prevCritValues[1] == null && thisCritValues[0] == null ) { values.set(1, thisCritValues[1]); intersectingRangeCriteria.put(previousCriteria.getListId(), previousCriteria); iter.remove(); } } } } } /** * Depending on whether or not the given {@link QueryCriteria} is a group criteria (which then contains a {@link List}<{@link QueryCriteria}>) * or a single {@link QueryCriteria}, the correct method to process the given {@link QueryCriteria} is called. * * @param query The {@link CriteriaQuery} that is being built * @param criteria The {@link QueryCriteria} instance * @param builder The {@link CriteriaBuilder} builder instance * @param queryType The (persistent {@link Entity}) {@link Class} that we are querying on * @return A {@link Predicate} created on the basis of the given {@link QueryCriteria} instance */ private <R,T> Predicate createPredicateFromSingleOrGroupCriteria(CriteriaQuery<R> query, CriteriaBuilder builder, Class<T> queryType, QueryCriteria criteria, QueryWhere queryWhere ) { Predicate predicate; if( criteria.isGroupCriteria() ) { assert ! criteria.hasValues() : "Criteria has both subcriteria (group criteria) and values! [" + criteria.toString() + "]"; predicate = createPredicateFromCriteriaList(query, builder, queryType, criteria.getCriteria(), queryWhere ); } else { assert ! criteria.hasCriteria() || Integer.parseInt(criteria.getListId()) < 0 : "Criteria has both values and subcriteria (group criteria)! [" + criteria.toString() + "]"; predicate = createPredicateFromSingleCriteria(query, builder, queryType, criteria, queryWhere); } return predicate; } /** * This method is the main method for creating a {@link Predicate} from a (non-group) {@link QueryCriteria} instance. * </p> * If it can not figure out how to create a {@link Predicate} from the given {@link QueryCriteria} instance, * then the (abstract) {@link #implSpecificCreatePredicateFromSingleCriteria(CriteriaQuery, QueryCriteria, CriteriaBuilder, Root, Class)} * method is called. * * @param query The {@link CriteriaQuery} that is being built * @param criteria The given {@link QueryCriteria} instance * @param builder The {@link CriteriaBuilder} builder instance * @param queryType The (persistent {@link Entity}) {@link Class} that we are querying on * @return A {@link Predicate} created on the basis of the given {@link QueryCriteria} */ private <R,T> Predicate createPredicateFromSingleCriteria( CriteriaQuery<R> query, CriteriaBuilder builder, Class<T> queryType, QueryCriteria criteria, QueryWhere queryWhere) { Predicate predicate = null; assert criteria.hasValues() || criteria.hasDateValues() || Integer.parseInt(criteria.getListId()) < 0 : "No values present for criteria with list id: [" + criteria.getListId() + "]"; String listId = criteria.getListId(); Attribute attr = getCriteriaAttributes().get(queryType).get(listId); if( attr != null ) { Expression entityField = getEntityField(query, listId, attr); predicate = basicCreatePredicateFromSingleCriteria(builder, entityField, criteria); } else { predicate = implSpecificCreatePredicateFromSingleCriteria(query, builder, queryType, criteria, queryWhere ); } return predicate; } /** * This is a helper method to retrieve a particular {@link Root} from a {@link CriteriaQuery} instance * * @param query The {@link CriteriaQuery} instance that we're building * @param queryType The {@link Class} matching the {@link Root} we want * @return The {@link Root} matching the given {@link Class} or null if it's not in the query */ public static <T> Root getRoot(AbstractQuery<T> query, Class queryType) { Root<?> table = null; for( Root<?> root : query.getRoots() ) { if( root.getJavaType().equals(queryType) ) { table = root; break; } } return table; } /** * This method retrieves the entity "field" that can be used as the LHS of a {@link Predicate} * </p> * This method is overridden in some extended {@link QueryCriteriaUtil} implementations * * @param query The {@link CriteriaQuery} that we're building * @param listId The list id of the given {@link QueryCriteria} * @return An {@link Expression} with the {@link Path} to the field represented by the {@link QueryCriteria#getListId()} value */ protected <T> Expression getEntityField(CriteriaQuery<T> query, String listId, Attribute attr) { return defaultGetEntityField(query, listId, attr); } @SuppressWarnings("unchecked") public static <T> Expression defaultGetEntityField(CriteriaQuery<T> query, String listId, Attribute attr) { Expression entityField = null; if( attr != null ) { Class attrType = attr.getDeclaringType().getJavaType(); for( From from : query.getRoots() ) { if( from.getJavaType().equals(attrType) ) { if( attr != null ) { if( attr instanceof SingularAttribute ) { entityField = from.get((SingularAttribute) attr); } else if( attr instanceof PluralAttribute ) { entityField = from.get((PluralAttribute) attr); } else { throw new IllegalStateException("Unexpected attribute type when processing criteria with list id " + listId + ": " + attr.getClass().getName() ); } break; } } } } // if entityField == null, this is because this QueryCriteria is a implementation specific criteria, such as "LAST_VARIABLE_LIST" return entityField; } /** * This method creates the basic types of {@link Predicate} from trivial {@link QueryCriteria} (NORMAL/REGEXP/RANGE). * * @param builder The {@link CriteriaBuilder}, helpful when creating {@link Predicate}s to add to the {@link CriteriaQuery} * @param entityField The {@link Expression} representing a field/column in an entity/table. * @param criteria The {@link QueryCriteria} with the values to use as the RHS of a {@link Predicate} * @return The created {@link Predicate} */ @SuppressWarnings("unchecked") public static Predicate basicCreatePredicateFromSingleCriteria(CriteriaBuilder builder, Expression entityField, QueryCriteria criteria) { Predicate predicate = null; List<Object> parameters = criteria.getParameters(); int numParameters = parameters.size(); assert ! parameters.isEmpty() : "Empty parameters for criteria [" + criteria.toString() + "]"; switch ( criteria.getType() ) { case NORMAL: if( numParameters == 1 ) { Object parameter = parameters.get(0); assert parameter != null : "Null parameter for criteria [" + criteria.toString() + "]"; predicate = builder.equal(entityField, parameter); } else { assert parameters.get(0) != null : "Null 1rst parameter for criteria [" + criteria.toString() + "]"; assert parameters.get(parameters.size()-1) != null : "Null last parameter for criteria [" + criteria.toString() + "]"; predicate = entityField.in(parameters); } break; case REGEXP: List<Predicate> predicateList = new ArrayList<Predicate>(); for( Object param : parameters ) { assert param != null : "Null regular expression parameter for criteria [" + criteria.toString() + "]"; String likeRegex = convertRegexToJPALikeExpression((String) param ); Predicate regexPredicate = builder.like((Expression<String>) entityField, likeRegex); predicateList.add(regexPredicate); } if( predicateList.size() == 1 ) { predicate = predicateList.get(0); } else { Predicate [] predicates = predicateList.toArray(new Predicate[predicateList.size()]); if( criteria.isUnion() ) { predicate = builder.or(predicates); } else { predicate = builder.and(predicates); } } break; case RANGE: assert numParameters > 0 && numParameters < 3: "Range expressions may only contain between 1 and 2 parameters, not " + numParameters + " [" + criteria.toString() + "]"; Object [] rangeObjArr = parameters.toArray(); Class rangeType = rangeObjArr[0] != null ? rangeObjArr[0].getClass() : rangeObjArr[1].getClass(); predicate = createRangePredicate( builder, entityField, rangeObjArr[0], rangeObjArr[1], rangeType); break; default: throw new IllegalStateException("Unknown criteria type: " + criteria.getType()); } assert predicate != null : "No predicate created " + "when evaluating " + criteria.getType().toString().toLowerCase() + " criteria " + "[" + criteria.toString() + "]"; return predicate; } /** * Conver the regex (parameter) string to the JPA like syntax * @param regexInput The parameter string * @return The String in JPA syntax for a regular expressions */ protected static String convertRegexToJPALikeExpression(String regexInput) { return regexInput.replace('*', '%').replace('.', '_'); } /** * Helper method for creating a ranged (between, open-ended) {@link Predicate} * * @param builder The {@link CriteriaBuilder}, helpful when creating {@link Predicate}s to add to the {@link CriteriaQuery} * @param entityField The {@link Expression} representing a field/column in an entity/table. * @param start The start value of the range, or null if open-ended (on the lower end) * @param end The end value of the range, or null if open-ended (on the upper end) * @param rangeType The {@link Class} or the parameter values * @return The created {@link Predicate} */ @SuppressWarnings("unchecked") private static <Y extends Comparable<? super Y>> Predicate createRangePredicate( CriteriaBuilder builder, Expression field, Object start, Object end, Class<Y> rangeType ) { if( start != null && end != null ) { // TODO :asserts! return builder.between(field, (Y) start, (Y) end); } else if ( start != null ) { return builder.greaterThanOrEqualTo(field, (Y) start); } else { return builder.lessThanOrEqualTo(field, (Y) end); } } /** * Some criteria do not directly refer to a field, such as those stored * in the criteria attributes {@link Map<Class, Map<String, Attribute>>} passed * as an argument to the constructor. * </p> * For example, the {@link QueryParameterIdentifiers#LAST_VARIABLE_LIST} criteria specifies * that only the most recent {@link VariableInstanceLog} should be retrieved. * </p> * This method is called from the {@link #createPredicateFromSingleCriteria(CriteriaQuery, QueryCriteria, CriteriaBuilder, Class)} * method when no {@link Attribute} instance can be found in the * instances criteria attributes {@link Map<Class, Map<String, Attribute>>}. * </p> * @param criteriaQuery The {@link CriteriaQuery} instance. * @param criteria The {@link QueryCriteria} instance with the criteria information. * @param criteriaBuilder The {@link CriteriaBuilder} instance used to help create the query predicate. * @param queryType The {@link Class} of the query, used to identify the type of query * @param resultType The {@link Class} of the result being request. * @return A {@link Predicate} representin the information in the {@link QueryCriteria} instance. */ // @formatter:off protected abstract <R,T> Predicate implSpecificCreatePredicateFromSingleCriteria( CriteriaQuery<R> query, CriteriaBuilder builder, Class queryType, QueryCriteria criteria, QueryWhere queryWhere); /** * This method does the persistence-related logic related to executing a query. * </p> * All implementations of this method should do the following, in approximately the following order: * <ol> * <li>Get an {@link EntityManager} instance</li> * <li>Join a transaction using the entity manager</li> * <li>Create a {@link Query} from the given {@link CriteriaQuery} instance.</li> * <li>Call the {@link #applyMetaCriteriaToQuery(Query, QueryWhere)} method</li> * <li>Retrieve the result from the {@link Query} instance.</li> * <li>Close the transaction created, and the created {@link EntityManager} instance. * <li>Return the query result</li> * </ol> * * @param criteriaQuery The created and filled {@link CriteriaQuery} instance * @param builder The {@link CriteriaBuilder}, helpful when creating {@link Predicate}s to add to the {@link CriteriaQuery} * @param queryWhere The {@link QueryWhere} instance containing the meta criteria information. * @return A {@link List} of instances, representing the query result. */ // @formatter:off protected abstract <T> List<T> createQueryAndCallApplyMetaCriteriaAndGetResult( QueryWhere queryWhere, CriteriaQuery<T> criteriaQuery, CriteriaBuilder builder); /** * Small method to apply the meta criteria from the {@link QueryWhere} instance to the {@link Query} instance * @param query The {@link Query} instance * @param queryWhere The {@link QueryWhere} instance, with the abstract information about the query */ public static void applyMetaCriteriaToQuery(Query query, QueryWhere queryWhere) { if( queryWhere.getCount() != null ) { query.setMaxResults(queryWhere.getCount()); } if( queryWhere.getOffset() != null ) { query.setFirstResult(queryWhere.getOffset()); } } /** * * * @param orderByListId * @param queryType * @param query * @return */ protected <T,R> Expression getOrderByExpression(CriteriaQuery<R> query, Class<T> queryType, String orderByListId) { Attribute field = getCriteriaAttributes().get(queryType).get(orderByListId); assert field != null : "No Attribute found for order-by listId " + orderByListId + " for result type " + queryType.getSimpleName(); Root table = getRoot(query, queryType); assert table != null : "Unable to find proper table (Root) instance in query for result type " + queryType.getSimpleName(); Path orderByPath; if( field instanceof SingularAttribute ) { orderByPath = table.get((SingularAttribute) field); } else { throw new UnsupportedOperationException("Ordering by a join field is not supported!"); } return orderByPath; } }