Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Joda DateTime as a Jersey parameter?

I'd like to use Joda's DateTime for query parameters in Jersey, but this isn't supported by Jersey out-of-the-box. I'm assuming that implementing an InjectableProvider is the proper way to add DateTime support.

Can someone point me to a good implementation of an InjectableProvider for DateTime? Or is there an alternative approach worth recommending? (I'm aware I can convert from Date or String in my code, but this seems like a lesser solution).

Thanks.

Solution:

I modified Gili's answer below to use the @Context injection mechanism in JAX-RS rather than Guice.

Update: This may not work properly if UriInfo isn't injected in your service method parameters.

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));
            }
        };
    }
}
like image 335
HolySamosa Avatar asked Nov 20 '12 21:11

HolySamosa


2 Answers

Here is an implementation that depends on Guice. You can using a different injector with minor modifications:

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));
            }
        };
    }
}

There are no Guice bindings. @Provider is a JAX-RS annotation. Guice just needs to be able to inject UriInfo and Jersey-Guice provides that binding.

like image 137
Gili Avatar answered Oct 06 '22 19:10

Gili


Another option to deal with the sending of Joda DateTime objects between the client-server is to marshal/de-marshal them explicitly using an adapter and an according annotation. The principle is to marshal it as Long object while de-marshalling instantiates a new DateTime object using the Long object for the constructor call. The Long object is obtained via the getMillis method. To have this work, specify the adapter to use in the classes that have a DateTime object:

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

Then write the adapter and the XML class to encapsulate the Long object:

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

}

If all goes to plan, the DateTime objects should be marshalled/de-marshalled by using the adaptor; check this by setting breakpoints in the adapter.

like image 26
user2336150 Avatar answered Oct 06 '22 17:10

user2336150