String insted of whitelabel error page in webflux? - spring

When I use org.springframework.boot:spring-boot-starter-web, I can remove Whitelabel error page and instead of it to show my message, when to link not found resource:
application.yml
server:
error:
whitelabel:
enabled: false
spring:
mvc:
throw-exception-if-no-handler-found: true
resources:
add-mappings: false
NotFoundResource.class
#RestControllerAdvice
class NotFoundResource {
#ExceptionHandler(NoHandlerFoundException.class)
public String handleNotFoundResource() {
return "Requested resource wasn't found on the server"
}
}
How do I do it with webflux? In weblux doesn't contain NoHandlerFoundException.class.

You can create your own ErrorWebExceptionHandler class for this requirement. Spring boot documentation gives insight on this.
[Quoted from documentation]
To change the error handling behavior, you can implement
ErrorWebExceptionHandler and register a bean definition of that type.
Because a WebExceptionHandler is quite low-level, Spring Boot also
provides a convenient AbstractErrorWebExceptionHandler to let you
handle errors in a WebFlux functional way, as shown in the following
example
For a more complete picture, you can also subclass
DefaultErrorWebExceptionHandler directly and override specific
methods.
You can put some breakpoints on DefaultErrorWebExceptionHandler class and check how it works to render error response. Then based on your project requirement you can customize it for your need.
Here is a very simple thing I tried out.
CustomErrorWebExceptionHandler class:
public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public CustomErrorWebExceptionHandler(
ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, applicationContext);
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return route(all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest serverRequest) {
Throwable throwable = (Throwable) serverRequest
.attribute("org.springframework.boot.web.reactive.error.DefaultErrorAttributes.ERROR")
.orElseThrow(
() -> new IllegalStateException("Missing exception attribute in ServerWebExchange"));
if (throwable.getMessage().equals("404 NOT_FOUND \"No matching handler\"")) {
return ServerResponse.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
.body(Mono.just("Requested resource wasn't found on the server"), String.class);
} else {
return ServerResponse.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
.body(Mono.just("Some Error happened"), String.class);
}
}
}
Create a bean from that class:
#Configuration(proxyBeanMethods = false)
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
#ConditionalOnClass(WebFluxConfigurer.class)
#AutoConfigureBefore(ErrorWebFluxAutoConfiguration.class)
public class Beans {
#Bean
#Order(-1)
public CustomErrorWebExceptionHandler modelMapper(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer,
ObjectProvider<ViewResolver> viewResolvers) {
CustomErrorWebExceptionHandler customErrorWebExceptionHandler = new CustomErrorWebExceptionHandler(
errorAttributes, resourceProperties,
applicationContext);
customErrorWebExceptionHandler
.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
customErrorWebExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
customErrorWebExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return customErrorWebExceptionHandler;
}
}
application.properties:
server.error.whitelabel.enabled=false
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
This StackOverflow answer was helpful.
https://stackoverflow.com/a/52508800/11251146

Related

How to disable SpringSecurity in Junit Test class using spring boot

