de.metas.ui.web.handlingunits.HUEditorViewBuffer_HighVolume.java Source code

Java tutorial

Introduction

Here is the source code for de.metas.ui.web.handlingunits.HUEditorViewBuffer_HighVolume.java

Source

package de.metas.ui.web.handlingunits;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

import org.adempiere.util.lang.Mutables;
import org.adempiere.util.lang.SynchronizedMutable;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

import de.metas.cache.CCache;
import de.metas.handlingunits.HuId;
import de.metas.handlingunits.model.I_M_HU;
import de.metas.ui.web.document.filter.DocumentFilter;
import de.metas.ui.web.document.filter.sql.SqlDocumentFilterConverterContext;
import de.metas.ui.web.exceptions.EntityNotFoundException;
import de.metas.ui.web.handlingunits.HUIdsFilterHelper.HUIdsFilterData;
import de.metas.ui.web.view.ViewEvaluationCtx;
import de.metas.ui.web.view.ViewId;
import de.metas.ui.web.view.ViewRowIdsOrderedSelection;
import de.metas.ui.web.window.datatypes.DocumentId;
import de.metas.ui.web.window.datatypes.DocumentIdsSelection;
import de.metas.ui.web.window.model.DocumentQueryOrderBy;
import de.metas.util.collections.IteratorUtils;
import de.metas.util.collections.PagedIterator.PageFetcher;
import lombok.NonNull;

