/* * Copyright 2014 - 2017 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.impl.expression.Expression; import com.blazebit.persistence.impl.expression.FunctionExpression; import com.blazebit.persistence.impl.expression.ParameterExpression; import com.blazebit.persistence.impl.expression.PathExpression; import com.blazebit.persistence.impl.expression.PathReference; import com.blazebit.persistence.impl.expression.SubqueryExpression; import com.blazebit.persistence.impl.expression.TreatExpression; import com.blazebit.persistence.impl.expression.VisitorAdapter; import com.blazebit.persistence.impl.predicate.EqPredicate; import com.blazebit.persistence.impl.predicate.InPredicate; import com.blazebit.persistence.impl.predicate.IsEmptyPredicate; import com.blazebit.persistence.impl.predicate.IsNullPredicate; import com.blazebit.persistence.impl.predicate.MemberOfPredicate; /** * * @author Christian Beikov * @author Moritz Becker * @since 1.0 */ public class JoinVisitor extends VisitorAdapter { private final AssociationParameterTransformerFactory parameterTransformerFactory; private final JoinManager joinManager; private final ParameterManager parameterManager; private final boolean needsSingleValuedAssociationIdRemoval; private boolean joinRequired; private boolean joinWithObjectLeafAllowed = true; private ClauseType fromClause; public JoinVisitor(AssociationParameterTransformerFactory parameterTransformerFactory, JoinManager joinManager, ParameterManager parameterManager, boolean needsSingleValuedAssociationIdRemoval) { this.parameterTransformerFactory = parameterTransformerFactory; this.joinManager = joinManager; this.parameterManager = parameterManager; this.needsSingleValuedAssociationIdRemoval = needsSingleValuedAssociationIdRemoval; // By default we require joins this.joinRequired = true; } public ClauseType getFromClause() { return fromClause; } public void setFromClause(ClauseType fromClause) { this.fromClause = fromClause; } @Override public void visit(PathExpression expression) { visit(expression, false); } private void visit(PathExpression expression, boolean idRemovable) { Expression aliasedExpression; if ((aliasedExpression = joinManager.getJoinableSelectAlias(expression, fromClause == ClauseType.SELECT, false)) != null) { aliasedExpression.accept(this); } else { joinManager.implicitJoin(expression, joinWithObjectLeafAllowed, null, fromClause, false, false, joinRequired, idRemovable); } } @Override public void visit(TreatExpression expression) { throw new IllegalArgumentException("Treat should not be a root of an expression: " + expression.toString()); } public boolean isJoinRequired() { return joinRequired; } public void setJoinRequired(boolean joinRequired) { this.joinRequired = joinRequired; } @Override public void visit(FunctionExpression expression) { // do not join outer expressions if (!com.blazebit.persistence.impl.util.ExpressionUtils.isOuterFunction(expression)) { super.visit(expression); } } // Added eager initialization of subqueries @Override public void visit(SubqueryExpression expression) { // TODO: we have to pass the fromClause into the subquery so that joins, generated by OUTERs from the subquery don't get // rendered if not needed // TODO: this is ugly ((AbstractCommonQueryBuilder<?, ?, ?, ?, ?>) expression.getSubquery()).applyImplicitJoins(); } public boolean isJoinWithObjectLeafAllowed() { return joinWithObjectLeafAllowed; } public void setJoinWithObjectLeafAllowed(boolean joinWithObjectLeafAllowed) { this.joinWithObjectLeafAllowed = joinWithObjectLeafAllowed; } @Override public void visit(EqPredicate predicate) { boolean original = joinRequired; joinRequired = false; removeAssociationIdIfPossible(predicate.getLeft(), predicate.getRight()); joinRequired = original; } @Override public void visit(InPredicate predicate) { boolean original = joinRequired; joinRequired = false; boolean rewritten = false; if (predicate.getRight().size() == 1) { Expression right = predicate.getRight().get(0); if (right instanceof PathExpression || right instanceof ParameterExpression) { removeAssociationIdIfPossible(predicate.getLeft(), right); rewritten = true; } } if (!rewritten) { predicate.getLeft().accept(this); for (Expression right : predicate.getRight()) { right.accept(this); } } joinRequired = original; } private void removeAssociationIdIfPossible(Expression left, Expression right) { if (needsSingleValuedAssociationIdRemoval && fromClause == ClauseType.JOIN) { if (removeAssociationIdIfPossible(left)) { // The left expression was successfully rewritten if (!removeAssociationIdIfPossible(right)) { // But the right expression failed Class<?> associationType = getAssociationType(left, right); ParameterManager.ParameterValueTranformer tranformer = parameterTransformerFactory.getToEntityTranformer(associationType); if (!rewriteToAssociationParam(tranformer, right)) { // If the other part wasn't a parameter, we have to do a "normal" implicit join left.accept(this); } } } else { if (removeAssociationIdIfPossible(right)) { // The right expression was successfully rewritten, but not the left Class<?> associationType = getAssociationType(left, right); ParameterManager.ParameterValueTranformer tranformer = parameterTransformerFactory.getToEntityTranformer(associationType); if (!rewriteToAssociationParam(tranformer, left)) { // If the other part wasn't a parameter, we have to do a "normal" implicit join right.accept(this); } } } } else { left.accept(this); right.accept(this); } } private boolean removeAssociationIdIfPossible(Expression expression) { if (expression instanceof PathExpression) { PathExpression pathExpression = (PathExpression) expression; visit(pathExpression, true); String lastElement = pathExpression.getExpressions().get(pathExpression.getExpressions().size() - 1).toString(); PathReference pathReference = pathExpression.getPathReference(); JoinNode node = (JoinNode) pathReference.getBaseNode(); String field = pathReference.getField(); if (field == null) { // If there is no field, we can only check if the last element is the join nodes relation name // If it is, then this is a plain association use like "alias.association" and we didn't remove anything if (node.getParentTreeNode() == null) { // A root node if (!lastElement.equals(node.getAlias())) { return true; } } else { // A normal join node that could be used either via association name or alias if (!lastElement.equals(node.getParentTreeNode().getRelationName()) && !lastElement.equals(node.getAlias())) { return true; } } } else { if (field.endsWith(lastElement) && (field.length() == lastElement.length() || field.charAt(field.length() - lastElement.length()) == '.')) { // If there is a field, we only have to check if the last element is in the field return false; } else { // If it isn't we have removed the id part return true; } } } return false; } private Class<?> getAssociationType(Expression expression1, Expression expression2) { if (expression1 instanceof PathExpression) { return ((PathExpression) expression1).getPathReference().getType(); } return ((PathExpression) expression2).getPathReference().getType(); } private boolean rewriteToAssociationParam(ParameterManager.ParameterValueTranformer tranformer, Expression expression) { if (!(expression instanceof ParameterExpression)) { return false; } ParameterExpression parameterExpression = (ParameterExpression) expression; ParameterManager.ParameterImpl<Object> param = (ParameterManager.ParameterImpl<Object>) parameterManager.getParameter(parameterExpression.getName()); param.setTranformer(tranformer); return true; } @Override public void visit(IsNullPredicate predicate) { boolean original = joinRequired; joinRequired = false; if (!removeAssociationIdIfPossible(predicate.getExpression())) { predicate.getExpression().accept(this); } joinRequired = original; } @Override public void visit(IsEmptyPredicate predicate) { boolean original = joinRequired; joinRequired = false; predicate.getExpression().accept(this); joinRequired = original; } @Override public void visit(MemberOfPredicate predicate) { boolean original = joinRequired; joinRequired = false; predicate.getLeft().accept(this); predicate.getRight().accept(this); joinRequired = original; } }