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

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

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

98.73
today-context/src/main/java/infra/cache/annotation/DefaultCacheAnnotationParser.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.annotation;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.io.Serializable;
23
import java.lang.annotation.Annotation;
24
import java.lang.reflect.AnnotatedElement;
25
import java.lang.reflect.Method;
26
import java.util.ArrayList;
27
import java.util.Collection;
28
import java.util.Set;
29

30
import infra.cache.interceptor.CacheEvictOperation;
31
import infra.cache.interceptor.CacheOperation;
32
import infra.cache.interceptor.CachePutOperation;
33
import infra.cache.interceptor.CacheableOperation;
34
import infra.core.annotation.AnnotatedElementUtils;
35
import infra.core.annotation.AnnotationUtils;
36
import infra.util.StringUtils;
37

38
/**
39
 * Strategy implementation for parsing Framework's {@link Caching}, {@link Cacheable},
40
 * {@link CacheEvict}, and {@link CachePut} annotations.
41
 *
42
 * @author Costin Leau
43
 * @author Juergen Hoeller
44
 * @author Chris Beams
45
 * @author Phillip Webb
46
 * @author Stephane Nicoll
47
 * @author Sam Brannen
48
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
49
 * @since 4.0
50
 */
51
@SuppressWarnings("serial")
52
public class DefaultCacheAnnotationParser implements CacheAnnotationParser, Serializable {
3✔
53

54
  private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = Set.of(
7✔
55
          Cacheable.class, CacheEvict.class, CachePut.class, Caching.class
56
  );
57

58
  @Override
59
  public boolean isCandidateClass(Class<?> targetClass) {
60
    return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS);
4✔
61
  }
62

63
  @Override
64
  @Nullable
65
  public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
66
    DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type);
5✔
67
    return parseCacheAnnotations(defaultConfig, type);
5✔
68
  }
69

70
  @Override
71
  @Nullable
72
  public Collection<CacheOperation> parseCacheAnnotations(Method method) {
73
    DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
6✔
74
    return parseCacheAnnotations(defaultConfig, method);
5✔
75
  }
76

77
  @Nullable
78
  private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
79
    Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
6✔
80
    if (ops != null && ops.size() > 1) {
6✔
81
      // More than one operation found -> local declarations override interface-declared ones...
82
      Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
6✔
83
      if (localOps != null) {
2!
84
        return localOps;
2✔
85
      }
86
    }
87
    return ops;
2✔
88
  }
89

90
  @Nullable
91
  private Collection<CacheOperation> parseCacheAnnotations(
92
          DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
93

94
    Collection<? extends Annotation> anns =
95
            localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS)
6✔
96
                    : AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS);
4✔
97
    if (anns.isEmpty()) {
3✔
98
      return null;
2✔
99
    }
100

101
    ArrayList<CacheOperation> ops = new ArrayList<>(1);
5✔
102
    anns.stream()
3✔
103
            .filter(ann -> ann instanceof Cacheable)
9✔
104
            .forEach(ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
11✔
105
    anns.stream()
3✔
106
            .filter(ann -> ann instanceof CacheEvict)
9✔
107
            .forEach(ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
11✔
108
    anns.stream()
3✔
109
            .filter(ann -> ann instanceof CachePut)
9✔
110
            .forEach(ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
11✔
111
    anns.stream()
3✔
112
            .filter(ann -> ann instanceof Caching)
9✔
113
            .forEach(ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
9✔
114
    return ops;
2✔
115
  }
116

117
  private CacheableOperation parseCacheableAnnotation(
118
          AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
119

120
    CacheableOperation.Builder builder = new CacheableOperation.Builder();
4✔
121

122
    builder.setName(ae.toString());
4✔
123
    builder.setCacheNames(cacheable.cacheNames());
4✔
124
    builder.setCondition(cacheable.condition());
4✔
125
    builder.setUnless(cacheable.unless());
4✔
126
    builder.setKey(cacheable.key());
4✔
127
    builder.setKeyGenerator(cacheable.keyGenerator());
4✔
128
    builder.setCacheManager(cacheable.cacheManager());
4✔
129
    builder.setCacheResolver(cacheable.cacheResolver());
4✔
130
    builder.setSync(cacheable.sync());
4✔
131

132
    defaultConfig.applyDefault(builder);
3✔
133
    CacheableOperation op = builder.build();
3✔
134
    validateCacheOperation(ae, op);
4✔
135

136
    return op;
2✔
137
  }
138

139
  private CacheEvictOperation parseEvictAnnotation(
140
          AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
141

142
    CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();
4✔
143

144
    builder.setName(ae.toString());
4✔
145
    builder.setCacheNames(cacheEvict.cacheNames());
4✔
146
    builder.setCondition(cacheEvict.condition());
4✔
147
    builder.setKey(cacheEvict.key());
4✔
148
    builder.setKeyGenerator(cacheEvict.keyGenerator());
4✔
149
    builder.setCacheManager(cacheEvict.cacheManager());
4✔
150
    builder.setCacheResolver(cacheEvict.cacheResolver());
4✔
151
    builder.setCacheWide(cacheEvict.allEntries());
4✔
152
    builder.setBeforeInvocation(cacheEvict.beforeInvocation());
4✔
153

154
    defaultConfig.applyDefault(builder);
3✔
155
    CacheEvictOperation op = builder.build();
3✔
156
    validateCacheOperation(ae, op);
4✔
157

158
    return op;
2✔
159
  }
160

161
  private CacheOperation parsePutAnnotation(
162
          AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) {
163

164
    CachePutOperation.Builder builder = new CachePutOperation.Builder();
4✔
165

166
    builder.setName(ae.toString());
4✔
167
    builder.setCacheNames(cachePut.cacheNames());
4✔
168
    builder.setCondition(cachePut.condition());
4✔
169
    builder.setUnless(cachePut.unless());
4✔
170
    builder.setKey(cachePut.key());
4✔
171
    builder.setKeyGenerator(cachePut.keyGenerator());
4✔
172
    builder.setCacheManager(cachePut.cacheManager());
4✔
173
    builder.setCacheResolver(cachePut.cacheResolver());
4✔
174

175
    defaultConfig.applyDefault(builder);
3✔
176
    CachePutOperation op = builder.build();
3✔
177
    validateCacheOperation(ae, op);
4✔
178

179
    return op;
2✔
180
  }
181

182
  private void parseCachingAnnotation(
183
          AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching, Collection<CacheOperation> ops) {
184

185
    Cacheable[] cacheables = caching.cacheable();
3✔
186
    for (Cacheable cacheable : cacheables) {
16✔
187
      ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
8✔
188
    }
189
    CacheEvict[] cacheEvicts = caching.evict();
3✔
190
    for (CacheEvict cacheEvict : cacheEvicts) {
16✔
191
      ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
8✔
192
    }
193
    CachePut[] cachePuts = caching.put();
3✔
194
    for (CachePut cachePut : cachePuts) {
16✔
195
      ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
8✔
196
    }
197
  }
1✔
198

199
  /**
200
   * Validates the specified {@link CacheOperation}.
201
   * <p>Throws an {@link IllegalStateException} if the state of the operation is
202
   * invalid. As there might be multiple sources for default values, this ensure
203
   * that the operation is in a proper state before being returned.
204
   *
205
   * @param ae the annotated element of the cache operation
206
   * @param operation the {@link CacheOperation} to validate
207
   */
208
  private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) {
209
    if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
8✔
210
      throw new IllegalStateException("Invalid cache annotation configuration on '" +
7✔
211
              ae + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
212
              "These attributes are mutually exclusive: either set the EL expression used to" +
213
              "compute the key at runtime or set the name of the KeyGenerator bean to use.");
214
    }
215
    if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
8✔
216
      throw new IllegalStateException("Invalid cache annotation configuration on '" +
7✔
217
              ae + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
218
              "These attributes are mutually exclusive: the cache manager is used to configure a" +
219
              "default cache resolver if none is set. If a cache resolver is set, the cache manager" +
220
              "won't be used.");
221
    }
222
  }
1✔
223

224
  @Override
225
  public boolean equals(@Nullable Object other) {
226
    return (other instanceof DefaultCacheAnnotationParser);
×
227
  }
228

229
  @Override
230
  public int hashCode() {
231
    return DefaultCacheAnnotationParser.class.hashCode();
3✔
232
  }
233

234
  /**
235
   * Provides default settings for a given set of cache operations.
236
   */
237
  private static class DefaultCacheConfig {
238

239
    private final Class<?> target;
240

241
    private String @Nullable [] cacheNames;
242

243
    @Nullable
244
    private String keyGenerator;
245

246
    @Nullable
247
    private String cacheManager;
248

249
    @Nullable
250
    private String cacheResolver;
251

252
    private boolean initialized = false;
3✔
253

254
    public DefaultCacheConfig(Class<?> target) {
2✔
255
      this.target = target;
3✔
256
    }
1✔
257

258
    /**
259
     * Apply the defaults to the specified {@link CacheOperation.Builder}.
260
     *
261
     * @param builder the operation builder to update
262
     */
263
    public void applyDefault(CacheOperation.Builder builder) {
264
      if (!this.initialized) {
3✔
265
        CacheConfig annotation = AnnotatedElementUtils.findMergedAnnotation(this.target, CacheConfig.class);
6✔
266
        if (annotation != null) {
2✔
267
          this.cacheNames = annotation.cacheNames();
4✔
268
          this.keyGenerator = annotation.keyGenerator();
4✔
269
          this.cacheManager = annotation.cacheManager();
4✔
270
          this.cacheResolver = annotation.cacheResolver();
4✔
271
        }
272
        this.initialized = true;
3✔
273
      }
274

275
      if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {
7✔
276
        builder.setCacheNames(this.cacheNames);
4✔
277
      }
278
      if (StringUtils.isBlank(builder.getKey()) && StringUtils.isBlank(builder.getKeyGenerator()) &&
10✔
279
              StringUtils.hasText(this.keyGenerator)) {
2✔
280
        builder.setKeyGenerator(this.keyGenerator);
4✔
281
      }
282

283
      if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {
9✔
284
        // One of these is set so we should not inherit anything
285
      }
286
      else if (StringUtils.hasText(this.cacheResolver)) {
4✔
287
        builder.setCacheResolver(this.cacheResolver);
5✔
288
      }
289
      else if (StringUtils.hasText(this.cacheManager)) {
4✔
290
        builder.setCacheManager(this.cacheManager);
4✔
291
      }
292
    }
1✔
293
  }
294

295
}
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