diff --git a/CHANGELOG.md b/CHANGELOG.md index b50c9fda..54c41f87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### Version 8.8 +* Adds jackson-jaxb codec + ### Version 8.7 * Bumps dependency versions for integrations * OkHttp/MockWebServer 2.4.0 diff --git a/jackson-jaxb/README.md b/jackson-jaxb/README.md new file mode 100644 index 00000000..a9bdbf4c --- /dev/null +++ b/jackson-jaxb/README.md @@ -0,0 +1,27 @@ +Jackson-Jaxb Codec +=================== + +This module adds support for encoding and decoding JSON via JAXB. + +Add `JacksonJaxbJsonEncoder` and/or `JacksonJaxbJsonDecoder` to your `Feign.Builder` like so: + +```java +GitHub github = Feign.builder() + .encoder(new JacksonJaxbJsonEncoder()) + .decoder(new JacksonJaxbJsonDecoder()) + .target(GitHub.class, "https://api.github.com"); +``` + +If you want to customize the `ObjectMapper` that is used, provide it to the `JacksonJaxbJsonEncoder` and `JacksonJaxbJsonDecoder`: + +```java +ObjectMapper mapper = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .configure(SerializationFeature.INDENT_OUTPUT, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + +GitHub github = Feign.builder() + .encoder(new JacksonJaxbJsonEncoder(mapper)) + .decoder(new JacksonJaxbJsonDecoder(mapper)) + .target(GitHub.class, "https://api.github.com"); +``` diff --git a/jackson-jaxb/build.gradle b/jackson-jaxb/build.gradle new file mode 100644 index 00000000..4372c168 --- /dev/null +++ b/jackson-jaxb/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'java' + +sourceCompatibility = 1.6 + +dependencies { + compile project(':feign-core') + compile 'javax.ws.rs:jsr311-api:1.1.1' + compile 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.6.1' + testRuntime 'com.sun.jersey:jersey-client:1.19' // for RuntimeDelegateImpl + testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 + testCompile project(':feign-core').sourceSets.test.output // for assertions +} \ No newline at end of file diff --git a/jackson-jaxb/src/main/java/feign/jackson/jaxb/JacksonJaxbJsonDecoder.java b/jackson-jaxb/src/main/java/feign/jackson/jaxb/JacksonJaxbJsonDecoder.java new file mode 100644 index 00000000..52bdf39d --- /dev/null +++ b/jackson-jaxb/src/main/java/feign/jackson/jaxb/JacksonJaxbJsonDecoder.java @@ -0,0 +1,31 @@ +package feign.jackson.jaxb; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; + +import java.io.IOException; +import java.lang.reflect.Type; + +import feign.FeignException; +import feign.Response; +import feign.codec.Decoder; + +import static com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; + +public final class JacksonJaxbJsonDecoder implements Decoder { + private final JacksonJaxbJsonProvider jacksonJaxbJsonProvider; + + public JacksonJaxbJsonDecoder() { + this.jacksonJaxbJsonProvider = new JacksonJaxbJsonProvider(); + } + + public JacksonJaxbJsonDecoder(ObjectMapper objectMapper) { + this.jacksonJaxbJsonProvider = new JacksonJaxbJsonProvider(objectMapper, DEFAULT_ANNOTATIONS); + } + + @Override + public Object decode(Response response, Type type) throws IOException, FeignException { + return jacksonJaxbJsonProvider.readFrom(Object.class, type, null, APPLICATION_JSON_TYPE, null, response.body().asInputStream()); + } +} diff --git a/jackson-jaxb/src/main/java/feign/jackson/jaxb/JacksonJaxbJsonEncoder.java b/jackson-jaxb/src/main/java/feign/jackson/jaxb/JacksonJaxbJsonEncoder.java new file mode 100644 index 00000000..36ef8f86 --- /dev/null +++ b/jackson-jaxb/src/main/java/feign/jackson/jaxb/JacksonJaxbJsonEncoder.java @@ -0,0 +1,40 @@ +package feign.jackson.jaxb; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.Charset; + +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; + +import static com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; + +public final class JacksonJaxbJsonEncoder implements Encoder { + private final JacksonJaxbJsonProvider jacksonJaxbJsonProvider; + + public JacksonJaxbJsonEncoder() { + this.jacksonJaxbJsonProvider = new JacksonJaxbJsonProvider(); + } + + public JacksonJaxbJsonEncoder(ObjectMapper objectMapper) { + this.jacksonJaxbJsonProvider = new JacksonJaxbJsonProvider(objectMapper, DEFAULT_ANNOTATIONS); + } + + + @Override + public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + jacksonJaxbJsonProvider.writeTo(object, bodyType.getClass(), null, null, APPLICATION_JSON_TYPE, null, outputStream); + template.body(outputStream.toByteArray(), Charset.defaultCharset()); + } catch (IOException e) { + throw new EncodeException(e.getMessage(), e); + } + } +} diff --git a/jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java b/jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java new file mode 100644 index 00000000..282f638a --- /dev/null +++ b/jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java @@ -0,0 +1,69 @@ +package feign.jackson.jaxb; + +import org.junit.Test; + +import java.util.Collection; +import java.util.Collections; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import feign.RequestTemplate; +import feign.Response; + +import static feign.Util.UTF_8; +import static feign.assertj.FeignAssertions.assertThat; + +public class JacksonJaxbCodecTest { + + @Test + public void encodeTest() { + JacksonJaxbJsonEncoder encoder = new JacksonJaxbJsonEncoder(); + RequestTemplate template = new RequestTemplate(); + + encoder.encode(new MockObject("Test"), MockObject.class, template); + + assertThat(template).hasBody("{\"value\":\"Test\"}"); + } + + @Test + public void decodeTest() throws Exception { + Response response = + Response.create(200, "OK", Collections.>emptyMap(), "{\"value\":\"Test\"}", UTF_8); + JacksonJaxbJsonDecoder decoder = new JacksonJaxbJsonDecoder(); + + assertThat(decoder.decode(response, MockObject.class)) + .isEqualTo(new MockObject("Test")); + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + static class MockObject { + + @XmlElement + private String value; + + MockObject() { + } + + MockObject(String value) { + this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof MockObject) { + MockObject other = (MockObject) obj; + return value.equals(other.value); + } + return false; + } + + @Override + public int hashCode() { + return value != null ? value.hashCode() : 0; + } + } +} diff --git a/jackson/build.gradle b/jackson/build.gradle index b4f2b53e..e660a120 100644 --- a/jackson/build.gradle +++ b/jackson/build.gradle @@ -4,7 +4,7 @@ sourceCompatibility = 1.6 dependencies { compile project(':feign-core') - compile 'com.fasterxml.jackson.core:jackson-databind:2.6.0' + compile 'com.fasterxml.jackson.core:jackson-databind:2.6.1' testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 testCompile project(':feign-core').sourceSets.test.output // for assertions diff --git a/settings.gradle b/settings.gradle index ecf20616..ef27845c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ rootProject.name='feign' -include 'core', 'sax', 'gson', 'httpclient', 'jackson', 'jaxb', 'jaxrs', 'okhttp', 'ribbon', 'slf4j' +include 'core', 'sax', 'gson', 'httpclient', 'jackson', 'jaxb', 'jaxrs', 'okhttp', 'ribbon', 'slf4j', 'jackson-jaxb' rootProject.children.each { childProject -> childProject.name = 'feign-' + childProject.name } +