I want to share a common "configuration" between multiple annotated classes. My initial approach was to point the annotations to a class which then extends the configuration class:
#MyAnnotation(config = SharedConfig.class)
class A {}
#MyAnnotation(config = SharedConfig.class)
class B {}
class SharedConfig extends BaseConfig{
public SharedConfig(){
super("abc",123)
}
}
My initial approach was to find the SharedConfig type during annotation processing and instantiate it to find out the actual config. The problem is I can't instantiate the SharedConfig class during the actual processing...
Any idea how to achieve this?
Indeed, you can't instantiate the config class. But what you can do, is query which annotations are on the config class.
So, you could imagine the following system:
old way:
#Movie(
name = "A Few Good Men",
director = #Director(lastName = "Reiner", firstName = "Rob"),
releaseYear = 1992,
liked = true)
public class Foo {}
#Movie(
name = "A Few Good Men",
director = #Director(lastName = "Reiner", firstName = "Rob"),
releaseYear = 1992,
liked = true)
public class Bar {}
new way:
#Movie(
name = "A Few Good Men",
director = #Director(lastName = "Reiner", firstName = "Rob"),
releaseYear = 1992)
public class Placeholder {
// This class serves solely as a place to store the above annotation.
private Placeholder() {}
}
#Movie(
config = Placeholder.class,
liked = true)
public class Foo {}
#Movie(
config = Placeholder.class,
liked = false)
public class Bar {}
Your rule would presumably be that anything explicitly set on the actual class is taken, and if there is nothing explicitly set but there is a 'config' class set, then the value from the annotation on that config class is taken.
Unfortunately, there's no (non-hacky) way to tell the difference between #Foo and #Foo(value="") when Foo is defined as #interface Foo {String value() default "";} - i.e. there is no way to differentiate an explicit setting of a value that is the same as the default value for a given anno parameter, so you can't actually use 'if you do not explicitly set it, then this defaulting mechanism applies' as a concept in annotations. Therefore, 'use the defaulting mechanism' must be based on the actual value - you need 'stand-in' values that mean: "Inherit from config". That means booleans are right out, unfortunately. You can use enums instead.
Here is an example:
public enum LikeStatus {
LIKED, DISLIKED, INHERIT;
}
// target classes/types
public #interface Movie {
Class<?> config() default Object.class;
LikeStatus liked default LikeStatus.INHERIT;
int releaseYear() default 0;
Director director() default #Director(lastName = "", firstName = "")
String name() default "";
}
and now you need to write some code that knows about the defaults and acts accordingly (so, if name() returns an empty string, that means you should check the config class for the Movie annotation and fetch its name. Same for a release year of 0, a director with a blank first and last name, and so on.
Related
I am working on serializing classes to XML. I've found #JsonInclude to be immensely helpful. However, I am having issues on filtering a class by its attribute. In the xml, if it's a US address I need to have one set of annotations for the fields using #JsonProperty. Because of this, I'm trying to use the Include annotation to only show the relevant field based on the country.
The US address needs to have USAddress as the wrapper element name. Foreign Addresses need to have ForeignAddress as the wrapper element name as well as different element names for state/zip.
Is there a way to access the class and it's attributes in the filter? I've tried using the super class for both types of addresses, but with Bazel, I run into circular dependency issues when doing that. Im super new to bazel :P
public class Thing {
#JsonInclude(value = Include.CUSTOM, valueFilter = CountryFilter.class)
private final USAddress usAddress = new USAddress({line1: "123 MockingBird Ln", country: "US"});
#JsonInclude(value = Include.CUSTOM, valueFilter = CountryFilter.class)
private final ForeignAddress foreignAddress = new ForeignAddress({line1: "123 MockingBird Ln", country: "DE"});
}
public class CountryFilter {
#Override
public boolean equals(Object obj) {
return ...; // This is where I'm having issues. Would like it to do something like obj.getCountry().equals("US").
}
}
I ended up using the #JsonInclude(Include.NON_NULL) and then used a conditional in the constructor to optionally set the address.
Is there some way in Spring Boot that I can perform validation on properties that depend on each other's values, and have the error message be associated with the property?
I want to return the errors to the user in a nice JSON structure:
{
"errors": {
"name": "is required if flag is true"
}
}
Example:
#Entity
public class MyEntity {
private boolean nameRequiredFlag;
// Required if "nameRequiredFlag" is set to true:
private String name;
}
One solution that doesn't solve my problem of associating the error message with the name property is to create a validator annotation for the entity:
#ValidEntity
public class MyEntity {
private boolean nameRequiredFlag;
// Required if "nameRequiredFlag" is set to true:
private String name;
}
#Constraint( validatedBy = { MyEntityValidator.class } )
#Documented
#Target( { ElementType.TYPE } )
#Retention( RetentionPolicy.RUNTIME )
public #interface ValidEntity{
Class<?>[] groups () default {};
String message () default "name is required if 'nameRequiredFlag' is true";
Class<? extends Payload>[] payload () default {};
}
public class MyEntityValidator implements Validator<ValidEntity, MyEntity> {
#Override
public boolean isValid ( MyEntity entity, ConstraintValidatorContext context ) {
if ( !entity.nameRequiredFlag ) return true;
return !StringUtils.isBlank( entity.getName() );
}
}
This is laughably cumbersome and doesn't solve my problem. Isn't there any way I can do this with the framework validation?
Edit: This is for a JSON API, and the consumer really needs to be able to associate the error message to a best guess at which field has an issue. It is not helpful to send the consumer an error message for the whole object, or a computed property.
Solution given by #EvicKhaosKat is one way of doing it. However, when there are too many fields dependent on each other in a complicated way, your class becomes full of annotations and I personally struggle a lot relating them.
A simpler approach is to create a method(s) in your pojo which does the cross field validations and returns a boolean. On the top of this method annotate it with #AssertTrue(message = "your message"). It will solve your problem in a cleaner fashion.
public class SampleClass {
private String duration;
private String week;
private String month;
#AssertTrue(message = "Duration and time attributes are not properly populated")
public boolean isDurationCorrect() {
if (this.duration.equalsIgnoreCase("month")) {
if (Arrays.asList("jan", "feb", "mar").contains(month))
return true;
}
if (this.duration.equalsIgnoreCase("week")) {
if (Arrays.asList("1-7", "8-15", "16-24", "25-31").contains(week))
return true;
}
return false;
}
}
Note: I have not tested this code but have used this approach in multiple places and it works.
Possible reason is that name validation operates on not-yet-fully constructed object, so nameRequiredFlag is not filled yet.
As an option there is a #GroupSequence annotation, which allows to group and perform validations in an order you specify.
For example it is possible to add to MyEntity annotations:
#ValidEntity(groups = DependentValidations.class)
#GroupSequence({MyEntity.class, DependentValidations.class})
So all the other validation annotations on MyEntity class gonna be performed first, and after that DependentValidations group, which consists of ValidEntity.
Thus ValidEntity will be called on fully created object, and the last in order.
(DependentValidations.class - just an empty interface created somewhere nearby, like any other marker interface)
https://www.baeldung.com/javax-validation-groups will possibly describe that in much more details.
p.s. answer provided by #Innovationchef will possibly suit the case more :)
Using the MapStruct framework, how do you map multiple fields into a single one (based on custom logic) while still mapping the other fields one to one?
Here is a simple example to illustrate what I mean:
public class Source {
private String firstname;
private String surname;
// other fields eg:
private String address;
private int age;
private int favoriteNumber;
}
public class Target {
private String fullname; // Sould be firstname + surname
// other fields eg:
private String address;
private int age;
private int favoriteNumber;
}
I know it's possible using expressions:
#Mapping(target = "fullname", expression = "java(el.getFirstname() + el.getSurname())")
But in my special use case, not depicted in this example, I need to use some external library for the merging/mapping of the two fields, which isn't feasible with expressions.
Is there a way to achieve merging two fields without expressions?
One approach would be to add a custom mapping method from the Source object to the merged value, and then declare the source for the merged field to be the whole source object:
interface CustomMappingMethodMapper {
#Mapping(target = "fullname", source = ".")
Target map(Source source);
default String getFullName(Source s) {
return s.getFirstname() + " " + s.getSurname();
}
}
You can use #AfterMapping annotation
https://mapstruct.org/documentation/stable/reference/html/#customizing-mappings-with-before-and-after
You would like to replace your interface with abstract class and then
#AfterMapping
void customMapping(#MappingTarget Target target, Source source) {
// any custom logic
}
You claim that calling an external library in an expression isn't feasible. This may not be true, depending on the nature of the library you're calling and the frameworks being used.
Static method
If the method being called is a static method on a class, it can be called directly within the expression annotation element of #Mapping. To avoid having to fully qualify the class being called, the imports element of #Mapper can be used.
#Mapper(imports = ExternalLibrary.class)
public interface SourceToTargetMapper {
#Mapping(target = "fullname",
expression = "java(ExternalLibrary.toFullName(s.getFirstname(), s.getSurname()))")
Target map(Source s);
}
Spring Framework bean
If the library method is a method on a Spring bean, the mapper can be made into a bean using the Spring component model, and the Spring bean containing the library method can be injected into the mapper:
#Mapper(componentModel = "spring")
public static abstract class SourceToTargetMapper {
#Autowired
ExternalLibrary externalLibrary;
#Mapping(target = "fullname",
expression = "java(externalLibrary.toFullName(s.getFirstname(), s.getSurname()))")
abstract Target map(Source s);
}
To use this mapper, inject it as a Spring bean, and call the mapping method on the bean:
#Component
public class Example {
#Autowired
private SourceToTargetMapper mapper;
public void demonstrate(Source s) {
System.out.println(mapper.map(s));
}
}
I haven't tested it myself, but I imagine this injection approach would work with the other component models supported by MapStruct (cdi & jsr330).
I needed to compose 2 basic fields into am Wrapped Object. Here's how I did id
#Mapping(source = "quantity", target = "energyQuantity.quantity")
#Mapping(source = "quantityUnit", target = "energyQuantity.energyQuantityUnit", qualifiedByName = "tradeSearchEnergyQuantityUnitToEnergyQuantityUnit")
Trade tradeSearchToDomainDTO(api.client.generated.model.Trade trade);
where energyQuantity is a Wrapper class for the 2 fields
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";
...
}
When we create a custom annotation, we declare elements as methods and later set values as if they were attributes.
For example, here we have declared a custom annotation ComponentType with elements name() and description() that look like methods.
public #interface ComponentType {
String name();// declared as method
String description();
}
When the annotation is used, they look like the below:
#ComponentType(name = "userContainer", // value looks like an attribute
description = "a user container")
public class UserEntity { }
My question is: Why doesn't Java allow to declaring elements as attributes, like this?
public #interface ComponentType {
String name; // Compilation Error
String description;
}
If the properties of an annotation weren't defined as abstract methods in an interface, they would have been members. Something like:
public #interface ComponentType {
String name;
String description;
}
However, all the members in an interface are implicitly final (and static) and the above code does not compile, because name and description aren't initialized.
But if they were actually initialized with some values:
public #interface ComponentType {
String name = "name";
String description = "description";
}
then snippets like the following one wouldn't have been possible:
#ComponentType(
name = "userContainer" //cannot assign a value to a final variable
, description = "a user container")
My observation is:
Java consider annotations as special type of interface so:
Like interface we can declare only final attributes in an annotation:
String appName = "test application";//final attribute, never reset value
Annotation may contains only abstract methods(a method that is declared without an implementation).
public #interface ComponentType {
String name();// declared as abstract method
When we annotated elements(e.g. class, method, attribute) by annotation we need to set return value of those abstract methods, which looks like attribute but actually acts as an implementation.
#ComponentType(name = "userContainer"//set return value of method name()
We can use the values we set during annotated elements(e.g. class, method, attribute) by simply calling abstract methods of annotation.
Annotation annotation = annotatedClassObject.getAnnotation(ComponentType.class);
ComponentType componentType = (ComponentType) annotation;
String someName = componentType.name(); //returns value set during annotating
So like as interface,
Annotation never support to declare any non-final attributes.
Annotation may contains some abstract methods and we need to set return value of
abstract method during annotated elements(e.g. class, method,
attribute).
Expecting More Feedback / Answer