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

com.kenshoo.pl.entity.internal.EntitiesFetcher 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.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.kenshoo.jooq.*;
import com.kenshoo.pl.data.ImpersonatorTable;
import com.kenshoo.pl.entity.UniqueKey;
import com.kenshoo.pl.entity.*;
import com.kenshoo.pl.entity.internal.fetch.*;
import org.jooq.*;
import org.jooq.lambda.Seq;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.kenshoo.pl.entity.Feature.FetchMany;
import static java.util.Objects.requireNonNull;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.Validate.notEmpty;
import static org.jooq.lambda.Seq.seq;


public class EntitiesFetcher {

    private final DSLContext dslContext;
    private final FeatureSet features;
    private final OldEntityFetcher oldEntityFetcher;

    public EntitiesFetcher(DSLContext dslContext) {
        this(dslContext, FeatureSet.EMPTY);
    }

    public EntitiesFetcher(DSLContext dslContext, FeatureSet features) {
        this.dslContext = dslContext;
        this.features = features;
        this.oldEntityFetcher = new OldEntityFetcher(dslContext);
    }

    public > Map, CurrentEntityState> fetchEntitiesByIds(final Collection> ids,
                                                                                               final EntityField... fieldsToFetchArgs) {
        return fetchEntitiesByIds(ids, ImmutableList.copyOf(fieldsToFetchArgs));
    }

    public > Map, CurrentEntityState> fetchEntitiesByIds(final Collection> ids,
                                                                                               final Collection> fieldsToFetch) {
        if (!features.isEnabled(FetchMany)) {
            return oldEntityFetcher.fetchEntitiesByIds(ids, fieldsToFetch);
        }

        if (ids.isEmpty()) {
            return Collections.emptyMap();
        }

        final UniqueKey uniqueKey = ids.iterator().next().getUniqueKey();
        final AliasedKey aliasedKey = new AliasedKey<>(uniqueKey);

        return fetchEntities(uniqueKey.getEntityType().getPrimaryTable(), aliasedKey, fieldsToFetch, query -> query.whereIdsIn(ids));
    }

    public > List fetch(final EntityType entityType,
                                                                    final PLCondition plCondition,
                                                                    final EntityField... fieldsToFetch) {

        requireNonNull(entityType, "An entity type must be provided");
        requireNonNull(plCondition, "A condition must be provided");
        notEmpty(fieldsToFetch, "There must be at least one field to fetch");

        if (!features.isEnabled(FetchMany)) {
            return oldEntityFetcher.fetch(entityType, plCondition, fieldsToFetch);
        }
        final AliasedKey aliasedKey = new AliasedKey<>(entityType.getPrimaryKey());

        final List> allFieldsToFetch = Seq.concat(Seq.of(fieldsToFetch), seq(plCondition.getFields())).distinct().collect(Collectors.toList());

        return Lists.newArrayList(fetchEntities(entityType.getPrimaryTable(), aliasedKey, allFieldsToFetch, query -> query.withCondition(plCondition.getJooqCondition())).values());
    }

    public List fetch(final EntityType entityType,
                                          final Collection> keys,
                                          final PLCondition plCondition,
                                          final EntityField... fieldsToFetch) {
        requireNonNull(entityType, "An entity type must be provided");
        notEmpty(keys, "There must be at least one keys to fetch");
        requireNonNull(plCondition, "A condition must be provided");
        notEmpty(fieldsToFetch, "There must be at least one field to fetch");

        if (!features.isEnabled(FetchMany)) {
            return oldEntityFetcher.fetch(entityType, keys, plCondition, fieldsToFetch);
        }

        final AliasedKey aliasedKey = new AliasedKey<>(entityType.getPrimaryKey());

        final List> allFieldsToFetch = Seq.concat(Seq.of(fieldsToFetch), seq(plCondition.getFields()), fieldsOf(keys)).distinct().collect(Collectors.toList());

        return Lists.newArrayList(fetchEntities(entityType.getPrimaryTable(), aliasedKey, allFieldsToFetch, query -> query.whereIdsIn(keys).withCondition(plCondition.getJooqCondition())).values());
    }

    public > Map, CurrentEntityState> fetchEntitiesByForeignKeys(E entityType, UniqueKey foreignUniqueKey, Collection> keys, Collection> fieldsToFetch) {
        if (!features.isEnabled(FetchMany)) {
            return oldEntityFetcher.fetchEntitiesByForeignKeys(entityType, foreignUniqueKey, keys, fieldsToFetch);
        }

        try (final TempTableResource foreignKeysTable = createForeignKeysTable(entityType.getPrimaryTable(), foreignUniqueKey, keys)) {
            final AliasedKey aliasedKey = new AliasedKey<>(foreignUniqueKey, foreignKeysTable);
            final DataTable startingTable = foreignKeysTable.getTable();

            return fetchEntities(startingTable, aliasedKey, fieldsToFetch, QueryBuilder::withoutPartitions);
        }
    }

    public , PE extends PartialEntity, ID extends Identifier> Map fetchPartialEntities(E entityType, Collection ids, final Class entityIface) {
        if (!features.isEnabled(FetchMany)) {
            return oldEntityFetcher.fetchPartialEntities(entityType, ids, entityIface);
        }

        final Map> entityMethodsMap = EntityTypeReflectionUtil.getMethodsMap(entityType, entityIface);
        Map, CurrentEntityState> entitiesMap = fetchEntitiesByIds(ids, entityMethodsMap.values());

        return seq(ids).filter(entitiesMap::containsKey).collect(toMap(identity(), id -> createInstance(entityIface, entityMethodsMap, entitiesMap.get(id))));
    }

