Spring Boot 2.3.1, with OpenJDK 14
Constraint Annotation:
#Documented
#Constraint(validatedBy = {MyConstraintAnnotationValidator.class})
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyConstraintAnnotation {
String message() default "";
Class[] groups() default {};
Class[] payload() default {};
Class clazz();
// or, not sure: Class<?> clazz();
}
Constraint Annotation Validator:
public class MyConstraintAnnotationValidator implements ConstraintValidator<MyConstraintAnnotation, String> {
#Override
public void initialize(MyConstraintAnnotation constraintAnnotation) {
// cac is: configurableApplicationContext
Object obj = cac.getBean(constraintAnnotation.clazz());
// problem: obj doesn't provide the methods to access
// on the left side of the variable shall be also the clazz type
// which is passed to the constraint annotation
}
#Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return true;
}
}
Usage of constraint annotation:
public class MyPojo {
...
#MyConstraintAnnotation(clazz = MyProperties.class)
private String field1;
...
// Getter/ Setter
}
Properties class:
#Component
#PropertySource("classpath:myproperties.properties")
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
// fields
// Getter / Setter
}
Via the constraint annotation I want to specify my properties class to be used in the validation process within the validator class.
But within the initialize(..) method I cannot access the Getter / Setter from my properties class (MyProperties.class), because configurableApplicationContext.getBean(...) returns Object, shall create a variable and cast it to the passed class (like MyProperties.class).
How to create a variable which has on the left side the same type which is passed to the constraint annotation?
Related
I came across this method signature in Spring Component interface.
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Indexed
public #interface Component
{
String value() default "";
}
What is the method signature String value() default ""; means?
How and When should we define such syntax for our coding purposes?
This is no method signature. it means that you can pass a String as parameter to the Component annotation, like this:
#Component(value = "value")
If you don't specify a value your self, the default value "" will be used.
If it had been like this:
String value(); // without the default
value would have been a mandatory parameter. Trying to use Component like this:
#Component()
would lead to an Exception, since you didn't provide a value.
EDIT: when to use.
If you don't know much about this syntax, or annotations in general, you shouldn't use them. About everything that can be done using annotations, especially custom made ones, can also be done without annotations.
Let's say you want to create an annotation to validate the value of a field.
I'll be using the example of Belgian postal codes. They all are 4 digits, and are between 1000 and 9999.
#Target( {ElementType.FIELD})
#Retention( RetentionPolicy.RUNTIME)
#Constraint( validatedBy = ValidatePostalCodeImpl.class)
public #interface ValidatePostalCode{
String message() default "You have entered an invalid postal code";
Class<?>[] groups() default {}; // needed for the validation
Class<? extends Payload>[] payload() default{}; // needed for the validation
int maxValue() default 9999; // so, by default, this will validate based
int minValue() default 1000; // on these values, but you will be able to
// override these
}
/* Validation implementation */
public class ValidatePostalCodeImpl implements ConstraintValidator<ValidatePostalCode, Integer> {
int upperValue;
int lowerValue;
#Override
public void initialize(ValidatePostalCode validate) {
this.upperValue = validate.maxValue(); // here you call them as if they indeed were regular methods
this.lowerValue = validate.minValue();
}
#Override
public boolean isValid(Integer integer, ConstraintValidatorContext context) {
return integer >= lowerValue && integer <= upperValue;
}
}
/* Usage */
#Entity
#Table(name = "addresses")
public class Addresses {
// home address -> In Belgium, so has to be between the default values:
#ValidatePostalCode
Integer belgianPostalCode;
// vacation home in another country, let's say the PC's there are between
// 12000 and 50000
#ValidatePostalCode(minValue = 12000, maxValue = 50000)
Integer foreignPostalCode;
}
Sure, this is a very limited example, but it should get you an idea.
The #interface keyword is used to define an annotation. This annotation has a property called value, which you could specify explicitly:
#Component(value = "myValue")
Or in the shorthand form:
#Component("myValue")
If you don't specify the value, it will default to "", as defined by the default keyword.
I have a RESTful service which receives POST request with UUID values and writes them in DB. So the problem is to validate if UUID is valid or not. For this purpose I implemented custom annotation:
#Constraint(validatedBy = {})
#Target({ElementType.FIELD})
#Retention(RUNTIME)
#Pattern(regexp = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}")
public #interface validUuid {
String message() default "{invalid.uuid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
But for some reason it doesn't work, even if I pass valid UUID I constantly get an exception:
javax.validation.UnexpectedTypeException: HV000030: No validator
could be found for constraint 'javax.validation.constraints.Pattern'
validating type 'java.util.UUID'
Are there any options to validate UUID properly?
You cannot apply the #Pattern annotation to something (java.util.UUID) that is not a CharSequence. From the #Pattern annotation documentation (emphesizes mine):
Accepts CharSequence. null elements are considered valid.
Moreover, as far as I see you try to extend the behavior of the validation annotation handler by passing it to the new annotation definition.
If you wish to perform more complex validation, simply create your annotation without another validation annotations - their combining doesn't work like this. There must be something to recognize annotations and validate them.
#Target({ElementType.FIELD})
#Retention(RUNTIME)
#Constraint(validatedBy = UuidValidator.class)
public #interface ValidUuid {
String message() default "{invalid.uuid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Now, create a validator which implements ConstraintValidator<ValidUuid, UUID> and override the methods performing the validation itself.
public class UuidValidator implements ConstraintValidator<ValidUuid, UUID> {
private final String regex = "....." // the regex
#Override
public void initialize(ValidUuid validUuid) { }
#Override
public boolean isValid(UUID uuid, ConstraintValidatorContext cxt) {
return uuid.toString().matches(this.regex);
}
}
And apply the annotation:
#ValidUuid
private UUID uuId;
you can use UUID.fromString(...); and catch IllegalArgumentException
I have a Jersey Rest API like this:
#POST
#Path("/doorder")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("text/plain")
public String doOrder(#BeanParam final #Valid OrderBean order) {
// Some implementation here
}
All my inputs are store in this bean:
#AddressAtLeastOne
public final class OrderBean {
#FormDataParam("address")
private String address;
#FormDataParam("city")
private String city;
#FormDataParam("postcode")
private String postcode;
// Other member variables
// Getters and setters
}
I added an annotation to validate the address (#AddressAtLeastOne). The address is valid if at least one of the 3 fields has a value.
Here's the annotation definition:
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
#Retention(RUNTIME)
#Constraint(validatedBy = AddressAtLeastOneValidator.class)
#Documented
public #interface AddressAtLeastOne {
String message() default "Address requires at least one field";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And here's the validator:
public class AddressAtLeastOneValidator implements ConstraintValidator<AddressAllOrNone, OrderBean> {
#Override
public boolean isValid(OrderBean demoBean, ConstraintValidatorContext constraintValidatorContext) {
// Check for at least one value
if((demoBean.getAddress() != null && !demoBean.getAddress().equals("") ||
(demoBean.getCity() != null && !demoBean.getCity().equals("")) ||
(demoBean.getPostcode() != null && !demoBean.getPostcode().equals("")))) {
return true;
}
return false;
}
}
Everything is fine! But now I want to rename the annotation #AddressAtLeastOne to #AtLeastOne and make it generic, so that I can apply it to any class. I need a mechanism where I can specify which member variables are part of the group I want to validate with #AtLeastOne. How can I do that?
One approach of doing this is to use Reflection -
Create a custom annotation suppose #GroupNotNullField and apply this annotation to all fields in bean class in which at least one field should have value. By this way, you can skip some fields in which validation is not required.
In the validator class, get all the fields of the bean class using Reflection
Check all the fields which are annotated with #GroupNotNullField annotation
Get the value of all such fields and check that at least one has value.
Return true or false depending on validation check.
I need to do some additional business logic for specified filed (with a custom annotation) after hibernate load this entity. So, I created a hibernate interceptor like this. But what confused me is that I can't get the annotation information. The encryptAnnotation is always null in the following codes.
public class HibernateInterceptor extends EmptyInterceptor {
public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
for (int i = 0; i < types.length; i++) {
Type type = types[i];
if (type instanceof StringType) {
StringType stringType = (StringType) types[i];
Encrypt encryptAnnotation = stringType.getJavaTypeDescriptor().getJavaTypeClass().getAnnotation(Encrypt.class);
if (encryptAnnotation != null) {
//todo: decrypt field
return true;
}
}
}
return false;
}
}
Here is the Entity and annotation definition:
#Entity
#Table(name = "table_name")
public class Trade implements Serializable {
#Encrypt
private String shiptoAddr;
}
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Encrypt {
}
You are trying to obtain the annotation from the mapping information and basically in the end you are trying to find the annotation on the String class, that is obviously not going to work.
Instead you need to detected all fields on the passed in entity object and check if the annotation is present on a field.
I am facing problem with creating some metadata structure inside annotations. We are using annotations for define special attributes for hibernate entity attributes but it might be usable everywhere.
I want to create condition which represent these structure:
attribute1 = ...
OR
(attribute2 = ...
AND
attribute3 = ...)
Problem is that I need to define some "tree" structure using this annotations. Here is some design I want to reach:
#interface Attribute {
... some attributes ...
}
#interface LogicalExpression {
}
#interface OR extends LogicalExpression {
Attribute[] attributes() default {};
LogicalExpression logicalExpressions() default {};
}
#interface AND extends LogicalExpression {
Attribute[] attributes() default {};
LogicalExpression logicalExpressions() default {};
}
#interface ComposedCondition {
Attribute[] attributes() default {};
LogicalExpression logicalExpressions() default {};
}
All these annotation I want to use according to this example:
public class Table {
#ComposedCondition(logicalExressions = {
#OR(attributes = {#Attribute(... some settings ...)},
logicalExpressions = {
#AND(attributes = {#Attribute(...), #Attribute(...)})
})
}
private String value;
}
I know that inheritance in that way I defined in the annotation definition above is not possible. But how can I consider my annotations AND, OR to be in one "family"?
Please check Why it is not possible to extends annotations in java?
But you can create meta annotations that can be used on annotation to create groups of annotations.
#LogicalExpression
#interface OR {
Attribute[] attributes() default {};
LogicalExpression logicalExpressions() default {};
}
But this will not help you with your second problem ie. use LogicalExpression as a Parent.
But you can do something like below. Declare LogicExpression as enum This way you can use single enum and various set of Attributes to execute conditions.
e.g. If you want to execute AND, OR condition then you can pass LogicExpression.AND, LogicExpression.OR and use orAttributes() method to execute OR condition and andAttributes() to execute AND condition
public enum LogicExpression {
OR,AND,NOT;
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.ANNOTATION_TYPE)
public #interface ComposedCondition {
LogicExpression[] getExpressions() default {};
Attributes[] orAttributes() default {};
Attributes[] andAttributes() default {};..
}