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

io.micronaut.data.model.PersistentEntityUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2022 original authors
 *
 * 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
 *
 * https://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 io.micronaut.data.model;

import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.data.annotation.sql.JoinColumn;
import io.micronaut.data.annotation.sql.JoinColumns;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * Persistent entity utils.
 *
 * @author Denis Stepanov
 * @since 3.5.0
 */
@Internal
public final class PersistentEntityUtils {

    private static final String UNDERSCORE = "_";

    private PersistentEntityUtils() {
    }

    /**
     * Check if the property is an association ID that can be accessed without join. In a case it's not an ID stored outside the associated table.
     * @param association The association
     * @param persistentProperty The association's property
     * @return true if can be accessed
     * @since 4.2.0
     */
    public static boolean isAccessibleWithoutJoin(Association association, PersistentProperty persistentProperty) {
        PersistentProperty identity = association.getAssociatedEntity().getIdentity();
        if (identity instanceof Embedded embedded) {
            for (PersistentProperty property : embedded.getAssociatedEntity().getPersistentProperties()) {
                if (property == persistentProperty) {
                    return !association.isForeignKey();
                }
            }

        }
        return identity == persistentProperty && !association.isForeignKey();
    }

    /**
     * Traverses properties that should be persisted.
     *
     * @param property The property to start traversing from
     * @param consumer The function to invoke on every property
     */
    public static void traversePersistentProperties(PersistentProperty property, BiConsumer, PersistentProperty> consumer) {
        traversePersistentProperties(Collections.emptyList(), property, consumer);
    }

    /**
     * Traverses properties that should be persisted.
     *
     * @param persistentEntity The persistent entity
     * @param consumer         The function to invoke on every property
     */
    public static void traversePersistentProperties(PersistentEntity persistentEntity, BiConsumer, PersistentProperty> consumer) {
        for (PersistentProperty identityProperty : persistentEntity.getIdentityProperties()) {
            traversePersistentProperties(Collections.emptyList(), identityProperty, consumer);
        }
        if (persistentEntity.getVersion() != null) {
            traversePersistentProperties(Collections.emptyList(), persistentEntity.getVersion(), consumer);
        }
        for (PersistentProperty property : persistentEntity.getPersistentProperties()) {
            traversePersistentProperties(Collections.emptyList(), property, consumer);
        }
    }

    /**
     * Traverses properties that should be persisted.
     *
     * @param persistentEntity The persistent entity
     * @param includeIdentity  Should be identifier included
     * @param includeVersion   Should be version included
     * @param consumer         The function to invoke on every property
     */
    public static void traversePersistentProperties(PersistentEntity persistentEntity, boolean includeIdentity, boolean includeVersion, BiConsumer, PersistentProperty> consumer) {
        if (includeIdentity) {
            for (PersistentProperty identityProperty : persistentEntity.getIdentityProperties()) {
                traversePersistentProperties(Collections.emptyList(), identityProperty, consumer);
            }
        }
        if (includeVersion && persistentEntity.getVersion() != null) {
            traversePersistentProperties(Collections.emptyList(), persistentEntity.getVersion(), consumer);
        }
        for (PersistentProperty property : persistentEntity.getPersistentProperties()) {
            traversePersistentProperties(Collections.emptyList(), property, consumer);
        }
    }

    /**
     * Count possible embedded properties.
     *
     * @param property The property
     * @return the count
     */
    public static int countPersistentProperties(PersistentProperty property) {
        return countPersistentProperties(List.of(), property);
    }

    /**
     * Count possible embedded properties.
     *
     * @param property     The property
     * @param associations The associations
     * @return the count
     */
    public static int countPersistentProperties(List associations,
                                                PersistentProperty property) {
        int[] count = new int[1];
        traversePersistentProperties(associations, property, (ignore1, ignore2) -> count[0]++);
        return count[0];
    }

    public static void traversePersistentProperties(List associations,
                                                    PersistentProperty property,
                                                    BiConsumer, PersistentProperty> consumerProperty) {
        traversePersistentProperties(associations, property, true, consumerProperty);
    }

    public static void traversePersistentProperties(PersistentPropertyPath propertyPath,
                                                    BiConsumer, PersistentProperty> consumerProperty) {
        traversePersistentProperties(propertyPath.getAssociations(), propertyPath.getProperty(), true, consumerProperty);
    }

    public static void traverse(PersistentPropertyPath propertyPath, Consumer consumer) {
        BiConsumer, PersistentProperty> consumerProperty
            = (associations, property) -> consumer.accept(new PersistentPropertyPath(associations, property));
        traversePersistentProperties(propertyPath.getAssociations(), propertyPath.getProperty(), true, consumerProperty);
    }

