Skip to content

Commit f44812c

Browse files
authored
WSDL Validator: Support for Emtpy Messages (#2955)
* Handle empty WSDL messages and skip validation for empty responses * Enhance WSDL message handling with null safety checks and suppressed warnings * Add WSDL file for testing empty message handling * Add assertion for null message part in WSDLParserTest
1 parent 5a3d293 commit f44812c

6 files changed

Lines changed: 157 additions & 65 deletions

File tree

core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/AbstractXMLSchemaValidator.java

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,36 @@
1414

1515
package com.predic8.membrane.core.interceptor.schemavalidation;
1616

17-
import com.predic8.membrane.core.exchange.*;
18-
import com.predic8.membrane.core.http.*;
19-
import com.predic8.membrane.core.interceptor.*;
20-
import com.predic8.membrane.core.multipart.*;
21-
import com.predic8.membrane.core.resolver.*;
22-
import com.predic8.membrane.core.util.*;
23-
import org.jetbrains.annotations.*;
24-
import org.slf4j.*;
25-
import org.w3c.dom.*;
26-
import org.xml.sax.*;
27-
28-
import javax.xml.transform.*;
29-
import javax.xml.transform.dom.*;
30-
import javax.xml.validation.*;
31-
import java.io.*;
32-
import java.util.*;
33-
import java.util.concurrent.*;
34-
import java.util.concurrent.atomic.*;
35-
36-
import static com.predic8.membrane.annot.Constants.*;
37-
import static com.predic8.membrane.core.http.Header.*;
38-
import static com.predic8.membrane.core.interceptor.Outcome.*;
17+
import com.predic8.membrane.core.exchange.Exchange;
18+
import com.predic8.membrane.core.http.Message;
19+
import com.predic8.membrane.core.interceptor.Interceptor;
20+
import com.predic8.membrane.core.interceptor.Outcome;
21+
import com.predic8.membrane.core.multipart.XOPReconstitutor;
22+
import com.predic8.membrane.core.resolver.ResolverMap;
23+
import com.predic8.membrane.core.util.ConfigurationException;
24+
import org.jetbrains.annotations.NotNull;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
import org.w3c.dom.Element;
28+
import org.xml.sax.SAXException;
29+
import org.xml.sax.SAXParseException;
30+
31+
import javax.xml.transform.Source;
32+
import javax.xml.transform.dom.DOMSource;
33+
import javax.xml.validation.SchemaFactory;
34+
import javax.xml.validation.Validator;
35+
import java.io.InputStream;
36+
import java.util.ArrayList;
37+
import java.util.LinkedHashMap;
38+
import java.util.List;
39+
import java.util.Map;
40+
import java.util.concurrent.ArrayBlockingQueue;
41+
import java.util.concurrent.atomic.AtomicLong;
42+
43+
import static com.predic8.membrane.annot.Constants.XSD_NS;
44+
import static com.predic8.membrane.core.http.Header.VALIDATION_ERROR_SOURCE;
45+
import static com.predic8.membrane.core.interceptor.Outcome.ABORT;
46+
import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE;
3947

4048
public abstract class AbstractXMLSchemaValidator extends AbstractMessageValidator {
4149

@@ -78,7 +86,7 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep
7886
var exceptions = new ArrayList<Exception>();
7987
var preliminaryError = getPreliminaryError(xopr, msg);
8088
if (preliminaryError == null) {
81-
List<Validator> vals = validators.take();
89+
var vals = validators.take();
8290
try {
8391
// the message must be valid for one schema embedded into WSDL
8492
for (var validator : vals) {

core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractor.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515
package com.predic8.membrane.core.interceptor.schemavalidation;
1616

1717
import com.predic8.membrane.core.util.wsdl.parser.*;
18-
import com.predic8.membrane.core.util.wsdl.parser.Operation.*;
19-
import org.jetbrains.annotations.*;
18+
import com.predic8.membrane.core.util.wsdl.parser.Operation.Direction;
19+
import org.jetbrains.annotations.NotNull;
2020

21-
import javax.xml.namespace.*;
21+
import javax.xml.namespace.QName;
2222
import java.util.*;
23-
import java.util.stream.*;
23+
import java.util.stream.Collectors;
24+
import java.util.stream.Stream;
2425

25-
import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.*;
26-
import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.*;
27-
import static java.util.stream.Collectors.*;
26+
import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.RPC;
27+
import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.INPUT;
28+
import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.OUTPUT;
29+
import static java.util.stream.Collectors.toSet;
2830

2931
public class WSDLMessageElementExtractor {
3032

@@ -108,7 +110,8 @@ private static String getElementNameRPC(Operation operation, Direction direction
108110
.flatMap(Collection::stream)
109111
.map(op -> op.getMessagesByDirection(direction))
110112
.flatMap(Collection::stream)
111-
.map(Message::getPart);
113+
.map(Message::getPart)
114+
.filter(Objects::nonNull); // Message can have no parts.
112115
}
113116

114117
private record PortTypesByStyle(List<PortType> portTypesRPC, List<PortType> portTypesDocument) {

core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidator.java

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,38 @@
1414

1515
package com.predic8.membrane.core.interceptor.schemavalidation;
1616

17-
import com.predic8.membrane.core.exchange.*;
18-
import com.predic8.membrane.core.http.*;
17+
import com.predic8.membrane.core.exchange.Exchange;
1918
import com.predic8.membrane.core.http.Message;
20-
import com.predic8.membrane.core.interceptor.*;
21-
import com.predic8.membrane.core.multipart.*;
22-
import com.predic8.membrane.core.resolver.*;
23-
import com.predic8.membrane.core.util.*;
24-
import com.predic8.membrane.core.util.wsdl.parser.Definitions.*;
25-
import org.jetbrains.annotations.*;
26-
import org.slf4j.*;
27-
import org.w3c.dom.*;
28-
import org.xml.sax.*;
29-
30-
import javax.xml.namespace.*;
31-
import javax.xml.parsers.*;
32-
import javax.xml.transform.*;
33-
import java.io.*;
34-
import java.util.*;
35-
36-
import static com.predic8.membrane.annot.Constants.SoapVersion.*;
19+
import com.predic8.membrane.core.interceptor.Interceptor;
20+
import com.predic8.membrane.core.interceptor.Outcome;
21+
import com.predic8.membrane.core.multipart.XOPReconstitutor;
22+
import com.predic8.membrane.core.resolver.ResolverMap;
23+
import com.predic8.membrane.core.resolver.ResourceRetrievalException;
24+
import com.predic8.membrane.core.util.ConfigurationException;
25+
import com.predic8.membrane.core.util.MessageUtil;
26+
import com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
import org.w3c.dom.Element;
30+
31+
import javax.xml.namespace.QName;
32+
import javax.xml.transform.Source;
33+
import java.io.InputStream;
34+
import java.util.List;
35+
import java.util.Map;
36+
import java.util.Set;
37+
38+
import static com.predic8.membrane.annot.Constants.SoapVersion.SOAP11;
39+
import static com.predic8.membrane.annot.Constants.SoapVersion.SOAP12;
3740
import static com.predic8.membrane.core.http.Header.VALIDATION_ERROR_SOURCE;
38-
import static com.predic8.membrane.core.interceptor.Outcome.*;
39-
import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLMessageElementExtractor.*;
41+
import static com.predic8.membrane.core.interceptor.Outcome.ABORT;
42+
import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE;
4043
import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLMessageElementExtractor.getPossibleRequestElements;
41-
import static com.predic8.membrane.core.util.SOAPUtil.FaultCode.*;
44+
import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLMessageElementExtractor.getPossibleResponseElements;
45+
import static com.predic8.membrane.core.util.SOAPUtil.FaultCode.Client;
4246
import static com.predic8.membrane.core.util.SOAPUtil.*;
43-
import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*;
47+
import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.SOAP_11;
48+
import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.SOAP_12;
4449

4550
public class WSDLValidator extends AbstractXMLSchemaValidator {
4651

@@ -95,6 +100,12 @@ public String getName() {
95100
@Override
96101
public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Exception {
97102
var message = exc.getMessage(flow);
103+
104+
if (flow == Interceptor.Flow.RESPONSE && message.isBodyEmpty() ) {
105+
log.info("Skipping validation of empty response.");
106+
return CONTINUE;
107+
}
108+
98109
var result = analyseSOAPMessage(xopr, message);
99110

100111
if (!result.isSOAP()) {
@@ -119,11 +130,11 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep
119130
}
120131
}
121132

122-
if (message instanceof Request && !isPossibleRequestElement(result.soapElement())) {
133+
if (flow == Interceptor.Flow.REQUEST && !isPossibleRequestElement(result.soapElement())) {
123134
setErrorResponse(exc, "%s is not a valid request element. Possible elements are %s".formatted(result.soapElement(), requestElements));
124135
return ABORT;
125136
}
126-
if (message instanceof Response && !result.isFault() && !isPossibleResponseElement(result.soapElement())) {
137+
if (flow == Interceptor.Flow.RESPONSE && !result.isFault() && !isPossibleResponseElement(result.soapElement())) {
127138
setErrorResponse(exc, "%s is not a valid response element. Possible elements are %s".formatted(result.soapElement(), responseElements));
128139
return ABORT;
129140
}

core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
package com.predic8.membrane.core.util.wsdl.parser;
1616

17-
import org.w3c.dom.*;
17+
import org.w3c.dom.Node;
1818

19-
import java.util.*;
19+
import java.util.List;
2020

2121
public class Message extends WSDLElement {
2222

@@ -26,9 +26,12 @@ public Message(WSDLParserContext ctx, Node node) {
2626

2727
/**
2828
* Document style only uses one part.
29-
* @return
29+
* @return first part or null if no part is defined.
3030
*/
3131
public Part getPart() {
32+
if (getParts().isEmpty()) {
33+
return null;
34+
}
3235
return getParts().getFirst();
3336
}
3437

core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414

1515
package com.predic8.membrane.core.util.soap;
1616

17-
import com.predic8.membrane.core.resolver.*;
18-
import com.predic8.membrane.core.util.wsdl.parser.*;
19-
import com.predic8.membrane.core.util.wsdl.parser.schema.*;
20-
import org.junit.jupiter.api.*;
17+
import com.predic8.membrane.core.resolver.ResolverMap;
18+
import com.predic8.membrane.core.util.wsdl.parser.Definitions;
19+
import com.predic8.membrane.core.util.wsdl.parser.schema.SchemaElement;
20+
import org.junit.jupiter.api.Test;
2121

22-
import java.util.*;
22+
import java.util.List;
2323

24-
import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.*;
24+
import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.DOCUMENT;
25+
import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.RPC;
2526
import static org.junit.jupiter.api.Assertions.*;
2627

2728
class WSDLParserTest {
@@ -194,4 +195,24 @@ void soap12() throws Exception {
194195
assertEquals("SayHelloResponse", definitions.getMessages().getLast().getName());
195196
}
196197

198+
/**
199+
* Message without a part
200+
* <wsdl:message name="CityResponse">
201+
* </wsdl:message>
202+
*/
203+
@SuppressWarnings("OptionalGetWithoutIsPresent")
204+
@Test
205+
void emptyMessage() throws Exception {
206+
var definitions = Definitions.parse(new ResolverMap(), "classpath:/ws/special/empty-message.wsdl");
207+
var schema = definitions.getSchemas().getFirst();
208+
var schemaElements = schema.getSchemaElements();
209+
var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList();
210+
assertEquals(List.of("getCity"), schemaElementNames);
211+
212+
assertEquals(2, definitions.getMessages().size());
213+
var msg = definitions.getMessages().stream().filter(m -> "CityResponse".equals(m.getName())).findFirst().get();
214+
assertEquals(0, msg.getParts().size());
215+
assertNull(msg.getPart());
216+
assertEquals("CityResponse", msg.getName());
217+
}
197218
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
3+
xmlns:cs="https://predic8.de/cities"
4+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5+
xmlns:s="http://schemas.xmlsoap.org/wsdl/soap/"
6+
targetNamespace="https://predic8.de/cities" name="cities">
7+
<wsdl:types>
8+
<xsd:schema targetNamespace="https://predic8.de/cities">
9+
<xsd:element name="getCity">
10+
<xsd:complexType>
11+
<xsd:sequence>
12+
<xsd:element name="name" type="xsd:string"/>
13+
</xsd:sequence>
14+
</xsd:complexType>
15+
</xsd:element>
16+
</xsd:schema>
17+
</wsdl:types>
18+
<wsdl:message name="City">
19+
<wsdl:part name="getCity" element="cs:getCity"/>
20+
</wsdl:message>
21+
<wsdl:message name="CityResponse">
22+
</wsdl:message>
23+
<wsdl:portType name="CityPort">
24+
<wsdl:operation name="getCity">
25+
<wsdl:input message="cs:City"></wsdl:input>
26+
<wsdl:output message="cs:CityResponse"></wsdl:output>
27+
</wsdl:operation>
28+
</wsdl:portType>
29+
<wsdl:binding name="CitySoapBinding" type="cs:CityPort">
30+
<s:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"></s:binding>
31+
<wsdl:operation name="getCity">
32+
<s:operation soapAction="https://predic8.de/cities"></s:operation>
33+
<wsdl:input>
34+
<s:body use="literal" namespace="https://predic8.de/cities"></s:body>
35+
</wsdl:input>
36+
<wsdl:output>
37+
<s:body use="literal" namespace="https://predic8.de/cities"></s:body>
38+
</wsdl:output>
39+
</wsdl:operation>
40+
</wsdl:binding>
41+
<wsdl:service name="CityService">
42+
<wsdl:port name="CityPort" binding="cs:CitySoapBinding">
43+
<s:address location="http://localhost:2001/services/cities"/>
44+
</wsdl:port>
45+
</wsdl:service>
46+
</wsdl:definitions>

0 commit comments

Comments
 (0)