I need to build a small module that translates a SpEL-encoded expression into a string. This in order to throw Jexl away and access Application context.
For example, if my correctly-configured property file contains
application.name=AppTest
company.name=ACME Inc.
I would like the following path, or a string similar to it, to be translated according to properties
/path/to/#{application.name}/#{company.name}
# or $ is irrelevant for me
Normally in Spring you can inject properties into beans, and I have succeeded already. But now I want the user to enter a template string that can be translated using all the properties in the Application Context. Currently I don't need to access bean properties, but I might have in future. The above is the simplified variant of the most common scenario when defining a folder path. Little complexity is added by runtime parameters (e.g. time of day) but I am asking the question as is to work step by step.
So I have tried to understand Spring Expressions by playing with them, with the help of JUnit. I wrote the following code but can't manage to make it work
final HashMap<String, Object> propertySource = new HashMap<String, Object>();
private final String FOLDER_PATTERN = "#tmp/appTest/#{company}_#{appname}/q1"; //#tmp is only token being replaced "hardcoded", not passed to Spring
@Before
public void setUp() throws Exception
{
propertySource.put("appname", APPNAME);
propertySource.put("company", COMPANY);
applicationContext.getEnvironment()
.getPropertySources()
.addLast(new MapPropertySource("test", propertySource));
}
@Test
public void playWithExpression()
{
ExpressionParser expParser = new SpelExpressionParser();
StandardEvaluationContext stdEvaluationContext = new StandardEvaluationContext();
stdEvaluationContext.setBeanResolver(new BeanFactoryResolver(applicationContext.getBeanFactory()));
// stdEvaluationContext.setVariables(propertySource);
final TemplateParserContext templateParserContext = new TemplateParserContext();
String folderPattern = FOLDER_PATTERN.replace("#tmp", SYSTEM_TEMP_DIR);
String realPath = expParser.parseExpression(folderPattern, templateParserContext)
.getValue(stdEvaluationContext, String.class);
String calculatedPath = folderPattern.replace("#{company}", COMPANY)
.replace("#{appname}", APPNAME);
assertEquals(calculatedPath, realPath);
}
Partially copying and pasting other examples, I:
#{..}
notationThe expression is successfully instantiated but getValue throws exception
org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'company' cannot be found on null
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:220)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:94)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:81)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:131)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:299)
at org.springframework.expression.common.CompositeStringExpression.getValue(CompositeStringExpression.java:89)
at org.springframework.expression.common.CompositeStringExpression.getValue(CompositeStringExpression.java:136)
at it.phoenix.web.data.managers.test.FoldersManagerTemplateTests.playWithExpression(FoldersManagerTemplateTests.java:80)
Judging from the error, it seems that Spring is trying to resolve company
token as a property of the root object, which is missing in this case.
If I use the #{@...}
notation then Spring tries to interpret the token as a bean identifier. Not my case.
How can I resolve properties from AppContext's PropertySource
s in an Expression?
There might be a more elegant solution but
private final String FOLDER_PATTERN = "#tmp/appTest/#{environment.getProperty('company')}"
+ "_#{environment.getProperty('appname')}/q1"; // #tmp is only token being replaced
// "hardcoded", not passed to Spring
with
String realPath = expParser.parseExpression(folderPattern, templateParserContext)
.getValue(stdEvaluationContext, applicationContext, String.class);
works (i.e. use the application context as the root object in the evaluation).
EDIT
Slightly better...
private final String FOLDER_PATTERN = "#tmp/appTest/#{getProperty('company')}"
+ "_#{getProperty('appname')}/q1"; // #tmp is only token being replaced
// "hardcoded", not passed to Spring
with
String realPath = expParser.parseExpression(folderPattern, templateParserContext)
.getValue(stdEvaluationContext, applicationContext.getEnvironment(), String.class);