Extending Spring data's #Document annotation - java

I am trying to wrap org.springframework.data.elasticsearch.annotations.Document into a custom annotation , MyDocument.
#MyDocument will inherit everything that the parent #Document has, and the spring data library should also process MyDocument annotation that way it would do it for parent #Document.
Is this possible at all?
I tried below code, but I can't set the required 'indexName' param for #Document.
#Document() // HOW TO PUT indexName of #Mydocument into this?
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public #interface MyDocument {
#AliasFor(annotation = Document.class, attribute = "indexName")
String indexName();
#AliasFor(annotation = Document.class, attribute = "useServerConfiguration")
boolean useServerConfiguration() default false;
#AliasFor(annotation = Document.class, attribute = "shards")
short shards() default 1;
#AliasFor(annotation = Document.class, attribute = "replicas")
short replicas() default 1;
#AliasFor(annotation = Document.class, attribute = "refreshInterval")
String refreshInterval() default "1s";
#AliasFor(annotation = Document.class, attribute = "indexStoreType")
String indexStoreType() default "fs";
#AliasFor(annotation = Document.class, attribute = "createIndex")
boolean createIndex() default true;
#AliasFor(annotation = Document.class, attribute = "versionType")
VersionType versionType() default VersionType.EXTERNAL;
// My document specific props
String myCustomProp() default "myDefault";
// more properties....
}
REFERENCE for #Document Annotation by spring data elasticsearch
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE })
public #interface Document {
String indexName();
#Deprecated
String type() default "";
boolean useServerConfiguration() default false;
short shards() default 1;
short replicas() default 1;
String refreshInterval() default "1s";
String indexStoreType() default "fs";
boolean createIndex() default true;
VersionType versionType() default VersionType.EXTERNAL;
}
EDITED : I actually needed to pass all the #Document params via this #MyDocument
EDIT#2 : Added #Document annotation class for reference

You are just missing the value argument of the #AliasFor annotation:
#Document() // HOW TO PUT indexName of #Mydocument into this?
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public #interface MyDocument {
#AliasFor(value = "indexName", annotation = Document.class)
String indexName();
boolean useServerConfiguration() default false;
short shards() default 1;
short replicas() default 1;
String refreshInterval() default "1s";
String indexStoreType() default "fs";
boolean createIndex() default true;
VersionType versionType() default VersionType.EXTERNAL;
// My document specific props
String myCustomProp() default "myDefault";
// more properties....
}
Note that this only works in spring-data-elasticsearch version 4.2.x (and above).

Related

Custom annotation used with #RequestMapping is not working in spring boot

I have created custom annotation like this
#Retention(RUNTIME)
#Target(METHOD)
#RequestMapping(consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = {
MediaType.APPLICATION_JSON_VALUE }, method = RequestMethod.POST)
public #interface JsonPostMapping {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "method")
RequestMethod[] method() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "params")
String[] params() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "headers")
String[] headers() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "consumes")
String[] consumes() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "produces")
String[] produces() default {};
}
I have the following controller
import com.config.requestmappings.JsonPostMapping;
#RestController
public class TestController {
#JsonPostMapping(value = "/hello")
public String hello() {
return "Hi, how are you";
}
}
The hello api is also being called with GET request, i am expecting that it should only be called upon POST request.
Remove the alias for method in the annotation and use #PostMapping instead of #RequestMapping. Set the defaults in the body of the annotation itself.
#PostMapping
public #interface JsonPostMapping {
// ...
#AliasFor(annotation = PostMapping.class)
String[] consumes() default { MediaType.APPLICATION_JSON_VALUE };
#AliasFor(annotation = PostMapping.class)
String[] produces() default { MediaType.APPLICATION_JSON_VALUE };
}

Annotation return default key value

when we use annotation
#NotNull and there is a constraint validation who happen
not null return automatically his message
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) #Retention(RUNTIME) #Repeatable(List.class) #Documented #Constraint(validatedBy = { }) public #interface NotNull {
String message() default "{jakarta.validation.constraints.NotNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/** * Defines several {#link NotNull} annotations on the same element. * * #see jakarta.validation.constraints.NotNull */ #Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) #Retention(RUNTIME) #Documented #interface List {
NotNull[] value(); } }
it there a way to return his key so:
jakarta.validation.constraints.NotNull.message
Assuming you are asking for a way to get the message key after a constraint violation was received - you should be able to do that by working with that object. In particular, what you should look for - ConstraintViolation#getMessageTemplate(). This returns the non-interpolated error message for a constraint violation.
For example, having a class:
class Test {
#NotNull
String string;
public Test(String string) {
this.string = string;
}
}
and then trying to do validation of the instance of such class:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator()
Set<ConstraintViolation<Test>> violations = validator.validate( new Test( null ) );
ConstraintViolation<Test> violation = violations.iterator().next();
assertEquals( "{jakarta.validation.constraints.NotNull.message}", violation.getMessageTemplate() );
If you are working with some frameworks and you catch an exception of ConstraintViolationException - look at ConstraintViolationException#getConstraintViolations(), which would give you that same collection of violations as in the example above.

Spring Boot Validation with environment variable

