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

com.kenshoo.pl.entity.PersistenceLayer Maven / Gradle / Ivy

Go to download

A Java persistence layer based on JOOQ for high performance and business flow support.

There is a newer version: 0.1.121-jooq-3.16.3
Show newest version
package com.kenshoo.pl.entity;

import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.kenshoo.pl.entity.audit.AuditRecord;
import com.kenshoo.pl.entity.internal.*;
import com.kenshoo.pl.entity.internal.audit.RecursiveAuditRecordGenerator;
import com.kenshoo.pl.entity.internal.validators.ValidationFilter;
import com.kenshoo.pl.entity.spi.CurrentStateConsumer;
import com.kenshoo.pl.entity.spi.OutputGenerator;
import com.kenshoo.pl.entity.spi.ValidationException;
import org.jooq.DSLContext;
import org.jooq.lambda.Seq;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static com.kenshoo.pl.entity.ChangeOperation.*;
import static com.kenshoo.pl.entity.HierarchyKeyPopulator.*;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static org.jooq.lambda.Seq.seq;


public class PersistenceLayer> {

    private final PLContext plContext;
    private final FieldsToFetchBuilder fieldsToFetchBuilder;
    private DeletionCommandPopulator deletionCommandPopulator;
    private final RecursiveAuditRecordGenerator recursiveAuditRecordGenerator;

    public PersistenceLayer(final DSLContext dslContext) {
        this(new PLContext.Builder(dslContext).build());
    }

    public PersistenceLayer(final PLContext plContext) {
        this.plContext = plContext;
        this.fieldsToFetchBuilder = new FieldsToFetchBuilder<>();
        this.deletionCommandPopulator = new DeletionCommandPopulator(plContext);
        this.recursiveAuditRecordGenerator = new RecursiveAuditRecordGenerator();
    }

    public >
    CreateResult create(Collection> commands, ChangeFlowConfig flowConfig, UniqueKey primaryKey) {
        ChangeContext changeContext = makeChanges(commands, flowConfig);
        CreateResult results = toCreateResults(commands, changeContext);
        setIdentifiersToSuccessfulCommands(flowConfig, primaryKey, changeContext, results);
        return results;
    }

    public CreateResult> create(Collection> commands, ChangeFlowConfig flowConfig) {
        return create(commands, flowConfig, flowConfig.getEntityType().getPrimaryKey());
    }

    private >
    void setIdentifiersToSuccessfulCommands(ChangeFlowConfig flowConfig, UniqueKey primaryKey, ChangeContext changeContext, CreateResult results) {
        final Optional> optionalIdentityField = flowConfig.getPrimaryIdentityField();

        seq(results.iterator())
            .filter(EntityChangeResult::isSuccess)
            .map(EntityChangeResult::getCommand)
            .forEach(cmd -> {
                optionalIdentityField.ifPresent(idField -> populateIdentityField(cmd, changeContext, idField));
                cmd.setIdentifier(primaryKey.createValue(cmd));
            });
    }

    private >
    CreateResult toCreateResults(Collection> commands, ChangeContext changeContext) {
        return new CreateResult<>(
                    seq(commands).map(cmd -> new EntityCreateResult<>(cmd, changeContext.getValidationErrors(cmd))),
                    changeContext.getStats());
    }

    public > UpdateResult update(Collection> commands, ChangeFlowConfig flowConfig) {
        ChangeContext changeContext = makeChanges(commands, flowConfig);
        return new UpdateResult<>(
                seq(commands).map(cmd -> new EntityUpdateResult<>(cmd, changeContext.getValidationErrors(cmd))),
                changeContext.getStats());
    }

    public > DeleteResult delete(Collection> commands, ChangeFlowConfig flowConfig) {
        ChangeContext changeContext = makeChanges(commands, flowConfig);
        return new DeleteResult<>(
                seq(commands).map(cmd -> new EntityDeleteResult<>(cmd, changeContext.getValidationErrors(cmd))),
                changeContext.getStats());
    }

    public > InsertOnDuplicateUpdateResult upsert(Collection> commands, ChangeFlowConfig flowConfig) {
        ChangeContext changeContext = makeChanges(commands, flowConfig);
        InsertOnDuplicateUpdateResult results = toUpsertResults(commands, changeContext);
        populateIdentityFieldToSuccessfulUpserts(flowConfig, changeContext, results);
        return results;
    }

    private > InsertOnDuplicateUpdateResult toUpsertResults(Collection> commands, ChangeContext changeContext) {
        return new InsertOnDuplicateUpdateResult<>(
                    seq(commands).map(cmd -> new EntityInsertOnDuplicateUpdateResult<>(cmd, changeContext.getValidationErrors(cmd))),
                    changeContext.getStats());
    }

