targetClass) {
try {
- return (T) cacheManager.getIfNotExpired(key, now()).orElseGet(() ->
- {
+ return (T) cacheManager.getIfNotExpired(key, now()).orElseGet(() -> {
String value = getValue(key);
if (transformationManager == null) {
@@ -207,7 +220,7 @@ protected Instant now() {
protected void resetToDefaults() {
cacheManager.resetExpirationTime();
if (transformationManager != null) {
- transformationManager.setTransformer(null);
+ transformationManager.unsetTransformer();
}
}
diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/CacheManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/CacheManager.java
index b868cb642..99c281b3d 100644
--- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/CacheManager.java
+++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/CacheManager.java
@@ -20,18 +20,27 @@
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+/**
+ * Manages caching of parameter values with configurable expiration times.
+ *
+ * This class is thread-safe. The cache storage is shared across all threads,
+ * while expiration time configuration is thread-local to support concurrent
+ * requests with different cache TTL requirements.
+ */
public class CacheManager {
static final Duration DEFAULT_MAX_AGE_SECS = Duration.of(5, SECONDS);
private final DataStore store;
- private Duration defaultMaxAge = DEFAULT_MAX_AGE_SECS;
- private Duration maxAge = defaultMaxAge;
+ private final AtomicReference defaultMaxAge = new AtomicReference<>(DEFAULT_MAX_AGE_SECS);
+ private final ThreadLocal maxAge = ThreadLocal.withInitial(() -> null);
public CacheManager() {
store = new DataStore();
}
+ @SuppressWarnings("unchecked") // DataStore stores Object, safe cast as we control what's stored
public Optional getIfNotExpired(String key, Instant now) {
if (store.hasExpired(key, now)) {
return Optional.empty();
@@ -40,19 +49,19 @@ public Optional getIfNotExpired(String key, Instant now) {
}
public void setExpirationTime(Duration duration) {
- this.maxAge = duration;
+ this.maxAge.set(duration);
}
public void setDefaultExpirationTime(Duration duration) {
- this.defaultMaxAge = duration;
- this.maxAge = duration;
+ this.defaultMaxAge.set(duration);
}
public void putInCache(String key, T value) {
- store.put(key, value, Clock.systemDefaultZone().instant().plus(maxAge));
+ Duration effectiveMaxAge = maxAge.get() != null ? maxAge.get() : defaultMaxAge.get();
+ store.put(key, value, Clock.systemDefaultZone().instant().plus(effectiveMaxAge));
}
public void resetExpirationTime() {
- maxAge = defaultMaxAge;
+ maxAge.remove();
}
}
diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/DataStore.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/DataStore.java
index 737faa353..4b6350cb5 100644
--- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/DataStore.java
+++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/cache/DataStore.java
@@ -15,6 +15,7 @@
package software.amazon.lambda.powertools.parameters.cache;
import java.time.Instant;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -22,7 +23,7 @@
*/
public class DataStore {
- private final ConcurrentHashMap store;
+ private final Map store;
public DataStore() {
this.store = new ConcurrentHashMap<>();
@@ -32,8 +33,8 @@ public void put(String key, Object value, Instant time) {
store.put(key, new ValueNode(value, time));
}
- public void remove(String Key) {
- store.remove(Key);
+ public void remove(String key) {
+ store.remove(key);
}
public Object get(String key) {
@@ -42,7 +43,11 @@ public Object get(String key) {
}
public boolean hasExpired(String key, Instant now) {
- boolean hasExpired = !store.containsKey(key) || now.isAfter(store.get(key).time);
+ ValueNode node = store.get(key);
+ if (node == null) {
+ return true;
+ }
+ boolean hasExpired = now.isAfter(node.time);
// Auto-clean if the parameter has expired
if (hasExpired) {
remove(key);
diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/TransformationManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/TransformationManager.java
index d3fbce14f..bddbf81d9 100644
--- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/TransformationManager.java
+++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/TransformationManager.java
@@ -15,31 +15,44 @@
package software.amazon.lambda.powertools.parameters.transform;
import java.lang.reflect.InvocationTargetException;
+
import software.amazon.lambda.powertools.parameters.exception.TransformationException;
/**
* Manager in charge of transforming parameter values in another format.
* Leverages a {@link Transformer} in order to perform the transformation.
* The transformer must be passed with {@link #setTransformer(Class)} before performing any transform operation.
+ *
+ * This class is thread-safe. Transformer configuration is thread-local to support concurrent
+ * requests with different transformation requirements.
*/
public class TransformationManager {
- private Class extends Transformer> transformer = null;
+ private final ThreadLocal> transformer = ThreadLocal.withInitial(() -> null);
/**
* Set the {@link Transformer} to use for transformation. Must be called before any transformation.
*
* @param transformerClass class of the {@link Transformer}
*/
+ @SuppressWarnings("rawtypes") // Transformer type parameter determined at runtime
public void setTransformer(Class extends Transformer> transformerClass) {
- this.transformer = transformerClass;
+ this.transformer.set(transformerClass);
+ }
+
+ /**
+ * Unset the {@link Transformer} and clean up thread-local storage.
+ * Should be called after transformation is complete to prevent memory leaks.
+ */
+ public void unsetTransformer() {
+ this.transformer.remove();
}
/**
* @return true if a {@link Transformer} has been passed to the Manager
*/
public boolean shouldTransform() {
- return transformer != null;
+ return transformer.get() != null;
}
/**
@@ -48,20 +61,22 @@ public boolean shouldTransform() {
* @param value the value to transform
* @return the value transformed
*/
+ @SuppressWarnings("rawtypes") // Transformer type parameter determined at runtime
public String performBasicTransformation(String value) {
- if (transformer == null) {
+ Class extends Transformer> transformerClass = transformer.get();
+ if (transformerClass == null) {
throw new IllegalStateException(
"You cannot perform a transformation without Transformer, use the provider.withTransformation() method to specify it.");
}
- if (!BasicTransformer.class.isAssignableFrom(transformer)) {
+ if (!BasicTransformer.class.isAssignableFrom(transformerClass)) {
throw new IllegalStateException("Wrong Transformer for a String, choose a BasicTransformer.");
}
try {
- BasicTransformer basicTransformer =
- (BasicTransformer) transformer.getDeclaredConstructor().newInstance(null);
+ BasicTransformer basicTransformer = (BasicTransformer) transformerClass.getDeclaredConstructor()
+ .newInstance(null);
return basicTransformer.applyTransformation(value);
- } catch (InstantiationException | IllegalAccessException | NoSuchMethodException |
- InvocationTargetException e) {
+ } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
+ | InvocationTargetException e) {
throw new TransformationException(e);
}
}
@@ -73,17 +88,19 @@ public String performBasicTransformation(String value) {
* @param targetClass the type of the target object.
* @return the value transformed in an object ot type T.
*/
+ @SuppressWarnings("rawtypes") // Transformer type parameter determined at runtime
public T performComplexTransformation(String value, Class targetClass) {
- if (transformer == null) {
+ Class extends Transformer> transformerClass = transformer.get();
+ if (transformerClass == null) {
throw new IllegalStateException(
"You cannot perform a transformation without Transformer, use the provider.withTransformation() method to specify it.");
}
try {
- Transformer complexTransformer = transformer.getDeclaredConstructor().newInstance(null);
+ Transformer complexTransformer = transformerClass.getDeclaredConstructor().newInstance(null);
return complexTransformer.applyTransformation(value, targetClass);
- } catch (InstantiationException | IllegalAccessException | NoSuchMethodException |
- InvocationTargetException e) {
+ } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
+ | InvocationTargetException e) {
throw new TransformationException(e);
}
}
diff --git a/powertools-serialization/pom.xml b/powertools-serialization/pom.xml
index 79b6653fa..d7f33df28 100644
--- a/powertools-serialization/pom.xml
+++ b/powertools-serialization/pom.xml
@@ -21,7 +21,7 @@
powertools-parent
software.amazon.lambda
- 2.6.0
+ 2.7.0
powertools-serialization
diff --git a/powertools-tracing/pom.xml b/powertools-tracing/pom.xml
index 3b1e58ccd..68231dbe1 100644
--- a/powertools-tracing/pom.xml
+++ b/powertools-tracing/pom.xml
@@ -24,7 +24,7 @@
powertools-parent
software.amazon.lambda
- 2.6.0
+ 2.7.0
Powertools for AWS Lambda (Java) - Tracing
diff --git a/powertools-validation/pom.xml b/powertools-validation/pom.xml
index 2c119972d..74051989e 100644
--- a/powertools-validation/pom.xml
+++ b/powertools-validation/pom.xml
@@ -24,7 +24,7 @@
powertools-parent
software.amazon.lambda
- 2.6.0
+ 2.7.0
Powertools for AWS Lambda (Java) - Validation