Skip to content

Commit

Permalink
Allow decoding of parameterizedTypes (generics) Fixes #759 (#758)
Browse files Browse the repository at this point in the history
* Allow decoding of parameterizedTypes (generics)

* Allow decoding of parameterizedTypes (generics)
  • Loading branch information
RicardoRdzG authored and kdavisk6 committed Aug 12, 2018
1 parent 141560b commit e4743d4
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 17 deletions.
13 changes: 9 additions & 4 deletions jaxb/src/main/java/feign/jaxb/JAXBDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.io.IOException;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.ParserConfigurationException;
Expand All @@ -36,13 +37,13 @@
*
* <pre>
* JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
* .withMarshallerJAXBEncoding("UTF-8")
* .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
* .withMarshallerJAXBEncoding(&quot;UTF-8&quot;)
* .withMarshallerSchemaLocation(&quot;http://apihost http://apihost/schema.xsd&quot;)
* .build();
*
*
* api = Feign.builder()
* .decoder(new JAXBDecoder(jaxbFactory))
* .target(MyApi.class, "http://api");
* .target(MyApi.class, &quot;http://api&quot;);
* </pre>
* <p>
* The JAXBContextFactory should be reused across requests as it caches the created JAXB contexts.
Expand All @@ -69,6 +70,10 @@ public Object decode(Response response, Type type) throws IOException {
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
while (type instanceof ParameterizedType) {
ParameterizedType ptype = (ParameterizedType) type;
type = ptype.getRawType();
}
if (!(type instanceof Class)) {
throw new UnsupportedOperationException(
"JAXB only supports decoding raw types. Found " + type);
Expand Down
66 changes: 53 additions & 13 deletions jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ public void encodesXml() throws Exception {
new JAXBEncoder(new JAXBContextFactory.Builder().build())
.encode(mock, MockObject.class, template);

assertThat(template).hasBody(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><mockObject><value>Test</value></mockObject>");
assertThat(template)
.hasBody(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><mockObject><value>Test</value></mockObject>");
}

@Test
Expand Down Expand Up @@ -101,10 +102,8 @@ public void encodesXmlWithCustomJAXBSchemaLocation() throws Exception {
encoder.encode(mock, MockObject.class, template);

assertThat(template).hasBody("<?xml version=\"1.0\" encoding=\"UTF-8\" " +
"standalone=\"yes\"?><mockObject xsi:schemaLocation=\"http://apihost "
+
"http://apihost/schema.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
+
"standalone=\"yes\"?><mockObject xsi:schemaLocation=\"http://apihost " +
"http://apihost/schema.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<value>Test</value></mockObject>");
}

Expand All @@ -122,11 +121,12 @@ public void encodesXmlWithCustomJAXBNoNamespaceSchemaLocation() throws Exception
RequestTemplate template = new RequestTemplate();
encoder.encode(mock, MockObject.class, template);

assertThat(template).hasBody("<?xml version=\"1.0\" encoding=\"UTF-8\" " +
"standalone=\"yes\"?><mockObject xsi:noNamespaceSchemaLocation=\"http://apihost/schema.xsd\" "
+
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<value>Test</value></mockObject>");
assertThat(template)
.hasBody(
"<?xml version=\"1.0\" encoding=\"UTF-8\" " +
"standalone=\"yes\"?><mockObject xsi:noNamespaceSchemaLocation=\"http://apihost/schema.xsd\" " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<value>Test</value></mockObject>");
}

@Test
Expand Down Expand Up @@ -178,9 +178,11 @@ public void decodesXml() throws Exception {

@Test
public void doesntDecodeParameterizedTypes() throws Exception {
thrown.expect(UnsupportedOperationException.class);
thrown.expect(feign.codec.DecodeException.class);
thrown.expectMessage(
"JAXB only supports decoding raw types. Found java.util.Map<java.lang.String, ?>");
"java.util.Map is an interface, and JAXB can't handle interfaces.\n"+
"\tthis problem is related to the following location:\n"+
"\t\tat java.util.Map");

class ParameterizedHolder {

Expand All @@ -199,6 +201,44 @@ class ParameterizedHolder {
new JAXBDecoder(new JAXBContextFactory.Builder().build()).decode(response, parameterized);
}

@XmlRootElement
static class Box<T> {

@XmlElement
private T t;

public void set(T t) {
this.t = t;
}

}

@Test
public void decodeAnnotatedParameterizedTypes() throws Exception {
JAXBContextFactory jaxbContextFactory =
new JAXBContextFactory.Builder().withMarshallerFormattedOutput(true).build();

Encoder encoder = new JAXBEncoder(jaxbContextFactory);

Box<String> boxStr = new Box<>();
boxStr.set("hello");
Box<Box<String>> boxBoxStr = new Box<>();
boxBoxStr.set(boxStr);
RequestTemplate template = new RequestTemplate();
encoder.encode(boxBoxStr, Box.class, template);

Response response = Response.builder()
.status(200)
.reason("OK")
.request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.<String, Collection<String>>emptyMap())
.body(template.body())
.build();

new JAXBDecoder(new JAXBContextFactory.Builder().build()).decode(response, Box.class);

}

/** Enabled via {@link feign.Feign.Builder#decode404()} */
@Test
public void notFoundDecodesToEmpty() throws Exception {
Expand Down

0 comments on commit e4743d4

Please sign in to comment.