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

io.micronaut.data.runtime.operations.internal.ReactiveCascadeOperations Maven / Gradle / Ivy

There is a newer version: 4.10.5
Show 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.runtime.operations.internal;

import io.micronaut.core.convert.ConversionService;
import io.micronaut.data.annotation.Relation;
import io.micronaut.data.model.Association;
import io.micronaut.data.model.query.builder.sql.SqlQueryBuilder;
import io.micronaut.data.model.runtime.RuntimeAssociation;
import io.micronaut.data.model.runtime.RuntimePersistentEntity;
import io.micronaut.data.model.runtime.RuntimePersistentProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Reactive cascade operations.
 *
 * @param  The operation context.
 * @author Denis Stepanov
 * @since 3.3
 */
public final class ReactiveCascadeOperations extends AbstractCascadeOperations {

    private static final Logger LOG = LoggerFactory.getLogger(ReactiveCascadeOperations.class);

    private final ReactiveCascadeOperationsHelper helper;

    /**
     * The default cosntructor.
     *
     * @param conversionService The conversion service
     * @param helper            The helper
     */
    public ReactiveCascadeOperations(ConversionService conversionService, ReactiveCascadeOperationsHelper helper) {
        super(conversionService);
        this.helper = helper;
    }

    /**
     * Cascade the entity operation.
     *
     * @param ctx              The context
     * @param entity           The entity instance
     * @param persistentEntity The persistent entity
     * @param isPost           Is post cascade?
     * @param cascadeType      The cascade type
     * @param               The entity type
     * @return The entity instance
     */
    public  Mono cascadeEntity(Ctx ctx,
                                     T entity,
                                     RuntimePersistentEntity persistentEntity,
                                     boolean isPost,
                                     Relation.Cascade cascadeType) {
        List cascadeOps = new ArrayList<>();

        cascade(ctx.annotationMetadata, ctx.repositoryType, isPost, cascadeType,
                CascadeContext.of(ctx.associations, entity, (RuntimePersistentEntity) persistentEntity), persistentEntity, entity, cascadeOps);

        Mono monoEntity = Mono.just(entity);

        for (CascadeOp cascadeOp : cascadeOps) {
            if (cascadeOp instanceof CascadeOneOp cascadeOneOp) {
                Object child = cascadeOneOp.child;
                RuntimePersistentEntity childPersistentEntity = cascadeOneOp.childPersistentEntity;
                RuntimeAssociation association = (RuntimeAssociation) cascadeOp.ctx.getAssociation();

                if (ctx.persisted.contains(child)) {
                    continue;
                }

                monoEntity = monoEntity.flatMap(e -> {

                RuntimePersistentProperty identity = childPersistentEntity.getIdentity();
                boolean hasId = identity.getProperty().get(child) != null;
                Mono thisEntity;
                Mono childMono;
                if ((!hasId || identity instanceof Association) && (cascadeType == Relation.Cascade.PERSIST)) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Cascading one PERSIST for '{}' association: '{}'", persistentEntity.getName(), cascadeOp.ctx.associations);
                    }
                    Mono persisted = helper.persistOne(ctx, child, childPersistentEntity).cache();
                    thisEntity = persisted.map(persistedEntity -> afterCascadedOne(e, cascadeOp.ctx.associations, child, persistedEntity));
                    childMono = persisted;
                } else if (hasId && (cascadeType == Relation.Cascade.UPDATE)) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Cascading one UPDATE for '{}' ({}) association: '{}'", persistentEntity.getName(),
                                persistentEntity.getIdentity().getProperty().get(entity), cascadeOp.ctx.associations);
                    }
                    Mono updated = helper.updateOne(ctx, child, childPersistentEntity).cache();
                    thisEntity = updated.map(updatedEntity -> afterCascadedOne(e, cascadeOp.ctx.associations, child, updatedEntity));
                    childMono = updated;
                } else {
                    childMono = Mono.just(child);
                    thisEntity = Mono.just(e);
                }

