Spring Boot 2 and method parameter validation - java

I created the following service interface:
import javax.validation.constraints.NotBlank;
import org.springframework.lang.NonNull;
import org.springframework.validation.annotation.Validated;
#Validated
public interface UserService {
User create(#NonNull Long telegramId, #NotBlank String name, #NonNull Boolean isBot);
}
but the following invocation:
userService.create(telegramId, "Mike", null);
passes the #NotNull validation for isBot parameter. How to correctly configure Spring Boot and my service in order to take into account #NonNull annotation and prevent method execution in case of null parameter?

I played around with this problem for a bit.
Your code looks fine to me: Make sure that the implementation of UserService also has the validation annotations present.
Ensure that you allow Spring to create the Bean; it should work as you expect.
Example
Service Definition
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Validated
public interface GreetingService {
String greet(#NotNull #NotBlank String greeting);
}
Service Implementation
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Service
public class HelloGreetingService implements GreetingService {
public String greet(#NotNull #NotBlank String greeting) {
return "hello " + greeting;
}
}
Testcase
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import javax.validation.ConstraintViolationException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
#SpringBootTest
class HelloGreetingServiceTest {
#Autowired
private GreetingService helloGreetingService;
#Test
void whenGreetWithStringInput_shouldDisplayGreeting() {
String input = "john doe";
assertEquals("hello john doe", helloGreetingService.greet(input));
}
#Test
void whenGreetWithNullInput_shouldThrowException() {
assertThrows(ConstraintViolationException.class, () -> helloGreetingService.greet(null));
}
#Test
void whenGreetWithBlankInput_shouldThrowException() {
assertThrows(ConstraintViolationException.class, () -> helloGreetingService.greet(""));
}
}
Testcases are green for me.
Github: https://github.com/almac777/spring-validation-playground
Source: https://www.baeldung.com/javax-validation-method-constraints
HTH!

Use the same thing in Implementation class instead interface.
Also can write one global exception like:
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
public class GlobalRestException extends ResponseEntityExceptionHandler {
...
...
/**
* Handle MethodArgumentNotValidException. Triggered when an object fails #Valid
* validation.
*
* #param ex the MethodArgumentNotValidException that is thrown when #Valid
* validation fails
* #param headers HttpHeaders
* #param status HttpStatus
* #param request WebRequest
* #return the ApiException object
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Error apiError = new Error(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getBindingResult().getFieldErrors());
apiError.addValidationError(ex.getBindingResult().getGlobalErrors());
return buildResponseEntity(apiError);
}
}
There are more method that can be override to handle different kind of exception like :
/**
* Handles javax.validation.ConstraintViolationException. Thrown when #Validated
* fails.
*
* #param ex the ConstraintViolationException
* #return the ApiException object
*/
#ExceptionHandler(javax.validation.ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolation(javax.validation.ConstraintViolationException ex) {
Error apiError = new Error(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getConstraintViolations());
return buildResponseEntity(apiError);
}

You need to make sure that #Validated annotation is used on 'class' which method arguments will need to be validated and Spring configuration need to be added
#Configuration
public class MethodValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}

Related

An Authentication object was not found in the SecurityContext when #Scheduled triggered?

I created a Spring Boot app and have trouble with some endpoints that can be triggered manually or via #Scheduled annotation.
The issue I get is the one below:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException:
An Authentication object was not found in the SecurityContext
Is there a way to trigger SecurityContext if the process called via #Scheduled?
I am new to Spring Security and it is very hard for me to understand the reference guide. I found some similar questions but still cannot understand how to apply answers to my case.
Example of my MyController:
#Secured("ROLE_ADMIN")
#RestController
#RequestMapping(value = "/api")
public class MyController {
#Scheduled(cron = "* * * * * *")
#GetMapping(path="/data")
public void getData() {
// Do some operations
}
}
The #Scheduled method should not be #Secured. Scheduled methods are already in trusted code.
Refactor your code, e.g.
#Secured("ROLE_ADMIN")
#RestController
#RequestMapping(value = "/api")
public class MyController {
#Autowired
private MyService myService;
#PostMapping(path="/run")
public void runJobs() {
myService.runJobs();
}
}
#Service
public class MyService {
#Scheduled(cron = "* * * * * *")
public void runJobs() {
// Do some operations
}
}
Basically you have to configure your scheduler with the necessary authentication. Something in the lines of the following:
import com.google.common.collect.ImmutableList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import java.util.List;
import java.util.concurrent.Executor;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.springframework.security.core.context.SecurityContextHolder.getContext;
#Configuration
public class SchedulerConfiguration implements SchedulingConfigurer {
private static final String KEY = "spring";
private static final String PRINCIPAL = "spring";
private static final List<SimpleGrantedAuthority> AUTHORITIES = ImmutableList.of(new SimpleGrantedAuthority("ADMIN"));
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
#Bean
public Executor taskExecutor() {
final AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(KEY, PRINCIPAL, AUTHORITIES);
final SecurityContext securityContext = getContext();
securityContext.setAuthentication(token);
return new DelegatingSecurityContextScheduledExecutorService(newSingleThreadScheduledExecutor(), securityContext);
}
}
Then you can annotate your controller with #Secured("hasAuthority('ADMIN')").

MethodValidationInterceptor and #Validated #ModelAttribute

I have a Spring Boot 2 app and I want to be able to validate controller arguments with Hibernate validator - which I'm using successfully. I have all my controller annotated as #Validated and I'm using the validation for request parameters like so #PathVariable #AssertUuid final String customerId - so far so good, everything works.
But, I want to also be able to validate #ModelAttribute from forms.
#Controller
#PreAuthorize("hasRole('ADMIN')")
#RequestMapping(path = "/customers")
#Validated
public class CustomerController
{
private final CustomerFacade customerFacade;
public CustomerController(
final CustomerFacade customerFacade
)
{
this.customerFacade = customerFacade;
}
#GetMapping("/create")
public ModelAndView create(
final AccessToken accessToken
)
{
return new ModelAndView("customer/create")
.addObject("customer", new CreateCustomerRequest());
}
#PostMapping("/create")
public ModelAndView handleCreate(
final AccessToken accessToken,
#Validated #ModelAttribute("customer") final CreateCustomerRequest customerValues,
final BindingResult validation
) throws
UserDoesNotHaveAdminAccessException
{
if (validation.hasErrors()) {
return new ModelAndView("customer/create")
.addObject("customer", customerValues);
}
CustomerResult newCustomer = customerFacade.createCustomer(
accessToken,
customerValues.getName()
);
return new ModelAndView(new RedirectView("..."));
}
public static final class CreateCustomerRequest
{
#NotNull
#NotBlank
private String name;
public CreateCustomerRequest(final String name)
{
this.name = name;
}
public CreateCustomerRequest()
{
}
public String getName()
{
return name;
}
}
}
But this causes the MethodValidationInterceptor to throw ConstraintViolationException when I send invalid data. This would normally make sense and I want this behaviour in every other case, but in this case, as you can see, I want to use the BindingResult to handle the validation errors - which is neccesary when working with forms.
Is there a way I could tell Spring to not validate this particular parameter with MethodValidationInterceptor, because it's already validated by the binder and I want to handle it differently?
I've been digging around in the spring code and it looks like is not designed to work together. I have the some ideas how to fix this:
remove the #Validated from the argument and
call validator.validate() explicitly in the controller method - ugly and dangerous (you might forget to call it)
create another AOP interceptor, that would find "pairs" of #ModelAttribute and BindingResult and call the validator there, forcing the validation globally
Am I going about this completely wrong? Am I missing something? Is there a better way?
I've come up with a solution that allows me to continue working, but I don't consider this problem solved.
As I've hinted in the original question, this Aspect forces validation of the #ModelAttribute when it isn't annotated with #Validated or #Valid.
This means that the ConstraintViolationException is not thrown for invalid #ModelAttribute and you can handle the errors in method body.
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.MethodParameter;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
#SuppressWarnings({"checkstyle:IllegalThrows"})
#Aspect
public class ControllerModelAttributeAutoValidatingAspect
{
private final Validator validator;
public ControllerModelAttributeAutoValidatingAspect(
final Validator validator
)
{
this.validator = validator;
}
#Around("execution(public * ((#org.springframework.web.bind.annotation.RequestMapping *)+).*(..)))")
public Object proceed(final ProceedingJoinPoint pjp) throws Throwable
{
MethodSignature methodSignature = MethodSignature.class.cast(pjp.getSignature());
List<MethodParameter> methodParameters = getMethodParameters(methodSignature);
PeekingIterator<MethodParameter> parametersIterator = Iterators.peekingIterator(methodParameters.iterator());
while (parametersIterator.hasNext()) {
MethodParameter parameter = parametersIterator.next();
if (!parameter.hasParameterAnnotation(ModelAttribute.class)) {
// process only ModelAttribute arguments
continue;
}
if (parameter.hasParameterAnnotation(Validated.class) || parameter.hasParameterAnnotation(Valid.class)) {
// if the argument is annotated as validated, the binder already validated it
continue;
}
MethodParameter nextParameter = parametersIterator.peek();
if (!Errors.class.isAssignableFrom(nextParameter.getParameterType())) {
// the Errors argument has to be right after the ModelAttribute argument to form a pair
continue;
}
Object target = pjp.getArgs()[methodParameters.indexOf(parameter)];
Errors errors = Errors.class.cast(pjp.getArgs()[methodParameters.indexOf(nextParameter)]);
validator.validate(target, errors);
}
return pjp.proceed();
}
private List<MethodParameter> getMethodParameters(final MethodSignature methodSignature)
{
return IntStream.range(0, methodSignature.getParameterNames().length)
.mapToObj(i -> new MethodParameter(methodSignature.getMethod(), i))
.collect(Collectors.toList());
}
}
Now you can just keep using validation annotations in your controller methods as you're used to, and at the same time, the final BindingResult validation works as expected.
#PostMapping("/create")
public ModelAndView handleCreate(
final AccessToken accessToken,
#ModelAttribute("customer") final CreateCustomerRequest customerValues,
final BindingResult validation
)
Thank you for sharing this solution.
I used it as inspiration and as a base for creating a more general method arguments validator that I intend to use on selected methods.
Validation is triggered for methods annotated by #Validate:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Validate {
}
Example:
#Validate
public void testMe(BindingModel bindingModel, Errors errors) {
if (!errors.hasErrors()) {
// bindingModel is valid
}
}
And here's the modified aspect class:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
#Aspect
#Component
public class ValidateAspect {
private final Validator validator;
public ValidateAspect(Validator validator) {
this.validator = validator;
}
#Around("#annotation(Validate)")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
List<MethodParameter> methodParameters = getMethodParameters(methodSignature);
for (int i = 0; i < methodParameters.size() - 1; i++) {
MethodParameter parameter = methodParameters.get(i);
MethodParameter nextParameter = methodParameters.get(i + 1);
if (!Errors.class.isAssignableFrom(nextParameter.getParameterType())) {
// the Errors argument has to be right after the validated argument to form a pair
continue;
}
Object target = pjp.getArgs()[methodParameters.indexOf(parameter)];
Errors errors = (Errors) pjp.getArgs()[methodParameters.indexOf(nextParameter)];
validator.validate(target, errors);
}
return pjp.proceed();
}
private static List<MethodParameter> getMethodParameters(MethodSignature methodSignature) {
return IntStream
.range(0, methodSignature.getParameterNames().length)
.mapToObj(i -> new MethodParameter(methodSignature.getMethod(), i))
.collect(Collectors.toList());
}
}
The above code is tested and (so far) seems to work properly with Spring Boot 2.1.4.RELEASE

