How to get values from custom sub-annotation? - java

I have a question about getting custom annotation value which is value of another custom annotation. For example I have a #SqlInfo annotation interface which have two values which is also annotation interfaces.
SqlInfo.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface SqlInfo {
CodificationInfo codificationInfo();
DocumentInfo documentInfo();
}
#CodificationInfo and #DocumentInfo is also annotation interfaces. Each of it has his own different values.
CodificationInfo.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface CodificationInfo {
enum KEYS {
DOMAIN,
FILE,
TABLE,
CLASS
}
String domain() default "";
String fileName() default "";
String table() default "";
Class codificationClass();
}
While I am using only #CodificationInfo annotation for the class. I am getting values from it by using this method:
Annotation values getter method
public Object getClassAnnotationValue(Class c, String key) {
Annotation annotation = c.getAnnotation(CodificationInfo.class);
return getObjectByKey(annotation, key);
}
private Object getObjectByKey(Annotation annotation, String key) {
if (annotation instanceof CodificationInfo) {
if (key.equalsIgnoreCase(CodificationInfo.KEYS.TABLE.toString())) {
return ((CodificationInfo) annotation).table();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.CLASS.toString())) {
return ((CodificationInfo) annotation).codificationClass();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.DOMAIN.toString())) {
return ((CodificationInfo) annotation).domain();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.FILE.toString())) {
return ((CodificationInfo) annotation).fileName();
}
}
return null;
}
I want to know how to get #CodificationInfo values while I am using #SqlInfo annotation for the class? It means - how to get values from sub-annotation?
P.S.: I know that I can use both annotations separately for the class. But I want to know the any way how to get values from sub-annotation. For example hibernate use it for #AuditOverrides annotation.

If you have a type declared like:
#SqlInfo(codificationInfo = #CodificationInfo(codificationClass = AClass.class)
public class MyType { }
you can reflectively get the inner annotation values with:
final SqlInfo sqlInfoAnnotation = (SqlInfo) c.getAnnotation(SqlInfo.class);
if (sqlInfoAnnotation == null) return;
final CodificationInfo codInfoAnnotation = sqlInfoAnnotation.codificationInfo();
final Class<?> codClass = codInfoAnnotation.codificationClass();
Note: you can avoid having to cast the annotation by not using raw types (prefer Class<?> over Class).

Related

Do not include property in POJO if it is null or doesn't exist in incoming JSON

