<commit_before>/*
 * Copyright 2016 Crown Copyright
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package stroom.explorer.server;

import org.springframework.stereotype.Component;
import stroom.explorer.shared.DocumentType;
import stroom.explorer.shared.ExplorerNode;
import stroom.security.Insecure;
import stroom.task.server.ExecutorProvider;

import javax.inject.Inject;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;

@Component
class ExplorerTreeModel {
    private static final long ONE_HOUR = 60 * 60 * 1000;
    private static final long TEN_MINUTES = 10 * 60 * 1000;

    private final ExplorerTreeDao explorerTreeDao;
    private final ExplorerActionHandlersImpl explorerActionHandlers;
    private final ExplorerSession explorerSession;
    private final ExecutorProvider executorProvider;

    private volatile TreeModel currentModel;
    private final AtomicBoolean performingRebuild = new AtomicBoolean();

    @Inject
    ExplorerTreeModel(final ExplorerTreeDao explorerTreeDao,
                      final ExplorerActionHandlersImpl explorerActionHandlers,
                      final ExplorerSession explorerSession,
                      final ExecutorProvider executorProvider) {
        this.explorerTreeDao = explorerTreeDao;
        this.explorerActionHandlers = explorerActionHandlers;
        this.explorerSession = explorerSession;
        this.executorProvider = executorProvider;
    }

    @Insecure
    public TreeModel getModel() {
        final long now = System.currentTimeMillis();

        // Create a model synchronously if it is currently null or hasn't been rebuilt for an hour.
        final long oneHourAgo = now - ONE_HOUR;
        if (requiresSynchronousRebuild(oneHourAgo)) {
            ensureModelExists(oneHourAgo);
        }

        // Force synchronous rebuild of the tree model if it is older than the minimum build time for the current session.
        explorerSession.getMinExplorerTreeModelBuildTime().ifPresent(buildTime -> {
            if (buildTime > currentModel.getCreationTime()) {
                updateModel();
            }
        });

        // If the model has not been rebuilt in the last 10 minutes for anybody then do so asynchronously.
        if (currentModel.getCreationTime() < now - TEN_MINUTES) {
            if (performingRebuild.compareAndSet(false, true)) {
                final Executor executor = executorProvider.getExecutor();
                CompletableFuture
                        .runAsync(() -> updateModel(), executor)
                        .thenRun(() -> performingRebuild.set(false));
            }
        }

        return currentModel;
    }

    private boolean requiresSynchronousRebuild(final long oldestAllowedForSynchronousRebuild) {
        return currentModel == null || currentModel.getCreationTime() < oldestAllowedForSynchronousRebuild;
    }

    private synchronized void ensureModelExists(final long oldestAllowedForSynchronousRebuild) {
        if (requiresSynchronousRebuild(oldestAllowedForSynchronousRebuild)) {
            updateModel();
        }
    }

    private void updateModel() {
        setCurrentModel(createModel());
    }

    private synchronized void setCurrentModel(final TreeModel treeModel) {
        if (currentModel == null || currentModel.getCreationTime() < treeModel.getCreationTime()) {
            currentModel = treeModel;
        }
    }

    private TreeModel createModel() {
        final TreeModel newTreeModel = new TreeModelImpl();
        final List<ExplorerTreeNode> roots = explorerTreeDao.getRoots();
        addChildren(newTreeModel, sort(roots), null);
        return newTreeModel;
    }

    private void addChildren(final TreeModel treeModel, final List<ExplorerTreeNode> children, final ExplorerNode parentNode) {
        for (final ExplorerTreeNode child : children) {
            final ExplorerNode explorerNode = createExplorerNode(child);
            explorerNode.setIconUrl(getIconUrl(child.getType()));
            treeModel.add(parentNode, explorerNode);

            final List<ExplorerTreeNode> subChildren = explorerTreeDao.getChildren(child);
            if (subChildren != null && subChildren.size() > 0) {
                addChildren(treeModel, sort(subChildren), explorerNode);
            }
        }
    }

    private List<ExplorerTreeNode> sort(final List<ExplorerTreeNode> list) {
        list.sort((o1, o2) -> {
            if (!o1.getType().equals(o2.getType())) {
                final int p1 = getPriority(o1.getType());
                final int p2 = getPriority(o2.getType());
                return Integer.compare(p1, p2);
            }

            return o1.getName().compareTo(o2.getName());
        });
        return list;
    }

    private String getIconUrl(final String type) {
        final DocumentType documentType = explorerActionHandlers.getType(type);
        if (documentType == null) {
            return null;
        }

        return documentType.getIconUrl();
    }

    private int getPriority(final String type) {
        final DocumentType documentType = explorerActionHandlers.getType(type);
        if (documentType == null) {
            return Integer.MAX_VALUE;
        }

        return documentType.getPriority();
    }

    void rebuild() {
        final long now = System.currentTimeMillis();
        explorerSession.setMinExplorerTreeModelBuildTime(now);
    }

    private ExplorerNode createExplorerNode(final ExplorerTreeNode explorerTreeNode) {
        return new ExplorerNode(explorerTreeNode.getType(), explorerTreeNode.getUuid(), explorerTreeNode.getName(), explorerTreeNode.getTags());
    }
}<commit_msg>#1153 The explorer tree now only rebuilds synchronously<commit_after>/*
 * Copyright 2016 Crown Copyright
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package stroom.explorer.server;

import org.springframework.stereotype.Component;
import stroom.explorer.shared.DocumentType;
import stroom.explorer.shared.ExplorerNode;
import stroom.security.Insecure;
import stroom.task.server.ExecutorProvider;

import javax.inject.Inject;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

@Component
class ExplorerTreeModel {
    private static final long ONE_HOUR = 60 * 60 * 1000;
    private static final long TEN_MINUTES = 10 * 60 * 1000;

    private final ExplorerTreeDao explorerTreeDao;
    private final ExplorerActionHandlersImpl explorerActionHandlers;
    private final ExplorerSession explorerSession;
    private final ExecutorProvider executorProvider;

    private volatile TreeModel currentModel;
    private final AtomicLong minExplorerTreeModelBuildTime = new AtomicLong();
    private final AtomicInteger performingRebuild = new AtomicInteger();

    @Inject
    ExplorerTreeModel(final ExplorerTreeDao explorerTreeDao,
                      final ExplorerActionHandlersImpl explorerActionHandlers,
                      final ExplorerSession explorerSession,
                      final ExecutorProvider executorProvider) {
        this.explorerTreeDao = explorerTreeDao;
        this.explorerActionHandlers = explorerActionHandlers;
        this.explorerSession = explorerSession;
        this.executorProvider = executorProvider;
    }

    @Insecure
    public TreeModel getModel() {
        final long now = System.currentTimeMillis();
        final AtomicBoolean done = new AtomicBoolean();

        // Create a model synchronously if it is currently null or hasn't been rebuilt for an hour.
        final long oneHourAgo = now - ONE_HOUR;
        if (requiresSynchronousRebuild(oneHourAgo)) {
            done.set(ensureModelExists(oneHourAgo));
        }

        // Force synchronous rebuild of the tree model if it is older than the minimum build time for the current session.
        if (!done.get()) {
            explorerSession.getMinExplorerTreeModelBuildTime().ifPresent(buildTime -> {
                if (buildTime > currentModel.getCreationTime()) {
                    done.set(updateModel());
                }
            });
        }

        // If the model has not been rebuilt in the last 10 minutes for anybody then do so asynchronously.
        if (!done.get()) {
            // Find out what the oldest tree model is that we will allow before performing an asynchronous rebuild.
            final long oldestAllowed = Math.max(minExplorerTreeModelBuildTime.get(), now - TEN_MINUTES);
            if (currentModel.getCreationTime() < oldestAllowed) {
                // Perform a build asynchronously if we aren't already building elsewhere.
                if (performingRebuild.compareAndSet(0, 1)) {
                    final Executor executor = executorProvider.getExecutor();
                    CompletableFuture
                            .runAsync(this::updateModel, executor)
                            .thenRun(performingRebuild::decrementAndGet);
                }
            }
        }

        return currentModel;
    }

    private boolean requiresSynchronousRebuild(final long oldestAllowedForSynchronousRebuild) {
        return currentModel == null || currentModel.getCreationTime() < oldestAllowedForSynchronousRebuild;
    }

    private synchronized boolean ensureModelExists(final long oldestAllowedForSynchronousRebuild) {
        if (requiresSynchronousRebuild(oldestAllowedForSynchronousRebuild)) {
            return updateModel();
        }
        return false;
    }

    private boolean updateModel() {
        performingRebuild.incrementAndGet();
        try {
            final TreeModel newTreeModel = new TreeModelImpl();
            final List<ExplorerTreeNode> roots = explorerTreeDao.getRoots();
            addChildren(newTreeModel, sort(roots), null);
            setCurrentModel(newTreeModel);
        } finally {
            performingRebuild.decrementAndGet();
        }
        return true;
    }

    private synchronized void setCurrentModel(final TreeModel treeModel) {
        if (currentModel == null || currentModel.getCreationTime() < treeModel.getCreationTime()) {
            currentModel = treeModel;
        }
    }

    private void addChildren(final TreeModel treeModel, final List<ExplorerTreeNode> children, final ExplorerNode parentNode) {
        for (final ExplorerTreeNode child : children) {
            final ExplorerNode explorerNode = createExplorerNode(child);
            explorerNode.setIconUrl(getIconUrl(child.getType()));
            treeModel.add(parentNode, explorerNode);

            final List<ExplorerTreeNode> subChildren = explorerTreeDao.getChildren(child);
            if (subChildren != null && subChildren.size() > 0) {
                addChildren(treeModel, sort(subChildren), explorerNode);
            }
        }
    }

    private List<ExplorerTreeNode> sort(final List<ExplorerTreeNode> list) {
        list.sort((o1, o2) -> {
            if (!o1.getType().equals(o2.getType())) {
                final int p1 = getPriority(o1.getType());
                final int p2 = getPriority(o2.getType());
                return Integer.compare(p1, p2);
            }

            return o1.getName().compareTo(o2.getName());
        });
        return list;
    }

    private String getIconUrl(final String type) {
        final DocumentType documentType = explorerActionHandlers.getType(type);
        if (documentType == null) {
            return null;
        }

        return documentType.getIconUrl();
    }

    private int getPriority(final String type) {
        final DocumentType documentType = explorerActionHandlers.getType(type);
        if (documentType == null) {
            return Integer.MAX_VALUE;
        }

        return documentType.getPriority();
    }

    void rebuild() {
        final long now = System.currentTimeMillis();

        minExplorerTreeModelBuildTime.getAndUpdate(prev -> {
            if (prev < now) {
                return now;
            }
            return prev;
        });

        explorerSession.setMinExplorerTreeModelBuildTime(now);
    }

    private ExplorerNode createExplorerNode(final ExplorerTreeNode explorerTreeNode) {
        return new ExplorerNode(explorerTreeNode.getType(), explorerTreeNode.getUuid(), explorerTreeNode.getName(), explorerTreeNode.getTags());
    }
}
{'fraction_non_alphanumeric': 0.07874132072684296, 'fraction_numerical': 0.0062047569803516025, 'mean_word_length': 2.7692093541202674, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2374355', 'n_tokens_mistral': 3566, 'n_tokens_neox': 3368, 'n_words': 922}