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));
}
};
}
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With