/*
 * #%L
 * metasfresh-webui-api
 * %%
 * Copyright (C) 2017 metas GmbH
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

public class HUEditorViewBuffer_HighVolume implements HUEditorViewBuffer {
    private static final int HIGHVOLUME_THRESHOLD = 100;
    private static final int STREAM_ALL_MAX_SIZE_ALLOWED = 200;

    private final ViewEvaluationCtx viewEvaluationCtx;

    private final HUEditorViewRepository huEditorRepo;
    private final ImmutableList<DocumentFilter> stickyFilters;

    private Supplier<ViewRowIdsOrderedSelection> defaultSelectionFactory;
    private final SynchronizedMutable<ViewRowIdsOrderedSelection> defaultSelectionRef;
    private final transient ConcurrentHashMap<ImmutableList<DocumentQueryOrderBy>, ViewRowIdsOrderedSelection> selectionsByOrderBys = new ConcurrentHashMap<>();

    private final CCache<DocumentId, HUEditorRow> cache_huRowsById = CCache
            .newLRUCache(I_M_HU.Table_Name + "#HUEditorRows#by#Id", 100, 2);

    HUEditorViewBuffer_HighVolume(final ViewId viewId, final HUEditorViewRepository huEditorRepo,
            final List<DocumentFilter> stickyFilters, final List<DocumentFilter> filters,
            final List<DocumentQueryOrderBy> orderBys, final SqlDocumentFilterConverterContext context) {
        this.viewEvaluationCtx = ViewEvaluationCtx.newInstanceFromCurrentContext();

        this.huEditorRepo = huEditorRepo;
        this.stickyFilters = ImmutableList.copyOf(stickyFilters);

        final List<DocumentFilter> filtersAll = ImmutableList.copyOf(Iterables.concat(stickyFilters, filters));

        defaultSelectionFactory = () -> huEditorRepo.createSelection(getViewEvaluationCtx(), viewId, filtersAll,
                orderBys, context);
        defaultSelectionRef = Mutables.synchronizedMutable(defaultSelectionFactory.get());

    }

    @Override
    public List<DocumentFilter> getStickyFilters() {
        return stickyFilters;
    }

    private ViewRowIdsOrderedSelection getDefaultSelection() {
        return defaultSelectionRef.computeIfNull(defaultSelectionFactory);
    }

    private ViewRowIdsOrderedSelection getSelection(final List<DocumentQueryOrderBy> orderBys) {
        final ViewRowIdsOrderedSelection defaultSelection = getDefaultSelection();

        if (orderBys == null || orderBys.isEmpty()) {
            return defaultSelection;
        }

        if (Objects.equals(defaultSelection.getOrderBys(), orderBys)) {
            return defaultSelection;
        }

        return selectionsByOrderBys.computeIfAbsent(ImmutableList.copyOf(orderBys),
                orderBysImmutable -> huEditorRepo.createSelectionFromSelection(getViewEvaluationCtx(),
                        defaultSelection, orderBysImmutable));
    }

    /** @return true if selection was really changed */
    private boolean changeSelection(final UnaryOperator<ViewRowIdsOrderedSelection> mapper) {
        final ViewRowIdsOrderedSelection defaultSelectionOld = defaultSelectionRef.getValue();

        defaultSelectionRef.computeIfNull(defaultSelectionFactory); // make sure it's not null (might be null if it was invalidated)
        final ViewRowIdsOrderedSelection defaultSelectionNew = defaultSelectionRef.compute(mapper);
        selectionsByOrderBys.clear(); // invalidate all the other selections, let it recompute when needed

        return !Objects.equals(defaultSelectionOld, defaultSelectionNew);
    }

    @Override
    public ViewId getViewId() {
        return getDefaultSelection().getViewId();
    }

    @Override
    public long size() {
        return getDefaultSelection().getSize();
    }

    @Override
    public void invalidateAll() {
        defaultSelectionRef.computeIfNotNull(defaultSelection -> {
            huEditorRepo.deleteSelection(defaultSelection);
            return null;
        });

        huEditorRepo.invalidateCache();
        cache_huRowsById.reset();
    }

    @Override
    public boolean addHUIds(final Collection<HuId> huIdsToAdd) {
        if (huIdsToAdd == null || huIdsToAdd.isEmpty()) {
            return false;
        }

        final DocumentIdsSelection rowIdsToAdd = HUEditorRowId.rowIdsFromTopLevelHuIds(huIdsToAdd);
        if (rowIdsToAdd.isEmpty()) {
            return false;
        }

        return changeSelection(
                defaultSelection -> huEditorRepo.addRowIdsToSelection(defaultSelection, rowIdsToAdd));
    }

    @Override
    public boolean removeHUIds(final Collection<HuId> huIdsToRemove) {
        if (huIdsToRemove == null || huIdsToRemove.isEmpty()) {
            return false;
        }

        final DocumentIdsSelection rowIdsToRemove = HUEditorRowId.rowIdsFromTopLevelHuIds(huIdsToRemove);

        cache_huRowsById.removeAll(rowIdsToRemove.toSet());

        return changeSelection(
                defaultSelection -> huEditorRepo.removeRowIdsFromSelection(defaultSelection, rowIdsToRemove));
    }

    @Override
    public boolean containsAnyOfHUIds(final Collection<HuId> huIdsToCheck) {
        final DocumentIdsSelection rowIds = HUEditorRowId.rowIdsFromTopLevelHuIds(huIdsToCheck);
        return huEditorRepo.containsAnyOfRowIds(getDefaultSelection(), rowIds);
    }

    private ViewEvaluationCtx getViewEvaluationCtx() {
        return viewEvaluationCtx;
    }

    @Override
    public Stream<HUEditorRow> streamAllRecursive(@NonNull final HUEditorRowFilter filter)
            throws UnsupportedOperationException {
        final ViewRowIdsOrderedSelection defaultSelection = getDefaultSelection();
        if (defaultSelection.getSize() > STREAM_ALL_MAX_SIZE_ALLOWED) {
            throw new UnsupportedOperationException("Streaming all rows when selection is bigger than "
                    + STREAM_ALL_MAX_SIZE_ALLOWED + " is not allowed");
        }

        return streamPage(0, STREAM_ALL_MAX_SIZE_ALLOWED, filter, defaultSelection.getOrderBys())
                .flatMap(HUEditorRow::streamRecursive).map(HUEditorRow::cast)
                .filter(HUEditorRowFilters.toPredicate(filter));
    }

    @Override
    public Stream<HUEditorRow> streamByIdsExcludingIncludedRows(@NonNull final HUEditorRowFilter filter) {
        return streamByIds(filter);
    }

    private Stream<HUEditorRow> streamByIds(@NonNull final HUEditorRowFilter filter) {
        final Stream<HUEditorRowId> huEditorRowIds;
        final ImmutableSet<HUEditorRowId> onlyRowIds = filter.getOnlyRowIds();
        if (onlyRowIds.isEmpty()) {
            final List<DocumentQueryOrderBy> defaultOrderBys = getDefaultSelection().getOrderBys();
            huEditorRowIds = streamHUIdsByPage(0, Integer.MAX_VALUE, defaultOrderBys)
                    .map(HUEditorRowId::ofTopLevelHU);
        } else {
            huEditorRowIds = onlyRowIds.stream();
        }

        return HUEditorRowsPagedLoadingIterator.builder().huEditorRepo(huEditorRepo).cache(cache_huRowsById)
                .rowIds(huEditorRowIds.iterator()).filter(filter).build().stream();
    }

    @Override
    public Stream<HUEditorRow> streamPage(final int firstRow, final int pageLength,
            @NonNull final HUEditorRowFilter filter, final List<DocumentQueryOrderBy> orderBys) {
        final Iterator<HUEditorRowId> rowIds = streamHUIdsByPage(firstRow, pageLength, orderBys)
                .map(HUEditorRowId::ofTopLevelHU).iterator();

        return HUEditorRowsPagedLoadingIterator.builder().huEditorRepo(huEditorRepo).cache(cache_huRowsById)
                .rowIds(rowIds).filter(filter).build().stream();
    }

    private PageFetcher<HuId> huIdsPageFetcher(final List<DocumentQueryOrderBy> orderBys) {
        final ViewEvaluationCtx viewEvalCtx = getViewEvaluationCtx();
        final ViewRowIdsOrderedSelection selection = getSelection(orderBys);
        return (firstRow, maxRows) -> huEditorRepo.retrieveHUIdsPage(viewEvalCtx, selection, firstRow, maxRows);
    }

    private Stream<HuId> streamHUIdsByPage(final int firstRow, final int maxRows,
            final List<DocumentQueryOrderBy> orderBys) {
        return IteratorUtils.<HuId>newPagedIterator().firstRow(firstRow).maxRows(maxRows).pageSize(100) // fetch 100items/chunk
                .pageFetcher(huIdsPageFetcher(orderBys)).build().stream();
    }

    @Override
    public HUEditorRow getById(@NonNull final DocumentId rowId) throws EntityNotFoundException {
        final HUEditorRowId huRowId = HUEditorRowId.ofDocumentId(rowId);
        final HUEditorRowId topLevelRowId = huRowId.toTopLevelRowId();
        final HuId topLevelHUId = topLevelRowId.getTopLevelHUId();

        final HUEditorRow topLevelRow = cache_huRowsById.getOrLoad(topLevelRowId.toDocumentId(),
                () -> huEditorRepo.retrieveForHUId(topLevelHUId));
        if (topLevelRowId.equals(huRowId)) {
            return topLevelRow;
        } else {
            return topLevelRow.getIncludedRowById(rowId)
                    .orElseThrow(() -> new EntityNotFoundException("No row found for " + rowId)
                            .setParameter("topLevelRowId", topLevelRowId));
        }
    }

    @Override
    public String getSqlWhereClause(final DocumentIdsSelection rowIds) {
        return huEditorRepo.buildSqlWhereClause(getDefaultSelection(), rowIds);
    }

    public static boolean isHighVolume(final List<DocumentFilter> stickyFilters) {
        final HUIdsFilterData huIdsFilterData = HUIdsFilterHelper.extractFilterDataOrNull(stickyFilters);
        if (huIdsFilterData == null) {
            return true;
        }

        final Set<HuId> huIds = huIdsFilterData.getInitialHUIds();
        if (huIds == null) {
            // null means no restrictions, so we might have a lot of HUs
            return true; // high volume
        } else if (huIds.isEmpty()) {
            // no HUs will be allowed
            return false; // not high volume
        } else {
            // consider high volume if it's above give threshold
            return huIds.size() >= HIGHVOLUME_THRESHOLD;
        }
    }

}