Skip to content

Commit 3ced4a5

Browse files
committed
Added support for JAXB
1 parent 710191f commit 3ced4a5

14 files changed

Lines changed: 1051 additions & 1 deletion

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### Version 7.0
22
* Expose reflective dispatch hook: InvocationHandlerFactory
3+
* Add JAXB integration
34

45
### Version 6.1.1
56
* Fix for #85

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,18 @@ api = Feign.builder()
123123
.target(Api.class, "https://apihost");
124124
```
125125

126+
### JAXB
127+
[JAXBModule](https://github.com/Netflix/feign/tree/master/jaxb) allows you to encode and decode XML using JAXB.
128+
129+
Add `JAXBEncoder` and/or `JAXBDecoder` to your `Feign.Builder` like so:
130+
131+
```java
132+
api = Feign.builder()
133+
.encoder(new JAXBEncoder())
134+
.decoder(new JAXBDecoder())
135+
.target(Api.class, "https://apihost");
136+
```
137+
126138
### JAX-RS
127139
[JAXRSModule](https://github.com/Netflix/feign/tree/master/jaxrs) overrides annotation processing to instead use standard ones supplied by the JAX-RS specification. This is currently targeted at the 1.1 spec.
128140

build.gradle

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ project(':feign-jackson') {
9595
}
9696
}
9797

98+
project(':feign-jaxb') {
99+
apply plugin: 'java'
100+
101+
test {
102+
useTestNG()
103+
}
104+
105+
dependencies {
106+
compile project(':feign-core')
107+
testCompile 'org.testng:testng:6.8.5'
108+
testCompile 'com.google.guava:guava:14.0.1'
109+
}
110+
}
111+
98112
project(':feign-jaxrs') {
99113
apply plugin: 'java'
100114

jaxb/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
JAXB Codec
2+
===================
3+
4+
This module adds support for encoding and decoding XML via JAXB.
5+
6+
Add `JAXBEncoder` and/or `JAXBDecoder` to your `Feign.Builder` like so:
7+
8+
```java
9+
JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
10+
.withMarshallerJAXBEncoding("UTF-8")
11+
.withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
12+
.build();
13+
14+
Response response = Feign.builder()
15+
.encoder(new JAXBEncoder(jaxbFactory))
16+
.decoder(new JAXBDecoder(jaxbFactory))
17+
.target(Response.class, "https://apihost");
18+
```
19+
20+
Alternatively, you can add the encoder and decoder to your Dagger object graph using the provided JAXBModule like so:
21+
22+
```java
23+
JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder().build();
24+
25+
Response response = Feign.create(Response.class, "https://apihost", new JAXBModule(jaxbFactory));
26+
```
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright 2014 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package feign.jaxb;
17+
18+
import javax.xml.bind.JAXBContext;
19+
import javax.xml.bind.JAXBException;
20+
import javax.xml.bind.Marshaller;
21+
import javax.xml.bind.PropertyException;
22+
import javax.xml.bind.Unmarshaller;
23+
import java.util.HashMap;
24+
import java.util.Iterator;
25+
import java.util.Map;
26+
import java.util.concurrent.ConcurrentHashMap;
27+
28+
/**
29+
* Creates and caches JAXB contexts as well as creates Marshallers and Unmarshallers for each context.
30+
*/
31+
public final class JAXBContextFactory {
32+
private final ConcurrentHashMap<Class, JAXBContext> jaxbContexts = new ConcurrentHashMap<Class, JAXBContext>(64);
33+
private final Map<String, Object> properties;
34+
35+
private JAXBContextFactory(Map<String, Object> properties) {
36+
this.properties = properties;
37+
}
38+
39+
/**
40+
* Creates a new {@link javax.xml.bind.Unmarshaller} that handles the supplied class.
41+
*/
42+
public Unmarshaller createUnmarshaller(Class<?> clazz) throws JAXBException {
43+
JAXBContext ctx = getContext(clazz);
44+
return ctx.createUnmarshaller();
45+
}
46+
47+
/**
48+
* Creates a new {@link javax.xml.bind.Marshaller} that handles the supplied class.
49+
*/
50+
public Marshaller createMarshaller(Class<?> clazz) throws JAXBException {
51+
JAXBContext ctx = getContext(clazz);
52+
Marshaller marshaller = ctx.createMarshaller();
53+
setMarshallerProperties(marshaller);
54+
return marshaller;
55+
}
56+
57+
private void setMarshallerProperties(Marshaller marshaller) throws PropertyException {
58+
Iterator<String> keys = properties.keySet().iterator();
59+
60+
while(keys.hasNext()) {
61+
String key = keys.next();
62+
marshaller.setProperty(key, properties.get(key));
63+
}
64+
}
65+
66+
private JAXBContext getContext(Class<?> clazz) throws JAXBException {
67+
JAXBContext jaxbContext = this.jaxbContexts.get(clazz);
68+
if (jaxbContext == null) {
69+
jaxbContext = JAXBContext.newInstance(clazz);
70+
this.jaxbContexts.putIfAbsent(clazz, jaxbContext);
71+
}
72+
return jaxbContext;
73+
}
74+
75+
/**
76+
* Creates instances of {@link feign.jaxb.JAXBContextFactory}
77+
*/
78+
public static class Builder {
79+
private final Map<String, Object> properties = new HashMap<String, Object>(5);
80+
81+
/**
82+
* Sets the jaxb.encoding property of any Marshaller created by this factory.
83+
*/
84+
public Builder withMarshallerJAXBEncoding(String value) {
85+
properties.put(Marshaller.JAXB_ENCODING, value);
86+
return this;
87+
}
88+
89+
/**
90+
* Sets the jaxb.schemaLocation property of any Marshaller created by this factory.
91+
*/
92+
public Builder withMarshallerSchemaLocation(String value) {
93+
properties.put(Marshaller.JAXB_SCHEMA_LOCATION, value);
94+
return this;
95+
}
96+
97+
/**
98+
* Sets the jaxb.noNamespaceSchemaLocation property of any Marshaller created by this factory.
99+
*/
100+
public Builder withMarshallerNoNamespaceSchemaLocation(String value) {
101+
properties.put(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, value);
102+
return this;
103+
}
104+
105+
/**
106+
* Sets the jaxb.formatted.output property of any Marshaller created by this factory.
107+
*/
108+
public Builder withMarshallerFormattedOutput(Boolean value) {
109+
properties.put(Marshaller.JAXB_FORMATTED_OUTPUT, value);
110+
return this;
111+
}
112+
113+
/**
114+
* Sets the jaxb.fragment property of any Marshaller created by this factory.
115+
*/
116+
public Builder withMarshallerFragment(Boolean value) {
117+
properties.put(Marshaller.JAXB_FRAGMENT, value);
118+
return this;
119+
}
120+
121+
/**
122+
* Creates a new {@link feign.jaxb.JAXBContextFactory} instance.
123+
*/
124+
public JAXBContextFactory build() {
125+
return new JAXBContextFactory(properties);
126+
}
127+
}
128+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2014 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package feign.jaxb;
17+
18+
import feign.FeignException;
19+
import feign.Response;
20+
import feign.codec.DecodeException;
21+
import feign.codec.Decoder;
22+
23+
import javax.inject.Inject;
24+
import javax.xml.bind.JAXBException;
25+
import javax.xml.bind.Unmarshaller;
26+
import java.io.IOException;
27+
import java.lang.reflect.Type;
28+
29+
/**
30+
* Decodes responses using JAXB.
31+
* <br>
32+
* <p>
33+
* Basic example with with Feign.Builder:
34+
* </p>
35+
* <pre>
36+
* JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
37+
* .withMarshallerJAXBEncoding("UTF-8")
38+
* .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
39+
* .build();
40+
*
41+
* api = Feign.builder()
42+
* .decoder(new JAXBDecoder(jaxbFactory))
43+
* .target(MyApi.class, "http://api");
44+
* </pre>
45+
* <p>
46+
* The JAXBContextFactory should be reused across requests as it caches the created JAXB contexts.
47+
* </p>
48+
*/
49+
public class JAXBDecoder implements Decoder {
50+
private final JAXBContextFactory jaxbContextFactory;
51+
52+
@Inject
53+
public JAXBDecoder(JAXBContextFactory jaxbContextFactory) {
54+
this.jaxbContextFactory = jaxbContextFactory;
55+
}
56+
57+
@Override
58+
public Object decode(Response response, Type type) throws IOException, FeignException {
59+
try {
60+
Unmarshaller unmarshaller = jaxbContextFactory.createUnmarshaller((Class) type);
61+
return unmarshaller.unmarshal(response.body().asInputStream());
62+
} catch (JAXBException e) {
63+
throw new DecodeException(e.toString(), e);
64+
} finally {
65+
if(response.body() != null) {
66+
response.body().close();
67+
}
68+
}
69+
}
70+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2014 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package feign.jaxb;
17+
18+
import feign.RequestTemplate;
19+
import feign.codec.EncodeException;
20+
import feign.codec.Encoder;
21+
22+
import javax.inject.Inject;
23+
import javax.xml.bind.JAXBException;
24+
import javax.xml.bind.Marshaller;
25+
import java.io.StringWriter;
26+
27+
/**
28+
* Encodes requests using JAXB.
29+
* <br>
30+
* <p>
31+
* Basic example with with Feign.Builder:
32+
* </p>
33+
* <pre>
34+
* JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
35+
* .withMarshallerJAXBEncoding("UTF-8")
36+
* .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
37+
* .build();
38+
*
39+
* api = Feign.builder()
40+
* .encoder(new JAXBEncoder(jaxbFactory))
41+
* .target(MyApi.class, "http://api");
42+
* </pre>
43+
* <p>
44+
* The JAXBContextFactory should be reused across requests as it caches the created JAXB contexts.
45+
* </p>
46+
*/
47+
public class JAXBEncoder implements Encoder {
48+
private final JAXBContextFactory jaxbContextFactory;
49+
50+
@Inject
51+
public JAXBEncoder(JAXBContextFactory jaxbContextFactory) {
52+
this.jaxbContextFactory = jaxbContextFactory;
53+
}
54+
55+
@Override
56+
public void encode(Object object, RequestTemplate template) throws EncodeException {
57+
try {
58+
Marshaller marshaller = jaxbContextFactory.createMarshaller(object.getClass());
59+
StringWriter stringWriter = new StringWriter();
60+
marshaller.marshal(object, stringWriter);
61+
template.body(stringWriter.toString());
62+
} catch (JAXBException e) {
63+
throw new EncodeException(e.toString(), e);
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)