Hi
Greetings. I'm thoroughly confused about the gazillion ways of handling
errors in CXF/JAX-RS. I've read whatever documentation I've found on the
wiki, and have followed the many posts to the mailing list on this
subject, and I still can't figure out how to cover all corner cases
without hacking the CXF source code (a situation I don't want to get
into).
Ok :-).
As far as JAX-RS itself is concerned, there are 3 ways to deal with errors
(marked as JAXRS1, etc).
JAXRS1. Return a JAXRS Response directly from a resource method, with this
Response containing the appropriate status code
JAXRS2. Create as many ExceptionMappers as needed which will capture exceptions and convert them into appropriate Responses. One
might have a single mapper capturing all the exceptions or a number of mappers capturing seperate exceptions...CXF JAXRS ships a
default WebApplicationExceptionMapper but one can register a custom WebApplicationExceptionMapper if the responses produced by the
default mapper are not satisfactory in some specific cases.
JAXRS3. Have Servlet Filter capturing ServletExceptions - all the exceptions which have been not mapped by the mappers earlier on
will be visible to Servlet Filter.
These options should give you a 100% coverage.
Next, CXF JAX-RS and CXF offers few more options (marked as CXF1, etc).
CXF1. As noted in JAXRS2, one can override responses produced by default mapper by registering a custom
WebApplicationExceptionMapper but the CXF JAXRS specifc option is to register a ResponseHandler filter instead and override the
Response there
CXF2. One can configure CXF JAXRS to block the propagation of the unmapped execptions to the Servlet filter in which case CXF XML
Binding (which CXF JAX-RS depends upon) will wrap the exception in its xml-specific format
CXF3. One can register CXF fault interceptors and handle all the exceptions in
these interceptors.
That is probably it, does it clarify things a bit ?
I need to capture 100% of all possible errors that can ever happen
inside CXF, the web container (Tomcat 6.X), and any possible CXF
application running in the container (we ONLY use REST based services
using CXF), and stream the errors/exceptions out as XML (we use XStream
for that). That's the requirement.
Here's what I've done:
1) I wrote a piece of code that implements the ResponseHandler
interface. I do quite a bit of analysis inside that code, and return a
custom response called an ErrorResponse. This eventually gets streamed
out to XML. This ResponseHandler is configured as a provider with the
<jaxrs:providers> mechanism in our Spring config file. It seems to cover
the cases where the error condition is wrapped inside a Response object.
Yes, it is option CXF1 but also see option JAXRS2. I'm not sure when this code
even executes for you given that you
say in 2) below that you capture all the exceptions with exception mappers.
2) I implemented a bunch of ExceptionMapper classes for the most common
exceptions, as well as a "catch-all" for other exceptions. This seems to
cover the cases where a service is throwing an exception. My mappers
convert the exception to XML and stuffs that in a Response object. The
mappers are also configured using the <jaxrs:providers> mechanism.
OK
3) I wrote an a piece of code that extends the
AbstractOutDatabindingInterceptor class (based on the CXF example that
Sergey posted a while back). This is configured using the following
mechanism:
<cxf:bus>
<cxf:outFaultInterceptors>
<ref bean="XMLOutFault.Interceptor"/>
</cxf:outFaultInterceptors>
</cxf:bus>
You probably do not need it if you already do 1) and 2) unless you also do JAXWS. If both JAXWS & JAXRS are involved then relying on
fault interceptors can be quite an attractive option...
4) I wrote a Servlet Filter (implementing the javax.servlet.Filter
interface), that catches any exception and streams it out as XML. The
problem is that nothing downstream seems to throw any exceptions, so the
doFilter() method always returns normally.
This is expected because in 2) you've captured all the exceptions. Only those exceptions which have not been mapped will be
propagated
I need to know what I have to do to cover 100% of all possible error
cases, and stream the error out using our custom XStream transcode back
to the client. Some code samples or fairly detailed info about how to
write the code, and how to wire it up would be fantastic. One of the
corner cases NOT covered by any of the above mentioned mechanisms is
when I specify a URL that is not mapped by CXF. In this case, the
org.apache.cxf.transport.servlet.ServletController's invoke() method
will call generateNotFound(), which sets the HTTP status code to 404,
and writes an error message directly to the HttpServletResponse. How can
I override this?
Perhaps you can extend CXFServlet and override its protected createServletController and then override generateNotFound() on your
custom controller ?
Another, possibly a more awkward workaround is to use your custom ServletFilter and pass along an HttpResponse filter ? Please see
HttpServletResponseFilter and ServletOutputStreamFilter in the CXF JAXRS source for some basic example...Ex, given
protected void generateNotFound(HttpServletRequest request, HttpServletResponse
res) throws IOException {
res.setStatus(404);
res.setContentType("text/html");
res.getWriter().write("<html><body>No service was found.</body></html>");
}
you can override in your HttpResponse filter setContentType() and setStatus() and if you get "text/html" and "404" (CXF JAXRS can
also reply with 404 but it won't set text/html) then it is that edge case you're dealing with and if it is the case then in
getWriter() your return a StringWriter buffer instead to block the default response....
I don't want ANY component other than my code, writing ANYTHING to the
servlet's OutputStream in case something goes wrong. I absolutely have
to control 100% of all error handling. Any tips, tricks, and pointers to
examples are greatly appreciated. Thanks much,
At the moment I think that given the above, you can get a 100% coverage...
thanks, Sergey
/Henrik