Skip to content

Commit 50f5b6e

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 a350235 commit 50f5b6e

File tree

12 files changed

+1287
-0
lines changed

12 files changed

+1287
-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: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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>
33+
* The JAXBContextFactory should be reused across requests as it caches the created JAXB contexts.
34+
*
35+
* <p>
36+
* A SOAP Fault can be returned with a 200 HTTP code. Hence, faults could be handled with no error
37+
* on the HTTP layer. In this case, you'll certainly have to catch {@link SOAPFaultException} to get
38+
* fault from your API client service. In the other case (Faults are returned with 4xx or 5xx HTTP
39+
* error code), you may use {@link SOAPErrorDecoder} in your API configuration.
40+
*
41+
* <pre>
42+
*
43+
* public interface MyApi {
44+
*
45+
* &#64;RequestLine("POST /getObject")
46+
* &#64;Headers({
47+
* "SOAPAction: getObject",
48+
* "Content-Type: text/xml"
49+
* })
50+
* MyJaxbObjectResponse getObject(MyJaxbObjectRequest request);
51+
*
52+
* }
53+
*
54+
* ...
55+
*
56+
* JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
57+
* .withMarshallerJAXBEncoding(&quot;UTF-8&quot;)
58+
* .withMarshallerSchemaLocation(&quot;http://apihost http://apihost/schema.xsd&quot;)
59+
* .build();
60+
*
61+
* api = Feign.builder()
62+
* .decoder(new SOAPDecoder(jaxbFactory))
63+
* .target(MyApi.class, &quot;http://api&quot;);
64+
*
65+
* ...
66+
*
67+
* try {
68+
* api.getObject(new MyJaxbObjectRequest());
69+
* } catch (SOAPFaultException faultException) {
70+
* log.info(faultException.getFault().getFaultString());
71+
* }
72+
* </pre>
73+
*
74+
* @see SOAPErrorDecoder
75+
* @see SOAPFaultException
76+
*/
77+
public class SOAPDecoder implements Decoder {
78+
79+
private final JAXBContextFactory jaxbContextFactory;
80+
private final String soapProtocol;
81+
private final boolean useFirstChild;
82+
83+
public SOAPDecoder(JAXBContextFactory jaxbContextFactory) {
84+
this.jaxbContextFactory = jaxbContextFactory;
85+
this.soapProtocol = SOAPConstants.DEFAULT_SOAP_PROTOCOL;
86+
this.useFirstChild = false;
87+
}
88+
89+
private SOAPDecoder(Builder builder) {
90+
this.soapProtocol = builder.soapProtocol;
91+
this.jaxbContextFactory = builder.jaxbContextFactory;
92+
this.useFirstChild = builder.useFirstChild;
93+
}
94+
95+
@Override
96+
public Object decode(Response response, Type type) throws IOException {
97+
if (response.status() == 404)
98+
return Util.emptyValueOf(type);
99+
if (response.body() == null)
100+
return null;
101+
while (type instanceof ParameterizedType) {
102+
ParameterizedType ptype = (ParameterizedType) type;
103+
type = ptype.getRawType();
104+
}
105+
if (!(type instanceof Class)) {
106+
throw new UnsupportedOperationException(
107+
"SOAP only supports decoding raw types. Found " + type);
108+
}
109+
110+
try {
111+
SOAPMessage message =
112+
MessageFactory.newInstance(soapProtocol)
113+
.createMessage(null, response.body().asInputStream());
114+
if (message.getSOAPBody() != null) {
115+
if (message.getSOAPBody().hasFault()) {
116+
throw new SOAPFaultException(message.getSOAPBody().getFault());
117+
}
118+
119+
Unmarshaller unmarshaller = jaxbContextFactory.createUnmarshaller((Class<?>) type);
120+
121+
if (this.useFirstChild) {
122+
return unmarshaller.unmarshal(message.getSOAPBody().getFirstChild());
123+
} else {
124+
return unmarshaller.unmarshal(message.getSOAPBody().extractContentAsDocument());
125+
}
126+
}
127+
} catch (SOAPException | JAXBException e) {
128+
throw new DecodeException(response.status(), e.toString(), response.request(), e);
129+
} finally {
130+
if (response.body() != null) {
131+
response.body().close();
132+
}
133+
}
134+
return Util.emptyValueOf(type);
135+
}
136+
137+
public static class Builder {
138+
String soapProtocol = SOAPConstants.DEFAULT_SOAP_PROTOCOL;
139+
JAXBContextFactory jaxbContextFactory;
140+
boolean useFirstChild = false;
141+
142+
public Builder withJAXBContextFactory(JAXBContextFactory jaxbContextFactory) {
143+
this.jaxbContextFactory = jaxbContextFactory;
144+
return this;
145+
}
146+
147+
/**
148+
* The protocol used to create message factory. Default is "SOAP 1.1 Protocol".
149+
*
150+
* @param soapProtocol a string constant representing the MessageFactory protocol.
151+
* @see SOAPConstants#SOAP_1_1_PROTOCOL
152+
* @see SOAPConstants#SOAP_1_2_PROTOCOL
153+
* @see SOAPConstants#DYNAMIC_SOAP_PROTOCOL
154+
* @see MessageFactory#newInstance(String)
155+
*/
156+
public Builder withSOAPProtocol(String soapProtocol) {
157+
this.soapProtocol = soapProtocol;
158+
return this;
159+
}
160+
161+
/**
162+
* Alters the behavior of the code to use the {@link SOAPBody#getFirstChild()} in place of
163+
* {@link SOAPBody#extractContentAsDocument()}.
164+
*
165+
* @return the builder instance.
166+
*/
167+
public Builder useFirstChild() {
168+
this.useFirstChild = true;
169+
return this;
170+
}
171+
172+
public SOAPDecoder build() {
173+
if (jaxbContextFactory == null) {
174+
throw new IllegalStateException("JAXBContextFactory must be non-null");
175+
}
176+
return new SOAPDecoder(this);
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)