I've enabled spring mvc annotation driven for my project. The idea is to use #Valid annotation with spring annotations to avoid the line in the controller like:
validator.validate(form, errors)
I've noticed that this stuff does not work with spring annotations from package:
org.springmodules.validation.bean.conf.loader.annotation.handle
After investigation I've found that I can use annotations from javax or org.hibernate.validator.constraints as alternative way.
But unfortunatly, I have some special cases when I cannot achieve this:
#MinSize(applyIf = "name NOT EQUALS 'default'", value = 1)
Will be good to know in which way spring annotation can be used with #Valid or any other alternative way to avoid refactoring related to applyIf attributes(Move conditions to java code).
Here is an example how to create a custom Validator.
At first create your own Annotation:
#Target({ ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = StringValidator.class)
public #interface ValidString {
String message() default "Invalid data";
int min() default -1;
int max() default -1;
String regex() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then you will need a custom validator:
public class StringValidator implements ConstraintValidator<ValidString, String> {
private int _min;
private int _max;
private String _regex;
private boolean _decode;
public void initialize(ValidString constraintAnnotation) {
_min = constraintAnnotation.min();
_max = constraintAnnotation.max();
_regex = constraintAnnotation.regex();
_decode = constraintAnnotation.decode();
}
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
String test = value.trim();
if (_min >= 0) {
if (test.length() < _min) {
return false;
}
}
if (_max > 0) {
if (test.length() > _max) {
return false;
}
}
if (_regex != null && !_regex.isEmpty()) {
if (!test.matches(_regex)) {
return false;
}
}
return true;
}
}
Finally you can use it in your Beans and Controller:
public class UserForm {
#ValidString(min=4, max=20, regex="^[a-z0-9]+")
private String name;
//...
}
// Method from Controller
#RequestMapping(method = RequestMethod.POST)
public String saveUser(#Valid UserForm form, BindingResult brResult) {
if (brResult.hasErrors()) {
//TODO:
}
return "somepage";
}
Something like this might help you out
public class UserValidator implements Validator {
#Override
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
User user = (User) target;
if(user.getName() == null) {
errors.rejectValue("name", "your_error_code");
}
// do "complex" validation here
}
}
Then in your controller you would have :
#RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, #ModelAttribute("user") User user, BindingResult result){
UserValidator userValidator = new UserValidator();
userValidator.validate(user, result);
if (result.hasErrors()){
// do something
}
else {
// do something else
}
}
If there are validation errors, result.hasErrors() will be true.
Note : You can also set the validator in a #InitBinder method of the controller, with "binder.setValidator(...)" . Or you could instantiate it in the default constructor of the controller. Or have a #Component/#Service UserValidator that you inject (#Autowired) in your controller : very useful, because most validators are singletons + unit test mocking becomes easier + your validator could call other Spring components.
Related
There is an annotation that should ideally throw an exception if the entity by id is not found for different controllers.
Annotation:
#Target({ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = CheckExistHandler.class)
public #interface CheckExist {
Class<?> entityClass();
String message() default "Entity with specified id does not exist!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
validator sketches:
#Component
public class CheckExistHandler implements ConstraintValidator<CheckExist, Long> {
#PersistenceContext
private EntityManager entityManager;
#Override
public boolean isValid(Long value, ConstraintValidatorContext context) {
if (value != 0 && entityManager.find(Topic.class, value) != null) {
return true;
} else {
return false;
}
}
}
Method for tests from one controller:
#GetMapping("/{topicId}")
public ResponseEntity<TopicDto> getTopicById(#CheckExist(entityClass = Topic.class) #PathVariable("topicId") Long topicId) {
if (!topicService.isExistByKey(topicId)) {
throw new NotFoundException("topic not found");
}
return new ResponseEntity<>(topicMapper.toDto(topicService.getByKey(topicId)), HttpStatus.OK);
}
In this regard, questions:
How to isolate a class from an annotation in a validator using reflection in order to correctly use the EntityManager?
How to throw an exception without getting 500?
I have created an annotation called #AllowAccessTo as follows,
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#PreAuthorize("hasAnyAuthority(#authorityService.getPrivilege(need to inject value form allowaccess annotation))")
public #interface AllowAccessTo {
String value() default "";
}
In my Rest Controller, I have annotated that custom annotation.
#RestController
#RequestMapping("/api")
public class FooEndpoint {
#GetMapping("/students")
#AllowAccessTo("GET_ALL_STUDENT")
public List<Student> getAllStudents() {
return students;
}
}
What I want to do is, I need to inject that "GET_ALL_STUDENT" value to
#authorityService.getPrivilege({{value from custom annotation}})
#PreAuthorize("hasAnyAuthority(#authorityService.getPrivilege(value form AllowAccessTo annotation))")
This is how I solve this.
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#PreAuthorize("#securityHandler.check")
public #interface AllowAccessTo {
String value() default "";
}
#Service("securityHandler")
#Slf4j
public class SecurityHandler {
#Autowired
private HttpServletRequest httpServletRequest;
public boolean check() {
try {
log.debug("checking permission based on jwt");
List < KseRoleDto > kseRoles = new ArrayList < > ();
String accessCode = checkAllowAccess();
// check permission with access code
if (hasPermission) {
return true;
} else {
return false;
}
} catch (Exception e) {
log.error("permission not matched and exception occurred", e);
return false;
}
}
public String checkAllowAccess() {
HandlerMethod attribute = (HandlerMethod) httpServletRequest.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
GrantEndpoint methodAnnotation = attribute.getMethodAnnotation(GrantEndpoint.class);
return methodAnnotation.value();
}
}
I have a Spring Boot Application where I need to perform some validation over the request's fields based on a header value.
So like any other Spring Boot App my entry point in my rest controller looks like
public ResponseEntity<Mono<MyResponse>> myOperation(MyRequest request, String another_parameter)
My problem here is, in order to perform my validations I was thinking on using org.springframework.validation.Validator
Whenever you want to implement above Interface, you have to do something like:
public class WebsiteUserValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyRequest.class.equals(clazz);
}
#Override
public void validate(Object obj, Errors errors) {
MyRequest user = (MyRequest) obj;
if (checkInputString(MyRequest.getSomeField())) {
errors.rejectValue("someField", "someField.empty");
}
}
private boolean checkInputString(String input) {
return (input == null || input.trim().length() == 0);
}
}
I would like to get the headers in the validate method implementations.
How to achieve that? (get the headers at any time so to speak).
I think use javax.validation.ConstraintValidator<A extends Annotation, T> will better.
for example
the Annotation
#Target({ElementType.TYPE, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy ={SexConstraintValidator.class} )
public #interface Sex {
//default error message
String message() default "default error message";
//groups
Class<?>[] groups() default {};
//payload
Class<? extends Payload>[] payload() default {};
}
SexConstraintValidator
public class SexConstraintValidator implements ConstraintValidator<Sex,String> {
#Override
public void initialize(Sex constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean isValid =doSomeValided();
return isValid;
}
}
validate object
public class ValidateObject {
#Sex(message="error message")
private String sex;
// ...
}
the validate method
import org.springframework.validation.annotation.Validated;
public ResponseEntity<Mono<MyResponse>> myOperation(#Validated ValidateObject request, String another_parameter)
or validate manual like this
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Set<ConstraintViolation<ValidateObject>> validate=validatorFactory.getValidator().validate(validateObject);
I have a flag DISABLE_FLAG and I want to use it to control multiple specific APIs in different controllers.
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
I can use an interceptor to handle all the urls. Is there a easy way to do that like annotation?
You could use AOP to do something like that.
Create your own annotation...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Maybe { }
and corresponding aspect...
#Aspect
public class MaybeAspect {
#Pointcut("#annotation(com.example.Maybe)")
public void callMeMaybe() {}
#Around("callMeMaybe()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// do your logic here..
if(DISABLE_FOO) {
// do nothing ? throw exception?
// return null;
throw new IllegalStateException();
} else {
// process the request normally
return joinPoint.proceed();
}
}
}
I don't think there is direct way to disable a constructed request mapping but We can disable API in many ways with some condition.
Here is the 2 ways disabling by spring profile or JVM properties.
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
// Using profile
if (env.acceptsProfiles("staging")) {
response.setStatus(404);
return "";
}
// Using JVM options
if("true".equals(System.getProperty("DISABLE_FOO"))) {
response.setStatus(404);
return "";
}
return "";
}
}
If you are thinking futuristic solution using cloud config is the best approach. https://spring.io/guides/gs/centralized-configuration/
Using Conditional components
This allows to build bean with conditions, if the condition failed on startup, the entire component will never be built. Group all your optional request mapping to new controller and add conditional annotation
#Conditional(ConditionalController.class)
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
return "";
}
public static class ConditionalController implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().acceptsProfiles("staging"); // Or whatever condition
}
}
}
You can solve this with annotations by utilizing spring profiles. You define two profiles one for enabled flag and another profile for the disabled flag. Your example would look like this:
#Profile("DISABLED_FLAG")
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#Profile("ENABLED_FLAG")
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
Here is the link to the spring framework documentation for this feature: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html
I did it as follows :
#Retention(RUNTIME)
#Target(ElementType.METHOD)
public #interface DisableApiControl {
}
This class is my customization statement. After could use AOP :
for AbstractBaseServiceImpl :
public abstract class AbstractBaseServiceImpl {
private static boolean disableCheck = false;
public void setDisableChecker(boolean checkParameter) {
disableCheck = checkParameter;
}
public boolean getDisableChecker() {
return disableCheck;
}
}
NOTE : The above class has been prepared to provide a dynamic structure.
#Aspect
#Component
public class DisableApiControlAspect extends AbstractBaseServiceImpl {
#Autowired
private HttpServletResponse httpServletResponse;
#Pointcut(" #annotation(disableMe)")
protected void disabledMethods(DisableApiControl disableMe) {
// comment line
}
#Around("disabledMethods(disableMe)")
public Object dontRun(ProceedingJoinPoint joinPoint, DisableApiControl disableMe) throws Throwable {
if (getDisableChecker()) {
httpServletResponse.sendError(HttpStatus.NOT_FOUND.value(), "Not found");
return null;
} else {
return joinPoint.proceed();
}
}
}
checker parameter added global at this point. The rest will be easier when the value is given as true / false when needed.
#GetMapping("/map")
#DisableApiControl
public List<?> stateMachineFindMap() {
return new ArrayList<>;
}
Custom annotation, as defined https://dzone.com/articles/bean-validation-and-jsr-303
Can we use #Capitalized annotation in controller?
e.g.
#RestController
public class Abc {
#RequestMapping(value="/abc", method=RequestMethod.POST)
public String abc(#Capitalized #RequestParam(value="abc") String abc) {
}
}
I used in this way, but it is not working. Any idea why it is not working?
Thanks,
Add Parameter ElementType in annotation target, then it will work.
E.g.:
#Target(ElementType.PARAMETER)
Assuming that #Capitalized is:
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = CapitalizedValidator.class)
#Documented
public #interface Capitalized {
String message() default "should be capital";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and you have a constraint validation impl as:
public class CapitalizedValidator implements ConstraintValidator<Capitalized, String> {
private String message;
#Override
public void initialize(Capitalized constraintAnnotation) {
message = constraintAnnotation.message();
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
String inUpperCase = value.toUpperCase();
if (inUpperCase.equals(value)) {
return true;
}
context.buildConstraintViolationWithTemplate(message);
return false;
}
}
Then try this with you're controller:
#Validated
#RestController
public class SampleController {
#RequestMapping(method = RequestMethod.POST)
public String post(#Capitalized #RequestParam("content") String content) {
return content;
}
}
After including below code in Application.java, Its working fine.
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}