Test the Spring Boot Rest API Post Method

I completely new to Junit and I have to write Junit Test case for my Rest Controller but I am not getting from where should I start. Any help would be really appreciated.
This is My Rest Controller class.
#RestController
public class RecognitionController {
private FrameDecoder frameDecoder;
private TagEncoder tagEncoder;
private RecognitionService recognitionService;
#Autowired
public RecognitionController(FrameDecoder frameDecoder, TagEncoder tagEncoder,
RecognitionService recognitionService) {
this.frameDecoder = frameDecoder;
this.tagEncoder = tagEncoder;
this.recognitionService = recognitionService;
}
/**
*
* #param take the input as Json Frame and map the output at api/detection Url.
* #return List of Json tag in the Http response.
*/
#RequestMapping(value = "/api/detection", method = RequestMethod.POST)
public List<JsonTag> analyseframe(#RequestBody JsonFrame frame) {
SimpleFrame simpleFrame = frameDecoder.decodeFrame(frame);
List<OrientedTag> orientedTags = recognitionService.analyseFrame(simpleFrame);
return tagEncoder.encodeTag(orientedTags);
}
}
For testing Rest Controller you need:
JUnit
Mockito
Spring Test
JsonPath
Controller:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
#RestController
#RequestMapping(value = "/Entity")
public class EntityRestController {
private EntityService service;
#RequestMapping(value = "/entity/all", method = RequestMethod.GET)
public List<Entity> findAll() {
List<Entity> models = service.findAll();
return createEntities(models);
}
private List<EntityDTO> createDTOs(List<Entity> models) {
List<EntityDTO> dtos = new ArrayList<>();
for (Entitymodel: models) {
dtos.add(createDTO(model));
}
return dtos;
}
private EntityDTO createDTO(Entity model) {
EntityDTO dto = new EntityDTO();
dto.setId(model.getId());
dto.setDescription(model.getDescription());
dto.setTitle(model.getTitle());
return dto;
}
}
Test example:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
#WebAppConfiguration
public class EntityRestControllerTest {
private MockMvc mockMvc;
#Autowired
private EntityService entityServiceMock;
//Add WebApplicationContext field here.
//The setUp() method is omitted.
#Test
public void findAllEntitiesTest() throws Exception {
Entity first = new Entity();
first.setId(1L);
first.setDescription("Lorem ipsum")
first.setTitle("Foo");
Entity second = new Entity();
second.setId(2L);
second.setDescription("Lorem ipsum")
second.setTitle("Bar");
when(entityServiceMock.findAll()).thenReturn(Arrays.asList(first, second));
mockMvc.perform(get("/entity/all"))
.andExpect(status().isOk())
.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].id", is(1)))
.andExpect(jsonPath("$[0].description", is("Lorem ipsum")))
.andExpect(jsonPath("$[0].title", is("Foo")))
.andExpect(jsonPath("$[1].id", is(2)))
.andExpect(jsonPath("$[1].description", is("Lorem ipsum")))
.andExpect(jsonPath("$[1].title", is("Bar")));
verify(entityServiceMock, times(1)).findAll();
verifyNoMoreInteractions(entityServiceMock);
}
}
Please follow the full tutorial for more details.
___EDIT_1___
I didn't understand from where "thenReturn" Method came
static method Mockito.when() has the following signature:
public static <T> OngoingStubbing<T> when(T methodCall)
When you mocking some service and putting it inside when as parameter - it returns object which IS OngoingStubbing<T>. All classes which implement OngoingStubbing<T> have thenReturn(T value) method and it's called.

