From 476440ce15426e4c94a25243b083638c54801fc3 Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Mon, 30 Nov 2020 12:30:53 +0100 Subject: [PATCH 01/10] Parameters injection --- powertools-parameters/pom.xml | 5 ++ .../powertools/parameters/BaseProvider.java | 2 +- .../lambda/powertools/parameters/Param.java | 31 ++++++++++ .../powertools/parameters/ParamManager.java | 38 ++++++++++++- .../exception/ProviderException.java | 12 ++++ .../internal/LambdaParametersAspect.java | 42 ++++++++++++++ .../parameters/internal/AnotherObject.java | 25 ++++++++ .../parameters/internal/CustomProvider.java | 30 ++++++++++ .../internal/LambdaParametersAspectTest.java | 57 +++++++++++++++++++ 9 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java create mode 100644 powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/ProviderException.java create mode 100644 powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java create mode 100644 powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java create mode 100644 powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java create mode 100644 powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java diff --git a/powertools-parameters/pom.xml b/powertools-parameters/pom.xml index e2abe43b9..df0292fb3 100644 --- a/powertools-parameters/pom.xml +++ b/powertools-parameters/pom.xml @@ -109,6 +109,11 @@ assertj-core test + + org.aspectj + aspectjweaver + test + diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java index 0ff2b4b3d..5c60b7c78 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java @@ -104,7 +104,7 @@ public BaseProvider withMaxAge(int maxAge, ChronoUnit unit) { * @param transformerClass Class of the transformer to apply. For convenience, you can use {@link Transformer#json} or {@link Transformer#base64} shortcuts. * @return the provider itself in order to chain calls (eg.
provider.withTransformation(json).get("key", MyObject.class)
). */ - protected BaseProvider withTransformation(Class transformerClass) { + public BaseProvider withTransformation(Class transformerClass) { if (transformationManager == null) { throw new IllegalStateException("Trying to add transformation while no TransformationManager has been provided."); } diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java new file mode 100644 index 000000000..b22f3cd1f --- /dev/null +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java @@ -0,0 +1,31 @@ +package software.amazon.lambda.powertools.parameters; + +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code Param} is used to signal that the annotated field should be + * populated with a value retrieved from a parameter store through a {@link ParamProvider}. + * + *

By default {@code Param} use {@link SSMProvider} as parameter provider. This can be overridden specifying + * the annotation variable {@code Param(provider = )}.
+ * The library provide a provider for AWS System Manager Parameters Store ({@link SSMProvider}) and a provider + * for AWS Secrets Manager ({@link SecretsProvider}). + * The user can implement a custom provider by extending the abstract class {@link BaseProvider}.

+ * + *

If the parameter value requires transformation before being assigned to the annotated field + * users can specify a {@link Transformer} + *

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Param { + String key(); + Class provider() default SSMProvider.class; + Class transformer() default Transformer.class; + long maxAgeInSeconds() default 5; +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java index f2b50425b..6df4eb7a3 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java @@ -16,8 +16,13 @@ import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.exception.ProviderException; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.ConcurrentHashMap; + /** * Utility class to retrieve instances of parameter providers. * Each instance is unique (singleton). @@ -26,21 +31,46 @@ public final class ParamManager { private static final CacheManager cacheManager = new CacheManager(); private static final TransformationManager transformationManager = new TransformationManager(); + private static final ConcurrentHashMap, BaseProvider> providers = new ConcurrentHashMap<>(); private static SecretsProvider secretsProvider; private static SSMProvider ssmProvider; + /** + * Get a concrete implementation of {@link BaseProvider}.
+ * You can specify {@link SecretsProvider} or {@link SSMProvider} or create your custom provider + * by extending {@link BaseProvider} if you need to integrate with a different parameter store. + * @return a {@link SecretsProvider} + */ + public static T getProvider(Class providerClass) { + if (providerClass == null) { + throw new IllegalStateException("You cannot provide a null provider class."); + } + try { + if(!providers.containsKey(providerClass)) { + Constructor constructor = providerClass.getDeclaredConstructor(CacheManager.class); + T provider = constructor.newInstance(cacheManager); + provider.setTransformationManager(transformationManager); + providers.put(providerClass, provider); + } + return (T) providers.get(providerClass); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + throw new ProviderException(e); + } + } + /** * Get a {@link SecretsProvider} with default {@link SecretsManagerClient}.
* If you need to customize the region, or other part of the client, use {@link ParamManager#getSecretsProvider(SecretsManagerClient)} instead. * @return a {@link SecretsProvider} */ public static SecretsProvider getSecretsProvider() { - if (secretsProvider == null) { + if (!providers.containsKey(SecretsProvider.class)) { secretsProvider = SecretsProvider.builder() .withCacheManager(cacheManager) .withTransformationManager(transformationManager) .build(); + providers.put(SecretsProvider.class, secretsProvider); } return secretsProvider; } @@ -51,11 +81,12 @@ public static SecretsProvider getSecretsProvider() { * @return a {@link SSMProvider} */ public static SSMProvider getSsmProvider() { - if (ssmProvider == null) { + if (!providers.containsKey(SSMProvider.class)) { ssmProvider = SSMProvider.builder() .withCacheManager(cacheManager) .withTransformationManager(transformationManager) .build(); + providers.put(SSMProvider.class, ssmProvider); } return ssmProvider; } @@ -66,12 +97,13 @@ public static SSMProvider getSsmProvider() { * @return a {@link SecretsProvider} */ public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { - if (secretsProvider == null) { + if (!providers.containsKey(SecretsProvider.class)) { secretsProvider = SecretsProvider.builder() .withClient(client) .withCacheManager(cacheManager) .withTransformationManager(transformationManager) .build(); + providers.put(SecretsProvider.class, secretsProvider); } return secretsProvider; } diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/ProviderException.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/ProviderException.java new file mode 100644 index 000000000..2f1bb9c3d --- /dev/null +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/ProviderException.java @@ -0,0 +1,12 @@ +package software.amazon.lambda.powertools.parameters.exception; + +public class ProviderException extends RuntimeException { + + public ProviderException(Exception e) { + super(e); + } + + public ProviderException(String message) { + super(message); + } +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java new file mode 100644 index 000000000..ed92dee4a --- /dev/null +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java @@ -0,0 +1,42 @@ +package software.amazon.lambda.powertools.parameters.internal; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.*; + +@Aspect +public class LambdaParametersAspect { + + @Pointcut("get(* *) && @annotation(paramAnnotation)") + public void getParam(Param paramAnnotation) { + } + + @Around("getParam(paramAnnotation)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final Param paramAnnotation) { + BaseProvider provider = ParamManager.getProvider(paramAnnotation.provider()); + if(null == provider) { + throw new IllegalArgumentException(String.format("ParamProvider %s not supported.", paramAnnotation.provider().getName())); + } + if(paramAnnotation.transformer().isInterface()) { + // No transformation + return provider.get(paramAnnotation.key()); + } else { + FieldSignature s = (FieldSignature) joinPoint.getSignature(); + if(String.class.isAssignableFrom(s.getFieldType())) { + // Basic transformation + return provider + .withTransformation(paramAnnotation.transformer()) + .get(paramAnnotation.key()); + } else { + // Complex transformation + return provider + .withTransformation(paramAnnotation.transformer()) + .get(paramAnnotation.key(), s.getFieldType()); + } + } + } + +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java new file mode 100644 index 000000000..b58ad7b3d --- /dev/null +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java @@ -0,0 +1,25 @@ +package software.amazon.lambda.powertools.parameters.internal; + +public class AnotherObject { + + public AnotherObject() {} + + private String another; + private int object; + + public String getAnother() { + return another; + } + + public void setAnother(String another) { + this.another = another; + } + + public int getObject() { + return object; + } + + public void setObject(int object) { + this.object = object; + } +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java new file mode 100644 index 000000000..e58ef746c --- /dev/null +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java @@ -0,0 +1,30 @@ +package software.amazon.lambda.powertools.parameters.internal; + +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; + +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +public class CustomProvider extends BaseProvider { + + private final Map values = new HashMap<>(); + + public CustomProvider(CacheManager cacheManager) { + super(cacheManager); + values.put("/simple", "value"); + values.put("/base64", Base64.getEncoder().encodeToString("value".getBytes())); + values.put("/json", "{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); + } + + @Override + protected String getValue(String key) { + return values.get(key); + } + + @Override + protected Map getMultipleValues(String path) { + return null; + } +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java new file mode 100644 index 000000000..e27f6aeb8 --- /dev/null +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java @@ -0,0 +1,57 @@ +package software.amazon.lambda.powertools.parameters.internal; + +import org.junit.jupiter.api.Test; +import software.amazon.lambda.powertools.parameters.Param; +import software.amazon.lambda.powertools.parameters.exception.TransformationException; +import software.amazon.lambda.powertools.parameters.transform.Base64Transformer; +import software.amazon.lambda.powertools.parameters.transform.JsonTransformer; +import software.amazon.lambda.powertools.parameters.transform.ObjectToDeserialize; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class LambdaParametersAspectTest { + + @Param(key = "/simple", provider = CustomProvider.class) + private String param; + + @Param(key = "/base64", provider = CustomProvider.class, transformer = Base64Transformer.class) + private String basicTransform; + + @Param(key = "/json", provider = CustomProvider.class, transformer = JsonTransformer.class) + private ObjectToDeserialize complexTransform; + + @Param(key = "/json", provider = CustomProvider.class, transformer = JsonTransformer.class) + private AnotherObject wrongTransform; + + @Test + public void testSimple() { + String paramValue = param; + + assertThat(paramValue).isEqualTo("value"); + } + + @Test + public void testWithBasicTransform() { + String paramValue = basicTransform; + assertThat(paramValue).isEqualTo("value"); + } + + @Test + public void testWithComplexTransform() { + ObjectToDeserialize paramValue = complexTransform; + assertThat(paramValue).isNotNull(); + assertThat(paramValue).isInstanceOf(ObjectToDeserialize.class); + assertThat(paramValue).matches( + o -> o.getFoo().equals("Foo") && + o.getBar() == 42 && + o.getBaz() == 123456789); + } + + @Test + public void testWithComplexTransformWrongTargetClass_ShouldThrowException() { + assertThatExceptionOfType(TransformationException.class) + .isThrownBy(() -> {AnotherObject obj = wrongTransform; }); + } + +} From 2d86a4875acc87180933468a3bd6419014114eae Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Mon, 30 Nov 2020 14:34:35 +0100 Subject: [PATCH 02/10] add doc on parameter injection annotation --- docs/content/utilities/parameters.mdx | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/content/utilities/parameters.mdx b/docs/content/utilities/parameters.mdx index 78ecc3502..525cb4f86 100644 --- a/docs/content/utilities/parameters.mdx +++ b/docs/content/utilities/parameters.mdx @@ -264,4 +264,48 @@ And then use it like this : S3Provider provider = new S3Provider(ParamManager.getCacheManager()); provider.setTransformationManager(ParamManager.getTransformationManager()); // optional, needed for transformations String value = provider.withBucket("myBucket").get("myKey"); +``` + +## Annotation +You can make use of the annotation ```@Param``` to inject a parameter value in a variable. + +```java +@Param(key = "/my/parameter") +private String value; +``` +By default it will use ```SSMProvider``` to retrieve the value from AWS System Manager Parameter Store. +You could specify a different provider as long as it extends ```BaseProvider``` and/or a ```Transformer```. +For example: + +```java +@Param(key = "/my/parameter/json", provider = SecretsProvider.class, transformer = JsonTransformer.class) +private ObjectToDeserialize value; +``` + +In this case ```SecretsProvider``` will be used to retrieve a raw value that is then trasformed into the target Object by using ```JsonTransformer```. +To show the convenience of the annotation compare the following two code snippets. + +```java:title=AppWithoutAnnotation.java + +public class AppWithoutAnnotation implements RequestHandler { + + // Get an instance of the SSM Provider + SSMProvider ssmProvider = ParamManager.getSsmProvider(); + + // Retrieve a single parameter + ObjectToDeserialize value = ssmProvider + .withTransformation(Transformer.json) + .get("/my/parameter/json"); + +} +``` +And with the usage of ```@Param``` + +```java:title=AppWithAnnotation.java +public class AppWithAnnotation implements RequestHandler { + + @Param(key = "/my/parameter/json" transformer = JsonTransformer.class) + ObjectToDeserialize value; + +} ``` \ No newline at end of file From dec58f65d24280ead92b76628c95179cc8c7f3d7 Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Mon, 30 Nov 2020 14:56:08 +0100 Subject: [PATCH 03/10] use of putIfAbsent for Thread safety --- .../lambda/powertools/parameters/ParamManager.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java index 6df4eb7a3..f53f3e75a 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java @@ -51,7 +51,7 @@ public static T getProvider(Class providerClass) { Constructor constructor = providerClass.getDeclaredConstructor(CacheManager.class); T provider = constructor.newInstance(cacheManager); provider.setTransformationManager(transformationManager); - providers.put(providerClass, provider); + providers.putIfAbsent(providerClass, provider); } return (T) providers.get(providerClass); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { @@ -70,7 +70,7 @@ public static SecretsProvider getSecretsProvider() { .withCacheManager(cacheManager) .withTransformationManager(transformationManager) .build(); - providers.put(SecretsProvider.class, secretsProvider); + providers.putIfAbsent(SecretsProvider.class, secretsProvider); } return secretsProvider; } @@ -86,7 +86,7 @@ public static SSMProvider getSsmProvider() { .withCacheManager(cacheManager) .withTransformationManager(transformationManager) .build(); - providers.put(SSMProvider.class, ssmProvider); + providers.putIfAbsent(SSMProvider.class, ssmProvider); } return ssmProvider; } @@ -103,7 +103,7 @@ public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { .withCacheManager(cacheManager) .withTransformationManager(transformationManager) .build(); - providers.put(SecretsProvider.class, secretsProvider); + providers.putIfAbsent(SecretsProvider.class, secretsProvider); } return secretsProvider; } @@ -114,12 +114,13 @@ public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { * @return a {@link SSMProvider} */ public static SSMProvider getSsmProvider(SsmClient client) { - if (ssmProvider == null) { + if (!providers.containsKey(SSMProvider.class)) { ssmProvider = SSMProvider.builder() .withClient(client) .withCacheManager(cacheManager) .withTransformationManager(transformationManager) .build(); + providers.putIfAbsent(SSMProvider.class, ssmProvider); } return ssmProvider; } From ef8ac2296985cf262a49c0675de189ca1068b9fc Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Mon, 30 Nov 2020 15:25:52 +0100 Subject: [PATCH 04/10] use of putIfAbsent for Thread safety - remove individual provider reference in favor of map usage --- .../powertools/parameters/ParamManager.java | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java index f53f3e75a..33c64aa08 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java @@ -31,10 +31,8 @@ public final class ParamManager { private static final CacheManager cacheManager = new CacheManager(); private static final TransformationManager transformationManager = new TransformationManager(); - private static final ConcurrentHashMap, BaseProvider> providers = new ConcurrentHashMap<>(); - private static SecretsProvider secretsProvider; - private static SSMProvider ssmProvider; + private static ConcurrentHashMap, BaseProvider> providers = new ConcurrentHashMap<>(); /** * Get a concrete implementation of {@link BaseProvider}.
@@ -47,6 +45,7 @@ public static T getProvider(Class providerClass) { throw new IllegalStateException("You cannot provide a null provider class."); } try { + if(!providers.containsKey(providerClass)) { Constructor constructor = providerClass.getDeclaredConstructor(CacheManager.class); T provider = constructor.newInstance(cacheManager); @@ -65,14 +64,7 @@ public static T getProvider(Class providerClass) { * @return a {@link SecretsProvider} */ public static SecretsProvider getSecretsProvider() { - if (!providers.containsKey(SecretsProvider.class)) { - secretsProvider = SecretsProvider.builder() - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build(); - providers.putIfAbsent(SecretsProvider.class, secretsProvider); - } - return secretsProvider; + return getProvider(SecretsProvider.class); } /** @@ -81,14 +73,7 @@ public static SecretsProvider getSecretsProvider() { * @return a {@link SSMProvider} */ public static SSMProvider getSsmProvider() { - if (!providers.containsKey(SSMProvider.class)) { - ssmProvider = SSMProvider.builder() - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build(); - providers.putIfAbsent(SSMProvider.class, ssmProvider); - } - return ssmProvider; + return getProvider(SSMProvider.class); } /** @@ -98,14 +83,13 @@ public static SSMProvider getSsmProvider() { */ public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { if (!providers.containsKey(SecretsProvider.class)) { - secretsProvider = SecretsProvider.builder() + providers.putIfAbsent(SecretsProvider.class, SecretsProvider.builder() .withClient(client) .withCacheManager(cacheManager) .withTransformationManager(transformationManager) - .build(); - providers.putIfAbsent(SecretsProvider.class, secretsProvider); + .build()); } - return secretsProvider; + return (SecretsProvider) providers.get(SecretsProvider.class); } /** @@ -115,14 +99,13 @@ public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { */ public static SSMProvider getSsmProvider(SsmClient client) { if (!providers.containsKey(SSMProvider.class)) { - ssmProvider = SSMProvider.builder() - .withClient(client) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build(); - providers.putIfAbsent(SSMProvider.class, ssmProvider); + providers.putIfAbsent(SSMProvider.class, SSMProvider.builder() + .withClient(client) + .withCacheManager(cacheManager) + .withTransformationManager(transformationManager) + .build()); } - return ssmProvider; + return (SSMProvider) providers.get(SSMProvider.class); } public static CacheManager getCacheManager() { From 51c2a69cc4b4223149db09faff53af939b69f010 Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Mon, 30 Nov 2020 15:27:44 +0100 Subject: [PATCH 05/10] use of putIfAbsent for Thread safety - remove individual provider reference in favor of map usage --- .../powertools/parameters/ParamManagerIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java index 813ed7638..0b4a2093f 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; @@ -57,8 +58,7 @@ public class ParamManagerIntegrationTest { public void setup() throws IllegalAccessException { openMocks(this); - writeStaticField(ParamManager.class, "ssmProvider", null, true); - writeStaticField(ParamManager.class, "secretsProvider", null, true); + writeStaticField(ParamManager.class, "providers", new ConcurrentHashMap<>(), true); } @Test From 64ab03beaf2a91ef0d68b753a5daac5254fe43f5 Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Mon, 30 Nov 2020 16:07:23 +0100 Subject: [PATCH 06/10] remove unused annotation property --- .../java/software/amazon/lambda/powertools/parameters/Param.java | 1 - 1 file changed, 1 deletion(-) diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java index b22f3cd1f..ef3d08b72 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java @@ -27,5 +27,4 @@ String key(); Class provider() default SSMProvider.class; Class transformer() default Transformer.class; - long maxAgeInSeconds() default 5; } From 0bd09abb44491074662dd64e28bb44a7598750c4 Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Tue, 1 Dec 2020 10:56:01 +0100 Subject: [PATCH 07/10] - use computeIfAbsent in ParamManager - added test for default @Param provider - removed ProviderException and added log info --- powertools-parameters/pom.xml | 10 +++- .../powertools/parameters/ParamManager.java | 60 +++++++++---------- .../exception/ProviderException.java | 12 ---- .../internal/LambdaParametersAspect.java | 7 ++- .../internal/LambdaParametersAspectTest.java | 52 ++++++++++++---- 5 files changed, 83 insertions(+), 58 deletions(-) delete mode 100644 powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/ProviderException.java diff --git a/powertools-parameters/pom.xml b/powertools-parameters/pom.xml index df0292fb3..4435e2bc6 100644 --- a/powertools-parameters/pom.xml +++ b/powertools-parameters/pom.xml @@ -82,7 +82,10 @@ aspectjrt compile - + + software.amazon.payloadoffloading + payloadoffloading-common + org.junit.jupiter @@ -99,6 +102,11 @@ mockito-core test + + org.mockito + mockito-inline + test + org.apache.commons commons-lang3 diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java index 33c64aa08..a95af6df2 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java @@ -13,10 +13,11 @@ */ package software.amazon.lambda.powertools.parameters; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.exception.ProviderException; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; import java.lang.reflect.Constructor; @@ -29,6 +30,8 @@ */ public final class ParamManager { + private static final Log LOG = LogFactory.getLog(ParamManager.class); + private static final CacheManager cacheManager = new CacheManager(); private static final TransformationManager transformationManager = new TransformationManager(); @@ -42,20 +45,9 @@ public final class ParamManager { */ public static T getProvider(Class providerClass) { if (providerClass == null) { - throw new IllegalStateException("You cannot provide a null provider class."); - } - try { - - if(!providers.containsKey(providerClass)) { - Constructor constructor = providerClass.getDeclaredConstructor(CacheManager.class); - T provider = constructor.newInstance(cacheManager); - provider.setTransformationManager(transformationManager); - providers.putIfAbsent(providerClass, provider); - } - return (T) providers.get(providerClass); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { - throw new ProviderException(e); + throw new IllegalStateException("providerClass cannot be null."); } + return (T) providers.computeIfAbsent(providerClass, (k) -> createProvider(k)); } /** @@ -82,14 +74,11 @@ public static SSMProvider getSsmProvider() { * @return a {@link SecretsProvider} */ public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { - if (!providers.containsKey(SecretsProvider.class)) { - providers.putIfAbsent(SecretsProvider.class, SecretsProvider.builder() - .withClient(client) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build()); - } - return (SecretsProvider) providers.get(SecretsProvider.class); + return (SecretsProvider) providers.computeIfAbsent(SecretsProvider.class, (k) -> SecretsProvider.builder() + .withClient(client) + .withCacheManager(cacheManager) + .withTransformationManager(transformationManager) + .build()); } /** @@ -98,14 +87,11 @@ public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { * @return a {@link SSMProvider} */ public static SSMProvider getSsmProvider(SsmClient client) { - if (!providers.containsKey(SSMProvider.class)) { - providers.putIfAbsent(SSMProvider.class, SSMProvider.builder() - .withClient(client) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build()); - } - return (SSMProvider) providers.get(SSMProvider.class); + return (SSMProvider) providers.computeIfAbsent(SSMProvider.class, (k) -> SSMProvider.builder() + .withClient(client) + .withCacheManager(cacheManager) + .withTransformationManager(transformationManager) + .build()); } public static CacheManager getCacheManager() { @@ -115,4 +101,18 @@ public static CacheManager getCacheManager() { public static TransformationManager getTransformationManager() { return transformationManager; } + + private static T createProvider(Class providerClass) { + try { + Constructor constructor = providerClass.getDeclaredConstructor(CacheManager.class); + T provider = constructor.newInstance(cacheManager); + provider.setTransformationManager(transformationManager); + return provider; + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + LOG.error("Failed creating provider instance", e); + throw new RuntimeException("Unexpected error occurred. Please raise issue at " + + "https://github.com/awslabs/aws-lambda-powertools-java/issues", e); + } + } + } diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/ProviderException.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/ProviderException.java deleted file mode 100644 index 2f1bb9c3d..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/ProviderException.java +++ /dev/null @@ -1,12 +0,0 @@ -package software.amazon.lambda.powertools.parameters.exception; - -public class ProviderException extends RuntimeException { - - public ProviderException(Exception e) { - super(e); - } - - public ProviderException(String message) { - super(message); - } -} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java index ed92dee4a..ea4d465cd 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java @@ -16,10 +16,11 @@ public void getParam(Param paramAnnotation) { @Around("getParam(paramAnnotation)") public Object injectParam(final ProceedingJoinPoint joinPoint, final Param paramAnnotation) { - BaseProvider provider = ParamManager.getProvider(paramAnnotation.provider()); - if(null == provider) { - throw new IllegalArgumentException(String.format("ParamProvider %s not supported.", paramAnnotation.provider().getName())); + if(null == paramAnnotation.provider()) { + throw new IllegalArgumentException("provider for Param annotation cannot be null!"); } + BaseProvider provider = ParamManager.getProvider(paramAnnotation.provider()); + if(paramAnnotation.transformer().isInterface()) { // No transformation return provider.get(paramAnnotation.key()); diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java index e27f6aeb8..d50dcbb10 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java @@ -1,7 +1,12 @@ package software.amazon.lambda.powertools.parameters.internal; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; import software.amazon.lambda.powertools.parameters.Param; +import software.amazon.lambda.powertools.parameters.ParamManager; +import software.amazon.lambda.powertools.parameters.SSMProvider; import software.amazon.lambda.powertools.parameters.exception.TransformationException; import software.amazon.lambda.powertools.parameters.transform.Base64Transformer; import software.amazon.lambda.powertools.parameters.transform.JsonTransformer; @@ -9,9 +14,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.openMocks; public class LambdaParametersAspectTest { + @Mock + private SSMProvider defaultProvider; + + @Param(key = "/default") + private String defaultValue; + @Param(key = "/simple", provider = CustomProvider.class) private String param; @@ -24,28 +37,43 @@ public class LambdaParametersAspectTest { @Param(key = "/json", provider = CustomProvider.class, transformer = JsonTransformer.class) private AnotherObject wrongTransform; + @BeforeEach + public void init() { + openMocks(this); + } + @Test - public void testSimple() { - String paramValue = param; + public void testDefault_ShouldUseSSMProvider() { + try (MockedStatic mocked = mockStatic(ParamManager.class)) { + mocked.when(() -> ParamManager.getProvider(SSMProvider.class)).thenReturn(defaultProvider); + when(defaultProvider.get("/default")).thenReturn("value"); - assertThat(paramValue).isEqualTo("value"); + assertThat(defaultValue).isEqualTo("value"); + mocked.verify(times(1), () -> ParamManager.getProvider(SSMProvider.class)); + verify(defaultProvider, times(1)).get("/default"); + + mocked.reset(); + } + } + + @Test + public void testSimple() { + assertThat(param).isEqualTo("value"); } @Test public void testWithBasicTransform() { - String paramValue = basicTransform; - assertThat(paramValue).isEqualTo("value"); + assertThat(basicTransform).isEqualTo("value"); } @Test public void testWithComplexTransform() { - ObjectToDeserialize paramValue = complexTransform; - assertThat(paramValue).isNotNull(); - assertThat(paramValue).isInstanceOf(ObjectToDeserialize.class); - assertThat(paramValue).matches( - o -> o.getFoo().equals("Foo") && - o.getBar() == 42 && - o.getBaz() == 123456789); + assertThat(complexTransform) + .isInstanceOf(ObjectToDeserialize.class) + .matches( + o -> o.getFoo().equals("Foo") && + o.getBar() == 42 && + o.getBaz() == 123456789); } @Test From 6c6c8e308f853b3f679818b49f4ae1ea26915945 Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Tue, 1 Dec 2020 15:20:53 +0100 Subject: [PATCH 08/10] remove log and dependency from pom --- powertools-parameters/pom.xml | 4 ---- .../amazon/lambda/powertools/parameters/ParamManager.java | 5 ----- 2 files changed, 9 deletions(-) diff --git a/powertools-parameters/pom.xml b/powertools-parameters/pom.xml index 4435e2bc6..c9ce32804 100644 --- a/powertools-parameters/pom.xml +++ b/powertools-parameters/pom.xml @@ -82,10 +82,6 @@ aspectjrt compile - - software.amazon.payloadoffloading - payloadoffloading-common - org.junit.jupiter diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java index a95af6df2..524890a2e 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java @@ -13,8 +13,6 @@ */ package software.amazon.lambda.powertools.parameters; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.lambda.powertools.parameters.cache.CacheManager; @@ -30,8 +28,6 @@ */ public final class ParamManager { - private static final Log LOG = LogFactory.getLog(ParamManager.class); - private static final CacheManager cacheManager = new CacheManager(); private static final TransformationManager transformationManager = new TransformationManager(); @@ -109,7 +105,6 @@ private static T createProvider(Class providerClass) provider.setTransformationManager(transformationManager); return provider; } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { - LOG.error("Failed creating provider instance", e); throw new RuntimeException("Unexpected error occurred. Please raise issue at " + "https://github.com/awslabs/aws-lambda-powertools-java/issues", e); } From 190a5f26608ad470112339082e1e45e445ecaddb Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Wed, 2 Dec 2020 11:58:25 +0100 Subject: [PATCH 09/10] update doc on annotation usage and installation --- docs/content/utilities/parameters.mdx | 61 ++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/docs/content/utilities/parameters.mdx b/docs/content/utilities/parameters.mdx index 525cb4f86..b6ba5d711 100644 --- a/docs/content/utilities/parameters.mdx +++ b/docs/content/utilities/parameters.mdx @@ -308,4 +308,63 @@ public class AppWithAnnotation implements RequestHandler + + ... + + org.codehaus.mojo + aspectj-maven-plugin + 1.11 + + ... + + ... + + software.amazon.lambda + powertools-parameters + + + + + + + compile + + + + + ... + + +``` +**Note:** If you are working with lambda function on runtime post java8, please refer [issue](https://github.com/awslabs/aws-lambda-powertools-java/issues/50) for workaround + +* [gradle](https://gradle.org): +```groovy +plugins{ + id 'java' + id 'aspectj.AspectjGradlePlugin' version '0.0.6' +} +repositories { + jcenter() +} +dependencies { + ... + implementation 'software.amazon.lambda:powertools-parameters:1.0.1' + aspectpath 'software.amazon.lambda:powertools-parameters:1.0.1' +} +``` + +**Note:** + +Please add `aspectjVersion = '1.9.6'` to the `gradle.properties` file. The aspectj plugin works at the moment with gradle 5.x only if +you are using `java 8` as runtime. Please refer to [open issue](https://github.com/awslabs/aws-lambda-powertools-java/issues/146) for more details. \ No newline at end of file From eb921326d42a038ec0055650e23bbe7517c8b900 Mon Sep 17 00:00:00 2001 From: Vito De Giosa Date: Wed, 2 Dec 2020 14:29:59 +0100 Subject: [PATCH 10/10] remove useless link --- docs/content/utilities/parameters.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/content/utilities/parameters.mdx b/docs/content/utilities/parameters.mdx index b6ba5d711..3c98901a1 100644 --- a/docs/content/utilities/parameters.mdx +++ b/docs/content/utilities/parameters.mdx @@ -313,7 +313,6 @@ public class AppWithAnnotation implements RequestHandler