java - examples - Usando Joda DateTime como un parámetro de Jersey?
jodatime java 8 (4)
Me gustaría utilizar el DateTime
de Joda para los parámetros de consulta en Jersey, pero esto no es compatible con los modelos de DateTime
de Jersey. Supongo que la implementación de un InjectableProvider
es la forma adecuada de agregar compatibilidad con DateTime
.
¿Alguien puede indicarme una buena implementación de un InjectableProvider
para DateTime
? ¿O hay un enfoque alternativo que valga la pena recomendar? (Soy consciente de que puedo convertir desde Date
o String
en mi código, pero esto parece una solución menor).
Gracias.
Solución:
@Context
la respuesta de Gili a continuación para usar el mecanismo de inyección de @Context
en JAX-RS en lugar de Guice.
Actualización: Es posible que esto no funcione correctamente si UriInfo no se incluye en los parámetros de su método de servicio.
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
import java.util.List;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;
import org.joda.time.DateTime;
/**
* Enables DateTime to be used as a QueryParam.
* <p/>
* @author Gili Tzabari
*/
@Provider
public class DateTimeInjector extends PerRequestTypeInjectableProvider<QueryParam, DateTime>
{
private final UriInfo uriInfo;
/**
* Creates a new DateTimeInjector.
* <p/>
* @param uriInfo an instance of {@link UriInfo}
*/
public DateTimeInjector( @Context UriInfo uriInfo)
{
super(DateTime.class);
this.uriInfo = uriInfo;
}
@Override
public Injectable<DateTime> getInjectable(final ComponentContext cc, final QueryParam a)
{
return new Injectable<DateTime>()
{
@Override
public DateTime getValue()
{
final List<String> values = uriInfo.getQueryParameters().get(a.value());
if( values == null || values.isEmpty())
return null;
if (values.size() > 1)
{
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).
entity(a.value() + " may only contain a single value").build());
}
return new DateTime(values.get(0));
}
};
}
}
Al leer la documentación , parece que necesitará que su método devuelva un String, que luego convertirá en DateTime, supongo que usando el constructor DateTime (largo) , hay un ejemplo (relativamente) fácil de seguir en Codehale , avíseme si desea que lo intente .
Aquí hay una implementación que depende de Guice. Puede usar un inyector diferente con modificaciones menores:
import com.google.inject.Inject;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
import java.util.List;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;
import org.joda.time.DateTime;
/**
* Enables DateTime to be used as a QueryParam.
* <p/>
* @author Gili Tzabari
*/
@Provider
public class DateTimeInjector extends PerRequestTypeInjectableProvider<QueryParam, DateTime>
{
private final com.google.inject.Provider<UriInfo> uriInfo;
/**
* Creates a new DateTimeInjector.
* <p/>
* @param uriInfo an instance of {@link UriInfo}
*/
@Inject
public DateTimeInjector(com.google.inject.Provider<UriInfo> uriInfo)
{
super(DateTime.class);
this.uriInfo = uriInfo;
}
@Override
public Injectable<DateTime> getInjectable(final ComponentContext cc, final QueryParam a)
{
return new Injectable<DateTime>()
{
@Override
public DateTime getValue()
{
final List<String> values = uriInfo.get().getQueryParameters().get(a.value());
if (values.size() > 1)
{
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).
entity(a.value() + " may only contain a single value").build());
}
if (values.isEmpty())
return null;
return new DateTime(values.get(0));
}
};
}
}
No hay enlaces de Guice. @Provider es una anotación JAX-RS. Guice solo necesita poder inyectar UriInfo y Jersey-Guice proporciona ese enlace.
@ Gili, lo siento, no tengo la reputación requerida para comentar directamente tu publicación, pero podrías por favor:
- agregar las declaraciones de importación utilizadas para su implementación?
- agrega un ejemplo de cómo enlazar todo con Guice?
Muchas gracias por adelantado.
METRO.
PROBLEMAS :
Me interesaría hacer lo mismo que HolySamosa, y también uso Guice, pero enfrento los siguientes problemas.
Si agrego:
bind(DateTimeInjector.class);
en mi GuiceServletContextListener
, obtengo:
java.lang.RuntimeException:
The scope of the component class com.foo.mapping.DateTimeInjector must be a singleton
y si agrego @Singleton
en la clase DateTimeInjector
, obtengo:
GRAVE: The following errors and warnings have been detected with resource and/or provider classes:
SEVERE: Missing dependency for method public java.util.List com.foo.ThingService.getThingByIdAndDate(java.lang.String,org.joda.time.DateTime) at parameter at index 1
SEVERE: Method, public java.util.List com.foo.ThingService.getThingByIdAndDate(java.lang.String,org.joda.time.DateTime), annotated with GET of resource, class com.foo.ThingService, is not recognized as valid resource method.
CONSEJOS / SOLUCIONES :
- ¡Presta atención a la anotación que usas (a diferencia de mí)! Por ejemplo, en realidad estaba usando
@PathParam
lugar de@QueryParam
. - En su servicio, no necesita tener
UriInfo uriInfo
en la firma del método. Solo los parámetros funcionales deberían ser suficientes y debería funcionar siUriInfo
está presente o no. - Se necesita configurar Guice con el siguiente para poder recoger el inyector.
Ejemplo:
// Configure Jersey with Guice:
Map<String, String> settings = new HashMap<String, String>();
settings.put(PackagesResourceConfig.PROPERTY_PACKAGES, "com.foo.mapping");
serve("/*").with(GuiceContainer.class, settings);
SOLUCIÓN COMPLETA :
import java.util.List;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;
import org.joda.time.DateTime;
import com.google.inject.Inject;
import com.foo.utils.DateTimeAdapter;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
/**
* Enables DateTime to be used as a PathParam.
*/
@Provider
public class DateTimeInjector extends PerRequestTypeInjectableProvider<PathParam, DateTime> {
private final com.google.inject.Provider<UriInfo> uriInfo;
/**
* Creates a new DateTimeInjector.
*
* @param uriInfo
* an instance of {@link UriInfo}
*/
@Inject
public DateTimeInjector(com.google.inject.Provider<UriInfo> uriInfo) {
super(DateTime.class);
this.uriInfo = uriInfo;
}
@Override
public Injectable<DateTime> getInjectable(final ComponentContext context, final PathParam annotation) {
return new Injectable<DateTime>() {
@Override
public DateTime getValue() {
final List<String> values = uriInfo.get().getPathParameters().get(annotation.value());
if (values == null) {
throwInternalServerError(annotation);
}
if (values.size() > 1) {
throwBadRequestTooManyValues(annotation);
}
if (values.isEmpty()) {
throwBadRequestMissingValue(annotation);
}
return parseDate(annotation, values);
}
private void throwInternalServerError(final PathParam annotation) {
String errorMessage = String.format("Failed to extract parameter [%s] using [%s]. This is likely to be an implementation error.",
annotation.value(), annotation.annotationType().getName());
throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(errorMessage).build());
}
private void throwBadRequestTooManyValues(final PathParam annotation) {
String errorMessage = String.format("Parameter [%s] must only contain one single value.", annotation.value());
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(errorMessage).build());
}
private void throwBadRequestMissingValue(final PathParam annotation) {
String errorMessage = String.format("Parameter [%s] must be provided.", annotation.value());
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(errorMessage).build());
}
private DateTime parseDate(final PathParam annotation, final List<String> values) {
try {
return DateTimeAdapter.parse(values.get(0));
} catch (Exception e) {
String errorMessage = String.format("Parameter [%s] is formatted incorrectly: %s", annotation.value(), e.getMessage());
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(errorMessage).build());
}
}
};
}
}
Otra opción para tratar el envío de objetos Joda DateTime entre el cliente-servidor es ordenarlos / descalificarlos explícitamente usando un adaptador y una anotación de acuerdo. El principio es reunirlo como objeto Largo mientras desmantela la instancia de un nuevo objeto DateTime utilizando el objeto Long para la llamada del constructor. El objeto Long se obtiene a través del método getMillis. Para tener este trabajo, especifique el adaptador para usar en las clases que tienen un objeto DateTime:
@XmlElement(name="capture_date")
@XmlJavaTypeAdapter(XmlDateTimeAdapter.class)
public DateTime getCaptureDate() { return this.capture_date; }
public void setCaptureDate(DateTime capture_date) { this.capture_date = capture_date; }
Luego escriba el adaptador y la clase XML para encapsular el objeto Long:
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
/**
* Convert between joda datetime and XML-serialisable millis represented as long
*/
public class XmlDateTimeAdapter extends XmlAdapter<XmlDateTime, DateTime> {
@Override
public XmlDateTime marshal(DateTime v) throws Exception {
if(v != null)
return new XmlDateTime(v.getMillis());
else
return new XmlDateTime(0);
}
@Override
public DateTime unmarshal(XmlDateTime v) throws Exception {
return new DateTime(v.millis, DateTimeZone.UTC);
}
}
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* XML-serialisable wrapper for joda datetime values.
*/
@XmlRootElement(name="joda_datetime")
public class XmlDateTime {
@XmlElement(name="millis") public long millis;
public XmlDateTime() {};
public XmlDateTime(long millis) { this.millis = millis; }
}
Si todo va según lo planeado, los objetos DateTime deben organizarse / desmantelarse usando el adaptador; compruebe esto estableciendo puntos de interrupción en el adaptador.