Ali Raza
Ali Raza

Reputation: 71

How to pass entity object to controller from custom validation annotation

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

Answers (1)

Ali Raza
Ali Raza

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

Related Questions