Hi I have the following object mapper:
public <T> T convertJSONtoPOJO(String inputJSON,
Class<T> valueType) throws Exception {
try {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
return objectMapper.readValue(inputJSON, valueType);
} catch (Exception exception) {
//my exception which doesnt matter now
}
}
And incoming JSON:
{
"propertyA": "1",
"propertyB": null,
}
And POJO:
#FieldDefaults(level = AccessLevel.PRIVATE)
#Data
public class MyClazz {
String propertyA;
String propertyB;
String propertyC;
}
By default #JsonProperty required is set to false on all of them.
Now while deserializing I want to obtain POJO which does NOT include:
a) non-required variables if they do NOT occur in JSON
b) variables which have null value.
Currently my code does NOT fail, it simply gives me the following POJO:
propertyA = 1
propertyB = null
propertyC = null
But I want to obtain POJO with only:
propertyA = 1
If any of your fields are not required, you have to check them whether they are null or else, while using them. Because your class is represented by all the fields you define for it, and there is no way to ignore those fields with null values.
It's possible while serializing and converting to json, but not in deserializing.
One solution is create your own anotation #RequiredOnLoad
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface RequiredOnLoad {
}
And your own load json, some like this:
//new function with check required fields
public <T> T myConvertJSONtoPOJO(String inputJSON,
Class<T> valueType) throws Exception {
T pojo = convertJSONtoPOJO(inputJSON,valueType);
Field[] allFields = Person.class.getDeclaredFields();
for (Field field : allFields)
{
if(field.isAnnotationPresent(RequiredOnLoad.class))
{
field.setAccessible(true);//to access private fields
Object value = field.get(pojo);
if (value == null )
{
throw new Exception("Field is required on load!");
}
}
}
return pojo;
}
//old function without check
public <T> T convertJSONtoPOJO(String inputJSON,
{
...
Of course, in your POJO you need put the annotation when you field is required in load time

Validating field values in a Java POJO

Use Case:
Let's assume this is my POJO:
class Sample{
String field1;
String field2;
double field3;
LocalDateTime field4;
LocalDateTime field5;
//...getters, setters and parameterised constructor
}
I am reading certain values from an external file and creating a POJO using a parameterised constructor. All the fields have certain validation constraints for them.
What I am looking for is a way for those constraints to be evaluated automatically when I am creating an object using the parameterised constructor. If one or more validation constraints fail, it should throw an error.
What I have tried so far:
I have tried the Bean Validation approach in Spring by creating my own annotation and validator. The code is below:
POJO
#ValidChecker(groups = Default.class)
class Sample{
String field1;
String field2;
double field3;
LocalDateTime field4;
LocalDateTime field5;
//...getters, setters and parameterised constructor
}
ValidChecker Annotation
#Constraint(validatedBy = DataValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidChecker {
String message() default "Data is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
DataValidator.java
#SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
public class DataValidator implements ConstraintValidator<ValidChecker, ValidationData> {
#Override
public void initialize(ValidChecker constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(ValidationData validationData, ConstraintValidatorContext constraintValidatorContext) {
if (validationData == null) {
return false;
}
if (BigDecimal.valueOf(validationData.getField3()).scale() != 2) {
return false;
}
if (validationData.getField5().isBefore(validationData.getField4())) {
return false;
}
return true;
}
}
The above code didn't work.
Suggestions Needed
Problem with the above approach
Alternate approach using Spring
Alternate approach by using some third party library
I looked quite a bit but couldn't find an approach without Spring bean validation. Can someone please help?
You could use the Bean Validation API directly in your constructor:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Sample>> violations = validator.validate(this);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}

Modify POJO class fields with custom setter or custom annotation (in Spring Boot)

Given a POJO in Spring Boot with several dozen fields of type String which is deserialized by Jackson. For demonstration purposes the following example only contains three fields:
#NoArgsConstructor
public class SomeRequest {
#JsonProperty("field_1")
private String field1;
#JsonProperty("field_2")
private String field2;
#JsonProperty("field_3")
private String field3;
}
I'm looking for a way to override the setter method but only for certain fields, i.e. I'd like to avoid repeating the below code for every affected field. This is doable for a handful number of fields but gets tedious for more than a handful.
public setField2(String field2) {
this.field2 = field2 + "?";
}
My idea was to place an annotation on the field like this:
#NoArgsConstructor
public class SomeRequest {
// ...
#JsonProperty("field_2")
#AppendQuestionMark
private String field2;
// ...
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface AppendQuestionMark {
}
But I'm lacking information on how to "implement" the AppendQuestionMark annotation which would override the field's setter method.
Or am I thinking way too complicated?
You can't change the settermethod's body if that's what you are asking. But you can create a method that will take an object (i.e. SomeRequest) as input and check which fields have your Annotation and change the values for those fields as you want.
For example, I created an annotation AppendStr.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface AppendStr {
public String str();;
}
Then I created another class 'AppendStrImpl` that will handle the implementation. I used the following code -
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class AppendStrImpl {
public void changeFields(Object object) throws Exception {
Class<?> clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(AppendStr.class)) {
// get the getter method name from the field name
String fieldName = field.getName();
String getterMethodName =
"get" +
fieldName.substring(0, 1).toUpperCase() +
fieldName.substring(1);
Method getterMethod = clazz.getMethod(getterMethodName);
String returnValue = (String) getterMethod.invoke(object);
String setterMethodName = getterMethodName.substring(0, 1).replace("g", "s")
+ getterMethodName.substring(1);
Method setterMethod = clazz.getMethod(setterMethodName, String.class);
setterMethod.invoke(object, returnValue + getAppendingString(field));
System.out.println((String) getterMethod.invoke(object));
}
}
}
private String getAppendingString(Field field) {
return field.getAnnotation(AppendStr.class)
.str();
}
}
And this is my POJO class -
public class POJO {
#AppendStr(str = "?")
private String filed1;
#AppendStr(str = "!")
private String filed2;
private String filed3;
#AppendStr(str = "+")
private String filed4;
// ... getters and setters
}
Then I called this method from the main method -
POJO pojo = new POJO("a", "b", "c", "d");
AppendStrImpl appendStrImpl = new AppendStrImpl();
try {
appendStrImpl.changeFields(pojo);
} catch (Exception e) {
e.printStackTrace();
}
Now you can make this call with hard coding or you can use #Aspect too if you want.
The github link is here.
Instead of creating a new annotation that appends a question mark to one generic string field in your pojo you can use the already present JsonDeserialize annotation over the string fields you are interested:
#Data
#NoArgsConstructor
public class SomeRequest {
#JsonProperty("field_1")
private String field1;
#JsonProperty("field_2")
//here the custom deserializer appends the question mark character
#JsonDeserialize(using = StringAppendQuestionMarkDeserializer.class)
private String field2;
}
In your spring boot project you can register the custom deserializer with the JsonComponent annotation like below:
#JsonComponent
public class StringAppendQuestionMarkDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return node.asText() + "?";
}
}
A spring boot test example using the custom deserializer:
#JsonTest
class CorespringApplicationTests {
#Test
void testDeserialize() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SomeRequest request = mapper.readValue("{\"field_1\":\"value1\",\"field_2\":\"value2\"}", SomeRequest.class);
System.out.println(request); //<-- SomeRequest(field1=value1, field2=value2?)
}
}
Something like the following should do the trick:
#Aspect
#Component
public class AppendQuestionMarkAspect {
#Around("#annotation(AppendQuestionMark)")
public Object appendQuestionMark(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] arguments = joinPoint.getArgs();
return joinPoint.proceed(new Object[] {((String) arguments[0]) + "?"});
}
}
Of course, it would be advisable to check that only one argument exists and that it is, in fact, a String. Or you can also define the pointcut as to be applied only to methods starting with set. But the essence of the code is there.

