• 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

93.08
today-context/src/main/java/infra/cache/config/CacheAdviceParser.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.config;
19

20
import org.jspecify.annotations.Nullable;
21
import org.w3c.dom.Element;
22

23
import java.util.ArrayList;
24
import java.util.Collection;
25
import java.util.List;
26

27
import infra.beans.factory.config.TypedStringValue;
28
import infra.beans.factory.parsing.ReaderContext;
29
import infra.beans.factory.support.BeanDefinitionBuilder;
30
import infra.beans.factory.support.ManagedList;
31
import infra.beans.factory.support.ManagedMap;
32
import infra.beans.factory.support.RootBeanDefinition;
33
import infra.beans.factory.xml.AbstractSingleBeanDefinitionParser;
34
import infra.beans.factory.xml.BeanDefinitionParser;
35
import infra.beans.factory.xml.ParserContext;
36
import infra.cache.interceptor.CacheEvictOperation;
37
import infra.cache.interceptor.CacheInterceptor;
38
import infra.cache.interceptor.CacheOperation;
39
import infra.cache.interceptor.CachePutOperation;
40
import infra.cache.interceptor.CacheableOperation;
41
import infra.cache.interceptor.NameMatchCacheOperationSource;
42
import infra.util.StringUtils;
43
import infra.util.xml.DomUtils;
44

45
/**
46
 * {@link BeanDefinitionParser
47
 * BeanDefinitionParser} for the {@code <tx:advice/>} tag.
48
 *
49
 * @author Costin Leau
50
 * @author Phillip Webb
51
 * @author Stephane Nicoll
52
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
53
 */
54
class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
3✔
55

56
  private static final String CACHEABLE_ELEMENT = "cacheable";
57

58
  private static final String CACHE_EVICT_ELEMENT = "cache-evict";
59

60
  private static final String CACHE_PUT_ELEMENT = "cache-put";
61

62
  private static final String METHOD_ATTRIBUTE = "method";
63

64
  private static final String DEFS_ELEMENT = "caching";
65

66
  @Override
67
  protected Class<?> getBeanClass(Element element) {
68
    return CacheInterceptor.class;
2✔
69
  }
70

71
  @Override
72
  protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
73
    builder.addPropertyReference("cacheManager", CacheNamespaceHandler.extractCacheManager(element));
6✔
74
    CacheNamespaceHandler.parseKeyGenerator(element, builder.getBeanDefinition());
5✔
75

76
    List<Element> cacheDefs = DomUtils.getChildElementsByTagName(element, DEFS_ELEMENT);
4✔
77
    if (!cacheDefs.isEmpty()) {
3!
78
      // Using attributes source.
79
      var attributeSourceDefinitions = parseDefinitionsSources(cacheDefs, parserContext);
5✔
80
      builder.addPropertyValue("cacheOperationSources", attributeSourceDefinitions);
5✔
81
    }
1✔
82
    else {
83
      // Assume annotations source.
84
      RootBeanDefinition definition = new RootBeanDefinition("infra.cache.annotation.AnnotationCacheOperationSource");
×
85
      definition.setEnableDependencyInjection(false);
×
86
      builder.addPropertyValue("cacheOperationSources", definition);
×
87
    }
88
  }
1✔
89

90
  private List<RootBeanDefinition> parseDefinitionsSources(List<Element> definitions, ParserContext parserContext) {
91
    ManagedList<RootBeanDefinition> defs = new ManagedList<>(definitions.size());
6✔
92

93
    // extract default param for the definition
94
    for (Element element : definitions) {
10✔
95
      defs.add(parseDefinitionSource(element, parserContext));
7✔
96
    }
1✔
97

98
    return defs;
2✔
99
  }
100

101
  private RootBeanDefinition parseDefinitionSource(Element definition, ParserContext parserContext) {
102
    Props prop = new Props(definition);
5✔
103
    // add cacheable first
104

105
    ManagedMap<TypedStringValue, Collection<CacheOperation>> cacheOpMap = new ManagedMap<>();
4✔
106
    cacheOpMap.setSource(parserContext.extractSource(definition));
5✔
107

108
    List<Element> cacheableCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT);
4✔
109

110
    for (Element opElement : cacheableCacheMethods) {
10✔
111
      String name = prop.merge(opElement, parserContext.getReaderContext());
6✔
112
      TypedStringValue nameHolder = new TypedStringValue(name);
5✔
113
      nameHolder.setSource(parserContext.extractSource(opElement));
5✔
114
      CacheableOperation.Builder builder = prop.merge(opElement,
6✔
115
              parserContext.getReaderContext(), new CacheableOperation.Builder());
4✔
116
      builder.setUnless(getAttributeValue(opElement, "unless", ""));
6✔
117
      builder.setSync(Boolean.parseBoolean(getAttributeValue(opElement, "sync", "false")));
7✔
118

119
      Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
11✔
120
      col.add(builder.build());
5✔
121
    }
1✔
122

123
    List<Element> evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT);
4✔
124

