Java annotation with SpEL parser with constant string - java

I am trying to create custom annotation for one of my use case.
#MyAnnotation(key = "constantString-${os.version}-{ #userId }"
public void methodName(String userId)
I am trying to SpEL parser to parse key field of the annotation. I want to support constant strings, application properties and method properties as a part of the key field.
So in above example, I am expecting evaluated key field will be
constantString-1.10-abc ("abc" is the value of the user argument while calling method)
But I am getting an error
EL1008E: Property or field 'constantString' cannot be found on object of type 'java.lang.reflect.Method' - maybe not public or not valid?
I am struggling to get SpEL honor constant string as a part of expression, as it keeps complaining while evaluating expression.
Is there a way in SpEL we can have constant string, property and method arguments supported?
Thanks

Related

Int from application.properties

In application.properties:
comment.length=3000
Now I'd like to use this constant:
#Entity(name="clients_client")
public class Client {
#Column(length="${comment.length}")
private String comment;
}
When compiling, I get this error:
java: incompatible types: java.lang.String cannot be converted to int
This is very close to being a duplicate of How to import value from properties file and use it in annotation?, but I think there is a subtle difference between the questions.
You are trying to refer to a property in the #Column annotation by using ${comment.length}. What is really happening is that you try to assign the String "${comment.length}" to the length attribute of the annotation. This is of course not allowed, it expects an int.
Java, or Spring, can not "magically" replace ${propertyName} with a property. Spring, however, has its own way of injecting property values:
#Value("${value.from.file}")
private String valueFromFile;
Even if your entity was a Spring bean (for example annotated with #Component), and you injected the property with #Value, it cannot be used in the annotation. This is because values in annotations need to be constant, and is explained in more detail in the accepted answer to the near duplicate question.
Now I'd like to use this constant:
It simply is not a constant, it is determined at runtime.

Spring 4 #Value where property default is a java system property

In Spring 4, using the #Value annotation, what is the right way to specify a system property as a default if a specified property does not exists?
Whereas this works for the no-default case:
#Value("${myapp.temp}")
private String tempDirectory;
This doesn't work when I need a default:
#Value("#{myapp.temp ?: systemProperties.java.io.tmpdir}")
private String tempDirectory;
Nor does this:
#Value("#{myapp.temp ?: systemProperties(java.io.tmpdir)}")
private String tempDirectory;
Both of these give me an exception at the time Spring is trying to create the bean:
org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'configurationService': Invocation of init method failed;
nested exception is java.lang.NullPointerException
Can this be done?
I tried the following and it worked for me:
#Value("${myapp.temp:#{systemProperties['java.io.tmpdir']}}")
private String tempDirectory;
The missing parts for you I believe was not using ?: and needing the #{}. According to this answer:
${...} is the property placeholder syntax. It can only be used to dereference properties.
#{...} is SpEL syntax, which is far more capable and complex. It can also handle property placeholders, and a lot more besides.
So basically what is happening is we are telling Spring to first interpret myapp.temp as property placeholder syntax by using the ${} syntax. We then use : instead of ?: (which is called the Elvis operator) since the elvis operator only applies to Spring Expression Language expressions, not property placeholder syntax. The third part of our statement is #{systemProperties['java.io.tmpdir']} which is telling Spring to interpret the next expression as a Spring Expression and allows us to get system properties.
Try systemProperties['java.io.tmpdir'].
It's a map, so if the key has a dot in the name, you should use [..]
For me it only works with different property-names (being property.name.a a key with a value in my application.properties and property.name.b a Environment-Variable) like:
#Value("${property.name.a:${property.name.b}}")
The same names didn´t work for me like expected (loading the default, when the first property isn´t present), e.g.:
#Value("${property.name.a:${property.name.a}}")

pass variable to a method as a parameter

I have following code in my tml file:
<t:loop source="navItem.subPages" value="var:subPage">
<t:any element="li" class="prop:classForPageName">
<t:pagelink page="var:subPage">${getMenuPageName(var:subPage)}</t:pagelink>
</t:any>
</t:loop>
I have a problem to pass a variable var:subPage to method ${getMenuPageName(var:subPage)}, as this throws an exception:
Could not convert 'getMenuPageName(var:subPage)' into a component parameter binding: Error parsing property expression 'getMenuPageName(var:subPage)': line 1:15 no viable alternative at input '('.
You can't use binding prefixes (like var:) inside property expressions.
You may only use prefix in front of the expression to let Tapestry know how it should interpret the remainder (the part after the colon).
Refer to NBF grammar for property expressions to see what's allowed inside:
Tapestry Documentation > User Guide > Property Expressions.
Property expressions were created to support just very basic constructs. If you need more complex expressions you should create corresponding methods in your java class and refer to them using the prop: binding prefix.
Template expansions you've mentioned (${...}) work the same as parameter bindings:
Under the covers, expansions are the same as parameter bindings. The
default binding prefix for expansions is "prop:" (that is, the name of
a property or a property expression), but other binding prefixes are
useful, especially "message:" (to access a localized message from the
component's message catalog).

Hibernate's validateValue() throws illegal argument exception if a property has no rules?

I have the following class:
class Foo
{
#NotEmpty
private String member1;
#NotEmpty
private String member2;
private String member3; //this one is optional, so has no rules
}
I have a library to which I add all the property names and corresponding UI fields, each time the UI's onChange event occurs, I call validateValue() on the given field name for that field, to validate it and show error/success message.
The problem is, in this case where I have no rules for member3, if I try to validate it by doing this:
String value = event.getValue(); //whatever the new value is now
validator.validateValue(Foo.class, "member3", value);
On the 2nd line, I get the following exception:
Caused by: java.lang.IllegalArgumentException: member3 is not a valid property of
com.xxx.Foo
Both member1 and member2 within the same class, are validated correctly.
Is there anything I can do to avoid getting this exception on the fields that don't have any rules set on them? If not, is there a way (without reflection or specifying it manually for each field) to check if a rule has no rules set on it, so i can avoid calling validateValue on it?
Which version of Hibernate Validator are you using? I checked with the latest version (5.1.0.Final) and there it works. If you can I recommend you upgrade.
You can also create an issue in the Validator issue tracker, reporting your problem and in particular which Validator version you are using.
Last but not least, to answer your question about alternatives. You could use the Bean Validation metadata API to find the constrained properties:
validator.getConstraintsForClass(Foo.class).getConstrainedProperties()
This will allow you to process only the properties which are acually constrained.

Accessing property values in Annotations

I want to access a property value in my annotation, as an attribute's value.
For ex. in my property file I have an entry expression: 3/10 * * * * ?.
In my Scheduler class I use annotation #Scheduled (cron = "**VALUE**"). I want to read this value from the properties file corresponding to the expression key.
Tried doing this with #Value, but it returns a type of Value which cannot be converted to String.
From spring 3.0.1 you can do it like this
#Scheduled(cron = "${rates.refresh.cron}")
Refer to
http://forum.springsource.org/showthread.php?83053-Feature-Scheduled-with-Value-cron-expression
However, you cannot do this for fixDelay and fixRate due to type casting problem (fixDelay expects value in long, while annotation return String only). Check Mark's comments in https://jira.springsource.org/browse/SPR-6670
You can try to use the APT (annotation processing tool) to replace the value in the annotation with a value from the property file.

Categories