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