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

org.babyfish.jimmer.sql.fetcher.impl.FetcherTask Maven / Gradle / Ivy

There is a newer version: 0.9.19
Show newest version
package org.babyfish.jimmer.sql.fetcher.impl;

import org.babyfish.jimmer.meta.EmbeddedLevel;
import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.PropId;
import org.babyfish.jimmer.meta.TargetLevel;
import org.babyfish.jimmer.runtime.DraftContext;
import org.babyfish.jimmer.runtime.DraftSpi;
import org.babyfish.jimmer.runtime.ImmutableSpi;
import org.babyfish.jimmer.runtime.Internal;
import org.babyfish.jimmer.sql.fetcher.Fetcher;
import org.babyfish.jimmer.sql.fetcher.Field;
import org.babyfish.jimmer.sql.fetcher.RecursionStrategy;
import org.babyfish.jimmer.sql.runtime.JSqlClientImplementor;

import java.sql.Connection;
import java.util.*;
import java.util.stream.Collectors;

class FetcherTask {

    private final FetchingCache cache;

    private final JSqlClientImplementor sqlClient;

    private final Field field;

    private final int batchSize;

    private final DataLoader dataLoader;

    private Map pendingMap = new LinkedHashMap<>();

    public FetcherTask(
            FetchingCache cache,
            JSqlClientImplementor sqlClient,
            Connection con,
            FetchPath path,
            Field field
    ) {
        this.cache = cache;
        this.sqlClient = sqlClient;
        this.field = field;
        this.batchSize = determineBatchSize();
        this.dataLoader = new DataLoader(sqlClient, con, path, field);
    }

    public void add(DraftSpi draft) {
        add(draft, 1);
    }

    private void add(DraftSpi draft, int depth) {
        if (isLoaded(draft)) {
            return;
        }
        Object key = cache.createKey(field, draft);
        if (key == null) {
            return;
        }
        Object value = cache.get(field, key);
        if (value != null) {
            setDraftProp(draft, FetchingCache.unwrap(value), field);
            return;
        }
        pendingMap.computeIfAbsent(key, it -> new TaskData(key, depth)).getDrafts().add(draft);
    }

    public boolean execute() {
        if (pendingMap.isEmpty()) {
            return true;
        }
        Map handledMap;
        if (pendingMap.size() > batchSize) {
            Iterator> itr =
                    pendingMap.entrySet().iterator();
            if (batchSize == 1) {
                Map.Entry e = itr.next();
                handledMap = Collections.singletonMap(e.getKey(), e.getValue());
                itr.remove();
            } else {
                handledMap = new LinkedHashMap<>((batchSize * 4 + 2) / 3);
                for (int i = batchSize; i > 0; --i) {
                    Map.Entry e = itr.next();
                    handledMap.put(e.getKey(), e.getValue());
                    itr.remove();
                }
            }
        } else {
            handledMap = this.pendingMap;
            pendingMap = new LinkedHashMap<>();
        }
        Iterator> handledEntryItr =
                handledMap.entrySet().iterator();
        while (handledEntryItr.hasNext()) {
            Map.Entry e = handledEntryItr.next();
            Object key = e.getKey();
            Object value = cache.get(field, key);
            if (value != null) {
                value = FetchingCache.unwrap(value);
                TaskData taskData = e.getValue();
                afterLoad(taskData, value,false);
                handledEntryItr.remove();
            }
        }
        if (!handledMap.isEmpty()) {
            Map loadedMap = dataLoader.load(
                    handledMap
                            .values()
                            .stream()
                            .map(it -> it.getDrafts().get(0))
                            .collect(Collectors.toList())
            );
            for (Map.Entry e : handledMap.entrySet()) {
                TaskData taskData = e.getValue();
                Object value = loadedMap.get(taskData.getDrafts().get(0));
                afterLoad(taskData, value, true);
            }
        }
        return pendingMap.isEmpty();
    }

