Skip to content

Commit d2c054c

Browse files
committed
Implementing cluster token caching #4159
- introduced pre-cache - refactored self cleaning cache - added new fields - introduced builder - introduced OAuth2OpaqueTokenClusterCachePersistence - added documentation - introduced integration test + unit tests
1 parent 0587392 commit d2c054c

File tree

46 files changed

+2543
-333
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2543
-333
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.mercedesbenz.sechub.commons.core.cache;
2+
3+
import static java.util.Objects.*;
4+
5+
import java.io.Serializable;
6+
import java.time.Duration;
7+
import java.time.Instant;
8+
9+
import javax.crypto.SealedObject;
10+
11+
import com.mercedesbenz.sechub.commons.core.security.CryptoAccess;
12+
import com.mercedesbenz.sechub.commons.core.security.CryptoAccessProvider;
13+
14+
/**
15+
* Represents the data stored in the cache under a specific key.
16+
*
17+
* <p>
18+
* The cached data can be any serializable object. If cryptoAccess provider is
19+
* nut null, the value is securely sealed using a {@link CryptoAccess} instance
20+
* of type otherwise the value be stored directly. <code>T</code>.
21+
* </p>
22+
*/
23+
public class CacheData<T extends Serializable> {
24+
25+
private final CryptoAccessProvider<T> cryptoAccessProvider;
26+
private final SealedObject sealedValue;
27+
private final Duration duration;
28+
private final T value;
29+
private final Instant createdAt;
30+
31+
public CacheData(T value, Duration duration, Instant created) {
32+
this(value, duration, null, created);
33+
}
34+
35+
public CacheData(T value, Duration duration, CryptoAccessProvider<T> cryptoAccessProvider) {
36+
this(value, duration, cryptoAccessProvider, null);
37+
}
38+
39+
public CacheData(T value, Duration duration, CryptoAccessProvider<T> cryptoAccessProvider, Instant now) {
40+
41+
requireNonNull(value, "Property 'value' must not be null");
42+
43+
this.createdAt = now != null ? now : Instant.now();
44+
this.cryptoAccessProvider = cryptoAccessProvider;
45+
46+
if (cryptoAccessProvider == null) {
47+
this.value = value;
48+
this.sealedValue = null;
49+
} else {
50+
this.value = null;
51+
this.sealedValue = cryptoAccessProvider.getCryptoAccess().seal(value);
52+
}
53+
this.duration = requireNonNull(duration, "Property 'duration' must not be null");
54+
}
55+
56+
public boolean isSealed() {
57+
return sealedValue != null;
58+
}
59+
60+
public T getValue() {
61+
if (cryptoAccessProvider == null) {
62+
return value;
63+
}
64+
return cryptoAccessProvider.getCryptoAccess().unseal(sealedValue);
65+
}
66+
67+
public Duration getDuration() {
68+
return duration;
69+
}
70+
71+
public Instant getCreatedAt() {
72+
return createdAt;
73+
}
74+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.mercedesbenz.sechub.commons.core.cache;
2+
3+
import java.io.Serializable;
4+
import java.time.Instant;
5+
6+
public interface CachePersistence<T extends Serializable> {
7+
8+
/**
9+
* Updates or creates a cache entry
10+
*
11+
* @param key the key for the cache entry
12+
* @param cacheData cache data containing
13+
*/
14+
void put(String key, CacheData<T> cacheData);
15+
16+
/**
17+
* Fetch cache entry for given key
18+
*
19+
* @param key key to identify value
20+
* @return cache data or <code>null</code> if not available
21+
*/
22+
CacheData<T> get(String key);
23+
24+
/**
25+
* Remove entry from cache
26+
*
27+
* @param key key to identify value
28+
*/
29+
void remove(String key);
30+
31+
/**
32+
* Removes all outdated entries from cache
33+
*
34+
* @param now an instant representing now
35+
*/
36+
void removeOutdated(Instant now);
37+
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.mercedesbenz.sechub.commons.core.cache;
2+
3+
import java.io.Serializable;
4+
import java.time.Duration;
5+
import java.time.Instant;
6+
import java.util.Map;
7+
import java.util.concurrent.ConcurrentHashMap;
8+
9+
/**
10+
* <b>Note:</b> This cache persistence is local to a single application instance
11+
* and is not shared across multiple instances. Avoid using it in scenarios
12+
* where a distributed caching mechanism is required.
13+
*
14+
* @param <T>
15+
*/
16+
public class InMemoryCachePersistence<T extends Serializable> implements CachePersistence<T> {
17+
18+
private final Map<String, CacheData<T>> cacheMap = new ConcurrentHashMap<>();
19+
20+
@Override
21+
public void remove(String key) {
22+
cacheMap.remove(key);
23+
}
24+
25+
@Override
26+
public void put(String key, CacheData<T> cacheData) {
27+
cacheMap.put(key, cacheData);
28+
}
29+
30+
@Override
31+
public CacheData<T> get(String key) {
32+
return cacheMap.get(key);
33+
}
34+
35+
@Override
36+
public void removeOutdated(Instant now) {
37+
cacheMap.forEach((key, value) -> {
38+
Instant cacheDataCreatedAt = value.getCreatedAt();
39+
Duration cacheDataDuration = value.getDuration();
40+
41+
if (cacheDataCreatedAt.plus(cacheDataDuration).isBefore(now)) {
42+
cacheMap.remove(key);
43+
}
44+
});
45+
46+
}
47+
48+
}
Lines changed: 29 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
// SPDX-License-Identifier: MIT
22
package com.mercedesbenz.sechub.commons.core.cache;
33

4-
import static java.util.Objects.requireNonNull;
4+
import static java.util.Objects.*;
55

66
import java.io.Serializable;
77
import java.time.Duration;
88
import java.time.Instant;
99
import java.util.Optional;
10-
import java.util.concurrent.ConcurrentHashMap;
1110
import java.util.concurrent.ScheduledExecutorService;
1211
import java.util.concurrent.ScheduledFuture;
1312
import java.util.concurrent.TimeUnit;
1413

15-
import javax.crypto.SealedObject;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
1616

1717
import com.mercedesbenz.sechub.commons.core.security.CryptoAccess;
18+
import com.mercedesbenz.sechub.commons.core.security.CryptoAccessProvider;
1819
import com.mercedesbenz.sechub.commons.core.shutdown.ApplicationShutdownHandler;
1920
import com.mercedesbenz.sechub.commons.core.shutdown.ShutdownListener;
2021

@@ -23,47 +24,48 @@
2324
* caching mechanism for generic data.
2425
*
2526
* <p>
26-
* It uses a {@link ConcurrentHashMap} to store data and a scheduled task to
27+
* It uses a {@link CachePersistence} to store data and a scheduled task to
2728
* clear expired entries periodically. By default, the cache cleanup runs every
2829
* minute with an initial delay of 1 minute. These values can be customized via
2930
* the constructor.
3031
* </p>
3132
*
32-
* <p>
33-
* <b>Note:</b> This cache is local to a single application instance and is not
34-
* shared across multiple instances. Avoid using it in scenarios where a
35-
* distributed caching mechanism is required.
36-
* </p>
37-
*
3833
* @param <T> the type of data stored in the cache (must be of type
3934
* {@link Serializable})
4035
*
41-
* @author hamidonos
36+
* @author hamidonos, de-jcup
4237
*/
43-
public class InMemoryCache<T extends Serializable> implements ShutdownListener {
38+
public class SelfCleaningCache<T extends Serializable> implements ShutdownListener, CryptoAccessProvider<T> {
39+
40+
private static final Logger logger = LoggerFactory.getLogger(SelfCleaningCache.class);
4441

4542
private static final Duration DEFAULT_CACHE_CLEAR_JOB_PERIOD = Duration.ofMinutes(1);
4643

47-
private final ConcurrentHashMap<String, CacheData> cacheMap = new ConcurrentHashMap<>();
4844
private final ScheduledExecutorService scheduledExecutorService;
4945
private final CryptoAccess<T> cryptoAccess = new CryptoAccess<>();
5046
private final ScheduledFuture<?> cacheClearJob;
5147
private final Duration cacheClearJobPeriod;
48+
private final CachePersistence<T> cachePersistence;
5249

53-
public InMemoryCache(ScheduledExecutorService scheduledExecutorService, ApplicationShutdownHandler applicationShutdownHandler) {
54-
this(DEFAULT_CACHE_CLEAR_JOB_PERIOD, scheduledExecutorService, applicationShutdownHandler);
50+
public SelfCleaningCache(CachePersistence<T> cachePersistence, ScheduledExecutorService scheduledExecutorService,
51+
ApplicationShutdownHandler applicationShutdownHandler) {
52+
this(cachePersistence, DEFAULT_CACHE_CLEAR_JOB_PERIOD, scheduledExecutorService, applicationShutdownHandler);
5553
}
5654

5755
/* @formatter:off */
58-
public InMemoryCache(Duration cacheClearJobPeriod,
56+
public SelfCleaningCache(CachePersistence<T> cachePersistence, Duration cacheClearJobPeriod,
5957
ScheduledExecutorService scheduledExecutorService,
6058
ApplicationShutdownHandler applicationShutdownHandler) {
6159
/* @formatter:on */
60+
this.cachePersistence = requireNonNull(cachePersistence, "Parameter 'cachePersistence' must not be null");
6261
this.scheduledExecutorService = requireNonNull(scheduledExecutorService, "Property 'scheduledExecutorService' must not be null");
6362
this.cacheClearJobPeriod = requireNonNull(cacheClearJobPeriod, "Property 'cacheClearJobPeriod' must not be null");
63+
6464
cacheClearJob = scheduleClearCacheJob();
65+
6566
requireNonNull(applicationShutdownHandler, "Property 'applicationShutdownHandler' must not be null");
6667
applicationShutdownHandler.register(this);
68+
6769
}
6870

6971
/**
@@ -81,7 +83,10 @@ public InMemoryCache(Duration cacheClearJobPeriod,
8183
*/
8284
public void put(String key, T value, Duration duration) {
8385
requireNonNull(key, "Argument 'key' must not be null");
84-
cacheMap.put(key, new CacheData(value, duration));
86+
if (logger.isTraceEnabled()) {
87+
logger.trace("Put to persistence ({}): key={}, duration={}, value= {}", cachePersistence.getClass().getSimpleName(), key, duration, value);
88+
}
89+
cachePersistence.put(key, new CacheData<T>(value, duration, this));
8590
}
8691

8792
/**
@@ -98,7 +103,7 @@ public void put(String key, T value, Duration duration) {
98103
public Optional<T> get(String key) {
99104
requireNonNull(key, "Argument 'key' must not be null");
100105

101-
CacheData cacheData = cacheMap.get(key);
106+
CacheData<T> cacheData = cachePersistence.get(key);
102107

103108
if (cacheData == null) {
104109
return Optional.empty();
@@ -115,7 +120,8 @@ public Optional<T> get(String key) {
115120
* @throws NullPointerException if the specified key is null
116121
*/
117122
public void remove(String key) {
118-
cacheMap.remove(key);
123+
requireNonNull(key, "key must not be null!");
124+
cachePersistence.remove(key);
119125
}
120126

121127
public Duration getCacheClearJobPeriod() {
@@ -139,49 +145,12 @@ private ScheduledFuture<?> scheduleClearCacheJob() {
139145
}
140146

141147
private void clearCache() {
142-
Instant now = Instant.now();
143-
144-
cacheMap.forEach((key, value) -> {
145-
Instant cacheDataCreatedAt = value.getCreatedAt();
146-
Duration cacheDataDuration = value.getDuration();
147-
148-
if (cacheDataCreatedAt.plus(cacheDataDuration).isBefore(now)) {
149-
cacheMap.remove(key);
150-
}
151-
});
148+
cachePersistence.removeOutdated(Instant.now());
152149
}
153150

154-
/**
155-
* Represents the data stored in the cache under a specific key.
156-
*
157-
* <p>
158-
* The cached data can be any serializable object. It is securely sealed using a
159-
* {@link CryptoAccess} instance of type <code>T</code>.
160-
* </p>
161-
*/
162-
private class CacheData {
163-
164-
private final SealedObject sealedValue;
165-
private final Duration duration;
166-
private final Instant createdAt = Instant.now();
167-
168-
public CacheData(T value, Duration duration) {
169-
requireNonNull(value, "Property 'value' must not be null");
170-
this.sealedValue = InMemoryCache.this.cryptoAccess.seal(value);
171-
this.duration = requireNonNull(duration, "Property 'duration' must not be null");
172-
}
173-
174-
public T getValue() {
175-
return cryptoAccess.unseal(sealedValue);
176-
}
177-
178-
public Duration getDuration() {
179-
return duration;
180-
}
181-
182-
public Instant getCreatedAt() {
183-
return createdAt;
184-
}
151+
@Override
152+
public CryptoAccess<T> getCryptoAccess() {
153+
return cryptoAccess;
185154
}
186155

187156
}

sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
import javax.crypto.SecretKey;
1111

1212
/**
13-
* Represents a common possibility to encrypt data. The secret key of an
14-
* instance can unseal and seal objects for example. Be aware: Every crypto
15-
* access object has its own secret key inside! So you need to use the same
16-
* crypto access object for you operations...
13+
* Represents a common possibility to encrypt data inside ONE JVM. The secret
14+
* key of an instance can unseal and seal objects for example. Be aware: Every
15+
* crypto access object has its own secret key inside! So you need to use the
16+
* same crypto access object for you operations...
17+
*
18+
* NOTE: If you want to persist or share an object in a encrypted way you have
19+
* to use other ways! This class is only meant to ensure that memory dumps do
20+
* not reveal object data.
1721
*
1822
* @author Albert Tregnaghi
1923
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.mercedesbenz.sechub.commons.core.security;
2+
3+
import java.io.Serializable;
4+
5+
public interface CryptoAccessProvider<T extends Serializable> {
6+
7+
/**
8+
* @return crypto access object, never <code>null</code>
9+
*/
10+
public CryptoAccess<T> getCryptoAccess();
11+
}

0 commit comments

Comments
 (0)