I have a Jersey (2.14) application, which works all right. I have some services running there. Now I'd like to configure the ServletContainer so, that any not catched Exceptions should be intercepted and logged or emailed somewhere.
I have already an implementation of ApplicationEventListener and a test endpoint for generating an exception.
This is the method, which should generate an exception (this is working :-) :
#GET
#Path(TEST_EXCEPTION)
public String testException(#Context final ServletContext context) {
String s = null;
int size = 0;
if (System.nanoTime() % 10 != 0) {
s = null;
} else {
s = "No exception will occur";
}
size = s.length();
return Integer.toString(size) + ":" + s;
}
And this is the implementation if my ApplicationEventListener:
public class MyApplicationEventListener implements ApplicationEventListener {
private transient volatile int count = 0;
private int exceptions = 0;
#Override
public void onEvent(final ApplicationEvent applicationEvent) {
ApplicationEvent.Type type = applicationEvent.getType();
}
#Override
public RequestEventListener onRequest(final RequestEvent requestEvent) {
RequestEvent.Type type = requestEvent.getType();
if (type == RequestEvent.Type.ON_EXCEPTION) {
exceptions++;
}
count++;
return null;
}
}
And this the configuration in my web.xml:
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com....rest</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
com....filter.MyApplicationEventListener
</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.tracing</param-name>
<param-value>ALL</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
onEvent() and onRequest() are both been called, but when an Exception happens, I don't get a ON_EXCEPTION, but a START.
What am I doing wrong? Or how can I get all exceptions resulting from the methods of my Jersey service?
I'd like to have/make something like Spring's HandlerExceptionResolver.
You can do this pretty easy with an ExceptionMapper. If an exception occurs you still should send a response back to the client. But in here you could also send an email message or log a ticket etc.
Here is an example that I did for ConstraintViolations but this can be an exception mapper for type Exception. You can check the type of the exception and do something accordingly. In the toResponse you could implement a notfiyEmail method that could send the stack trace and messge to an email address for example or log a jira ticket which I have done in the past. Note though that you need to add the #Provider at the top and tell your rest Application/ResourceConfig to scan the package for this class. Alternatively you can register it manually with your ResourceConfig.
#Provider
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
private static final Logger logger = Logger.getLogger(ConstraintViolationExceptionMapper.class.getName());
public Response toResponse(ConstraintViolationException exception) {
final int violationCount = exception.getConstraintViolations().size();
ConstraintViolation<?>[] constraintViolations = exception.getConstraintViolations().toArray(
new ConstraintViolation<?>[violationCount]);
Violation[] violations = new Violation[exception.getConstraintViolations().size()];
for (int i = 0; i < violationCount; i++) {
ConstraintViolation<?> cv = constraintViolations[i];
Violation violation = new Violation(cv.getPropertyPath().toString(), cv.getMessage());
violations[i] = violation;
}
ConstraintErrorResponse responseEntity = new ConstraintErrorResponse();
responseEntity.setViolations(violations);
logger.info("Seinding exception response");
return Response.status(Response.Status.BAD_REQUEST).entity(responseEntity).build();
}
}
...when an Exception happens, I don't get a ON_EXCEPTION, but a
START.
What am I doing wrong?
You need to return an instance of RequestEventListener from the START event, rather than returning null, in order to tell Jersey to carry on firing events for the request.
From 21.1.2. Event Listeners :
The second method onRequest is invoked by Jersey runtime every time a
new request is received. The request event type passed to the method
is always START. If you want to listen to any other request lifecycle
events for the new request, you are expected to return an instance of
RequestEventListener that will handle the request.
See https://stackoverflow.com/a/33271754/5473824 for an example which logs exception events.
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.
I'm using Camel 2.15.3 and camel-netty4, and since upgrading from camel-netty3, I'm having problems receiving full JSON messages via UDP. Each JSON message is about 3 to 5 kbytes, but my MessageToMessageDecoder implementation is only giving me the first 2048 (i.e. 2k bytes). From a test program, I send in one UDP message, and from my debug prints within my MessageToMessageDecoder it shows that the decode() method is only called once.
I'm currently reading through Netty In Action, but i see this in my log file: UnpooledUnsafeDirectByteBuf(ridx: 0, widx: 2048, cap: 2048))
I desperately need to get this fixed in production, and just need to be able to receive JSON messasges via UDP and send them through my Camel routes. I'm confused about what is the best framing (if any) to use?
With netty3 this was working fine and I had a UdpPacketDecoder implements ChannelUpstreamHandler that invoked Channels.fireMessageReceived(ctx, message, me.getRemoteAddress()) to fire the message to the next handler and it seemed to work fine.
My route looks like the below. It consumes from netty4:udp and produces to a SEDA queue, just for now while testing:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route startupOrder="104" customId="true" id="ROUTE_ID_RAW_CQMS_EVENTS" xmlns="http://camel.apache.org/schema/spring">
<from uri="netty4:udp://devserver-09.dev.s.mission.net:11111?serverPipelineFactory=#CQMS_SERVER_PIPELINE_FACTORY_ROUTE_ID_RAW_CQMS_EVENTS&keepAlive=true&sync=false&receiveBufferSize=26214400&sendBufferSize=26214400&allowDefaultCodec=false&disconnectOnNoReply=false&receiveBufferSizePredictor=8192"/>
<setProperty propertyName="CamelCharsetName" id="setProperty1">
<expressionDefinition>iso-8859-1</expressionDefinition>
</setProperty>
<threads poolSize="7" maxPoolSize="14" threadName="threads_ROUTE_ID_RAW_CQMS_EVENTS" callerRunsWhenRejected="true" id="threads1">
<to uri="seda:SEDA_INPUT_QUEUE_102?size=200000&concurrentConsumers=10&waitForTaskToComplete=Never&failIfNoConsumers=true&timeout=10000" id="to1"/>
<setProperty propertyName="CamelCharsetName" id="setProperty2">
<expressionDefinition>iso-8859-1</expressionDefinition>
</setProperty>
</threads>
</route>
I print out the received DatagramPacket, which shows this: UnpooledUnsafeDirectByteBuf(ridx: 0, widx: 2048, cap: 2048))
Here is my MessageToMessageDecoder implementation:
package com.mission.mplr.multiprotocollistenerrouter;
import com.vonage.mplr.utils.MiscUtils;
import io.netty.channel.ChannelHandlerContext; // Represents the "binding" between a ChannelHandler and the ChannelPipeline.
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.nio.charset.Charset;
import java.util.List;
import org.slf4j.Logger; // The org.slf4j.Logger interface is the main user entry point of SLF4J API.
import org.slf4j.LoggerFactory; // Utility class producing Loggers for various logging APIs, most notably for log4j.
public class UdpDatagramDecoder extends MessageToMessageDecoder<DatagramPacket> {
private static final Logger logger = LoggerFactory.getLogger(UdpDatagramDecoder.class);
private static final Logger errorLogger = LoggerFactory.getLogger("ERROR_LOGGER");
private final String CHARSET_NAME;
UdpDatagramDecoder(String charsetName) {
this.CHARSET_NAME = charsetName;
}
#Override
public boolean acceptInboundMessage(Object msg) throws Exception {
return true;
}
#Override
protected void decode(ChannelHandlerContext chc, DatagramPacket packet, List out) throws Exception {
logger.info("decode(): ENTER");
logger.info("decode(): Received datagram = {}", packet);
String packetAsString = packet.content().toString(Charset.forName(CHARSET_NAME));
if(packetAsString == null) {
return; // Nothing to do
} else {
out.add(packetAsString);
packet.retain();
}
logger.info("decode(): bodyBytesAsString[size={}] = {}", packetAsString.length(), packetAsString);
String bodyBytesAsHex = MiscUtils.stringAsHex(packetAsString, CHARSET_NAME);
logger.info("decode(): bodyBytesAsHex[size={}] = {}", bodyBytesAsHex.length(), bodyBytesAsHex);
logger.info("decode(): EXIT");
}
}
// ------------- end --------------
My server pipeline has this initChannel() implementation:
#Override
protected void initChannel(Channel ch) throws Exception {
logger.trace("initChannel(): ENTER");
ChannelPipeline channelPipeline = ch.pipeline();
serverInvoked = true;
String theSourceRouteId = consumer.getRoute().getId();
logger.debug("initChannel(): consumer = {}, theSourceRouteId = {}", consumer.toString(), theSourceRouteId);
// -------------------------------------------------------------------
// Here we add the custom UDP datagram decoder. Decoders are typically
// stateful, thus we create a new instance with every pipeline.
// -------------------------------------------------------------------
String udpPacketDecoderName = "CQMS_UDP_DATAGRAM_DECODER_" + theSourceRouteId;
logger.debug("initChannel(): Adding {}", udpPacketDecoderName);
channelPipeline.addLast(udpPacketDecoderName, new UdpDatagramDecoder(CHARSET_NAME));
// -----------------------------------------------------------------------------------------
// Default Camel ServerChannelHandler for the consumer, to allow Camel to route the message.
// -----------------------------------------------------------------------------------------
String serverChannelHandlerName = "CQMS_SERVER_CHANNEL_HANDLER_" + theSourceRouteId;
logger.debug("initChannel(): Adding {}", serverChannelHandlerName);
channelPipeline.addLast(serverChannelHandlerName, new ServerChannelHandler(consumer));
logger.trace("initChannel(): EXIT");
}
Netty uses 2048 as upper limit for datagram packets by default. You can change this via setting your own instance of FixedRecvByteBufAllocator on the Bootstrap. Not sure how this can be done via Camel though.
Thanks so much Norman! Below is the solution that works for Camel 2.15.3.
Basically, we read the upper limit from the application's configuration and set it within the ServerInitializerFactory's initChannel(Channel ch) method.
#Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline channelPipeline = ch.pipeline();
serverInvoked = true;
// -------------------------------------------------------------------
// Here we add the custom UDP datagram decoder. Decoders are typically
// stateful, thus we create a new instance with every pipeline.
// -------------------------------------------------------------------
String udpDecoderName = "UDP_DECODER_" + theSourceRouteId;
channelPipeline.addLast(udpDecoderName, new UdpPacketDecoder_ADAPTER(CHARSET_NAME));
// ---------------------------------------------------------------------
// Netty4 has default of 2048 bytes as upper limit for datagram packets.
// Here we override the default upper limit based on a config param.
// ---------------------------------------------------------------------
if(ConfigManager.getInstance().getRecvByteBufAllocator() > 0) {
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(ConfigManager.getInstance().getRecvByteBufAllocator()));
}
// -----------------------------------------------------------
// Add string encoder (downstream) / string decoder (upstream)
// -----------------------------------------------------------
// For decoding from a ChannelBuffer to a String object
String stringDecoderName = "SERVER_PIPELINE_STRING_DECODER_" + theSourceRouteId;
channelPipeline.addLast(stringDecoderName, STR_DECODER);
// For encoding from a String object into a ChannelBuffer
String stringEncoderName = "SERVER_PIPELINE_STRING_ENCODER_" + theSourceRouteId;
channelPipeline.addLast(stringEncoderName, STR_ENCODER);
// For encoding from a String object into a DatagramPacket
String datagramPacketEncoderName = "SERVER_PIPELINE_DATAGRAM_PACKET_ENCODER_" + theSourceRouteId;
channelPipeline.addLast(datagramPacketEncoderName, DATAGRAM_PACKET_ENCODER);
// -----------------------------------------------------------------------------------------
// Default Camel ServerChannelHandler for the consumer, to allow Camel to route the message.
// -----------------------------------------------------------------------------------------
String serverChannelHandlerName = "SERVER_CHANNEL_HANDLER_" + theSourceRouteId;
channelPipeline.addLast(serverChannelHandlerName, new ServerChannelHandler(consumer));
}
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;
}
I am developing a REST service using SpringMVC, where I have #RequestMapping at class and method level.
This application is currently configured to return error-page jsp configured in web.xml.
<error-page>
<error-code>404</error-code>
<location>/resourceNotFound</location>
</error-page>
I however want to return custom JSON instead of this error page.
I am able to handle exception and return json for other exceptions, by writing this in controller, but not sure how and where to write the logic to return JSON when the url does not exist at all.
#ExceptionHandler(TypeMismatchException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ResponseEntity<String> handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.patient.bad.request", null, locale);
errorMessage += ex.getValue();
String errorURL = req.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
return new ResponseEntity<String>(errorInfo.toJson(), headers, HttpStatus.BAD_REQUEST);
}
I tried #ControllerAdvice, it works for other exception scenarios, but not when mapping is not avaialble,
#ControllerAdvice
public class RestExceptionProcessor {
#Autowired
private MessageSource messageSource;
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ResponseEntity<String> requestMethodNotSupported(HttpServletRequest req, HttpRequestMethodNotSupportedException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.patient.bad.id", null, locale);
String errorURL = req.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ResponseEntity<String> requestHandlingMethodNotSupported(HttpServletRequest req, NoSuchRequestHandlingMethodException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.patient.bad.id", null, locale);
String errorURL = req.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
}
}
After digging around DispatcherServlet and HttpServletBean.init() in SpringFramework I see that its possible in Spring 4.
org.springframework.web.servlet.DispatcherServlet
/** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
private boolean throwExceptionIfNoHandlerFound = false;
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if(throwExceptionIfNoHandlerFound) {
ServletServerHttpRequest req = new ServletServerHttpRequest(request);
throw new NoHandlerFoundException(req.getMethod().name(),
req.getServletRequest().getRequestURI(),req.getHeaders());
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
throwExceptionIfNoHandlerFound is false by default and we should enable that in web.xml
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
And then you can catch it in a class annotated with #ControllerAdvice using this method.
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ResponseEntity<String> requestHandlingNoHandlerFound(HttpServletRequest req, NoHandlerFoundException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.bad.url", null, locale);
String errorURL = req.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
}
Which allows me to return JSON response for bad URLs for which no mapping exist, instead of redirecting to a JSP page :)
{"message":"URL does not exist","url":"http://localhost:8080/service/patientssd"}
If you are using Spring Boot, set BOTH of these two properties:
spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true
Now your #ControllerAdvice annotated class can handle the "NoHandlerFoundException", as below.
#ControllerAdvice
#RequestMapping(produces = "application/json")
#ResponseBody
public class RestControllerAdvice {
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<Map<String, Object>> unhandledPath(final NoHandlerFoundException e) {
Map<String, Object> errorInfo = new LinkedHashMap<>();
errorInfo.put("timestamp", new Date());
errorInfo.put("httpCode", HttpStatus.NOT_FOUND.value());
errorInfo.put("httpStatus", HttpStatus.NOT_FOUND.getReasonPhrase());
errorInfo.put("errorMessage", e.getMessage());
return new ResponseEntity<Map<String, Object>>(errorInfo, HttpStatus.NOT_FOUND);
}
}
note it is not sufficient to only specify this property:
spring.mvc.throw-exception-if-no-handler-found=true
, as by default Spring maps unknown urls to /**, so there really never is "no handler found".
To disable the unknown url mapping to /**, you need
spring.resources.add-mappings=false ,
which is why the two properties together produce the desired behavior.
If you're using spring 3.2 or later you can use a controller advice (#ControllerAdvice) to deal with, amongst other things, mapping errors (404's). You can find documentation here. Take a look at section 17.11. You can use this, for example, to provide more detailed logging on why your request bindings aren't being matched for specific urls, or to simply return a more specific response than a generic 404.
you can return json in the location below,that /handle/404.
<error-page>
<error-code>404</error-code>
<location>/handle/404</location>
</error-page>
after you config this in web.xml,a 404 error will redirect to /handle/404,and you can create a controller with this mapping and return a json result. for example.
#RestController
#RequestMapping(value = "handle")
public class HttpErrorController {
#RequestMapping(value = "404")
public String handle404() {
return "404 error";
}
}
Here i am listing some json to pojo convertion java api's
GSon
Jackson
JSONGen
JSON Tools
Can you please list out these api's according their performance and ease of use. Also let me know if there is any other java api which is better than the above
Mainly this depends on the context of what kind of app are you developing, and it it is in the client side (for example, Android) or in the server side (for example, SpringMVC). This is my experience, maybe someone has more points to prove that me. I always use Jackson in SpringMVC servers, why?, because It's simple, when you're developing a RESTfull webservices you leave to SpringMVC the managment of the system such as redirect requests, perform the bussiness logic, and more. You will have then two servlets one to manage the page request such as go to http://myserver.com/home/myprofile/ and another servlet to expose an RESTfull api, this is where Jackson enter, as you know (or maybe not) all the webapps that run in Tomcat has an web.xml, here you tell to the Tomcat instance who servlet It's gonna to handle what request, checkout this example:
<!-- Spring MVC Dispatcher Servlet -->
<servlet>
<servlet-name>SpringMvcServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:config/applicationContext.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMvcServlet</servlet-name>
<url-pattern>/home/*</url-pattern>
</servlet-mapping>
<!-- Jersey -->
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>ar.com.kimboo.server.rest</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-serlvet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
Jersey It's a library that uses Jackson to serialize/deserialize HttpRequest. Note that all the request to /home/* are handled by SpringMVC and all request to /rest/* are handled by Jersey. So when you hit something like hit to "http://server.com/server/rest/animals/" and the request It's delivered to:
#Component
#Path("/animals")
public class AdvertisingRESTServiceImpl {
#Autowired AnimalServiceImpl animalsService;
/**
* #return All animals in the db.
* #uri http://localhost:8080/server/rest/animals/
*/
#GET #Path("/") #Produces(MediaType.APPLICATION_JSON)
public #ResponseBody List<Animals> getAllAnimals() {
return animalsService.getAllAnimals();
}
}
You can return a collection of Objects and Jersey will take care of serialize them. Everything behind scene without configure nothing but some xml. Sure you can use another libraries to serialize json in the server side like GSON. But you have to implmenet your own HttpMessageConverter (this class takes care of serialize/deserialize the json) what Jersey already had.
Always that I work in the client side (Android) I use gson to serialize/deserialize json from the server side. I prefer used Gson because It's pretty simple you, rather than use RestTemplate or some library. The only thing that I need do with Gson is... nothing:
This turn an object instance into json:
String json = new Gson().toJson(new Puppy("Robert",2));
And this turns a json into an object:
Animal robert = new Gson().fromJson("{name:\"Robert\",age:\"2\"}", Animal.class);
With this kind of tool you can do some cool Restfull http client, like this one:
public class HttpFuck {
Gson gson = new Gson();
public <T> T post(String url, Class<T> clazz, List<NameValuePair> parameters) {
// Create a new HttpClient and Post Header
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
try {
// Add your data
httppost.setEntity(new UrlEncodedFormEntity(parameters));
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
StringBuilder json = inputStreamToString(response.getEntity().getContent());
T gsonObject = gson.fromJson(json.toString(), clazz);
return gsonObject;
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public <T> T get(String url, Class<T> clazz) {
// Create a new HttpClient and Post Header
HttpClient httpclient = new DefaultHttpClient();
HttpGet httppost = new HttpGet(url);
try {
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
StringBuilder json = inputStreamToString(response.getEntity().getContent());
T gsonObject = gson.fromJson(json.toString(), clazz);
return gsonObject;
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// Fast Implementation
private StringBuilder inputStreamToString(InputStream is) throws IOException {
String line = "";
StringBuilder total = new StringBuilder();
// Wrap a BufferedReader around the InputStream
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
// Read response until the end
while ((line = rd.readLine()) != null) {
total.append(line);
}
// Return full string
return total;
}
}
The bad news are that you have to wrap almost everything in an object. For example you cant do something like new Gson().fromJson(string, String.class). But It's pretty flexive, I strongly recommend it to use it in Android apps.
Well long story short, this is all that I know about json technologies, hope this helps you. :)