How to pass name value annotation in Java method? - java

The annotation class Get.java
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#interface Get {
String value();
}
The annotation class Field.java
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#interface Field {
String value();
}
The interface class GetInterface.java
public interface GetInterface {
#Get("/group/user?")
void getUser(#Field("id") String id);
}
I want to use this GetInterface in the following, how can I get the annotation name so I can use that as the parameter attribute to construct a get url with query parameters
public class Request implements GetInterface {
#Override
public getUser(String id) {
String getPath = "/group/user?"; //How can I get this path from the annotation?
String name = "how can I get the name attribute 'id'????";
String value = id; //this will be 123 in this example
//with the name an value, I can then construct a url like this
//https://www.foo.com/group/user?id=123
}
public static void main(String[] args) {
getUser("123);
}
}

Do the following:
Method m = GetInterface.class.getMethod("getUser");
Get getAnnotation = m.getAnnotation(Get.class)
Field fieldAnnotation = m.getParameters()[0].getAnnotation(Field.class)
String path = getAnnotation.value();
String fieldName = fieldAnnotation.value();

Related

Annotation value not reading from springboot properties.file

Created Custom annotation and add annotation at method level and pass value to Spring-Aspect.
springboot: application.properties spring.event.type=TEST
Output: PreHook Value|${spring.event.type}
I am expecting : TEST
Can someone please help how to populate value from properties file and inject to annotation.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface PreHook {
String eventType();
}
#Aspect
#Component
public class ValidationAOP {
#Before("#annotation(com.example.demo.annotation.PreHook)")
public void doAccessCheck(JoinPoint call) {
System.out.println("ValidationAOP.doAccessCheck");
MethodSignature signature = (MethodSignature) call.getSignature();
Method method = signature.getMethod();
PreHook preHook = method.getAnnotation(PreHook.class);
System.out.println("PreHook Value|" + preHook.eventType());
}
}`
#RestController
public class AddController {
#GetMapping("/")
#PreHook(eventType = "${spring.event.type}")
public String test() {
System.out.println("Testcontroller");
return "Welcome Home";
}
}
You have to add SPEL processing to you annotation to evaluate that expression. You should not expect Spring to handle everything for you magicaly out of the box.
public void doAccessCheck(JoinPoint call) {
///(....)
PreHook preHook = method.getAnnotation(PreHook.class);
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(preHook.eventType());
String parsedType= (String) exp.getValue();
System.out.println("PreHook Value|" + parsedType);
}
Please refer below link for details. you are just few steps away.
Use property file in Spring Test

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.

How to validate an attribute based on another in spring-boot in a clean way?

I am validating REST service request/bean in a spring-boot 2.3.1.RELEASE web application. Currently, I am using Hibernate Validator, though I am open to using any other way for validation.
Say, I have a model Foo, which I receive as a request in a Rest Controller. And I want to validate if completionDate is not null then status should be either "complete" or "closed".
#StatusValidate
public class Foo {
private String status;
private LocalDate completionDate;
// getters and setters
}
I created a custom class level annotation #StatusValidate.
#Constraint(validatedBy = StatusValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface StatusValidate {
String message() default "default status error";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
I created StatusValidator class.
public class StatusValidator implements ConstraintValidator<StatusValidate, Foo> {
#Override
public void initialize(StatusValidateconstraintAnnotation) {
}
#Override
public boolean isValid(Foovalue, ConstraintValidatorContext context) {
if (null != value.getCompletionDate() && (!value.getStatus().equalsIgnoreCase("complete") && !value.getStatus().equalsIgnoreCase("closed"))) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).
.addPropertyNode("status").addConstraintViolation();
return false;
}
return true;
}
}
When I validate Foo object (by using #Valid or #Validated or manually calling the validator.validate() method), I get following data in the ConstraintViolation.
Code:
// Update.class is a group
Set<ConstraintViolation<Foo>> constraintViolations = validator.validate(foo, Update.class);
constraintViolations.forEach(constraintViolation -> {
ErrorMessage errorMessage = new ErrorMessage();
errorMessage.setKey(constraintViolation.getPropertyPath().toString());
errorMessage.setValue(constraintViolation.getInvalidValue());
// Do something with errorMessage here
});
constraintViolation.getPropertyPath().toString() => status
constraintViolation.getInvalidValue() => (Foo object)
How can I set an invalid value (actual value of status attribute) in custom ConstraintValidator or anywhere else so that constraintViolation.getInvalidValue() returns value of status attribute?
OR
Is there a better way of validating request payload/bean where validation of an attribute depends on another attribute's value?
Edit :
I can do something like
if(constraintViolation.getPropertyPath().toString().equals("status")) {
errorMessage.setValue(foo.getStatus());
}
but this would involve maintaining the String constant of attribute names somewhere for eg. "status". Though, in the StatusValidator also, I am setting the attribute name .addPropertyNode("status") which also I would like to avoid.
Summary :
I am looking for a solution (not necessarily using custom validations or hibernate validator) where
I can validate json requestor or a bean, for an attribute whose validations depends on values of other attributes.
I don't have to maintain bean attribute names as String constants anywhere (maintenance nightmare).
I am able to get the invalid property name and value both.
You can use dynamic payload to provide additional data in the constraint violation. It can be set using HibernateConstraintValidatorContext:
context.unwrap(HibernateConstraintValidatorContext.class)
.withDynamicPayload(foo.getStatus().toString());
And javax.validation.ConstraintViolation can, in turn, be unwrapped to HibernateConstraintViolation in order to retrieve the dynamic payload:
constraintViolation.unwrap(HibernateConstraintViolation.class)
.getDynamicPayload(String.class);
In the example above, we pass a simple string, but you can pass an object containing all the properties you need.
Note that this will only work with Hibernate Validator, which is the most widely used implementation of the Bean Validation specification (JSR-303/JSR-349), and used by Spring as its default validation provider.
You can use the expression language to evaluate the property path. E.g.
Set<ConstraintViolation<Foo>> constraintViolations = validator.validate(foo);
constraintViolations.forEach(constraintViolation -> {
Path propertyPath = constraintViolation.getPropertyPath();
Foo rootBean = constraintViolation.getRootBean();
Object invalidPropertyValue = getPropertyValue(rootBean, propertyPath);
System.out.println(MessageFormat.format("{0} = {1}", propertyPath, invalidPropertyValue));
});
private static Object getPropertyValue(Object bean, Path propertyPath) {
ELProcessor el = new ELProcessor();
el.defineBean("bean", bean);
String propertyExpression = MessageFormat.format("bean.{0}", propertyPath);
Object propertyValue = el.eval(propertyExpression);
return propertyValue;
}
The expression language does also work with nested beans. Here is a full example
You will need Java >1.8 and the follwing dependencies:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.0.2.Final</version>
</dependency>
and my java code
public class Main {
public static void main(String[] args) {
ValidatorFactory buildDefaultValidatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = buildDefaultValidatorFactory.getValidator();
// I added Bar to show how nested bean property validation works
Bar bar = new Bar();
// Must be 2 - 4 characters
bar.setName("A");
Foo foo = new Foo();
foo.setBar(bar);
foo.setCompletionDate(LocalDate.now());
// must be complete or closed
foo.setStatus("test");
Set<ConstraintViolation<Foo>> constraintViolations = validator.validate(foo);
System.out.println("Invalid Properties:");
constraintViolations.forEach(constraintViolation -> {
Path propertyPath = constraintViolation.getPropertyPath();
Foo rootBean = constraintViolation.getRootBean();
Object invalidPropertyValue = getPropertyValue(rootBean, propertyPath);
System.out.println(MessageFormat.format("{0} = {1}", propertyPath, invalidPropertyValue));
});
}
private static Object getPropertyValue(Object bean, Path propertyPath) {
ELProcessor el = new ELProcessor();
el.defineBean("bean", bean);
String propertyExpression = MessageFormat.format("bean.{0}", propertyPath);
Object propertyValue = el.eval(propertyExpression);
return propertyValue;
}
#StatusValidate
public static class Foo {
private String status;
private LocalDate completionDate;
#Valid
private Bar bar;
public void setBar(Bar bar) {
this.bar = bar;
}
public Bar getBar() {
return bar;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public LocalDate getCompletionDate() {
return completionDate;
}
public void setCompletionDate(LocalDate completionDate) {
this.completionDate = completionDate;
}
}
public static class Bar {
#Size(min = 2, max = 4)
private String status;
public String getStatus() {
return status;
}
public void setName(String status) {
this.status = status;
}
}
#Constraint(validatedBy = StatusValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public static #interface StatusValidate {
String message()
default "default status error";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class StatusValidator implements ConstraintValidator<StatusValidate, Foo> {
#Override
public boolean isValid(Foo value, ConstraintValidatorContext context) {
if (null != value.getCompletionDate() && (!value.getStatus().equalsIgnoreCase("complete")
&& !value.getStatus().equalsIgnoreCase("closed"))) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("status").addConstraintViolation();
return false;
}
return true;
}
}
}
Output is:
Invalid Properties:
status = test
bar.status = A
Use #NotNull for Completion date and use Custom enum validator for status like this :
/*enum class*/
public enum Status{
COMPLETE,
CLOSED
}
/*custom validator*/
#ValueValidator(EnumValidatorClass = Status.class)
#NotNull
private String status;
#NotNull
private LocalDate completionDate;
/*anotation interface*/
#Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EnumValueValidator.class)
#Documented
public #interface ValueValidator {
public abstract String message() default "Invalid Status!";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
public abstract Class<? extends java.lang.Enum<?>> EnumValidatorClass();
}
/*anotation implementation*/
public class EnumValueValidator implements ConstraintValidator<ValueValidator, String>{
private List<String> values;
#Override
public void initialize(ValueValidator annotation)
{
values = Stream.of(annotation.EnumValidatorClass().getEnumConstants()).map(Enum::name).collect(Collectors.toList());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return values.contains(value);
}}

#PropertyMapping with list parameter

I have the next annotation with #PropertyMapping:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#ImportAutoConfiguration
#PropertyMapping("spring.test.testcontainers.datasource")
public #interface AutoConfigureDatasourceContainer {
#PropertyMapping("containers")
DatasourceContainer[] containers() default {};
}
And child annotation:
#Target({})
#Retention(RUNTIME)
public #interface DatasourceContainer {
#PropertyMapping("port")
int port() default 31335;
#PropertyMapping("username")
String username() default "";
#PropertyMapping("password")
String password() default "";
#PropertyMapping("database")
String database() default "";
#PropertyMapping("type")
DatasourceType type() default DatasourceType.MYSQL;
}
I expect that usages of this two annotations in next form:
#AutoConfigureDatasourceContainer(containers =
{
#DatasourceContainer(username = "username", password = "password", database = "users", type = DatasourceType.MYSQL)
})
will produce next property:
spring.test.testcontainers.datasource.containers[0].port=31335
spring.test.testcontainers.datasource.containers[0].username=username
.....
and so on. But this is not so.
I didn't find any examples in documentation about situations like this.
What is wrong with my code?

How to get values from custom sub-annotation?

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).

Categories

Resources