Reputation: 71
I've a request data transfer object
(DTO) which is for accepting POST body of an API. That DTO has an ENUM key of an X
entity. I created a custom validation annotation and validate that ENUM exists in my system via its entity repository which returns entity or null based on its existence.
I want to pass that X
validated entity to my controller. I don't want to fetch that X
entity by ENUM again in my business logic because I've already done that in validation.
I save that entity in a static variable from annotation validator class and access it later when I want that in business logic but if API get hit concurrently, my static variable gets overridden by the second request and when my business logic of the first request fetches X
entity, it gets the entity of the second request due to override issue.
I want that X
entity object to be scoped inside its own request cycle and destroyed or GC after request completion like other objects. How can I achieve this?
My Custom Validator:
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidateXimpl.class)
@Documented
public @interface ValidateX {
String message() default "Message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default{};
}
Custom validator implementation:
public class ValidateXimpl implements ConstraintValidator<ValidateX, String> {
@Autowired
DBRepo dbRepo;
@Override
public void initialize(ValidateX annotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext ctx) {
try {
XEntity X = dbRepo.findByEnum(value); // I want this entity in my controller
return (!value.isEmpty() && X != null);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
My DTO where I put my validation:
public class DTO{
....
@ValidateX
String xEntityEnum
....
}
My controller:
@RequestMapping(value = '/x', method = RequestMethod.POST)
public @ResponseBody
Map<String, Object> createX(@Validated @RequestBody DTO dto, BindingResult errors) {
// I want that entity here which I get from db repo in my validator
}
Upvotes: 0
Views: 2028
Reputation: 71
I just solved this from RequestContextHolder
Solution Code:
Validator Implementation
public class ValidateXimpl implements ConstraintValidator<ValidateX, String> {
@Autowired
DBRepo dbRepo;
@Override
public void initialize(ValidateX annotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext ctx) {
try {
XEntity X = dbRepo.findByEnum(value); // I want this entity in my controller
RequestContextHolder.getRequestAttributes().setAttribute(
"XEntity", X, RequestAttributes.SCOPE_REQUEST
); // I saved it in RequestContextHolder
return (!value.isEmpty() && X != null);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
My Controller
@RequestMapping(value = '/x', method = RequestMethod.POST)
public @ResponseBody
Map<String, Object> createX(@Validated @RequestBody DTO dto, BindingResult errors) {
// I want that entity here which I get from db repo in my validator
System.out.println(
RequestContextHolder.getRequestAttributes().getAttribute(
"XEntity",
RequestAttributes.SCOPE_REQUEST
)
); // I get that from RequestContextHolder
}
This is what I actually need. RequestContextHolder
gets clear on request completion and also only available in its own request thread because of request scope setting in setAttribute
function.
Upvotes: 2