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

openmrs / openmrs-core / 16085050040

05 Jul 2025 05:39AM UTC coverage: 65.351% (-0.04%) from 65.388%
16085050040

push

github

rkorytkowski
Add cache.stack system property for api and hibernate

6 of 28 new or added lines in 2 files covered. (21.43%)

2 existing lines in 2 files now uncovered.

23544 of 36027 relevant lines covered (65.35%)

0.65 hits per line

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

66.27
/api/src/main/java/org/openmrs/api/cache/CacheConfig.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.api.cache;
11

12
import java.io.ByteArrayInputStream;
13
import java.io.IOException;
14
import java.io.InputStream;
15
import java.net.URL;
16
import java.nio.charset.StandardCharsets;
17
import java.util.ArrayList;
18
import java.util.LinkedHashMap;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22

23
import org.apache.commons.lang.StringUtils;
24
import org.apache.commons.text.CaseUtils;
25
import org.infinispan.commons.dataconversion.MediaType;
26
import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
27
import org.infinispan.configuration.parsing.ParserRegistry;
28
import org.infinispan.manager.DefaultCacheManager;
29
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
30
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
31
import org.jgroups.JChannel;
32
import org.jgroups.protocols.TCP;
33
import org.slf4j.Logger;
34
import org.slf4j.LoggerFactory;
35
import org.springframework.beans.factory.annotation.Value;
36
import org.springframework.cache.CacheManager;
37
import org.springframework.context.annotation.Bean;
38
import org.springframework.context.annotation.Configuration;
39
import org.springframework.core.io.Resource;
40
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
41
import org.springframework.core.io.support.ResourcePatternResolver;
42
import org.yaml.snakeyaml.DumperOptions;
43
import org.yaml.snakeyaml.Yaml;
44

45
/**
46
 * CacheConfig provides a cache manager for the @Cacheable annotation and uses Infinispan under the hood.
47
 * The config of Infinispan is loaded from infinispan-api-local.xml/infinispan-api.xml and can be customized by 
48
 * providing a different file through the cache_config property. It is expected for the config to contain a template 
49
 * named "entity" to be used to create caches.
50
 * <p>
51
 * Caches can be added by modules through a cache-api.yaml file in the classpath.
52
 * The file shall contain only the <b>caches</b> element as defined in Infinispan docs at 
53
 * <a href="https://infinispan.org/docs/13.0.x/titles/configuring/configuring.html#multiple_caches">multiple caches</a> 
54
 * <p>
55
 * Please note the underlying implementation changed from ehcache to Infinispan since 2.8.x 
56
 * to support replicated/distributed caches.
57
 */