125
    for (Element opElement : evictCacheMethods) {
10✔
126
      String name = prop.merge(opElement, parserContext.getReaderContext());
6✔
127
      TypedStringValue nameHolder = new TypedStringValue(name);
5✔
128
      nameHolder.setSource(parserContext.extractSource(opElement));
5✔
129
      CacheEvictOperation.Builder builder = prop.merge(opElement,
6✔
130
              parserContext.getReaderContext(), new CacheEvictOperation.Builder());
4✔
131

132
      String wide = opElement.getAttribute("all-entries");
4✔
133
      if (StringUtils.hasText(wide)) {
3✔
134
        builder.setCacheWide(Boolean.parseBoolean(wide.trim()));
5✔
135
      }
136

137
      String after = opElement.getAttribute("before-invocation");
4✔
138
      if (StringUtils.hasText(after)) {
3✔
139
        builder.setBeforeInvocation(Boolean.parseBoolean(after.trim()));
5✔
140
      }
141

142
      Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
11✔
143
      col.add(builder.build());
5✔
144
    }
1✔
145

146
    List<Element> putCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_PUT_ELEMENT);
4✔
147

148
    for (Element opElement : putCacheMethods) {
10✔
149
      String name = prop.merge(opElement, parserContext.getReaderContext());
6✔
150
      TypedStringValue nameHolder = new TypedStringValue(name);
5✔
151
      nameHolder.setSource(parserContext.extractSource(opElement));
5✔
152
      CachePutOperation.Builder builder = prop.merge(opElement,
6✔
153
              parserContext.getReaderContext(), new CachePutOperation.Builder());
4✔
154
      builder.setUnless(getAttributeValue(opElement, "unless", ""));
6✔
155

156
      Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
11✔
157
      col.add(builder.build());
5✔
158
    }
1✔
159

160
    RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class);
5✔
161
    attributeSourceDefinition.setSource(parserContext.extractSource(definition));
5✔
162
    attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpMap);
6✔
163
    attributeSourceDefinition.setEnableDependencyInjection(false);
3✔
164
    return attributeSourceDefinition;
2✔
165
  }
166

167
  private static String getAttributeValue(Element element, String attributeName, String defaultValue) {
168
    String attribute = element.getAttribute(attributeName);
4✔
169
    if (StringUtils.hasText(attribute)) {
3✔
170
      return attribute.trim();
3✔
171
    }
172
    return defaultValue;
2✔
173
  }
174

175
  /**
176
   * Simple, reusable class used for overriding defaults.
177
   */
178
  private static class Props {
179

180
    private final String key;
181

182
    private final String keyGenerator;
183

184
    private final String cacheManager;
185

186
    private final String condition;
187

188
    private final String method;
189

190
    private String @Nullable [] caches;
191

192
    Props(Element root) {
2✔
193
      String defaultCache = root.getAttribute("cache");
4✔
194
      this.key = root.getAttribute("key");
5✔
195
      this.keyGenerator = root.getAttribute("key-generator");
5✔
196
      this.cacheManager = root.getAttribute("cache-manager");
5✔
197
      this.condition = root.getAttribute("condition");
5✔
198
      this.method = root.getAttribute(METHOD_ATTRIBUTE);
5✔
199

200
      if (StringUtils.hasText(defaultCache)) {
3✔
201
        this.caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
5✔
202
      }
203
    }
1✔
204

205
    <T extends CacheOperation.Builder> T merge(Element element, ReaderContext readerCtx, T builder) {
206
      String cache = element.getAttribute("cache");
4✔
207

208
      // sanity check
209
      String[] localCaches = this.caches;
3✔
210
      if (StringUtils.hasText(cache)) {
3✔
211
        localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim());
4✔
212
      }
213
      if (localCaches != null) {
2!
214
        builder.setCacheNames(localCaches);
4✔
215
      }
216
      else {
217
        readerCtx.error("No cache specified for " + element.getNodeName(), element);
×
218
      }
219

220
      builder.setKey(getAttributeValue(element, "key", this.key));
7✔
221
      builder.setKeyGenerator(getAttributeValue(element, "key-generator", this.keyGenerator));
7✔
222
      builder.setCacheManager(getAttributeValue(element, "cache-manager", this.cacheManager));
7✔
223
      builder.setCondition(getAttributeValue(element, "condition", this.condition));
7✔
224

225
      if (StringUtils.hasText(builder.getKey()) && StringUtils.hasText(builder.getKeyGenerator())) {
8✔
226
        throw new IllegalStateException("Invalid cache advice configuration on '" +
7✔
227
                element + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
228
                "These attributes are mutually exclusive: either set the EL expression used to" +
229
                "compute the key at runtime or set the name of the KeyGenerator bean to use.");
230
      }
231

232
      return builder;
2✔
233
    }
234

235
    @Nullable
236
    String merge(Element element, ReaderContext readerCtx) {
237
      String method = element.getAttribute(METHOD_ATTRIBUTE);
4✔
238
      if (StringUtils.hasText(method)) {
3✔
239
        return method.trim();
3✔
240
      }
241
      if (StringUtils.hasText(this.method)) {
4!
242
        return this.method;
3✔
243
      }
244
      readerCtx.error("No method specified for " + element.getNodeName(), element);
×
245
      return null;
×
246
    }
247
  }
248

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