    private boolean isLoaded(DraftSpi draft) {
        if (!isLoaded(draft, field)) {
            return false;
        }
        if (sqlClient.getFilters().getFilter(field.getProp().getTargetType()) != null) {
            return false;
        }
        Fetcher childFetcher = field.getChildFetcher();
        Object childValue = draft.__get(field.getProp().getId());
        if (childFetcher != null && childValue != null) {
            for (Field childField : childFetcher.getFieldMap().values()) {
                if (!isLoaded(childValue, childField)) {
                    return false;
                }
            }
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    private static boolean isLoaded(Object obj, Field field) {
        if (obj instanceof List) {
            List drafts = (List) obj;
            for (DraftSpi draft : drafts) {
                if (!isLoaded(draft, field)) {
                    return false;
                }
            }
            return true;
        }
        return ((DraftSpi) obj).__isLoaded(field.getProp().getId());
    }

    @SuppressWarnings("unchecked")
    private void afterLoad(TaskData taskData, Object value, boolean updateCache) {
        if (updateCache) {
            cache.put(field, taskData.getKey(), value);
        }
        for (DraftSpi draft : taskData.getDrafts()) {
            setDraftProp(draft, value, field);
        }
        RecursionStrategy recursionStrategy =
                (RecursionStrategy) field.getRecursionStrategy();
        if (value instanceof List) {
            List targets = (List) value;
            for (ImmutableSpi target : targets) {
                DraftContext draftContext = Internal.currentDraftContext();
                if (recursionStrategy != null &&
                        recursionStrategy.isRecursive(
                                new RecursionStrategy.Args<>(target, taskData.depth)
                        )
                ) {
                    add(draftContext.toDraftObject(target), taskData.getDepth() + 1);
                }
            }
        } else if (value != null &&
                recursionStrategy != null &&
                recursionStrategy.isRecursive(
                        new RecursionStrategy.Args<>(value, taskData.depth)
                )
        ) {
            DraftContext draftContext = Internal.currentDraftContext();
            add(draftContext.toDraftObject(value), taskData.getDepth() + 1);
        }
    }

    private int determineBatchSize() {
        int size = field.getBatchSize();
        if (size == 0) {
            if (field.getProp().isReferenceList(TargetLevel.PERSISTENT)) {
                return sqlClient.getDefaultListBatchSize();
            }
            return sqlClient.getDefaultBatchSize();
        }
        return size;
    }

    private void setDraftProp(DraftSpi draft, Object value, Field field) {
        PropId propId = field.getProp().getId();
        if (value == null && field.getProp().isReferenceList(TargetLevel.ENTITY)) {
            draft.__set(propId, Collections.emptyList());
        } else {
            draft.__set(propId, value);
        }
        if (!field.isImplicit()) {
            show(draft, field);
        }
    }

    private static void show(DraftSpi draft, Field field) {
        if (!field.isImplicit()) {
            ImmutableProp prop = field.getProp();
            draft.__show(prop.getId(), true);
            if (prop.isEmbedded(EmbeddedLevel.SCALAR)) {
                Fetcher childFetcher = field.getChildFetcher();
                if (childFetcher != null) {
                    for (Field childField : childFetcher.getFieldMap().values()) {
                        PropId childPropId = childField.getProp().getId();
                        if (draft.__isLoaded(childPropId)) {
                            DraftSpi childDraft = (DraftSpi) draft.__get(childPropId);
                            if (childDraft != null) {
                                show(childDraft, childField);
                            }
                        }
                    }
                }
            }
        }
    }

    private static class TaskData {

        private final Object key;

        private final int depth;

        private final List drafts = new ArrayList<>();

        public TaskData(Object key, int depth) {
            this.key = key;
            this.depth = depth;
        }

        public Object getKey() {
            return key;
        }

        public int getDepth() {
            return depth;
        }

        public List getDrafts() {
            return drafts;
        }

        @Override
        public String toString() {
            return "TaskData{" +
                    "depth=" + depth +
                    ", drafts=" + drafts +
                    '}';
        }
    }
}