I have been trying to populate an annotation attribute value at runtime, but it is not being able to compile The error is
"The value for annotation attribute DynamoDBIndexHashKey.globalSecondaryIndexName must be a constant expression"
Here is the snippet
public static final String GLOBAL_SECONDARY_INDEX = "RENDITIONS" + System.getenv("REGION_CODE") + "-" +System.getenv("STAGE") + "-renditionStatus-Index";
@DynamoDBIndexHashKey(globalSecondaryIndexName = GLOBAL_SECONDARY_INDEX, attributeName ="renditionStatus")
The value for GLOBAL_SECONDARY_INDEX for the attribute globalSecondaryIndexName is not able to compile as a constant despite of using the "+" operator. Am I missing something?
Many Thanks in advance for trying to help
It is possible to dynamically resolve GSI name, although it is pretty hacky.
System.getenv("REGION_CODE") in the expression just use some kind of a template string e.g. {{env.REGION_CODE}} or __REGION_CODE__, in this example I will use {{env.REGION_CODE}}.public class MyClass {
@DynamoDBIndexHashKey(globalSecondaryIndexName = "RENDITIONS-{{env.REGION_CODE}}-{{env.STAGE}}", attributeName = "renditionStatus")
private String bar;
}
package com.mycompany.myproduct.utils;
import java.util.Arrays;
import java.util.stream.Collectors;
public class TemplateProcessor {
public static String processTemplate(String mustacheTemplate) {
return Arrays.stream(mustacheTemplate.split("\\{\\{|}}"))
.map(chunk -> chunk.startsWith("env.") ? System.getenv(chunk.substring(4)) : chunk)
.collect(Collectors.joining(""));
}
}
Patch all the codes where DynamoDBIndexHashKey.globalSecondaryIndexName() is used.
i. Find and copy all the classes that use DynamoDBIndexHashKey.globalSecondaryIndexName() into your project while making sure that the package part remains the same as the original.
ii. Everyplace where globalSecondaryIndexName() is called wrap it with calling the previously created processTemplate method.
Luckily (currently, in com.amazonaws:aws-java-sdk-dynamodb:1.12.422) the only place that calls DynamoDBIndexHashKey.globalSecondaryIndexName() is StandardAnnotationMaps.FieldMap.globalSecondaryIndexNames() so the patching won't take too much time.
The end result should look something like this:
package com.amazonaws.services.dynamodbv2.datamodeling;
...
import static com.mycompany.myproduct.utils.TemplateProcessor.processTemplate;
...
final class StandardAnnotationMaps {
...
public Map<KeyType,List<String>> globalSecondaryIndexNames() {
...
String processed = processTemplate(indexHashKey.globalSecondaryIndexName());
gsis.put(HASH, Collections.singletonList(processed));
...
}
...
}
As an alternative to the solution suggested by balazs, you can use the Spring Expression Language (SpEL), see
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.scrum-master</groupId>
<artifactId>SO_JavaUseSpELWithoutSpring_62301513</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>6.0.6</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-jcl</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
package de.scrum_master.stackoverflow.q62301513;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface DynamoDBIndexHashKey {
String value();
}
package de.scrum_master.stackoverflow.q62301513;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@DynamoDBIndexHashKey("'RENDITIONS-' + get('REGION_CODE') + '-' + get('STAGE') + '-renditionStatus-Index'")
public class Main {
public static void main(String[] args) {
ExpressionParser expressionParser = new SpelExpressionParser();
String parsedAnnotationValue = expressionParser
.parseExpression(Main.class.getAnnotation(DynamoDBIndexHashKey.class).value())
.getValue(System.getenv(), String.class);
System.out.println(parsedAnnotationValue);
}
}
Now e.g. set environment variables REGION_CODE=DE and STAGE=integration and run the program. The console log will print:
RENDITIONS-DE-integration-renditionStatus-Index
SpEL is quite powerful, and you can use it outside of Spring, if you do not mind that spring-core is a transitive dependency. But I think, this is acceptable if you make heavy use of it.
Update: If instead of using the Map.get method of the evaluation root object System.getenv() in your SpEL expression, you can also use #root and use the map accessor syntax ['myKey'], i.e. instead of
@DynamoDBIndexHashKey("'RENDITIONS-' + get('REGION_CODE') + '-' + get('STAGE') + '-renditionStatus-Index'")
you can write
@DynamoDBIndexHashKey("'RENDITIONS-' + #root['REGION_CODE'] + '-' + #root['STAGE'] + '-renditionStatus-Index'")
You could also bind the value to a named variable like env, if you prefer not to use the root object ex- or implicitly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With