    private > void populateIdentityFieldToSuccessfulUpserts(ChangeFlowConfig flowConfig, ChangeContext changeContext, InsertOnDuplicateUpdateResult results) {
        flowConfig.getPrimaryIdentityField().ifPresent(identityField -> seq(results.iterator())
                .filter(EntityChangeResult::isSuccess)
                .map(EntityChangeResult::getCommand)
                .filter(cmd -> cmd.getChangeOperation() == CREATE)
                .forEach(cmd -> populateIdentityField(cmd, changeContext, identityField)));
    }

    private ChangeContext makeChanges(Collection> commands, ChangeFlowConfig flowConfig) {
        deletionCommandPopulator.handleRecursive(commands, flowConfig);
        ChangeContextImpl context = new ChangeContextImpl(Hierarchy.build(flowConfig), flowConfig.getFeatures());
        context.addFetchRequests(fieldsToFetchBuilder.build(commands, flowConfig));
        prepareRecursive(commands, context, flowConfig);
        Collection> validCmds = seq(commands).filter(cmd -> !context.containsError(cmd)).toList();
        ChangeContext overridingCtx = new OverridingContext(context);
        if (!validCmds.isEmpty()) {
            flowConfig.retryer().run((() -> dslContext().transaction((configuration) -> generateOutputRecursive(flowConfig, validCmds, overridingCtx))));
        }
        final Stream> auditRecords =
            recursiveAuditRecordGenerator.generateMany(flowConfig,
                                                       validCmds.stream(),
                                                       overridingCtx);
        plContext.auditRecordPublisher().publish(auditRecords);
        return overridingCtx;
    }

    private > void prepareRecursive(
            Collection> commands,
            ChangeContext context,
            ChangeFlowConfig flow) {

        prepareOneLayer(only(commands, withOperator(DELETE)), DELETE, context, flow);
        prepareOneLayer(only(commands, withOperator(UPDATE)), UPDATE, context, flow);

        commands.stream()
                .filter(cmd -> cmd.allowMissingEntity() && isMissing(cmd, context))
                .forEach(cmd -> cmd.updateOperator(CREATE));

        prepareOneLayer(only(commands, withOperator(CREATE)), CREATE, context, flow);

        List> validChanges = seq(commands).filter(cmd -> !context.containsErrorNonRecursive(cmd)).toList();

        populateParentKeysIntoChildren(context, validChanges);

        flow.childFlows().forEach(childFlow -> prepareChildFlowRecursive(validChanges, childFlow, context));
    }

    private > void populateParentKeysIntoChildren(ChangeContext context, Collection> commands) {
        new Builder()
                .with(context.getHierarchy())
                .whereParentFieldsAre(notAutoInc())
                .gettingValues(fromCommands()).build()
                .populateKeysToChildren(only(commands, withOperator(CREATE)));

        new Builder()
                .with(context.getHierarchy())
                .whereParentFieldsAre(anyField())
                .gettingValues(fromContext(context)).build()
                .populateKeysToChildren(only(commands, withOperator(UPDATE)));
    }

    private , CHILD extends EntityType> void prepareChildFlowRecursive(List> validChanges, ChangeFlowConfig childFlow, ChangeContext context) {
        prepareRecursive(validChanges.stream().flatMap(parent -> parent.getChildren(childFlow.getEntityType())).collect(toList()), context, childFlow);
    }

    private boolean isMissing(ChangeEntityCommand cmd, ChangeContext context) {
        return context.getEntity(cmd) == CurrentEntityState.EMPTY;
    }

    private > Collection> prepareOneLayer(Collection> commands, ChangeOperation changeOperation, ChangeContext changeContext, ChangeFlowConfig flowConfig) {

        if (commands.isEmpty()) {
            return emptyList();
        }

        if (!flowConfig.getEntityType().getSupportedOperation().supports(changeOperation)) {
            throw new IllegalStateException("Operation " + changeOperation + " is not supported by entity type");
        }

        commands = filterCommands(commands, flowConfig, changeOperation, changeContext);

        if (commands.isEmpty()) {
            return emptyList();
        }

        Stopwatch stopwatch = Stopwatch.createStarted();
        fetcher(flowConfig.getFeatures()).fetchEntities(commands, changeOperation, changeContext, flowConfig);
        changeContext.getStats().addFetchTime(stopwatch.elapsed(TimeUnit.MILLISECONDS));

        commands = filterCommands(commands, getSupportedFilters(flowConfig.getPostFetchFilters(), changeOperation), changeOperation, changeContext);
        commands = resolveSuppliersAndFilterErrors(commands, changeContext);
        commands = filterCommands(commands, getSupportedFilters(flowConfig.getPostSupplyFilters(), changeOperation), changeOperation, changeContext);
        enrichCommandsPostFetch(commands, flowConfig, changeOperation, changeContext);

        return validateChanges(commands, new ValidationFilter<>(flowConfig.getValidators()), changeOperation, changeContext);
    }