                if (!hasId
                        && (cascadeType == Relation.Cascade.PERSIST || cascadeType == Relation.Cascade.UPDATE)
                        && SqlQueryBuilder.isForeignKeyWithJoinTable(association)) {
                    return childMono.flatMap(c -> {
                        if (ctx.persisted.contains(c)) {
                            return Mono.just(e);
                        }
                        ctx.persisted.add(c);
                        return thisEntity.flatMap(e2 -> {
                            Mono op = helper.persistManyAssociation(ctx, association, e2, (RuntimePersistentEntity) persistentEntity, c, childPersistentEntity);
                            return op.thenReturn(e2);
                        });
                    });
                } else {
                    return childMono.flatMap(c -> {
                        ctx.persisted.add(c);
                        return thisEntity;
                    });
                }
                });

            } else if (cascadeOp instanceof CascadeManyOp cascadeManyOp) {
                RuntimePersistentEntity childPersistentEntity = cascadeManyOp.childPersistentEntity;

                if (cascadeType == Relation.Cascade.UPDATE) {
                    monoEntity = updateChildren(ctx, monoEntity, cascadeOp, cascadeManyOp, childPersistentEntity, e -> {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Cascading many UPDATE for '{}' association: '{}'", persistentEntity.getName(), cascadeOp.ctx.associations);
                        }
                        Flux childrenFlux = Flux.empty();
                        for (Object child : cascadeManyOp.children) {
                            if (ctx.persisted.contains(child)) {
                                continue;
                            }
                            Mono modifiedEntity;
                            if (childPersistentEntity.getIdentity().getProperty().get(child) == null) {
                                modifiedEntity = helper.persistOne(ctx, child, childPersistentEntity);
                            } else {
                                modifiedEntity = helper.updateOne(ctx, child, childPersistentEntity);
                            }
                            childrenFlux = childrenFlux.concatWith(modifiedEntity);
                        }
                        return childrenFlux.collectList();
                    });
                } else if (cascadeType == Relation.Cascade.PERSIST) {
                    if (helper.isSupportsBatchInsert(ctx, persistentEntity)) {
                        monoEntity = updateChildren(ctx, monoEntity, cascadeOp, cascadeManyOp, childPersistentEntity, e -> {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Cascading many PERSIST for '{}' association: '{}'", persistentEntity.getName(), cascadeOp.ctx.associations);
                            }
                            RuntimePersistentProperty identity = childPersistentEntity.getIdentity();
                            Predicate veto = val -> ctx.persisted.contains(val) || identity.getProperty().get(val) != null && !(identity instanceof Association);
                            Flux childrenFlux = helper.persistBatch(ctx, cascadeManyOp.children, childPersistentEntity, veto);
                            // Concat children inserted now with children that were persisted before
                            for (Object child : cascadeManyOp.children) {
                                if (veto.test(child)) {
                                    childrenFlux = childrenFlux.concatWith(Flux.just(child));
                                }
                            }
                            return childrenFlux.collectList();
                        });
                    } else {
                        monoEntity = updateChildren(ctx, monoEntity, cascadeOp, cascadeManyOp, childPersistentEntity, e -> {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Cascading many PERSIST for '{}' association: '{}'", persistentEntity.getName(), cascadeOp.ctx.associations);
                            }

                            Flux childrenFlux = Flux.empty();
                            for (Object child : cascadeManyOp.children) {
                                if (ctx.persisted.contains(child) || childPersistentEntity.getIdentity().getProperty().get(child) != null) {
                                    childrenFlux = childrenFlux.concatWith(Mono.just(child));
                                    continue;
                                }
                                Mono persisted = helper.persistOne(ctx, child, childPersistentEntity);
                                childrenFlux = childrenFlux.concatWith(persisted);
                            }
                            return childrenFlux.collectList();
                        });
                    }
                }
            }
        }
        return monoEntity;
    }

    private  Mono updateChildren(Ctx ctx,
                                       Mono monoEntity,
                                       CascadeOp cascadeOp,
                                       CascadeManyOp cascadeManyOp,
                                       RuntimePersistentEntity childPersistentEntity,
                                       Function>> fn) {
        monoEntity = monoEntity.flatMap(e -> fn.apply(e).flatMap(newChildren -> {
            T entityAfterCascade = afterCascadedMany(e, cascadeOp.ctx.associations, cascadeManyOp.children, newChildren);
            RuntimeAssociation association = (RuntimeAssociation) cascadeOp.ctx.getAssociation();
            if (SqlQueryBuilder.isForeignKeyWithJoinTable(association)) {
                if (helper.isSupportsBatchInsert(ctx, cascadeOp.ctx.parentPersistentEntity)) {
                    Predicate veto = ctx.persisted::contains;
                    Mono op = helper.persistManyAssociationBatch(ctx, association, cascadeOp.ctx.parent, cascadeOp.ctx.parentPersistentEntity, newChildren, childPersistentEntity, veto);
                    return op.thenReturn(entityAfterCascade);
                } else {
                    Mono res = Mono.just(entityAfterCascade);
                    for (Object child : newChildren) {
                        if (ctx.persisted.contains(child)) {
                            continue;
                        }
                        Mono op = helper.persistManyAssociation(ctx, association, cascadeOp.ctx.parent, cascadeOp.ctx.parentPersistentEntity, child, childPersistentEntity);
                        res = res.flatMap(op::thenReturn);
                    }
                    return res;
                }
            }
            ctx.persisted.addAll(newChildren);
            return Mono.just(entityAfterCascade);
        }));
        return monoEntity;
    }

    /**
     * The cascade operations helper.
     *
     * @param  The operation context.
     */
    public interface ReactiveCascadeOperationsHelper {

        /**
         * Is supports batch insert.
         *
         * @param ctx              The context
         * @param persistentEntity The persistent entity
         * @return True if supports
         */
        default boolean isSupportsBatchInsert(Ctx ctx, RuntimePersistentEntity persistentEntity) {
            return true;
        }

        /**
         * Is supports batch update.
         *
         * @param ctx              The context
         * @param persistentEntity The persistent entity
         * @return True if supports
         */
        default boolean isSupportsBatchUpdate(Ctx ctx, RuntimePersistentEntity persistentEntity) {
            return true;
        }

        /**
         * Is supports batch delete.
         *
         * @param ctx              The context
         * @param persistentEntity The persistent entity
         * @return True if supports
         */
        default boolean isSupportsBatchDelete(Ctx ctx, RuntimePersistentEntity persistentEntity) {
            return true;
        }

        /**
         * Persist one entity during cascade.
         *
         * @param ctx              The context
         * @param entityValue      The entity value
         * @param persistentEntity The persistent entity
         * @param               The entity type
         * @return The entity value
         */
         Mono persistOne(Ctx ctx, T entityValue, RuntimePersistentEntity persistentEntity);

        /**
         * Persist multiple entities in batch during cascade.
         *
         * @param ctx              The context
         * @param entityValues     The entity values
         * @param persistentEntity The persistent entity
         * @param predicate        The veto predicate
         * @param               The entity type
         * @return The entity values
         */
         Flux persistBatch(Ctx ctx,
                                 Iterable entityValues,
                                 RuntimePersistentEntity persistentEntity,
                                 Predicate predicate);

        /**
         * Update one entity during cascade.
         *
         * @param ctx              The context
         * @param entityValue      The entity value
         * @param persistentEntity The persistent entity
         * @param               The entity type
         * @return The entity value
         */
         Mono updateOne(Ctx ctx, T entityValue, RuntimePersistentEntity persistentEntity);

        /**
         * Persist JOIN table relationship.
         *
         * @param ctx                    The context
         * @param runtimeAssociation     The association
         * @param parentEntityValue      The parent entity value
         * @param parentPersistentEntity The parent persistent entity
         * @param childEntityValue       The child entity value
         * @param childPersistentEntity  The child persistent entity
         * @return The empty mono
         */
        Mono persistManyAssociation(Ctx ctx,
                                          RuntimeAssociation runtimeAssociation,
                                          Object parentEntityValue, RuntimePersistentEntity parentPersistentEntity,
                                          Object childEntityValue, RuntimePersistentEntity childPersistentEntity);

        /**
         * Persist JOIN table relationships in batch.
         *
         * @param ctx                    The context
         * @param runtimeAssociation     The association
         * @param parentEntityValue      The parent entity value
         * @param parentPersistentEntity The parent persistent entity
         * @param childEntityValues      The child entity values
         * @param childPersistentEntity  The child persistent entity
         * @param veto                   The veto predicate
         * @return The empty mono
         */
        Mono persistManyAssociationBatch(Ctx ctx,
                                               RuntimeAssociation runtimeAssociation,
                                               Object parentEntityValue, RuntimePersistentEntity parentPersistentEntity,
                                               Iterable childEntityValues, RuntimePersistentEntity childPersistentEntity,
                                               Predicate veto);
    }

}