001package com.nimbusds.common.infinispan;
002
003
004import org.apache.logging.log4j.Logger;
005import org.infinispan.Cache;
006import org.infinispan.configuration.cache.CacheMode;
007
008import java.util.List;
009
010
011/**
012 * Infinispan cache workarounds.
013 *
014 * <ul>
015 *     <li>Fix around invalidation bug, to force cache load before putIfAbsent,
016 *         replace, delete operations. See tracking Connect2id server issue
017 *         https://bitbucket.org/connect2id/server/issues/239/race-condition-in-cacheremove-in
018 *     <li>Enforcing zero cache size for "stateless mode" to enable Infinispan
019 *         to be run in local mode with no in-memory store, using the
020 *         configured stores instead.
021 * </ul>
022 */
023public class CacheWorkArounds<K, V> {
024        
025        
026        /**
027         * The handled special modes.
028         */
029        public enum Mode {
030                INVALIDATION,
031                STATELESS
032        }
033        
034        
035        /**
036         * Checks if the specified cache is configured in invalidation mode.
037         *
038         * @param cache The cache to check.
039         *
040         * @return {@code true} if the cache is configured in invalidation
041         *         mode, else {@code false}.
042         */
043        public static boolean detectInvalidationMode(final Cache<?,?> cache) {
044                
045                return List.of(CacheMode.INVALIDATION_SYNC, CacheMode.INVALIDATION_ASYNC)
046                        .contains(cache.getCacheConfiguration().clustering().cacheMode());
047        }
048        
049        
050        /**
051         * Checks if the specified cache is configured in "stateless mode".
052         *
053         * @param cache The cache to check.
054         *
055         * @return {@code true} if the cache is configured in "stateless mode",
056         *         else {@code false}.
057         */
058        public static boolean detectStatelessMode(final Cache<?,?> cache) {
059                
060                final boolean isLocal = CacheMode.LOCAL.equals(cache.getCacheConfiguration().clustering().cacheMode());
061                
062                final boolean isSizeOne =
063                        (1 == cache.getCacheConfiguration().memory().size()) // legacy
064                        ||
065                        (1 == cache.getCacheConfiguration().memory().maxCount());
066
067                return isLocal && isSizeOne;
068        }
069        
070        
071        /**
072         * The underlying Infinispan cache.
073         */
074        private final Cache<K, V> infinispanCache;
075        
076        
077        /**
078         * The special mode, {@code null} if none.
079         */
080        private final Mode mode;
081        
082        
083        /**
084         * Creates a new cache workarounds instance.
085         *
086         * @param infinispanCache The Infinispan cache.
087         */
088        public CacheWorkArounds(final Cache<K, V> infinispanCache) {
089                this(infinispanCache, null);
090        }
091        
092        
093        /**
094         * Creates a new cache workarounds instance.
095         *
096         * @param infinispanCache The Infinispan cache.
097         * @param log             Optional logger for the detection,
098         *                        {@code null} if not specified.
099         */
100        public CacheWorkArounds(final Cache<K, V> infinispanCache, final Logger log) {
101                this.infinispanCache = infinispanCache;
102                if (detectInvalidationMode(infinispanCache)) {
103                        mode = Mode.INVALIDATION;
104                } else if (detectStatelessMode(infinispanCache)) {
105                        mode = Mode.STATELESS;
106                } else {
107                        mode = null;
108                }
109                if (log != null && mode != null) {
110                        log.info("[CM8020] Detected Infinispan cache {} in {} mode", infinispanCache.getName(), mode);
111                }
112        }
113        
114        
115        /**
116         * Returns the special workaround mode.
117         *
118         * @return The workaround mode, {@code null} if none (implies follow
119         *         regular operation).
120         */
121        public Mode getMode() {
122                return mode;
123        }
124        
125        
126        /**
127         * Returns {@code true} if the cache is in invalidation mode.
128         *
129         * @return {@code true} if the cache is in invalidation mode, else
130         *         {@code false}.
131         */
132        public boolean isInvalidation() {
133                
134                return Mode.INVALIDATION.equals(getMode());
135        }
136        
137        
138        /**
139         * Returns {@code true} if the cache is in the special "stateless
140         * mode".
141         *
142         * @return {@code true} if the cache is in the "stateless mode", else
143         *         {@code false}.
144         */
145        public boolean isStateless() {
146                
147                return Mode.STATELESS.equals(getMode());
148        }
149        
150        
151        /**
152         * If the cache is in the special "stateless mode" causes the
153         * underlying data container (in memory) to be cleared. Should be
154         * called before get, replace, putIfAbsent, remove and iteration
155         * operations.
156         */
157        public void clearCacheIfStateless() {
158                
159                if (! isStateless()) {
160                        return;
161                }
162                
163                infinispanCache.getAdvancedCache().getDataContainer().clear();
164        }
165}