Подтвердить что ты не робот

Использование 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));
            }
        };
    }
}
4b9b3361

Ответ 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());
                }
            }

        };
    }
}