Dynamic binding of a filter in Jersey doesn't work for sub-resources

I created a Jersey filter and I need it assigned to some resources (not all). Therefore, I'm using dynamic binding to achieve that.
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext featureContext) {
Path resourcePath = resourceInfo.getResourceClass().getAnnotation(Path.class);
if (resourcePath != null && resourcePath.value().contains("/v2/"))
{
featureContext.register(MyFilter.class);
}
}
}
So I want this filter to be applied to all methods in those resources that contain a certain string in their paths. Some of those resources use sub-resource locators to define sub-resources. E.g.,
#Path("/v2/resource_path")
#Consumes({ ... })
#Produces({ ... })
class MyResource
{
#Path("/subresource_path")
public MySubResource getSubResource(#Context ResourceContext rc)
{
return rc.getResource(MySubResource.class);
}
}
Even though Jersey documentation claims
The configure method will be executed once for each resource method that is defined in the application.
the configure method in MyDynamicFeature shown above doesn't get called for getSubResource method of MyResource class at all. It does get called for all the rest of the methods in MyResource class though (which I omitted in the example).
Is there a way to make this work for sub-resources? I need my filter to be applied to MySubResource as well.
We use Jersey 2.21.
Check out this issue. I'm not sure that it is currently possible. If you add some logging in your feature to log the method and class, you will see that subresource methods are never traversed. As explained by Marek in the issue, it's because in order to handle this, the sub-resource locator method would need to be invoked, which it never is.
The only workaround is to use Name Binding instead. I've tested this and it works (see below). The idea is to make a custom annotation, and annotate the filter, the resource class, and sub-resource class you want filtered. For example
#NameBinding
#Target({METHOD, TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface SomeAnno {}
#SomeAnno
public class Filter implements ContainerRequestFilter {}
#SomeAnno
#Path("v2")
public class V2Resource {
#Path("sub")
public V2SubResource get() {
return new V2SubResource();
}
#SomeAnno
public static class V2SubResource {
#GET
public String get() { return "get"; }
}
}
The above would bind all the resource methods in V2Resource as well as V2SubResource.
Below is a complete example using Jersey Test Framework. Run it like any other JUnit test
UPDATE: Note that with the below tests, with the current (2.26) version of Jersey, the tests for the version 2 resource hangs because of the 1000 status code being returned in the filter. I guess Jersey doesn't like this. To fix this just change the status code in the filter to 500 and fix the test assertions accordingly to test for a 500 status code.
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.NameBinding;
import javax.ws.rs.Path;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Stack Overflow question http://stackoverflow.com/q/36878817/2587435
*
* Run this like any other JUnit test. Only one required test dependency
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-inmemory</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*
* #author Paul Samsotha
*/
public class DynamicSubresourceTest extends JerseyTest {
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public static #interface Status1000 {}
#Provider
#Status1000
public static class Status1000Filter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext context) throws IOException {
context.abortWith(Response.status(500).build());
}
}
#Path("v1")
public static class V1Resource {
#GET
public String get() {
return "v1";
}
#Path("sub")
public V1SubResource getSub() {
return new V1SubResource();
}
public static class V1SubResource {
#GET
public String get() {
return "v1subresource";
}
}
}
#Path("v2")
#Status1000
public static class V2Resource {
#GET
public String get() {
return "v2";
}
#Path("sub")
public V2SubResource getSub() {
return new V2SubResource();
}
#Status1000
public static class V2SubResource {
#GET
public String get() {
return "v2subresource";
}
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig(V1Resource.class, V2Resource.class)
.property(ServerProperties.WADL_FEATURE_DISABLE, true)
.register(Status1000Filter.class)
.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
#Test
public void should_return_1000_for_v2_resource_method() {
final Response response = target("v2").request().get();
assertThat(response.getStatus(), is(500));
}
#Test
public void should_return_1000_for_v2_subresource_locator() {
final Response response = target("v2/sub").request().get();
assertThat(response.getStatus(), is(500));
}
#Test
public void should_return_data_for_v1_resource_method() {
final Response response = target("v1").request().get();
assertThat(response.getStatus(), is(200));
assertThat(response.readEntity(String.class), is("v1"));
}
#Test
public void should_return_data_for_v1_subresource_locator() {
final Response response = target("v1/sub").request().get();
assertThat(response.getStatus(), is(200));
assertThat(response.readEntity(String.class), is("v1subresource"));
}
}

Use #Validated and #Valid with spring validator

I have a java bean being used to send JSON messages to a spring #RestController and I have bean validation setup and running just fine using #Valid. But I want to move to Protobuf/Thrift and move away from REST. It is an internal API and a lot of big companies have done away with REST internally. What this really means is that I no longer have control of the message objects - they are generated externally. I can't put annotations on them anymore.
So now my validation has to be programmatic. How do I do this? I have coded up a Validator and it works just great. But it doesn't use the nice #Valid annotation. I have to do the following:
#Service
public StuffEndpoint implements StuffThriftDef.Iface {
#Autowired
private MyValidator myValidator;
public void things(MyMessage msg) throws BindException {
BindingResult errors = new BeanPropertyBindingResult(msg, msg.getClass().getName());
errors = myValidator.validate(msg);
if (errors.hasErrors()) {
throw new BindException(errors);
} else {
doRealWork();
}
}
}
This stinks. I have to do this in every single method. Now, I can put a lot of that into one method that throws BindException and that makes it one line of code to add to every method. But that's still not great.
What I want is to see it look like this:
#Service
#Validated
public StuffEndpoint implements StuffThriftDef.Iface {
public void things(#Valid MyMessage msg) {
doRealWork();
}
}
And still get the same result. Remember, my bean has no annotations. And yes, I know I can use the #InitBinder annotation on a method. But that only works for web requests.
I don't mind injecting the correct Validator into this class, but I would prefer if my ValidatorFactory could pull the correct one based on the supports() method.
Is this possible? Is there a way to configure bean validation to actually use Spring validation instead? Do I have to hijack a Aspect somewhere? Hack into the LocalValidatorFactory or the MethodValidationPostProcessor?
Thanks.
Its pretty complicated thing to combine Spring validation and JSR-303 constrains. And there is no 'ready to use' way. The main inconvenience is that Spring validation uses BindingResult, and JSR-303 uses ConstraintValidatorContext as result of validation.
You can try to make your own validation engine, using Spring AOP. Let's consider, what we need to do for it. First of all, declare AOP dependencies (if you didn't yet):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
I'm using Spring of version 4.2.4.RELEASE, but of cause you can use your own. AspectJ needed for use aspect annotation. Next step, we have to create simple validator registry:
public class CustomValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator){
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for(Validator validator : validatorList){
if(validator.supports(o.getClass())){
result.add(validator);
}
}
return result;
}
}
As you see it is very simple class, which allow us to find validator for object. Now lets create annotation, that will be mark methods, that need to be validated:
package com.mydomain.validation;
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomValidation {
}
Because of standard BindingException class is not RuntimeException, we can't use it in overriden methods. This means we need define our own exception:
public class CustomValidatorException extends RuntimeException {
private BindingResult bindingResult;
public CustomValidatorException(BindingResult bindingResult){
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Now we are ready to create an aspect that will do most of the work. Aspect will execute before methods, which marked with CustomValidation annotation:
#Aspect
#Component
public class CustomValidatingAspect {
#Autowired
private CustomValidatorRegistry registry; //aspect will use our validator registry
#Before(value = "execution(public * *(..)) && annotation(com.mydomain.validation.CustomValidation)")
public void doBefore(JoinPoint point){
Annotation[][] paramAnnotations =
((MethodSignature)point.getSignature()).getMethod().getParameterAnnotations();
for(int i=0; i<paramAnnotations.length; i++){
for(Annotation annotation : paramAnnotations[i]){
//checking for standard org.springframework.validation.annotation.Validated
if(annotation.annotationType() == Validated.class){
Object arg = point.getArgs()[i];
if(arg==null) continue;
validate(arg);
}
}
}
}
private void validate(Object arg) {
List<Validator> validatorList = registry.getValidatorsForObject(arg);
for(Validator validator : validatorList){
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if(errors.hasErrors()){
throw new CustomValidatorException(errors);
}
}
}
}
execution(public * *(..)) && #annotation(com.springapp.mvc.validators.CustomValidation) means, that this aspect will applied to any public methods of beans, which marked with #CustomValidation annotation. Also note, that to mark validated parameters we are using standard org.springframework.validation.annotation.Validated annotation. But of cause we could make our custom. I think other code of aspect is very simple and does not need any comments. Further code of example validator:
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return aClass==Person.class;
}
#Override
public void validate(Object o, Errors errors) {
Person person = (Person)o;
if(person.getAge()<=0){
errors.rejectValue("age", "Age is too small");
}
}
}
Now we have make tune the configuration and all ready to use:
#Configuration
#ComponentScan(basePackages = "com.mydomain")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig{
.....
#Bean
public CustomValidatorRegistry validatorRegistry(){
CustomValidatorRegistry registry = new CustomValidatorRegistry();
registry.addValidator(new PersonValidator());
return registry;
}
}
Note, proxyTargetClass is true because we will use cglib class proxy.
Example of target method in service class:
#Service
public class PersonService{
#CustomValidation
public void savePerson(#Validated Person person){
....
}
}
Because of #CustomValidation annotation aspect will be applied, and because of #Validated annotation person will be validated. And example of usage of service in controller(or any other class):
#Controller
public class PersonConroller{
#Autowired
private PersonService service;
public String savePerson(#ModelAttribute Person person, ModelMap model){
try{
service.savePerson(person);
}catch(CustomValidatorException e){
model.addAttribute("errors", e.getBindingResult());
return "viewname";
}
return "viewname";
}
}
Keep in mind, that if you will invoke #CustomValidation from methods of PersonService class, validation will not work. Because it will invoke methods of original class, but not proxy. This means, that you can invoke this methods only from outside of class (from other classes), if you want validation to be working (eg #Transactional works same way).
Sorry for long post. My answer is not about 'simple declarative way', and possible you will do not need it. But I was curious resolve this problem.
I marked #Ken's answer as correct because it is. But I have taken it a little further and wanted to post what I have made. I hope anybody coming to this page will find it interesting. I might try to get it in front of the Spring folks to see if it might be something included in future releases.
The idea is to have a new annotation to replace #Valid. So I called it #SpringValid. Using this annotation would kick off the system put together above. Here are all the pieces:
SpringValid.java
package org.springframework.validation.annotation;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER})
#Retention(RUNTIME)
public #interface SpringValid {
}
SpringValidationAspect.java
package org.springframework.validation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
#Aspect
#Component
public class SpringValidationAspect {
private SpringValidatorRegistry springValidatorRegistry;
#Autowired
public SpringValidationAspect(final SpringValidatorRegistry springValidatorRegistry) {
this.springValidatorRegistry = springValidatorRegistry;
}
public SpringValidatorRegistry getSpringValidatorRegistry() {
return springValidatorRegistry;
}
#Before("#target(org.springframework.validation.annotation.Validated) "
+ "&& execution(public * *(#org.springframework.validation.annotation.SpringValid (*), ..)) "
+ "&& args(validationTarget)")
public void beforeMethodThatNeedsValidation(Object validationTarget) {
validate(validationTarget);
}
private void validate(Object arg) {
List<Validator> validatorList = springValidatorRegistry.getValidatorsForObject(arg);
for (Validator validator : validatorList) {
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if (errors.hasErrors()) {
throw new SpringValidationException(errors);
}
}
}
}
Spring's examples show classes annotated with #Validated so I wanted to keep that. The above aspect only targets classes with #Validated at the class-level. And, just like when you use #Valid, it looks for the #SpringValid annotation stuck to a method parameter.
SpringValidationException.java
package org.springframework.validation;
import org.springframework.validation.BindingResult;
public class SpringValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private BindingResult bindingResult;
public SpringValidationException(final BindingResult bindingResult) {
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
SpringValidatorRegistry.java
package org.springframework.validation;
import org.springframework.validation.Validator;
import java.util.ArrayList;
import java.util.List;
public class SpringValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator) {
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for (Validator validator : validatorList) {
if (validator.supports(o.getClass())) {
result.add(validator);
}
}
return result;
}
}
Just like the first answer, a place to register all classes that implement Spring's org.springframework.validation.Validator interface.
SpringValidator.java
package org.springframework.validation.annotation;
import org.springframework.stereotype.Component;
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;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Component
public #interface SpringValidator {
}
This is just extra sauce to make it easier to register/find Validators. You could register all your Validators by hand, or you could find them via reflection. So this part is not required, I just thought it made things easier.
MyConfig.java
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.SpringValidationAspect;
import org.springframework.validation.SpringValidatorRegistry;
import org.springframework.validation.annotation.SpringValidator;
import java.util.Map;
import javax.validation.Validator;
#Configuration
public class MyConfig {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SpringValidatorRegistry validatorRegistry() {
SpringValidatorRegistry registry = new SpringValidatorRegistry();
Map<String, Object> validators =
applicationContext.getBeansWithAnnotation(SpringValidator.class);
validators.values()
.forEach(v -> registry.addValidator((org.springframework.validation.Validator) v));
return registry;
}
#Bean
public SpringValidationAspect springValidationAspect() {
return new SpringValidationAspect(validatorRegistry());
}
}
See, scan your classpath and look for #SpringValidator classes and register them. Then register the Aspect and away you go.
Here is an example of such a Validator:
MyMessageValidator.java
package com.example.validators;
import com.example.messages.MyMessage;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.SpringValidator;
#SpringValidator
public class MyMessageValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyMessage.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "firstField", "{javax.validation.constraints.NotNull}",
"firstField cannot be null");
MyMessage obj = (MyMessage) target;
if (obj.getSecondField != null && obj.getSecondField > 100) {
errors.rejectField(errors, "secondField", "{javax.validation.constraints.Max}", "secondField is too big");
}
}
}
And here is the service class that uses the #SpringValid annotation:
MyService.java
package com.example.services;
import com.example.messages.MyMessage;
import org.springframework.validation.annotation.SpringValid;
import org.springframework.validation.annotation.Validated;
import javax.inject.Inject;
#Validated
public class MyService {
public String doIt(#SpringValid final MyMessage msg) {
return "we did it!";
}
}
Hope this makes sense for someone at some point. I personally think it is quite useful. A lot of companies are starting to move their internal APIs away from REST and to something like Protobuf or Thrift. You can still use Bean Validation but you have to use XML, and it isn't all that nice. So I hope this will be helpful to people who want to still do programmatic validation.
Hope it helps someone. I've got it working by adding the following configuration:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#Configuration
public class ValidatorConfiguration {
#Bean
public MethodValidationPostProcessor getMethodValidationPostProcessor(){
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(this.validator());
return processor;
}
#Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}
}
The service is then annotated the same way (#Validated on the class and #Valid on the parameter) and can be injected into another bean where the method can be called directly and validation happens.

Categories