• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 19575886058

21 Nov 2025 03:51PM UTC coverage: 84.413% (+0.003%) from 84.41%
19575886058

push

github

TAKETODAY
:white_check_mark:

61710 of 78169 branches covered (78.94%)

Branch coverage included in aggregate %.

145709 of 167549 relevant lines covered (86.97%)

3.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.04
today-context/src/main/java/infra/cache/concurrent/ConcurrentMapCacheManager.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.cache.concurrent;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.util.Arrays;
23
import java.util.Collection;
24
import java.util.Collections;
25
import java.util.Map;
26
import java.util.concurrent.ConcurrentHashMap;
27
import java.util.concurrent.ConcurrentMap;
28
import java.util.function.Supplier;
29

30
import infra.beans.factory.BeanClassLoaderAware;
31
import infra.cache.Cache;
32
import infra.cache.CacheManager;
33
import infra.cache.support.CaffeineCacheManager;
34
import infra.core.serializer.support.SerializationDelegate;
35

36
/**
37
 * {@link CacheManager} implementation that lazily builds {@link ConcurrentMapCache}
38
 * instances for each {@link #getCache} request. Also supports a 'static' mode where
39
 * the set of cache names is pre-defined through {@link #setCacheNames}, with no
40
 * dynamic creation of further cache regions at runtime.
41
 *
42
 * <p>Supports the asynchronous {@link Cache#retrieve(Object)} and
43
 * {@link Cache#retrieve(Object, Supplier)} operations through basic
44
 * {@code CompletableFuture} adaptation, with early-determined cache misses.
45
 *
46
 * <p>Note: This is by no means a sophisticated CacheManager; it comes with no
47
 * cache configuration options. However, it may be useful for testing or simple
48
 * caching scenarios. For advanced local caching needs, consider
49
 * {@link CaffeineCacheManager} or
50
 * {@link infra.cache.jcache.JCacheCacheManager}.
51
 *
52
 * @author Juergen Hoeller
53
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
54
 * @see ConcurrentMapCache
55
 * @since 4.0
56
 */
57
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
58

59
  private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
12✔
60

61
  private volatile boolean dynamic = true;
6✔
62

63
  private boolean allowNullValues = true;
6✔
64

65
  private boolean storeByValue = false;
6✔
66

67
  @Nullable
68
  private SerializationDelegate serialization;
69

70
  /**
71
   * Construct a dynamic ConcurrentMapCacheManager,
72
   * lazily creating cache instances as they are being requested.
73
   */
74
  public ConcurrentMapCacheManager() { }
3✔
75

76
  /**
77
   * Construct a static ConcurrentMapCacheManager,
78
   * managing caches for the specified cache names only.
79
   */
80
  public ConcurrentMapCacheManager(String... cacheNames) {
2✔
81
    setCacheNames(Arrays.asList(cacheNames));
4✔
82
  }
1✔
83

84
  /**
85
   * Specify the set of cache names for this CacheManager's 'static' mode.
86
   * <p>The number of caches and their names will be fixed after a call
87
   * to this method, with no creation of further cache regions at runtime.
88
   * <p>Note that this method replaces existing caches of the given names
89
   * and prevents the creation of further cache regions from here on - but
90
   * does <i>not</i> remove unrelated existing caches. For a full reset,
91
   * consider calling {@link #resetCaches()} before calling this method.
92
   * <p>Calling this method with a {@code null} collection argument resets
93
   * the mode to 'dynamic', allowing for further creation of caches again.
94
   *
95
   * @see #resetCaches()
96
   */
97
  public void setCacheNames(@Nullable Collection<String> cacheNames) {
98
    if (cacheNames != null) {
2!
99
      for (String name : cacheNames) {
10✔
100
        this.cacheMap.put(name, createConcurrentMapCache(name));
8✔
101
      }
1✔
102
      this.dynamic = false;
4✔
103
    }
104
    else {
105
      this.dynamic = true;
×
106
    }
107
  }
1✔
108

109
  /**
110
   * Specify whether to accept and convert {@code null} values for all caches
111
   * in this cache manager.
112
   * <p>Default is "true", despite ConcurrentHashMap itself not supporting {@code null}
113
   * values. An internal holder object will be used to store user-level {@code null}s.
114
   * <p>Note: A change of the null-value setting will reset all existing caches,
115
   * if any, to reconfigure them with the new null-value requirement.
116
   */
