All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.blazebit.persistence.impl.EmbeddableSplittingVisitor 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.lang.StringUtils;
import com.blazebit.persistence.parser.EntityMetamodel;
import com.blazebit.persistence.parser.expression.AbortableVisitorAdapter;
import com.blazebit.persistence.parser.expression.ArithmeticExpression;
import com.blazebit.persistence.parser.expression.ArrayExpression;
import com.blazebit.persistence.parser.expression.DateLiteral;
import com.blazebit.persistence.parser.expression.EntityLiteral;
import com.blazebit.persistence.parser.expression.EnumLiteral;
import com.blazebit.persistence.parser.expression.Expression;
import com.blazebit.persistence.parser.expression.FunctionExpression;
import com.blazebit.persistence.parser.expression.GeneralCaseExpression;
import com.blazebit.persistence.parser.expression.ListIndexExpression;
import com.blazebit.persistence.parser.expression.MapEntryExpression;
import com.blazebit.persistence.parser.expression.MapKeyExpression;
import com.blazebit.persistence.parser.expression.NullExpression;
import com.blazebit.persistence.parser.expression.NumericLiteral;
import com.blazebit.persistence.parser.expression.ParameterExpression;
import com.blazebit.persistence.parser.expression.PathExpression;
import com.blazebit.persistence.parser.expression.PathReference;
import com.blazebit.persistence.parser.expression.SimpleCaseExpression;
import com.blazebit.persistence.parser.expression.StringLiteral;
import com.blazebit.persistence.parser.expression.SubqueryExpression;
import com.blazebit.persistence.parser.expression.TimeLiteral;
import com.blazebit.persistence.parser.expression.TimestampLiteral;
import com.blazebit.persistence.parser.expression.TrimExpression;
import com.blazebit.persistence.parser.expression.TypeFunctionExpression;
import com.blazebit.persistence.parser.expression.WhenClauseExpression;
import com.blazebit.persistence.parser.predicate.BooleanLiteral;
import com.blazebit.persistence.spi.ExtendedAttribute;
import com.blazebit.persistence.spi.ExtendedManagedType;
import com.blazebit.persistence.spi.JpaProvider;

import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import static com.blazebit.persistence.parser.util.JpaMetamodelUtils.ATTRIBUTE_NAME_COMPARATOR;

/**
 *
 * @author Christian Beikov
 * @since 1.3.0
 */
class EmbeddableSplittingVisitor extends AbortableVisitorAdapter {

    protected final EntityMetamodel metamodel;
    protected final JpaProvider jpaProvider;
    protected final AliasManager aliasManager;
    protected final SplittingVisitor splittingVisitor;
    protected final List splittedOffExpressions;
    protected Expression expressionToSplit;

    public EmbeddableSplittingVisitor(EntityMetamodel metamodel, JpaProvider jpaProvider, AliasManager aliasManager, SplittingVisitor splittingVisitor) {
        this.metamodel = metamodel;
        this.jpaProvider = jpaProvider;
        this.aliasManager = aliasManager;
        this.splittingVisitor = splittingVisitor;
        this.splittedOffExpressions = new ArrayList<>();
    }

    protected void clear() {
        splittedOffExpressions.clear();
    }

    public List getSplittedOffExpressions() {
        return splittedOffExpressions;
    }

    public List splitOff(Expression expression) {
        expressionToSplit = null;
        expression.accept(this);
        if (collectSplittedOffExpressions(expression)) {
            return null;
        }
        return splittedOffExpressions;
    }

