JAX-RS 404 Not Found ExceptionMapper - java

I'm looking for way to present custom error pages if no matching JAX-RS resource was found. My idea was to use an ExceptionMapper, but I don't know the exception class to use:
#Provider
public class NotFoundMapper implements ExceptionMapper<WHATEXCEPTION?> {
...
}
RestEasy has a proprietary NotFoundException, but is there a way that works with Jersey, too? Ideally a standard compliant way?

Do you mean if your rest service is listening under localhost/rest then localhost/rest/asdf should return you a customized 404 page? It is specific to the JAX-RS engine, you must check the source code of Jersey. For example CXF throws a WebApplicationException, but it does not provide any way to customize it. See JAXRSInInterceptor.processRequest() line 156 to see how it is done :
org.apache.cxf.common.i18n.Message errorMsg =
new org.apache.cxf.common.i18n.Message("NO_ROOT_EXC",
BUNDLE,
message.get(Message.REQUEST_URI),
rawPath);
LOG.warning(errorMsg.toString());
Response resp = JAXRSUtils.createResponse(resource, message, errorMsg.toString(),
Response.Status.NOT_FOUND.getStatusCode(), false);
throw new WebApplicationException(resp);
To summarize there is no nice and standard way, you can try to use a request filter or an ExceptionMapper

Related

JAX-RS exception handling on Websphere Liberty