Spring AOP using method & parameter annotations

Is there a way to get Spring AOP to recognize the value of an argument that has been annotated? (There is no guarantee in the order of the arguments passed into the aspect, so I'm hoping to use an annotation to mark the parameter that needs to be used to process the aspect)
Any alternative approaches would also be extremely helpful.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Wrappable {
}
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface Key {
}
#Wrappable
public void doSomething(Object a, #Key Object b) {
// something
}
#Aspect
#Component
public class MyAspect {
#After("#annotation(trigger)" /* what can be done to get the value of the parameter that has been annotated with #Key */)
public void trigger(JoinPoint joinPoint, Trigger trigger) { }
Here is an example of an aspect class which should process a method tagged with #Wrappable annotation. Once the wrapper method is called, you can iterate over method parameters to find out if any parameter is tagged with the #Key annotation. The keyParams list contains any parameter tagged with a #Key annotation.
#Aspect
#Component
public class WrappableAspect {
#After("#annotation(annotation) || #within(annotation)")
public void wrapper(
final JoinPoint pointcut,
final Wrappable annotation) {
Wrappable anno = annotation;
List<Parameter> keyParams = new ArrayList<>();
if (annotation == null) {
if (pointcut.getSignature() instanceof MethodSignature) {
MethodSignature signature =
(MethodSignature) pointcut.getSignature();
Method method = signature.getMethod();
anno = method.getAnnotation(Wrappable.class);
Parameter[] params = method.getParameters();
for (Parameter param : params) {
try {
Annotation keyAnno = param.getAnnotation(Key.class);
keyParams.add(param);
} catch (Exception e) {
//do nothing
}
}
}
}
}
}
We cannot get the parameter annotation value as an argument to AOP like we are doing it for the method annotation because the annotation is not an actual parameter and in there you can only reference actual arguments.
args(#Key b)
This annotation will give you the value of Object(b) not the value of #Key annotation.
We can do in this way to get the value of the parameter annotation:
MethodSignature methodSig = (MethodSignature) joinpoint.getSignature();
Annotation[][] annotations = methodSig.getMethod().getParameterAnnotations();
if (annotations != null) {
for (Annotation[] annotArr : annotations) {
for (Annotation annot : annotArr) {
if (annot instanceof KeyAnnotation) {
System.out.println(((KeyAnnotation) annot).value());
}
}
}
}

Getting annotation value of generic interface in java

I am trying to get the annotation values. This is my scenario as follows:
This is the annotation I declared.
#Retention(RetentionPolicy.RUNTIME)
public #interface PluginMessage {
String name();
String version();
}
This is the class the uses the annotation for some values
#PluginMessage(name = "RandomName", version = "1")
public class Response{
private Date Time;
}
This is a generic interface which will be used in the next code snippet.
public interface ResponseListener<E> {
void onReceive(E response);
}
I Invoke this by calling the following code:
addListener(new ResponseListener<Response>() {
#Override
public void onReceive(Response response) {
System.out.println();
}
});
This is the implementation of the addListener method:
public <E> void addListener(ResponseListener<E> responseListener) {
Annotation[] annotations = responseListener.getClass().getAnnotations();
}
The annotations are always empty, any idea of what I am doing wrong? I am trying to get the value of them here.
You may get annotations here:
.addListener(new ResponseListener<Response>() {
public void onReceive(Response response) {
final Annotation[] annotations = response.getClass().getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation.toString() = " + annotation.toString());
}
}
});
Your .addListener implementation makes no sense. Instead of getting annotations from ResponseListener(which has no annotations) instance, you have to add listener to listeners pool. Then you have to call listener.onReceive(...) for each listener when you will receive the response. I believe something like that should be implemented there.

Categories

Resources