I have created simple Rest Api service using spring boot 2.2.5.RELEASE, I have just enabled Spring Security in the application. The JUnits are not working. I tried some of the ways to solve this issue but its not working.
Looking at references in books and online (including questions answered in Stack Overflow) I learned about two methods to disable security in tests:
#WebMvcTest(value =MyController.class, secure=false)
#AutoConfigureMockMvc(secure = false)
#EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
All these annotation i tried one by one on Test class but its not working.
1.#WebMvcTest(value =MyController.class, secure=false)
2.#AutoConfigureMockMvc(secure = false)
Both of these settings were identified in various Stack Overflow answers as being deprecated, but I tried them anyway.
Unfortunately, they didn't work. Apparently, in Version 2.2.1 of Spring Boot (the version I am using) secure isn't just deprecated, it is gone. Tests with the annotations using the "secure = false" parameter do not compile.
The code snippet looks like this:
Code Snippet
package com.akarsh.controller;
import static org.junit.Assert.*;
#RunWith(SpringRunner.class)
#AutoConfigureMockMvc
#EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
#SpringBootTest(classes = SpringBootProj2Application.class,webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SurveyControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private SurveyService surveyService;
#Test
public void retrieveDetailsForQuestion_Test() throws Exception {
Question mockQuestion = new Question("Question1",
"Largest Country in the World", "Russia", Arrays.asList(
"India", "Russia", "United States", "China"));
Mockito.when(
surveyService.retrieveQuestion(Mockito.anyString(), Mockito
.anyString())).thenReturn(mockQuestion);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/surveys/Survey1/questions/Question1").accept(
MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
String expected = "{\"id\":\"Question1\",\"description\":\"Largest Country in the World\",\"correctAnswer\":\"Russia\",\"options\":[\"India\",\"Russia\",\"United States\",\"China\"]}";
String actual=result.getResponse().getContentAsString();
JSONAssert.assertEquals(expected,actual , false);
}
\\
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Authentication : User-->Roles
// Authorization : Role->Access
#Autowired
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}secret").roles("USER")
.and()
.withUser("akarsh").password("{noop}ankit").roles("ADMIN","USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and().authorizeRequests()
.antMatchers("/surveys/**").hasRole("USER")
.antMatchers("/users/**").hasRole("USER")
.antMatchers("/**").hasRole("ADMIN")
.and().csrf().disable()
.headers().frameOptions().disable();
}
}
\
Getting following exception
Description:
A component required a bean of type 'org.springframework.security.config.annotation.ObjectPostProcessor' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.config.annotation.ObjectPostProcessor' in your configuration.
2020-04-13 14:51:15.659 ERROR 5128 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener#36902638] to prepare test instance [com.akarsh.controller.SurveyControllerTest#3eb8057c]
\\
#RestController
public class SurveyController {
#Autowired
SurveyService surveyService;
#GetMapping("/surveys/{surveyId}/questions")
public List<Question> retrieveQuestionForSrvey(#PathVariable String surveyId)
{
if(surveyId!=null)
{
return surveyService.retrieveQuestions(surveyId);
}
return null;
}
#GetMapping("/surveys/{surveyId}/questions/{questionId}")
public Question retrieveQuestion(#PathVariable String surveyId,#PathVariable String questionId)
{
if(surveyId!=null)
{
return surveyService.retrieveQuestion(surveyId, questionId);
}
return null;
}
#PostMapping("/surveys/{surveyId}/questions")
public ResponseEntity<?> addQuestionForSrvey(#PathVariable String surveyId, #RequestBody Question question) {
Question createdTodo = surveyService.addQuestion(surveyId, question);
if (createdTodo == null) {
return ResponseEntity.noContent().build();
}
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(createdTodo.getId()).toUri();
return ResponseEntity.created(location).build();
}

Catching exception Feign

I want to handle any exception from feign client, even if service is not available. However I can not catch them using try/catch. This is my feign client:
#FeignClient(name = "api-service", url ="localhost:8888")
public interface ClientApi extends SomeApi {
}
Where api is:
#Path("/")
public interface SomeApi {
#GET
#Path("test")
String getValueFromApi();
}
Usage of client with try/catch:
#Slf4j
#Service
#AllArgsConstructor
public class SampleController implements SomeApi {
#Autowired
private final ClientApi clientApi;
#Override
public String getValueFromApi() {
try {
return clientApi.getValueFromApi();
} catch (Throwable e) {
log.error("CAN'T CATCH");
return "";
}
}
}
Dependencies are in versions:
spring-boot 2.2.2.RELEASE
spring-cloud Hoxton.SR1
Code should work according to How to manage Feign errors?.
I received few long stack traces among them exceptions are :
Caused by: java.net.ConnectException: Connection refused (Connection refused)
Caused by: feign.RetryableException: Connection refused (Connection refused) executing GET http://localhost:8888/test
Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: ClientApi#getValueFromApi() failed and no fallback available.
How to properly catch Feign exeptions, even if client service (in this case localhost:8888) is not available?
Ps. When feign client service is available it works, ok. I am just focused on the exceptions aspect.
A better way to handle the situation where your service is not available is to use a circuit breaker pattern. Fortunately, it is easy using Netflix Hystrix as an implementation of the circuit breaker pattern.
First of all, you need to enable Hystrix for feign clients in application configuration.
application.yml
feign:
hystrix:
enabled: true
Then you should write a fallback class for the specified feign client interface.
In this case getValueFormApi method in fallback class will act mostly like catch block that you wrote(with exception when circuit will be in open state and original method will not be attempted).
#Component
public class ClientApiFallback implements ClientApi {
#Override
public String getValueFromApi(){
return "Catch from fallback";
}
}
Lastly, you just need to specify the fallback class for your feign client.
#FeignClient(name = "api-service", url ="localhost:8888", fallback = ClientApiFallback.class)
public interface ClientApi extends SomeApi {
}
That way your method getValueFromApi is fail safe. If,
for any reason, any uncaught exceptions escape from getValueFromApi the ClientApiFallback method will be called.
To enable circuit breaker and also configure your application to deal with unexpected errors, you need to:
1.- Enable the circuit breaker itself
#SpringBootApplication
#EnableFeignClients("com.perritotutorials.feign.client")
#EnableCircuitBreaker
public class FeignDemoClientApplication {
2.- Create your fallback bean
#Slf4j
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PetAdoptionClientFallbackBean implements PetAdoptionClient {
#Setter
private Throwable cause;
#Override
public void savePet(#RequestBody Map<String, ?> pet) {
log.error("You are on fallback interface!!! - ERROR: {}", cause);
}
}
Some things you must keep in mind for fallback implementations:
Must be marked as #Component, they are unique across the application.
Fallback bean should have a Prototype scope because we want a new one to be created for each exception.
Use constructor injection for testing purposes.
3.- Your ErrorDecoder, to implement fallback startegies depending on the HTTP error returned:
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyCustomBadRequestException();
}
if (response.status() >= 500) {
return new RetryableException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
4.- In your configuration class, add the Retryer and the ErrorDecoder into the Spring context:
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
#Bean
public Retryer retryer() {
return new Retryer.Default();
}
You can also add customization to the Retryer:
class CustomRetryer implements Retryer {
private final int maxAttempts;
private final long backoff;
int attempt;
public CustomRetryer() {
this(2000, 5); //5 times, each 2 seconds
}
public CustomRetryer(long backoff, int maxAttempts) {
this.backoff = backoff;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
public void continueOrPropagate(RetryableException e) {
if (attempt++ >= maxAttempts) {
throw e;
}
try {
Thread.sleep(backoff);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
#Override
public Retryer clone() {
return new CustomRetryer(backoff, maxAttempts);
}
}
If you want to get a functional example about how to implement Feign in your application, read this article.

Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'request' is not active for the current thread for feign client

I am calling another microservice once my current microservice is up and ready using feign client in my current microservice built using Jhipster.
So my Feign Interface is
package com.persistent.integration.client;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.persistent.integration.service.dto.DataPipelineDTO;
#AuthorizedFeignClient(name = "Integrationconfiguration")
public interface DataPipelinesResourceFeign {
#RequestMapping(value = "/api/data-pipelines", method = RequestMethod.GET)
List<DataPipelineDTO> getAllDataPipelines(#RequestParam(value = "pageable") Pageable pageable );
}
}
And I have implemented ApplicationRunner where I have called feign client method.
#Component
public class ApplicationInitializer implements ApplicationRunner {
#Autowired
private DataPipelinesResourceFeign dataPipelinesResourceFeign;
#Autowired
private ActiveMQListener activeMqListener;
#Override
public void run(ApplicationArguments args) throws Exception {
// TODO Auto-generated method stub
Pageable pageable = PageRequest.of(0, 20);
try {
List <DataPipelineDTO> allStartedDataPipeLines = dataPipelinesResourceFeign.getAllDataPipelines(pageable); //.stream().filter(p->p.getState().equals(State.STARTED)).collect(Collectors.toList());
allStartedDataPipeLines.forEach(datapipe ->
{
try {
activeMqListener.consume(datapipe);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
But after running this, it gives below exception at dataPipelinesResourceFeign.getAllDataPipelines :
com.netflix.hystrix.exception.HystrixRuntimeException: DataPipelinesResourceFeign#getAllDataPipelines(Pageable) failed and no fallback available.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'request' is not active for the current thread; consider
defining a scoped proxy for this bean if you intend to refer to it
from a singleton; nested exception is java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:362)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractB>eanFactory.java:199)
at
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTarge>tSource.java:35)
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.>java:193)
at com.sun.proxy.$Proxy147.getAccessToken(Unknown Source) at
com.persistent.integration.security.oauth2.AuthorizationHeaderUtil.getAuthoriza>tionHeaderFromOAuth2Context(AuthorizationHeaderUtil.java:28)
at
com.persistent.integration.client.TokenRelayRequestInterceptor.apply(TokenRelay>RequestInterceptor.java:23)
at
feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)
at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at
feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
at
rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 68 more Caused by: java.lang.IllegalStateException: No
thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.web.context.request.RequestContextHolder.currentRequestAttr>ibutes(RequestContextHolder.java:131)
at
org.springframework.web.context.request.AbstractRequestAttributesScope.get(Abst>ractRequestAttributesScope.java:42)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:350)
many suggestions on internet were to add listerner RequestContextListener. But problem persisted even if I added listener in webConfigurer.java in onStartup method.
{
servletContext.addListener(RequestContextListener.class);
}
But of no use.
Any leads would be appreciated.
I found a workaround for this. I don't know why TokenRelayRequestIntercepton isn't working but you can use your own RequestInterceptor based on Spring's SecurityContext.
First, define a RequestInterceptor :
public class MyRequestInterceptor implements RequestInterceptor {
public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer";
public MyRequestInterceptor() {
super();
}
#Override
public void apply(RequestTemplate template) {
// demander un token à keycloak et le joindre à la request
Optional<String> header = getAuthorizationHeader();
if (header.isPresent()) {
template.header(AUTHORIZATION, header.get());
}
}
public static Optional<String> getAuthorizationHeader() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getDetails() != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oAuth2AuthenticationDetails =
(OAuth2AuthenticationDetails) authentication.getDetails();
return Optional.of(String.format("%s %s", oAuth2AuthenticationDetails.getTokenType(),
oAuth2AuthenticationDetails.getTokenValue()));
} else {
return Optional.empty();
}
}
}
and then, declare a config class for your feign client using your RequestInterceptor, it should contains something like this :
#Bean(name = "myRequestInterceptor")
public RequestInterceptor getMyRequestInterceptor() throws IOException {
return new MyRequestInterceptor();
}
Your Feign client shoud look like this:
#FeignClient(name = "SERVICE_NAME", configuration = MyFeignConfiguration.class)
public interface MyRestClient {
I had the same issue with Feign Client running on startup using ApplicationRunner and I came up with following solution.
I defined my FeignClientsConfiguration with OAuth2FeignRequestInterceptor, which accepts predefined bean DefaultOAuth2ClientContext and OAuth2 configuration OAuth2ProtectedResourceDetails:
#Configuration
public class MyConfig extends FeignClientsConfiguration {
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor( DefaultOAuth2ClientContext oAuth2ClientContext, MyOauth2Properties properties) {
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resourceDetails(properties));
}
#Bean
public DefaultOAuth2ClientContext oAuth2ClientContext() {
return new DefaultOAuth2ClientContext();
}
private OAuth2ProtectedResourceDetails resourceDetails(MyOauth2Properties oauth2Properties) {
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setAccessTokenUri(oauth2Properties.getAccessTokenUri());
resourceDetails.setUsername(oauth2Properties.getUsername());
resourceDetails.setPassword(oauth2Properties.getPassword());
resourceDetails.setClientId(oauth2Properties.getClientId());
return resourceDetails;
}
}
Your feign client will look something like this:
#FeignClient(url = "http://localhost:8080/api/v1")
public interface FeignClient {
}
After all this, calling FeignClient from ApplicationRunner.run() works fine.
Spring Boot 2.2.6

using validators in spring-data-rest returns http 500 instead of 400

I'm trying to get the validation in spring-data-rest to work. From the documentation you only need to make a validator available, and I've got that to work, but when a validation constraint is successfully caught/processed I get a 500 error page with the stack trace.
In the config class, RepositoryRestMvcConfiguration it has a validationExceptionHandler which looks like it should get such validation errors to return as 400 rather than 500. It is also a lazy loaded bean.
Do I have an incorrect setup? or is there another way to get spring-data-rest to return 400 instead of 500?
I'm using spring-data-rest version 2.0.0 Release
Stack trace return by tomcat:
HTTP Status 500 - Request processing failed; nested exception is javax.validation.ConstraintViolationException: Validation failed for classes [test.domain.Account] during persist time for groups [javax.validation.groups.Default, ]
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.ConstraintViolationException: Validation failed for classes [test.domain.Account] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='size must be between 0 and 10', propertyPath=login, rootBeanClass=class test.domain.Account, messageTemplate='{javax.validation.constraints.Size.message}'}
]
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:965)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:855)
javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:829)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Account Entity:
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#Column(unique = true)
#Size(max = 10)
String login;
}
RestMvcConfig:
#Configuration
public class RestExporterRestConfig extends RepositoryRestMvcConfiguration {}
Seem to have got it working; i had to override the validatingRepositoryEventListener() and manually add validators to the listener;
#Configuration
public class RestExporterRestConfig extends RepositoryRestMvcConfiguration {
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Bean
#Override
public ValidatingRepositoryEventListener validatingRepositoryEventListener() {
ValidatingRepositoryEventListener listener = new ValidatingRepositoryEventListener();
configureValidatingRepositoryEventListener(listener);
listener.addValidator("afterCreate", validator());
listener.addValidator("beforeCreate", validator());
return listener;
}
}
I now get a 400 returned as follows;
400 Bad Request
{"errors":
[{ "entity":"Account",
"message":"size must be between 0 and 10",
"invalidValue":"login 0dsfdsfdsfdsfdsfdsfdsfds",
"property":"login"
}]
}
The previous answers didn't work for me, I think due to changes in Spring Data Rest so here is an updated answer that did work with JPA and MongoDb to save anyone else spending ages on this.
Had to add this to my build.gradle dependencies
compile('org.hibernate:hibernate-validator:4.2.0.Final')
and this config class
#Configuration
public class CustomRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("afterCreate", validator());
validatingListener.addValidator("beforeCreate", validator());
validatingListener.addValidator("afterSave", validator());
validatingListener.addValidator("beforeSave", validator());
}
}
Now that Java implements default methods on interfaces, Spring has deprecated the Adapter types. You can implement the solution provided by Romell as shown below.
Personally, I'm not as concerned with the after methods, but feel free to choose on your own.
#Configuration
public class CustomRepositoryRestConfigurer implements RepositoryRestConfigurer {
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator());
validatingListener.addValidator("beforeSave", validator());
}
}

