Skip to content
Merged
104 changes: 103 additions & 1 deletion docs/content/utilities/parameters.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,106 @@ 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;

}
```

### Install

If you want to use the ```@Param``` annotation in your project add configuration to compile-time weave (CTW) the powertools-parameters aspects into your project.

* [maven](https://maven.apache.org/):
```xml
<build>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
...
<aspectLibraries>
...
<aspectLibrary>
<groupId>software.amazon.lambda</groupId>
<artifactId>powertools-parameters</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
```
**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.
11 changes: 10 additions & 1 deletion powertools-parameters/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@
<artifactId>aspectjrt</artifactId>
<scope>compile</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand All @@ -99,6 +98,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
Expand All @@ -109,6 +113,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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
import software.amazon.lambda.powertools.parameters.cache.CacheManager;
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 +31,28 @@ 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("providerClass cannot be null.");
}
return (T) providers.computeIfAbsent(providerClass, (k) -> createProvider(k));
}

/**
* 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 +61,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 +70,11 @@ public static SSMProvider getSsmProvider() {
* @return a {@link SecretsProvider}
*/
public static SecretsProvider getSecretsProvider(SecretsManagerClient client) {
if (secretsProvider == null) {
secretsProvider = SecretsProvider.builder()
.withClient(client)
.withCacheManager(cacheManager)
.withTransformationManager(transformationManager)
.build();
}
return secretsProvider;
return (SecretsProvider) providers.computeIfAbsent(SecretsProvider.class, (k) -> SecretsProvider.builder()
.withClient(client)
.withCacheManager(cacheManager)
.withTransformationManager(transformationManager)
.build());
}

/**
Expand All @@ -82,14 +83,11 @@ 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();
}
return ssmProvider;
return (SSMProvider) providers.computeIfAbsent(SSMProvider.class, (k) -> SSMProvider.builder()
.withClient(client)
.withCacheManager(cacheManager)
.withTransformationManager(transformationManager)
.build());
}

public static CacheManager getCacheManager() {
Expand All @@ -99,4 +97,17 @@ public static CacheManager getCacheManager() {
public static TransformationManager getTransformationManager() {
return transformationManager;
}

private static <T extends BaseProvider> T createProvider(Class<T> providerClass) {
try {
Constructor<T> constructor = providerClass.getDeclaredConstructor(CacheManager.class);
T provider = constructor.newInstance(cacheManager);
provider.setTransformationManager(transformationManager);
return provider;
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException("Unexpected error occurred. Please raise issue at " +
"https://github.com/awslabs/aws-lambda-powertools-java/issues", e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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) {
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());
} 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;
}
}
Loading