001package com.nimbusds.common.infinispan;
002
003
004import org.infinispan.Cache;
005import org.infinispan.configuration.cache.StoreConfiguration;
006import org.infinispan.manager.EmbeddedCacheManager;
007import org.infinispan.persistence.redis.configuration.CommonRedisStoreConfiguration;
008import org.infinispan.persistence.redis.configuration.RedisServerConfiguration;
009
010import java.util.HashMap;
011import java.util.HashSet;
012import java.util.Map;
013import java.util.Set;
014
015
016/**
017 * Redis cache store checks.
018 *
019 * <p>See https://github.com/infinispan/infinispan-cachestore-redis notes.
020 *
021 * <p>When using AWS ElastiCache, an explicit non-zero database index must be
022 * specified for all Redis Store configurations. AWS ElastiCache inserts a
023 * special ElastiCacheMasterReplicationTimestamp key in the default database
024 * (at zero index) to aid replication, which may lead to unexpected
025 * unmarshalling IO exceptions when the Infinispan cache needs to iterate over
026 * the stored keys.
027 */
028public class RedisCacheStoreChecks {
029        
030        
031        /**
032         * Configuration warning.
033         */
034        public static class ConfigWarning extends Exception {
035                
036                public ConfigWarning(final String message) {
037                        super(message);
038                }
039        }
040        
041        
042        /**
043         * Configuration exception.
044         */
045        public static class ConfigError extends RuntimeException {
046                
047                public ConfigError(final String message) {
048                        super(message);
049                }
050        }
051        
052        
053        /**
054         * Checks the configured Redis cache stores to ensure each uses its own
055         * database number and that number is non-zero.
056         *
057         * @param cacheManager The initialised Infinispan cache manager.
058         *
059         * @throws ConfigWarning If a Redis cache store with zero database
060         *                       number was detected.
061         * @throws ConfigError   If a Redis cache store with a clashing
062         *                       database number was detected.
063         */
064        public static void check(final EmbeddedCacheManager cacheManager)
065                throws ConfigWarning, ConfigError{
066                
067                Map<String,Set<Integer>> hostToDbIndexMappings = new HashMap<>();
068                
069                for (String cacheName: cacheManager.getCacheNames()) {
070                        
071                        Cache<?,?> cache = cacheManager.getCache(cacheName);
072                        
073                        for (StoreConfiguration storeConfig: cache.getCacheConfiguration().persistence().stores()) {
074                                
075                                if (! (storeConfig instanceof CommonRedisStoreConfiguration redisStoreConfig)) {
076                                        continue; // skip
077                                }
078
079                                if (redisStoreConfig.database() == 0) {
080                                        throw new ConfigWarning(
081                                                "The Redis Cache store for " + cacheName + " must specify a non-zero " +
082                                                "database number to prevent potential conflicts with meta keys such " +
083                                                "as ElastiCacheMasterReplicationTimestamp that may get put into " +
084                                                "the default (0) database");
085                                }
086                                
087                                for (RedisServerConfiguration redisServerConfig: redisStoreConfig.servers()) {
088                                        
089                                        String host = redisServerConfig.host() + ":" + redisServerConfig.port();
090                                        
091                                        Set<Integer> dbNums = hostToDbIndexMappings.get(host);
092                                        
093                                        if (dbNums == null) {
094                                                dbNums = new HashSet<>();
095                                        }
096                                        
097                                        hostToDbIndexMappings.put(host, dbNums);
098                                        
099                                        if (dbNums.contains(redisStoreConfig.database())) {
100                                                throw new ConfigError("The Redis Cache store " + host +
101                                                        " for " + cacheName + " uses a database number " +
102                                                        redisStoreConfig.database() + " that is already taken");
103                                        }
104                                        
105                                        dbNums.add(redisStoreConfig.database());
106                                }
107                        }
108                }
109        }
110        
111        
112        /**
113         * Prevents public instantiation.
114         */
115        private RedisCacheStoreChecks() {}
116}