Использование Joda DateTime в качестве параметра Джерси?
Я хотел бы использовать Joda DateTime
для параметров запроса в Джерси, но это не поддерживается Джерси из коробки. Я предполагаю, что реализация InjectableProvider
- это правильный способ добавления поддержки DateTime
.
Может ли кто-нибудь указать мне хорошую реализацию InjectableProvider
для DateTime
? Или есть альтернативный подход, рекомендуемый? (Я знаю, что могу конвертировать из Date
или String
в мой код, но это похоже на меньшее решение).
Спасибо.
Решение:
Я изменил ответ Гили ниже, чтобы использовать механизм впрыска @Context
в JAX-RS, а не в Guice.
Обновление: может не работать должным образом, если UriInfo не вводится в параметры вашего метода сервиса.
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));
}
};
}
}
Ответы
Ответ 1
Вот реализация, которая зависит от Guice. Вы можете использовать другой инжектор с незначительными модификациями:
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));
}
};
}
}
Нет привязок Guice. @Provider - аннотация JAX-RS. Guice просто должен иметь возможность вводить UriInfo, а Jersey-Guice обеспечивает привязку.
Ответ 2
Еще один вариант, связанный с отправкой объектов Joda DateTime между клиентским сервером, заключается в том, чтобы маршал/де-маршал их явно с использованием адаптера и соответствующей аннотации.
Принцип состоит в том, чтобы маршалировать его как объект Long, в то время как де-маршаллинг создает экземпляр нового объекта DateTime, используя объект Long для вызова конструктора. Длинный объект получен методом getMillis.
Чтобы выполнить эту работу, укажите адаптер для использования в классах, у которых есть объект 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; }
Затем напишите адаптер и класс XML для инкапсуляции объекта 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; }
}
Если все идет по плану, объекты DateTime должны быть маршаллированы/де-маршаллированы с помощью адаптера; проверьте это, установив контрольные точки в адаптере.
Ответ 3
Отчитав документацию, оказалось, что вам нужно будет вернуть метод String, который затем превратится в DateTime, Я полагаю, используя конструктор DateTime (long), там (относительно) легко следовать пример в кодексе, дайте мне знать, если вы хотите, чтобы я пошел на него.
Ответ 4
@Gili, извините, у меня нет необходимой репутации, чтобы напрямую прокомментировать ваше сообщение, но не могли ли вы:
- добавить операторы импорта, используемые для вашей реализации?
- добавить пример того, как вы связываете все с Guice?
Спасибо вам заблаговременно.
М.
<сильные > ПРОБЛЕМЫ:
Мне было бы интересно сделать то же самое, что и HolySamosa, и я тоже использую Guice, но я сталкиваюсь с нижеперечисленными проблемами.
Если я добавлю:
bind(DateTimeInjector.class);
в моем GuiceServletContextListener
, я получаю:
java.lang.RuntimeException:
The scope of the component class com.foo.mapping.DateTimeInjector must be a singleton
и если я добавлю @Singleton
в класс DateTimeInjector
, я получаю:
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.
СОВЕТЫ/РЕШЕНИЯ:
- Обратите внимание на то, какую аннотацию вы используете (в отличие от меня)! Например, я использовал
@PathParam
вместо @QueryParam
.
- В вашем сервисе вам не нужно иметь
UriInfo uriInfo
в сигнатуре метода. Просто функциональных параметров должно быть достаточно, и он должен работать, присутствует ли UriInfo
или нет.
- Для того, чтобы убрать инжектор, нужно было сконфигурировать нижнее устройство.
Пример:
// 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);
ПОЛНОЕ РЕШЕНИЕ:
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());
}
}
};
}
}