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

io.devbench.uibuilder.data.common.datasource.CommonDataSourceContext Maven / Gradle / Ivy

/*
 *
 * Copyright © 2018 Webvalto Ltd.
 *
 * 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 io.devbench.uibuilder.data.common.datasource;

import com.vaadin.flow.component.Component;
import io.devbench.uibuilder.core.session.context.UIContext;
import io.devbench.uibuilder.core.session.context.UIContextStored;
import io.devbench.uibuilder.data.common.exceptions.DataSourceNotActiveException;
import lombok.AccessLevel;
import lombok.Getter;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.concurrent.*;
import java.util.function.Supplier;

@UIContextStored
public class CommonDataSourceContext {

    /**
     * One per classloader is ok for now, later we may need to create a better executor implementation,
     * which will use the SingletonManager. But this has to be decided, when we see multiple jobs like
     * this cleanup.
     */
    static final ThreadPoolExecutor CLEANUP_THREAD_POOL_EXECUTOR;

    static {
        CLEANUP_THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 10, 50L, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(100),
            runnable -> new Thread(runnable) {
                @Override
                public synchronized void start() {
                    this.setDaemon(true);
                    super.start();
                }
            });
    }


    @Getter(AccessLevel.PACKAGE) //for test
    private final Map>> referenceHoldersMap;

    CommonDataSourceContext() {
        referenceHoldersMap = new ConcurrentHashMap<>();
    }

    public static CommonDataSourceContext getInstance() {
        return UIContext
            .getContext()
            .computeIfAbsent(CommonDataSourceContext.class, CommonDataSourceContext::new);
    }

    synchronized void refreshRequestedForDataSource(String dataSourceName, @Nullable CommonDataSourceSelector selector) {
        if (selector != null) {
            String key = createKey(dataSourceName, selector);
            if (referenceHoldersMap.containsKey(key)) {
                referenceHoldersMap.get(key).stream()
                    .filter(CommonDataSourceReferenceHolder::containsReferences)
                    .filter(referenceHolder -> referenceHolder.hasReferenceTo(selector.getContextHolder()))
                    .forEach(CommonDataSourceReferenceHolder::notifyReferencesAboutRefresh);
            } else {
                throw new DataSourceNotActiveException(dataSourceName, selector);
            }
        }
    }

    public DATA_SOURCE replaceDataSource(
        @NotNull String dataSourceId,
        @NotNull CommonDataSourceSelector selector,
        @NotNull Supplier dataSourceSupplier
    ) {
        String key = createKey(dataSourceId, selector);
        List> referenceHolders = referenceHoldersMap.getOrDefault(key, Collections.emptyList());
        referenceHolders.forEach(it -> it.setDataSource(dataSourceSupplier.get()));
        referenceHolders.forEach(it -> it.notifyReferencesAboutDataSourceChange(dataSourceId, selector));
        return findDataSource(dataSourceId, selector, dataSourceSupplier);
    }

    public DATA_SOURCE findDataSource(
        @NotNull String dataSourceName,
        @NotNull CommonDataSourceSelector selector,
        @NotNull Supplier dataSourceSupplier
    ) {
        Objects.requireNonNull(dataSourceName, "`dataSourceName` must not be null");
        Objects.requireNonNull(selector, "`selector` must not be null");
        Objects.requireNonNull(dataSourceSupplier, "`dataSourceSupplier` must not be null");

        List> holders =
            referenceHoldersMap
                .computeIfAbsent(createKey(dataSourceName, selector), ignore -> Collections.synchronizedList(new ArrayList<>()));

        CommonDataSourceReferenceHolder dataSourceReferenceHolder;

        synchronized (holders) {
            dataSourceReferenceHolder = holders.stream()
                .filter(holder -> holder.canHoldReference(selector.getContextHolder()))
                .findFirst()
                .orElseGet(() -> registerNewReferenceHolderThenReturnIt(selector, dataSourceSupplier, holders));
        }

        dataSourceReferenceHolder.registerReference(selector.getContextHolder());

        startCleanupDeamon();

        return dataSourceReferenceHolder.getDataSource();
    }

    private void startCleanupDeamon() {
        try {
            CLEANUP_THREAD_POOL_EXECUTOR.execute(this::cleanup);
        } catch (RejectedExecutionException ignore) {
        }
    }

    private void cleanup() {
        Set keys = new HashSet<>(referenceHoldersMap.keySet());
        keys.stream()
            .map(key -> Pair.of(key, referenceHoldersMap.get(key)))
            .peek(pair -> clearReferenceHoldersWithoutReferences(pair.getValue()))
            .filter(pair -> pair.getValue() != null && pair.getValue().isEmpty())
            .forEach(pair -> referenceHoldersMap.remove(pair.getKey()));
    }

    private void clearReferenceHoldersWithoutReferences(List> holders) {
        if (holders != null) {
            holders.removeIf(holder -> !holder.containsReferences());
        }
    }


    private CommonDataSourceReferenceHolder registerNewReferenceHolderThenReturnIt(
        @NotNull CommonDataSourceSelector selector,
        Supplier dataSourceSupplier,
        List> holders
    ) {

        CommonDataSourceReferenceHolder referenceHolder = createReferenceHolder(selector.getContextHolder(), dataSourceSupplier.get());
        holders.add(referenceHolder);
        return referenceHolder;
    }

    private String createKey(String dataSourceName, @NotNull CommonDataSourceSelector selector) {
        return dataSourceName + selector.getDefaultQuery();
    }

    CommonDataSourceReferenceHolder createReferenceHolder(Component component, DATA_SOURCE dataSource) {
        return new CommonDataSourceReferenceHolder<>(component, dataSource);
    }

    public void findDataSource(String dataSourceName, CommonDataSourceSelector commonDataSourceSelector) {
        findDataSource(dataSourceName, commonDataSourceSelector, () -> null);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy