Creating custom annotations with place-holder? - java

I'm creating some custom annotations. I need to create someones with "place-holders" as it is used in Spring
#Value("#{aParameter}")
or in JSF 2
#ManagedProperty(value="#{aParameter}")
I suppose that I must have a mapping somewhere (.properties or .xml file or an enum class) but I need to know to code this approach in custom annotation interface. I mean how to declare a place-holder in the annoatation interface ? and how to ensure the assignement of its value (in mapping file) when applying the annotation somewhere?
Thanks in advance.

You don't do it in the annotation declaration - you do it in the code using that annotation.
For example the #Value is declared like this:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public #interface Value {
/**
* The actual value expression: e.g. "#{systemProperties.myProp}".
*/
String value();
}
and if you trace how it's used you'll see that in org.springframework.web.bind.annotation.support.HandlerMethodInvoker class the value is fetched directly from the annotation defaultValue = ((Value) paramAnn).value(); and then resolved like this:
if (defaultValue != null) {
args[i] = resolveDefaultValue(defaultValue);
}
...
class AnnotationMethodHandlerAdapter{
...
protected Object resolveDefaultValue(String value) {
if (beanFactory == null) {
return value;
}
String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
if (exprResolver == null) {
return value;
}
return exprResolver.evaluate(placeholdersResolved, expressionContext);
}
So the logic taking care of resolving properties and such is placed in classes
that actually use read annotations and make them useful.

Related

Modify parameter value of custom annotation

Is there any way to implement annotation in order to change his parameter value by itself?
For example:
I would like create custom RequestMapping annotation to get rid of some code duplicates.
Current code:
#RequestMapping("/this/is/duplicate/few/times/some")
public class SomeController {
}
And I want to create something like this
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping()
public #interface CustomRequestMapping {
String value() default "";
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String "/this/is/duplicate/few/times"+value();
}
In order to reduce Request Mapping value to this:
#CustomRequestMapping("/some")
public class SomeController {
}
Unfortunately I cant find way to make that compilable.
Or maybe there is a way to use AliasFor annotation to pass parameter into destination array. Something like this:
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping()
public #interface CustomRequestMapping {
#AliasFor(annotation = RequestMapping.class, attribute = "value{1}")
String value() default "";
#AliasFor(annotation = RequestMapping.class, attribute = "value{0}")
String prefixPath() default "/this/is/duplicate/few/times";
}
What it seems is you are trying to make a subtype of an annotation and modify one of its attributes default value
Subtyping of annotation is not possible and here is the JSR stating the reason.
It complicates the annotation type system,and makes it much more difficult
to write “Specific Tools”.
“Specific Tools” — Programs that query known annotation types of arbitrary
external programs. Stub generators, for example, fall into this category.
These programs will read annotated classes without loading them into the
virtual machine, but will load annotation interfaces.
One solution to your duplication problem could be to extract a constant.
#Annotation(MyClass.FOO+"localValue")
public class MyClass
{
public static final String FOO = "foo";
...
}

MapStruct : Nested Iterable to Non-Iterable mapping?

I found this example about Iterable to Non-Iterable mapping using Qualifier :
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-iterable-to-non-iterable
But how to make this mapping able to map nested properties (using dot annotation)?
E.g. mapping the field xyz of first element of a collection in the source object to a plain field on the target object?
The example define a Qualifier
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface FirstElement {
}
then define a Custom Mapper
public class MapperUtils {
#FirstElement
public <T> T first(List<T> in) {
if (in != null && !in.isEmpty()) {
return in.get(0);
}
else {
return null;
}
}
}
and, finally, the mapping is defined as
#Mapping(target = "emailaddress", source = "emails", qualifiedBy = FirstElement.class )
But if I would like to extract from the first element of emails collection a specific field, e.g. like I would have done with code emails.get(0).getEmailAddress?
For example, I expect to write a mapping like this :
#Mapping(target = "emailaddress", source = "emails[0].emailAddress")
You just need to change the MapperUtils
public class MapperUtils {
#FirstElement
public String firstEmailAddress(List<Person> in) {
if (in != null && !in.isEmpty()) {
return in.get(0).getEmailAddress();
}
else {
return null;
}
}
}
Basically the parameter of the Annotated method should have the Iterable that you want to map from, and the return type should be the Non-Iterable that you want to map to.
If you do not want to create a custom mapping for the mapping an alternative is to use the expression attribute.
For example:
#Mapping(target = "emailaddress", expression = "emails != null && !emails.isEmpty() ? emails.get(0).getEmailAddress() : null")
However, be careful using the expression can lead to compile time problems if you make a mistake. MapStruct does not do any check for the validity of the expression and uses it as is.
Another option is to use the below line in your mapper
This uses a helperclass to help with the conversion
#Mapping(target = "emailaddress", qualifiedByName={"helperClass", "emailsToAddress"},
source = "emails")
add the helperclass in the components used part of #Mapper
#Mapper(
componentModel="spring",
uses ={
helperClass.class
},
)
The helper class would look something like
#Component
#Named("helperClass")
public class helperClass {
#Named("emailsToAddress")
public String emailsToAddress(List<Email> emails) {
if(emails != null || !emails.isEmpty )
return emails.get(0).getAddress();
else
return null;
}

Getting the original Object form an Annotation object

I am in a situation where I am getting all the annotations of a class using
final Annotation[] annotations = declaringClass.getAnnotations();
Now I know that one of the annotations is of type MyAnnotation which has the following format
public #interface MyAnnotation {
boolean state() default true;
I would like to be able to get the value set for the parameter state, how do I do this? Annotation seems to be a proxy and not the actual object.
If you're just looking for that particular annotation, you can get it directly like this:
MyAnnotation a = declaringClass.getAnnotation(MyAnnotation.class);
boolean state = a.state();
If you specifically want it from your array, just cast it:
MyAnnotation a = (MyAnnotation)annotations[i];
boolean state = a.state();

Using Spring #Value inside #Size

I am using Spring Boot and javax validation, particularly #Size.
I am trying to grab the values for size constraints from the application.properties file:
#Size(min= #Value("${device.name.minsize}"), max=#Value("${device.name.maxsize}"))
private String name;
But I receive the following compile time error:
Error:(26, 16) java: annotation not valid for an element of type int
Trying to fix this issue I'm attempting the following:
#Size(min=Integer.parseInt( #Value("${device.name.minsize}") ), max=Integer.parseInt( #Value("${device.name.maxsize}") ) )
But this has multiple errors as well.
How can I convert the #Value annotations correctly?
Am I headed down the wrong path?
What I am looking for is a clean way to pull size limitations out of code and into configuration that I can access server side and in my templated angularJS/html.
I don't think you'll be able to do that. Annotations require constant values as their parameters, since they need to be handled at compile time.
You could externalize the xml:
http://beanvalidation.org/1.1/spec/#xml-config
Alternatively, if you just want to use JSR-303 annotation metadata in AngularJS, you might have a look at Valdr and Valdr BeanValidation:
https://github.com/netceteragroup/valdr
https://github.com/netceteragroup/valdr-bean-validation
For yet another approach take a look at https://github.com/jirutka/validator-spring.
It allows you to use SpSEL expressions in bean validation annotations including config properties.
You won't be able to use the standard annotations like #Size though, you'd have to formulate the constraints as SpEL expressions.
The bad news: there's no way to do what you want with standard annotations from Java Validation API.
The good news: you can easily create a custom annotation that does exactly what you want.
You need to create a custom validation annotation (let's call it #ConfigurableSize) that takes as parameters two strings, one for the name of the property holding the min size and one for the name of the property holding the max size.
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Repeatable(ConfigurableSize.List.class)
#Constraint(validatedBy = {ConfigurableSizeCharSequenceValidator.class})
public #interface ConfigurableSize {
String message() default "size is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String minProperty() default "";
String maxProperty() default "";
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#interface List {
ConfigurableSize[] value();
}
}
The validator will retrieve the property values upon initialization, then it will perform the exact same validation checks as the #Size constraint. Even the constraint violation will have the exact same message. Please notice that if the property name is omitted the min and max will default respectively to 0 and Integer.MAX_VALUE, i.e. the same defaults for #Size.
public class ConfigurableSizeCharSequenceValidator implements ConstraintValidator<ConfigurableSize, CharSequence> {
private final PropertyResolver propertyResolver;
private int min;
private int max;
#Autowired
public ConfigurableSizeCharSequenceValidator(PropertyResolver propertyResolver) {
this.propertyResolver = propertyResolver;
}
#Override
public void initialize(ConfigurableSize configurableSize) {
String minProperty = configurableSize.minProperty();
String maxProperty = configurableSize.maxProperty();
this.min = "".equals(minProperty) ? 0 :
propertyResolver.getRequiredProperty(minProperty, Integer.class);
this.max = "".equals(maxProperty) ? Integer.MAX_VALUE :
propertyResolver.getRequiredProperty(maxProperty, Integer.class);
validateParameters();
}
private void validateParameters() {
if (this.min < 0) {
throw new IllegalArgumentException("The min parameter cannot be negative.");
} else if (this.max < 0) {
throw new IllegalArgumentException("The max parameter cannot be negative.");
} else if (this.max < this.min) {
throw new IllegalArgumentException("The length cannot be negative.");
}
}
#Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if (value == null) {
return true;
} else {
int length = value.length();
boolean retVal = length >= this.min && length <= this.max;
if (!retVal) {
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
hibernateContext.addMessageParameter("min", this.min)
.addMessageParameter("max", this.max);
hibernateContext.disableDefaultConstraintViolation();
hibernateContext
.buildConstraintViolationWithTemplate("{javax.validation.constraints.Size.message}")
.addConstraintViolation();
}
return retVal;
}
}
}
You apply the custom annotation in your bean
public class Device {
#ConfigurableSize(minProperty = "device.name.minsize", maxProperty = "device.name.maxsize")
private String name;
}
Then finally in your application.properties you'll define the properties
device.name.minsize=4
device.name.maxsize=8
And that's it. You can find more details and a full example in this blog post:
https://codemadeclear.com/index.php/2021/03/22/easily-configure-validators-via-properties-in-a-spring-boot-project/

Generating annotations in Java

I have a GSON annotation ("SerializedName") which I want to translate from my custom annotation. I mean, if I have "Serial" annotation with "SerialType" element (which tell me what the serialization type I want for the field), after setting the GSON type in the "SerialType" - how can I generate the GSON annotation for the specific field?
Example code:
#Target(ElementType.FIELD)
public #interface Serial
{
SerialType type();
String value();
}
public class Example
{
#Serial(type = SerialType.GSON, value = "test")
public int field;
}
will generated to:
public class Example
{
#SerializedName("test")
public int field;
}
Try looking at annotation processors. You can find more info in the docs
Here is a good post describing how to use them.

Categories

Resources