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

org.glowroot.central.util.ClusterManager Maven / Gradle / Ivy

There is a newer version: 0.14.0-beta.3
Show newest version
/*
 * Copyright 2017-2018 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.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import com.google.common.collect.Maps;
import org.checkerframework.checker.nullness.qual.Nullable;
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 jgroupsProperties) {
        Map 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  Cache createCache(
            String cacheName, CacheLoader loader);

    public abstract  ConcurrentMap createReplicatedMap(
            String mapName);

    public abstract  DistributedExecutionMap createDistributedExecutionMap(
            String cacheName);

    public abstract void close() throws InterruptedException;

    private static class ClusterManagerImpl extends ClusterManager {

        private final EmbeddedCacheManager cacheManager;

        private ClusterManagerImpl(File centralDir, String jgroupsConfigurationFile,
                Map jgroupsProperties) {
            GlobalConfiguration configuration = new GlobalConfigurationBuilder()
                    .transport().defaultTransport()
                    .addProperty("configurationFile",
                            getConfigurationFilePropertyValue(centralDir, jgroupsConfigurationFile))
                    .build();
            cacheManager = doWithSystemProperties(jgroupsProperties,
                    () -> new DefaultCacheManager(configuration));
        }

        @Override
        public  Cache createCache(
                String cacheName, CacheLoader loader) {
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
            configurationBuilder.clustering()
                    .cacheMode(CacheMode.INVALIDATION_ASYNC);
            cacheManager.defineConfiguration(cacheName, configurationBuilder.build());
            return new CacheImpl(cacheManager.getCache(cacheName), loader);
        }

        @Override
        public  ConcurrentMap createReplicatedMap(
                String mapName) {
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
            configurationBuilder.clustering()
                    .cacheMode(CacheMode.REPL_ASYNC);
            cacheManager.defineConfiguration(mapName, configurationBuilder.build());
            return cacheManager.getCache(mapName);
        }

        @Override
        public  DistributedExecutionMap createDistributedExecutionMap(
                String cacheName) {
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
            configurationBuilder.clustering()
                    .cacheMode(CacheMode.LOCAL);
            cacheManager.defineConfiguration(cacheName, configurationBuilder.build());
            return new DistributedExecutionMapImpl(cacheManager.getCache(cacheName));
        }

        @Override
        public void close() throws InterruptedException {
            cacheManager.stop();
            // org.infinispan.factories.NamedExecutorsFactory.stop() calls shutdownNow() on all
            // executors, but does not awaitTermination(), so sleep a bit to allow time
            Thread.sleep(1000);
        }

        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 doWithSystemProperties(Map properties,
                Supplier supplier) {
            Map priorSystemProperties = new HashMap<>();
            for (Map.Entry 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  Cache createCache(
                String cacheName, CacheLoader loader) {
            return new NonClusterCacheImpl(new ConcurrentHashMap<>(), loader);
        }

        @Override
        public  DistributedExecutionMap createDistributedExecutionMap(
                String cacheName) {
            return new NonClusterDistributedExecutionMapImpl<>(new ConcurrentHashMap<>());
        }

        @Override
        public  ConcurrentMap createReplicatedMap(
                String mapName) {
            return new ConcurrentHashMap<>();
        }

        @Override
        public void close() {}
    }

    private static class CacheImpl
            implements Cache {

        private final org.infinispan.Cache cache;
        private final CacheLoader loader;

        private CacheImpl(org.infinispan.Cache cache, CacheLoader 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.putForExternalRead(key, value);
            }
            return value;
        }

        @Override
        public void invalidate(K key) {
            cache.remove(key);
        }
    }

    private static class NonClusterCacheImpl
            implements Cache {

        private final ConcurrentMap cache;
        private final CacheLoader loader;

        private NonClusterCacheImpl(ConcurrentMap cache, CacheLoader 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);
        }
    }

    private static class DistributedExecutionMapImpl
            implements DistributedExecutionMap {

        private final org.infinispan.Cache cache;

        private DistributedExecutionMapImpl(org.infinispan.Cache 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  Optional execute(String key,
                SerializableFunction task) throws Exception {
            CollectingConsumer consumer = new CollectingConsumer();
            CompletableFuture future = cache.getCacheManager().executor().submitConsumer(
                    new AdapterFunction(cache.getName(), key, task), consumer);
            // TODO short-circuit after receiving one (non-empty and non-shutting-down) response,
            // instead of waiting for all responses
            future.get(60, SECONDS);
            if (consumer.logStackTrace) {
                logger.warn("context for remote error(s) logged above",
                        new Exception("location stack trace"));
            }
            if (consumer.values.isEmpty()) {
                return Optional.empty();
            } else {
                // TODO first non-shutting-down response
                return Optional.of(consumer.values.remove());
            }
        }
    }

    private static class NonClusterDistributedExecutionMapImpl
            implements DistributedExecutionMap {

        private final ConcurrentMap cache;

        private NonClusterDistributedExecutionMapImpl(ConcurrentMap 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  Optional execute(String key,
                SerializableFunction task) throws Exception {
            V value = cache.get(key);
            if (value == null) {
                return Optional.empty();
            }
            return Optional.of(task.apply(value));
        }
    }

    @SuppressWarnings("serial")
    private static class AdapterFunction
            implements SerializableFunction> {

        private final String cacheName;
        private final String key;
        private final SerializableFunction task;

        private AdapterFunction(String cacheName, String key, SerializableFunction task) {
            this.cacheName = cacheName;
            this.key = key;
            this.task = task;
        }

        @Override
        public Optional apply(EmbeddedCacheManager cacheManager) {
            org.infinispan.Cache 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
            implements TriConsumer, /*@Nullable*/ Throwable> {

        private final Queue values = new ConcurrentLinkedQueue<>();
        private volatile boolean logStackTrace;

        @Override
        public void accept(Address address, @Nullable Optional value,
                @Nullable Throwable throwable) {
            if (throwable != null) {
                logger.warn("received error from {}: {}", address, throwable.getMessage(),
                        throwable);
                logStackTrace = true;
                return;
            }
            // value is only null when throwable is not null
            if (checkNotNull(value).isPresent()) {
                values.add(value.get());
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy