Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

693 soap demo replacement for blz service #709

Merged
merged 55 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
2ea5526
included the code from local repo
christiangoerdes Aug 25, 2023
75ea2df
included logger
christiangoerdes Aug 25, 2023
174e32e
added request.xml
christiangoerdes Aug 25, 2023
bb4871d
implemented notFoundTest
christiangoerdes Aug 25, 2023
8916d6c
implemented request1 test
christiangoerdes Aug 25, 2023
176f40d
implemented request test 2 and 3
christiangoerdes Aug 25, 2023
3f696a2
optimized the code
christiangoerdes Aug 25, 2023
d67ff9a
removed logger
christiangoerdes Aug 25, 2023
d5f3cc5
refactoring
christiangoerdes Aug 25, 2023
5dd8792
including xpath wip!
christiangoerdes Aug 25, 2023
8eeff36
changed Unit Test to Xpath
christiangoerdes Aug 30, 2023
e35eee8
added membrane annot
christiangoerdes Aug 30, 2023
f8f4e4e
added soap fault for wrong cities
christiangoerdes Aug 30, 2023
63b9ec2
sout to comment
christiangoerdes Aug 30, 2023
1fea4f1
Refactoring
predic8 Aug 30, 2023
1413c43
changed switch to hashmap with record
christiangoerdes Sep 12, 2023
4e1c229
adjusted population
christiangoerdes Sep 12, 2023
7b5f677
added soap faults
christiangoerdes Sep 12, 2023
cc0413e
removed unnecessary prefixes
christiangoerdes Sep 12, 2023
3b487d5
removed unused function
christiangoerdes Sep 12, 2023
c9914e2
added thread safe comment
christiangoerdes Sep 12, 2023
431a0f6
added host to wsdl response
christiangoerdes Sep 12, 2023
a037bf4
refactoring
christiangoerdes Sep 12, 2023
963333e
removed unused imports
christiangoerdes Sep 12, 2023
f9e6e46
Merge branch 'master' into 693-replacement-for-blz-service
christiangoerdes Sep 12, 2023
89d5b69
Refactoring
predic8 Sep 12, 2023
1860ad0
added wsdl regex pattern
christiangoerdes Sep 13, 2023
160a26b
implemented test for isWSDLRequest
christiangoerdes Sep 13, 2023
09a37a4
removed request param in path + test
christiangoerdes Sep 13, 2023
50c52dd
removed request param in path + test
christiangoerdes Sep 13, 2023
d25a028
fixed test
christiangoerdes Sep 13, 2023
d7ac6cc
added example
christiangoerdes Sep 13, 2023
8e471f4
added example
christiangoerdes Sep 13, 2023
6006509
Merge remote-tracking branch 'origin/693-replacement-for-blz-service'…
christiangoerdes Sep 13, 2023
54adafa
refactoring
christiangoerdes Sep 13, 2023
981fac8
changed wsdl
christiangoerdes Sep 18, 2023
813905c
refactoring
christiangoerdes Sep 18, 2023
f72f9c5
WSDL refactoring
predic8 Sep 18, 2023
9ca3353
updated to newest wsdl
christiangoerdes Sep 18, 2023
bc3d51e
updated city.wsdl
christiangoerdes Sep 20, 2023
a4e7ea4
Merge branch 'master' into 693-replacement-for-blz-service
t-burch Oct 18, 2023
04e6cec
Merge branch 'master' into 693-replacement-for-blz-service
t-burch Oct 25, 2023
ee2f887
Merge branch 'master' into 693-replacement-for-blz-service
t-burch Nov 13, 2023
432a82f
added ExampleTest(WithoutInternet) for SampleSoapService
christiangoerdes Nov 13, 2023
0f89a0b
refactoring
christiangoerdes Nov 13, 2023
9b35a5e
refactoring
christiangoerdes Nov 13, 2023
aee0adf
Merge branch 'master' into 693-replacement-for-blz-service
christiangoerdes Nov 27, 2023
8b004c0
Merge branch 'master' into 693-replacement-for-blz-service
t-burch Jan 4, 2024
36b0d8f
Merge branch 'master' into 693-replacement-for-blz-service
t-burch Jan 11, 2024
437cd3c
Merge branch 'master' into 693-replacement-for-blz-service
t-burch Jan 23, 2024
a2c0ddd
Merge branch 'master' into 693-replacement-for-blz-service
t-burch Feb 8, 2024
4cb971e
Addressed conversations
t-burch Feb 8, 2024
f35086a
Merge remote-tracking branch 'origin/693-replacement-for-blz-service'…
t-burch Feb 8, 2024
9feca69
Reviewed
predic8 Feb 8, 2024
c4c21d4
Changed WSDL content type to text/xml; charset="utf-8"
t-burch Feb 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package com.predic8.membrane.core.interceptor.soap;

import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.http.*;
import com.predic8.membrane.core.interceptor.*;
import org.w3c.dom.*;

import javax.xml.parsers.*;
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.transform.*;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;

import static com.predic8.membrane.core.Constants.*;
import static com.predic8.membrane.core.http.Header.CONTENT_TYPE;
import static com.predic8.membrane.core.http.MimeType.*;
import static com.predic8.membrane.core.http.Response.*;
import static com.predic8.membrane.core.interceptor.Outcome.*;
import static com.predic8.membrane.core.openapi.util.Utils.*;
import static com.predic8.membrane.core.util.XMLUtil.*;
import static javax.xml.stream.XMLStreamConstants.*;

@MCElement(name = "sampleSoapService")
public class SampleSoapServiceInterceptor extends AbstractInterceptor {

public static final Pattern WSDL_PATH_PARAM = Pattern.compile("(?i).+\\?.*wsdl.*");
public static final String CITY_SERVICE_NS = "https://predic8.de/city-service";

public SampleSoapServiceInterceptor() {
name = "SampleSoapService";
}

@Override
public Outcome handleRequest(Exchange exc) throws Exception {
if (isWSDLRequest(exc)) {
exc.setResponse(createWSDLResponse(exc));
} else if(!exc.getRequest().isPOSTRequest()) {
exc.setResponse(createMethodNotAllowedSOAPFault());
} else {
try {
exc.setResponse(createGetCityResponse(exc));
} catch (Exception e) {
exc.setResponse(createResourceNotFoundSOAPFault());
}
}
return RETURN;
}

private static Response createResourceNotFoundSOAPFault() throws Exception {
return ok(getSoapFault("Resource Not Found", "404", "Cannot parse SOAP message. Request should contain e.g. <name>Bonn</name>")).contentType(APPLICATION_XML).build();
}

private static Response createGetCityResponse(Exchange exc) throws Exception {
return ok(getResponse(getCity(exc))).contentType(TEXT_XML).build();
}

private static Response createMethodNotAllowedSOAPFault() throws Exception {
return ok(getSoapFault("Method Not Allowed", "405", "Use POST to access the service.")).contentType(APPLICATION_XML).build();
}

private Response createWSDLResponse(Exchange exc) throws XMLStreamException {
return ok().header(CONTENT_TYPE, TEXT_XML_UTF8)
.body(setWsdlServer(
getResourceAsStream(this,"/wsdl/city.wsdl"),exc)
).build();
}


static boolean isWSDLRequest(Exchange exc) {
return WSDL_PATH_PARAM.matcher(exc.getRequest().getUri()).matches();
}

private static String getCity(Exchange exc) throws Exception {
return getElementAsString(exc.getRequest().getBodyAsStream(), "name");
}

private static final HashMap<String, City> cityMap = new HashMap<>() {{
put("Bonn", new City("Bonn", 327_000, "Germany"));
put("Bielefeld", new City("Bielefeld", 333_000, "Germany"));
put("Manila", new City("Manila", 1_780_000, "Philippines"));
put("Da Nang", new City("Da Nang", 1_220_000, "Vietnam"));
put("London", new City("London", 8_980_000, "England"));
put("New York", new City("New York", 8_460_000, "USA"));
}};

public static String getSoapFault(String faultString, String code, String errorMessage) {
return """
<s11:Envelope xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/">
<s11:Body>
<s11:Fault>
<faultcode>s11:Client</faultcode>
<faultstring>%s</faultstring>
<detail>
<errorcode>%s</errorcode>
<errormessage>%s</errormessage>
</detail>
</s11:Fault>
</s11:Body>
</s11:Envelope>
""".formatted(faultString, code, errorMessage);
}

public static String getElementAsString(InputStream is, String localName) throws Exception {
// MLInputFactory is not required to be thread-safe, ...
// https://javadoc.io/static/com.sun.xml.ws/jaxws-rt/2.2.10-b140319.1121/com/sun/xml/ws/api/streaming/XMLStreamReaderFactory.Default.html#:~:text=XMLInputFactory%20is%20not%20required%20to,using%20a%20XMLInputFactory%20per%20thread.
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(is);

while (reader.hasNext()) {
if (reader.next() == START_ELEMENT) {
if (reader.getName().getLocalPart().equals(localName)) {
return reader.getElementText();
}
}
}
throw new Exception();
}

public static String setWsdlServer(InputStream is, Exchange exc) throws XMLStreamException {
// XMLEventFactory is not required to be thread-safe, ...
// https://javadoc.io/static/com.sun.xml.ws/jaxws-rt/2.2.10-b140319.1121/com/sun/xml/ws/api/streaming/XMLStreamReaderFactory.Default.html
StringWriter modifiedXmlWriter = new StringWriter();
XMLEventReader reader = XMLInputFactory.newInstance().createXMLEventReader(Objects.requireNonNull(is));
XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(modifiedXmlWriter);
XMLEventFactory fac = XMLEventFactory.newInstance();

while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
StartElement startElement = event.asStartElement();
if ("address".equals(startElement.getName().getLocalPart())) {
writer.add(fac.createStartElement("soap", "soap", "address"));
writer.add(fac.createAttribute("location", getSOAPAddress(exc)));
} else {
writer.add(event);
}
} else {
writer.add(event);
}
}

return modifiedXmlWriter.toString();
}

public static String getSOAPAddress(Exchange exc) {
return exc.getInboundProtocol() + "://" + exc.getRequest().getHeader().getHost() + getPathWithoutParam(exc.getOriginalRequestUri());
}

static String getPathWithoutParam(String exc) {
return exc.replaceAll("\\?.*$", "");
}


public static String getResponse(String city) throws ParserConfigurationException, TransformerException {
try {
return xml2string(createResponse(city));
} catch (Exception e) {
return getSoapFault("Not Found", "404", "Do not know %s. Try Bonn, London or New York".formatted(city));
}
}

private static Document createResponse(String city) throws Exception {
// DocumentBuilderFactory is not guaranteed to be thread safe
// https://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/1.5/api/javax/xml/parsers/DocumentBuilderFactory.html
Document res = createDocumentBuilder().newDocument();
res.appendChild(createResponseEnvelope(city, res));
return res;
}

private static Element createResponseEnvelope(String city, Document res) {
Element env = res.createElementNS(SOAP11_NS, "s:Envelope");
env.setAttribute("xmlns:s", SOAP11_NS);
env.setAttribute("xmlns:cs", CITY_SERVICE_NS);
env.appendChild(createBody(city, res));
return env;
}

private static Element createBody(String city, Document res) {
Element body = res.createElement("s:Body");
body.appendChild(createCityDetails(city, res));
return body;
}

private static Element createCityDetails(String city, Document res) {
Element details = res.createElement("cs:getCityResponse");
details.appendChild(createCountry(city, res));
details.appendChild(createPopulation(city, res));
return details;
}

private static Element createPopulation(String city, Document res) {
Element pop = res.createElement("population");
pop.appendChild(res.createTextNode(String.valueOf(cityMap.get(city).population)));
return pop;
}

private static Element createCountry(String city, Document res) {
Element country = res.createElement("country");
country.appendChild(res.createTextNode(cityMap.get(city).country));
return country;
}

private static DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
return DocumentBuilderFactory.newInstance().newDocumentBuilder();
}

public record City(String name, int population, String country) {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.xml.transform.OutputKeys.*;


/**
Expand Down Expand Up @@ -92,10 +93,10 @@ public static String documentToString(Document doc) {
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(METHOD, "xml");
transformer.setOutputProperty(INDENT, "no");
transformer.setOutputProperty(ENCODING, "UTF-8");
transformer.transform(new DOMSource(doc), new StreamResult(sw));
return sw.toString();
} catch (Exception ex) {
Expand Down
17 changes: 6 additions & 11 deletions core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,12 @@ public static boolean isFault(XMLInputFactory xmlInputFactory, XOPReconstitutor

String expected;
switch (state) {
case 0:
expected = "Envelope";
break;
case 1:
expected = "Body";
break;
case 2:
expected = "Fault";
break;
default:
return false;
case 0 -> expected = "Envelope";
case 1 -> expected = "Body";
case 2 -> expected = "Fault";
default -> {
return false;
}
}
if (expected.equals(name.getLocalPart())) {
if (state == 2)
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/java/com/predic8/membrane/core/util/XMLUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.predic8.membrane.core.util;

import org.w3c.dom.*;

import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import java.io.*;

import static javax.xml.transform.OutputKeys.INDENT;
import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION;

public class XMLUtil {

// TODO 2. param boolean indent
// Write Test
public static String xml2string(Document doc) throws TransformerException {
TransformerFactory tfFactory = TransformerFactory.newInstance(); // Comment ThreadSafe? with URL
Transformer tf = tfFactory.newTransformer();
tf.setOutputProperty(OMIT_XML_DECLARATION, "yes");

tf.setOutputProperty(INDENT, "yes");
tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

StringWriter writer = new StringWriter();
tf.transform(new DOMSource(doc), new StreamResult(writer));
return writer.toString();
}
}
55 changes: 55 additions & 0 deletions core/src/main/resources/wsdl/city.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="https://predic8.de/cities"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
targetNamespace="https://predic8.de/cities" name="cities">
<wsdl:types>
<xsd:schema targetNamespace="https://predic8.de/cities">
<xsd:element name="getCity">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="getCityResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="country" type="xsd:string" />
<xsd:element name="population" type="xsd:integer" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="City">
<wsdl:part name="getCity" element="tns:getCity"/>
</wsdl:message>
<wsdl:message name="CityResponse">
<wsdl:part name="getCityResponse" element="tns:getCityResponse"/>
</wsdl:message>
<wsdl:portType name="CityPort">
<wsdl:operation name="getCity">
<wsdl:input message="tns:City"/>
<wsdl:output message="tns:CityResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CitySoapBinding" type="tns:CityPort">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getCity">
<soap:operation soapAction="https://predic8.de/cities"/>
<wsdl:input>
<soap:body use="literal" namespace="https://predic8.de/cities"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal" namespace="https://predic8.de/cities"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="CityService">
<wsdl:port name="CityPort" binding="tns:CitySoapBinding">
<soap:address location="http://localhost:2000/cities"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Loading
Loading