In a Spring project (4.3.14.RELEASE) I need to validate Map<String, List<InnerObj>> map in the MVC layer.
For this purpose I wrote a custom validator
public class MapValidator implements ConstraintValidator<ValidMap, Map<String, List<InnerObj>>> {
#Override
public boolean isValid(final Map<String, List<InnerObj>> map,
final ConstraintValidatorContext context) {
if (map == null || map.size() == 0) {
return false;
}
// iterations over all objects and validation
return true;
}
and annotation
#Target({METHOD, FIELD, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = MapValidator.class)
public #interface ValidMap {
String message() default "valid.map";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
How to force the validation to propagate (something like #Valid) to the inner object (List and InnerObj) from my validator?
In principle, you can inject the Spring-configured Validator into your MapValidator and call its validate method on list elements. You'll need, however, to integrate the resulting Set<ConstraintViolation<ISoftBetFRBCoinDTO>> into the ConstraintValidatorContext argument somehow.
This approach seems to be an overkill.
Actually, Bean Validation does support validating collection elements via #Valid for method arguments:
public void method(Map<String, List<#Valid Address>> addressMap) {
This usage of #Valid is called method validation in the Bean Validation specification.
It's just in order to activate method validation in Spring, you have to annotate the containing class with Spring's #Validated annotation:
#RestController
#Validated
public class HelloWorldController {
#PostMapping("/hello")
public Map<String, List<Address>> helloWorld(#RequestBody Map<String, List<#Valid Address>> addressMap) {
return addressMap;
}
}
From Spring documentation:
To be eligible for Spring-driven method validation, all target classes need to be annotated with Spring’s #Validated annotation, which can optionally also declare the validation groups to use. See MethodValidationPostProcessor for setup details with the Hibernate Validator and Bean Validation 1.1 providers.
See also the linked javadoc of MethodValidationPostProcessor.
Related
For a SpringBoot project I'm using custom validation using custom annotations.
In particular, I have a lot of enums and I would like to validate if into the inputs there are correct enums values (e.g. from json to DTO objects)
I'm using this approach:
I defined my annotation:
#Target( { FIELD, PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = PlatformValidator.class)
public #interface PlatformValidation {
String message() default "Invalid platform format";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and my validator:
public class PlatformValidator implements ConstraintValidator<PlatformValidation, String>
{
public boolean isValid(String platform, ConstraintValidatorContext cxt) {
try {
if (!isNull(platform)) {
Platform.valueOf(platform);
}
return true;
} catch (Exception ex) {
return false;
}
}
}
My question is, can I have a generic enum validation annotation and specify the enum that I need as a parameter of my annotation?
Something like #MyValidation(enum=MyEnum, message= ...)
For the generics approach is use Java reflection. So the annotation should be:
#EnumValidation(enum=YourEnum.class, message=...)
The tutorial about reflection: Jenkov's Tutorial
I'm looking for something like JSR-303 Validation Groups (bean validation, when you mark method argument with #Validated(GroupName.class) in controller and specify group in request class fields where needed), but it should decide how to validate at runtime depending on one of request fields.
For example, if we have controller class like this
#Controller
public class MyController {
#Autowired
private MyService myService;
#ResponseBody
#RequestMapping(value = "/path", method = RequestMethod.PUT)
public ResponseVo storeDetail(/*maybe some annotation here*/ DetailRequestVo requestVo) {
return myService.storeDetail(requestVo);
}
class DetailRequestVo {
String type;
Long weight;
Long radius;
}
}
And we want validation depending on type field value: if type = "wheel" then radius and weight fields should be presented, if type = "engine" then only weight field should be presented.
Does Spring (as of 3.2.17) provide API to implement these rules in more declarative approach? org.springframework.validation.Validator looks like not an option here, because its method boolean supports(Class<?> clazz) decides based on class info, not instance info.
Thanks in advance
Not sure about Spring but you can do that with plain JSR-303 using a custom Validator for the class itself... like
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE })
#Constraint(validatedBy = TypeValidator.class)
#Documented
public #interface ValidType {
...
}
public class TypeValidator implements ConstraintValidator<ValidType , Object> {
public boolean isValid(final Object target, final ConstraintValidatorContext context) {
DetailRequestVo request = (DetailRequestVo) target;
// do your checks here
}
and used like
#ValidType
class DetailRequestVo {
String type;
Long weight;
Long radius;
}
Since the custom Validator has access to the whole DetailRequestVo-Object you can do your check of field A depending on field B etc.
I have a use case where I need to limit the values that can be passed as the query param.
#Path("/foo")
public interface Foo {
#GET
#Path("/details/id/{id}")
void getFooDetails(#PathParam("id") String id, #QueryParam("sort") String sortDirection);
}
public class FooImpl {
public void getFooDetails(String id, String sortDir) {
//Implementation
}
}
In the above example, I want to restrict the value of query param sort that can be passed via the API to ASC, DESC.
Is there any existing CXF annotation which I can use to restrict the values on a parameter? I haven't found any and so I tried the following solution.
My Approach:
#Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface ValueSet {
String[] allowedValues();
}
The modified interface looks like this.
#Path("/foo")
public interface Foo {
#GET
#PathParam("/details/id/{id}")
void getFooDetails(#PathParam("id") String id, #QueryParam("sort") #ValueSet(allowedValues = {"ASC", "DESC"}) String sortDirection);
}
I wrote a CXF Interceptor which intercepts the API invocation. I used reflection to get a handle on FooImpl.getFooDetails params. But the problem I faced is that the interceptor looks at FooImpl.getFooDetails method and doesn't find the annotations #QueryParam on the method params since #QueryParam is on the base method and the annotation is not inherited.
Interceptor implementation:
#Provider
public class ParamValidationInterceptor extends AbstractPhaseInterceptor<Message> {
public ParamValidationInterceptor() {
super(Phase.PRE_INVOKE);
super.addBefore(someInterceptor);
}
#Override
public void handleMessage(Message message) throws Fault {
UriInfo uriInfo = new UriInfoImpl(message);
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
Method methodToInvoke = (Method) message.get("org.apache.cxf.resource.method");
Parameter[] parameters = methodToInvoke.getParameters();
for (Parameter parameter : parameters) {
if (parameter.isAnnotationPresent(ValueSet.class)) {
ValueSet valueSet = parameter.getAnnotation(ValueSet.class);
QueryParam queryParam = parameter.getAnnotation(QueryParam.class);
Object invokedVal = queryParams.get(queryParam.value());
String[] allowedValues = valueSet.allowedValues();
if (!Arrays.asList(allowedValues).contains(invokedVal)) {
throw new CustomException();
}
}
}
}
}
Can anyone suggest a way forward? It would be great if anyone can suggest an alternative approach.
P.S: I am using CXF as an implementation for JAX-RS and spring is used as a container.
Update:
Like #Cássio Mazzochi Molin and #Andy McCright suggested, I will go with #Pattern annotation. But I am curious to know why the JAX-RS annotations are not inherited from the interface although the spec says they will be inherited.
Annotation inheritance
According to the section §3.6 Annotation Inheritance of the JAX-RS specification, it is recommended to always repeat annotations instead of relying on annotation inheritance.
Refer to this answer for the complete quote.
#QueryParam can be applied to different targets
Bear in mind that the #QueryParam annotation can be applied to:
Resource method parameters
Resource class fields
Resource class bean properties
Hence a manual validation can be tricky.
Use Bean Validation
For validation purposes, you should consider Bean Validation. Consider a #Pattern annotation with the allowed values:
#Pattern(regexp = "ASC|DESC")
And just annotate your resource method parameter:
#GET
#Path("foo")
public Response getFoos(#QueryParam("sort")
#Pattern(regexp = "ASC|DESC") String sortDirection) {
...
}
If you prefer case insensitive values, use:
#Pattern(regexp = "ASC|DESC", flags = Pattern.Flag.CASE_INSENSITIVE)
If the given value is invalid, a ConstraintViolationException will be thrown. To handle such exception and return a customized response, you can use an ExceptionMapper:
#Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
...
}
}
Perhaps it is just a typo, but CXF may not be recognizing the getFooDetails method (on the interface) because it is annotated with #PathParam instead of #Path.
Instead of using your ValueSet approach, I used BeanValidation, but the following code works for me.
Foo.java
#Path("/foo")
public interface Foo {
#GET
#Path("/details/id/{id}")
Response getFooDetails(
#PathParam("id") #Pattern(regexp="[0-9]*") String id,
#QueryParam("sort") #Pattern(regexp = "ASC|DESC") String sortDirection);
}
FooImpl.java
public class FooImpl implements Foo {
#Override
public Response getFooDetails(String id, String sortDirection) {
Integer idInt = Integer.parseInt(id);
if ("ASC".equals(sortDirection) || sortDirection == null) {
...
} else if ("DESC".equals(sortDirection)) {
...
}
return ...;
}
I've got this working on WebSphere Liberty 17.0.0.2 which is based on CXF 3.1.11.
Hope this helps,
Andy
I am trying to use the Meta-annotation of spring using the aliasFor annotation to create a custom annotation for the springs RequestParam
Simply 'extend/replace'
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface RequestParam {
#AliasFor("name")
String value() default "";
----
}
with my annotation
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface QueryParam {
#AliasFor(annotation = RequestParam.class, attribute = "name")
String name() default "";
#AliasFor(annotation = RequestParam.class, attribute = "required")
boolean required() default false;
#AliasFor(annotation = RequestParam.class, attribute = "defaultValue")
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
This way it throws the Exception
org.springframework.core.annotation.AnnotationConfigurationException: #AliasFor declaration on attribute [name] in annotation [package.QueryParam] declares an alias for attribute [name] in meta-annotation [org.springframework.web.bind.annotation.RequestParam] which is not meta-present.
Problem is that without the RequestParam annotated on the QueryParam this doesn't work. And it is not possible to put the RequestParam as it PARAMETER targeted.
#RequestParam <--This is not possible.
public #interface QueryParam
So is there another way to achieve this ?
Basically what you want to achieve is not possible now, at least for the Spring v 4.3.3 There are main two problems, the first one is the fact that annotations like #RequestParam are declared with #Target(ElementType.PARAMETER) which make it impossible to be used as part of meta annotations. Furthermore, Spring MVC looks up annotations on method parameters using org.springframework.core.MethodParameter.getParameterAnnotations() which does not support meta-annotations or composed annotations. But if you really need some customizations there you can use HandlerMethodArgumentResolver instead of meta annotations.
So you code will look something like
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface QueryParam {
String name() default "";
boolean required() default false;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
Then using the HandlerMethodArgumentResolver add the custom logic which you need.
public class QueryParamResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(QueryParam.class) != null;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
QueryParam attr = parameter.getParameterAnnotation(QueryParam.class);
// here you can use any logic which you need
return webRequest.getParameter(attr.value());
}
}
Then we need to register our HandlerMethodArgumentResolver
#Configuration
#EnableWebMvc
public class Config extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new QueryParamResolver());
}
}
And last lets use our custom annotation
#GetMapping("/test")
public String test(#QueryParam("foo") String foo){
// something here
}
i had same problem and I entered the wrong package for GetAmpping. I must import «import org.springframework.web.bind.annotation.GetMapping» .
I have a REST-Api where I want to validate the parameters on method Level. With #Valid and a custom object where validation annotations are inside, it works fine, but it fails, if I use a String and write the annotations directly into the method signiture.
My validator
#Documented
#Constraint(validatedBy = {})
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Pattern(regexp = "^[A-Za-z]{5}[0-9]{3,128}$")
public #interface CustomValidator{
public abstract String message() default "Custom Message"
public abstract Class<?>[] groups() default {};
public abstract Class<?>[] payload() default {};
}
My controller class
#RestController
public class MyController {
...
}
What works but is not so nice Code Style
#Data
public class FoobarForm{
#NotNull
#CustomValidator
String foobar;
}
#RequestMapping(value = "/foo/{bar}", method = RequestMethod.GET)
public Bank checkFoobar(#Valid #CustomValidator #PathVariable String foobar) {
What I want
#RequestMapping(value = "/foo/{bar}", method = RequestMethod.GET)
public Bank checkIban(#Validated #NotNull #CustomValidator #PathVariable String foobar) {
As you can see, I want to validate a String parameter on method Level with a custom Validator. But that does not work. If I put an invalid argument in the method, it passes and the validation is never performed.
Of course I can check the Regex in another way since it is a path variable, but I have the same scenario at other Methods and want to have a general Solution for this problem.
Info:
I currently use Spring Version: 4.0.7