Is it possible to use static final variables for Spring Expression Language? - spring

I'm using CaffeineCache in my SpringBoot application, and here is one of my annotations for a method:
#Cacheable(value = PROMOCODE_BY_CONFIG_BUNDLE_CODE, key = "{#configBundleCode, #siebelId ?: 'all'}")
public Long countPromocodesByConfigBundleCodeAndSiebelId(String configBundleCode, String siebelId) {
return preferentialMapper.countPromocodesByConfigBundleCodeAndSiebelId(configBundleCode, siebelId, NULL_SIEBEL_PROMOCODE_CACHE_KEY);
}
I have this variable:
private static final String NULL_SIEBEL_PROMOCODE_CACHE_KEY = "all";
and I want to use it in my SpEL query instead of 'all'. I've tried to use it with # or $ symbols, but that doesn't work.
Is it possible to use the variable in the query, and how?

That is called a type operator: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions-types
So, you can do like this:
T(com.my.proj.ClassWithConstant).NULL_SIEBEL_PROMOCODE_CACHE_KEY
But your NULL_SIEBEL_PROMOCODE_CACHE_KEY has to be public.

Related

Spring expression language: Using a variable as key for a map lookup in SpEL

I'm trying to use variable as key to look up a value in a map. I would like to be able to reference the variable directly (in this case jobTitle), but for some reason I need to prefix the variable with either #root. or #this. in order to get it to work. So this works:
parser.parseExpression("{ \"Manager\":\"37.5\", \"CEO\":\"40\"}[#root.jobTitle]"
(resolves to "37.5")
but this doesn't
parser.parseExpression("{ \"Manager\":\"37.5\", \"CEO\":\"40\"}[jobTitle]"
(resolves to null)
jobTitle is a root attribute on the context object. From looking at the SpEL docs it seems like I should be able to reference the attribute directly? Am I doing something wrong?
Full working code below
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Employee employee = new Employee("Joe Bloggs", "Manager");
Expression exp = parser.parseExpression(
"{ \"Manager\":\"37.5\", \"Ceo\":\"40\"}[#root.jobTitle]");
StandardEvaluationContext context = new StandardEvaluationContext(employee);
context.setRootObject(employee);
System.out.println(exp.getValue(context, String.class));
}
static class Employee {
private String firstName;
private String jobTitle;
Employee(String firstName, String jobTitle) {
this.firstName = firstName;
this.jobTitle = jobTitle;
}
public String getJobTitle() {
return jobTitle;
}
}
From looking at the SpEL docs it seems like I should be able to reference the attribute directly?
This is correct for below case, both will print "Manager"
System.out.println(parser.parseExpression("#root.jobTitle").getValue(context, String.class));
System.out.println(parser.parseExpression("jobTitle").getValue(context, String.class));
But for expression inside [], the handling is different. Since there is no language specification for SpEL, I can just explain base on the behaviour.
When you run
System.out.println(parser.parseExpression(
"{\"jobTitle\":\"37.5\"}[jobTitle]").getValue(context, String.class));
This will actually print "37.5"([jobTitle] is treated as [\"jobTitle\"]). The author decided to make it easier to access value of Map, and the drawback is you need to specify with #root.jobTitle when referencing the root object.

Does #Value support letting default value reference another #Value?

#Value("${test.v1:hello}")
private String testV1;
#Value("${test.v2:${test.v1}}")
private String testV2;
When test.v2 is not configured, i want the testV2 is the same with testV1. Does #Value support letting default value reference another #Value?
I think the best thing you can do is to define test.v2=${test.v1} in your application.properties file. This way, if you override the value for test.v2 through some other ways like environment variables, it will have the defined value, otherwise, it will have the test.v1 value.
As an alternative, you can use the "+" operand.
#Value("${test.v1:hello}")
private String testV1;
#Value("${test.v2:}" + "${test.v1}")
private String testV2;
#PostConstruct
public void postConstruct() {
if (StringUtils.isEmpty(testV2) {
testV2 = testV1;
}
}

Assign a value to a parameter in application properties before use

I have a property in my application.properties file in a SpringBoot project -
app.module.service.url=http://localhost:8090/{DOCID}
I have injected it into my Java file as below -
#Value("${app.module.service.url}")
private String url;
Now I need to replace DOCID with some value in my function(i.e-dynamic). How do I get to do this or am I completely missing it?
I am aware that we can do this in case of message properties in Spring. But here I have nothing to do with Locales.
Edit:
There is some value which I need to put in place of {DOCID} when using it within my Java implementation.
...
public class Sample{
#Value("${app.module.service.url}")
private String url;
public void sampleFunc(){
String str = "random Value" //some dynamic value goes in here
...
Now I need to replace {DOCID} with str in url
Two way binding is not possible in Spring Properties.It can only read run time and environmental variables.
Please refer following link for more information.
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding
You can use spring UriTemplate for this purpose.
In your implementation class:
...
public class Sample{
#Value("${app.module.service.url}")
private String url;
public void sampleFunc(){
String dockId = someValue; // customize according to your need
UriTemplate uriTemplate = new UriTemplate(url);
URI uri = uriTemplate.expand(docId);
new RestTemplate().postForEntity(uri, requestObhect, Responseclass.class);
}
...
For more information, you can refer :
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/UriTemplate.html
Try like this ...
app.module.service.url=http://localhost:8090/{{docId}}
And set run configuration as below
-DdocId = 133323 // any number which you want

How can I use a local variable in the annotation #Preauthorize?

i need to do something like this
String myVar = "myString";
...
#Preauthorize("customMethod(myVar)")
public void myMethod() {
...
}
but I'm failing at it. How can I do that? It says it cannot be resolved
EDIT:I'm decoupling few rest services and sometimes I have to share infos between them
#Value("${my-properties}")
String urlIWantToShare;
...
#PreAuthorize("isValid(#myValue,urlIWantToShare)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue)) {
return true;
}
this "isValid" custom security method will call an external service, that doesn't know anything about the caller and his infos. I need to transmit few infos and I need to take them from different kind of sources
One of the sources is my application.properties
EDIT2: I managed to do this
#PreAuthorize("isValid(#myValue, #myProperty)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue,
#Value("${my-property-from-app-properties}") String myProperty))
..but I want to use not only actual static properties but runtime one. Any help?
You can create a wrapper method without parameters which will call the desired method with parameters. In the annotation you can use the method without parameters
Apologies if I have misunderstood what you are trying to do, but from my understanding you're trying to set an annotation at runtime based on a variable / app.properties, so that you can then read this variable and then execute your class?
If this is the case, You cannot do this from an annotation alone as annotations cannot read local variables and cannot be set at runtime.
However, one option for you is to have an object which contains the 'values' of interest for you and then read the values from the object.
Something like the below:
PoJo
public class testObject{
#test
private String myVar;
private String myValue;
//Getters and Setters
}
Get Object values
public void getFields (Object obj){
Field fields = obj.getClass().getDeclaredFields();
for (Field f : fields){
test fieldAnnotation = f.getAnnotation(test.Class);
if (fieldAnnotation != null){
f.get(obj);
// Do checks based on this
}
}
}
Main Class
public static void main(String[] args){
//Create object
testObject test = new testObject();
test.setOne("testOne");
test.setTwo("testTwo");
getFields(test);
}
I've pulled this code based on what I had to do to get the fields - but in my case, I did not know the object types I was going to be passed. You are simply using the annotation to 'mark' the fields you want to retrieve and then reading the value from the object.
If you're in a similar situation, then you can see my answer here: initial answer
Let me know if i've misunderstood this and I can try and further clarify my answer.

#Cacheble annotation on no parameter method

I want to have #Cacheable annotation on method with no parameter. In that case, I use #Cacheable as follows
#Cacheable(value="usercache", key = "mykey")
public string sayHello(){
return "test"
}
However, when I call this method, it doesn't get executed and it get exception as below
org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 0): Property or field 'mykey' cannot be found on object of type 'org.springframework.cache.interceptor.CacheExpressionRootObject' - maybe not public?
Please suggest.
It seems that Spring doesn't allow you to provide a static text for the cache key in the SPEL, and it doesn't include as default the name of the method on the key, so, you could be in a situation when two methods using the same cacheName and without a key would potentially cache different results with the same key.
The easiest workaround is to provide the name of the method as the key:
#Cacheable(value="usercache", key = "#root.methodName")
public string sayHello(){
return "test"
}
This would set sayHello as the key.
If you really need a static key, you should define a static variable in the class, and use #root.target:
public static final String MY_KEY = "mykey";
#Cacheable(value="usercache", key = "#root.target.MY_KEY")
public string sayHello(){
return "test"
}
You can find here the list of SPEL expressions that you can use in your key.
Try adding single quotes around mykey. It's a SPEL expression, and the singles quotes make it a String again.
#Cacheable(value="usercache", key = "'mykey'")
You can omit the key parameter. Spring will then put the value with key SimpleKey.EMPTY into the cache:
#Cacheable("usercache")
Alternatively (apart from using SPEL outlined in the other solutions) you can always inject the CacheManager and manually handle it.
Add # in the key
#Cacheable(value="usercache", key = "#mykey")
public string sayHello(){
return "test"
}

Resources