From bffff2def3333515712c0bd45dffd4da45a9bc0a Mon Sep 17 00:00:00 2001 From: thomas Date: Tue, 26 May 2026 15:26:48 +0200 Subject: [PATCH 1/4] Handle empty WSDL messages and skip validation for empty responses --- .../AbstractXMLSchemaValidator.java | 54 +++++++++------- .../WSDLMessageElementExtractor.java | 19 +++--- .../schemavalidation/WSDLValidator.java | 61 +++++++++++-------- .../core/util/wsdl/parser/Message.java | 7 ++- .../core/util/soap/WSDLParserTest.java | 35 ++++++++--- 5 files changed, 111 insertions(+), 65 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/AbstractXMLSchemaValidator.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/AbstractXMLSchemaValidator.java index 7c4ebd77c8..9edd089da5 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/AbstractXMLSchemaValidator.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/AbstractXMLSchemaValidator.java @@ -14,28 +14,36 @@ package com.predic8.membrane.core.interceptor.schemavalidation; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.interceptor.*; -import com.predic8.membrane.core.multipart.*; -import com.predic8.membrane.core.resolver.*; -import com.predic8.membrane.core.util.*; -import org.jetbrains.annotations.*; -import org.slf4j.*; -import org.w3c.dom.*; -import org.xml.sax.*; - -import javax.xml.transform.*; -import javax.xml.transform.dom.*; -import javax.xml.validation.*; -import java.io.*; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.*; - -import static com.predic8.membrane.annot.Constants.*; -import static com.predic8.membrane.core.http.Header.*; -import static com.predic8.membrane.core.interceptor.Outcome.*; +import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.core.http.Message; +import com.predic8.membrane.core.interceptor.Interceptor; +import com.predic8.membrane.core.interceptor.Outcome; +import com.predic8.membrane.core.multipart.XOPReconstitutor; +import com.predic8.membrane.core.resolver.ResolverMap; +import com.predic8.membrane.core.util.ConfigurationException; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.atomic.AtomicLong; + +import static com.predic8.membrane.annot.Constants.XSD_NS; +import static com.predic8.membrane.core.http.Header.VALIDATION_ERROR_SOURCE; +import static com.predic8.membrane.core.interceptor.Outcome.ABORT; +import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; public abstract class AbstractXMLSchemaValidator extends AbstractMessageValidator { @@ -78,7 +86,7 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep var exceptions = new ArrayList(); var preliminaryError = getPreliminaryError(xopr, msg); if (preliminaryError == null) { - List vals = validators.take(); + var vals = validators.take(); try { // the message must be valid for one schema embedded into WSDL for (var validator : vals) { diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractor.java index 5eab122873..c11ecac8a2 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractor.java @@ -15,16 +15,18 @@ package com.predic8.membrane.core.interceptor.schemavalidation; import com.predic8.membrane.core.util.wsdl.parser.*; -import com.predic8.membrane.core.util.wsdl.parser.Operation.*; -import org.jetbrains.annotations.*; +import com.predic8.membrane.core.util.wsdl.parser.Operation.Direction; +import org.jetbrains.annotations.NotNull; -import javax.xml.namespace.*; +import javax.xml.namespace.QName; import java.util.*; -import java.util.stream.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.*; -import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.*; -import static java.util.stream.Collectors.*; +import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.RPC; +import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.INPUT; +import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.OUTPUT; +import static java.util.stream.Collectors.toSet; public class WSDLMessageElementExtractor { @@ -108,7 +110,8 @@ private static String getElementNameRPC(Operation operation, Direction direction .flatMap(Collection::stream) .map(op -> op.getMessagesByDirection(direction)) .flatMap(Collection::stream) - .map(Message::getPart); + .map(Message::getPart) + .filter(Objects::nonNull); // Message can have no parts. } private record PortTypesByStyle(List portTypesRPC, List portTypesDocument) { diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidator.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidator.java index 94adb8eb67..92fac850b1 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidator.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidator.java @@ -14,33 +14,38 @@ package com.predic8.membrane.core.interceptor.schemavalidation; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; +import com.predic8.membrane.core.exchange.Exchange; import com.predic8.membrane.core.http.Message; -import com.predic8.membrane.core.interceptor.*; -import com.predic8.membrane.core.multipart.*; -import com.predic8.membrane.core.resolver.*; -import com.predic8.membrane.core.util.*; -import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; -import org.jetbrains.annotations.*; -import org.slf4j.*; -import org.w3c.dom.*; -import org.xml.sax.*; - -import javax.xml.namespace.*; -import javax.xml.parsers.*; -import javax.xml.transform.*; -import java.io.*; -import java.util.*; - -import static com.predic8.membrane.annot.Constants.SoapVersion.*; +import com.predic8.membrane.core.interceptor.Interceptor; +import com.predic8.membrane.core.interceptor.Outcome; +import com.predic8.membrane.core.multipart.XOPReconstitutor; +import com.predic8.membrane.core.resolver.ResolverMap; +import com.predic8.membrane.core.resolver.ResourceRetrievalException; +import com.predic8.membrane.core.util.ConfigurationException; +import com.predic8.membrane.core.util.MessageUtil; +import com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; + +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.predic8.membrane.annot.Constants.SoapVersion.SOAP11; +import static com.predic8.membrane.annot.Constants.SoapVersion.SOAP12; import static com.predic8.membrane.core.http.Header.VALIDATION_ERROR_SOURCE; -import static com.predic8.membrane.core.interceptor.Outcome.*; -import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLMessageElementExtractor.*; +import static com.predic8.membrane.core.interceptor.Outcome.ABORT; +import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLMessageElementExtractor.getPossibleRequestElements; -import static com.predic8.membrane.core.util.SOAPUtil.FaultCode.*; +import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLMessageElementExtractor.getPossibleResponseElements; +import static com.predic8.membrane.core.util.SOAPUtil.FaultCode.Client; import static com.predic8.membrane.core.util.SOAPUtil.*; -import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*; +import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.SOAP_11; +import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.SOAP_12; public class WSDLValidator extends AbstractXMLSchemaValidator { @@ -95,6 +100,12 @@ public String getName() { @Override public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Exception { var message = exc.getMessage(flow); + + if (flow == Interceptor.Flow.RESPONSE && message.isBodyEmpty() ) { + log.info("Skipping validation of empty response."); + return CONTINUE; + } + var result = analyseSOAPMessage(xopr, message); if (!result.isSOAP()) { @@ -119,11 +130,11 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep } } - if (message instanceof Request && !isPossibleRequestElement(result.soapElement())) { + if (flow == Interceptor.Flow.REQUEST && !isPossibleRequestElement(result.soapElement())) { setErrorResponse(exc, "%s is not a valid request element. Possible elements are %s".formatted(result.soapElement(), requestElements)); return ABORT; } - if (message instanceof Response && !result.isFault() && !isPossibleResponseElement(result.soapElement())) { + if (flow == Interceptor.Flow.RESPONSE && !result.isFault() && !isPossibleResponseElement(result.soapElement())) { setErrorResponse(exc, "%s is not a valid response element. Possible elements are %s".formatted(result.soapElement(), responseElements)); return ABORT; } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java index df4c4a4fae..d6fb1e962b 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java @@ -14,9 +14,9 @@ package com.predic8.membrane.core.util.wsdl.parser; -import org.w3c.dom.*; +import org.w3c.dom.Node; -import java.util.*; +import java.util.List; public class Message extends WSDLElement { @@ -29,6 +29,9 @@ public Message(WSDLParserContext ctx, Node node) { * @return */ public Part getPart() { + if (getParts().isEmpty()) { + return null; + } return getParts().getFirst(); } diff --git a/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java b/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java index b3242a6a73..8d4778d35b 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java @@ -14,15 +14,17 @@ package com.predic8.membrane.core.util.soap; -import com.predic8.membrane.core.resolver.*; -import com.predic8.membrane.core.util.wsdl.parser.*; -import com.predic8.membrane.core.util.wsdl.parser.schema.*; -import org.junit.jupiter.api.*; +import com.predic8.membrane.core.resolver.ResolverMap; +import com.predic8.membrane.core.util.wsdl.parser.Definitions; +import com.predic8.membrane.core.util.wsdl.parser.schema.SchemaElement; +import org.junit.jupiter.api.Test; -import java.util.*; +import java.util.List; -import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.*; -import static org.junit.jupiter.api.Assertions.*; +import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.DOCUMENT; +import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.RPC; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; class WSDLParserTest { @@ -194,4 +196,23 @@ void soap12() throws Exception { assertEquals("SayHelloResponse", definitions.getMessages().getLast().getName()); } + /** + * Message without a part + * + * + * @throws Exception + */ + @Test + void emptyMessage() throws Exception { + var definitions = Definitions.parse(new ResolverMap(), "classpath:/ws/special/empty-message.wsdl"); + var schema = definitions.getSchemas().getFirst(); + var schemaElements = schema.getSchemaElements(); + var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList(); + assertEquals(List.of("getCity"), schemaElementNames); + + assertEquals(2, definitions.getMessages().size()); + var msg = definitions.getMessages().stream().filter(m -> "CityResponse".equals(m.getName())).findFirst().get(); + assertEquals(0, msg.getParts().size()); + assertEquals("CityResponse", msg.getName()); + } } \ No newline at end of file From 68662229f6a341d60489de9df2fa145153795cde Mon Sep 17 00:00:00 2001 From: thomas Date: Tue, 26 May 2026 15:27:41 +0200 Subject: [PATCH 2/4] Enhance WSDL message handling with null safety checks and suppressed warnings --- .../com/predic8/membrane/core/util/wsdl/parser/Message.java | 2 +- .../com/predic8/membrane/core/util/soap/WSDLParserTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java index d6fb1e962b..9df232b6fb 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java @@ -26,7 +26,7 @@ public Message(WSDLParserContext ctx, Node node) { /** * Document style only uses one part. - * @return + * @return first part or null if no part is defined. */ public Part getPart() { if (getParts().isEmpty()) { diff --git a/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java b/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java index 8d4778d35b..25a6844efb 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java @@ -200,8 +200,8 @@ void soap12() throws Exception { * Message without a part * * - * @throws Exception */ + @SuppressWarnings("OptionalGetWithoutIsPresent") @Test void emptyMessage() throws Exception { var definitions = Definitions.parse(new ResolverMap(), "classpath:/ws/special/empty-message.wsdl"); From 05274fb2c406723ac6694846fd31d1e51b71c73e Mon Sep 17 00:00:00 2001 From: thomas Date: Tue, 26 May 2026 15:31:09 +0200 Subject: [PATCH 3/4] Add WSDL file for testing empty message handling --- .../resources/ws/special/empty-message.wsdl | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 core/src/test/resources/ws/special/empty-message.wsdl diff --git a/core/src/test/resources/ws/special/empty-message.wsdl b/core/src/test/resources/ws/special/empty-message.wsdl new file mode 100644 index 0000000000..a5f898a75d --- /dev/null +++ b/core/src/test/resources/ws/special/empty-message.wsdl @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 13c3a09e5433c874d8dbcd6395b4e28046aeefb8 Mon Sep 17 00:00:00 2001 From: thomas Date: Tue, 26 May 2026 15:52:57 +0200 Subject: [PATCH 4/4] Add assertion for null message part in WSDLParserTest --- .../com/predic8/membrane/core/util/soap/WSDLParserTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java b/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java index 25a6844efb..03455111ec 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java @@ -23,8 +23,7 @@ import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.DOCUMENT; import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.RPC; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; class WSDLParserTest { @@ -213,6 +212,7 @@ void emptyMessage() throws Exception { assertEquals(2, definitions.getMessages().size()); var msg = definitions.getMessages().stream().filter(m -> "CityResponse".equals(m.getName())).findFirst().get(); assertEquals(0, msg.getParts().size()); + assertNull(msg.getPart()); assertEquals("CityResponse", msg.getName()); } } \ No newline at end of file