How to escape SpEL dollar signs in Spring YAML configuration? - java

In a Spring YAML configuration file, I need to have a parameter like
csv:
file:
pattern: /some/path/${app-name}.csv
where the ${app-name} is dynamically replaced in run time by the Java code, and I do not want Spring to replace it at the startup.
To achieve this, I need to escape the $ character so Spring does not interpret it as SpEL.
The following answers do not work in YAML:
\$ How to escape EL dollar signs?
#{'$'} Escape property reference in Spring property file
Could not read properties if it contains dollar symbol (${var})
I tried all the combinations, like
pattern: /some/path/\${app-name}.csv
pattern: "/some/path/\${app-name}.csv"
pattern: /some/path/#{'$'}{app-name}.csv
pattern: "/some/path/#{'$'}{app-name}.csv"
and none of them produces the variable containing the requested string, with the dollar sign but without the escape characters.
Please notice that it is YAML configuration. In YAML files, # is the line comment character, everything from this character on is ignored. And if I use \#, the \ is then passed to the string.
ADDED: There has been an Spring project open issue 9628 open since 25.06.2008:
There is presently no way to inject a ${...} expression that won't be picked up by PropertyPlaceholderConfigurer. Ideally we should be able to inject a string that contains ${...} for later use in its target bean without involvement from PropertyPlaceholderConfigurer.

I had the same problem, i just found dumb clever solution
define a property named dollarSign or ds for short.
ds: "$"
then use it like so, ${ds} will be replace by $ at runtime.
csv:
file:
pattern: /some/path/${ds}{app-name}.csv
it was kind of funny when it worked.

Spring currently does not offer an escaping mechanism for property placeholders, there is an open issue (opened on 25.06.2008). In the comments, this workaround is mentioned (I am not sure whether it works with YAML):
csv:
file:
pattern: /some/path/#{'$'}{app-name}.csv
Note that when used after whitespace or at the beginning of a line, # in YAML starts a comment.

I've encountered a same problem. So you can resolve this by using yaml literal style symbol "|" , or by using literal_strip "|-" like following example.
application.yml
csv:
file:
pattern: |-
/some/path/${app-name}.csv
Actually My problem is config a formula in yml and then dynamic resolve the expression in java. Sharing the solution here.
I choose spring el solution and use spring version 5.0.9.RELEASE.
I define a formular in yml,
score:
formula: |-
10 * #x + #y
Then in a spring component bean,
#Value("${score.formula}")
String scoreFormula;
At last by using spring el,
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("x", 1);
context.setVariable("y", 1);
Integer score = parser.parseExpression(scoreFormula).getValue(context,Integer.class);
reference
yaml-multi-line

The answer really depends on how exactly you inject values into your code. Since you haven't provided it yet, I'll just list the various working techniques.
You can use #Value annotation to inject your property. Since, unlike #ConfigurationProperties, #Value does SpEL evaluation, you have to escape your value.
application.yml:
csv:
file:
pattern: /some/path/#{'$'}{app-name}.csv
TestController.java:
#RestController
public class TestController {
#Value("${csv.file.pattern}") private String pattern;
#GetMapping("pattern") public ResponseEntity<String> getPattern() {
return ResponseEntity.ok(pattern);
}
}
A GET to /pattern would result in an output /some/path/#{app-name}.csv, just as you needed
You can use #ConfigurationProperties, and here it really depends on how you structure your configuration objects. Generally, though, #ConfigurationProperties should require no escaping, as they don't support SpEL by default. The following works, though, so if your setup is different, modify it:
application.yml:
csv:
file:
pattern: /some/path/#{app-name}.csv
Config.java:
#ConfigurationProperties(prefix = "csv.file")
public class Config {
private String pattern;
public String getPattern() { return pattern; }
public void setPattern(String pattern) { this.pattern = pattern; }
}
TestController.java:
#RestController
public class TestController {
#Autowired private Config config;
#GetMapping("pattern") public ResponseEntity<String> getPattern() {
return ResponseEntity.ok(config.getPattern());
}
}
Again, a GET to /pattern would result in an output /some/path/#{app-name}.csv
What you most likely have is some different structure in your Config.java (post the relevant code, maybe?), and this could cause the property to not be processed properly.

Why not try using ${sys:$} which is ugly but effective. I think no one will use $ as the key.

Actually none of the answers worked for me. However, adding a double dollar sign worked for me fine:
csv:
file:
pattern: /some/path/$${app-name}.csv

You need to use #{'$'} and as you use yaml you need to surround the value with double quotes:
csv:
file:
pattern: "/some/path/#{'$'}{app-name}.csv"

