/*
* Copyright 2017 the original author or authors.
*
* 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 org.glowroot.central.util;
import java.io.File;
import java.io.Serializable;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.remoting.transport.Address;
import org.infinispan.util.function.SerializableFunction;
import org.infinispan.util.function.TriConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.glowroot.central.util.Cache.CacheLoader;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.SECONDS;
public abstract class ClusterManager {
private static final Logger logger = LoggerFactory.getLogger(ClusterManager.class);
public static ClusterManager create() {
return new NonClusterManager();
}
public static ClusterManager create(File centralDir, Map<String, String> jgroupsProperties) {
Map<String, String> properties = Maps.newHashMap(jgroupsProperties);
String jgroupsConfigurationFile = properties.remove("jgroups.configurationFile");
if (jgroupsConfigurationFile != null) {
String initialNodes = properties.get("jgroups.initialNodes");
if (initialNodes != null) {
// transform from "host1:port1,host2:port2,..." to "host1[port1],host2[port2],..."
properties.put("jgroups.initialNodes",
Pattern.compile(":([0-9]+)").matcher(initialNodes).replaceAll("[$1]"));
}
return new ClusterManagerImpl(centralDir, jgroupsConfigurationFile, properties);
} else {
return new NonClusterManager();
}
}
public abstract <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object> Cache<K, V> createCache(
String cacheName, CacheLoader<K, V> loader);
public abstract <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Serializable> ConcurrentMap<K, V> createReplicatedMap(
String mapName);
public abstract <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object> DistributedExecutionMap<K, V> createDistributedExecutionMap(
String cacheName);
public abstract void close();
private static class ClusterManagerImpl extends ClusterManager {
private final EmbeddedCacheManager cacheManager;
private ClusterManagerImpl(File centralDir, String jgroupsConfigurationFile,
Map<String, String> jgroupsProperties) {
GlobalConfiguration configuration = new GlobalConfigurationBuilder()
.transport().defaultTransport()
.addProperty("configurationFile",
getConfigurationFilePropertyValue(centralDir, jgroupsConfigurationFile))
.build();
cacheManager = doWithSystemProperties(jgroupsProperties,
() -> new DefaultCacheManager(configuration));
}
@Override
public <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object> Cache<K, V> createCache(
String cacheName, CacheLoader<K, V> loader) {
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.clustering()
.cacheMode(CacheMode.INVALIDATION_ASYNC);
cacheManager.defineConfiguration(cacheName, configurationBuilder.build());
return new CacheImpl<K, V>(cacheManager.getCache(cacheName), loader);
}
@Override
public <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Serializable> ConcurrentMap<K, V> createReplicatedMap(
String mapName) {
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.clustering()
.cacheMode(CacheMode.REPL_ASYNC);
cacheManager.defineConfiguration(mapName, configurationBuilder.build());
return cacheManager.getCache(mapName);
}
@Override
public <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object> DistributedExecutionMap<K, V> createDistributedExecutionMap(
String cacheName) {
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.clustering()
.cacheMode(CacheMode.LOCAL);
cacheManager.defineConfiguration(cacheName, configurationBuilder.build());
return new DistributedExecutionMapImpl<K, V>(cacheManager.getCache(cacheName));
}
@Override
public void close() {
cacheManager.stop();
}
private static String getConfigurationFilePropertyValue(File centralDir,
String jgroupsConfigurationFile) {
File file = new File(jgroupsConfigurationFile);
if (file.isAbsolute()) {
return jgroupsConfigurationFile;
}
file = new File(centralDir, jgroupsConfigurationFile);
if (file.exists()) {
return file.getAbsolutePath();
}
if (jgroupsConfigurationFile.equals("jgroups-tcp.xml")
|| jgroupsConfigurationFile.equals("jgroups-udp.xml")) {
return jgroupsConfigurationFile;
}
throw new IllegalStateException(
"Could not find jgroups.configurationFile: " + jgroupsConfigurationFile);
}
private static <V> V doWithSystemProperties(Map<String, String> properties,
Supplier<V> supplier) {
Map<String, String> priorSystemProperties = Maps.newHashMap();
for (Map.Entry<String, String> entry : properties.entrySet()) {
String key = entry.getKey();
String priorValue = System.getProperty(key);
if (priorValue != null) {
priorSystemProperties.put(key, priorValue);
}
System.setProperty(key, entry.getValue());
}
try {
return supplier.get();
} finally {
for (String key : properties.keySet()) {
String priorValue = priorSystemProperties.get(key);
if (priorValue == null) {
System.clearProperty(key);
} else {
System.setProperty(key, priorValue);
}
}
}
}
}
private static class NonClusterManager extends ClusterManager {
@Override
public <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object> Cache<K, V> createCache(
String cacheName, CacheLoader<K, V> loader) {
return new NonClusterCacheImpl<K, V>(Maps.newConcurrentMap(), loader);
}
@Override
public <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object> DistributedExecutionMap<K, V> createDistributedExecutionMap(
String cacheName) {
return new NonClusterDistributedExecutionMapImpl<>(Maps.newConcurrentMap());
}
@Override
public <K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Serializable> ConcurrentMap<K, V> createReplicatedMap(
String mapName) {
return Maps.newConcurrentMap();
}
@Override
public void close() {}
}
private static class CacheImpl<K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object>
implements Cache<K, V> {
private final org.infinispan.Cache<K, V> cache;
private final CacheLoader<K, V> loader;
private CacheImpl(org.infinispan.Cache<K, V> cache, CacheLoader<K, V> loader) {
this.cache = cache;
this.loader = loader;
}
@Override
public V get(K key) throws Exception {
V value = cache.get(key);
if (value == null) {
value = loader.load(key);
cache.put(key, value);
}
return value;
}
@Override
public void invalidate(K key) {
cache.remove(key);
}
@Override
public void evict(K key) {
cache.evict(key);
}
}
private static class NonClusterCacheImpl<K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object>
implements Cache<K, V> {
private final ConcurrentMap<K, V> cache;
private final CacheLoader<K, V> loader;
private NonClusterCacheImpl(ConcurrentMap<K, V> cache, CacheLoader<K, V> loader) {
this.cache = cache;
this.loader = loader;
}
@Override
public V get(K key) throws Exception {
V value = cache.get(key);
if (value == null) {
value = loader.load(key);
cache.put(key, value);
}
return value;
}
@Override
public void invalidate(K key) {
cache.remove(key);
}
@Override
public void evict(K key) {
cache.remove(key);
}
}
private static class DistributedExecutionMapImpl<K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object>
implements DistributedExecutionMap<K, V> {
private final org.infinispan.Cache<K, V> cache;
private DistributedExecutionMapImpl(org.infinispan.Cache<K, V> cache) {
this.cache = cache;
}
@Override
public @Nullable V get(K key) {
return cache.get(key);
}
@Override
public void put(K key, V value) {
cache.put(key, value);
}
@Override
public void remove(K key, V value) {
cache.remove(key, value);
}
@Override
public <R extends Serializable> Optional<R> execute(String key,
SerializableFunction<V, R> task) throws Exception {
CollectingConsumer<R> consumer = new CollectingConsumer<R>();
CompletableFuture<Void> future = cache.getCacheManager().executor().submitConsumer(
new AdapterFunction<K, V, R>(cache.getName(), key, task), consumer);
// TODO short-circuit after receiving one (non-empty) response, instead of waiting for
// all responses
future.get(60, SECONDS);
if (consumer.values.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(consumer.values.remove());
}
}
@Override
public <R extends Serializable> boolean executeVoid(String key,
SerializableFunction<V, Optional<Boolean>> task) throws Exception {
return cache.values().parallelStream()
.filterKeys(ImmutableSet.of(key))
.map(task)
.filter(Optional::isPresent)
.map(Optional::get)
.findAny()
.isPresent();
}
}
private static class NonClusterDistributedExecutionMapImpl<K extends /*@NonNull*/ Serializable, V extends /*@NonNull*/ Object>
implements DistributedExecutionMap<K, V> {
private final ConcurrentMap<K, V> cache;
private NonClusterDistributedExecutionMapImpl(ConcurrentMap<K, V> cache) {
this.cache = cache;
}
@Override
public @Nullable V get(K key) {
return cache.get(key);
}
@Override
public void put(K key, V value) {
cache.put(key, value);
}
@Override
public void remove(K key, V value) {
cache.remove(key, value);
}
@Override
public <R extends Serializable> Optional<R> execute(String key,
SerializableFunction<V, R> task) throws Exception {
V value = cache.get(key);
if (value == null) {
return Optional.empty();
}
return Optional.of(task.apply(value));
}
@Override
public <R extends Serializable> boolean executeVoid(String key,
SerializableFunction<V, Optional<Boolean>> task) throws Exception {
V value = cache.get(key);
if (value == null) {
return false;
}
task.apply(value);
return true;
}
}
@SuppressWarnings("serial")
private static class AdapterFunction<K, V, R extends /*@NonNull*/ Object>
implements SerializableFunction<EmbeddedCacheManager, Optional<R>> {
private final String cacheName;
private final String key;
private final SerializableFunction<V, R> task;
private AdapterFunction(String cacheName, String key, SerializableFunction<V, R> task) {
this.cacheName = cacheName;
this.key = key;
this.task = task;
}
@Override
public Optional<R> apply(EmbeddedCacheManager cacheManager) {
org.infinispan.Cache<K, V> cache = cacheManager.getCache(cacheName, false);
if (cache == null) {
return Optional.empty();
}
V value = cache.get(key);
if (value == null) {
return Optional.empty();
}
return Optional.ofNullable(task.apply(value));
}
}
private static class CollectingConsumer<V extends /*@NonNull*/ Object>
implements TriConsumer<Address, /*@Nullable*/ Optional<V>, /*@Nullable*/ Throwable> {
private final Queue<V> values = Queues.newConcurrentLinkedQueue();
@Override
public void accept(Address address, @Nullable Optional<V> value,
@Nullable Throwable throwable) {
if (throwable != null) {
logger.warn("received error from {}: {}", address, throwable.getMessage(),
throwable);
return;
}
// value is only null when throwable is not null
if (checkNotNull(value).isPresent()) {
values.add(value.get());
}
}
}
}