58
@Configuration
59
public class CacheConfig {
1✔
60
        private final static Logger log = LoggerFactory.getLogger(CacheConfig.class);
1✔
61
        
62
        @Value("${cache.type:local}")
63
        private String cacheType;
64
        
65
        @Value("${cache.config:}")
66
        private String cacheConfig;
67
        
68
        @Value("${cache.stack:}")
69
        private String cacheStack;
70
        
71
        @Value("${cache.api.bind.port:}")
72
        private String apiCacheBindPort;
73
        
74
        private String jChannelConfig;
75

76
        @Bean(name = "apiCacheManager")
77
        public CacheManager apiCacheManager() throws Exception {
78
                if (StringUtils.isBlank(cacheConfig)) {
1✔
79
                        String local = "local".equalsIgnoreCase(cacheType.trim()) ? "-local" : "";
1✔
80
                        cacheConfig = "infinispan-api" + local + ".xml";
1✔
81
                }
82

83
                ParserRegistry parser = new ParserRegistry();
1✔
84
                ConfigurationBuilderHolder baseConfigBuilder = parser.parseFile(cacheConfig);
1✔
85
                if(cacheType.trim().equals("cluster")) {
1✔
NEW
86
                        jChannelConfig = getJChannelConfig(cacheStack);
×
NEW
87
                        JChannel jchannel = new JChannel(jChannelConfig);
×
NEW
88
                        TCP tcp = jchannel.getProtocolStack().findProtocol(TCP.class);
×
NEW
89
                        if (StringUtils.isBlank(apiCacheBindPort)) {
×
NEW
90
                                String hibernateCacheBindPort = System.getProperty("jgroups.bind.port");
×
NEW
91
                                if(hibernateCacheBindPort == null) hibernateCacheBindPort = "7800";
×
NEW
92
                                apiCacheBindPort=String.valueOf(Integer.parseInt(hibernateCacheBindPort) + 1);
×
93
                        }
NEW
94
                        tcp.setBindPort(Integer.parseInt(apiCacheBindPort));
×
NEW
95
                        JGroupsTransport transport = new JGroupsTransport(jchannel);
×
NEW
96
                        baseConfigBuilder.getGlobalConfigurationBuilder().transport().clusterName("infinispan-api-cluster").transport(transport);
×
97
                }
98
                // Determine cache type based on loaded template for "entity"
99
                String cacheType = baseConfigBuilder.getNamedConfigurationBuilders().get("entity").build().elementName();
1✔
100
                cacheType = StringUtils.removeEnd(cacheType, "-configuration");
1✔
101
                cacheType = CaseUtils.toCamelCase(cacheType, false, '-');
1✔
102

103
                DumperOptions options = new DumperOptions();
1✔
104
                options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
1✔
105
                options.setPrettyFlow(true);
1✔
106
                Yaml yaml = new Yaml(options);
1✔
107

108
                for (URL configFile : getCacheConfigurations()) {
1✔
109
                        // Apply cache type for caches using the 'entity' template 
110
                        // and add the 'infinispan.cacheContainer.caches' parent.
111
                        // Skip already defined caches.
112
                        InputStream fullConfig = buildFullConfig(yaml, configFile,
1✔
113
                                baseConfigBuilder.getNamedConfigurationBuilders().keySet(), cacheType);
1✔
114
                        parser.parse(fullConfig, baseConfigBuilder, null,
1✔
115
                                MediaType.APPLICATION_YAML);
116
                }
1✔
117
                
118
                DefaultCacheManager cacheManager = new DefaultCacheManager(baseConfigBuilder, true);
1✔
119
                return new SpringEmbeddedCacheManager(cacheManager);
1✔
120
        }
121

122
        private static InputStream buildFullConfig(Yaml yaml, URL configFile, Set<String> skipCaches, String cacheType) throws IOException {
123
                Map<String, Object> loadedConfig = yaml.load(configFile.openStream());
1✔
124

125
                Map<String, Object> config = new LinkedHashMap<>();
1✔
126
                Map<String, Object> cacheContainer = new LinkedHashMap<>();
1✔
127
                Map<String, Object> caches = new LinkedHashMap<>();
1✔
128
                Map<String, Object> cacheList = new LinkedHashMap<>();
1✔
129
                config.put("infinispan", cacheContainer);
1✔
130
                cacheContainer.put("cacheContainer", caches);
1✔
131

132
                @SuppressWarnings("unchecked")
133
                Map<String, Object> loadedCaches = (Map<String, Object>) loadedConfig.get("caches");
1✔
134
                for (Map.Entry<String, Object> entry : loadedCaches.entrySet()) {
1✔
135
                        @SuppressWarnings("unchecked")
136
                        Map<String, Object> value = (Map<String, Object>) entry.getValue();
1✔
137
                        if ("entity".equals(value.get("configuration"))) {
1✔
138
                                Map<Object, Object> cache = new LinkedHashMap<>();
1✔
139
                                cache.put(cacheType, value);
1✔
140
                                if (!skipCaches.contains(entry.getKey())) {
1✔
141
                                        cacheList.put(entry.getKey(), cache);
1✔
142
                                }
143
                        } else {
1✔
144
                                if (!skipCaches.contains(entry.getKey())) {
×
145
                                        cacheList.put(entry.getKey(), value);
×
146
                                }
147
                        }
148
                }
1✔
149
                if (!cacheList.isEmpty()) {
1✔
150
                        caches.put("caches", cacheList);
1✔
151
                }
152

153
                String configDump = yaml.dump(config);
1✔
154
                return new ByteArrayInputStream(configDump.getBytes(StandardCharsets.UTF_8));
1✔
155
        }
156

157
        public List<URL> getCacheConfigurations() {
158
                Resource[] configResources;
159
                try {
160
                        ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
1✔
161
                        configResources = patternResolver.getResources("classpath*:cache-api.yaml");
1✔
162
                } catch (IOException e) {
×
163
                        throw new IllegalStateException("Unable to find cache configurations", e);
×
164
                }
1✔
165

166
                List<URL> files = new ArrayList<>();
1✔
167
                for (Resource configResource : configResources) {
1✔
168
                        try {
169
                                URL file = configResource.getURL();
1✔
170
                                files.add(file);
1✔
171
                        } catch (IOException e) {
×
172
                                log.error("Failed to get cache config file: {}", configResource, e);
×
173
                        }
1✔
174
                }
175

176
                return files;
1✔
177
        }
178
        
179
        public String getJChannelConfig(String cacheStack) {
180
                String jChannelConfig;
181
                switch (cacheStack.trim()) {
1✔
182
                        case "tcp":
NEW
183
                                jChannelConfig = "default-configs/default-jgroups-tcp.xml";
×
NEW
184
                                break;
×
185
                        case "kubernetes":
NEW
186
                                jChannelConfig = "default-configs/default-jgroups-kubernetes.xml";
×
NEW
187
                                break;
×
188
                        case "google":
NEW
189
                                jChannelConfig = "default-configs/default-jgroups-google.xml";
×
NEW
190
                                break;
×
191
                        case "tunnel":
NEW
192
                                jChannelConfig = "default-configs/default-jgroups-tunnel.xml";
×
NEW
193
                                break;
×
194
                        case "ec2":
NEW
195
                                jChannelConfig = "default-configs/default-jgroups-ec2.xml";
×
NEW
196
                                break;
×
197
                        case "azure":
NEW
198
                                jChannelConfig = "default-configs/default-jgroups-azure.xml";
×
NEW
199
                                break;
×
200
                        default:
201
                                jChannelConfig = "default-configs/default-jgroups-udp.xml";
1✔
202
                }
203
                return jChannelConfig;
1✔
204
        }
205
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc