Skip to content

Commit 968b4e9

Browse files
author
ricardo
committed
Allow decoding of parameterizedTypes (generics)
1 parent c8fed2d commit 968b4e9

File tree

2 files changed

+59
-18
lines changed

2 files changed

+59
-18
lines changed

jaxb/src/main/java/feign/jaxb/JAXBDecoder.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import java.io.IOException;
1717
import java.lang.reflect.Type;
18+
import java.lang.reflect.ParameterizedType;
1819
import javax.xml.bind.JAXBException;
1920
import javax.xml.bind.Unmarshaller;
2021
import javax.xml.parsers.ParserConfigurationException;
@@ -36,13 +37,13 @@
3637
*
3738
* <pre>
3839
* JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
39-
* .withMarshallerJAXBEncoding("UTF-8")
40-
* .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
40+
* .withMarshallerJAXBEncoding(&quot;UTF-8&quot;)
41+
* .withMarshallerSchemaLocation(&quot;http://apihost http://apihost/schema.xsd&quot;)
4142
* .build();
42-
*
43+
*
4344
* api = Feign.builder()
4445
* .decoder(new JAXBDecoder(jaxbFactory))
45-
* .target(MyApi.class, "http://api");
46+
* .target(MyApi.class, &quot;http://api&quot;);
4647
* </pre>
4748
* <p>
4849
* The JAXBContextFactory should be reused across requests as it caches the created JAXB contexts.
@@ -69,7 +70,7 @@ public Object decode(Response response, Type type) throws IOException {
6970
return Util.emptyValueOf(type);
7071
if (response.body() == null)
7172
return null;
72-
if (type instanceof ParameterizedType) {
73+
while (type instanceof ParameterizedType) {
7374
ParameterizedType ptype = (ParameterizedType) type;
7475
type = ptype.getRawType();
7576
}

jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ public void encodesXml() throws Exception {
4747
new JAXBEncoder(new JAXBContextFactory.Builder().build())
4848
.encode(mock, MockObject.class, template);
4949

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

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

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

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

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

132132
@Test
@@ -178,9 +178,11 @@ public void decodesXml() throws Exception {
178178

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

185187
class ParameterizedHolder {
186188

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

204+
@XmlRootElement
205+
static class Box<T> {
206+
207+
@XmlElement
208+
private T t;
209+
210+
public void set(T t) {
211+
this.t = t;
212+
}
213+
214+
}
215+
216+
@Test
217+
public void decodeAnnotatedParameterizedTypes() throws Exception {
218+
JAXBContextFactory jaxbContextFactory =
219+
new JAXBContextFactory.Builder().withMarshallerFormattedOutput(true).build();
220+
221+
Encoder encoder = new JAXBEncoder(jaxbContextFactory);
222+
223+
Box<String> boxStr = new Box<>();
224+
boxStr.set("hello");
225+
Box<Box<String>> boxBoxStr = new Box<>();
226+
boxBoxStr.set(boxStr);
227+
RequestTemplate template = new RequestTemplate();
228+
encoder.encode(boxBoxStr, Box.class, template);
229+
230+
Response response = Response.builder()
231+
.status(200)
232+
.reason("OK")
233+
.request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
234+
.headers(Collections.<String, Collection<String>>emptyMap())
235+
.body(template.body())
236+
.build();
237+
238+
new JAXBDecoder(new JAXBContextFactory.Builder().build()).decode(response, Box.class);
239+
240+
}
241+
202242
/** Enabled via {@link feign.Feign.Builder#decode404()} */
203243
@Test
204244
public void notFoundDecodesToEmpty() throws Exception {

0 commit comments

Comments
 (0)