I want to add an annotation which essentially verifies if any of the fields in the java class object is null. It should return true if any of the object values is null, otherwise false.. i.e
#NameBinding
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.CLASS)
public #interface IsNull {}
I should be able to use this annotation on any of the classes
Eg:
#IsNull
public class EmployeeRequest {
String name;
}
Usage:
EmployeeRequest empRequest = new EmployeeRequest();
if(empRequest.isNull())
fail request
Is this possible?
I want to add this as an annotation because , i want to use this isNull on different classes.
Is it possible to encapsulate an annotation within an other annotation with values?
I have a lot of end points on my API and would like to automate the required roles / permissions for the end point access.
Here is the current code :
#RestController
#RequestMapping("/health")
public class HealthController {
#GetMapping("/isAlive")
#PreAuthorize("hasAnyAuthority('ISALIVE', 'HEALTH_ENDPOINT')")
public String isAlive() {
return "Server is up and running";
}
#GetMapping("/hello")
#PreAuthorize("hasAnyAuthority('HELLO', 'HEALTH_ENDPOINT')")
public String hello() {
return "Hello";
}
}
I have 2 authorities per end point, the first is the name of the mapping and method and the second is the name of the class.
In this example there is only 2 different end points with makes it easy but the finished product will have in the hundreds and doing all of the authorities by hand is going to be error-prone and not very efficient.
This is the desired result :
#RestController
#RequestMapping("/health")
public class HealthController {
#GetMapping("/isAlive")
#MyAuthorisationAnnotation
public String isAlive() {
return "Server is up and running";
}
#GetMapping("/hello")
#MyAuthorisationAnnotation
public String hello() {
return "Hello";
}
}
Where #MyAuthorisationAnnotation would give the right parameters to the #PreAuthorize annotation.
Is it possible?
Is it possible to encapsulate an annotation within an other annotation
with values?
An annotation can have another annotation as a member.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAuthorizationAnnotation {
PreAuthorize value() default #PreAuthorize("hasAnyAuthority('HELLO', 'HEALTH_ENDPOINT')");
}
However I don't think this helps. I am not familiar with Spring, but I think this post solves your problem, because it seems that #PreAuthorize can be applied to annotations so you can leverage transitivity if you use the checked annotation in a method.
Solution's Post
I am trying to create a UniqueName annotation as a cutomize bean validation annotation for a create project api:
#PostMapping("/users/{userId}/projects")
public ResponseEntity createNewProject(#PathVariable("userId") String userId,
#RequestBody #Valid ProjectParam projectParam) {
User projectOwner = userRepository.ofId(userId).orElseThrow(ResourceNotFoundException::new);
Project project = new Project(
IdGenerator.nextId(),
userId,
projectParam.getName(),
projectParam.getDescription()
);
...
}
#Getter
#NoArgsConstructor(access = AccessLevel.PRIVATE)
class ProjectParam {
#NotBlank
#NameConstraint
private String name;
private String description;
}
#Constraint(validatedBy = UniqueProjectNameValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD })
public #interface UniqueName {
public String message() default "already existed";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default{};
}
public class UniqueProjectNameValidator implements ConstraintValidator<UniqueName, String> {
#Autowired
private ProjectQueryMapper mapper;
public void initialize(UniqueName constraint) {
}
public boolean isValid(String value, ConstraintValidatorContext context) {
// how can I get the userId info??
return mapper.findByName(userId, value) == null;
}
}
The problem is that name field just need uniqueness for user level. So I need to get the {userId} from the URL field for validation. But how can I add this into the UniqueProjectNameValidator? Or is there some better way to handle this validation? This is just a small part of a large object, the real object has many other complex validations in the request handler which make the code quite dirty.
As #Abhijeet mentioned, dynamically passing the userId property to the constraint validator is impossible. As to how to handle this validation case better, there's the clean solution and the dirty solution.
The clean solution is to extract all the business logic to a service method, and validate the ProjectParam at the service level. This way, you can add a userId property to ProjectParam, and map it from the #PathVariable onto the #RequestBody before calling the service. You then adjust UniqueProjectNameValidator to validate ProjectParams rather than Strings.
The dirty solution is to use Hibernate Validator's cross-parameter constraints (see also this link for an example). You essentially treat both of your controller method parameters as the input for your custom validator.
If I'm not wrong, what you are asking is, how can you pass your userId to your custom annotation i.e. #UniqueName so that you can access the userId to validate projectName field against existing projectNames for passed userId.
It means you are asking about is, How to pass variable/parameter dynamically to annotation which is not possible. You have to use some other approach like Interceptors or Do the validation manually.
You can refer to the following answers as well:
How to pass value to custom annotation in java?
Passing dynamic parameters to an annotation?
#Mikhail Dyakonov in this article proposed a rule of thumb to choose the best validation method using java:
JPA validation has limited functionality, but it is a great choice for the simplest constraints on entity classes if such
constraints can be mapped to DDL.
Bean Validation is a flexible, concise, declarative, reusable, and readable way to cover most of the checks that you could have in
your domain model classes. This is the best choice, in most cases,
once you don't need to run validations inside a transaction.
Validation by Contract is a Bean validation for method calls. You can use it when you need to check input and output parameters of a
method, for example, in a REST call handler.
Entity listeners although they are not as declarative as the Bean validation annotations, they are a great place to check big
object's graphs or make a check that needs to be done inside a
database transaction. For example, when you need to read some data
from the DB to make a decision, Hibernate has analogs of such
listeners.
Transaction listeners are a dangerous yet ultimate weapon that works inside the transactional context. Use it when you need to decide
at runtime what objects have to be validated or when you need to check
different types of your entities against the same validation
algorithm.
I think Entity listeners match your unique constraint validation issue, because within the Entity Listener you'll be able to access your JPA Entity before persisting/updating it and executing your check query easier.
However as #crizzis pointed me, there is a significant restriction with this approach. As stated in JPA 2 specification (JSR 317):
In general, the lifecycle method of a portable application should not
invoke EntityManager or Query operations, access other entity
instances, or modify relationships within the same persistence
context. A lifecycle callback method may modify the non-relationship
state of the entity on which it is invoked.
Whether you try this approach, first you'll need an ApplicationContextAware implementation for getting current EntityManager instance. It's an old Spring Framework trick, maybe You're already using it.
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public final class BeanUtil implements ApplicationContextAware {
private static ApplicationContext CONTEXT;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
CONTEXT = applicationContext;
}
public static <T> T getBean(Class<T> beanClass) {
return CONTEXT.getBean(beanClass);
}
}
This is my Entity Listener
#Slf4j
public class GatewaUniqueIpv4sListener {
#PrePersist
void onPrePersist(Gateway gateway) {
try {
EntityManager entityManager = BeanUtil.getBean(EntityManager.class);
Gateway entity = entityManager
.createQuery("SELECT g FROM Gateway g WHERE g.ipv4 = :ipv4", Gateway.class)
.setParameter("ipv4", gateway.getIpv4())
.getSingleResult();
// Already exists a Gateway with the same Ipv4 in the Database or the PersistenceContext
throw new IllegalArgumentException("Can't be to gateways with the same Ip address " + gateway.getIpv4());
} catch (NoResultException ex) {
log.debug(ex.getMessage(), ex);
}
}
}
Finally, I added this annotation to my Entity Class #EntityListeners(GatewaUniqueIpv4sListener.class)
You can find the complete working code here gateways-java
A clean and simple approach could be check validations in which you need to access the database within your transactional services. Even you could use the Specification, Strategy, and Chain of Responsibility patterns in order to implement a better solution.
I believe you can do what you're asking, but you might need to generalize your approach just a bit.
As others have mentioned, you can not pass two attributes into a validator, but, if you changed your validator to be class level validator instead of a field level validator, it can work.
Here is a validator we created that makes sure that two fields are the same value when submitted. Think of the password and confirm password use case that you often see websites, or email and confirm email use case.
Of course, in your particular case, you'll need to pass in the user's id and the name of the project that they are trying to create.
Annotation:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Taken from:
* http://stackoverflow.com/questions/1972933/cross-field-validation-with-hibernate-validator-jsr-303
* <p/>
* Validation annotation to validate that 2 fields have the same value.
* An array of fields and their matching confirmation fields can be supplied.
* <p/>
* Example, compare 1 pair of fields:
*
* #FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match")
* <p/>
* Example, compare more than 1 pair of fields:
* #FieldMatch.List({
* #FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
* #FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")})
*/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = FieldMatchValidator.class)
#Documented
public #interface FieldMatch {
String message() default "{constraints.fieldmatch}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* #return The first field
*/
String first();
/**
* #return The second field
*/
String second();
/**
* Defines several <code>#FieldMatch</code> annotations on the same element
*
* #see FieldMatch
*/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List {
FieldMatch[] value();
}
}
The Validator:
import org.apache.commons.beanutils.BeanUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* Taken from:
* http://stackoverflow.com/questions/1972933/cross-field-validation-with-hibernate-validator-jsr-303
*/
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
try {
Object firstObj = BeanUtils.getProperty(value, firstFieldName);
Object secondObj = BeanUtils.getProperty(value, secondFieldName);
return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
} catch (Exception ignore) {
// ignore
}
return true;
}
}
Then here our command object:
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import javax.validation.GroupSequence;
#GroupSequence({Required.class, Type.class, Data.class, Persistence.class, ChangePasswordCommand.class})
#FieldMatch(groups = Data.class, first = "password", second = "confirmNewPassword", message = "The New Password and Confirm New Password fields must match.")
public class ChangePasswordCommand {
#NotBlank(groups = Required.class, message = "New Password is required.")
#Length(groups = Data.class, min = 6, message = "New Password must be at least 6 characters in length.")
private String password;
#NotBlank(groups = Required.class, message = "Confirm New Password is required.")
private String confirmNewPassword;
...
}
I wrote this simple annotation:
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.METHOD})
public #interface CSVColumn {
String name();
int order();
}
which I'm currently using to annotate some getter methods of a class.
I'm able to get the values of this annotation at runtime:
Class classType = objects[0].getClass();
Method[] methodsArray = classType.getDeclaredMethods();
List<Method> methods = Arrays.asList(methodsArray);
//FIlter annotated methods
methods = methods.stream().filter(method -> method.getAnnotation(CSVColumn.class) != null).collect(Collectors.toList());
Iterator<Method> methodIterator = methods.iterator();
while (methodIterator.hasNext()) {
Method method = methodIterator.next();
CSVColumn csvColumn = method.getAnnotation(CSVColumn.class);
String header = csvColumn.name();
}
but how could I set the field name of the annotation to another value?
Should/could I declare some setter methods in the annotation class?
Use the utility class AnnotationUtil
You can do changeAnnotation(csvColumn, "name", "newName")
Is there a way by which I can set the id inside annotated method...
Annotation class:
import java.lang.annotation.*;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public
#interface MyAnnotation {
int id();
}
//Set id at runtime
public class A {
#MyAnnotation(id = ? )
public void method1() {
// I want to set the id here for my annotation...
}
}
Yes, but it's a bit unintuitive. You'll have to edit its bytecode using a tool like JavaAssist.
Here is an article describing what you're after.