    public static void traversePersistentProperties(PersistentPropertyPath propertyPath,
                                                    boolean traverseEmbedded,
                                                    BiConsumer, PersistentProperty> consumerProperty) {
        traversePersistentProperties(propertyPath.getAssociations(), propertyPath.getProperty(), traverseEmbedded, consumerProperty);
    }

    public static void traversePersistentProperties(List associations,
                                                    PersistentProperty property,
                                                    boolean traverseEmbedded,
                                                    BiConsumer, PersistentProperty> consumerProperty) {
        if (property instanceof Embedded embedded) {
            if (traverseEmbedded) {
                PersistentEntity embeddedEntity = embedded.getAssociatedEntity();
                Collection embeddedProperties = embeddedEntity.getPersistentProperties();
                List newAssociations = new ArrayList<>(associations);
                newAssociations.add((Association) property);
                for (PersistentProperty embeddedProperty : embeddedProperties) {
                    traversePersistentProperties(newAssociations, embeddedProperty, consumerProperty);
                }
            } else {
                consumerProperty.accept(associations, property);
            }
        } else if (property instanceof Association association) {
            if (association.isForeignKey()) {
                return;
            }
            List newAssociations = new ArrayList<>(associations);
            newAssociations.add((Association) property);
            PersistentEntity associatedEntity = association.getAssociatedEntity();
            PersistentProperty assocIdentity = associatedEntity.getIdentity();
            if (assocIdentity == null) {
                throw new IllegalStateException("Identity cannot be missing for: " + associatedEntity);
            }
            if (assocIdentity instanceof Association) {
                traversePersistentProperties(newAssociations, assocIdentity, consumerProperty);
            } else {
                // In case there is JoinColumn defined on property, we might use specified column
                // instead of association id
                PersistentProperty joinColumnAssocIdentity = getJoinColumnAssocIdentity(property, associatedEntity);
                if (joinColumnAssocIdentity != null) {
                    consumerProperty.accept(newAssociations, joinColumnAssocIdentity);
                } else {
                    consumerProperty.accept(newAssociations, assocIdentity);
                }
            }
        } else {
            consumerProperty.accept(associations, property);
        }
    }

    /**
     * Computes a dot separated property path for the given camel case path.
     *
     * @param path The camel case path, can contain underscore to indicate how we should traverse entity properties
     * @param entity the persistent entity
     * @return The dot separated version or null if it cannot be computed
     */
    public static Optional getPersistentPropertyPath(PersistentEntity entity, String path) {
        String decapitalizedPath = NameUtils.decapitalize(path);
        if (entity.getPropertyByName(decapitalizedPath) != null) {
            // First try to see if there is direct property on the entity
            return Optional.of(decapitalizedPath);
        }
        // Then see if path contains underscore to indicate which paths/entities to lookup
        String[] entityPaths = path.split(UNDERSCORE);
        if (entityPaths.length > 1) {
            String assocPath = entityPaths[0];
            PersistentProperty pp = entity.getPropertyByName(assocPath);
            if (pp instanceof Association assoc) {
                PersistentEntity assocEntity = assoc.getAssociatedEntity();
                String restPath = path.replaceFirst(assocPath + UNDERSCORE, "");
                Optional tailPath = getPersistentPropertyPath(assocEntity, restPath);
                if (tailPath.isPresent()) {
                    return Optional.of(assocPath + "." + tailPath.get());
                }
                throw new IllegalArgumentException("Invalid path [" + restPath + "] of [" + assocEntity + "]");
            }
        }
        return entity.getPath(path);
    }

    private static PersistentProperty getJoinColumnAssocIdentity(PersistentProperty property, PersistentEntity associatedEntity) {
        AnnotationMetadata propertyAnnotationMetadata = property.getAnnotationMetadata();
        AnnotationValue joinColumnsAnnotationValue = propertyAnnotationMetadata.getAnnotation(JoinColumns.class);
        if (joinColumnsAnnotationValue == null) {
            return null;
        }
        List> joinColumnsAnnotationValueAnnotations = joinColumnsAnnotationValue.getAnnotations(AnnotationMetadata.VALUE_MEMBER);
        if (joinColumnsAnnotationValueAnnotations.size() != 1) {
            // we can match only by one JoinColumn
            return null;
        }
        AnnotationValue joinColumnAnnotationValue = joinColumnsAnnotationValueAnnotations.get(0);
        String fieldName = joinColumnAnnotationValue.stringValue("referencedColumnName").orElse(null);
        if (fieldName == null) {
            return null;
        }
        Collection assocPersistentProperties = associatedEntity.getPersistentProperties();
        for (PersistentProperty assocPersistentProperty : assocPersistentProperties) {
            if (fieldName.equals(assocPersistentProperty.getPersistedName())) {
                return assocPersistentProperty;
            }
        }
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy