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

com.kenshoo.pl.entity.internal.DbCommandsOutputGenerator 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.internal;

import com.google.common.base.Stopwatch;
import com.kenshoo.jooq.DataTable;
import com.kenshoo.pl.data.*;
import com.kenshoo.pl.entity.*;
import com.kenshoo.pl.entity.spi.OutputGenerator;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.jooq.ForeignKey;
import org.jooq.Record;
import org.jooq.TableField;

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

import static com.kenshoo.pl.entity.ChangeOperation.CREATE;
import static com.kenshoo.pl.entity.HierarchyKeyPopulator.autoInc;
import static com.kenshoo.pl.entity.HierarchyKeyPopulator.fromContext;
import static org.jooq.lambda.Seq.seq;
import static org.jooq.lambda.function.Functions.not;


public class DbCommandsOutputGenerator> implements OutputGenerator {

    private final E entityType;
    private final CommandsExecutor commandsExecutor;

    public DbCommandsOutputGenerator(E entityType, PLContext plContext) {
        this.commandsExecutor = CommandsExecutor.of(plContext.dslContext());
        this.entityType = entityType;
    }

    @Override
    public void generate(Collection> entityChanges, ChangeOperation operator, ChangeContext changeContext) {
        Stopwatch stopwatch = Stopwatch.createStarted();

        if (operator == ChangeOperation.DELETE) {
            generateForDelete(changeContext, entityChanges);
        } else {
            final ChangesContainer primaryTableCommands =
                    generateForCreateOrUpdate(entityChanges,
                            this::isOfPrimaryTable,
                            operator,
                            changeContext);

            entityType.getPrimaryIdentityField().ifPresent(identityField -> {
                        if (operator == CREATE) {
                            populateGeneratedIdsToContext(identityField,
                                    entityChanges,
                                    changeContext,
                                    primaryTableCommands);

                            //noinspection unchecked
                            new HierarchyKeyPopulator.Builder()
                                    .with(changeContext.getHierarchy())
                                    .whereParentFieldsAre(autoInc())
                                    .gettingValues(fromContext(changeContext)).build()
                                    .populateKeysToChildren((Collection>)entityChanges);
                        }
                    }
            );

            generateForCreateOrUpdate(entityChanges,
                    not(this::isOfPrimaryTable),
                    operator,
                    changeContext);
        }

        changeContext.getStats().addUpdateTime(stopwatch.elapsed(TimeUnit.MILLISECONDS));
    }

    private void populateGeneratedIdsToContext(final EntityField identityField,
                                               Collection> entityChanges,
                                               ChangeContext changeContext,
                                               ChangesContainer changesContainer) {
        final TableField identityTableField = getFirstTableField(identityField);

        seq(entityChanges)
                .map(change -> ImmutablePair.of(change, changesContainer.getInsert(entityType.getPrimaryTable(), change)))
                .filter(pair -> pair.getRight().isPresent())
                .forEach(pair -> {
                    final CreateRecordCommand cmd = pair.getRight().get();
                    Object generatedValue = cmd.get(identityTableField);
                    changeContext.addEntity(pair.getLeft(), new EntityWithGeneratedId(identityField, generatedValue));
                });
    }

    private TableField getFirstTableField(final EntityField entityField) {
        return entityField.getDbAdapter().getTableFields()
                .findFirst()
                .orElseThrow(() -> new IllegalStateException("No table fields found for an entity field"));
    }

    private boolean isOfPrimaryTable(FieldChange f) {
        return f.getField().getDbAdapter().getTable() == entityType.getPrimaryTable();
    }

    private  void translateChange(EntityChange entityChange, FieldChange change, ChangesContainer changesContainer, ChangeOperation changeOperation, ChangeContext changeContext) {
        EntityField entityField = change.getField();
        if (!entityField.isVirtual()) {
            translateChange(entityChange, change, entityField, changesContainer, changeOperation, changeContext);
        }
    }

    private  void translateChange(final EntityChange entityChange, final FieldChange change, final EntityField entityField, final ChangesContainer changesContainer, final ChangeOperation changeOperation, final ChangeContext changeContext) {
        final DataTable fieldTable = entityField.getDbAdapter().getTable();
        final DataTable primaryTable = entityType.getPrimaryTable();
        AbstractRecordCommand recordCommand;
        if (fieldTable == primaryTable) {
            if (changeOperation == CREATE) {
                recordCommand = changesContainer.getInsert(primaryTable, entityChange, () -> newCreateRecord(entityChange));
            } else {
                recordCommand = changesContainer.getUpdate(primaryTable, entityChange, () -> new UpdateRecordCommand(primaryTable, getDatabaseId(entityChange)));
            }
        } else {
            recordCommand = changesContainer.getInsertOnDuplicateUpdate(fieldTable, entityChange, () -> {
                CreateRecordCommand createRecordCommand = new CreateRecordCommand(fieldTable);
                populate(foreignKeyValues(entityChange, changeOperation, changeContext, fieldTable), createRecordCommand);
                return createRecordCommand;
            });

        }
        populateFieldChange(change, recordCommand);
    }