Use a combination of empty key and dollar sign $ as default value:
csv:
file:
pattern: /some/path/${:$}{app-name}.csv

Related

Spring boot exact comma next to the path variable

I have an spring boot app with 2.1.0.RELEASE version.
I have a path variable in my url like #GetMapping("/{type}/car") and I call my app with:
http://localhost:8080/BMW;AAAAAAAAAAAAA/car
But i get only BMW string. Spring skip exact comma and "AAAAAAAAAAAAA".
I apply a filter and I have a same expereince with it. I wanna get path variable like "BMW;AAAAAAAAAAAAA", but I dont get it.
The reason why I want to filter this kind of call out, because it is a security hole.
Try encoding first, the value will be this BMW%3BAAAAAAAAAAAAA, you can use UriUtils for encoding and decoding.
I find a possible solution:
#Configuration
public class SolutionConfig extends WebMvcConfigurationSupport {
#Override
protected PathMatchConfigurer getPathMatchConfigurer() {
PathMatchConfigurer pathMatchConfigurer = super.getPathMatchConfigurer();
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
pathMatchConfigurer.setUrlPathHelper(urlPathHelper);
return pathMatchConfigurer;
}
}
Source code link: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java
Key method name is "removeSemicolonContent".

Automatically trim trailing white space for properties in properties file

Spring doesn't trim the values given in properties file. As per the discussion here, it looks like they have kept in intentionally.
However, in our project, we want to trim the values automatically before it is getting used in the application.
I am using 2.1.4.RELEASE.
I tried by adding following Bean configuration
#Bean
public static PropertyPlaceholderConfigurer properties() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[]{new ClassPathResource("application.properties")};
ppc.setLocations(resources);
ppc.setIgnoreUnresolvablePlaceholders(true);
ppc.setTrimValues(true);
return ppc;
}
Because of this setting, it is not able to load properties file and throwing following exception
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'kafka.groupId' in value "${kafka.groupId}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)
at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:237)
at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:211)
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175)
Anyone has tried to solve this problem?
I referred the following links but didn't get much help.
Automatically Trim Trailing White Space for properties in Props file loaded into Spring,
https://htr3n.github.io/2018/11/spring-boot-trailing-whitespaces/
One easy way to do it would be to "hack" the spEl Expression to force the use of the String.trim() function.
Let's say you have a property test.myvalue equal to azerty (with trailing spaces) in the application.properties file, then you could inject this property in your class like this :
#Value("#{'${test.myvalue}'.trim()}")
String myvalue;
The resulting myvalue will be equal to azerty (no trailing spaces) once injected in your class.
Obviously this trimming won't be set globally to all injected values in your app, and you'll have to do it to all injected value, but I think this approach gives more flexibility.

Spring cannot include/exclude beans using filters of type REGEX with an asterisk in #ComponentScan

I am trying to manage the beans my spring application loads using IncludeFilters property of #ComponentScan annotation. I use filter of type FilterType.REGEX. I would like to match anything as the last part of my pattern using but I seems not to work that way at all.
I have a bean definitions:
package org.example.child;
public class ChildDao {}
...
public class ChildService{}
...
public class ChildComponent{}
and configuration class definition:
#ComponentScan(
value = "com.example",
includeFilters = {#ComponentScan.Filter(type = FilterType.REGEX,
pattern = "com.example.*.Child*")})
With such a configuration, Spring does not find any bean at all.
When asterisk is used to match not the very last part of the pattern but is used somewhere in between, then it seems to work without a problem.
For example, following configuration matches all the Services without a problem:
#ComponentScan(
value = "com.example",
includeFilters = {#ComponentScan.Filter(type = FilterType.REGEX,
pattern = "com.example.*.*Service")})
Is this a designed behaviour of the framework or should it be possible to match the last part of the pattern using such an 'ant like' regex pattern?
Filters of type FilterType.REGEX are matched using standard java Pattern and Matcher, so no ant-like patterns like "com.example.*.Child*" would match. The only reason why "com.example.*.*Service" is because of the .* which matches any sequence of characters. In order to include/exlude using regex use valid regex.
Edit:
So in this case, one possible option would be to use pattern like com.example.child.Child.* to match any classes from com.example.child package starting with the Child in their names.

can we use spring expressions (spel) in other annotations?