I would like to put the value of a spring boot environment variable into a validation annotation (#Min, #Max), but i don't know how to do it. Here is my code :
public class MessageDTO {
#Value("${validationMinMax.min}")
private Integer min;
#JsonProperty("Message_ID")
#NotBlank(message = "messageId cannot be blank.")
#Pattern(regexp = "\\w+", message = "messageId don't suits the pattern")
private String messageId;
#JsonProperty("Message_Type")
#NotBlank(message = "messageType cannot be blank")
private String messageType;
#JsonProperty("EO_ID")
#NotBlank(message = "eoId cannot be blank")
private String eoId;
#JsonProperty("UI_Type")
#NotNull(message = "uiType cannot be null")
#Min(1)
#Max(3)
private Integer uiType;
And here is my application.yml :
server:
port: 8080
spring:
data:
cassandra:
keyspace-name: message_keyspace
port: 9042
contact-points:
- localhost
validationMinMax:
min: 1
max: 3
I would like to put the field "min" and "max" of my yml into the annotation field #Min() and #Max() of my attribute uiType. Does anyone knows how to do it ? Thanks in advance for your help !
You can write your own validation annotation with a custom validator. In this validator you can autowire spring beans and inject configuration properties:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { MyValidator.class })
#Documented
public #interface MyValidationAnnotation {
String message() default "";
Class<?>[] groups() default {};
Class<? extends javax.validation.Payload>[] payload() default {};
}
The validator class:
public class MyValidator implements ConstraintValidator<MyValidationAnnotation, Integer> {
#Autowired
private MyService service;
public void initialize(MyValidationAnnotation constraintAnnotation) {
// ...
}
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if(service.validate(value)) {
return true;
} else {
return false;
}
}
}
And then use it:
#MyValidationAnnotation
Integer foo;

How insert field name when using internationalization with javax validations?

I'm using javax validation annotations with Spring Boot and internationalization. So I have the following field:
#Size(min = 3, max = 3, message = "{javax.validation.constraints.Size.message}")
private String currencyCode;
The US resource bundle has:
javax.validation.constraints.Size.message = size must be between {min} and {max}
So when this field fails on validation, I see the message "size must be between 3 and 3".
But what I want is to have a message like:
javax.validation.constraints.Size.message = {fieldName}'s size must be between {min} and {max}
Which would result in the message "currencyCode's size must be between 3 and 3".
Is this possible? Do I need to override a bean to make it work? Is there a pre-defined property for the field name?
You can create handler for MethodArgumentNotValidException
#ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) { ...
inside them you can invoke
e.getBindingResult().getFieldErrors();
This is collection with all informations about invalid field error eg. field name, rejected value.
At next, use message like in your's example and use StrSubstitutor from org.apache.commons.lang3. Just create map of parameters
key --> fieldName , value --> currencyCode
Of course you have to write some code, but here is the solution :)
By default #Size annotation does not provide option to put label in the message, instead you can create your own implementation like below so that it can be used across your application:
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = MySizeValidator.class)
public #interface MySize {
String message() default "{javax.validation.constraints.Size.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int min() default 0;
int max() default Integer.MAX_VALUE;
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#interface List {
Size[] value();
}
public abstract String label();
}
then create your own validator like MySizeValidator to validate MySize annotation like below
public class MySizeValidator implements ConstraintValidator<MySize, String> {
private static final Log log = LoggerFactory.make();
private int min;
private int max;
#Override
public void initialize(MySize parameters) {
min = parameters.min();
max = parameters.max();
validateParameters();
}
#Override
public boolean isValid(String field, ConstraintValidatorContext constraintValidatorContext) {
if (field == null) {
return true;
}
int length = field.length();
return length >= min && length <= max;
}
private void validateParameters() {
if (min < 0) {
throw log.getMinCannotBeNegativeException();
}
if (max < 0) {
throw log.getMaxCannotBeNegativeException();
}
if (max < min) {
throw log.getLengthCannotBeNegativeException();
}
}
}
Create ValidationMessages.properties with below key value
javax.validation.constraints.mySize.message={label}'s size must be between {min} and {max}
then in your POJO, you can put this new annotation,
#MySize(min = 3, max = 3, message = "{javax.validation.constraints.mySize.message}", label = "Currency Code")
private String currencyCode;

Get the name of an annotated field

If I had defined an annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Input {
String type() default "text";
String name();
String pattern() default "";
}
and use it for this methods:
#Column(name="nome", unique = true, nullable = false)
#Order(value=1)
#Input
private String nome;
#Column(name="resumo", length=140)
#Order(value=2)
#Input
private String resumo;
Is there any way to assign to attribute name the name of the annotated field (for instance: for the field String nome the value would be nome and for the field String resumo would be resumo)?
You cannot default the annotation variables to field names. But where ever you are processing the annotation, you can code such that it defaults to field's name. Example below
Field field = ... // get fields
Annotation annotation = field.getAnnotation(Input.class);
if(annotation instanceof Input){
Input inputAnnotation = (Input) annotation;
String name = inputAnnotation.name();
if(name == null) { // if the name not defined, default it to field name
name = field.getName();
}
System.out.println("name: " + name); //use the name
}

Categories