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}