I want to be able to do this:
#Controller
#RequestMapping("/#{handlerMappingPaths.security}/*")
public class SecurityController {
etc
//for instance, to resuse the value as a base for the folder resolution
#Value("#{handlerMappingPaths.security}/")
public String RESOURCE_FOLDER;
#RequestMapping(value="/signin-again", method = RequestMethod.GET)
public String signinAgainHandler() {
return RESOURCE_FOLDER + "signin_again";
}
}
this doesn't appear to work now, am I missing something?
One way you can find out things like this is to have a look yourself. This is an example for eclipse, but it should work similarly for other IDEs:
First of all, make sure you have the sources of the spring libraries you are using. This is easiest if you use maven, using the maven-eclipse-plugin or using m2eclipse.
Then, in Eclipse select Navigate -> Open Type.... Enter the type you are looking for (something like RequestMa* should do for lazy typers like myself). Enter / OK. Now right-click the class name in the source file and select References -> Project. In the search view, all uses of this class or annotation will appear.
One of them is DefaultAnnotationHandlerMapping.determineUrlsForHandlerMethods(Class, boolean), where this code snippet will tell you that expression language is not evaluated:
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
RequestMapping mapping = AnnotationUtils.findAnnotation(
method, RequestMapping.class);
if (mapping != null) {
String[] mappedPatterns = mapping.value();
if (mappedPatterns.length > 0) {
for (String mappedPattern : mappedPatterns) {
// this is where Expression Language would be parsed
// but it isn't, as you can see
if (!hasTypeLevelMapping && !mappedPattern.startsWith("/")) {
mappedPattern = "/" + mappedPattern;
}
addUrlsForPath(urls, mappedPattern);
}
}
else if (hasTypeLevelMapping) {
urls.add(null);
}
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
Remember, it's called Open Source. There's no point in using Open Source Software if you don't try to understand what you are using.
Answering in 2020: with current Spring versions, SpEL expressions can be used in #RquestMappning annotations.
They are correctly parsed.
Inner details:
Spring's RequestMappingHandlerMapping calls embeddedValueResolver#resolveStringValue.
JavaDoc of EmbeddedValueResolver states the following:
StringValueResolver adapter for resolving placeholders and expressions
against a ConfigurableBeanFactory. Note that this adapter resolves
expressions as well, in contrast to the
ConfigurableBeanFactory.resolveEmbeddedValue method. The
BeanExpressionContext used is for the plain bean factory, with no
scope specified for any contextual objects to access.
Since: 4.3
This means both regular placeholders (e.g. ${my.property}) and SpEL expressions will be parsed.
Note that because regular placeholders are parsed first and SpEL expressions are parsed later, it's even possible to set the value of a property to a SpEL expression. Spring will first replace the placeholder with the property value (SpEL expression) and then parse the SpEL expression.
#Sean answered the question of whether spring supported this, but I also wanted to answer the question of just generally how not to duplicate configuration when using annotations. Turns out this is possible using static imports, as in:
import static com.test.util.RequestMappingConstants.SECURITY_CONTROLLER_PATH
#Controller
#RequestMapping("/" + SECURITY_CONTROLLER_PATH + "/*")
public class SecurityController {
etc
//for instance, to resuse the value as a base for the folder resolution
public String RESOURCE_FOLDER = SECURITY_CONTROLLER_PATH + "/";
#RequestMapping(value="/signin-again", method = RequestMethod.GET)
public String signinAgainHandler() {
return RESOURCE_FOLDER + "signin_again";
}
}

Controller arguments annotated with Spring 3 MVC PathParameter is stripping data after the last '.'

When experimenting with Spring MVC, I noticed the values passed to controller arguments annotated with #PathVariable will have all the characters from the last '.' on stripped, unless the last character is a '/'.
For example, given the following code:
#RequestMapping("/host/${address})"
public String getHost(#PathVariable String address, Model model) {
model.addAttribute("host", hostRepository.getHost(address));
return "host";
}
If the URL is "/host/127.0.0.1", the value of address will be "127.0.0". If the URL is "/host/127.0.0.1/", the value of address will be "127.0.0.1".
Is there away to prevent this stripping?
There are plenty of such reports in their issue tracker already (for example, SPR-5778). But they don't fix it, so it seems to be a legitimate behaviour.
The official workaround is to set useDefaultSuffixPattern = false on DefaultAnnotationHandlerMapping, but it has several drawbacks:
It is applied to all controllers
It completely disables extension handling (for example, for ContentNegotiationViewResolver)
It breaks "trailing slash doesn't matter" rule
More sophisticated workarounds use a customized PathMatcher, as described here.
It's apparently been handled as a file extension and stripped. Not sure if it's a bug. I would fill an issue at their issuetracker.
Update: please check this topic, it's actually not a bug and it can be solved programmatically: Trying to create REST-ful URLs with mulitple dots in the “filename” part - Spring 3.0 MVC

Categories