Skip to content

Commit 2bf7796

Browse files
pilakvelo
authored andcommitted
Adding SOAP CoDec (+ JAXB modifications) (#786)
1 parent e15e9f0 commit 2bf7796

File tree

15 files changed

+1200
-27
lines changed

15 files changed

+1200
-27
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,28 @@ public class Example {
342342
}
343343
```
344344

345+
### SOAP
346+
[SOAP](./soap) includes an encoder and decoder you can use with an XML API.
347+
348+
349+
This module adds support for encoding and decoding SOAP Body objects via JAXB and SOAPMessage. It also provides SOAPFault decoding capabilities by wrapping them into the original `javax.xml.ws.soap.SOAPFaultException`, so that you'll only need to catch `SOAPFaultException` in order to handle SOAPFault.
350+
351+
Add `SOAPEncoder` and/or `SOAPDecoder` to your `Feign.Builder` like so:
352+
353+
```java
354+
public class Example {
355+
public static void main(String[] args) {
356+
Api api = Feign.builder()
357+
.encoder(new SOAPEncoder(jaxbFactory))
358+
.decoder(new SOAPDecoder(jaxbFactory))
359+
.errorDecoder(new SOAPErrorDecoder())
360+
.target(MyApi.class, "http://api");
361+
}
362+
}
363+
```
364+
365+
NB: you may also need to add `SOAPErrorDecoder` if SOAP Faults are returned in response with error http codes (4xx, 5xx, ...)
366+
345367
### SLF4J
346368
[SLF4JModule](./slf4j) allows directing Feign's logging to [SLF4J](http://www.slf4j.org/), allowing you to easily use a logging backend of your choice (Logback, Log4J, etc.)
347369

jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
package feign.jaxb;
1515

1616
import java.util.HashMap;
17-
import java.util.Iterator;
1817
import java.util.List;
1918
import java.util.Map;
19+
import java.util.Map.Entry;
2020
import java.util.concurrent.ConcurrentHashMap;
2121
import javax.xml.bind.JAXBContext;
2222
import javax.xml.bind.JAXBException;
@@ -31,8 +31,8 @@
3131
*/
3232
public final class JAXBContextFactory {
3333

34-
private final ConcurrentHashMap<Class, JAXBContext> jaxbContexts =
35-
new ConcurrentHashMap<Class, JAXBContext>(64);
34+
private final ConcurrentHashMap<Class<?>, JAXBContext> jaxbContexts =
35+
new ConcurrentHashMap<>(64);
3636
private final Map<String, Object> properties;
3737

3838
private JAXBContextFactory(Map<String, Object> properties) {
@@ -43,26 +43,21 @@ private JAXBContextFactory(Map<String, Object> properties) {
4343
* Creates a new {@link javax.xml.bind.Unmarshaller} that handles the supplied class.
4444
*/
4545
public Unmarshaller createUnmarshaller(Class<?> clazz) throws JAXBException {
46-
JAXBContext ctx = getContext(clazz);
47-
return ctx.createUnmarshaller();
46+
return getContext(clazz).createUnmarshaller();
4847
}
4948

5049
/**
5150
* Creates a new {@link javax.xml.bind.Marshaller} that handles the supplied class.
5251
*/
5352
public Marshaller createMarshaller(Class<?> clazz) throws JAXBException {
54-
JAXBContext ctx = getContext(clazz);
55-
Marshaller marshaller = ctx.createMarshaller();
53+
Marshaller marshaller = getContext(clazz).createMarshaller();
5654
setMarshallerProperties(marshaller);
5755
return marshaller;
5856
}
5957

6058
private void setMarshallerProperties(Marshaller marshaller) throws PropertyException {
61-
Iterator<String> keys = properties.keySet().iterator();
62-
63-
while (keys.hasNext()) {
64-
String key = keys.next();
65-
marshaller.setProperty(key, properties.get(key));
59+
for (Entry<String, Object> en : properties.entrySet()) {
60+
marshaller.setProperty(en.getKey(), en.getValue());
6661
}
6762
}
6863

@@ -90,11 +85,11 @@ private void preloadContextCache(List<Class<?>> classes) throws JAXBException {
9085
}
9186

9287
/**
93-
* Creates instances of {@link feign.jaxb.JAXBContextFactory}
88+
* Creates instances of {@link feign.jaxb.JAXBContextFactory}.
9489
*/
9590
public static class Builder {
9691

97-
private final Map<String, Object> properties = new HashMap<String, Object>(5);
92+
private final Map<String, Object> properties = new HashMap<>(10);
9893

9994
/**
10095
* Sets the jaxb.encoding property of any Marshaller created by this factory.
@@ -136,6 +131,24 @@ public Builder withMarshallerFragment(Boolean value) {
136131
return this;
137132
}
138133

134+
/**
135+
* Sets the given property of any Marshaller created by this factory.
136+
*
137+
* <p>
138+
* Example : <br>
139+
* <br>
140+
* <code>
141+
* new JAXBContextFactory.Builder()
142+
* .withProperty("com.sun.xml.internal.bind.xmlHeaders", "&lt;!DOCTYPE Example SYSTEM \&quot;example.dtd\&quot;&gt;")
143+
* .build();
144+
* </code>
145+
* </p>
146+
*/
147+
public Builder withProperty(String key, Object value) {
148+
properties.put(key, value);
149+
return this;
150+
}
151+
139152
/**
140153
* Creates a new {@link feign.jaxb.JAXBContextFactory} instance with a lazy loading cached
141154
* context

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

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@
1414
package feign.jaxb;
1515

1616
import java.io.IOException;
17-
import java.lang.reflect.Type;
1817
import java.lang.reflect.ParameterizedType;
18+
import java.lang.reflect.Type;
1919
import javax.xml.bind.JAXBException;
20-
import javax.xml.bind.Unmarshaller;
2120
import javax.xml.parsers.ParserConfigurationException;
2221
import javax.xml.parsers.SAXParserFactory;
23-
import javax.xml.transform.Source;
2422
import javax.xml.transform.sax.SAXSource;
2523
import feign.Response;
2624
import feign.Util;
@@ -90,15 +88,10 @@ public Object decode(Response response, Type type) throws IOException {
9088
false);
9189
saxParserFactory.setNamespaceAware(namespaceAware);
9290

93-
Source source = new SAXSource(saxParserFactory.newSAXParser().getXMLReader(),
94-
new InputSource(response.body().asInputStream()));
95-
Unmarshaller unmarshaller = jaxbContextFactory.createUnmarshaller((Class) type);
96-
return unmarshaller.unmarshal(source);
97-
} catch (JAXBException e) {
98-
throw new DecodeException(e.toString(), e);
99-
} catch (ParserConfigurationException e) {
100-
throw new DecodeException(e.toString(), e);
101-
} catch (SAXException e) {
91+
return jaxbContextFactory.createUnmarshaller((Class<?>) type).unmarshal(new SAXSource(
92+
saxParserFactory.newSAXParser().getXMLReader(),
93+
new InputSource(response.body().asInputStream())));
94+
} catch (JAXBException | ParserConfigurationException | SAXException e) {
10295
throw new DecodeException(e.toString(), e);
10396
} finally {
10497
if (response.body() != null) {

jaxb/src/main/java/feign/jaxb/JAXBEncoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void encode(Object object, Type bodyType, RequestTemplate template) {
5656
"JAXB only supports encoding raw types. Found " + bodyType);
5757
}
5858
try {
59-
Marshaller marshaller = jaxbContextFactory.createMarshaller((Class) bodyType);
59+
Marshaller marshaller = jaxbContextFactory.createMarshaller((Class<?>) bodyType);
6060
StringWriter stringWriter = new StringWriter();
6161
marshaller.marshal(object, stringWriter);
6262
template.body(stringWriter.toString());

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<module>ribbon</module>
4040
<module>sax</module>
4141
<module>slf4j</module>
42+
<module>soap</module>
4243
<module>reactive</module>
4344
<module>example-github</module>
4445
<module>example-wikipedia</module>
@@ -52,6 +53,9 @@
5253
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
5354
<project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
5455

56+
<!-- specifying jvm arguments -->
57+
<jvm.options>-Duser.language=en</jvm.options>
58+
5559
<!-- default bytecode version for src/main -->
5660
<main.java.version>1.8</main.java.version>
5761
<main.signature.artifact>java18</main.signature.artifact>
@@ -317,6 +321,7 @@
317321
<configuration>
318322
<redirectTestOutputToFile>true</redirectTestOutputToFile>
319323
<trimStackTrace>false</trimStackTrace>
324+
<argLine>${jvm.options}</argLine>
320325
</configuration>
321326
<dependencies>
322327
<dependency>

soap/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
SOAP Codec
2+
===================
3+
4+
This module adds support for encoding and decoding SOAP Body objects via JAXB and SOAPMessage. It also provides SOAPFault decoding capabilities by wrapping them into the original `javax.xml.ws.soap.SOAPFaultException`, so that you'll only need to catch `SOAPFaultException` in order to handle SOAPFault.
5+
6+
Add `SOAPEncoder` and/or `SOAPDecoder` to your `Feign.Builder` like so:
7+
8+
```java
9+
public interface MyApi {
10+
11+
@RequestLine("POST /getObject")
12+
@Headers({
13+
"SOAPAction: getObject",
14+
"Content-Type: text/xml"
15+
})
16+
MyJaxbObjectResponse getObject(MyJaxbObjectRequest request);
17+
18+
}
19+
20+
...
21+
22+
JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
23+
.withMarshallerJAXBEncoding("UTF-8")
24+
.withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
25+
.build();
26+
27+
api = Feign.builder()
28+
.encoder(new SOAPEncoder(jaxbFactory))
29+
.decoder(new SOAPDecoder(jaxbFactory))
30+
.target(MyApi.class, "http://api");
31+
32+
...
33+
34+
try {
35+
api.getObject(new MyJaxbObjectRequest());
36+
} catch (SOAPFaultException faultException) {
37+
log.info(faultException.getFault().getFaultString());
38+
}
39+
40+
```
41+
42+
Because a SOAP Fault can be returned as well with a 200 http code than a 4xx or 5xx HTTP error code (depending on the used API), you may also use `SOAPErrorDecoder` in your API configuration, in order to be able to catch `SOAPFaultException` in case of SOAP Fault. Add it, like below:
43+
44+
```java
45+
api = Feign.builder()
46+
.encoder(new SOAPEncoder(jaxbFactory))
47+
.decoder(new SOAPDecoder(jaxbFactory))
48+
.errorDecoder(new SOAPErrorDecoder())
49+
.target(MyApi.class, "http://api");
50+
```

soap/pom.xml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright 2012-2018 The Feign Authors
5+
6+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7+
in compliance with the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software distributed under the License
12+
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13+
or implied. See the License for the specific language governing permissions and limitations under
14+
the License.
15+
16+
-->
17+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
18+
<modelVersion>4.0.0</modelVersion>
19+
20+
<parent>
21+
<groupId>io.github.openfeign</groupId>
22+
<artifactId>parent</artifactId>
23+
<version>10.0.2-SNAPSHOT</version>
24+
</parent>
25+
26+
<artifactId>feign-soap</artifactId>
27+
<name>Feign SOAP</name>
28+
<description>Feign SOAP CoDec</description>
29+
30+
<properties>
31+
<main.basedir>${project.basedir}/..</main.basedir>
32+
</properties>
33+
34+
<dependencies>
35+
<dependency>
36+
<groupId>${project.groupId}</groupId>
37+
<artifactId>feign-core</artifactId>
38+
</dependency>
39+
40+
<dependency>
41+
<groupId>${project.groupId}</groupId>
42+
<artifactId>feign-jaxb</artifactId>
43+
</dependency>
44+
45+
<dependency>
46+
<groupId>${project.groupId}</groupId>
47+
<artifactId>feign-core</artifactId>
48+
<type>test-jar</type>
49+
<scope>test</scope>
50+
</dependency>
51+
</dependencies>
52+
53+
<profiles>
54+
<profile>
55+
<activation>
56+
<jdk>11</jdk>
57+
</activation>
58+
59+
<!--
60+
JAX-WS was removed from java SDK on JEP 320
61+
http://openjdk.java.net/jeps/320
62+
-->
63+
<dependencies>
64+
<dependency>
65+
<groupId>javax.xml.bind</groupId>
66+
<artifactId>jaxb-api</artifactId>
67+
<version>2.3.1</version>
68+
</dependency>
69+
<dependency>
70+
<groupId>org.glassfish.jaxb</groupId>
71+
<artifactId>jaxb-runtime</artifactId>
72+
<version>2.4.0-b180830.0438</version>
73+
<scope>test</scope>
74+
</dependency>
75+
<dependency>
76+
<groupId>javax.xml.ws</groupId>
77+
<artifactId>jaxws-api</artifactId>
78+
<version>2.3.1</version>
79+
</dependency>
80+
<dependency>
81+
<groupId>com.sun.xml.messaging.saaj</groupId>
82+
<artifactId>saaj-impl</artifactId>
83+
<version>1.5.0</version>
84+
</dependency>
85+
</dependencies>
86+
</profile>
87+
</profiles>
88+
89+
</project>

0 commit comments

Comments
 (0)