2009-02-24 5 views
199

Estoy aprendiendo JAX-RS (también conocido como JSR-311) usando Jersey. exitosamente He creado un recurso de raíz y estoy jugando con los parámetros:JAX-RS/Jersey ¿cómo personalizar el manejo de errores?

@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 
    } 
} 

Esto funciona muy bien, y se ocupa de cualquier formato en la localidad actual que es entendido por la fecha (String) constructor (como AAAA/mm/dd y mm/dd/YYYY). Pero si proporciono un valor que no es válido o no lo entiendo, recibo una respuesta 404.

Por ejemplo:

GET /hello?name=Mark&birthDate=X 

404 Not Found 

¿Cómo puedo personalizar este comportamiento? Tal vez un código de respuesta diferente (probablemente "400 Bad Request")? ¿Qué hay de registrar un error? ¿Tal vez añadir una descripción del problema ("formato de fecha incorrecta") en un encabezado personalizado para ayudar a solucionar problemas? ¿O devolver una respuesta de error completa con detalles, junto con un código de estado 5xx?

Respuesta

253

Hay varios enfoques para personalizar el comportamiento de manejo de errores con JAX-RS. Aquí hay tres de las maneras más fáciles.

El primer enfoque es crear una clase de excepción que extienda WebApplicationException.

Ejemplo:

public class NotAuthorizedException extends WebApplicationException { 
    public NotAuthorizedException(String message) { 
     super(Response.status(Response.Status.UNAUTHORIZED) 
      .entity(message).type(MediaType.TEXT_PLAIN).build()); 
    } 
} 

Y para tirar esto recién crear Excepción simplemente:

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

Aviso, no es necesario declarar la excepción de una cláusula throws porque WebApplicationException es un tiempo de ejecución Excepción. Esto devolverá una respuesta 401 al cliente.

El segundo y más fácil es simplemente construir una instancia de WebApplicationException directamente en su código. Este enfoque funciona siempre que no tenga que implementar sus propias excepciones de aplicaciones.

Ejemplo:

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

Este código también devuelve un 401 al cliente.

Por supuesto, esto es solo un ejemplo simple. Puede hacer que la excepción sea mucho más compleja si es necesario, y puede generar el código de respuesta http que necesite.

Otro enfoque es envolver una Excepción existente, tal vez una ObjectNotFoundException con una clase contenedora pequeña que implemente la interfaz ExceptionMapper anotada con una anotación @Provider. Esto le dice al tiempo de ejecución de JAX-RS, que si se levanta la Excepción envuelta, devuelva el código de respuesta definido en el ExceptionMapper.

+3

En su ejemplo, la llamada a super() debe ser ligeramente diferente:.. super (Response.Status (Status.UNAUTHORIZED) entidad (mensaje) .type ("text/plain") construir()); Gracias por la comprensión sin embargo. –

+0

Creo que el método no se ejecutará en su ejemplo. – deamon

+61

En el escenario mencionado en la pregunta, no tendrá la oportunidad de lanzar una excepción, ya que Jersey levantará una excepción ya que no podrá crear una instancia del objeto Date desde el valor de entrada. ¿Hay alguna manera de interceptar la excepción de Jersey? Hay una interfaz ExceptionMapper, pero que también intercepta las excepciones arrojadas por el método (en este caso, consiga). –

11

Una solución obvia: tomar en una cadena, convertir a la fecha usted mismo. De esta forma, puede definir el formato que desee, detectar excepciones y volver a lanzar o personalizar el error que se envía. Para el análisis, SimpleDateFormat debería funcionar bien.

Estoy seguro de que también hay formas de enganchar los manejadores para los tipos de datos, pero tal vez un código simple sea todo lo que necesita en este caso.

5

Yo también como StaxMan probablemente implementaría QueryParam como una Cadena, luego manejaría la conversión, reiniciando según sea necesario.

Si el comportamiento específico de la configuración regional es el comportamiento deseado y esperado, se utiliza el siguiente para devolver el error 400 Bad Request:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

consulte el Javadoc de javax.ws.rs.core.Response.Status para más opciones.

26

También puede escribir una clase reutilizable para las variables anotadas-QueryParam

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

    private Calendar date; 

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

entonces utilizar de esta manera:

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

Aunque la gestión de errores es trivial en este caso (devolver una respuesta 400), el uso de esta clase le permite factorizar el manejo de parámetros en general, lo que puede incluir el registro, etc.

+0

Estoy tratando de agregar un manejador de param de consulta personalizado en Jersey (migrando desde CXF) esto se ve notablemente similar a lo que estoy haciendo, pero no sé cómo instalar/crear un nuevo proveedor. Tu clase anterior no me muestra esto. Estoy usando objetos JodaTime DateTime para QueryParam y no tengo un proveedor para decodificarlos. ¿Es tan fácil como subclasificarlo, dándole un constructor de cadenas y manejando eso? –

+1

Simplemente crea una clase como la anterior 'DateParam' que envuelve un 'org.joda.time.DateTime' en lugar de' java.util.Calendar'. Lo usa con '@ QueryParam' en lugar de' DateTime' en sí. –

+1

Si usa Joda DateTime, Jersey viene con DateTimeParam para que lo use directamente. No es necesario que escribas el tuyo. Ver https://github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params/DateTimeParam.java – Srikanth

