I'm trying to test the method below with Mockito and Junit:
#Transactional
#RequestMapping(method=RequestMethod.PUT,value ="/updateEmployer/{empId}")
public #ResponseBody Object updateEmployer(#PathVariable Integer empId,) throws Exception {
Employee e = EmployeeRepository.findOne(empId);
for (Department de : e.getDepartement()){
de.setDepartmentName(e.getName + "_" + de.getName());
}
EmployeeRepository..saveAndFlush(e);
return null;
}
This is the method Test:
#Test // throw java.lang.NullPointerException
public void updateEmployeeFailureTest() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders
.put("/updateEmployer/{empId}",18)
.accept(MediaType.APPLICATION_JSON)).andDo(print())
.andExpect(MockMvcResultMatchers.view().name("errorPage"))
.andExpect(MockMvcResultMatchers.model().attributeExists("exception"))
.andExpect(MockMvcResultMatchers.forwardedUrl("/WEB-INF/jsp/errorPage.jsp"))
.andExpect(MockMvcResultMatchers.status().isInternalServerError());
}
The printstack:
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /updateEmployer/18
Parameters = {}
Headers = {Content-Type=[application/json], Accept= application/json]}
Handler:
Type = com.controllers.employeeController
Method = public java.lang.Object com.controllers.employeeController.updateEmployer(java.lang.Integer) throws java.lang.Exception
Async:
Was async started = false
Async result = null
Resolved Exception:
***Type = java.lang.NullPointerException***
ModelAndView:
View name = errorPage
View = null
Attribute = exception
***value = java.lang.NullPointerException***
FlashMap:
MockHttpServletResponse:
Status = 500
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = /WEB-INF/jsp/errorPage.jsp
Redirected URL = null
Cookies = []
It's work but when i try to catch the text or the exception throwed by this method
adding #Test (expected= java.lang.NullPointerException.class) i have this error:
java.lang.AssertionError: Expected exception: java.lang.NullPointerException
when i try to get the nullPointerException Text as a value of the attribute (exception) of the section ModelAndView i get this error:
java.lang.AssertionError: Model attribute 'exception' expected:java.lang.NullPointerException but was:java.lang.NullPointerException
Is there a way to expect the exception throwed or the text in the value attribut ( value = java.lang.NullPointerException) or the Text in the Resolved Exception section using mockito (mockmvc)?
Any help will be much appreciated
You need to test that the exception attribute of the model is an instance of NullPointerException.
This can be done using a Hamcrest matcher:
.andExpect(MockMvcResultMatchers.model().attribute(
"exception",
Matchers.isA(NullPointerException.class))
A simpler solution would be to catch the exception through the MvcResult, like this:
...
MvcResult result = mockMvc.perform(...)
...
...
.andReturn();
assertThat(result.getResolvedException(), instanceOf(YourException.class));
assertThat(result.getResolvedException().getMessage(), is("Your exception message");
...
Related
I have a consumer (RabbitListner) in RPC mode and I would like to know if it is possible to throw exception that can be treated by the publisher.
To make more clear my explication the case is as follow :
The publisher send a message in RPC mode
The consumer receive the message, check the validity of the message and if the message can not be take in count, because of missing parameters, then I would like to throw Exception. The exception can be a specific business exception or a particular AmqpException but I want that the publisher can handle this exception if it is not go in timeout.
I try with the AmqpRejectAndDontRequeueException, but my publisher do not receive the exception, but just a response which is empty.
Is it possible to be done or may be it is not a good practice to implement like that ?
EDIT 1 :
After the #GaryRussel response here is the resolution of my question:
For the RabbitListner I create an error handler :
#Configuration
public class RabbitErrorHandler implements RabbitListenerErrorHandler {
#Override public Object handleError(Message message, org.springframework.messaging.Message<?> message1, ListenerExecutionFailedException e) {
throw e;
}
}
Define the bean into a configuration file :
#Configuration
public class RabbitConfig extends RabbitConfiguration {
#Bean
public RabbitTemplate getRabbitTemplate() {
Message.addWhiteListPatterns(RabbitConstants.CLASSES_TO_SEND_OVER_RABBITMQ);
return new RabbitTemplate(this.connectionFactory());
}
/**
* Define the RabbitErrorHandle
* #return Initialize RabbitErrorHandle bean
*/
#Bean
public RabbitErrorHandler rabbitErrorHandler() {
return new RabbitErrorHandler();
}
}
Create the #RabbitListner with parameters where rabbitErrorHandler is the bean that I defined previously :
#Override
#RabbitListener(queues = "${rabbit.queue}"
, errorHandler = "rabbitErrorHandler"
, returnExceptions = "true")
public ReturnObject receiveMessage(Message message) {
For the RabbitTemplate I set this attribute :
rabbitTemplate.setMessageConverter(new RemoteInvocationAwareMessageConverterAdapter());
When the messsage threated by the consumer, but it sent an error, I obtain a RemoteInvocationResult which contains the original exception into e.getCause().getCause().
See the returnExceptions property on #RabbitListener (since 2.0). Docs here.
The returnExceptions attribute, when true will cause exceptions to be returned to the sender. The exception is wrapped in a RemoteInvocationResult object.
On the sender side, there is an available RemoteInvocationAwareMessageConverterAdapter which, if configured into the RabbitTemplate, will re-throw the server-side exception, wrapped in an AmqpRemoteException. The stack trace of the server exception will be synthesized by merging the server and client stack traces.
Important
This mechanism will generally only work with the default SimpleMessageConverter, which uses Java serialization; exceptions are generally not "Jackson-friendly" so can’t be serialized to JSON. If you are using JSON, consider using an errorHandler to return some other Jackson-friendly Error object when an exception is thrown.
What worked for me was :
On "serving" side :
Service
#RabbitListener(id = "test1", containerFactory ="BEAN CONTAINER FACTORY",
queues = "TEST QUEUE", returnExceptions = "true")
DataList getData() {
// this exception will be transformed by rabbit error handler to a RemoteInvocationResult
throw new IllegalStateException("mon expecion");
//return dataHelper.loadAllData();
}
On "requesting" side :
Service
public void fetchData() throws AmqpRemoteException {
var response = (DataList) amqpTemplate.convertSendAndReceive("TEST EXCHANGE", "ROUTING NAME", new Object());
Optional.ofNullable(response)
.ifPresentOrElse(this::setDataContent, this::handleNoData);
}
Config
#Bean
AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
var rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter jsonMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.registerModule(new JavaTimeModule());
var jsonConverter = new Jackson2JsonMessageConverter(objectMapper);
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = Map.of(
DataList.class.getName(), DataList.class,
RemoteInvocationResult.class.getName(), RemoteInvocationResult.class
);
classMapper.setIdClassMapping(idClassMapping);
jsonConverter.setClassMapper(classMapper);
// json converter with returned exception awareness
// this will transform RemoteInvocationResult into a AmqpRemoteException
return new RemoteInvocationAwareMessageConverterAdapter(jsonConverter);
}
You have to return a message as an error, which the consuming application can choose to treat as an exception. However, I don't think normal exception handling flows apply with messaging. Your publishing application (the consumer of the RPC service) needs to know what can go wrong and be programmed to deal with those possibilities.
Upon scanning following code with findbugs, it reports Dodgy code:NP: Load of known null value in new ....(at line where new Exception is thrown)
Sometimes it is required to check null before initializing an object.
Why is this considered "dodgy"??
public class Employee{
#Valid
private Department dept;
#JsonCreator
public Employee(#JsonProperty(value = "department", required = true) Department aDepartment)
throws EmpServiceException{
if (aDepartment == null) {
throw new EmpServiceException(aDepartment, "Invalid Request");
}
this.dept= aDepartment;
}
My guess is that FindBugs is pointing out that the line where you throw the exception
throw new EmpServiceException(aDepartment, "Invalid Request");
is equivalent to
throw new EmpServiceException(null, "Invalid Request");
and wants you to use the latter. Is the first argument for that EmpServiceException constructor annotated with #NonNull?
I am new to this #ExceptionHandler. I need to return response in JSON format if there is any exception. My code is returning response in JSON format if the operation is successful. But when any exception is thrown it is return HTML response as I have used #ExceptionHandler.
Value and reason in #ResponseStatus is coming properly but in HTML. How can I can change it to a JSON response? Please help.
In my controller class i have this methods:
#RequestMapping(value = "/savePoints", method = RequestMethod.POST, consumes = "application/json", produces = "application/json;charset=UTF-8")
public #ResponseBody
GenericResponseVO<TestResponseVO> saveScore(
#RequestBody(required = true) GenericRequestVO<TestVO> testVO) {
UserContext userCtx = new UserContext();
userCtx.setAppId("appId");
return gameHandler.handle(userCtx, testVO);
}
Exception handling method:
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Error in the process")
#ExceptionHandler(Exception.class)
public void handleAllOtherException() {
}
You can annotate the handler method with #ResponseBody and return any object you want and it should be serialized to JSON (depending on your configuration of course). For instance:
public class Error {
private String message;
// Constructors, getters, setters, other properties ...
}
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public Error handleValidationException(MethodArgumentNotValidException e) {
// Optionally do additional things with the exception, for example map
// individual field errors (from e.getBindingResult()) to the Error object
return new Error("Invalid data");
}
which should produce response with HTTP 400 code and following body:
{
"message": "Invalid data"
}
Also see Spring JavaDoc for #ExceptionHandler which lists possible return types, one of which is:
#ResponseBody annotated methods (Servlet-only) to set the response content. The return value will be converted to the response stream using message converters.
Replace
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Error in the process")
by
#ResponseStatus(value = HttpStatus.NOT_FOUND)
the 'reason' attribute force html render!
I've waste 1 day on that.....
I have a global exception handler used for every exception and I want it to treat different my JSON methods. But I want to keep it centralized.
#ControllerAdvice
public class GlobalExceptionHandler extends AbstractHandlerExceptionResolver{
#ExceptionHandler(Exception.class)
#Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// Omitted code like logging, message translation, etc.
String contentType = response.getContentType();
//FIXME: This do NOT WORK. contentType will be null
if(contentType != null && contentType.startsWith(MediaType.APPLICATION_JSON_VALUE)){
// Add error as a header
modelAndView.setView( new MappingJackson2JsonView() );
}else{
// Add error to model
modelAndView.setViewName(MyJSPView);
}
}
After debugging I see that content type is null and I cant not use it. How could I distinguish between both calls?. For testing I coded this pair of methods:
#RequestMapping(value = "jspTest")
public String jspTest(){
throw new UserMessageException(ErrorMessages.TESTING_ERROR);
}
#RequestMapping(value = "jsonTest", produces = ContentType.JSON)
#ResponseBody
public String jsonTest(){
throw new UserMessageException(ErrorMessages.TESTING_ERROR);
}
I found a solution to the problem.
I had mistakenly mixed #ControllerAdvice and #ExceptionHandler in an AbstractHandlerExceptionResolver. They are different ways to handle an exception. See this link
So I replaced #ControllerAdvice for #Component and removed #ExceptionHandler. Now the Method handler returns in the handler parameter
I used this method:
private static boolean isJson(Object handler){
if( ! (handler instanceof HandlerMethod)){
return false;
}
RequestMapping mapping = ((HandlerMethod) handler).getMethodAnnotation(RequestMapping.class);
for(String mimeType : mapping.produces()){
if( mimeType.indexOf(MediaType.APPLICATION_JSON_VALUE) != -1 ){
return true;
}
}
// Mime types produced does not include application/json
return false;
}
We are using spring-security-core:2.0-RC4, spring-security-rest:1.4.0 plugin with grails 2.4.2. Both of them are working fine. When user enters invalid credentials, spring-security-rest:1.4.0 plugin gives 401, which is configured in Config.groovy
grails.plugin.springsecurity.rest.login.failureStatusCode = 401
And here is the small snippet of console output
rest.RestAuthenticationFilter - Actual URI is /api/login; endpoint URL is /api/login
rest.RestAuthenticationFilter - Applying authentication filter to this request
credentials.DefaultJsonPayloadCredentialsExtractor - Extracted credentials from JSON payload. Username: admin#asdasdmopi.com, password: [PROTECTED]
rest.RestAuthenticationFilter - Trying to authenticate the request
authentication.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
dao.DaoAuthenticationProvider - User 'admin#something.com' not found
rest.RestAuthenticationFilter - Authentication failed: Bad credentials
rest.RestAuthenticationFailureHandler - Setting status code to 401
context.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
Now there is no error message or response, just status 401 is send to client. Now I am trying to send a error response when there is 401 status.
Added following line in UrlMappings.groovy
"401"(controller:'unauthorized',action:'sendErrorResponse')
Created UnauthorizedController.groovy and added sendErrorResponse() as follows
def sendErrorResponse() {
try{
int errorCode = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.errorCode
int status = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.status
String message = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.message
String extendedMessage = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.extendedMessage
String moreInfo = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.moreInfo
throw new AccountException(status,errorCode,message,extendedMessage,moreInfo)
}catch(AccountException e){
log.error e.errorResponse()
response.setStatus(e.errorResponse().status)
render e.errorResponse()
}
}
My thinking was that on 401 the controller will be called and the method will render error response, but It doesn't work.
Is my Approach right?
Any other Best practice or idea to implement this?
Any pointers in right direction are appreciated.
Thanks a ton.
You need to override grails.plugin.springsecurity.rest.RestAuthenticationFailureHandler bean with your own customized version.
It can be something like this:
#Slf4j
#CompileStatic
class CustomRestAuthenticationFailureHandler implements AuthenticationFailureHandler {
/**
* Configurable status code, by default: conf.rest.login.failureStatusCode?:HttpServletResponse.SC_FORBIDDEN
*/
Integer statusCode
MessageSource messageSource
/**
* Called when an authentication attempt fails.
* #param request the request during which the authentication attempt occurred.
* #param response the response.
* #param exception the exception which was thrown to reject the authentication request.
*/
void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(statusCode)
response.addHeader('WWW-Authenticate', Holders.config.get("grails.plugin.springsecurity.rest.token.validation.headerName").toString())
def errorMessage
if (exception instanceof AccountExpiredException) {
errorMessage = messageSource.getMessage("springSecurity.errors.login.expired", null as Object[], LocaleContextHolder.getLocale())
} else if (exception instanceof CredentialsExpiredException) {
errorMessage = messageSource.getMessage("springSecurity.errors.login.passwordExpired", null as Object[], LocaleContextHolder.getLocale())
} else if (exception instanceof DisabledException) {
errorMessage = messageSource.getMessage("springSecurity.errors.login.disabled", null as Object[], LocaleContextHolder.getLocale())
} else if (exception instanceof LockedException) {
errorMessage = messageSource.getMessage("springSecurity.errors.login.locked", null as Object[], LocaleContextHolder.getLocale())
} else {
errorMessage = messageSource.getMessage("springSecurity.errors.login.fail", null as Object[], LocaleContextHolder.getLocale())
}
PrintWriter out = response.getWriter()
response.setContentType("aplication/json")
response.setCharacterEncoding("UTF-8");
out.print(new JsonBuilder([message: errorMessage]).toString());
out.flush();
}
}
And in your resources.groovy you should have
restAuthenticationFailureHandler(CustomRestAuthenticationFailureHandler) {
statusCode = HttpServletResponse.SC_UNAUTHORIZED
messageSource = ref("messageSource")
}