    protected boolean collectSplittedOffExpressions(Expression expression) {
        splittedOffExpressions.clear();
        if (expressionToSplit != null) {
            JoinNode baseNode;
            String field;
            if (expressionToSplit instanceof PathExpression) {
                PathReference pathReference = ((PathExpression) expressionToSplit).getPathReference();
                baseNode = (JoinNode) pathReference.getBaseNode();
                field = pathReference.getField();
            } else if (expressionToSplit instanceof MapKeyExpression) {
                baseNode = ((JoinNode) ((MapKeyExpression) expressionToSplit).getPath().getBaseNode()).getKeyJoinNode();
                field = null;
            } else {
                // This should never happen
                return false;
            }
            String fieldPrefix = field == null ? "" : field + ".";
            ExtendedManagedType managedType = metamodel.getManagedType(ExtendedManagedType.class, baseNode.getManagedType());
            Map orderedAttributes = new TreeMap<>();
            EntityType ownerType;
            if (baseNode.getParentTreeNode() == null && field == null) {
                ownerType = baseNode.getEntityType();
                for (SingularAttribute idAttribute : managedType.getIdAttributes()) {
                    addAttributes(ownerType, null, fieldPrefix, "", idAttribute, orderedAttributes);
                }
            } else {
                Map> ownedAttributes;
                String prefix = field;
                if (baseNode.getParentTreeNode() != null && jpaProvider.getJpaMetamodelAccessor().isElementCollection(baseNode.getParentTreeNode().getAttribute())) {
                    String elementCollectionPath = baseNode.getParentTreeNode().getRelationName();
                    ExtendedManagedType entityManagedType = metamodel.getManagedType(ExtendedManagedType.class, baseNode.getParent().getEntityType());
                    ownedAttributes = entityManagedType.getAttributes();
                    if (prefix == null) {
                        prefix = elementCollectionPath;
                    } else {
                        prefix = elementCollectionPath + "." + prefix;
                    }
                } else {
                    ownedAttributes = managedType.getOwnedSingularAttributes();
                }
                for (String embeddedPropertyPath : JpaUtils.getEmbeddedPropertyPaths((Map>) ownedAttributes, prefix, false, false)) {
                    orderedAttributes.put(embeddedPropertyPath, Boolean.FALSE);
                }
            }

            // Signal the caller that the expression was eliminated
            if (orderedAttributes.isEmpty()) {
                return true;
            }

            for (String orderedAttribute : orderedAttributes.keySet()) {
                splittedOffExpressions.add(splittingVisitor.splitOff(expression, expressionToSplit, orderedAttribute));
            }
        }

        return false;
    }

    @Override
    public Boolean visit(PathExpression expr) {
        PathReference pathReference = expr.getPathReference();
        if (pathReference == null) {
            return ((SelectInfo) aliasManager.getAliasInfo(expr.toString())).getExpression().accept(this);
        }

        JoinNode baseNode = (JoinNode) pathReference.getBaseNode();
        Attribute attr;
        if (pathReference.getField() == null) {
            if (baseNode.getParentTreeNode() != null) {
                attr = baseNode.getParentTreeNode().getAttribute();
                if (attr instanceof PluralAttribute && ((PluralAttribute) attr).getElementType() instanceof EmbeddableType) {
                    expressionToSplit = expr;
                }
            }
        } else {
            ExtendedManagedType managedType = metamodel.getManagedType(ExtendedManagedType.class, baseNode.getManagedType());
            attr = managedType.getAttribute(pathReference.getField()).getAttribute();

            // This kind of happens when we do an entity select where the entity is split into it's component paths
            // We don't want to split it here though as it won't end up in a group by clause or anything anyway
            if (attr instanceof PluralAttribute) {
                return true;
            }

            SingularAttribute singularAttr = (SingularAttribute) attr;

            int dotIndex = expr.getField().lastIndexOf('.');
            if (dotIndex == -1 && singularAttr.getType() instanceof EmbeddableType) {
                expressionToSplit = expr;
            }
        }

        return true;
    }

