Skip to content

Commit a488c41

Browse files
kistlersvelo
andauthored
feign-soap-jakarta module like feign-soap, but with feign-jaxb-jakarta instead of feign-jaxb (#2094)
* soap-jakarta module like soap, but with jaxb-jakarta * require java 11 * add SOAP Jakarta and JAXB Jakarta to the feature list --------- Co-authored-by: Marvin Froeder <[email protected]>
1 parent 093b331 commit a488c41

File tree

12 files changed

+1303
-0
lines changed

12 files changed

+1303
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<module>slf4j</module>
4949
<module>spring4</module>
5050
<module>soap</module>
51+
<module>soap-jakarta</module>
5152
<module>reactive</module>
5253
<module>dropwizard-metrics4</module>
5354
<module>dropwizard-metrics5</module>

soap-jakarta/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
```
51+
52+
In certain situations the declarations on the SOAP envelope are not inherited by JAXB when reading the documents. This is particularly
53+
troublesome when it is not possible to correct the XML at the source.
54+
55+
To account for this situation, use the `useFirstChild` option on the `SOAPDecoder` builder. This will instruct JAX be to use `SOAPBody#getFirstChild()`
56+
instead of `SOAPBody#extractContentAsDocument()`. This will allow users to supply a `package-info.java` to manage the element namespaces
57+
explicitly and define what should occur if the namespace declarations are missing.

soap-jakarta/pom.xml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright 2012-2023 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>12.4-SNAPSHOT</version>
24+
</parent>
25+
26+
<artifactId>feign-soap-jakarta</artifactId>
27+
<name>Feign SOAP Jakarta</name>
28+
<description>Feign SOAP CoDec</description>
29+
30+
<properties>
31+
<main.java.version>11</main.java.version>
32+
<maven.compiler.source>11</maven.compiler.source>
33+
<maven.compiler.target>11</maven.compiler.target>
34+
<main.basedir>${project.basedir}/..</main.basedir>
35+
36+
<moditect.skip>true</moditect.skip>
37+
</properties>
38+
39+
<dependencies>
40+
<dependency>
41+
<groupId>${project.groupId}</groupId>
42+
<artifactId>feign-core</artifactId>
43+
</dependency>
44+
45+
<dependency>
46+
<groupId>${project.groupId}</groupId>
47+
<artifactId>feign-jaxb-jakarta</artifactId>
48+
<version>${project.version}</version>
49+
</dependency>
50+
51+
<dependency>
52+
<groupId>${project.groupId}</groupId>
53+
<artifactId>feign-core</artifactId>
54+
<type>test-jar</type>
55+
<scope>test</scope>
56+
</dependency>
57+
58+
<dependency>
59+
<groupId>jakarta.xml.ws</groupId>
60+
<artifactId>jakarta.xml.ws-api</artifactId>
61+
<version>4.0.0</version>
62+
</dependency>
63+
<dependency>
64+
<groupId>jakarta.xml.soap</groupId>
65+
<artifactId>jakarta.xml.soap-api</artifactId>
66+
<version>3.0.0</version>
67+
</dependency>
68+
<dependency>
69+
<groupId>jakarta.xml.bind</groupId>
70+
<artifactId>jakarta.xml.bind-api</artifactId>
71+
<version>4.0.0</version>
72+
</dependency>
73+
<dependency>
74+
<groupId>com.sun.xml.messaging.saaj</groupId>
75+
<artifactId>saaj-impl</artifactId>
76+
<version>3.0.2</version>
77+
</dependency>
78+
<dependency>
79+
<groupId>com.sun.xml.bind</groupId>
80+
<artifactId>jaxb-impl</artifactId>
81+
<version>4.0.3</version>
82+
<scope>runtime</scope>
83+
</dependency>
84+
</dependencies>
85+
86+
</project>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Copyright 2012-2023 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.soap;
15+
16+
import feign.Response;
17+
import feign.Util;
18+
import feign.codec.DecodeException;
19+
import feign.codec.Decoder;
20+
import feign.jaxb.JAXBContextFactory;
21+
import jakarta.xml.bind.JAXBException;
22+
import jakarta.xml.bind.Unmarshaller;
23+
import jakarta.xml.soap.*;
24+
import jakarta.xml.ws.soap.SOAPFaultException;
25+
import java.io.IOException;
26+
import java.lang.reflect.ParameterizedType;
27+
import java.lang.reflect.Type;
28+
29+
/**
30+
* Decodes SOAP responses using SOAPMessage and JAXB for the body part. <br>
31+
*
32+
* <p>The JAXBContextFactory should be reused across requests as it caches the created JAXB
33+
* contexts.
34+
*
35+
* <p>A SOAP Fault can be returned with a 200 HTTP code. Hence, faults could be handled with no
36+
* error on the HTTP layer. In this case, you'll certainly have to catch {@link SOAPFaultException}
37+
* to get fault from your API client service. In the other case (Faults are returned with 4xx or 5xx
38+
* HTTP error code), you may use {@link SOAPErrorDecoder} in your API configuration.
39+
*
40+
* <pre>
41+
*
42+
* public interface MyApi {
43+
*
44+
* &#64;RequestLine("POST /getObject")
45+
* &#64;Headers({
46+
* "SOAPAction: getObject",
47+
* "Content-Type: text/xml"
48+
* })
49+
* MyJaxbObjectResponse getObject(MyJaxbObjectRequest request);
50+
*
51+
* }
52+
*
53+
* ...
54+
*
55+
* JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
56+
* .withMarshallerJAXBEncoding(&quot;UTF-8&quot;)
57+
* .withMarshallerSchemaLocation(&quot;http://apihost http://apihost/schema.xsd&quot;)
58+
* .build();
59+
*
60+
* api = Feign.builder()
61+
* .decoder(new SOAPDecoder(jaxbFactory))
62+
* .target(MyApi.class, &quot;http://api&quot;);
63+
*
64+
* ...
65+
*
66+
* try {
67+
* api.getObject(new MyJaxbObjectRequest());
68+
* } catch (SOAPFaultException faultException) {
69+
* log.info(faultException.getFault().getFaultString());
70+
* }
71+
* </pre>
72+
*
73+
* @see SOAPErrorDecoder
74+
* @see SOAPFaultException
75+
*/
76+
public class SOAPDecoder implements Decoder {
77+
78+
private final JAXBContextFactory jaxbContextFactory;
79+
private final String soapProtocol;
80+
private final boolean useFirstChild;
81+
82+
public SOAPDecoder(JAXBContextFactory jaxbContextFactory) {
83+
this.jaxbContextFactory = jaxbContextFactory;
84+
this.soapProtocol = SOAPConstants.DEFAULT_SOAP_PROTOCOL;
85+
this.useFirstChild = false;
86+
}
87+
88+
private SOAPDecoder(Builder builder) {
89+
this.soapProtocol = builder.soapProtocol;
90+
this.jaxbContextFactory = builder.jaxbContextFactory;
91+
this.useFirstChild = builder.useFirstChild;
92+
}
93+
94+
@Override
95+
public Object decode(Response response, Type type) throws IOException {
96+
if (response.status() == 404) return Util.emptyValueOf(type);
97+
if (response.body() == null) return null;
98+
while (type instanceof ParameterizedType) {
99+
ParameterizedType ptype = (ParameterizedType) type;
100+
type = ptype.getRawType();
101+
}
102+
if (!(type instanceof Class)) {
103+
throw new UnsupportedOperationException(
104+
"SOAP only supports decoding raw types. Found " + type);
105+
}
106+
107+
try {
108+
SOAPMessage message =
109+
MessageFactory.newInstance(soapProtocol)
110+
.createMessage(null, response.body().asInputStream());
111+
if (message.getSOAPBody() != null) {
112+
if (message.getSOAPBody().hasFault()) {
113+
throw new SOAPFaultException(message.getSOAPBody().getFault());
114+
}
115+
116+
Unmarshaller unmarshaller = jaxbContextFactory.createUnmarshaller((Class<?>) type);
117+
118+
if (this.useFirstChild) {
119+
return unmarshaller.unmarshal(message.getSOAPBody().getFirstChild());
120+
} else {
121+
return unmarshaller.unmarshal(message.getSOAPBody().extractContentAsDocument());
122+
}
123+
}
124+
} catch (SOAPException | JAXBException e) {
125+
throw new DecodeException(response.status(), e.toString(), response.request(), e);
126+
} finally {
127+
if (response.body() != null) {
128+
response.body().close();
129+
}
130+
}
131+
return Util.emptyValueOf(type);
132+
}
133+
134+
public static class Builder {
135+
String soapProtocol = SOAPConstants.DEFAULT_SOAP_PROTOCOL;
136+
JAXBContextFactory jaxbContextFactory;
137+
boolean useFirstChild = false;
138+
139+
public Builder withJAXBContextFactory(JAXBContextFactory jaxbContextFactory) {
140+
this.jaxbContextFactory = jaxbContextFactory;
141+
return this;
142+
}
143+
144+
/**
145+
* The protocol used to create message factory. Default is "SOAP 1.1 Protocol".
146+
*
147+
* @param soapProtocol a string constant representing the MessageFactory protocol.
148+
* @see SOAPConstants#SOAP_1_1_PROTOCOL
149+
* @see SOAPConstants#SOAP_1_2_PROTOCOL
150+
* @see SOAPConstants#DYNAMIC_SOAP_PROTOCOL
151+
* @see MessageFactory#newInstance(String)
152+
*/
153+
public Builder withSOAPProtocol(String soapProtocol) {
154+
this.soapProtocol = soapProtocol;
155+
return this;
156+
}
157+
158+
/**
159+
* Alters the behavior of the code to use the {@link SOAPBody#getFirstChild()} in place of
160+
* {@link SOAPBody#extractContentAsDocument()}.
161+
*
162+
* @return the builder instance.
163+
*/
164+
public Builder useFirstChild() {
165+
this.useFirstChild = true;
166+
return this;
167+
}
168+
169+
public SOAPDecoder build() {
170+
if (jaxbContextFactory == null) {
171+
throw new IllegalStateException("JAXBContextFactory must be non-null");
172+
}
173+
return new SOAPDecoder(this);
174+
}
175+
}
176+
}

0 commit comments

Comments
 (0)