Please wait. This can take some minutes ...
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.
com.blazebit.persistence.impl.AbstractFullQueryBuilder Maven / Gradle / Ivy
/*
* Copyright 2014 - 2020 Blazebit.
*
* 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 com.blazebit.persistence.impl;
import com.blazebit.persistence.CaseWhenStarterBuilder;
import com.blazebit.persistence.CriteriaBuilder;
import com.blazebit.persistence.FullQueryBuilder;
import com.blazebit.persistence.HavingOrBuilder;
import com.blazebit.persistence.JoinType;
import com.blazebit.persistence.KeysetPage;
import com.blazebit.persistence.MultipleSubqueryInitiator;
import com.blazebit.persistence.ObjectBuilder;
import com.blazebit.persistence.PaginatedCriteriaBuilder;
import com.blazebit.persistence.RestrictionBuilder;
import com.blazebit.persistence.SelectObjectBuilder;
import com.blazebit.persistence.SimpleCaseWhenStarterBuilder;
import com.blazebit.persistence.SubqueryBuilder;
import com.blazebit.persistence.SubqueryInitiator;
import com.blazebit.persistence.impl.function.count.AbstractCountFunction;
import com.blazebit.persistence.impl.keyset.KeysetMode;
import com.blazebit.persistence.impl.keyset.KeysetPaginationHelper;
import com.blazebit.persistence.impl.keyset.SimpleKeysetLink;
import com.blazebit.persistence.impl.query.CTENode;
import com.blazebit.persistence.impl.query.CustomQuerySpecification;
import com.blazebit.persistence.impl.query.CustomSQLTypedQuery;
import com.blazebit.persistence.impl.query.EntityFunctionNode;
import com.blazebit.persistence.impl.query.QuerySpecification;
import com.blazebit.persistence.parser.expression.Expression;
import com.blazebit.persistence.parser.expression.ExpressionCopyContext;
import com.blazebit.persistence.parser.expression.PathExpression;
import com.blazebit.persistence.parser.util.JpaMetamodelUtils;
import com.blazebit.persistence.spi.JpaMetamodelAccessor;
import javax.persistence.TypedQuery;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.SingularAttribute;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import static com.blazebit.persistence.parser.util.JpaMetamodelUtils.ATTRIBUTE_NAME_COMPARATOR;
/**
*
* @param The query result type
* @param The concrete builder type
* @author Christian Beikov
* @author Moritz Becker
* @since 1.0.0
*/
public abstract class AbstractFullQueryBuilder, Z, W, FinalSetReturn extends BaseFinalSetOperationBuilderImpl> extends AbstractQueryBuilder implements FullQueryBuilder {
protected static final Set NO_CLAUSE_EXCLUSION = EnumSet.noneOf(ClauseType.class);
protected static final Set OBJECT_QUERY_WITHOUT_GROUP_BY_EXCLUSIONS = EnumSet.of(ClauseType.GROUP_BY);
protected static final Set COUNT_QUERY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT);
protected static final Set COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT, ClauseType.GROUP_BY);
protected static final Set ID_QUERY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.SELECT);
protected static final Set ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.SELECT, ClauseType.GROUP_BY);
protected String cachedCountQueryString;
protected String cachedExternalCountQueryString;
protected Set cachedIdentifierExpressionsToUseNonRootJoinNodes;
/**
* This flag indicates whether the current builder has been used to create a
* PaginatedCriteriaBuilder. In this case we must not allow any calls to
* group by and distinct since the corresponding managers are shared with
* the PaginatedCriteriaBuilder and any changes would affect the
* PaginatedCriteriaBuilder as well.
*/
private boolean createdPaginatedBuilder = false;
private boolean explicitPaginatedIdentifier = false;
private ResolvedExpression[] entityIdentifierExpressions;
private ResolvedExpression[] uniqueIdentifierExpressions;
private JoinNodeGathererVisitor joinNodeGathererVisitor;
/**
* Create flat copy of builder
*
* @param builder
*/
protected AbstractFullQueryBuilder(AbstractFullQueryBuilder, ?, ?, ?> builder) {
super(builder);
this.entityIdentifierExpressions = builder.entityIdentifierExpressions;
}
public AbstractFullQueryBuilder(MainQuery mainQuery, boolean isMainQuery, Class clazz, String alias, FinalSetReturn finalSetOperationBuilder) {
super(mainQuery, isMainQuery, clazz, alias, finalSetOperationBuilder);
}
@Override
protected void prepareForModification(ClauseType changedClause) {
super.prepareForModification(changedClause);
cachedCountQueryString = null;
cachedExternalCountQueryString = null;
cachedIdentifierExpressionsToUseNonRootJoinNodes = null;
uniqueIdentifierExpressions = null;
}
@Override
AbstractCommonQueryBuilder copy(QueryContext queryContext, Map joinManagerMapping, ExpressionCopyContext copyContext) {
throw new UnsupportedOperationException("This should only be used on CTEs!");
}
@Override
public FullQueryBuilder copy(Class resultClass) {
prepareAndCheck();
MainQuery mainQuery = cbf.createMainQuery(getEntityManager());
mainQuery.copyConfiguration(this.mainQuery.getQueryConfiguration());
CriteriaBuilderImpl newBuilder = new CriteriaBuilderImpl(mainQuery, true, resultClass, null);
newBuilder.fromClassExplicitlySet = true;
newBuilder.applyFrom(this, true, true, false, Collections.emptySet(), Collections.emptySet(), new IdentityHashMap(), ExpressionCopyContext.EMPTY);
return newBuilder;
}
@Override
public CriteriaBuilder createPageIdQuery(int firstResult, int maxResults, String identifierExpression) {
return createPageIdQuery(null, firstResult, maxResults, getIdentifierExpressionsToUse(identifierExpression, null));
}
@Override
public CriteriaBuilder createPageIdQuery(KeysetPage keysetPage, int firstResult, int maxResults, String identifierExpression) {
return createPageIdQuery(keysetPage, firstResult, maxResults, getIdentifierExpressionsToUse(identifierExpression, null));
}
@Override
public CriteriaBuilder createPageIdQuery(int firstResult, int maxResults, String identifierExpression, String... identifierExpressions) {
return createPageIdQuery(null, firstResult, maxResults, getIdentifierExpressionsToUse(identifierExpression, identifierExpressions));
}
@Override
public CriteriaBuilder createPageIdQuery(KeysetPage keysetPage, int firstResult, int maxResults, String identifierExpression, String... identifierExpressions) {
return createPageIdQuery(keysetPage, firstResult, maxResults, getIdentifierExpressionsToUse(identifierExpression, identifierExpressions));
}
private ResolvedExpression[] getIdentifierExpressionsToUse(String identifierExpression, String[] identifierExpressions) {
ResolvedExpression[] resolvedExpressions = getIdentifierExpressions(identifierExpression, identifierExpressions);
ResolvedExpression[] uniqueResolvedExpressions = functionalDependencyAnalyzerVisitor.getFunctionalDependencyRootExpressions(whereManager.rootPredicate.getPredicate(), resolvedExpressions, joinManager.getRoots().get(0));
if (uniqueResolvedExpressions != null) {
return uniqueResolvedExpressions;
}
return resolvedExpressions;
}
protected CriteriaBuilder createPageIdQuery(KeysetPage keysetPage, int firstResult, int maxResults, ResolvedExpression[] identifierExpressionsToUse) {
prepareAndCheck();
MainQuery mainQuery = cbf.createMainQuery(getEntityManager());
mainQuery.copyConfiguration(this.mainQuery.getQueryConfiguration());
CriteriaBuilderImpl newBuilder = new CriteriaBuilderImpl<>(mainQuery, true, Object[].class, null);
newBuilder.fromClassExplicitlySet = true;
newBuilder.applyFrom(this, true, false, false, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, getIdentifierExpressionsToUseNonRootJoinNodes(identifierExpressionsToUse), new IdentityHashMap(), ExpressionCopyContext.EMPTY);
newBuilder.setFirstResult(firstResult);
newBuilder.setMaxResults(maxResults);
// Paginated criteria builders always need the last order by expression to be unique
List orderByExpressions = orderByManager.getOrderByExpressions(false, whereManager.rootPredicate.getPredicate(), hasGroupBy ? Arrays.asList(getIdentifierExpressions()) : Collections.emptyList(), null);
if (!orderByExpressions.get(orderByExpressions.size() - 1).isResultUnique()) {
throw new IllegalStateException("The order by items of the query builder are not guaranteed to produce unique tuples! Consider also ordering by the entity identifier!");
}
if (keysetPage != null) {
KeysetMode keysetMode = KeysetPaginationHelper.getKeysetMode(keysetPage, null, firstResult, maxResults);
if (keysetMode == KeysetMode.NONE) {
newBuilder.keysetManager.setKeysetLink(null);
} else if (keysetMode == KeysetMode.NEXT) {
newBuilder.keysetManager.setKeysetLink(new SimpleKeysetLink(keysetPage.getHighest(), keysetMode));
} else {
newBuilder.keysetManager.setKeysetLink(new SimpleKeysetLink(keysetPage.getLowest(), keysetMode));
}
newBuilder.keysetManager.initialize(orderByExpressions);
}
String[] identifierToUseSelectAliases = new String[identifierExpressionsToUse.length];
Map identifierExpressionStringMap = new HashMap<>(identifierExpressionsToUse.length);
for (int i = 0; i < identifierExpressionsToUse.length; i++) {
identifierExpressionStringMap.put(identifierExpressionsToUse[i].getExpressionString(), i);
}
Integer index;
for (int i = 0; i < orderByExpressions.size(); i++) {
String potentialSelectAlias = orderByExpressions.get(i).getExpression().toString();
AliasInfo aliasInfo = aliasManager.getAliasInfo(potentialSelectAlias);
if (aliasInfo instanceof SelectInfo) {
index = identifierExpressionStringMap.get(((SelectInfo) aliasInfo).getExpression().toString());
if (index != null) {
identifierToUseSelectAliases[i] = potentialSelectAlias;
}
}
}
for (int i = 0; i < identifierExpressionsToUse.length; i++) {
newBuilder.selectManager.select(identifierExpressionsToUse[i].getExpression().copy(ExpressionCopyContext.EMPTY), identifierToUseSelectAliases[i]);
}
newBuilder.selectManager.setDefaultSelect();
return newBuilder;
}
private String getCountQueryStringWithoutCheck() {
if (cachedCountQueryString == null) {
cachedCountQueryString = buildPageCountQueryString(false, true);
}
return cachedCountQueryString;
}
private String getExternalCountQueryString() {
if (cachedExternalCountQueryString == null) {
cachedExternalCountQueryString = buildPageCountQueryString(true, true);
}
return cachedExternalCountQueryString;
}
protected String buildPageCountQueryString(boolean externalRepresentation, boolean countAll) {
StringBuilder sbSelectFrom = new StringBuilder();
if (externalRepresentation && isMainQuery) {
mainQuery.cteManager.buildClause(sbSelectFrom);
}
buildPageCountQueryString(sbSelectFrom, externalRepresentation, countAll && !hasGroupBy);
return sbSelectFrom.toString();
}
protected final void buildPageCountQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation, boolean countAll) {
sbSelectFrom.append("SELECT ");
int countStartIdx = sbSelectFrom.length();
int countEndIdx;
boolean isResultUnique;
if (countAll) {
if (mainQuery.jpaProvider.supportsCountStar()) {
sbSelectFrom.append("COUNT(*)");
} else if (mainQuery.jpaProvider.supportsCustomFunctions()) {
sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation("count_star", 0)).append(')');
} else {
sbSelectFrom.append("COUNT(");
appendIdentifierExpressions(sbSelectFrom);
sbSelectFrom.append(")");
}
countEndIdx = sbSelectFrom.length() - 1;
isResultUnique = true;
} else if (mainQuery.jpaProvider.supportsCustomFunctions()) {
sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation(AbstractCountFunction.FUNCTION_NAME, 1));
sbSelectFrom.append("'DISTINCT',");
isResultUnique = appendIdentifierExpressions(sbSelectFrom);
sbSelectFrom.append(")");
countEndIdx = sbSelectFrom.length() - 1;
appendPageCountQueryStringExtensions(sbSelectFrom);
} else {
sbSelectFrom.append("COUNT(");
sbSelectFrom.append("DISTINCT ");
isResultUnique = appendIdentifierExpressions(sbSelectFrom);
sbSelectFrom.append(")");
countEndIdx = sbSelectFrom.length() - 1;
appendPageCountQueryStringExtensions(sbSelectFrom);
}
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
// The count query does not have any fetch owners
Set countNodesToFetch = Collections.emptySet();
if (countAll) {
joinManager.buildClause(sbSelectFrom, NO_CLAUSE_EXCLUSION, null, false, externalRepresentation, false, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, countNodesToFetch, Collections.emptySet());
whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts);
} else {
// Collect usage of collection join nodes to optimize away the count distinct
// Note that we always exclude the nodes with group by dependency. We consider just the ones from the identifiers
Set identifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes();
Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, null, true, externalRepresentation, true, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, countNodesToFetch, identifierExpressionsToUseNonRootJoinNodes);
boolean hasCollectionJoinUsages = collectionJoinNodes.size() > 0;
whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts);
// Instead of a count distinct, we render a count(*) if we have no collection joins and the identifier expression is result unique
// It is result unique when it contains the query root primary key or a unique key that of a uniqueness preserving association of that
if (!hasCollectionJoinUsages && isResultUnique) {
if (mainQuery.jpaProvider.supportsCustomFunctions()) {
String countStar;
if (mainQuery.jpaProvider.supportsCountStar()) {
countStar = "COUNT(*";
} else {
countStar = mainQuery.jpaProvider.getCustomFunctionInvocation("count_star", 0);
}
for (int i = countStartIdx, j = 0; i < countEndIdx; i++, j++) {
if (j < countStar.length()) {
sbSelectFrom.setCharAt(i, countStar.charAt(j));
} else {
sbSelectFrom.setCharAt(i, ' ');
}
}
} else {
// Strip off the distinct part
int i = countStartIdx + "COUNT(".length();
countEndIdx = i + "DISTINCT ".length();
for (; i < countEndIdx; i++) {
sbSelectFrom.setCharAt(i, ' ');
}
}
}
}
}
protected void appendPageCountQueryStringExtensions(StringBuilder sbSelectFrom) {
}
protected boolean appendIdentifierExpressions(StringBuilder sbSelectFrom) {
boolean isResultUnique;
ResolvedExpression[] identifierExpressions = getIdentifierExpressions();
ResolvedExpression[] resultUniqueExpressions = getUniqueIdentifierExpressions();
if (resultUniqueExpressions == null) {
isResultUnique = false;
} else {
// We only render the identifiers that are necessary to make it unique
identifierExpressions = resultUniqueExpressions;
isResultUnique = resultUniqueExpressions == entityIdentifierExpressions || functionalDependencyAnalyzerVisitor.isResultUnique();
}
queryGenerator.setQueryBuffer(sbSelectFrom);
for (int i = 0; i < identifierExpressions.length; i++) {
identifierExpressions[i].getExpression().accept(queryGenerator);
sbSelectFrom.append(", ");
}
sbSelectFrom.setLength(sbSelectFrom.length() - 2);
return isResultUnique;
}
protected ResolvedExpression[] getUniqueIdentifierExpressions() {
if (uniqueIdentifierExpressions == null) {
ResolvedExpression[] identifierExpressions = getIdentifierExpressions();
// Fast path, if we see that the identifier expressions are the entity identifier expressions, we don't need to check uniqueness
if (identifierExpressions == entityIdentifierExpressions) {
uniqueIdentifierExpressions = identifierExpressions;
} else {
uniqueIdentifierExpressions = functionalDependencyAnalyzerVisitor.getFunctionalDependencyRootExpressions(whereManager.rootPredicate.getPredicate(), identifierExpressions, joinManager.getRoots().get(0));
}
}
return uniqueIdentifierExpressions;
}
protected ResolvedExpression[] getIdentifierExpressionsToUse() {
ResolvedExpression[] identifierExpressions = getIdentifierExpressions();
ResolvedExpression[] resultUniqueExpressions = getUniqueIdentifierExpressions();
if (resultUniqueExpressions == null) {
return identifierExpressions;
}
return resultUniqueExpressions;
}
protected Set getIdentifierExpressionsToUseNonRootJoinNodes() {
if (cachedIdentifierExpressionsToUseNonRootJoinNodes == null) {
cachedIdentifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes(getIdentifierExpressionsToUse());
}
return cachedIdentifierExpressionsToUseNonRootJoinNodes;
}
protected Set getIdentifierExpressionsToUseNonRootJoinNodes(ResolvedExpression[] identifierExpressionsToUse) {
if (joinNodeGathererVisitor == null) {
joinNodeGathererVisitor = new JoinNodeGathererVisitor();
}
Set joinNodes = joinNodeGathererVisitor.collectNonRootJoinNodes(identifierExpressionsToUse);
// Remove join nodes that use non-optional one-to-one associations
Iterator iterator = joinNodes.iterator();
OUTER: while (iterator.hasNext()) {
JoinNode joinNode = iterator.next();
JoinTreeNode parentTreeNode;
while ((parentTreeNode = joinNode.getParentTreeNode()) != null) {
if (parentTreeNode.isOptional() || parentTreeNode.getAttribute().getPersistentAttributeType() != Attribute.PersistentAttributeType.ONE_TO_ONE) {
continue OUTER;
}
joinNode = joinNode.getParent();
}
iterator.remove();
}
return joinNodes;
}
protected ResolvedExpression[] getIdentifierExpressions() {
if (hasGroupBy) {
return getGroupByIdentifierExpressions();
}
return getQueryRootEntityIdentifierExpressions();
}
@Override
public String getCountQueryString() {
if (!havingManager.isEmpty()) {
throw new IllegalStateException("Cannot count a HAVING query yet!");
}
prepareAndCheck();
return getExternalCountQueryString();
}
@Override
public TypedQuery getCountQuery() {
if (!havingManager.isEmpty()) {
throw new IllegalStateException("Cannot count a HAVING query yet!");
}
return getCountQuery(getCountQueryStringWithoutCheck());
}
protected TypedQuery getCountQuery(String countQueryString) {
prepareAndCheck();
// We can only use the query directly if we have no ctes, entity functions or hibernate bugs
Set keyRestrictedLeftJoins = getKeyRestrictedLeftJoins();
Set alwaysIncludedNodes = getIdentifierExpressionsToUseNonRootJoinNodes();
List entityFunctions = null;
boolean normalQueryMode = !isMainQuery || (!mainQuery.cteManager.hasCtes() && (entityFunctions = joinManager.getEntityFunctions(COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, true, alwaysIncludedNodes)).isEmpty() && keyRestrictedLeftJoins.isEmpty());
if (normalQueryMode && isEmpty(keyRestrictedLeftJoins, COUNT_QUERY_CLAUSE_EXCLUSIONS)) {
TypedQuery countQuery = em.createQuery(countQueryString, Long.class);
if (isCacheable()) {
mainQuery.jpaProvider.setCacheable(countQuery);
}
parameterManager.parameterizeQuery(countQuery);
return countQuery;
}
if (entityFunctions == null) {
entityFunctions = joinManager.getEntityFunctions(COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, true, alwaysIncludedNodes);
}
TypedQuery baseQuery = em.createQuery(countQueryString, Long.class);
Set parameterListNames = parameterManager.getParameterListNames(baseQuery);
List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, COUNT_QUERY_CLAUSE_EXCLUSIONS);
List entityFunctionNodes = getEntityFunctionNodes(baseQuery, entityFunctions);
boolean shouldRenderCteNodes = renderCteNodes(false);
List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST;
QuerySpecification querySpecification = new CustomQuerySpecification(
this, baseQuery, parameterManager.getParameters(), parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
);
TypedQuery countQuery = new CustomSQLTypedQuery<>(
querySpecification,
baseQuery,
parameterManager.getTransformers(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(countQuery);
return countQuery;
}
@Override
public PaginatedCriteriaBuilder page(int firstRow, int pageSize) {
return pageBy(firstRow, pageSize, (ResolvedExpression[]) null);
}
@Override
public PaginatedCriteriaBuilder page(Object entityId, int pageSize) {
return pageByAndNavigate(entityId, pageSize, getQueryRootEntityIdentifierExpressions());
}
@Override
public PaginatedCriteriaBuilder pageAndNavigate(Object entityId, int pageSize) {
return pageByAndNavigate(entityId, pageSize, getQueryRootEntityIdentifierExpressions());
}
@Override
public PaginatedCriteriaBuilder page(KeysetPage keysetPage, int firstRow, int pageSize) {
return pageBy(keysetPage, firstRow, pageSize, (ResolvedExpression[]) null);
}
@Override
public PaginatedCriteriaBuilder pageBy(int firstRow, int pageSize, String identifierExpression) {
return pageBy(firstRow, pageSize, getIdentifierExpressions(identifierExpression, null));
}
@Override
public PaginatedCriteriaBuilder pageByAndNavigate(Object entityId, int pageSize, String identifierExpression) {
return pageByAndNavigate(entityId, pageSize, getIdentifierExpressions(identifierExpression, null));
}
@Override
public PaginatedCriteriaBuilder pageBy(KeysetPage keysetPage, int firstRow, int pageSize, String identifierExpression) {
return pageBy(keysetPage, firstRow, pageSize, getIdentifierExpressions(identifierExpression, null));
}
@Override
public PaginatedCriteriaBuilder pageBy(int firstRow, int pageSize, String identifierExpression, String... identifierExpressions) {
return pageBy(firstRow, pageSize, getIdentifierExpressions(identifierExpression, identifierExpressions));
}
@Override
public PaginatedCriteriaBuilder pageByAndNavigate(Object entityId, int pageSize, String identifierExpression, String... identifierExpressions) {
return pageByAndNavigate(entityId, pageSize, getIdentifierExpressions(identifierExpression, identifierExpressions));
}
@Override
public PaginatedCriteriaBuilder pageBy(KeysetPage keysetPage, int firstRow, int pageSize, String identifierExpression, String... identifierExpressions) {
return pageBy(keysetPage, firstRow, pageSize, getIdentifierExpressions(identifierExpression, identifierExpressions));
}
protected ResolvedExpression[] getQueryRootEntityIdentifierExpressions() {
if (entityIdentifierExpressions == null) {
JoinNode rootNode = joinManager.getRootNodeOrFail("Paginated criteria builders do not support multiple from clause elements!");
Set> idAttributes = JpaMetamodelUtils.getIdAttributes(mainQuery.metamodel.entity(rootNode.getJavaType()));
@SuppressWarnings("unchecked")
List identifierExpressions = new ArrayList<>(idAttributes.size());
StringBuilder sb = new StringBuilder();
addAttributes("", idAttributes, identifierExpressions, sb, rootNode);
entityIdentifierExpressions = identifierExpressions.toArray(new ResolvedExpression[identifierExpressions.size()]);
}
return entityIdentifierExpressions;
}
private void addAttributes(String prefix, Set> attributes, List resolvedExpressions, StringBuilder sb, JoinNode rootNode) {
for (SingularAttribute attribute : attributes) {
String attributeName;
if (prefix.isEmpty()) {
attributeName = attribute.getName();
} else {
attributeName = prefix + attribute.getName();
}
if (attribute.getType() instanceof EmbeddableType) {
Set> subAttributes = new TreeSet<>(ATTRIBUTE_NAME_COMPARATOR);
subAttributes.addAll(((EmbeddableType) attribute.getType()).getSingularAttributes());
addAttributes(attributeName + ".", subAttributes, resolvedExpressions, sb, rootNode);
} else {
sb.setLength(0);
rootNode.appendDeReference(sb, attributeName, false);
PathExpression expression = (PathExpression) rootNode.createExpression(attributeName);
JpaMetamodelAccessor jpaMetamodelAccessor = mainQuery.jpaProvider.getJpaMetamodelAccessor();
expression.setPathReference(new SimplePathReference(rootNode, attributeName, getMetamodel().type(jpaMetamodelAccessor.getAttributePath(getMetamodel(), rootNode.getManagedType(), attributeName).getAttributeClass())));
resolvedExpressions.add(new ResolvedExpression(sb.toString(), expression));
}
}
}
private ResolvedExpression[] getIdentifierExpressions(String identifierExpression, String[] identifierExpressions) {
if (identifierExpression == null) {
throw new IllegalArgumentException("Invalid null identifier expression passed to page()!");
}
// Note: Identifier expressions are inner joined!
List resolvedExpressions = new ArrayList<>(identifierExpressions == null ? 1 : identifierExpression.length() + 1);
Expression expression = expressionFactory.createSimpleExpression(identifierExpression, false);
joinManager.implicitJoin(expression, true, true, false, null, null, JoinType.INNER, null, new HashSet(), false, false, true, false, false, false);
StringBuilder sb = new StringBuilder();
implicitJoinWhereClause();
functionalDependencyAnalyzerVisitor.clear(whereManager.rootPredicate.getPredicate(), joinManager.getRoots().get(0), true);
functionalDependencyAnalyzerVisitor.analyzeFormsUniqueTuple(expression);
queryGenerator.setQueryBuffer(sb);
if (functionalDependencyAnalyzerVisitor.getSplittedOffExpressions().isEmpty()) {
sb.setLength(0);
expression.accept(queryGenerator);
resolvedExpressions.add(new ResolvedExpression(sb.toString(), expression));
} else {
for (Expression splittedOffExpression : functionalDependencyAnalyzerVisitor.getSplittedOffExpressions()) {
sb.setLength(0);
splittedOffExpression.accept(queryGenerator);
resolvedExpressions.add(new ResolvedExpression(sb.toString(), splittedOffExpression));
}
}
if (identifierExpressions != null) {
for (String expressionString : identifierExpressions) {
expression = expressionFactory.createSimpleExpression(expressionString, false);
joinManager.implicitJoin(expression, true, true, false, null, null, JoinType.INNER, null, new HashSet(), false, false, true, false, false, false);
functionalDependencyAnalyzerVisitor.analyzeFormsUniqueTuple(expression);
if (functionalDependencyAnalyzerVisitor.getSplittedOffExpressions().isEmpty()) {
sb.setLength(0);
expression.accept(queryGenerator);
ResolvedExpression resolvedExpression = new ResolvedExpression(sb.toString(), expression);
if (resolvedExpressions.contains(resolvedExpression)) {
throw new IllegalArgumentException("Duplicate identifier expression '" + expressionString + "' in " + Arrays.toString(identifierExpressions) + "!");
}
resolvedExpressions.add(resolvedExpression);
} else {
for (Expression splittedOffExpression : functionalDependencyAnalyzerVisitor.getSplittedOffExpressions()) {
sb.setLength(0);
splittedOffExpression.accept(queryGenerator);
ResolvedExpression resolvedExpression = new ResolvedExpression(sb.toString(), splittedOffExpression);
if (resolvedExpressions.contains(resolvedExpression)) {
throw new IllegalArgumentException("Duplicate identifier expression '" + expressionString + "' in " + Arrays.toString(identifierExpressions) + "!");
}
resolvedExpressions.add(resolvedExpression);
}
}
}
}
@SuppressWarnings("unchecked")
ResolvedExpression[] entries = resolvedExpressions.toArray(new ResolvedExpression[resolvedExpressions.size()]);
if (!functionalDependencyAnalyzerVisitor.isResultUnique()) {
throw new IllegalArgumentException("The identifier expressions [" + expressionString(entries) + "] do not form a unique tuple which is required for pagination!");
}
return entries;
}
private PaginatedCriteriaBuilder pageBy(int firstRow, int pageSize, ResolvedExpression[] identifierExpressions) {
prepareForModification(ClauseType.GROUP_BY);
if (selectManager.isDistinct()) {
throw new IllegalStateException("Cannot paginate a DISTINCT query");
}
if (!havingManager.isEmpty()) {
throw new IllegalStateException("Cannot paginate a HAVING query");
}
createdPaginatedBuilder = true;
explicitPaginatedIdentifier = identifierExpressions != null;
return new PaginatedCriteriaBuilderImpl(this, false, null, firstRow, pageSize, identifierExpressions);
}
private PaginatedCriteriaBuilder pageByAndNavigate(Object entityId, int pageSize, ResolvedExpression[] identifierExpressions) {
prepareForModification(ClauseType.GROUP_BY);
if (selectManager.isDistinct()) {
throw new IllegalStateException("Cannot paginate a DISTINCT query");
}
if (!havingManager.isEmpty()) {
throw new IllegalStateException("Cannot paginate a HAVING query");
}
checkEntityId(entityId, identifierExpressions);
createdPaginatedBuilder = true;
explicitPaginatedIdentifier = identifierExpressions != null;
return new PaginatedCriteriaBuilderImpl(this, false, entityId, pageSize, identifierExpressions);
}
private PaginatedCriteriaBuilder pageBy(KeysetPage keysetPage, int firstRow, int pageSize, ResolvedExpression[] identifierExpressions) {
prepareForModification(ClauseType.GROUP_BY);
if (selectManager.isDistinct()) {
throw new IllegalStateException("Cannot paginate a DISTINCT query");
}
if (!havingManager.isEmpty()) {
throw new IllegalStateException("Cannot paginate a HAVING query");
}
createdPaginatedBuilder = true;
explicitPaginatedIdentifier = identifierExpressions != null;
return new PaginatedCriteriaBuilderImpl(this, true, keysetPage, firstRow, pageSize, identifierExpressions);
}
protected static String expressionString(ResolvedExpression[] identifierExpressions) {
StringBuilder sb = new StringBuilder();
for (ResolvedExpression identifierExpression : identifierExpressions) {
sb.append(identifierExpression.getExpressionString()).append(", ");
}
sb.setLength(sb.length() - 2);
return sb.toString();
}
private void checkEntityId(Object entityId, ResolvedExpression[] identifierExpressions) {
if (entityId == null) {
throw new IllegalArgumentException("Invalid null entity id given");
}
if (identifierExpressions.length == 0) {
throw new IllegalArgumentException("Empty identifier expressions given");
}
if (identifierExpressions.length > 1) {
if (!entityId.getClass().isArray()) {
throw new IllegalArgumentException("The type of the given entity id '" + entityId.getClass().getName()
+ "' is not an array of the identifier components " + Arrays.toString(identifierExpressions) + " !");
}
Object[] entityIdComponents = (Object[]) entityId;
if (entityIdComponents.length != identifierExpressions.length) {
throw new IllegalArgumentException("The number of entity id components is '" + entityIdComponents.length
+ "' which does not match the number of identifier component expressions " + identifierExpressions.length + " !");
}
for (int i = 0; i < identifierExpressions.length; i++) {
checkEntityIdComponent(entityIdComponents[i], identifierExpressions[i].getExpression(), identifierExpressions[i].getExpressionString());
}
} else {
checkEntityIdComponent(entityId, identifierExpressions[0].getExpression(), "identifier");
}
}
private void checkEntityIdComponent(Object component, Expression expression, String componentName) {
AttributeHolder attribute = JpaUtils.getAttributeForJoining(getMetamodel(), expression);
Class type = attribute.getAttributeType().getJavaType();
if (type == null || !type.isInstance(component)) {
throw new IllegalArgumentException("The type of the given " + componentName + " '" + component.getClass().getName()
+ "' is not an instance of the expected type '" + JpaMetamodelUtils.getTypeName(attribute.getAttributeType()) + "'");
}
}
@Override
public SelectObjectBuilder> selectNew(Class clazz) {
prepareForModification(ClauseType.SELECT);
if (clazz == null) {
throw new NullPointerException("clazz");
}
verifyBuilderEnded();
return selectManager.selectNew(this, clazz);
}
@Override
public SelectObjectBuilder> selectNew(Constructor constructor) {
prepareForModification(ClauseType.SELECT);
if (constructor == null) {
throw new NullPointerException("constructor");
}
verifyBuilderEnded();
return selectManager.selectNew(this, constructor);
}
@Override
@SuppressWarnings("unchecked")
public FullQueryBuilder selectNew(ObjectBuilder objectBuilder) {
prepareForModification(ClauseType.SELECT);
if (objectBuilder == null) {
throw new NullPointerException("objectBuilder");
}
verifyBuilderEnded();
selectManager.selectNew((X) this, objectBuilder);
return (FullQueryBuilder) this;
}
@Override
@SuppressWarnings("unchecked")
public X fetch(String path) {
prepareForModification(ClauseType.JOIN);
verifyBuilderEnded();
joinManager.implicitJoin(expressionFactory.createPathExpression(path), true, true, true, null, null, null, null, new HashSet(), false, false, true, false, true, false);
return (X) this;
}
@Override
@SuppressWarnings("unchecked")
public X fetch(String... paths) {
prepareForModification(ClauseType.JOIN);
verifyBuilderEnded();
HashSet currentlyResolvingAliases = new HashSet<>();
for (String path : paths) {
joinManager.implicitJoin(expressionFactory.createPathExpression(path), true, true, true, null, null, null, null, currentlyResolvingAliases, false, false, true, false, true, false);
}
return (X) this;
}
@Override
public X innerJoinFetch(String path, String alias) {
return join(path, alias, JoinType.INNER, true);
}
@Override
public X innerJoinFetchDefault(String path, String alias) {
return joinDefault(path, alias, JoinType.INNER, true);
}
@Override
public X leftJoinFetch(String path, String alias) {
return join(path, alias, JoinType.LEFT, true);
}
@Override
public X leftJoinFetchDefault(String path, String alias) {
return joinDefault(path, alias, JoinType.LEFT, true);
}
@Override
public X rightJoinFetch(String path, String alias) {
return join(path, alias, JoinType.RIGHT, true);
}
@Override
public X rightJoinFetchDefault(String path, String alias) {
return joinDefault(path, alias, JoinType.RIGHT, true);
}
@Override
public X join(String path, String alias, JoinType type, boolean fetch) {
return join(path, alias, type, fetch, false);
}
@Override
public X joinDefault(String path, String alias, JoinType type, boolean fetch) {
return join(path, alias, type, fetch, true);
}
@SuppressWarnings("unchecked")
private X join(String path, String alias, JoinType type, boolean fetch, boolean defaultJoin) {
prepareForModification(ClauseType.JOIN);
if (path == null) {
throw new NullPointerException("path");
}
if (alias == null) {
throw new NullPointerException("alias");
}
if (type == null) {
throw new NullPointerException("type");
}
if (alias.isEmpty()) {
throw new IllegalArgumentException("Empty alias");
}
verifyBuilderEnded();
joinManager.join(path, alias, type, fetch, defaultJoin);
return (X) this;
}
@Override
public X distinct() {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling distinct() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.distinct();
}
@Override
public RestrictionBuilder having(String expression) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.having(expression);
}
@Override
public CaseWhenStarterBuilder> havingCase() {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingCase();
}
@Override
public SimpleCaseWhenStarterBuilder> havingSimpleCase(String expression) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingSimpleCase(expression);
}
@Override
public HavingOrBuilder havingOr() {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingOr();
}
@Override
public SubqueryInitiator havingExists() {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingExists();
}
@Override
public SubqueryInitiator havingNotExists() {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingNotExists();
}
@Override
public SubqueryBuilder havingExists(FullQueryBuilder criteriaBuilder) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingExists(criteriaBuilder);
}
@Override
public SubqueryBuilder havingNotExists(FullQueryBuilder criteriaBuilder) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingNotExists(criteriaBuilder);
}
@Override
public SubqueryInitiator> havingSubquery() {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingSubquery();
}
@Override
public SubqueryInitiator> havingSubquery(String subqueryAlias, String expression) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingSubquery(subqueryAlias, expression);
}
@Override
public MultipleSubqueryInitiator> havingSubqueries(String expression) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingSubqueries(expression);
}
@Override
public SubqueryBuilder> havingSubquery(FullQueryBuilder criteriaBuilder) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingSubquery(criteriaBuilder);
}
@Override
public SubqueryBuilder> havingSubquery(String subqueryAlias, String expression, FullQueryBuilder criteriaBuilder) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.havingSubquery(subqueryAlias, expression, criteriaBuilder);
}
@Override
public X setHavingExpression(String expression) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.setHavingExpression(expression);
}
@Override
public MultipleSubqueryInitiator setHavingExpressionSubqueries(String expression) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
return super.setHavingExpressionSubqueries(expression);
}
@Override
public X groupBy(String... paths) {
if (explicitPaginatedIdentifier) {
throw new IllegalStateException("Cannot add a GROUP BY clause when paginating by the expressions [" + expressionString(getIdentifierExpressions()) + "]");
}
return super.groupBy(paths);
}
@Override
public X groupBy(String expression) {
if (explicitPaginatedIdentifier) {
throw new IllegalStateException("Cannot add a GROUP BY clause when paginating by the expressions [" + expressionString(getIdentifierExpressions()) + "]");
}
return super.groupBy(expression);
}
}