Test spring based rest service with JerseyTest and Grizzly: dependency injection not working

Here my Rest WS:
#Path("/personService")
#Service
public class PersonRestService {
Logger logger = LoggerFactory.getLogger(PersonRestService.class);
#Autowired
private PersonService personService;
#GET
#Path("{id}")
#Produces({ MediaType.APPLICATION_JSON })
public Person getPersonByID(#PathParam("id") String id) {
logger.debug("getItemByID with id {}", id);
return personService.getPersonById(id);
}
Here is the unit test:
public class PersonServiceRestTest extends JerseyTest {
public PersonServiceRestTest() throws Exception {
super(new WebAppDescriptor.Builder("com.intesasanpaolo.web.rest.service").
contextPath("test")
.contextParam("contextConfigLocation", "classpath*:application-context/web-test-context.xml")
.contextListenerClass(ContextLoaderListener.class)
.build());
}
#Test
public void testGetPerson() {
Client client = Client.create();
WebResource webResource = client.resource("http://localhost:9998/test/personService/1");
ClientResponse response = webResource.type(MediaType.APPLICATION_JSON).get(ClientResponse.class);
System.out.println(response);
}
}
When I run the test I receive and exception on personService.getPersonById(id) since personService is not autowired.
It seems that all the rest services created by grizzly do not share the spring context defined above:
INFO: Scanning for root resource and provider classes in the packages:
com.intesasanpaolo.web.rest.service
10-feb-2014 19.10.01 com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
class com.intesasanpaolo.web.rest.service.PersonRestService
class com.intesasanpaolo.web.rest.service.MyResource
I read a lot of discussion but still no solution.
Any idea?
Kind regards.
Massimo
Ok, I found the problem.
The context was not shared because the WebAppDescriptor was not configured with the SpringServlet.
The working configuration is the following.
super(new WebAppDescriptor.Builder("com.intesasanpaolo.web.rest.service")
.contextPath("test")
.contextParam("contextConfigLocation", "classpath*:application-context/web-test-context.xml")
.servletClass(SpringServlet.class)
.initParam("com.sun.jersey.api.json.POJOMappingFeature", "true")
.contextListenerClass(ContextLoaderListener.class)
.build());
Hope this could help someone ;)
Max

Resources