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

openmrs / openmrs-core / 37033370540

02 Oct 2026 04:21PM UTC coverage: 66.43% (+0.1%) from 66.293%
37033370540

push

github

ibacher
TRUNK-6810: Extend cluster-safety to RolePrivilegeCache (#6627)

281 of 297 new or added lines in 9 files covered. (94.61%)

2 existing lines in 1 file now uncovered.

25927 of 39029 relevant lines covered (66.43%)

0.66 hits per line

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

84.62
/api/src/main/java/org/openmrs/api/cache/ExternalReadSpringCache.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.util.concurrent.Callable;
13
import java.util.concurrent.ConcurrentHashMap;
14
import java.util.concurrent.ConcurrentMap;
15
import java.util.concurrent.locks.ReentrantLock;
16

17
import org.infinispan.Cache;
18
import org.infinispan.commons.util.NullValue;
19
import org.infinispan.context.Flag;
20
import org.infinispan.spring.common.provider.SpringCache;
21
import org.springframework.cache.support.SimpleValueWrapper;
22

23
/**
24
 * A {@link SpringCache} that writes values with {@link Cache#putForExternalRead}, for caches whose
25
 * values are read from the database. In an invalidation cluster a plain put invalidates the key on
26
 * every other node, so nodes reading the same key would keep evicting each other's entries;
27
 * {@code putForExternalRead} leaves them in place. Evictions and clears still reach every node.
28
 * <p>
29
 * {@code putForExternalRead} does nothing if the key is already cached, so {@code put} does not
30
 * overwrite an entry. That suits {@code @Cacheable}, which only puts on a miss, but not
31
 * {@code @CachePut}; evict the entry instead of overwriting it.
32
 *
33
 * @since 2.8.10
34
 */
35
public class ExternalReadSpringCache extends SpringCache {
36

37
        private final Cache<Object, Object> nativeCache;
38

39
        /** Per-key locks, so a synchronized {@code @Cacheable} load runs once per key on this node. */
40
        private final ConcurrentMap<Object, ReentrantLock> loadLocks = new ConcurrentHashMap<>();
1 ✔
41

42
        public ExternalReadSpringCache(Cache<Object, Object> nativeCache) {
43
                super(nativeCache);
1 ✔
44
                this.nativeCache = nativeCache;
1 ✔
45
        }
1 ✔
46

47
        @Override
48
        public void put(Object key, Object value) {
49
                nativeCache.putForExternalRead(key, value != null ? value : NullValue.NULL);
1 ✔
50
        }
1 ✔
51

52
        /**
53
         * Writes the value on this node only, so that, like {@link #put}, it does not invalidate the entry
54
         * on other nodes.
55
         */
56
        @Override
57
        public ValueWrapper putIfAbsent(Object key, Object value) {
58
                Object existing = nativeCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL).putIfAbsent(key,
1 ✔
59
                    value != null ? value : NullValue.NULL);
60
                if (existing == null) {
1 ✔
61
                        return null;
1 ✔
62
                }
63
                return new SimpleValueWrapper(existing instanceof NullValue ? null : existing);
1 ✔
64
        }
65

66
        @Override
67
        @SuppressWarnings("unchecked")
68
        public <T> T get(Object key, Callable<T> valueLoader) {
69
                ValueWrapper cached = get(key);
1 ✔
70
                if (cached != null) {
1 ✔
NEW
71
                        return (T) cached.get();
×
72
                }
73

74
                ReentrantLock lock = loadLocks.computeIfAbsent(key, k -> new ReentrantLock());
1 ✔
75
                lock.lock();
1 ✔
76
                try {
77
                        cached = get(key);
1 ✔
78
                        if (cached != null) {
1 ✔
NEW
79
                                return (T) cached.get();
×
80
                        }
81

82
                        T value;
83
                        try {
84
                                value = valueLoader.call();
1 ✔
NEW
85
                        } catch (Exception e) {
×
NEW
86
                                throw new ValueRetrievalException(key, valueLoader, e);
×
87
                        }
1 ✔
88
                        put(key, value);
1 ✔
89
                        return value;
1 ✔
90
                } finally {
91
                        lock.unlock();
1 ✔
92
                        loadLocks.remove(key, lock);
1 ✔
93
                }
94
        }
95
}
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