63
@Provider 
public class BadURIExceptionMapper implements ExceptionMapper<NotFoundException> { 

public Response toResponse(NotFoundException exception){ 

    return Response.status(Response.Status.NOT_FOUND). 
    entity(new ErrorResponse(exception.getClass().toString(), 
       exception.getMessage())). 
    build(); 
} 
} 

Cree la clase superior. Esto manejará 404 (NotFoundException) y aquí en el método toResponse puede dar su respuesta personalizada. Del mismo modo, hay ParamException, etc. que necesitaría asignar para proporcionar respuestas personalizadas.

+0

Puede usar implements ExceptionMapper así como excepciones genéricas – userRaj

+0

Esto manejaría WebApplicationExceptions lanzadas por JAX-RS Client también, ocultando el origen del error. Mejor tener una excepción personalizada (no derivada de WebApplicationException) o lanzar aplicaciones web con respuesta completa. WebApplication Las excepciones lanzadas por JAX-RS Client deben manejarse directamente en la llamada, de lo contrario la respuesta de otro servicio se transfiere como respuesta de su servicio a pesar de que se trata de un error de servidor interno no controlado. –

33

Jersey lanza una com.sun.jersey.api.ParamException cuando falla en Resolver referencia los parámetros para una solución es crear un ExceptionMapper que maneja estos tipos de excepciones:

@Provider 
public class ParamExceptionMapper implements ExceptionMapper<ParamException> { 
    @Override 
    public Response toResponse(ParamException exception) { 
     return Response.status(Status.BAD_REQUEST).entity(exception.getParameterName() + " incorrect type").build(); 
    } 
} 
+0

¿dónde debería crear este asignador específicamente para que Jersey lo registre? – Patricio

+1

Todo lo que tienes que hacer es agregar la anotación @Provider, mira aquí para más detalles: http://stackoverflow.com/questions/15185299/jax-rs-jersey-exceptionmappers-user-defined-exception –

4

documentación @QueryParam dice

" The type T of the annotated parameter, field or property must either:

1) Be a primitive type
2) Have a constructor that accepts a single String argument
3) Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String))
4) Have a registered implementation of javax.ws.rs.ext.ParamConverterProvider JAX-RS extension SPI that returns a javax.ws.rs.ext.ParamConverter instance capable of a "from string" conversion for the type.
5) Be List, Set or SortedSet, where T satisfies 2, 3 or 4 above. The resulting collection is read-only. "

Si desea controlar qué respuesta va al usuario cuando el parámetro de consulta en forma de cadena no se puede convertir a su tipo T, puede lanzar WebApplicationException. Dropwizard viene con las siguientes clases * Param que puedes usar para tus necesidades.

BooleanParam, DateTimeParam, IntParam, LongParam, LocalDateParam, NonEmptyStringParam, UUIDParam. Ver https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params

Si necesita Joda DateTime, simplemente use Dropwizard DateTimeParam.

Si la lista anterior no se ajusta a sus necesidades, defina la suya extendiendo AbstractParam. Anular el método de análisis Si necesita control sobre el cuerpo de la respuesta de error, anule el método de error.

Buen artículo de Coda en este Hale está en http://codahale.com/what-makes-jersey-interesting-parameter-classes/

import io.dropwizard.jersey.params.AbstractParam; 

import java.util.Date; 

import javax.ws.rs.core.Response; 
import javax.ws.rs.core.Response.Status; 

public class DateParam extends AbstractParam<Date> { 

    public DateParam(String input) { 
     super(input); 
    } 

    @Override 
    protected Date parse(String input) throws Exception { 
     return new Date(input); 
    } 

    @Override 
    protected Response error(String input, Exception e) { 
     // customize response body if you like here by specifying entity 
     return Response.status(Status.BAD_REQUEST).build(); 
    } 
} 

Fecha (String arg) constructor está en desuso. Usaría las clases de fecha Java 8 si está en Java 8. De lo contrario, se recomienda la fecha joda.

1

Este es el comportamiento correcto en realidad. Jersey intentará encontrar un controlador para su entrada e intentará construir un objeto a partir de la información provista. En este caso, intentará crear un nuevo objeto Date con el valor X proporcionado al constructor. Dado que esta es una fecha no válida, por convención Jersey devolverá 404.

Lo que puede hacer es reescribir y poner la fecha de nacimiento como una cadena, luego intente analizar y si no obtiene lo que quiere, está libre de lanzar cualquier excepción que desee mediante cualquiera de los mecanismos de mapeo de excepciones (hay varios).

-5
abtrack class Responce 
{ 
private String message ; 
private int code ; 

public String getMessage(){ 
return this.message ; 
} 
public void setMessage(String message){ 
this.message =message ; 
} 


public String getCode(){ 
return this.code ; 
} 
public void setCode(String code){ 
this.code =code ; 
} 

} 

@XmlRootElement(name='MyResponce') 
class MyResponce extends Responce { 

} 

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

    @GET 
    @Produces("text/html") 
    public MyResponce get(
     MyResponce myResponce = new MyResponce(); 
     @QueryParam("name") String name, 
     @QueryParam("birthDate") Date birthDate) throw WSException { 
      try { 
}catch(Exception) 
myResponce.setCode(400); 
myResponce.setMessage("Exception") 
    } 
return myResponce ; 

}