如何将Joda DateTime对象作为HTTP POST的消息体传递给Jersey REST端点?

时间:2014-10-24 21:44:36

标签: json jaxb jersey jodatime moxy

我想将一个org.joda.time.DateTime作为json POST的消息体传递给Jersey端点。我的泽西使用MOXy。我已经创建了一个自定义XmlAdapter来实现这一点,但我不清楚如何连接这个适配器。我发现自定义适配器的示例在要发布的对象上使用注释,我不能这样做(我不能在DateTime类上放置注释,因为我无法操纵其源代码)。

我正在使用Jersey测试框架来测试它。

我的泽西终点:

@POST
@Path("/{memberId}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public ResultBean recordDate(@PathParam("memberId") Long memberId, DateTime dateTime) {
    // TODO stuff happens
    return new ResultBean(memberId, dateTime);
}

我的泽西测试:

public class FooEndpointImplTest extends JerseyTest {

@Test
public void testWithDate() {
    Long memberId = 1L;
        DateTime date = new DateTime();
        Entity<DateTime> dateEntity = Entity.json(date);
        ResultBean result = target(
                "/" + memberId)
                .request().post(dateEntity, ResultBean.class);
        assertNotNull(result);
    }
}

我的适配器:

package foo;

import javax.xml.bind.annotation.adapters.XmlAdapter;

import org.joda.time.DateTime;

public class DateTimeAdapter extends XmlAdapter<String, DateTime> {

    @Override
    public DateTime unmarshal(String v) throws Exception {
        Long millis = Long.parseLong(v);
        return new DateTime(millis);
    }

    @Override
    public String marshal(DateTime v) throws Exception {
        return Long.toString(v.getMillis());
    }

}

当我按原样运行此测试时,我收到500错误。

2 个答案:

答案 0 :(得分:1)

首先要做的是:joda DateTime需要MessageBodyReader/Writer

如果没有一些调整,看起来像JSON序列化的DateTime的POST将无法工作。 Gson序列化的DateTime将如下所示:

{ "iMillis": 1414507195233,
  "iChronology": {
    "iBase": {
        "iBase": {
            ...
        },
        "iParam": {
            ...
        }}}}

如果您尝试使用Gson再次反序列化,Gson将失败,因为org.joda.time.Chronology - &gt; iChronology没有 default-non-arg构造函数,Gson需要对该对象进行反序列化。 Afaig,在使用任何标准反序列化器反序列化DateTime时,您将遇到此类问题。

所以我最终根据FasterXML/jackson-datatype-joda创建了MessageBodyReader / Writer

<强>阅读器:

// ...
import org.joda.time.DateTime;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;

@Consumes(MediaType.APPLICATION_JSON)
public class JodaTimeBodyReader implements MessageBodyReader<DateTime> {

    private static ObjectMapper mapper = new ObjectMapper();

    public JodaTimeBodyReader() {
        mapper.registerModule( new JodaModule());
    }


    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return type == DateTime.class;
    }

    @Override
    public DateTime readFrom(Class<DateTime> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
        try {
            return mapper.readValue(entityStream, DateTime.class);
        } catch (Exception e) {
            throw new ProcessingException("Error deserializing a org.joda.time.DateTime.", e);
        }
    }

}

<强>编剧:

// ...
import org.joda.time.DateTime;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;

@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class JodaTimeBodyWriter implements MessageBodyWriter<DateTime> {

    private static ObjectMapper mapper = new ObjectMapper();

    public JodaTimeBodyWriter() {
        mapper.registerModule( new JodaModule());
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return type == DateTime.class;
    }

    @Override
    public long getSize(DateTime t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        // deprecated by JAX-RS 2.0 and ignored by Jersey runtime
        return 0;
    }

    @Override
    public void writeTo(DateTime t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
        try {
            entityStream.write( mapper.writeValueAsBytes(t));
        } catch (Exception e) {
            throw new ProcessingException("Error serializing a org.joda.time.DateTime to the output stream", e);
        }
    }

}

两者均在ResourceConfig中注册。

Writer将以1414507195233(yap,而非JSON)回复,如果您将此回发到您的资源,您将获得有效的DateTime。

如果您像上面的示例一样获得JSON,您现在可以升级Reader以解析iMillis的JSON并使用Long值。对于TimeZone,您也可以使用dateTime.withZone(...)来设置值。

我使用的Maven依赖项:

<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.5</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-joda</artifactId>
    <version>2.4.0</version>
</dependency>

泽西岛2.12

希望这在某种程度上有所帮助。

答案 1 :(得分:1)

而不是:

@POST
@Path("/{memberId}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public ResultBean recordDate(@PathParam("memberId") Long memberId, DateTime dateTime) {
    // TODO stuff happens
    return new ResultBean(memberId, dateTime);
}

你可以这样做:

@POST
@Path("/{memberId}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public ResultBean recordDate(@PathParam("memberId") Long memberId, DateTimeWrapper dateTimeWrapper) {
    // TODO stuff happens
    return new ResultBean(memberId, dateTime);
}

DateTimeWrapper的样子。由于DateTime对象现在是属性而非顶级,XmlAdapter将适用。

@XmlAccessorType(XmlAccessType.FIELD)
public class DateTimeWrapper {

    @XmlValue
    @XmlJavaTypeAdapter(YourDateTimeAdapter.class)
    private DateTime value;

}