    public , PE extends PartialEntity> List fetchByCondition(E entityType, final Condition condition, final Class entityIface) {
        if (!features.isEnabled(FetchMany)) {
            return oldEntityFetcher.fetchByCondition(entityType, condition, entityIface);
        }

        final Map> entityMethodsMap = EntityTypeReflectionUtil.getMethodsMap(entityType, entityIface);
        final AliasedKey aliasedKey = new AliasedKey<>(entityType.getPrimaryKey());

        Collection entities = fetchEntities(entityType.getPrimaryTable(), aliasedKey, entityMethodsMap.values(), query -> query.withCondition(condition)).values();

        return entities.stream()
                .map(entity -> createInstance(entityIface, entityMethodsMap, entity))
                .collect(toList());
    }

    private > Map, CurrentEntityState> fetchEntities(
            final DataTable startingTable,
            final AliasedKey aliasedKey,
            final Collection> fieldsToFetch,
            Consumer> queryModifier
    ) {
        final ExecutionPlan executionPlan = new ExecutionPlan(startingTable, fieldsToFetch);
        final ExecutionPlan.OneToOnePlan oneToOnePlan = executionPlan.getOneToOnePlan();

        final QueryBuilder mainQueryBuilder = new QueryBuilder(dslContext).selecting(selectFieldsOf(oneToOnePlan.getFields(), aliasedKey))
                .from(startingTable)
                .innerJoin(oneToOnePlan.getPaths())
                .leftJoin(oneToOnePlan.getSecondaryTableRelations());
        queryModifier.accept(mainQueryBuilder);

        final Map, CurrentEntityState> entities = fetchMainEntities(aliasedKey, oneToOnePlan, mainQueryBuilder);

        executionPlan.getManyToOnePlans().forEach(plan -> {
            final QueryBuilder subQueryBuilder = new QueryBuilder(dslContext).selecting(selectFieldsOf(plan.getFields(), aliasedKey))
                    .from(startingTable)
                    .innerJoin(plan.getPath());
            queryModifier.accept(subQueryBuilder);

            fetchAndPopulateSubEntities(aliasedKey, entities, plan, subQueryBuilder);
        });

        return entities;
    }

    private , SUB extends EntityType> void fetchAndPopulateSubEntities(AliasedKey aliasedKey, Map, CurrentEntityState> entities, ExecutionPlan.ManyToOnePlan plan, QueryBuilder queryBuilder) {
        try (QueryExtension> queryExtender = queryBuilder.build()) {
            Map, List>> multiValuesMap = fetchMultiValuesMap(queryExtender.getQuery(), aliasedKey, plan.getFields());
            multiValuesMap.forEach((Identifier id, List> multiValues) -> {
                final SUB subEntityType = entityTypeOf(plan.getFields());
                ((CurrentEntityMutableState) entities.get(id)).add(subEntityType, multiValues);
            });
        }
    }

    private > Map, CurrentEntityState> fetchMainEntities(AliasedKey aliasedKey, ExecutionPlan.OneToOnePlan oneToOnePlan, QueryBuilder queryBuilder) {
        try (QueryExtension> queryExtender = queryBuilder.build()) {
            return fetchEntitiesMap(queryExtender.getQuery(), aliasedKey, oneToOnePlan.getFields());
        }
    }

    private > Map, CurrentEntityState> fetchEntitiesMap(ResultQuery query, AliasedKey aliasedKey, List> fields) {
        return query.fetchMap(record -> RecordReader.createKey(record, aliasedKey), record -> RecordReader.createEntity(record, fields));
    }

    private 
, SUB extends EntityType> Map, List>> fetchMultiValuesMap(ResultQuery query, AliasedKey
aliasedKey, List> fields) { final Map, List>> multiValuesMap = new HashMap<>(); query.fetchInto(record -> { Identifier
key = RecordReader.createKey(record, aliasedKey); multiValuesMap.computeIfAbsent(key, k -> Lists.newArrayList()); multiValuesMap.get(key).add(RecordReader.createFieldsValueMap(record, fields)); }); return multiValuesMap; } public > TempTableResource createForeignKeysTable(final DataTable primaryTable, final UniqueKey foreignUniqueKey, final Collection> keys) { ImpersonatorTable impersonatorTable = new ImpersonatorTable(primaryTable); foreignUniqueKey.getTableFields().forEach(impersonatorTable::createField); return TempTableHelper.tempInMemoryTable(dslContext, impersonatorTable, batchBindStep -> { for (Identifier key : keys) { EntityField[] keyFields = foreignUniqueKey.getFields(); List values = new ArrayList<>(); for (EntityField field : keyFields) { addToValues(key, field, values); } batchBindStep.bind(values.toArray()); } } ); } private , T> void addToValues(Identifier key, EntityField field, List values) { field.getDbAdapter().getDbValues(key.get(field)).forEach(values::add); } private > List> selectFieldsOf(Collection> fields, AliasedKey aliasedKey) { return dbFieldsOf(fields).concat(aliasedKey.aliasedFields()).toList(); } private Seq> dbFieldsOf(Collection> fields) { return seq(fields).flatMap(field -> field.getDbAdapter().getTableFields()); } private > E entityTypeOf(Collection> fields) { return (E) seq(fields).findFirst().get().getEntityType(); } private Seq> fieldsOf(final Collection> ids) { return Seq.of(ids.iterator().next().getUniqueKey().getFields()); } private , PE extends PartialEntity> PE createInstance(final Class entityIface, Map> entityMethodsMap, CurrentEntityState currentState) { Class[] interfaces = {entityIface}; return (PE) Proxy.newProxyInstance(entityIface.getClassLoader(), interfaces, new PartialEntityInvocationHandler<>(entityMethodsMap, currentState)); } }