I need some help with understanding how Websphere Liberty (18.0.0.1) handles exceptions thrown within a JAX-RS endpoint invocation. I'm using Liberty feature jaxrs-2.0, so the implementation should be provided by WLP.
Now, my application has a POST HTTP endpoint accepting JSON payload and I'd like to provide a custom error messages for all the possible wrong client inputs.
Here's one case that works in a way I expected it:
Client sends application/xml instead of application/json
There's a ClientErrorException thrown by the container
I can use my own exception mapper (implementing ExceptionMapper<WebApplicationException> to handle this exception (actually to handle all the web application exception, which I'm fine with)
This way I can format the error message, mark error with ID, whatever is needed. That's good
And here's the case not working for me:
Client sends application/json, but with empty body
The core exception in this case is java.io.EOFException: No content to map to Object due to end of input - yeah, that looks accurate
Now what I can't figure out - instead of wrapping this EOFException into some kind of WebApplicationException (which I could handle easily), WLP is wrapping the exception issue into JaxRsRuntimeException
A couple of points here:
I don't want to create a mapper implementing ExceptionMapper<JaxRsRuntimeException> because that exception is not a part of JAX-RS 2.0 spec and I'd have to provide the import to JaxRsRuntimeException and wire the application with some Liberty-specific library.
A possible solution is to have my mapper implement a generic ExceptionMapper<RuntimeException> and string check if it finds exception of classname 'JaxRsRuntimeException' and then handle it. But that just doesn't seem right to me.
So, is that a WLP design not to give me a WebApplicationException in this case? What would be the elegant solution to handle this scenario?
Thanks
EDIT: Added some parts of source code.
REST endpoint and resource method:
#Path("/books")
public class BookEndpoint {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response createBook(Book book, #Context UriInfo uriInfo) {
bookDao.create(book);
UriBuilder builder = uriInfo.getAbsolutePathBuilder();
builder.path(Integer.toString(book.getId()));
return Response.created(builder.build()).entity(book).build();
}
}
Entity with JAXB annotations:
#XmlRootElement
public class Book {
private int id;
private String title;
// getters, setters
}
Exception stack trace:
com.ibm.ws.jaxrs20.JaxRsRuntimeException: java.io.EOFException: No content to map to Object duto end of input
at org.apache.cxf.jaxrs.utils.JAXRSUtils.toJaxRsRuntimeException(JAXRSUtils.java:1928)
at [internal classes]
at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:201)
at [internal classes]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.EOFException: No content to map to Object duto end of input
at org.codehaus.jackson.map.ObjectMapper._initForReading(ObjectMapper.java:2775)
at [internal classes]
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBodyReader(JAXRSUtils.java:1413)
at [internal classes]
... 48 more
This is the expected behavior based on Section 3.3.4 (and 4.5.1) of the JAX-RS 2.0 Spec. These sections describe how exceptions from JAX-RS resources and providers are handled - in short:
If the exception is a WebApplicationException, then it will automatically mapped to a Response.
If there is an ExceptionMapper registered that can handle the thrown exception, then that will be used to generate the response.
Unchecked exceptions are propagated to the container (i.e. Liberty's JAX-RS implementation code).
Unmapped exceptions must be handled via a container-specific exception and then appropriately propagated to the underlying container - in this case a ServletException must be passed to the web container.
The JaxRsRuntimeException is used to satisfy step 4.
In this scenario the built-in JSON provider (based on Jackson 1.X) is throwing the EOFException. Since there are no exception mappers for the EOFException (or any of it's superclasses), it is ultimately mapped to a ServletException by way of the JaxRsRuntimeException.
In order for an application to handle this scenario, there are a few different options:
You can register an ExceptionMapper that is specific to this exception type (EOFException or any of it's superclasses - i.e. IOException). You should not need to register a mapper for JaxRsRuntimeException as that exception is only used internally in Liberty - and should not be mapped. If you are seeing the JaxRsRuntimeException passed to an ExceptionMapper, then you should open a support case with IBM, as this is likely a bug.
With an ExceptionMapper<EOFException> you can return a specific response whenever an EOFException is thrown from a provider or resource.
You can register your own MessageBodyReader that will convert JSON to objects (using Jackson or any other JSON serialization code) but that will handle empty message bodies in the way you want - for example, converting it to null or using some kind of default object instance. Because user-registered providers take priority over built-in providers, this MBR would be used instead of Liberty's Jackson-based MBR.
This approach definitely gives you more control over how the data is deserialized as well as the exception handling.
Register a ContainerRequestFilter provider that will abort when the message body is empty. Here is an example:
#Provider
public class EmptyBodyCheckFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext crc) throws IOException {
if (crc.getEntityStream().available() < 1) {
crc.abortWith(Response.status(400).entity("Invalid request - empty message body").build());
}
}
}
I've successfully tested options 1 and 3 using the WebSphere Liberty May 2018 Beta. I haven't personally tested option 2 for this scenario, but based on using custom MBRs in the past, this should work.
One thing to keep in mind is that when Liberty GAs the jaxrs-2.1 feature, it will use JSONB as the built-in provider for serializing/deserializing JSON instead of Jackson. I tested your scenario using JAX-RS 2.1 (also in the May Beta) and instead of an EOFException, the JSONB code throws a NoSuchElementException. If you think you might move to JAX-RS 2.1, then I would suggest option 2 or 3. Option 1 would require that you create a new ExceptionMapper for JAX-RS 2.1.
Hope this helps,
Andy
Not a direct answert on "why WLP wrap the exception ..etc" but maybe add an exception interceptor as you did but on"ExceptionMapper<Exception>"and recusrsively iterate on the "causes" to check if java.io.EOFExceptionis one of those...

Jersey Viewable with Json

The JAX-RS implementation Jersey supports MVC style web applications through the Viewable class, which is a container for a template name and a model object. It is used like this:
#GET
#Template
#Produces({MediaType.TEXT_HTML})
public Viewable get() {
JsonObject response = null;
try{
response = service.getDetails(id);
}
catch(Exception ex) {
log.error("failed to get details", ex);
throw ex;
}
return new Viewable("/test", response);
}
this is right way to send the json from Viewable? Is there a way to set a json object explicitly?
A few things: I don't have any experience using Viewable in particular, but I am familiar with JAX-RS and can probably throw a couple of pointers your way.
Exception Handlers
JAX-RS defines a feature for mapping exceptions to responses. This functionality is nice for removing those exception blocks from your resource code. Check out the Jersey docs on this topic for a tutorial on how to register these. A quick summary is: 1) implement ExceptionMapper and 2) register the class as a Provider.
For starters, I recommend creating a simple suite that maps to common HTTP codes. For example:
NotFoundException - returns a 404 response and is used when a single entity is requested but not found.
InvalidInputException - returns a 422 response and is used when a request does not pass validation (like trying to save an phone number in an email field).
BadRequestException - usually the framework will handle these situations for you, but if not, a Bad Request is one that is not formatted properly. So if a required header is missing, or if a client tries to save a collection when only a single entity is allowed.
Exception* - There is a star here because an unexpected exception is usually due to a server error, so 500 is an appropriate default response. A reason you may want to create a global uncaught exception handler is to prevent the stacktrace from being returned in the response body. That can be bad for security reasons.
View and Model
You should not need the #Template annotation if you are using the Viewable object. Also, Viewable is expecting a template as the first argument and a model (map) as the second argument. The model should have keys that match variables in your JSP. Right now your method will look for a file called test.jsp in the root of whatever your template config is set to in web.xml. If you take all of that into consideration, your method could look something like this:
#GET
#Produces(MediaType.TEXT_HTML)
public Viewable getMobileReport() {
return new Viewable("/test", service.getMobileReport(id));
}

SOAP annotation for path

I need to manage different versions that come in the URL of the Webservice. I'm using a common method in the Webservice for SOAP and REST, and when I tried to get the path in the endPoint class I got the correct path for REST, but not for SOAP(I got a null in this case), does anybody how to get the path in soap?
The url looks like: http://localhost:8083/webService/v1/test and the code is:
#Stateless
#WebService(endpointInterface = "ItestgEndpoint", serviceName="testService")
#Component("testEndpoint")
#Path("/webService")
#Consumes({MediaType.APPLICATION_XML})
#Produces({MediaType.APPLICATION_XML})
public class TestEndpoint implements ItestgEndpoint{
#PUT
#Path("/{version}/test")
#Consumes({MediaType.APPLICATION_XML })
#Produces({MediaType.APPLICATION_XML })
#WebResult(name="testResponse")
public testResponse testEvent(#WebParam(targetNamespace="http://test/web", name="message")
#RequestParam MessageClass message,
#WebParam(name="version") #PathParam("version") String version
) throws TimeoutException, EMSException, ValidationException, AuthenticationException {
logger.info(version);
}
I saw this post SOAP and REST Webservice with one implementation in Java EE, but it didn't solve my problem.
Thanks
SOAP has no "path" like REST has. Depending on the service toolkit you use (CXF, axis, ...) you may need to collect the information in the request that you can extract from the transport mechanism of the soap message. This may or may not be available to you.
Usually, you wire the SOAP service to a path in a configuration. For example in axis, you set the soap servlet dispatcher in the web.xml, same is for cxf. The actual service is then wired in the beans.xml or services.wsdd.
The SOAP handler finds the service by the name of the endpoint and will then send the call to that endpoint which will dispatch it to the right method. The method is in the transmitted SOAP header, not in the URI.
In REST, the identification of the target service/method is in the URI, not in the XML.
So, in my opinion, for SOAP, this is by declaration and the wiring is static, while in REST, you can have the version as a path parameter.
UPDATE: Since everything is possible if you just try hard enough :-) you could rewrite the dispatcher code to allow an extra path parameter on SOAP, maybe the available packages allow some kind of interceptor class, that allows you to rewrite the SOAP header to point to another endpoint, depending on a header attribute that you invent.

How can I see if the request path matched but Accept did not in a catch-all handler?

Normally spring will return a 404 response for a request mapping where the path did not match, and return a 406 if the path did match but the "Accept" header did not match.
I have a default controller which acts as a "catch-all" which handles rest faults by returning faults in the Accepted format. Controller is of the form:
#Controller
public class DefaultController {
#RequestMapping("/**")
public void unmappedRequest(HtpServletRequest req) {
throw new ResourceNotFoundException();
}
}
Trouble is if I do get a match here, I can't tell if it matched elsewhere. I want to return the correct error to the client and tell them the Acceptable types they may retry with. Currently all I can do is throw a general ResourceNotFound exception.
Is this something I can do in a #Controller or am I going to need to resort to writing some sort of filter chain for this?
FWIW I'm using the ReST exception handling pattern demonstrated by Stormpath
I don't think a catch all controller is good way to handle "unmapped urls" at all.
I would suggest implementing a custom implementation of AbstractHandlerExceptionResolver, and not relying on the Default implementation provided by Spring. The doResolveException can be extended to do almost anything you want with the request and response.
If you want this custom ExceptionResolver to apply only to specific controllers (REST controllers), you can set mappedHandlerClasses on the exception resolver with a list of controllers you want. Also, you can set the order of the custom exception resolver such that it sits before the default resolver.
If you think I'm off topic here, please let me know.

Jersey (JSR311-Implementation) & Redirections

Is there a way to redirect the user-agent in a Jersey Resource?
In Spring MVC there's the "redirect:"-syntax but I didn't find anything comparable in Jersey's Viewable class. The only method I found working was using HttpServletResponse.sendRedirect().
You have to return a Response object containing your status code and Location-header. The easiest way is to use javax.ws.rs.core.Response.temporaryRedirect(URI).
When using Viewable you might need to throw a WebApplicationException containing that Response object.

Categories