Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solve: the dynamically updated property value cannot be obtained, and the concurrency problem #334

Merged
merged 1 commit into from
Dec 12, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyFilter;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyResolver;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertySource;
import java.util.HashMap;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
Expand All @@ -15,7 +18,7 @@ public class CachingDelegateEncryptablePropertySource<T> extends PropertySource<
private final PropertySource<T> delegate;
private final EncryptablePropertyResolver resolver;
private final EncryptablePropertyFilter filter;
private final Map<String, Object> cache;
private final Map<String, CachedValue> cache;

public CachingDelegateEncryptablePropertySource(PropertySource<T> delegate, EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter) {
super(delegate.getName(), delegate.getSource());
Expand All @@ -25,7 +28,7 @@ public CachingDelegateEncryptablePropertySource(PropertySource<T> delegate, Encr
this.delegate = delegate;
this.resolver = resolver;
this.filter = filter;
this.cache = new HashMap<>();
this.cache = new ConcurrentHashMap<>();
}

@Override
Expand All @@ -35,24 +38,48 @@ public PropertySource<T> getDelegate() {

@Override
public Object getProperty(String name) {
// Can be called recursively, so, we cannot use computeIfAbsent.
if (cache.containsKey(name)) {
return cache.get(name);
//The purpose of this cache is to reduce the cost of decryption,
// so it's not a bad idea to read the original property every time, it's generally fast.
Object originValue = delegate.getProperty(name);
if (!(originValue instanceof String)) {
//Because we read the original property every time, if it isn't a String,
// there's no point in caching it.
return originValue;
}

CachedValue cachedValue = cache.get(name);
if (cachedValue != null && Objects.equals(originValue, cachedValue.originValue)) {
// If the original property has not changed, it is safe to return the cached result.
return cachedValue.resolvedValue;
}
synchronized (name.intern()) {
if (!cache.containsKey(name)) {
Object resolved = getProperty(resolver, filter, delegate, name);
if (resolved != null) {
cache.put(name, resolved);
}

//originValue must be String here
if (filter.shouldInclude(delegate, name)) {
String originStringValue = (String) originValue;
String resolved = resolver.resolvePropertyValue(originStringValue);
CachedValue newCachedValue = new CachedValue(originStringValue, resolved);
//If the mapping relationship in the cache changes during
// the calculation process, then ignore it directly.
if (cachedValue == null) {
cache.putIfAbsent(name, newCachedValue);
} else {
cache.replace(name, cachedValue, newCachedValue);
}
return cache.get(name);
//return the result calculated this time
return resolved;
}
return originValue;
}

@Override
public void refresh() {
log.info("Property Source {} refreshed", delegate.getName());
cache.clear();
}

@AllArgsConstructor
private static class CachedValue {
private final String originValue;
private final String resolvedValue;
}
}