Jeff Schmidt wrote:
> Hi Craig:
>
> This is uncanny. First the multipart request solution, and now this.
> :) I was just trying to figure out how to code something up myself.
> For my perspective though, I'm trying to keep business logic out of
> the JAX-RS resource classes, and confine it to some number services
> the resources will invoke to get the job done. The idea is for JAXB
> annotation classes and method parameters to define the service
> interface, with no declared, checked exceptions. For example:
>
> public interface SessionService {
>
> public void logout(String applicationName);
> public void keepAlive(String applicationName);
> }
>
> I don't want the service interfaces or implementations to be tied in
> anyway to JAX-RS/Jersey. I also don't want any service interface to be
> dependent on business classes. My goals in doing this are to make it
> easy to plug in different service implementations so I can better
> exercise the resource classes in my IDE and not be tied into a huge
> web application. I also want to make it so these same services can be
> invoked by the Spring Web Services based SOAP endpoints.
>
> So the resource class portion for the logout() method can be something
> like:
>
> @GET
> @Path("/logout")
> public Response logout(
> @DefaultValue(ApiConstants.DEF_APPLICATION_NAME)
> @QueryParam(ApiConstants.QUERY_PARAM_APPLICATION_NAME)
> String applicationName) {
>
> _sessionService.logout(applicationName);
>
> //TODO: return entity like SOAP LogoutEndPoint?
> return Response.ok().build();
> }
>
> Note this is a work in progress, and yes the APIs are session oriented. :)
>
I must confess to a couple of raised eyebrows when I saw that :-).
> So, an important aspect is exception handling. I don't want to add
> 'throws Exception' to each service method and force the resource to
> even address the existence of checked exceptions. I'd rather use
> runtime exceptions which can be thrown by the service method and
> handled by the JAX-RS ExceptionMapper implementations.
That was my thinking as well. I've also been influenced by a year's
exposure to Ruby, where essentially all exceptions behave like Java
RuntimeExceptions (you don't declare them explicitly), and I find that
style pretty nice.
> I'm trying to keep these exception classes JAX-RS/Jersey agnostic so
> that they can be used by the Spring WS exception mapper as well for
> SOAP fault processing.
>
That should be true for the exception classes I've been playing with --
they have no annotations at all. The only JAX-RS specific part is the
corresponding ExceptionMapper implementation classes.
> I too wish to provide more than just an HTTP status code. Returning
> 400 (Bad Request) might be helpful if the client provided bugus input
> parameters, but I would like to do more.
>
Me too.
> Please see additional comments below.
>
> Cheers,
>
> Jeff
>
> On Oct 23, 2008, at 6:22 PM, Craig McClanahan wrote:
>
>> In addition to the jersey-multipart stuff I contributed for creating
>> and consuming MIME multipart messages (see the "jersey-multipart"
>> module in the "contribs" directory), one of the other things I've
>> been doing in my "day job" work is defining a common "best practices"
>> approach to use for error handling in RESTful web services. We all
>> know that JAX-RS (and therefore Jersey) lets us create and consume
>> HTTP messages with any needed status code, but that still doesn't
>> address two concerns:
>>
>> * Ensuring that every developer on the team uses the same HTTP status
>> code
>> for the same "error" scenario, across multiple services (so that the
>> service APIs
>> all look like they actually came from the same company :-).
>
> For this aspect I was going to encourage developers to throw
> exceptions. For example, some fragments of the ServiceException:
>
> public class ServiceException extends RuntimeException {
>
> final protected int _responseStatusCode;
> protected FaultInfo _faultInfo = null;
>
> public ServiceException(String message, int responseStatusCode) {
> super(message);
> _responseStatusCode = responseStatusCode;
> }
>
> public ServiceException(String message, int responseStatusCode,
> FaultInfo faultInfo) {
> this(message, responseStatusCode);
> _faultInfo = faultInfo;
> }
>
> ...
>
> public int getResponseStatusCode() {
> return _responseStatusCode;
> }
>
> public FaultInfo getFaultInfo() {
> return _faultInfo;
> }
> }
>
I have a similar base exception class, including having a property for
the actual HTTP status code. What kind of stuff goes in a FaultInfo?
> Then, a couple sub-class fragments:
>
> public class InputValidationException extends ServiceException {
>
> public static final int RESPONSE_STATUS_CODE =
> HttpURLConnection.HTTP_BAD_REQUEST;
>
> public InputValidationException(String message) {
> super(message, RESPONSE_STATUS_CODE);
> }
>
> public InputValidationException(String message, FaultInfo faultInfo) {
> super(message, RESPONSE_STATUS_CODE, faultInfo);
> }
>
> ...
> }
>
> public class NotFoundException extends ServiceException {
>
> public static final int RESPONSE_STATUS_CODE =
> HttpURLConnection.HTTP_NOT_FOUND;
>
> public NotFoundException(String message) {
> super(message, RESPONSE_STATUS_CODE);
> }
>
> public NotFoundException(String message, FaultInfo faultInfo) {
> super(message, RESPONSE_STATUS_CODE, faultInfo);
> }
>
> ...
> }
>
> Thus, service and resource classes that throw this set of exceptions
> will use the same HTTP status codes. I'm using the HttpURLConnection
> constants here so that the exception classes are not JAX-RS/Jersey
> dependent.
>
That makes sense.
>> * Passing error messages in a consistent format that the client can
>> rely on, and
>> parse to good effect. For example, if your API offers a POST URI to
>> create a new
>> resource, but several of the input fields in the entity have
>> incorrect or missing values,
>> it would be nice to have a data structure that lets me include zero
>> or more error
>> messages, optionally tagged to the corresponding field names (so that
>> a client side
>> UI can present them nicely).
>
> I would like to return some entity to help out the client. My thought
> was to provide the entity as a class instance added to the exception
> constructor (FaultInfo). The code that detects the problem can
> populate this class to better assist the client. I would like for
> this entity to be marshaled in the same manner a proper response would
> be by JAX-RS, using @Produces/_at_Consumed to return the right format.
> Starting with something from the SOAP side of things, I defined this
> class:
>
> @XmlRootElement(name = "Error")
> @XmlType(name = "ErrorType")
> @XmlAccessorType(XmlAccessType.FIELD)
> public class FaultInfo {
>
> @XmlElement(name = "Code")
> protected ErrorCode _errorCode;
> @XmlElement(name = "Message")
> protected String _message;
> @XmlElement(name = "Resource")
> protected String _resource;
> @XmlElement(name = "DevInfo")
> protected String _devInfo;
>
> public FaultInfo() {
> }
>
> public FaultInfo(ErrorCode errorCode) {
> _errorCode = errorCode;
> _message = errorCode.getMessage();
> }
>
> public FaultInfo(ErrorCode errorCode, String resource) {
> this(errorCode);
> _resource = resource;
> }
>
> public FaultInfo(ErrorCode errorCode, String resource, String devInfo) {
> this(errorCode, resource);
> _devInfo = devInfo;
> }
>
> public ErrorCode getErrorCode() {
> return _errorCode;
> }
>
> public String getResource() {
> return _resource;
> }
> }
>
Looks like you've gone a bit further than I have in some respects. The
properties I've currently included in a Message bean (what actually gets
serialized is a collection of such beans, so you can have zero or more
messages) are:
* text -- Localized text for this message (required)
* code -- String identifier to look a particular error up in some
catalog (optional)
* field -- Name of the field this error is associated wth (optional;
default interpretation is "global error")
* stackTrace -- If error caused by an exception, a place to include a
stack trace
(although our best practices recommend *not* including this for
messages visible
to a third party client, because stack traces can reveal a lot about
your internal architecture)
* hint -- Mechanism for the server to describe potential workarounds
that might
be displayed by a client UI (optional)
> Note that devInfo is 'developer info', which provides more detail than
> what would be returned in production. I would like consistency in the
> error message, and even some kind of error code that would be easier
> to handle by the client. So, the ErroCode class is an enumeration like:
>
> @XmlEnum
> public enum ErrorCode {
>
> @XmlEnumValue("InvalidUser")
> INVALID_USER("Invalid Username/password"),
>
> @XmlEnumValue("AuthorizationError")
> AUTHORIZATION_ERROR("User not authorized to use this webservice"),
>
> @XmlEnumValue("InternalError")
> INTERNAL_ERROR("Internal Server Error - Please try again later"),
> @XmlEnumValue("InvalidSourceType")
> INVALID_SOURCE_TYPE("Invalid Source Type"),
> @XmlEnumValue("InvalidSourceId")
> INVALID_SOURCE_ID("Invalid Source Id"),
>
> @XmlEnumValue("InvalidTokenType")
> INVALID_TOKEN_TYPE("Invalid token type"),
>
> @XmlEnumValue("InvalidTokenValue")
> INVALID_TOKEN_VALUE("Invalid token value"),
>
> ;
>
> final private String _message;
>
> ErrorCode(String message) {
> _message = message;
> }
>
> public String getMessage() {
> return _message;
> }
> }
>
Is your thinking that the set of possible errors should be fixed, or
extensible? I'm of the latter opinion, and would plan on including in
the documentation some simple ideas on how to register your own custom
exception class (not needed on the server because the exception mappers
get picked up automatically; optionally needed on the client for
ErrorsHelper.asException to be generic).
> And then, in one of the service class methods, when an input
> validation fails:
>
> final TokenTypes tokenType =
> TokenTypes.getTypeFromName(tokenTypeStr);
> if (tokenType == null) {
> final String msg = String.format("tokenType of '%s' is not
> valid", tokenTypeStr);
> throw new InputValidationException(
> msg,
> new FaultInfo(ErrorCode.INVALID_TOKEN_TYPE,
> tokenTypeStr, msg));
> }
>
> So, by throwing InputValidationException, HTTP response code 400 will
> be used, and the FaultInfo entity is populated with some helpful
> information. Finally, the exception mapper is defined as:
>
> @Provider
> public class ServiceExceptionMapper implements
> ExceptionMapper<com.mycompany.ws.service.ServiceException> {
>
> public Response toResponse(ServiceException ex) {
> return Response.status(ex.getResponseStatusCode()).
> entity(ex.getFaultInfo()).
> build();
> }
> }
>
> This all does seem a bit awkward, and as you can see only a single
> fault is being reported.
My validation exception class has a property of type
Map<String,List<String>> where the key is the field name (empty string
for "global" validation errors) and the value is zero or more localized
messages related to this particular field.
>
>> Does this sound like an interesting problem to anyone else?
>
> Count me in!
>
>> The solution being developed by our team started from the notion that
>> Java developers are used to handling "exceptional" situations with
>> ... "exceptions" :-). Back in the Jersey 0.8 or so time frame,
>> JAX-RS added the concept of an ExceptionMapper, which basically acts
>> like a MessageBodyWriter when the resource method itself throws an
>> exception. So, maybe we can give the server side developer a set of
>> well defined exception classes (and corresponding exception mappers)
>> that the resource method can just throw, and let the library deal
>> with setting the correct status code and an entity containing the
>> right message infomation.
>>
>> On the server side, your resource method code might look something
>> like this:
>>
>> @GET
>> @Path("/customers/{id}")
>> @Produces({"application/json","application/xml","text/xml"})
>> public Customer find(@PathParam("id") String id) {
>> // Finder method returns null if no such customer exists
>> Customer cust = MyBusinessLogic.findCustomerById(id);
>> if (cust != null) {
>> return cust; // Use the provider for formatting this response
>> } else {
>> // NotFoundException is a RuntimeException so you do not
>> need to
>> // declare it in a throws clause, but you SHOULD javadoc it
>> throw new NotFoundException("Customer '" + id + "' does not
>> exist");
>> }
>> }
>>
>> The library would provide an exception mapper that enforces our "best
>> practice" policy to use an HTTP 404 "Not Found" status for this
>> condition, and package an appropriate message body containing the
>> message text, in a standard XML or JSON format.
>
> I'm very interested in seeing what you have in mind. I don't know if
> you'll get it into Jersey to meet my timeframe, but I am interested in
> learning about your approach and perhaps implementing something
> similar for now, and using it directly in Jersey 1.1 or whenever
> you've integrated it.
>
Cool ... I'll take a crack at this fairly soon, and we can spend some
time refining it. If you're using Maven to build, you'll be able to
declare dependencies on the snapshots if you're willing to deal with
that ... they will get published by the Hudson builds.
> I can't comment too much on the client-side of things as I've much to
> do on the server side. :)
>
I know that feeling ... it's not unlike what everyone else on my team is
feeling, but *somebody* has to take a look at the client side of the
world :-).
> Thanks!
>
Thanks for the feedback!
Craig
>>
>>
>> For extra credit, I write client SDKs for our APIs as well. Why
>> can't we make it possible for the client developer to reason in
>> exceptions just like the server side developer can? That's actually
>> pretty straightforward, if you have a client side library that knows
>> how to map HTTP error responses back into an exception again.
>>
>> Let's imagine a client side business logic class (based on
>> jersey-client) that abstracts calls to the above service with a nice
>> Java API. The code to call the server method described above might
>> look like:
>>
>> public class CustomerService {
>>
>> WebResource service = ...; // See jersey-client examples for
>> setting this up
>>
>> public Customer findCustomerById(String id) {
>> try {
>> return service.
>> path("customers/" + id).
>> accept("text/xml").
>> get(Customer.class);
>> } catch (UniformInterfaceException e) {
>> throw ErrorsHelper.asException(e.getResponse());
>> }
>> }
>>
>> }
>>
>> where ErrorsHelper.asException analyzes the HTTP status of the
>> response and synthesizes an appropriate exception (in our case,
>> turning a 404 into a NotFoundException) and grabs the relevant
>> information out of the response body (including the error message text).
>>
>> So, does this sort of thing sound like something you might use? It
>> would be pretty straightforward to generalize what I've already got
>> working to make it fit in with Jersey (that's mostly a matter of a
>> Maven based build environment plus package name changes), if it
>> sounds like this would be generally useful.
>>
>> Craig
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>> <mailto:users-unsubscribe_at_jersey.dev.java.net>
>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>> <mailto:users-help_at_jersey.dev.java.net>
>>
>
>
>
> --
>
> Jeff Schmidt
>
>
>
>
>
>