    private CreateRecordCommand newCreateRecord(EntityChange entityChange) {
        CreateRecordCommand cmd = new CreateRecordCommand(entityType.getPrimaryTable());
        populateParentKeys(entityChange, cmd);
        return cmd;
    }

    private  void populateFieldChange(FieldChange change, AbstractRecordCommand recordCommand) {
        T value = change.getValue();
        Iterator> tableFields = change.getField().getDbAdapter().getTableFields().iterator();
        Iterator values = change.getField().getDbAdapter().getDbValues(value).iterator();
        while (tableFields.hasNext()) {
            //noinspection unchecked
            recordCommand.set((TableField) tableFields.next(), values.next());
        }
    }

    @Override
    public Stream> requiredFields(Collection> fieldsToUpdate, ChangeOperation changeOperation) {
        // If update, find which secondary tables are affected.
        // For those secondary tables take their foreign keys to primary, translate referenced fields of primary to EntityFields and add them to fields to fetch
        if (changeOperation == ChangeOperation.UPDATE) {
            DataTable primaryTable = entityType.getPrimaryTable();
            if (isFieldsInSecondaryTables(fieldsToUpdate, primaryTable)) {
                return getPrimaryKeyFields(entityType);
            }
        }

        return Stream.empty();
    }

    private boolean isFieldsInSecondaryTables(Collection> fieldsToUpdate, DataTable primaryTable) {
        return fieldsToUpdate.stream().anyMatch(field -> field.getDbAdapter().getTable() != primaryTable);
    }

    private Stream> getPrimaryKeyFields(E entityType) {
        DataTable primaryTable = entityType.getPrimaryTable();
        List> primaryKeyFields = primaryTable.getPrimaryKey().getFields();
        return entityType.getFields()
                .filter(entityField -> entityField.getDbAdapter().getTableFields().anyMatch(primaryKeyFields::contains))
                .map(entityField -> (EntityField) entityField);
    }

    private void populate(DatabaseId id, AbstractRecordCommand toRecord) {
        for (int i = 0; i < id.getTableFields().length; i++) {
            //noinspection unchecked
            TableField field = (TableField) id.getTableFields()[i];
            toRecord.set(field, id.getValues()[i]);
        }
    }

    private DatabaseId foreignKeyValues(EntityChange cmd, ChangeOperation changeOperation, ChangeContext context, DataTable childTable) {
        ForeignKey foreignKey = childTable.getForeignKey(((ChangeEntityCommand) cmd).getEntityType().getPrimaryTable());
        Collection> parentFields = entityType(cmd).findFields(foreignKey.getKey().getFields());
        boolean hasIdentity = entityType.getPrimaryIdentityField().isPresent();
        Object[] values = changeOperation == CREATE && !hasIdentity ? EntityDbUtil.getFieldValues(parentFields, cmd) : EntityDbUtil.getFieldValues(parentFields, context.getEntity(cmd));
        if (foreignKey.getFields().size() != values.length) {
            throw new IllegalStateException("Foreign key from " + childTable.getName() + " doesn't have the same number of fields as " + foreignKey);
        }
        return new DatabaseId(
                foreignKey.getFields().toArray(new TableField[foreignKey.getFields().size()]),
                values);
    }

    private EntityType entityType(EntityChange cmd) {
        return ((ChangeEntityCommand)cmd).getEntityType();
    }

    private void populateParentKeys(EntityChange entityChange, AbstractRecordCommand recordCommand) {
        if (entityChange.getKeysToParent() != null) {
            DatabaseId parentIdValues = EntityDbUtil.getDatabaseId(entityChange.getKeysToParent());
            populate(parentIdValues, recordCommand);
        }
    }

    private DatabaseId getDatabaseId(EntityChange entityChange) {
        DatabaseId databaseId = EntityDbUtil.getDatabaseId(entityChange.getIdentifier());
        Identifier keysToParent = entityChange.getKeysToParent();
        if(keysToParent != null) {
            databaseId = databaseId.append(EntityDbUtil.getDatabaseId(entityChange.getKeysToParent()));
        }
        return databaseId;
    }

    private void generateForDelete(final ChangeContext changeContext,
                                   final Iterable> entityChanges) {
        ChangesContainer changesContainer = new ChangesContainer(entityType.onDuplicateKey());
        entityChanges.forEach( entityChange ->
                changesContainer.getDelete(entityType.getPrimaryTable(),
                        entityChange,
                        () -> new DeleteRecordCommand(entityType.getPrimaryTable(),
                                getDatabaseId(entityChange))));
        changesContainer.commit(commandsExecutor, changeContext.getStats());
    }

    private ChangesContainer generateForCreateOrUpdate(final Iterable> entityChanges,
                                                       final Predicate> filter,
                                                       final ChangeOperation operator,
                                                       final ChangeContext changeContext) {

        final ChangesContainer tableCommands = new ChangesContainer(entityType.onDuplicateKey());

        seq(entityChanges).forEach(cmd ->
                cmd.getChanges()
                        .filter(filter)
                        .forEach(fieldChange -> translateChange(cmd,
                                fieldChange,
                                tableCommands,
                                operator,
                                changeContext)));

        tableCommands.commit(commandsExecutor, changeContext.getStats());
        return tableCommands;
    }
}