Skip to content
44 changes: 44 additions & 0 deletions docs/content/utilities/parameters.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

// 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<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

@Param(key = "/my/parameter/json" transformer = JsonTransformer.class)
ObjectToDeserialize value;

}
```
5 changes: 5 additions & 0 deletions powertools-parameters/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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. <pre>provider.withTransformation(json).get("key", MyObject.class)</pre>).
*/
protected BaseProvider withTransformation(Class<? extends Transformer> transformerClass) {
public BaseProvider withTransformation(Class<? extends Transformer> transformerClass) {
if (transformationManager == null) {
throw new IllegalStateException("Trying to add transformation while no TransformationManager has been provided.");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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}.
*
* <p>By default {@code Param} use {@link SSMProvider} as parameter provider. This can be overridden specifying
* the annotation variable {@code Param(provider = <Class-of-the-provider>)}.<br/>
* 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}.</p>
*
* <p>If the parameter value requires transformation before being assigned to the annotated field
* users can specify a {@link Transformer}
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Param {
String key();
Class<? extends BaseProvider> provider() default SSMProvider.class;
Class<? extends Transformer> transformer() default Transformer.class;
Comment thread
vitodegiosa marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -27,22 +32,39 @@ public final class ParamManager {
private static final CacheManager cacheManager = new CacheManager();
private static final TransformationManager transformationManager = new TransformationManager();

private static SecretsProvider secretsProvider;
private static SSMProvider ssmProvider;
private static ConcurrentHashMap<Class<? extends BaseProvider>, BaseProvider> providers = new ConcurrentHashMap<>();

/**
* Get a concrete implementation of {@link BaseProvider}.<br/>
* 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 extends BaseProvider> T getProvider(Class<T> providerClass) {
if (providerClass == null) {
throw new IllegalStateException("You cannot provide a null provider class.");
}
try {

if(!providers.containsKey(providerClass)) {
Comment thread
vitodegiosa marked this conversation as resolved.
Outdated
Constructor<T> 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);
Comment thread
vitodegiosa marked this conversation as resolved.
Outdated
}
}

/**
* Get a {@link SecretsProvider} with default {@link SecretsManagerClient}.<br/>
* 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) {
secretsProvider = SecretsProvider.builder()
.withCacheManager(cacheManager)
.withTransformationManager(transformationManager)
.build();
}
return secretsProvider;
return getProvider(SecretsProvider.class);
}

/**
Expand All @@ -51,13 +73,7 @@ public static SecretsProvider getSecretsProvider() {
* @return a {@link SSMProvider}
*/
public static SSMProvider getSsmProvider() {
if (ssmProvider == null) {
ssmProvider = SSMProvider.builder()
.withCacheManager(cacheManager)
.withTransformationManager(transformationManager)
.build();
}
return ssmProvider;
return getProvider(SSMProvider.class);
}

/**
Expand All @@ -66,14 +82,14 @@ public static SSMProvider getSsmProvider() {
* @return a {@link SecretsProvider}
*/
public static SecretsProvider getSecretsProvider(SecretsManagerClient client) {
if (secretsProvider == null) {
secretsProvider = SecretsProvider.builder()
if (!providers.containsKey(SecretsProvider.class)) {
providers.putIfAbsent(SecretsProvider.class, SecretsProvider.builder()
Comment thread
vitodegiosa marked this conversation as resolved.
Outdated
.withClient(client)
.withCacheManager(cacheManager)
.withTransformationManager(transformationManager)
.build();
.build());
}
return secretsProvider;
return (SecretsProvider) providers.get(SecretsProvider.class);
}

/**
Expand All @@ -82,14 +98,14 @@ public static SecretsProvider getSecretsProvider(SecretsManagerClient client) {
* @return a {@link SSMProvider}
*/
public static SSMProvider getSsmProvider(SsmClient client) {
if (ssmProvider == null) {
ssmProvider = SSMProvider.builder()
.withClient(client)
.withCacheManager(cacheManager)
.withTransformationManager(transformationManager)
.build();
if (!providers.containsKey(SSMProvider.class)) {
providers.putIfAbsent(SSMProvider.class, SSMProvider.builder()
Comment thread
vitodegiosa marked this conversation as resolved.
Outdated
.withClient(client)
.withCacheManager(cacheManager)
.withTransformationManager(transformationManager)
.build());
}
return ssmProvider;
return (SSMProvider) providers.get(SSMProvider.class);
}

public static CacheManager getCacheManager() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package software.amazon.lambda.powertools.parameters.exception;

public class ProviderException extends RuntimeException {
Comment thread
vitodegiosa marked this conversation as resolved.
Outdated

public ProviderException(Exception e) {
super(e);
}

public ProviderException(String message) {
Comment thread
vitodegiosa marked this conversation as resolved.
Outdated
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -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()));
Comment thread
vitodegiosa marked this conversation as resolved.
Outdated
}
if(paramAnnotation.transformer().isInterface()) {
Comment thread
vitodegiosa marked this conversation as resolved.
// 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());
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String, String> getMultipleValues(String path) {
return null;
}
}
Loading