I'm working on a base repo in which I want to add a custom validation example in the rest layer, but it is not working, is not printing the flags at the moment of the validation, it just go right through the controller layer instead of the validator class.
What am I missing?
This is the rest layer:
#RequestMapping("equipment/")
public interface DeviceREST extends RestConstants {
#GetMapping("test")
#Produces(MediaType.APPLICATION_JSON)
BaseResult<UserDTO> test(
/* Example of a validation layer before moving forward device impl */
#QueryParam("genre") #Valid #AllowedGenderExampleValidations final String genre
) throws Exception;
}
AllowedGenderExampleValidations class
#Documented
#Retention(RUNTIME)
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Constraint(validatedBy = ExampleValidationsValidator.class)
public #interface AllowedGenderExampleValidations {
String message() default "Invalid value for genre query param";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class ExampleValidationsValidator implements ConstraintValidator<AllowedGenderExampleValidations, String> {
private List<String> allowedGenres = new ArrayList<>();
#Override
public void initialize(AllowedGenderExampleValidations constraint) {
System.out.println("##########################################################################");
List<GenderExampleEnum> allowedValues = new ArrayList<>(EnumSet.allOf(GenderExampleEnum.class));
for(GenderExampleEnum g : allowedValues) {
this.allowedGenres.add(g.name().toLowerCase());
}
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
System.out.println("##########################################################################");
return value != null && this.allowedGenres.contains(value) && this.allowedGenres.contains(value.toLowerCase());
}
}
GenderExampleEnum class
public enum GenderExampleEnum {
M("Man"),
W("Woman"),
O("Other");
private final String genre;
public String getGenre() { return genre; };
GenderExampleEnum(String genre) { this.genre = genre; }
public static GenderExampleEnum fromValue(String code) throws IllegalArgumentException {
for(var g : GenderExampleEnum.values()) {
if(code.toLowerCase().equalsIgnoreCase(g.name())) {
return g;
}
}
return GenderExampleEnum.O;
}
}
Controller class
#Controller
public class DeviceImpl implements DeviceREST {
private static final Logger logger = LoggerFactory.getLogger(DeviceImpl.class);
#Autowired private DeviceService deviceService;
#Autowired private DataTransferUtil dataTransferUtil;
#Override
public BaseResult<UserDTO> test(String genre) throws Exception {
var serviceResponse = deviceService.testFirstMethod();
var mappingResponse = dataTransferUtil.mapFirstTestMethod(serviceResponse);
return new BaseResult<UserDTO>(mappingResponse);
}
}
Test response, missing validations for query param
URL: localhost:8080/equipment/test?genre=o
I can't derive for sure from the context which Spring Boot version you are using, but for Spring boot 2.3.0 or later, a common mistake is relying on the spring-boot-starter-validation that is not included by default anymore.
You should make sure to add the following dependency if you want to use validation in a Spring Boot 2.3.0 or up project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
See spring docs here.
Could you please let me know if this solved your issue by replying or accepting this answer?
I should have added this annotation in the Rest Layer -> "#Validated" to make it work, without that dependency, validators won't be triggered
Related
I implemented a validation using the chain of responsibility pattern. The request payload to validate can have different parameters. The logic is: if the payload has some parameters, validate it and continue to validate other, else throw an exception. In a level of the validation chain I need to call other services, and here comes into play the Dependency Injection.
The validation structure is like a tree, starting from top to bottom.
So, the class where I need to start the Validation
#Service
public class ServiceImpl implements Service {
private final .....;
private final Validator validator;
public ServiceImpl(
#Qualifier("lastLevelValidator") Validator validator, .....) {
this.validator = validator;
this...........=............;
}
/...../
private void validateContext(RequestContex rc) {
Validator validation = new FirstLevelValidator(validator);
validation.validate(rc);
}
}
So the Validator Interface
public interface Validator<T> {
void validate(T object);
}
The validation classes that implements Validator
#Component
public class FirstLevelValidator implements Validator<RequestContext>{
private final Validator<RequestContext> validator;
#Autowired
public FirstLevelValidator(#Qualifier("lastLevelValidator") Validator<RequestContext> validator) {
this.validator = validator;
}
#Override
public void validate(RequestContext requestContext) {
if ( requestContext.getData() == null ) {
LOGGER.error(REQUEST_ERROR_MSG);
throw new BadRequestException(REQUEST_ERROR_MSG, INVALID_CODE);
}
if (requestContex.getData() == "Some Data") {
Validator validator = new SecondLevelValidator(this.validator);
validator.validate(requestContext);
} else {/* other */ }
}
#Component
public class SecondLevelValidator implements Validator<RequestContext>{
private final Validator<RequestContext> validator;
#Autowired
public SecondLevelValidator(#Qualifier("lastLevelValidator") Validator<RequestContext> validator) {
this.validator = validator;
}
#Override
public void validate(RequestContext requestContext) {
if ( requestContext.getOption() == null ) {
LOGGER.error(REQUEST_ERROR_MSG);
throw new BadRequestException(REQUEST_ERROR_MSG, INVALID_CODE);
}
if ( requestContext.getOption() == " SOME " ) {
validator.validate(requestContext); //HERE WHERE I CALL THE Qualifier
}
}
#Component
public class LastLevelValidator implements Validator<RequestContext>{
private final ClientService1 client1;
private final ClientService2 client2;
public LastLevelValidator(ClientService1 client1, ClientService2 client2) {
this.client1 = client1;
this.client2 = client2;
}
#Override
public void validate(RequestContext requestContext) {
Integer userId = client2.getId()
List<ClientService1Response> list = client1.call(requestContext.id(), userId);
boolean isIdListValid = list
.stream()
.map(clientService1Response -> clientService1Response.getId())
.collect(Collectors.toSet()).containsAll(requestContext.getListId());
if (!isIdListValid) {
LOGGER.error(NOT_FOUND);
throw new BadRequestException(NOT_FOUND, INVALID_CODE);
} else { LOGGER.info("Context List validated"); }
}
}
In the LastLevelValidator I need to call other services to make the validation, for that I inject into each validator class (First.., Second..) the #Qualifier("lastLevelValidator") object, so when I need to instantiate the LastLevelValidation class I can call it like validator.validate(requestContext); instance of validator.validate(ClientService1, ClientService2 ) that it would force me to propagate the ClientServices objects through all the chain from the ServiceImpl class.
Is it this a good solution ?
Is there any concern I didn't evaluate?
I tried also declaring the services I need to call for the validation as static in the LastLevelValidation, in the way that I can call it like LastLevelValidation.methodvalidar(), but look like not a good practice declares static objects.
I tried to pass the objects I need propagating it for each Validation class, but seems to me that if I need another object for the validation I have to pass it through all the validation chain.
I am trying to validate the postal code in my but the approach i am thinking of is not working out and I can't understand why.
I created a Validator, that hast to throw a ValidationException if it's not valid.
#Service
public class ZipCodeValidator{
public void validate(String zipCode){
validateNotEmpty(zipCode);
validateHasNoSpaces(zipCode);
}
private void validateNotEmpty(String zipCode){
if (zipCode.length() != 0){
throw new ValidationException("Postal Code can't be empty");
}
}
private void validateHasNoSpaces(String zipCode) {
if (zipCode.contains(" ")){
throw new ValidationException("Postal Code can't contain spaces");
}
}
}
In my service, where i receive the postal code I want to throw my custom exception (to which i pass the error message) like this:
try{
validator.validate(zipCode);
}catch (ValidationException ex){
throw new ZipCodeValidationException(ex.getMessage());
}
However it doesn't seem to work, that exception is not caught so the program runs further.
What am I doing wrong?
Is the whole approach wrong? What would you recommend?
Here's my custom Exception
public class ZipCodeValidationException extends Exception{
public ZipCodeValidationException(String message) {
super(message);
}
}
I recommend the following:
to process all exceptions in universal place as ExceptionHandler class, for more details see: https://www.baeldung.com/exception-handling-for-rest-with-spring
you can extend ValidationException from RuntimeException, that approach will allow unloading the code from try-catch constructions
#Service annotation is not quite right for converters or validators, as rule #Service class contains business logic, for helpers classes will be better use #Component, in total no differences between these two annotations only understanding which layer of application that component has
Please share the code for more suggestions and help.
Hi Please find my answer in 2 steps, first the correct and then the second the suggested way to implement.
Correction:
Please use ObjectUtils.isEmpty(arg) for checking if string is 0 length or null. Here is the modified version of your code
public interface ZipcodeService {
void validate(#Zipcode String zipCode) throws ZipCodeValidationException;
}
#Service
public static class ZipcodeServiceImpl implements ZipcodeService {
private final ZipCodeRegexMatcher zipCodeRegexMatcher;
public ZipcodeServiceImpl() {
zipCodeRegexMatcher = new ZipCodeRegexMatcher();
}
#Override
public void validate(String zipCode) throws ZipCodeValidationException {
// uncomment for Regex Validation
// boolean valid = zipCodeRegexMatcher.isValid(zipCode);
// uncomment for Simple text validation
final boolean valid = !ObjectUtils.isEmpty(zipCode);
if (!valid) {
throw new ZipCodeValidationException("Invalid zipcode");
}
}
}
This is how the caller looks like from Controller
#GetMapping(path = "dummy")
public String getDummy(#RequestParam("zipcode") String zipCode) {
try {
zipcodeService.validate(zipCode);
return zipCode;
} catch (ZipCodeValidationException e) {
return e.getMessage();
}
}
Suggested way:
add following entry to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Create Annotation and Validator as given below
#Constraint(validatedBy = {ZipcodeValidator.class})
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface Zipcode {
String message() default "Invalid Zipcode value";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class ZipcodeValidator implements ConstraintValidator<Zipcode, String> {
private final ZipCodeRegexMatcher zipCodeRegexMatcher;
public ZipcodeValidator() {
zipCodeRegexMatcher = new ZipCodeRegexMatcher();
}
#Override
public boolean isValid(String zipCode, ConstraintValidatorContext constraintValidatorContext) {
return zipCodeRegexMatcher.isValid(zipCode);
}
}
Once this setup is done, head over to Controller class and annotated class with
#Validated and field you want to have validation on with the Custom Annotation i.e Zipcode we have just created. We are creating a Custom Validator in this case ZipcodeValidator by extending ConstraintValidator.
This is how the caller looks like:
#GetMapping
public String get(#Zipcode #RequestParam("zipcode") String zipCode) {
return zipCode;
}
On Failed validation, it throws javax.validation.ConstraintViolationException: get.zipCode: Invalid Zipcode value which you can customize according to your need by using ControllerAdvice.
You can also use #Zipcode annotation at the service level and it works the same way. Regarding ZipCodeRegexMatcher instead of creating it inside the constructor you can create a bean and inject that dependency. It is a simple class that has regex for zipcode and performs validation.
public static class ZipCodeRegexMatcher {
public static final String ZIP_REGEX = "^[0-9]{5}(?:-[0-9]{4})?$";
private final Pattern pattern;
public ZipCodeRegexMatcher() {
pattern = Pattern.compile(ZIP_REGEX);
}
public boolean isValid(String zipCode) {
return pattern.matcher(zipCode).matches();
}
}
The entire code is located here
I am creating a GetEndpoint exposed like below
#GetMapping
public void someMethod(#RequestParam(value = "selectedColor", required = false,
defaultValue = "WHITE") Color seletedColor) {
....
}
I need to convert all method parameters to a class object like below. Please let me know how to set default value defaultValue = "WHITE" at field level in below class
#GetMapping
public void someMethod(RequestParameter request) {
....
}
public Class RequestParameter {
// How to set default value if parameter is null
private Color seletedColor;
}
I would suggest using a getter to have this logic. For example:
public class RequestParameter {
...
public Color getSelectedColor() {
return Optional.ofNullabe(selectedColor).orElse(Color.WHITE);
}
}
Once you decide to bind params to a custom request parameter class, you can no longer take advantage of the built-in parameter annotations that make it so easy to set defaults.
If you still want to set default values per-endpoint for one of your request class's field-level parameters, it's actually not too hard.
Make your own custom HandlerMethodArgumentResolver and pair it with a custom annotation that the resolver can check for.
public class MyRequestParameterMethodArgumentResolver implements HandlerMethodArgumentResolver {
public static final String DEFAULT_COLOR = "ffffff";
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(MyRequestParameter.class);
}
#Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
String colorParam = webRequest.getParameter("color");
// defaultColor may be overridden per-endpoint using this custom parameter annotation
DefaultColor defaultColorAnnotation = parameter.getParameterAnnotation(DefaultColor.class);
String defaultColor = defaultColorAnnotation != null ? defaultColorAnnotation.value() : DEFAULT_COLOR;
Color color = Optional.ofNullable(colorParam).map(Color::decode).orElse(Color.decode(defaultColor));
return new MyRequestParameter(color);
}
}
That resolver of course must be registered
public class MyApplication implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new MyRequestParameterMethodArgumentResolver());
}
...
and then just define your custom annotation
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface DefaultColor {
#AliasFor("value")
String color() default "";
#AliasFor("color")
String value() default "";
}
and finally, you will have the freedom to give each endpoint its own default color
#GetMapping
public void someMethod( #DefaultColor("ff0000") MyRequestParameter requestParams) {
....
}
#GetMapping
public void someOtherMethod( #DefaultColor("00ff00") MyRequestParameter requestParams) {
....
}
I'm trying to use the Spring Validator and #Validated annotation to validate a Get Request parameter but cannot get the validator to run. I'm using a ModelAttribute to try and get the validator to run on the Path Variable instead of the Request Body. Is it possible to run a validator on a Get Request Path Variable?
Here is my controller class and method
#RestController
public class ProfileController {
#RequestMapping(value = "/profile/{param}", method = RequestMethod.GET)
public IVRProfile getProfile(#Validated(ParamValidator.class) #ModelAttribute("param") String param) {
return sampleProfile();
}
#ModelAttribute("param")
public String paramAsModelAttribute(#PathVariable String param) {
return param;
}
}
And the Validator class
#Component
public class ParamValidator implements Validator
{
#Override
public boolean supports(Class<?> clazz)
{
System.out.println("Validator supports test");
return String.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors)
{
System.out.println("Validator Test");
// Validation code
}
}
Neither prints statements are executed when hitting the endpoint.
Any help on what I could be missing or do differently would be greatly appreciated, thanks!
You can implement desired validation functionality as following.
public class ParamValidator implements ConstraintValidator<ParamConstraint, String> {
#Override
public void initialize(ParamConstraint paramConstraint) {
}
#Override
public boolean isValid(String paramField, ConstraintValidatorContext cxt) {
//Perform paramField validation
return true;
}
}
-
#Documented
#Constraint(validatedBy = ParamValidator.class)
#Target( { ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface ParamConstraint {
String message() default "Default validation message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
-
#RequestMapping(value = "/profile/{param}", method = RequestMethod.GET)
public IVRProfile getProfile(#Valid #ParamConstraint #ModelAttribute("param") String param) {
return sampleProfile();
}
And finally don't forget to annotate Controller with #Validated.
#RestController
#Validated
public class ProfileController {
//...
}
More details you can find in the example as mentioned here.
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
You need to added an #Validated annotation to Controller class and any validation annotation before path variable field
If you want to get single RequestParams like status, you can force it by following the code below.
#RestController
public class ProfileController {
#RequestMapping(value = "/profile/{param}", method = RequestMethod.GET)
public IVRProfile getProfile(#RequestParam(name = "status", required = true) String status, #ModelAttribute("param") String param) {}
}
if you want to force PathVariable, then do this.
#RestController
public class ProfileController {
#RequestMapping(value = "/profile/{param}", method = RequestMethod.GET)
public IVRProfile getProfile(#PathVariable(name = "param", required = true) String param, #ModelAttribute("param") String param) {}
}
Hope this work!!!
I am trying to implement pagination to my Spring Data JPA repository in Spring Boot but I am stuck with the following exception when running uni tests:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.Pageable]: Specified class is an interface
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
...
Could someone point out to me what am I missing here? This is my repository:
#Repository
public interface VenueRepository extends PagingAndSortingRepository<Venue, Long> {
public Page<Venue> findAll(Pageable pageable);
}
and controller:
#RestController
#RequestMapping("/venues")
public class VenueController {
#Autowired
private VenueRepository venueRepo;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Page<Venue>> getVenues(Pageable pageable) {
return new ResponseEntity<>(venueRepo.findAll(pageable), HttpStatus.OK);
}
}
and finally my test:
#Test
public void responseOkVenuesTest() throws Exception {
mvc.perform(get("/venues").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk());
}
I spent couple of hours trying to make this work and am running out of ideas. Thank you for any tips!
Change your method getVenues in the way that you can pass the parameters to instantiate a PageRequest instead of passing Pageable :
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<Venue>> getVenues(int from,int to) {
return new ResponseEntity<>(
venueRepo.findAll((new PageRequest(from, to)), HttpStatus.OK).getContent();
}
In addition to #SEY_91's answer you might also like to use the following solution inspired with How to remove redundant Spring MVC method by providing POST-only #Valid? and used in my Spring Boot-driven application for long time.
In short, here is an annotation to annotate controller method parameters:
#Target(PARAMETER)
#Retention(RUNTIME)
public #interface PlainModelAttribute {
}
Now, just a method processor that would scan for parameters annotated with #PlainModelAttribute:
public final class PlainModelAttributeMethodProcessor
extends ModelAttributeMethodProcessor {
private final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index;
private PlainModelAttributeMethodProcessor(final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index) {
super(true);
this.index = index;
}
public static HandlerMethodArgumentResolver plainModelAttributeMethodProcessor(final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index) {
return new PlainModelAttributeMethodProcessor(index);
}
#Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.hasParameterAnnotation(PlainModelAttribute.class) || super.supportsParameter(parameter);
}
#Override
protected Object createAttribute(final String attributeName, final MethodParameter parameter, final WebDataBinderFactory binderFactory,
final NativeWebRequest request) {
final TypeToken<?> typeToken = TypeToken.of(parameter.getGenericParameterType());
final Converter<? super NativeWebRequest, ?> converter = index.get(typeToken);
if ( converter == null ) {
throw new IllegalArgumentException("Cannot find a converter for " + typeToken.getType());
}
return converter.convert(request);
}
#Override
protected void bindRequestParameters(final WebDataBinder binder, final NativeWebRequest request) {
final HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if ( !isSafe(resolve(servletRequest.getMethod())) ) {
((ServletRequestDataBinder) binder).bind(servletRequest);
}
}
private static HttpMethod resolve(final String name) {
return HttpMethod.valueOf(name.toUpperCase());
}
private static boolean isSafe(final HttpMethod method)
throws UnsupportedOperationException {
switch ( method ) {
case GET:
case HEAD:
case OPTIONS:
return true;
case POST:
case PUT:
case PATCH:
case DELETE:
return false;
case TRACE:
throw new UnsupportedOperationException();
default:
throw new AssertionError(method);
}
}
}
I don't really remember, but a resolve() method equivalent should be present in Spring Framework somewhere. Note that I use Google Guava TypeToken in order to let the processor be compatible with generic types (since I use models like IQuery<Foo> and IQuery<Bar> in controllers). Now just register the processor:
#Configuration
#EnableWebMvc
public class MvcConfiguration
extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(createModelAttributeMethodProcessor());
}
private static HandlerMethodArgumentResolver createModelAttributeMethodProcessor() {
return plainModelAttributeMethodProcessor(ImmutableMap.of(pageableTypeToken, MvcConfiguration::toPageable));
}
private static final TypeToken<Pageable> pageableTypeToken = new TypeToken<Pageable>() {
};
private static Pageable toPageable(final WebRequest request) {
return new PageRequest(
ofNullable(request.getParameter("page")).map(Integer::parseInt).orElse(0),
ofNullable(request.getParameter("size")).map(Integer::parseInt).orElse(1)
);
}
}
Here is a web request to a Pageable DTO conversion, and the converter must be registered as an argument resolver. So now it's ready to use:
#RestController
#RequestMapping("/")
public class Controller {
#RequestMapping(method = GET)
public String get(#PlainModelAttribute final Pageable pageable) {
return toStringHelper(pageable)
.add("offset", pageable.getOffset())
.add("pageNumber", pageable.getPageNumber())
.add("pageSize", pageable.getPageSize())
.add("sort", pageable.getSort())
.toString();
}
}
A few examples:
/ ⇒ PageRequest{offset=0, pageNumber=0, pageSize=1, sort=null}
/?page=43 ⇒ PageRequest{offset=43, pageNumber=43, pageSize=1, sort=null}
/?size=32 ⇒ PageRequest{offset=0, pageNumber=0, pageSize=32, sort=null}
/?page=22&size=32 ⇒ PageRequest{offset=704, pageNumber=22, pageSize=32, sort=null}