117
  public void setAllowNullValues(boolean allowNullValues) {
118
    if (allowNullValues != this.allowNullValues) {
4!
119
      this.allowNullValues = allowNullValues;
3✔
120
      // Need to recreate all Cache instances with the new null-value configuration...
121
      recreateCaches();
2✔
122
    }
123
  }
1✔
124

125
  /**
126
   * Return whether this cache manager accepts and converts {@code null} values
127
   * for all of its caches.
128
   */
129
  public boolean isAllowNullValues() {
130
    return this.allowNullValues;
3✔
131
  }
132

133
  /**
134
   * Specify whether this cache manager stores a copy of each entry ({@code true}
135
   * or the reference ({@code false} for all of its caches.
136
   * <p>Default is "false" so that the value itself is stored and no serializable
137
   * contract is required on cached values.
138
   * <p>Note: A change of the store-by-value setting will reset all existing caches,
139
   * if any, to reconfigure them with the new store-by-value requirement.
140
   *
141
   * @since 4.0
142
   */
143
  public void setStoreByValue(boolean storeByValue) {
144
    if (storeByValue != this.storeByValue) {
4!
145
      this.storeByValue = storeByValue;
3✔
146
      // Need to recreate all Cache instances with the new store-by-value configuration...
147
      recreateCaches();
2✔
148
    }
149
  }
1✔
150

151
  /**
152
   * Return whether this cache manager stores a copy of each entry or
153
   * a reference for all its caches. If store by value is enabled, any
154
   * cache entry must be serializable.
155
   *
156
   * @since 4.0
157
   */
158
  public boolean isStoreByValue() {
159
    return this.storeByValue;
3✔
160
  }
161

162
  @Override
163
  public void setBeanClassLoader(ClassLoader classLoader) {
164
    this.serialization = new SerializationDelegate(classLoader);
6✔
165
    // Need to recreate all Cache instances with new ClassLoader in store-by-value mode...
166
    if (isStoreByValue()) {
3!
167
      recreateCaches();
×
168
    }
169
  }
1✔
170

171
  @Override
172
  @Nullable
173
  public Cache getCache(String name) {
174
    Cache cache = this.cacheMap.get(name);
6✔
175
    if (cache == null && this.dynamic) {
5✔
176
      synchronized(this.cacheMap) {
5✔
177
        cache = this.cacheMap.get(name);
6✔
178
        if (cache == null) {
2!
179
          cache = createConcurrentMapCache(name);
4✔
180
          this.cacheMap.put(name, cache);
6✔
181
        }
182
      }
3✔
183
    }
184
    return cache;
2✔
185
  }
186

187
  @Override
188
  public Collection<String> getCacheNames() {
189
    return Collections.unmodifiableSet(this.cacheMap.keySet());
×
190
  }
191

192
  /**
193
   * Reset this cache manager's caches, removing them completely for on-demand
194
   * re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
195
   *
196
   * @since 5.0
197
   */
198
  public void resetCaches() {
199
    this.cacheMap.values().forEach(Cache::clear);
5✔
200
    if (this.dynamic) {
3✔
201
      this.cacheMap.clear();
3✔
202
    }
203
  }
1✔
204

205
  /**
206
   * Remove the specified cache from this cache manager.
207
   *
208
   * @param name the name of the cache
209
   * @since 5.0
210
   */
211
  public void removeCache(String name) {
212
    this.cacheMap.remove(name);
5✔
213
  }
1✔
214

215
  private void recreateCaches() {
216
    for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
12✔
217
      entry.setValue(createConcurrentMapCache(entry.getKey()));
8✔
218
    }
1✔
219
  }
1✔
220

221
  /**
222
   * Create a new ConcurrentMapCache instance for the specified cache name.
223
   *
224
   * @param name the name of the cache
225
   * @return the ConcurrentMapCache (or a decorator thereof)
226
   */
227
  protected Cache createConcurrentMapCache(String name) {
228
    SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
8✔
229
    return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization);
12✔
230
  }
231

232
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc