Hello again:
It sounds like we're thinking more alike than I had originally
guessed. Please see comments below.
Cheers,
Jeff
On Oct 23, 2008, at 10:35 PM, Craig McClanahan wrote:
> 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 :-).
Yep. We must move to another form of authentication, and figure out
how to monetize a stateless API. :)
>> 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.
Oh someday I do want to spend more time with Ruby and Rails. But I
keep getting Java work. :):)
>> 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.
Great, that sure meets my requirements.
>> 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?
I'm sure you saw that further down below. :)
>> 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.
Maybe in some respects, but it is not very well thought out and I'm
trying to meet requirements in a short timeframe, so generality is not
a big priority. :)
> 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")
Does the field refer to an input parameter (@PathParam, @QueryParam)
as well as some XML element or attribute for any uploaded entity? I'm
not sure how general you can make this.
> * 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)
I agree. Being able to switch this on for development mode would be
very helpful. But, in production, this stuff must not get out.
> * hint -- Mechanism for the server to describe potential workarounds
> that might
> be displayed by a client UI (optional)
Do you see that as text to be more or less directly displayed by the
UI. Would it be localized too?
>> 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).
I think ErrorCode makes this nice and neat in some respects, such as
standard codes and messages that can be referred to precisely in the
code throwing the exceptions (much like the HTTP response code). But,
having to define a new enum value to handle a new error does kind of
suck. And, the message text is not l localized etc.
>> 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.
That sounds more generally useful then what I've provided above. :)
>>>
>>> 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.
Great! Our project is still based on Ant, no Maven yet. I guess I can
use Maven to build your snapshots and then use the resulting jars in
our Ant build.
>> 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 :-).
I'm glad you're looking at it. :) We do have to come up with some
example client code, so I will be concerned with this shortly myself. :)
>> Thanks!
>>
> Thanks for the feedback!
No problem. Thanks for doing what you're doing. :)
> 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
>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>
>>
>>
>> --
>> Jeff Schmidt
>>
--
Jeff Schmidt