    private > EntitiesToContextFetcher fetcher(FeatureSet features) {
        return new EntitiesToContextFetcher(new EntitiesFetcher(dslContext(), features));
    }

    private , C extends ChangeEntityCommand> Collection resolveSuppliersAndFilterErrors(Collection commands, ChangeContext changeContext) {
        List validCommands = Lists.newArrayListWithCapacity(commands.size());

        for (C command : commands) {
            CurrentEntityState currentState = changeContext.getEntity(command);
            try {
                command.resolveSuppliers(currentState);
                validCommands.add(command);
            } catch (ValidationException e) {
                changeContext.addValidationError(command, e.getValidationError());
            }
        }

        return validCommands;
    }

    private , C extends ChangeEntityCommand> Collection filterCommands(Collection commands, ChangeFlowConfig flowConfig, ChangeOperation changeOperation, ChangeContext changeContext) {
        if(changeOperation == CREATE) {
            return Lists.newArrayList(new RequiredFieldsChangesFilter<>(flowConfig.getRequiredRelationFields()).filter(commands, changeOperation, changeContext));
        } else {
            return commands;
        }
    }

    private > void enrichCommandsPostFetch(Collection> commands, ChangeFlowConfig flowConfig, ChangeOperation changeOperation, ChangeContext changeContext) {
        flowConfig.getPostFetchCommandEnrichers().stream()
                .filter(CurrentStateConsumer.supporting(changeOperation))
                .forEach(enricher -> enricher.enrich(commands,  changeOperation, changeContext));
    }


    private > List> getSupportedFilters(List> filters, ChangeOperation changeOperation) {
        return filters.stream().filter(CurrentStateConsumer.supporting(changeOperation)).collect(toList());
    }

    private , T extends ChangeEntityCommand> Collection filterCommands(Collection changes, List> changesFilters, ChangeOperation changeOperation, ChangeContext changeContext) {
        Collection filteredChanges = ImmutableList.copyOf(changes);
        for (ChangesFilter changesFilter : changesFilters) {
            filteredChanges = Lists.newArrayList(changesFilter.filter(filteredChanges, changeOperation, changeContext));
        }
        return filteredChanges;
    }

    private > Collection> validateChanges(Collection> changes, ChangesFilter validationFilter, ChangeOperation changeOperation, ChangeContext changeContext) {
        return Lists.newArrayList(validationFilter.filter(changes, changeOperation, changeContext));
    }

    private > void generateOutputRecursive(ChangeFlowConfig flowConfig, Collection> commands, ChangeContext context) {
        for (OutputGenerator outputGenerator : flowConfig.getOutputGenerators()) {
            Seq.of(DELETE, UPDATE, CREATE)
                    .filter(CurrentStateConsumer.supporting(outputGenerator))
                    .map(op -> seq(commands).filter(cmd -> cmd.getChangeOperation() == op).toList())
                    .filter(list -> !list.isEmpty())
                    .forEach(list -> outputGenerator.generate(list, list.get(0).getChangeOperation(), context));
        }

        // invoke recursive
        flowConfig.childFlows().forEach(childFlow -> generateOutputChildFlowRecursive(commands, childFlow, context));
    }

    private , CHILD extends EntityType> void generateOutputChildFlowRecursive(Collection> entityChanges, ChangeFlowConfig childFlow, ChangeContext context) {
        generateOutputRecursive(childFlow, entityChanges.stream().flatMap(parent -> parent.getChildren(childFlow.getEntityType())).collect(toList()), context);
    }

    private  List only(Iterable items, Predicate predicate) {
        return seq(items).filter(predicate).toList();
    }

    private Predicate> withOperator(ChangeOperation op) {
        return cmd -> op == cmd.getChangeOperation();
    }

    private void populateIdentityField(final ChangeEntityCommand cmd, final ChangeContext changeContext, final EntityField idField) {
        final CurrentEntityState currentState = Optional.ofNullable(changeContext.getEntity(cmd))
                                      .orElseThrow(() -> new IllegalStateException("Could not find entity of command in the change context"));
        cmd.set(idField,  currentState.get(idField));
    }

    private DSLContext dslContext() {
        return plContext.dslContext();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy