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

eu.ill.preql.support.AttributeMapper Maven / Gradle / Ivy

Go to download

Preql (Predicate query language) is a project designed to filter JPA collections using client-side expressions.

There is a newer version: 2.0.1
Show newest version
/*
 * Copyright 2018 Institut Laue–Langevin
 *
 * 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 eu.ill.preql.support;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.ManagedType;
import jakarta.persistence.metamodel.Metamodel;
import jakarta.persistence.metamodel.PluralAttribute;
import java.util.Set;

import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static jakarta.persistence.criteria.JoinType.LEFT;
import static jakarta.persistence.metamodel.Attribute.PersistentAttributeType.EMBEDDED;

/**
 * A mapper to map attributes to their respective paths in the entity object graph
 *
 * @author Jamie Hall
 */
public class AttributeMapper {

    private static final Logger    logger = LoggerFactory.getLogger(AttributeMapper.class);
    private final        Root   root;
    private final        Metamodel metamodel;

    /**
     * @param root      the root type in the from clause.
     * @param metamodel provides access to the metamodel of persistent
     *                  entities in the persistence unit.
     */
    public AttributeMapper(final Root root, final Metamodel metamodel) {
        this.metamodel = metamodel;
        this.root = root;
    }

    /**
     * Get the path for a given attribute
     *
     * @param attribute the name of the represented attribute
     * @return the path for the property
     */
    public Path get(final String attribute) {
        requireNonNull(attribute, "Attribute cannot be null");
        return traverse(attribute);
    }

    /**
     * Traverse the attribute
     *
     * @param attribute he name of the represented attribute to be traversed
     * @return the path to the attribute
     */
    private Path traverse(final String attribute) {
        final String[] attributes = splitAttributes(attribute);
        return traverse(attributes, this.root);
    }

    /**
     * Split the attribute name by a dot
     *
     * @param attribute the name of the represented attribute
     * @return an array of attribute names
     */
    private String[] splitAttributes(final String attribute) {
        return attribute.split("\\.");
    }

    /**
     * @param attributes the name of the represented attribute
     * @param startRoot  the root to start from
     * @return the path for the given attribute
     */
    private Path traverse(final String[] attributes, final Path startRoot) {
        ManagedType metadata = metamodel.managedType(startRoot.getJavaType());
        Path        root     = startRoot;

        for (final String attribute : attributes) {
            hasAttributeName(attribute, metadata);
            if (isAssociation(attribute, metadata)) {
                Class       association      = getType(attribute, metadata);
                ManagedType previousMetadata = metadata;
                metadata = metamodel.managedType(association);
                Join join = hasJoin(metadata, previousMetadata);
                if (join == null) {
                    logger.debug("Creating join between {} and {}", previousMetadata.getJavaType(), metadata.getJavaType());
                    root = ((From) root).join(attribute, LEFT);
                    continue;
                }
                root = join;
                continue;
            }
            logger.debug("Create attribute path for type {} attribute  {}", metadata.getJavaType(), attribute);
            root = root.get(attribute);
            if (isEmbedded(attribute, metadata)) {
                Class embedded = getType(attribute, metadata);
                metadata = metamodel.managedType(embedded);
            }
        }
        return root;
    }

    /**
     * Check that a join has already been added for a given association
     *
     * @param metadata         the metadata
     * @param previousMetadata the parent metadata
     * @return a join or null if not found
     */
    private Join hasJoin(final ManagedType metadata, final ManagedType previousMetadata) {
        for (Join join : root.getJoins()) {
            logger.debug("Join type {} and parent type: {}", join.getJavaType(), join.getParent().getJavaType());
            if ((join.getJavaType().equals(metadata.getJavaType()))
                    && join.getParent().getJavaType().equals(previousMetadata.getJavaType())) {
                logger.debug("Found existing join for {} and {}", metadata.getJavaType(), previousMetadata.getJavaType());
                return join;
            }
        }
        return null;
    }

    /**
     * Check that an attribute name exists for a given type
     *
     * @param attribute the name of the represented attribute
     * @param metadata  the metadata that represents the entity, mapped superclass or embeddable for the given attribute
     */
    private  void hasAttributeName(final String attribute, final ManagedType metadata) {
        final Set> names = metadata.getAttributes();
        for (final Attribute name : names) {
            if (name.getName().equals(attribute)) {
                return;
            }
        }
        throw new IllegalArgumentException(format("Unknown attribute %s on %s", attribute, metadata.getJavaType()));
    }

    /**
     * Find the java type for a given attribute
     *
     * @param attribute the name of the represented attribute
     * @param metadata  the metadata that represents the entity, mapped superclass or embeddable for the given attribute
     * @return the Java type of the represented attribute.
     */
    private  Class getType(final String attribute, final ManagedType metadata) {
        if (metadata.getAttribute(attribute).isCollection()) {
            return ((PluralAttribute) metadata.getAttribute(attribute)).getBindableJavaType();
        }
        return metadata.getAttribute(attribute).getJavaType();
    }

    /**
     * Check if the given attribute is an association
     *
     * @param attribute the name of the represented attribute
     * @param metadata  the metadata that represents the entity of the given property
     * @return true if it is an association, otherwise false
     */
    private  boolean isAssociation(final String attribute, final ManagedType metadata) {
        return metadata.getAttribute(attribute).isAssociation();
    }

    /**
     * @param attribute the name of the represented attribute
     * @param metadata  the metadata that represents the entity, mapped superclass or embeddable for the given attribute
     * @return true if it is embedded otherwise false
     */
    private  boolean isEmbedded(final String attribute, final ManagedType metadata) {
        return metadata.getAttribute(attribute).getPersistentAttributeType() == EMBEDDED;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy