views:

6000

answers:

5

I'm learning JAX-RS (aka, JSR-311) using Jersey. I've successfuly created a Root Resource and am playing around with parameters:

@Path("/hello")
public class HelloWorldResource {

    @GET
    @Produces("text/html")
    public String get(
     @QueryParam("name") String name,
     @QueryParam("birthDate") Date birthDate) {

         // Return a greeting with the name and age
    }
}

This works great, and handles any format in the current locale which is understood by the Date(String) constructor (like YYYY/mm/dd and mm/dd/YYYY). But if I supply a value which is invalid or not understood, I get a 404 response.

For example:

GET /hello?name=Mark&birthDate=X

404 Not Found

How can I customize this behavior? Maybe a different response code (probably "400 Bad Request")? What about logging an error? Maybe add a description of the problem ("bad date format") in a custom header to aid troubleshooting? Or return a whole Error response with details, along with a 5xx status code?

+2  A: 

One obvious solution: take in a String, convert to Date yourself. That way you can define format you want, catch exceptions and either re-throw or customize error being sent. For parsing, SimpleDateFormat should work fine.

I am sure there are ways to hook handlers for data types too, but perhaps little bit of simple code is all you need in this case.

StaxMan
+11  A: 

There are several approaches to customize the error handling behavior with JAX-RS. Here are three of the easier ways.

The first approach is to create an Exception class that extends WebApplicationException.

Example:

public class NotAuthorizedException extends WebApplicationException {
     public NotAuthorizedException(String message) {
         super(Response.status.UNAUTHORIZED).
           entity(message).type("text/plain").build());
     }
}

And to throw this newly create Exception you simply:

@Path("accounts/{accountId}/")
    public Item getItem(@PathParam("accountId") String accountId) {
       // An unauthorized user tries to enter
       throw new NotAuthorizedException("You Don't Have Permission");
}

Notice, you don't need to declare the exception in a throws clause because WebApplicationException is a runtime Exception. This will return a 401 response to the client.

The second and easier approach is to simply construct an instance of the WebApplicationException directly in your code. This approach works as long as you don't have to implement your own application Exceptions.

Example:

@Path("accounts/{accountId}/")
public Item getItem(@PathParam("accountId") String accountId) {
   // An unauthorized user tries to enter
   throw new WebApplicationException(Response.status.UNAUTHORIZED);
}

This code too returns a 401 to the client.

Of course, this is just a simple example. You can make the Exception much more complex if necessary, and you can generate what ever http response code you need to.

One other approach is to wrap an existing Exception, perhaps an ObjectNotFoundException with an small wrapper class that implements the ExceptionMapper interface annotated with a @Provider annotation. This tells the JAX-RS runtime, that if the wrapped Exception is raised, return the response code defined in the ExceptionMapper.

Steve Levine
In your example, the call to super() should be slightly different:super(Response.status(Status.UNAUTHORIZED). entity(message).type("text/plain").build());Thanks for the insight though.
Jon
I think that the method wouldn't be executed in his example.
deamon
In the scenario mentioned in the question, you will not get a chance to throw an exception, as Jersey will raise exception as it will not be able to create instance of Date object from the input value. Is there a way to intercept Jersey exception? There is one ExceptionMapper interface, however that also intercepts the exceptions thrown by the method (get in this case).
Rejeev Divakaran
+1  A: 

I too like StaxMan would probably implement that QueryParam as a String, then handle the conversion, rethrowing as necessary.

If the locale specific behavior is the desired and expected behavior, you would use the following to return the 401 BAD REQUEST error:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

See the JavaDoc for javax.ws.rs.core.Response.Status for more options.

dshaw
+3  A: 

You could also write a reusable class for QueryParam-annotated variables

public class DateParam {
  private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

  private Calendar date;

  public DateParam(String in) throws WebApplicationException {
    try {
      date = Calendar.getInstance();
      date.setTime(format.parse(string));
    }
    catch (ParseException exception) {
      throw new WebApplicationException(400);
    }
  }
  public Calendar getDate() {
    return date;
  }
  public String format() {
    return format.format(value.getTime());
  }
}

then use it like this:

private @QueryParam("from") DateParam startDateParam;
private @QueryParam("to") DateParam endDateParam;
// ...
startDateParam.getDate();

Although the error handling is trivial in this case (throwing a 400 response), using this class allows you to factor-out parameter handling in general which might include logging etc.

Charles Brooking
A: 

Ideally, you could implement an ExceptionHandler. It appears though, that this will not be invoked if the constructor of your custom @QueryParam class throws an IllegalArgumentException.

Is it true that the ExceptionHandler will only be called when an exception is thrown from the request method body?

npellow