    protected void addAttributes(EntityType ownerType, String elementCollectionPath, String fieldPrefix, String prefix, SingularAttribute singularAttribute, Map orderedAttributes) {
        String attributeName;
        if (prefix.isEmpty()) {
            attributeName = singularAttribute.getName();
        } else {
            attributeName = prefix + singularAttribute.getName();
        }
        if (singularAttribute.getType() instanceof EmbeddableType) {
            String newPrefix = attributeName + ".";
            Set> subAttributes = new TreeSet<>(ATTRIBUTE_NAME_COMPARATOR);
            subAttributes.addAll(((EmbeddableType) singularAttribute.getType()).getSingularAttributes());
            for (SingularAttribute attribute : subAttributes) {
                addAttributes(ownerType, elementCollectionPath, fieldPrefix, newPrefix, attribute, orderedAttributes);
            }
        } else if (singularAttribute.getType() instanceof ManagedType) {
            String newPrefix = attributeName + ".";
            Collection attributeNames;
            if (elementCollectionPath == null) {
                attributeNames = jpaProvider.getJoinMappingPropertyNames(ownerType, elementCollectionPath, fieldPrefix + attributeName).keySet();
            } else {
                attributeNames = jpaProvider.getJoinMappingPropertyNames(ownerType, elementCollectionPath, elementCollectionPath + "." + fieldPrefix + attributeName).keySet();
            }

            ExtendedAttribute extendedAttribute = (ExtendedAttribute) metamodel.getManagedType(ExtendedManagedType.class, ownerType).getAttributes().get(fieldPrefix + attributeName);
            if (extendedAttribute != null && StringUtils.isEmpty(extendedAttribute.getMappedBy())) {
                ExtendedManagedType managedType = metamodel.getManagedType(ExtendedManagedType.class, (ManagedType) singularAttribute.getType());
                for (String attrName : attributeNames) {
                    addAttributes(ownerType, elementCollectionPath, fieldPrefix, newPrefix, (SingularAttribute) managedType.getAttributes().get(attrName).getAttribute(), orderedAttributes);
                }
            }
        } else {
            orderedAttributes.put(attributeName, Boolean.FALSE);
        }
    }

    @Override
    public Boolean visit(ListIndexExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(MapKeyExpression expression) {
        PathExpression path = expression.getPath();
        PathReference pathReference = path.getPathReference();
        while (pathReference == null) {
            Expression aliasedExpression = ((SelectInfo) aliasManager.getAliasInfo(path.toString())).getExpression();
            if (aliasedExpression instanceof PathExpression) {
                path = (PathExpression) aliasedExpression;
                pathReference = path.getPathReference();
            } else {
                // This should never happen
                return false;
            }
        }

        JoinNode baseNode = (JoinNode) pathReference.getBaseNode();
        Attribute attr;
        if (baseNode.getParentTreeNode() != null) {
            attr = baseNode.getParentTreeNode().getAttribute();
            if (attr instanceof MapAttribute && ((MapAttribute) attr).getKeyType() instanceof EmbeddableType) {
                expressionToSplit = expression;
            }
        }
        return false;
    }

    @Override
    public Boolean visit(NullExpression expression) {
        // The actual semantics of NULL are, that NULL != NULL
        return true;
    }

    @Override
    public Boolean visit(FunctionExpression expression) {
        switch (expression.getFunctionName().toUpperCase()) {
            // MIN and MAX work with embeddables
            case "MIN":
            case "MAX":
            case "WINDOW_MIN":
            case "WINDOW_MAX": {
                Expression expr = expression.getExpressions().get(0);
                return expr instanceof PathExpression && visit((PathExpression) expr);
            }
            default:
                // The use of other functions with embeddable does not make any sense, so don't inspect these
                return false;
        }
    }

    /* Using embeddables in other expressions doesn't make sense, so don't inspect these */

    @Override
    public Boolean visit(ArrayExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(GeneralCaseExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(SimpleCaseExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(WhenClauseExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(ParameterExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(MapEntryExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(SubqueryExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(TypeFunctionExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(TrimExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(ArithmeticExpression expression) {
        return false;
    }

    @Override
    public Boolean visit(NumericLiteral expression) {
        return false;
    }

    @Override
    public Boolean visit(BooleanLiteral expression) {
        return false;
    }

    @Override
    public Boolean visit(StringLiteral expression) {
        return false;
    }

    @Override
    public Boolean visit(DateLiteral expression) {
        return false;
    }

    @Override
    public Boolean visit(TimeLiteral expression) {
        return false;
    }

    @Override
    public Boolean visit(TimestampLiteral expression) {
        return false;
    }

    @Override
    public Boolean visit(EnumLiteral expression) {
        return false;
    }

    @Override
    public Boolean visit(EntityLiteral expression) {
        return false;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy