From 78c88cae256b35bbffe55d95c7495d03239a0f27 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Sun, 8 Mar 2026 19:48:59 +0100 Subject: [PATCH 01/28] Add WSDL parsing utilities and schema validation tests - Introduced comprehensive WSDL parsing utilities (`Definitions`, `Binding`, `Service`, etc.). - Added schema parsing components with support for `Include` and `Import`. - Implemented tests for schema validation, WSDL definitions, services, bindings, and operations. - Enhanced SOAP version detection through `WSDLSOAPVersionExtractor`. - Refactored `AuthorizationService` and `ResolverMap` for improved URI handling. --- .../com/predic8/membrane/annot/Constants.java | 4 + .../AuthorizationService.java | 3 +- .../AbstractXMLSchemaValidator.java | 42 +-- .../ValidatorInterceptor.java | 12 +- .../WSDLMessageElementExtractor.java | 94 +++++ .../WSDLSOAPVersionExtractor.java | 46 +++ .../schemavalidation/WSDLSchemaExtractor.java | 93 +++++ .../schemavalidation/WSDLValidator.java | 350 ++++++++---------- .../schemavalidation/XMLSchemaValidator.java | 14 +- .../core/util/wsdl/parser/Address.java | 27 ++ .../core/util/wsdl/parser/Binding.java | 115 ++++++ .../util/wsdl/parser/BindingOperation.java | 24 ++ .../core/util/wsdl/parser/Definitions.java | 157 ++++++++ .../core/util/wsdl/parser/Message.java | 43 +++ .../core/util/wsdl/parser/Operation.java | 108 ++++++ .../membrane/core/util/wsdl/parser/Part.java | 58 +++ .../membrane/core/util/wsdl/parser/Port.java | 68 ++++ .../core/util/wsdl/parser/PortType.java | 43 +++ .../core/util/wsdl/parser/Service.java | 42 +++ .../membrane/core/util/wsdl/parser/Type.java | 9 + .../core/util/wsdl/parser/WSDLElement.java | 28 ++ .../util/wsdl/parser/WSDLParserContext.java | 59 +++ .../core/util/wsdl/parser/WSDLParserUtil.java | 29 ++ .../parser/schema/AbstractIncludeImport.java | 34 ++ .../core/util/wsdl/parser/schema/Import.java | 22 ++ .../core/util/wsdl/parser/schema/Include.java | 21 ++ .../core/util/wsdl/parser/schema/Schema.java | 110 ++++++ .../wsdl/parser/schema/SchemaElement.java | 19 + .../SOAPMessageValidatorInterceptorTest.java | 10 +- .../WSDLMessageElementExtractorTest.java | 69 ++++ .../WSDLSchemaExtractorTest.java | 53 +++ .../schemavalidation/WSDLValidatorTest.java | 22 +- .../membrane/core/resolver/ResolverTest.java | 2 +- .../core/util/soap/WSDLParserTest.java | 95 +++++ .../core/util/wsdl/parser/OperationTest.java | 17 + .../resources/validation/inline-anytype.wsdl | 123 +++--- .../test/resources/ws/include/include.wsdl | 73 ++++ .../src/test/resources/ws/include/include.xsd | 18 + .../xsd/inc/MessageStructureTypes2.xsd | 14 + .../include/xsd/inc/shared/xsd/DataTypes.xsd | 11 + .../xsd/inc/shared/xsd/inc/BaseTypes.xsd | 9 + .../resources/ws/include/xsd/messages.xsd | 10 + 42 files changed, 1877 insertions(+), 323 deletions(-) create mode 100644 core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractor.java create mode 100644 core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSOAPVersionExtractor.java create mode 100644 core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java create mode 100644 core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java create mode 100644 core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractorTest.java create mode 100644 core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java create mode 100644 core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/OperationTest.java create mode 100644 core/src/test/resources/ws/include/include.wsdl create mode 100644 core/src/test/resources/ws/include/include.xsd create mode 100644 core/src/test/resources/ws/include/xsd/inc/MessageStructureTypes2.xsd create mode 100644 core/src/test/resources/ws/include/xsd/inc/shared/xsd/DataTypes.xsd create mode 100644 core/src/test/resources/ws/include/xsd/inc/shared/xsd/inc/BaseTypes.xsd create mode 100644 core/src/test/resources/ws/include/xsd/messages.xsd diff --git a/annot/src/main/java/com/predic8/membrane/annot/Constants.java b/annot/src/main/java/com/predic8/membrane/annot/Constants.java index bebbe4ba45..9a7835e624 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/Constants.java +++ b/annot/src/main/java/com/predic8/membrane/annot/Constants.java @@ -14,6 +14,8 @@ package com.predic8.membrane.annot; +import com.sun.net.httpserver.*; + import javax.xml.namespace.*; import java.io.*; import java.util.*; @@ -61,6 +63,8 @@ public class Constants { public static final String HTTP_VERSION_11 = "1.1"; + public static final String WSDL11_NS = "http://schemas.xmlsoap.org/wsdl/"; + public static final String WSDL_SOAP11_NS = "http://schemas.xmlsoap.org/wsdl/soap/"; public static final String WSDL_SOAP12_NS = "http://schemas.xmlsoap.org/wsdl/soap12/"; public static final String WSDL_HTTP_NS = "http://schemas.xmlsoap.org/wsdl/http/"; diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java b/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java index 3dd0fbe998..219c362249 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java @@ -27,6 +27,7 @@ import com.predic8.membrane.core.transport.http.*; import com.predic8.membrane.core.transport.http.client.*; import com.predic8.membrane.core.transport.ssl.*; +import com.predic8.membrane.core.util.*; import jakarta.mail.internet.*; import org.jose4j.jwt.*; import org.jose4j.lang.*; @@ -318,7 +319,7 @@ private String createClientToken(FlowContext flowContext) { } public InputStream resolve(ResolverMap rm, String baseLocation, String url) throws Exception { - url = ResolverMap.combine(baseLocation, url); + url = ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, baseLocation, url); // ask the internal httpClient (might be proxied/authenticated), if HTTP if (url.startsWith("http")) { var exc = get(url).buildExchange(); 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 2667ec39ef..716ecc98ee 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 @@ -20,13 +20,13 @@ import com.predic8.membrane.core.multipart.*; import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.*; -import com.predic8.schema.Schema; import org.jetbrains.annotations.*; import org.slf4j.*; +import org.w3c.dom.*; import org.xml.sax.*; import javax.xml.transform.*; -import javax.xml.transform.stream.*; +import javax.xml.transform.dom.*; import javax.xml.validation.*; import java.io.*; import java.util.*; @@ -46,20 +46,14 @@ public abstract class AbstractXMLSchemaValidator extends AbstractMessageValidato protected final String location; protected final ResolverMap resolver; protected final ValidatorInterceptor.FailureHandler failureHandler; - private final boolean skipFaults; protected final AtomicLong valid = new AtomicLong(); protected final AtomicLong invalid = new AtomicLong(); public AbstractXMLSchemaValidator(ResolverMap resolver, String location, ValidatorInterceptor.FailureHandler failureHandler) { - this(resolver, location, failureHandler, false); - } - - public AbstractXMLSchemaValidator(ResolverMap resolver, String location, ValidatorInterceptor.FailureHandler failureHandler, boolean skipFaults) { this.location = location; this.resolver = resolver; this.failureHandler = failureHandler; - this.skipFaults = skipFaults; xopr = new XOPReconstitutor(); } @@ -100,10 +94,6 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep } else { exceptions.add(new Exception(preliminaryError)); } - if (skipFaults && isFault(msg)) { - valid.incrementAndGet(); - return CONTINUE; - } String errorMsg = getErrorMsg(exceptions); // Errors als simple String if (failureHandler != null) { failureHandler.handleFailure(errorMsg, exc); @@ -116,33 +106,28 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep } protected List createValidators() { - SchemaFactory sf = SchemaFactory.newInstance(XSD_NS); + var sf = SchemaFactory.newInstance(XSD_NS); sf.setResourceResolver(resolver.toLSResourceResolver()); - List validators = new ArrayList<>(); - for (Schema schema : getSchemas()) { - log.debug("Creating validator for schema: {}", schema); - validators.add(getValidator(schema, sf)); + var validators = new ArrayList(); + for (var schema : getSchemas()) { + log.debug("Creating validator for schema: {}", location); + validators.add(createValidator(schema, sf)); } return validators; } - private @NotNull Validator getValidator(Schema schema, SchemaFactory sf) { + private @NotNull Validator createValidator(Element schema, SchemaFactory sf) { try { - Validator validator = sf.newSchema(getStreamSource(schema)).newValidator(); - validator.setResourceResolver(resolver.toLSResourceResolver()); + DOMSource source = new DOMSource(schema); + source.setSystemId(location); + var validator = sf.newSchema(source).newValidator(); validator.setErrorHandler(new SchemaValidatorErrorHandler()); return validator; } catch (SAXException e) { - throw new ConfigurationException("Cannot read schema %s.".formatted(schema.getName()),e); + throw new ConfigurationException("Cannot read schema %s.".formatted(location),e); } } - private @NotNull StreamSource getStreamSource(Schema schema) { - StreamSource ss = new StreamSource(new StringReader(schema.getAsString())); - ss.setSystemId(location); - return ss; - } - private String getErrorMsg(List excs) { StringBuilder buf = new StringBuilder(); buf.append("%s: ".formatted(getErrorTitle())); @@ -177,13 +162,12 @@ public long getInvalid() { return invalid.get(); } - protected abstract List getSchemas(); + protected abstract List getSchemas(); protected abstract Source getMessageBody(InputStream input); protected abstract void setErrorResponse(Exchange exchange, String message); protected abstract void setErrorResponse(Exchange exchange, Interceptor.Flow flow,List exceptions); - protected abstract boolean isFault(Message msg); protected abstract String getPreliminaryError(XOPReconstitutor xopr, Message msg); @Override diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java index 7e9b5290cb..8c5cd2b473 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java @@ -46,7 +46,7 @@ @MCElement(name = "validator") public class ValidatorInterceptor extends AbstractInterceptor implements ApplicationContextAware { - private static final Logger log = LoggerFactory.getLogger(ValidatorInterceptor.class.getName()); + private static final Logger log = LoggerFactory.getLogger(ValidatorInterceptor.class); private String wsdl; private String schema; @@ -99,22 +99,22 @@ private MessageValidator getMessageValidator() throws Exception { if (wsdl != null) { if (schemaMappings != null) logIgnoringRefSchemas(); - return new WSDLValidator(resourceResolver, combine(getBaseLocation(), wsdl), serviceName, createFailureHandler(), skipFaults); + return new WSDLValidator(resourceResolver, ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), wsdl), serviceName, createFailureHandler(), skipFaults); } if (schema != null) { if (schemaMappings != null) logIgnoringRefSchemas(); - return new XMLSchemaValidator(resourceResolver, combine(getBaseLocation(), schema), createFailureHandler()); + return new XMLSchemaValidator(resourceResolver, ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), schema), createFailureHandler()); } if (jsonSchema != null) { - return new JSONYAMLSchemaValidator(resourceResolver, combine(getBaseLocation(), jsonSchema), createFailureHandler(), schemaVersion) {{ + return new JSONYAMLSchemaValidator(resourceResolver, ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), jsonSchema), createFailureHandler(), schemaVersion) {{ if(schemaMappings != null) setSchemaMappings(schemaMappings.getSchemaMap()); }}; } if (schematron != null) { if (schemaMappings != null) logIgnoringRefSchemas(); - return new SchematronValidator(combine(getBaseLocation(), schematron), createFailureHandler(), router, applicationContext); + return new SchematronValidator(ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), schematron), createFailureHandler(), router, applicationContext); } WSDLValidator validator = getWsdlValidatorFromSOAPProxy(); @@ -131,7 +131,7 @@ private static void logIgnoringRefSchemas() { if(soapProxy == null) return null; wsdl = soapProxy.getWsdl(); name = "soap validator"; - return new WSDLValidator(resourceResolver, combine(getBaseLocation(), wsdl), serviceName, createFailureHandler(), skipFaults); + return new WSDLValidator(resourceResolver, ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), wsdl), serviceName, createFailureHandler(), skipFaults); } private @Nullable String getBaseLocation() { 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 new file mode 100644 index 0000000000..389706b8a0 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractor.java @@ -0,0 +1,94 @@ +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 org.slf4j.*; + +import javax.xml.namespace.*; +import java.util.*; +import java.util.stream.*; + +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.*; + +public class WSDLMessageElementExtractor { + + private static final Logger log = LoggerFactory.getLogger(WSDLMessageElementExtractor.class); + + public static Set getPossibleRequestElements(Definitions definitions, String serviceName) { + return getPossibleElements(definitions, INPUT, serviceName); + } + + public static Set getPossibleResponseElements(Definitions definitions, String serviceName) { + return getPossibleElements(definitions, OUTPUT, serviceName); + } + + public static Set getPossibleElements(Definitions definitions, Direction direction, String serviceName) { + PortTypesByStyle portTypes; + if (definitions.getServices().isEmpty()) { + portTypes = new PortTypesByStyle(Collections.emptyList(), definitions.getPortTypes()); + } else { + portTypes = getPortTypesByStyle(definitions, serviceName); + } + + var operationNamesRPC = portTypes.portTypesRPC().stream().map(pt -> pt.getOperations()) + .flatMap(Collection::stream) + .map(op -> new QName(definitions.getTargetNamespace(), getElementNameRPC(op,direction))) + .collect(toSet()); + + + Set namesDocumentStyle = getParts(direction, portTypes.portTypesDocument()) + .map(part -> part.getElementQName()) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + namesDocumentStyle.addAll(operationNamesRPC); + + return namesDocumentStyle; + } + + private static @NotNull PortTypesByStyle getPortTypesByStyle(Definitions definitions, String serviceName) { + List portTypesRPC = new ArrayList<>(); + List portTypesDocument = new ArrayList<>(); + + for (var binding : getBindings(definitions, serviceName)) { + if (binding.getStyle() == RPC) { + portTypesRPC.add(binding.getPortType()); + } + portTypesDocument.add(binding.getPortType()); + } + return new PortTypesByStyle(portTypesRPC, portTypesDocument); + } + + private record PortTypesByStyle(List portTypesRPC, List portTypesDocument) { + } + + private static @NotNull List getBindings(Definitions definitions, String serviceName) { + List services; + if (serviceName != null) { + services = List.of(definitions.getService(serviceName)); + } else { + services = definitions.getServices(); + } + return services.stream().flatMap(s -> s.getPorts().stream()) + .map(port -> port.getBinding()).toList(); + } + + private static String getElementNameRPC(Operation operation, Direction direction) { + if (direction == INPUT) { + return operation.getName(); + } + return operation.getName() + "Response"; + } + + private static @NotNull Stream getParts(Direction direction, List result) { + return result.stream().map(pt -> pt.getOperations()) + .flatMap(Collection::stream) + .map(op -> op.getMessagesByDirection(direction)) + .flatMap(Collection::stream).toList().stream().map(message -> message.getPart()); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSOAPVersionExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSOAPVersionExtractor.java new file mode 100644 index 0000000000..3282515dc2 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSOAPVersionExtractor.java @@ -0,0 +1,46 @@ +package com.predic8.membrane.core.interceptor.schemavalidation; + +import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; +import org.w3c.dom.*; + +import java.util.*; + +import static com.predic8.membrane.annot.Constants.*; +import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*; + +public class WSDLSOAPVersionExtractor { + + public static Set getSOAPVersions(Element wsdl) { + var result = new HashSet(); + var ports = wsdl.getElementsByTagNameNS(WSDL11_NS, "port"); + + // No port element, assume abstract WSDL, all versions could be valid + if (ports.getLength() == 0) { + return Set.of(SOAP_11, SOAP_12); + } + + for (int i = 0; i < ports.getLength(); i++) { + + Element port = (Element) ports.item(i); + NodeList children = port.getChildNodes(); + + for (int j = 0; j < children.getLength(); j++) { + + Node n = children.item(j); + if (n.getNodeType() != Node.ELEMENT_NODE) + continue; + + String ns = n.getNamespaceURI(); + + if ("http://schemas.xmlsoap.org/wsdl/soap/".equals(ns)) { + result.add(SOAP_11); + } + + if ("http://schemas.xmlsoap.org/wsdl/soap12/".equals(ns)) { + result.add(SOAP_12); + } + } + } + return result; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java new file mode 100644 index 0000000000..7b359a9358 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java @@ -0,0 +1,93 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + +package com.predic8.membrane.core.interceptor.schemavalidation; + +import org.w3c.dom.*; +import org.xml.sax.*; + +import javax.xml.*; +import javax.xml.parsers.*; +import javax.xml.transform.*; +import javax.xml.transform.dom.*; +import java.io.*; +import java.util.*; + +import static javax.xml.XMLConstants.*; + +public final class WSDLSchemaExtractor { + + private static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"; // TODO + private static final String XMLNS_NS = XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + + private WSDLSchemaExtractor() { + } + + public static List getSchemas(Element wsdl) { + try { + var definitions = wsdl; //.getDocumentElement(); + var result = new ArrayList(); + var schemas = wsdl.getElementsByTagNameNS(XML_SCHEMA_NS, "schema"); + for (int i = 0; i < schemas.getLength(); i++) { + result.add(extractSchema((Element) schemas.item(i), + getNamespaceDeclarations(definitions))); + } + return result; + } catch (Exception e) { + throw new RuntimeException("Could not extract embedded schemas from WSDL.", e); + } + } + + private static List getNamespaceDeclarations(Element element) { + var namespaces = new ArrayList(); + var attributes = element.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + var attr = (Attr) attributes.item(i); + if (XMLNS_NS.equals(attr.getNamespaceURI())) { + namespaces.add(attr); + } + } + return namespaces; + } + + private static Document extractSchema(Element originalSchema, List definitionNamespaces) throws Exception { + var fac = DocumentBuilderFactory.newInstance(); + // fac.setFeature(FEATURE_SECURE_PROCESSING, true); + fac.setNamespaceAware(true); + var builder = fac.newDocumentBuilder(); + + var schema = builder.newDocument(); + var copiedSchema = (Element) schema.importNode(originalSchema, true); + schema.appendChild(copiedSchema); + + addMissingNamespaceDeclarations(copiedSchema, definitionNamespaces); + + return schema; + } + + private static void addMissingNamespaceDeclarations(Element schema, List definitionNamespaces) { + for (var nsDecl : definitionNamespaces) { + if (!schema.hasAttributeNS(XMLNS_NS, getNamespaceLocalName(nsDecl))) { + schema.setAttributeNS(XMLNS_NS, nsDecl.getName(), nsDecl.getValue()); + } + } + } + + private static String getNamespaceLocalName(Attr nsDecl) { + if (XMLNS_ATTRIBUTE.equals(nsDecl.getPrefix())) { + return nsDecl.getLocalName(); + } + return XMLNS_ATTRIBUTE; + } +} \ No newline at end of file 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 98447a34fc..fa8e3b1c23 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 @@ -15,18 +15,21 @@ package com.predic8.membrane.core.interceptor.schemavalidation; import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.Message; import com.predic8.membrane.core.http.*; +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.schema.*; +import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; import com.predic8.wsdl.*; 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.*; @@ -35,201 +38,154 @@ import static com.predic8.membrane.core.interceptor.Outcome.*; import static com.predic8.membrane.core.util.SOAPUtil.FaultCode.*; import static com.predic8.membrane.core.util.SOAPUtil.*; -import static com.predic8.membrane.core.util.WSDLUtil.*; +import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*; public class WSDLValidator extends AbstractXMLSchemaValidator { - private static final Logger log = LoggerFactory.getLogger(WSDLValidator.class.getName()); - - /** - * A WSDL can have several definition.service elements. The serviceName is the - * name of the service against to validate - */ - private final String serviceName; - - /** - * List of toplevel soapElements that are valid for requests - */ - private Set requestElements = new HashSet<>(); - - /** - * List of toplevel soapElements that are valid for responses - */ - private Set responseElements = new HashSet<>(); - - /** - * There might be additional toplevel Elements in a schema that are not used in - * a WSDL message. This field controls if it is checked if an element can be used as - * a request or response message - */ - private boolean checkIfSOAPElementIsUsedAsAWSDLMessage; - - /** - * Does WSDL supports SOAP Version 1.1? - */ - private boolean soap11; - - /** - * Does WSDL supports SOAP Version 1.1? - */ - private boolean soap12; - - public WSDLValidator(ResolverMap resourceResolver, String location, String serviceName, ValidatorInterceptor.FailureHandler failureHandler, boolean skipFaults) { - super(resourceResolver, location, failureHandler, skipFaults); - this.serviceName = serviceName; - } - - @Override - public String getName() { - return "wsdl-validator"; - } - - @Override - public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Exception { - Message msg = exc.getMessage(flow); - SOAPAnalysisResult result = SOAPUtil.analyseSOAPMessage( xopr, msg); - - if (result.version() == SOAP11 && !soap11) { - setErrorResponse(exc,"SOAP version 1.1 is not valid"); - return ABORT; - } - if (result.version() == SOAP12 && !soap12) { - setErrorResponse(exc,"SOAP version 1.2 is not valid"); - return ABORT; - } - - if (checkIfSOAPElementIsUsedAsAWSDLMessage) { - if (msg instanceof Request && !isPossibleRequestElement(result.soapElement())) { - setErrorResponse(exc,"%s is not a valid request element. Possible elements are %s".formatted(result.soapElement(), requestElements)); - return ABORT; - } - if (msg instanceof Response && !isPossibleResponseElement(result.soapElement())) { - setErrorResponse(exc,"%s is not a valid response element. Possible elements are %s".formatted(result.soapElement(), requestElements)); - return ABORT; - } - } - return super.validateMessage(exc, flow); - } - - private boolean isPossibleRequestElement(javax.xml.namespace.QName name) { - return isPossibleSOAPElement(requestElements, name); - } - - private boolean isPossibleResponseElement(javax.xml.namespace.QName name) { - return isPossibleSOAPElement(responseElements, name); - } - - private boolean isPossibleSOAPElement(Set elementNames, QName name) { - return elementNames.stream().anyMatch(qn -> qn.equals(name)); - } - - @Override - protected List getSchemas() { - WSDLParserContext ctx = new WSDLParserContext(); - ctx.setInput(location); - - Definitions definitions = parseWsdl(ctx); - readPossibleToplevelSOAPElements(definitions); - return getSchemas(definitions); - } - - private Definitions parseWsdl(WSDLParserContext ctx) { - Definitions definitions; - try { - definitions = getWsdlParser().parse(ctx); - } - catch (NoSuchElementException e) { - log.error(e.getMessage()); - throw new RuntimeException(); - } - catch (RuntimeException e) { - if (e.getCause() instanceof ResourceRetrievalException re) { - String msg = "Could not read WSDL from %s or its dependent XML Schemas.".formatted(location); - log.error(msg); - throw new IllegalStateException(msg, re); - } - log.error("Error downloading WSDL from {}.", location); - throw e; - } - return definitions; - } - - private static @NotNull List getSchemas(Definitions definitions) { - return definitions.getTypes().stream().map(Types::getSchemas).flatMap(List::stream).toList(); - } - - private void readPossibleToplevelSOAPElements(Definitions definitions) { - - if (serviceName != null) { - checkIfSOAPElementIsUsedAsAWSDLMessage = true; - Service service = WSDLUtil.getService(definitions, serviceName); - determinePossibleSoapVersions(service); - requestElements = getPossibleSOAPElements(service, Direction.REQUEST); - responseElements = getPossibleSOAPElements(service, Direction.RESPONSE); - return; - } - - // WSDL without a service element - if (definitions.getServices().isEmpty()) { - checkIfSOAPElementIsUsedAsAWSDLMessage = true; - // No binding information so allow all SOAP versions - soap11 = true; - soap12 = true; - definitions.getPortTypes().forEach(portType -> { - requestElements.addAll(getPossibleSOAPElements(portType, Direction.REQUEST)); - responseElements.addAll(getPossibleSOAPElements(portType, Direction.RESPONSE)); - }); - return; - } - - // Check what SOAP versions are declared in the WSDL - definitions.getServices().forEach(this::determinePossibleSoapVersions); - } - - private void determinePossibleSoapVersions(Service service) { - service.getPorts().forEach(port -> { - switch (getSOAPVersion(port)) { - case SOAP11: soap11 = true; break; - case SOAP12: soap12 = true; break; - } - }); - } - - private @NotNull WSDLParser getWsdlParser() { - WSDLParser parser = new WSDLParser(); - parser.setResourceResolver(resolver.toExternalResolver().toExternalResolver()); - return parser; - } - - @Override - protected Source getMessageBody(InputStream input) { - return MessageUtil.getSOAPBody(input); - } - - @Override - protected void setErrorResponse(Exchange exchange, String message) { - exchange.setResponse(SOAPUtil.createSOAPFaultResponse(Client, getErrorTitle(),Map.of("error",message))); - } - - @Override - protected void setErrorResponse(Exchange exchange, Interceptor.Flow flow, List exceptions) { - exchange.setResponse(createSOAPFaultResponse(Client, getErrorTitle(), Map.of("validation", convertExceptionsToMap(exceptions)))); - } - - @Override - protected boolean isFault(Message msg) { - return SOAPUtil.analyseSOAPMessage( xopr, msg).isFault(); - } - - @Override - protected String getPreliminaryError(XOPReconstitutor xopr, Message msg) { - if (isSOAP( xopr, msg)) - return null; - return "Not a SOAP message."; - } - - @Override - public String getErrorTitle() { - return "WSDL message validation failed"; - } -} + private static final Logger log = LoggerFactory.getLogger(WSDLValidator.class.getName()); + + /** + * A WSDL can have several definition.service elements. The serviceName is the + * name of the service against to validate + */ + private final String serviceName; + + /** + * List of toplevel soapElements that are valid for requests + */ + private Set requestElements; + + /** + * List of toplevel soapElements that are valid for responses + */ + private Set responseElements; + + /** + * There might be additional toplevel Elements in a schema that are not used in + * a WSDL message. This field controls if it is checked if an element can be used as + * a request or response message + */ + private boolean checkIfSOAPElementIsUsedAsAWSDLMessage; + + private Set versions; + + private final boolean skipFaults; + + /** + * Parsed WSDL document + */ + private com.predic8.membrane.core.util.wsdl.parser.Definitions definitions; + + public WSDLValidator(ResolverMap resourceResolver, String location, String serviceName, ValidatorInterceptor.FailureHandler failureHandler, boolean skipFaults) { + super(resourceResolver, location, failureHandler); + this.skipFaults = skipFaults; + this.serviceName = serviceName; + + try { + definitions = com.predic8.membrane.core.util.wsdl.parser.Definitions.parse(resourceResolver, location); + } catch (ResourceRetrievalException e) { + throw new ConfigurationException(""" + Could not extract embedded schemas from WSDL at location %s. + """.formatted(location), e); + } catch (Exception e) { + throw new ConfigurationException(""" + Could not parse WSDL as XML document at location %s. + Error Message: %s + """.formatted(location, e.getMessage())); + } + + requestElements = WSDLMessageElementExtractor.getPossibleRequestElements(definitions, serviceName); + responseElements = WSDLMessageElementExtractor.getPossibleResponseElements(definitions, serviceName); + + versions = definitions.getSoapVersions(); + } + + @Override + public String getName() { + return "wsdl-validator"; + } + + @Override + public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Exception { + var msg = exc.getMessage(flow); + var result = analyseSOAPMessage(xopr, msg); + + if (!result.isSOAP()) { + setErrorResponse(exc, "Not a valid SOAP message."); + return ABORT; + } + + if (result.isFault() && skipFaults) { + log.debug("Skipping validation of fault message."); + return CONTINUE; + } + + if (!versions.isEmpty()) { + if (result.version() == SOAP11 && !versions.contains(SOAP_11)) { + setErrorResponse(exc, "SOAP version 1.1 is not valid"); + return ABORT; + } + if (result.version() == SOAP12 && !versions.contains(SOAP_12)) { + setErrorResponse(exc, "SOAP version 1.2 is not valid"); + return ABORT; + } + } + +// if (checkIfSOAPElementIsUsedAsAWSDLMessage) { + if (msg instanceof Request && !isPossibleRequestElement(result.soapElement())) { + setErrorResponse(exc, "%s is not a valid request element. Possible elements are %s".formatted(result.soapElement(), requestElements)); + return ABORT; + } + if (msg instanceof Response && !isPossibleResponseElement(result.soapElement())) { + setErrorResponse(exc, "%s is not a valid response element. Possible elements are %s".formatted(result.soapElement(), responseElements)); + return ABORT; + } +// } + + return super.validateMessage(exc, flow); + } + + private boolean isPossibleRequestElement(javax.xml.namespace.QName name) { + return isPossibleSOAPElement(requestElements, name); + } + + private boolean isPossibleResponseElement(javax.xml.namespace.QName name) { + return isPossibleSOAPElement(responseElements, name); + } + + private boolean isPossibleSOAPElement(Set elementNames, QName name) { + return elementNames.stream().anyMatch(qn -> qn.equals(name)); + } + + @Override + protected List getSchemas() { + return definitions.getSchemaElements(); + } + + @Override + protected Source getMessageBody(InputStream input) { + return MessageUtil.getSOAPBody(input); + } + + @Override + protected void setErrorResponse(Exchange exchange, String message) { + exchange.setResponse(createSOAPFaultResponse(Client, getErrorTitle(), Map.of("error", message))); + } + + @Override + protected void setErrorResponse(Exchange exchange, Interceptor.Flow flow, List exceptions) { + exchange.setResponse(createSOAPFaultResponse(Client, getErrorTitle(), Map.of("validation", convertExceptionsToMap(exceptions)))); + } + + @Override + protected String getPreliminaryError(XOPReconstitutor xopr, Message msg) { + if (isSOAP(xopr, msg)) + return null; + return "Not a SOAP message."; + } + + @Override + public String getErrorTitle() { + return "WSDL message validation failed"; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidator.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidator.java index 8d04220ed6..8d4dc41dcf 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidator.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidator.java @@ -20,8 +20,8 @@ import com.predic8.membrane.core.multipart.*; import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.*; -import com.predic8.schema.Schema; import org.slf4j.*; +import org.w3c.dom.*; import org.xml.sax.*; import javax.xml.transform.*; @@ -32,10 +32,11 @@ import static com.predic8.membrane.annot.Constants.*; import static com.predic8.membrane.core.exceptions.ProblemDetails.*; -import static com.predic8.membrane.core.http.Header.VALIDATION_ERROR_SOURCE; +import static com.predic8.membrane.core.http.Header.*; public class XMLSchemaValidator extends AbstractXMLSchemaValidator { - private static final Logger log = LoggerFactory.getLogger(XMLSchemaValidator.class.getName()); + + private static final Logger log = LoggerFactory.getLogger(XMLSchemaValidator.class); public XMLSchemaValidator(ResolverMap resourceResolver, String location, ValidatorInterceptor.FailureHandler failureHandler) { super(resourceResolver, location, failureHandler); @@ -48,7 +49,7 @@ public String getName() { } @Override - protected List getSchemas() { + protected List getSchemas() { return null; // never gets called } @@ -109,11 +110,6 @@ protected void setErrorResponse(Exchange exchange, Interceptor.Flow flow, List RPC; + default -> DOCUMENT; + }; + } + } + + private WSDLParserContext ctx; + + private Style style; + private List operations; + private PortType portType; + + public Binding(WSDLParserContext ctx, Node node) { + super(node); + this.ctx = ctx; + operations = getBindingOperations(node); + portType = getPortType(node); + } + + public Style getStyle() { + return style; + } + + public List getOperations() { + return operations; + } + + public PortType getPortType() { + return portType; + } + + private List getBindingOperations(Node node) { + var result = new ArrayList(); + var children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + var child = children.item(i); + + if ((child.getNodeType() == ELEMENT_NODE) + && "operation".equals(child.getLocalName()) + && WSDL11_NS.equals(child.getNamespaceURI())) { + result.add(new BindingOperation(ctx, child)); + } + + if (child.getNodeType() == ELEMENT_NODE + && "binding".equals(child.getLocalName())) { + style = getStyle(child); + ctx.style(style); + if (child.getNamespaceURI().equals(WSDL_SOAP11_NS)) { + soapVersion = SOAPVersion.SOAP_11; + } else if (child.getNamespaceURI().equals(WSDL_SOAP12_NS)) { + soapVersion = SOAPVersion.SOAP_12; + } + ctx.getDefinitions().addSoapVersion(soapVersion); + + } + } + return result; + } + + private static Style getStyle(Node child) { + var node = child.getAttributes().getNamedItem("style"); + if (node == null) { + return DOCUMENT; + } + return Style.fromString(node.getNodeValue()); + } + + private PortType getPortType(Node node) { + if (!(node instanceof Element bindingElement)) { + return null; + } + + var type = bindingElement.getAttribute("type"); + if (type == null || type.isEmpty()) { + return null; + } + + var portTypeQName = WSDLParserUtil.resolveQName(type, bindingElement); + + Element definitions = bindingElement.getOwnerDocument().getDocumentElement(); + NodeList portTypes = definitions.getElementsByTagNameNS(WSDL11_NS, "portType"); + + for (int i = 0; i < portTypes.getLength(); i++) { + Element portType = (Element) portTypes.item(i); + + if (portTypeQName.getLocalPart().equals(portType.getAttribute("name"))) { + return new PortType(ctx, portType); + } + } + + return null; + } + +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java new file mode 100644 index 0000000000..b155b65f41 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java @@ -0,0 +1,24 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +public class BindingOperation extends WSDLElement{ + + private WSDLParserContext ctx; + + private String name; + private String soapAction; + + public BindingOperation(WSDLParserContext ctx, Node node) { + super(node); + this.ctx = ctx; + } + + public String getName() { + return name; + } + + public String getSoapAction() { + return soapAction; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java new file mode 100644 index 0000000000..78bbc9d774 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -0,0 +1,157 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import com.predic8.membrane.core.resolver.*; +import com.predic8.membrane.core.util.wsdl.parser.schema.*; +import org.w3c.dom.*; + +import java.io.*; +import java.util.*; + +import static com.predic8.membrane.annot.Constants.*; +import static org.w3c.dom.Node.*; + +public class Definitions { + + public enum SOAPVersion { + SOAP_11, SOAP_12 + } + + WSDLParserContext ctx; + + List schemas = new ArrayList<>(); + List messages; + List portTypes = new ArrayList<>(); + List bindings; + List services = new ArrayList<>(); + + List importedSchemas = new ArrayList<>(); + + String targetNamespace; + + Set soapVersions = new HashSet<>(); + + private Definitions() { + } + + public Definitions(WSDLParserContext ctx) { + this.ctx = ctx.definitions(this); + } + + public void parse(Element element) { + targetNamespace = element.getAttribute("targetNamespace"); + + for (var schemaElement : getSchemaElements(element)) { + schemas.add(new Schema(ctx, schemaElement)); + } + + for (var e : getElements(element, "service")) { + new Service(ctx, e); + } + + // Abstract WSDL without binding and service elements + if (services.isEmpty()) { + for (var e : getElements(element, "portType")) { + new PortType(ctx, e); + } + } + } + + public static Definitions parse(Resolver resolver, String location) throws Exception { + var defs = new Definitions(); + defs.ctx = new WSDLParserContext(defs, resolver, location); + defs.parse(WSDLParserUtil.parse(resolver.resolve(location))); + return defs; + } + + public void parse(InputStream is, Resolver resolver) { + try { + ctx.resolver(resolver); + //defs.ctx = new WSDLParserContext(defs, resolver,""); + parse(WSDLParserUtil.parse(is)); + } catch (Exception e) { + throw new RuntimeException("Could not parse WSDL", e); + } + } + + public List getSchemas() { + return schemas; + } + + public List getSchemaElements() { + return schemas.stream().map(schema -> schema.getSchemaElement()).toList(); + } + + public List getMessages() { + return messages; + } + + public List getPortTypes() { + return portTypes; + } + + public List getBindings() { + return bindings; + } + + public List getServices() { + return services; + } + + public Service getService(String name) { + return services.stream().filter(s -> s.getName().equals(name)).findFirst().orElse(null); + } + + public String getTargetNamespace() { + return targetNamespace; + } + + public List getImportedSchemas() { + return importedSchemas; + } + + public Set getSoapVersions() { + return soapVersions; + } + + public void addImportedSchema(Schema schema) { + importedSchemas.add(schema); + } + + public void addSoapVersion(SOAPVersion soapVersion) { + soapVersions.add(soapVersion); + } + + private List getSchemaElements(Element wsdl) { + var schemas = new ArrayList(); + + var typesList = wsdl.getElementsByTagNameNS(WSDL11_NS, "types"); + for (int i = 0; i < typesList.getLength(); i++) { + var types = (Element) typesList.item(i); + var children = types.getChildNodes(); + + for (int j = 0; j < children.getLength(); j++) { + var child = children.item(j); + + if ((child.getNodeType() == ELEMENT_NODE) + && "schema".equals(child.getLocalName()) + && XSD_NS.equals(child.getNamespaceURI())) { + schemas.add((Element) child); + } + } + } + + return schemas; + } + + private static List getElements(Element wsdl, String name) { + var services = new ArrayList(); + var list = wsdl.getElementsByTagNameNS(WSDL11_NS, name); + + for (int i = 0; i < list.getLength(); i++) { + services.add((Element) list.item(i)); + } + return services; + } + + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..3f242e21b5 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Message.java @@ -0,0 +1,43 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import com.predic8.membrane.annot.*; +import org.w3c.dom.*; + +import static com.predic8.membrane.annot.Constants.WSDL11_NS; + +public class Message extends WSDLElement { + + private WSDLParserContext ctx; + + private Part part; + + public Message(WSDLParserContext ctx, Node node) { + super(node); + this.ctx = ctx; + this.part = getPart(node); + } + + public Part getPart() { + return part; + } + + private Part getPart(Node node) { + NodeList children = node.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + + if (child.getNodeType() == Node.ELEMENT_NODE + && "part".equals(child.getLocalName()) + && WSDL11_NS.equals(child.getNamespaceURI())) { + return new Part(ctx, child); + } + } + return null; + } + + @Override + public String toString() { + return "Message [part=" + part + "]"; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java new file mode 100644 index 0000000000..061ad2b4c0 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -0,0 +1,108 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import com.predic8.wsdl.*; +import org.w3c.dom.*; + +import javax.xml.namespace.*; +import java.util.*; + +import static com.predic8.membrane.annot.Constants.*; +import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.*; +import static org.w3c.dom.Node.*; + +public class Operation extends WSDLElement { + + private WSDLParserContext ctx; + + private List inputs; + private List outputs; + private List faults; + + public enum Direction { + INPUT, OUTPUT; + + public boolean matches(String s) { + return name().equalsIgnoreCase(s); + } + } + + public Operation(WSDLParserContext ctx, Node node) { + super(node); + this.ctx = ctx; + inputs = getInputs(node); + outputs = getOutputs(node); + faults = getFaults(node); + } + + public List getInputs() { + return inputs; + } + + public List getOutputs() { + return outputs; + } + + public List getMessagesByDirection(Direction direction) { + if (direction == INPUT) + return inputs; + return outputs; + } + + public List getFaults() { + return faults; + } + + private List getInputs(Node node) { + return getMessagesByDirection(node, INPUT); + } + + private List getOutputs(Node node) { + return getMessagesByDirection(node, OUTPUT); + } + + private List getFaults(Node node) { + return new ArrayList<>(); + } + + private List getMessagesByDirection(Node node, Direction direction) { + var result = new ArrayList(); + var children = node.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + + if (child.getNodeType() == ELEMENT_NODE + && direction.matches(child.getLocalName()) + && WSDL11_NS.equals(child.getNamespaceURI())) { + + Element io = (Element) child; + String messageAttr = io.getAttribute("message"); + if (messageAttr == null || messageAttr.isEmpty()) { + continue; + } + + QName messageQName = WSDLParserUtil.resolveQName(messageAttr, io); + Message message = findMessage(messageQName, io.getOwnerDocument()); + if (message != null) { + result.add(message); + } + } + } + + return result; + } + + private Message findMessage(QName messageQName, Document document) { + var definitions = document.getDocumentElement(); + var messages = definitions.getElementsByTagNameNS(WSDL11_NS, "message"); + + for (int i = 0; i < messages.getLength(); i++) { + Element message = (Element) messages.item(i); + if (messageQName.getLocalPart().equals(message.getAttribute("name"))) { + return new Message(ctx, message); + } + } + + return null; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java new file mode 100644 index 0000000000..869d67b927 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java @@ -0,0 +1,58 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +import javax.xml.namespace.*; + +public class Part extends WSDLElement { + + private WSDLParserContext ctx; + + private QName element; + private QName type; + + public Part(WSDLParserContext ctx, Node node) { + super(node); + this.ctx = ctx; + this.element = getElementQName(node); + this.type = getTypeQName(node); + } + + + public QName getElementQName() { + return element; + } + + public QName getTypeQName() { + return type; + } + + private QName getTypeQName(Node node) { + if (!(node instanceof org.w3c.dom.Element partElement)) { + return null; + } + + String typeAttr = partElement.getAttribute("type"); + if (typeAttr.isEmpty()) { + return null; + } + + return WSDLParserUtil.resolveQName(typeAttr, partElement); + } + + private QName getElementQName(Node node) { + if (!(node instanceof Element element)) { + return null; + } + var elementAttr = element.getAttribute("element"); + if (elementAttr.isEmpty()) { + return null; + } + return WSDLParserUtil.resolveQName(elementAttr, element); + } + + @Override + public String toString() { + return "Part [element=" + element + ", type=" + type + "]"; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java new file mode 100644 index 0000000000..d5c07bfc41 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java @@ -0,0 +1,68 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +import static com.predic8.membrane.annot.Constants.WSDL11_NS; +import static org.w3c.dom.Node.ELEMENT_NODE; + +public class Port extends WSDLElement { + + private WSDLParserContext ctx; + + private Address address; + private Binding binding; + + public Port(WSDLParserContext ctx, Node node) { + super(node); + this.ctx = ctx; + address = getAddress(node); + binding = getBinding(node); + + } + + public Address getAddress() { + return address; + } + + public Binding getBinding() { + return binding; + } + + public Address getAddress(Node node) { + var children = node.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + + if (child.getNodeType() == ELEMENT_NODE) { + var localName = child.getLocalName(); + if ("address".equals(localName)) { + return new Address(ctx,child); + } + } + } + return null; + } + + public Binding getBinding(Node node) { + if (!(node instanceof Element port)) + return null; + + String bindingAttr = port.getAttribute("binding"); + if (bindingAttr == null || bindingAttr.isEmpty()) + return null; + + var bindingQName = WSDLParserUtil.resolveQName(bindingAttr, port); + var bindings = getDefinitions().getElementsByTagNameNS(WSDL11_NS, "binding"); + + for (int i = 0; i < bindings.getLength(); i++) { + Element binding = (Element) bindings.item(i); + + if (bindingQName.getLocalPart().equals(binding.getAttribute("name"))) { + return new Binding(ctx, binding); + } + } + + return null; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java new file mode 100644 index 0000000000..05916c5f29 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java @@ -0,0 +1,43 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +import java.util.*; + +import static org.w3c.dom.Node.*; + +public class PortType extends WSDLElement { + + private WSDLParserContext ctx; + + private List operations; + + public PortType(WSDLParserContext ctx, Node node) { + super(node); + this.ctx = ctx; + operations = getOperations(node); + ctx.getDefinitions().portTypes.add(this); + } + + public List getOperations() { + return operations; + } + + private List getOperations(Node node) { + var operations = new ArrayList(); + NodeList children = node.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + + if (child.getNodeType() == ELEMENT_NODE + && "operation".equals(child.getLocalName()) +// && WSDL11_NS.equals(child.getNamespaceURI()) + ) { + operations.add(new Operation(ctx, child)); + } + } + + return operations; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java new file mode 100644 index 0000000000..3d7bf2f5de --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java @@ -0,0 +1,42 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +import java.util.*; + +import static com.predic8.membrane.annot.Constants.*; + +public class Service extends WSDLElement { + + public static final String PORT = "port"; + + private final WSDLParserContext ctx; + + private String name; + private List ports; + + public Service(WSDLParserContext ctx, Node element) { + super(element); + this.ctx = ctx; + ports = getPorts(element); + ctx.getDefinitions().services.add(this); + } + + public List getPorts() { + return ports; + } + + public List getPorts(Node service) { + var ports = new ArrayList(); + var children = service.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + var node = children.item(i); + if (node instanceof Element e && + PORT.equals(e.getLocalName()) && WSDL11_NS.equals(e.getNamespaceURI())) { + ports.add(new Port(ctx,e)); + } + } + return ports; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java new file mode 100644 index 0000000000..70c532e368 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java @@ -0,0 +1,9 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +public class Type extends WSDLElement{ + public Type(Node node) { + super(node); + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java new file mode 100644 index 0000000000..1b7614a5e7 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -0,0 +1,28 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +public class WSDLElement { + private Element element; + private String name; + + public WSDLElement(Node node) { + if (!(node instanceof Element element)) { + throw new RuntimeException("Not an element: " + node.getClass()); + } + this.element = element; + var nameNode = element.getAttributes().getNamedItem("name"); + if (nameNode != null) { + name = nameNode.getNodeValue(); + } + } + + public String getName() { + return name; + } + + public Element getDefinitions() { + return element.getOwnerDocument().getDocumentElement(); + } + +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java new file mode 100644 index 0000000000..265e02390c --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java @@ -0,0 +1,59 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import com.predic8.membrane.core.resolver.*; +import com.predic8.membrane.core.util.wsdl.parser.Binding.*; + +public class WSDLParserContext { + + private Definitions definitions; + private Resolver resolver; + private String basePath; + private Style style; + + public WSDLParserContext(Resolver resolver, String basePath) { + this.basePath = basePath; + this.resolver = resolver; + } + + public WSDLParserContext(Definitions wsdl, Resolver resolver, String basePath) { + this.definitions = wsdl; + this.resolver = resolver; + this.basePath = basePath; + } + + public WSDLParserContext definitions(Definitions definitions) { + this.definitions = definitions; + return this; + } + + public WSDLParserContext basePath(String basePath) { + this.basePath = basePath; + return this; + } + + public WSDLParserContext resolver(Resolver resolver) { + this.resolver = resolver; + return this; + } + + public WSDLParserContext style(Style style) { + this.style = style; + return this; + } + + public Style getStyle() { + return style; + } + + public Definitions getDefinitions() { + return definitions; + } + + public Resolver getResolver() { + return resolver; + } + + public String getBasePath() { + return basePath; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java new file mode 100644 index 0000000000..8d000f9b75 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java @@ -0,0 +1,29 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +import javax.xml.namespace.*; +import javax.xml.parsers.*; +import java.io.*; + +public class WSDLParserUtil { + + public static Element parse(InputStream is) throws Exception { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + return dbf.newDocumentBuilder().parse(is).getDocumentElement(); + } + + + public static QName resolveQName(String value, Node context) { + + if (!value.contains(":")) + return new QName(context.lookupNamespaceURI(null), value); + + String[] p = value.split(":"); + String prefix = p[0]; + String local = p[1]; + + return new QName(context.lookupNamespaceURI(prefix), local); + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java new file mode 100644 index 0000000000..4af0cbea1f --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -0,0 +1,34 @@ +package com.predic8.membrane.core.util.wsdl.parser.schema; + +import com.predic8.membrane.core.resolver.*; +import com.predic8.membrane.core.util.wsdl.parser.*; +import org.w3c.dom.*; + +import java.io.*; + +public abstract class AbstractIncludeImport extends WSDLElement { + + protected String schemaLocation; + + public AbstractIncludeImport(WSDLParserContext ctx, Node node) { + super(node); + var e = (Element) node; + schemaLocation = e.getAttribute("schemaLocation"); + } + + protected Schema getSchema(WSDLParserContext ctx) { + try { + var combined = ResolverMap.combine(ctx.getBasePath(), schemaLocation); + InputStream is = ctx.getResolver().resolve(combined); + return new Schema(ctx.basePath(combined), WSDLParserUtil.parse(is)); + } catch (ResourceRetrievalException e) { + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String getSchemaLocation() { + return schemaLocation; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java new file mode 100644 index 0000000000..535a62110e --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -0,0 +1,22 @@ +package com.predic8.membrane.core.util.wsdl.parser.schema; + +import com.predic8.membrane.core.util.wsdl.parser.*; +import org.w3c.dom.*; + +public class Import extends AbstractIncludeImport { + + private WSDLParserContext ctx; + private String namespace; + + private Schema schema; + + public Import(WSDLParserContext ctx,Node node) { + super(ctx,node); + this.ctx = ctx; + var e = (Element) node; + namespace = e.getAttribute("namespace"); + + schema=getSchema(ctx); + ctx.getDefinitions().addImportedSchema(schema); + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java new file mode 100644 index 0000000000..ddc13e6f10 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java @@ -0,0 +1,21 @@ +package com.predic8.membrane.core.util.wsdl.parser.schema; + +import com.predic8.membrane.core.util.wsdl.parser.*; +import org.jetbrains.annotations.*; +import org.w3c.dom.*; + +public class Include extends AbstractIncludeImport { + + public Include(WSDLParserContext ctx, Node node, Schema schema) { + super(ctx,node); + this.schemaLocation = getSchemaLocation( node); + + schema.getSchemaElements().addAll(getSchema(ctx).getSchemaElements()); + } + + private static @NotNull String getSchemaLocation(Node node) { + if (node instanceof Element e) + return e.getAttribute("schemaLocation"); + return null; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java new file mode 100644 index 0000000000..d598d117b2 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -0,0 +1,110 @@ +package com.predic8.membrane.core.util.wsdl.parser.schema; + +import com.predic8.membrane.core.util.wsdl.parser.*; +import org.w3c.dom.*; + +import java.util.*; + +import static com.predic8.membrane.annot.Constants.*; +import static org.w3c.dom.Node.*; + +public class Schema extends WSDLElement { + + private WSDLParserContext ctx; + + private String targetNamespace; + private Element schema; + private List schemaElements; + private List imports; + private List includes; + + public Schema(WSDLParserContext ctx, Node node) { + super(node); + this.ctx = ctx; + this.targetNamespace = getTargetNamespace(node); + this.schema = (Element) node; + this.schemaElements = getSchemaElements(schema); + this.imports = getImports(node); + this.includes = getIncludes(node); + } + + public String getTargetNamespace() { + return targetNamespace; + } + + public Element getSchemaElement() { + return schema; + } + + public List getSchemaElements() { + return schemaElements; + } + + public List getImports() { + return imports; + } + + public List getIncludes() { + return includes; + } + + private String getTargetNamespace(Node node) { + if (!(node instanceof Element schema)) { + return null; + } + + String tns = schema.getAttribute("targetNamespace"); + return (tns == null || tns.isEmpty()) ? null : tns; + } + + private List getIncludes(Node node) { + var result = new ArrayList(); + var children = node.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + var child = children.item(i); + + if (child.getNodeType() == ELEMENT_NODE + && "include".equals(child.getLocalName()) + && XSD_NS.equals(child.getNamespaceURI())) { + result.add(new Include(ctx, child, this)); + } + } + + return result; + } + + private List getImports(Node node) { + var result = new ArrayList(); + var children = node.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + var child = children.item(i); + + if (child.getNodeType() == ELEMENT_NODE + && "import".equals(child.getLocalName()) + && XSD_NS.equals(child.getNamespaceURI())) { + result.add(new Import(ctx, child)); + } + } + + return result; + } + + private List getSchemaElements(Element schema) { + var result = new ArrayList(); + var children = schema.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + var child = children.item(i); + + if (child.getNodeType() == ELEMENT_NODE + && "element".equals(child.getLocalName()) + && XSD_NS.equals(child.getNamespaceURI())) { + result.add(new SchemaElement(ctx, child)); + } + } + return result; + } + +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java new file mode 100644 index 0000000000..f63802bc99 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java @@ -0,0 +1,19 @@ +package com.predic8.membrane.core.util.wsdl.parser.schema; + +import com.predic8.membrane.core.util.wsdl.parser.*; +import org.w3c.dom.*; + +public class SchemaElement extends WSDLElement { + + WSDLParserContext ctx; + + public SchemaElement(WSDLParserContext ctx,Node node) { + super(node); + this.ctx = ctx; + } + + @Override + public String toString() { + return "SchemaElement{name=%s}".formatted(getName()); + } +} diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPMessageValidatorInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPMessageValidatorInterceptorTest.java index 310d632363..088f259f7a 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPMessageValidatorInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPMessageValidatorInterceptorTest.java @@ -128,12 +128,13 @@ void testHandleRequestInvalidEmailMessageDoubleRequestElement() throws Exception @Test void handleRequestInvalidEmailMessageUnknownElement() throws Exception { - Exchange exc = post("http://ws.xwebservices.com") + var exc = post("http://ws.xwebservices.com") .body(getContent("/validation/invalidEmail3.xml")) .buildExchange(); assertEquals(ABORT, createValidatorInterceptor(E_MAIL_SERVICE_WSDL).handleRequest(exc)); var body = exc.getResponse().getBodyAsStringDecoded(); + System.out.println(body); assertTrue(body.contains(WSDL_MESSAGE_VALIDATION_FAILED)); assertTrue(body.contains("cvc-complex-type.2.4.a")); assertTrue(body.contains("line")); @@ -148,10 +149,11 @@ void inlineSchemaWithAnyType() throws Exception { assertEquals(ABORT, createValidatorInterceptor(INLINE_ANYTYPE_WSDL).handleRequest(exc)); var body = exc.getResponse().getBodyAsStringDecoded(); + System.out.println(body); assertTrue(body.contains(WSDL_MESSAGE_VALIDATION_FAILED)); - assertTrue(body.contains("cvc-elt.1.a")); - assertTrue(body.contains("line")); - assertTrue(body.contains("column")); + assertTrue(body.contains("ValidateEmailRequest")); + assertTrue(body.contains("is not a valid request element")); + assertTrue(body.contains("[{http://www.examples.com/wsdl/HelloService.wsdl}sayHello]")); } private String getContent(String fileName) throws Exception { diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java new file mode 100644 index 0000000000..2b27a5c906 --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java @@ -0,0 +1,69 @@ +package com.predic8.membrane.core.interceptor.schemavalidation; + +import com.predic8.membrane.core.resolver.*; +import com.predic8.membrane.core.util.wsdl.parser.*; +import org.junit.jupiter.api.*; + +import javax.xml.namespace.*; +import java.io.*; + +import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLMessageElementExtractor.*; +import static org.junit.jupiter.api.Assertions.*; + +class WSDLMessageElementExtractorTest { + + private static final String CITIES_NS = "https://predic8.de/cities"; + private static final QName GET_CITY_QNAME = new QName(CITIES_NS, "getCity"); + private static final QName GET_CITYRESPONSE_QNAME = new QName(CITIES_NS, "getCityResponse"); + + private static final String XMAIL_NS = "urn:ws-xwebservices-com:XWebEmailValidation:EmailValidation:v2:Messages"; + private static final QName GET_EMAIL_QNAME = new QName(XMAIL_NS, "ValidateEmailRequest"); + private static final QName GET_EMAILRESPONSE_QNAME = new QName(XMAIL_NS, "ValidateEmailResponse"); + + @Test + void extract() throws Exception { + var location = "/ws/cities.wsdl"; + try (var wsdl = getClass().getResourceAsStream(location)) { + var doc = parseWsdl(wsdl, location); + var requestElements = getPossibleRequestElements(doc, null); + + assertEquals(1, requestElements.size()); + assertTrue(requestElements.contains(GET_CITY_QNAME)); + + var responseElements = getPossibleResponseElements(doc, null); + assertEquals(1, responseElements.size()); + assertTrue(responseElements.contains(GET_CITYRESPONSE_QNAME)); + } + } + + @Test + void eMailServiceWSDL() throws Exception { + var doc = Definitions.parse(new ResolverMap(), "classpath:/validation/XWebEmailValidation.wsdl.xml"); + var requestElements = getPossibleRequestElements(doc, null); + assertEquals(1, requestElements.size()); + assertTrue(requestElements.contains(GET_EMAIL_QNAME)); + + var responseElements = getPossibleResponseElements(doc, null); + assertEquals(1, responseElements.size()); + assertTrue(responseElements.contains(GET_EMAILRESPONSE_QNAME)); + + } + + @Test + void rpcStyle() throws Exception { + var location = "/validation/inline-anytype.wsdl"; + try (var wsdl = getClass().getResourceAsStream(location)) { + var doc = parseWsdl(wsdl, location); + var requestElements = getPossibleRequestElements(doc, "Hello_Service"); + assertEquals(1, requestElements.size()); + assertTrue(requestElements.contains(new QName("http://www.examples.com/wsdl/HelloService.wsdl", "sayHello"))); + } + } + + private static Definitions parseWsdl(InputStream is, String location) { + var ctx = new WSDLParserContext(null, new ResolverMap(), location); + var definitions = new Definitions(ctx); + definitions.parse(is, null); + return definitions; + } +} \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractorTest.java new file mode 100644 index 0000000000..c3228f28d8 --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractorTest.java @@ -0,0 +1,53 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + +package com.predic8.membrane.core.interceptor.schemavalidation; + +import com.predic8.membrane.annot.*; +import org.junit.jupiter.api.*; +import org.w3c.dom.*; +import org.xml.sax.*; + +import javax.xml.parsers.*; +import javax.xml.transform.dom.*; +import java.io.*; + +import static com.predic8.membrane.annot.Constants.WSDL_SOAP11_NS; +import static com.predic8.membrane.annot.Constants.XSD_NS; +import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLSchemaExtractor.*; +import static org.junit.jupiter.api.Assertions.*; + +class WSDLSchemaExtractorTest { + + @Test + void extractWithCorrectNamespaceFromWSDLDefinitionElement() throws Exception { + + var schemas = getSchemas(parse( this.getClass().getResourceAsStream("/ws/cities.wsdl")) + .getDocumentElement()); + assertEquals(1, schemas.size()); + + var schema = schemas.getFirst().getDocumentElement(); + assertEquals(XSD_NS , schema.getNamespaceURI()); + assertEquals("schema", schema.getLocalName()); + + assertEquals(XSD_NS, schema.getAttribute("xmlns:xsd")); + assertEquals(WSDL_SOAP11_NS, schema.getAttribute("xmlns:s")); + } + + private static Document parse(InputStream wsdl) throws Exception { + var fac = DocumentBuilderFactory.newInstance(); + fac.setNamespaceAware(true); + return fac.newDocumentBuilder().parse(wsdl); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidatorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidatorTest.java index 034f645970..11a34274bd 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidatorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLValidatorTest.java @@ -39,7 +39,7 @@ public class WSDLValidatorTest { @Test void invalidRequestElement() throws Exception { - Exchange exc = getRequestExchange(soap11(""" + var exc = getRequestExchange(soap11(""" """)); @@ -108,8 +108,7 @@ void twoServicesElementOfProperService() throws Exception { @Test void twoServicesElementOfWrongService() throws Exception { - - Exchange exc = getRequestExchange(soap11(""" + var exc = getRequestExchange(soap11(""" Paris """)); @@ -134,12 +133,15 @@ void validateFaultsAndFail() throws Exception { @Test void skipFaults() throws Exception { - Exchange exc = getResponseExchange(soap11(""" + var exc = getResponseExchange(soap11(""" """)); - assertEquals(CONTINUE, createValidator(CITIES_WSDL, null, true).validateMessage(exc, RESPONSE)); + + Outcome actual = createValidator(CITIES_WSDL, null, true).validateMessage(exc, RESPONSE); dumpResonseBody(exc); + assertEquals(CONTINUE, actual); + } @Test @@ -148,14 +150,14 @@ void multiplePortsSoap11() throws Exception { Paris """)); - Outcome outcome = createValidator(MULTIPLE_PORTS_WSDL, "Service", false).validateMessage(exc, REQUEST); + Outcome outcome = createValidator(MULTIPLE_PORTS_WSDL, "Service", true).validateMessage(exc, REQUEST); dumpResonseBody(exc); assertEquals(CONTINUE, outcome); } @Test void abstractWsdl() throws Exception { - Exchange exc = getRequestExchange(soap11(""" + var exc = getRequestExchange(soap11(""" Paris """)); @@ -168,11 +170,11 @@ void abstractWsdl() throws Exception { @Test void abstractWsdlNoReferencedRequestElement() throws Exception { - Exchange exc = getRequestExchange(soap11(""" + var exc = getRequestExchange(soap11(""" 7 """)); - Outcome outcome = createValidator(ABSTRACT_SERVICE_NO_BINDING_WSDL, null, false).validateMessage(exc, REQUEST); + var outcome = createValidator(ABSTRACT_SERVICE_NO_BINDING_WSDL, null, false).validateMessage(exc, REQUEST); dumpResonseBody(exc); assertEquals(ABORT, outcome); assertTrue(exc.getResponse().getBodyAsStringDecoded().contains("is not a valid request element")); @@ -212,7 +214,7 @@ private static Exchange getResponseExchange(String body) { } private static WSDLValidator createValidator(String location, String serviceName, boolean skipFaults) { - WSDLValidator validator = new WSDLValidator(new ResolverMap(), location, serviceName, (msg, exc) -> log.info("Validation failure: {}", msg), skipFaults); + var validator = new WSDLValidator(new ResolverMap(), location, serviceName, (msg, exc) -> log.info("Validation failure: {}", msg), skipFaults); validator.init(); return validator; } diff --git a/core/src/test/java/com/predic8/membrane/core/resolver/ResolverTest.java b/core/src/test/java/com/predic8/membrane/core/resolver/ResolverTest.java index c2dca5d940..62c53fc3c3 100644 --- a/core/src/test/java/com/predic8/membrane/core/resolver/ResolverTest.java +++ b/core/src/test/java/com/predic8/membrane/core/resolver/ResolverTest.java @@ -143,7 +143,7 @@ public void testMembraneServiceProxyCombine(BasisUrlType basisUrlType) throws IO assertNotNull(resolverMap.resolve(wsdlLocation)); for (String relUrl : new String[]{"1.xsd", "./1.xsd", "../resolver/1.xsd", "http://localhost:3029/resolver/1.xsd"}) { try { - assertNotNull(resolverMap.resolve(ResolverMap.combine(wsdlLocation, relUrl))); + assertNotNull(resolverMap.resolve(ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, wsdlLocation, relUrl))); } catch (Exception e) { throw new RuntimeException("Error during combine(\"" + wsdlLocation + "\", \"" + relUrl + "\"):", e); } 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 new file mode 100644 index 0000000000..4383c1f18a --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/util/soap/WSDLParserTest.java @@ -0,0 +1,95 @@ +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 java.io.*; +import java.util.*; + +import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.DOCUMENT; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class WSDLParserTest { + + @Test + void simpleSchema() throws Exception { + try (InputStream is = getClass().getResourceAsStream("/ws/cities.wsdl")) { + var definitions = new Definitions(new WSDLParserContext(null, null, null)); + definitions.parse(is, null); + assertEquals(1,definitions.getSchemas().size()); + var schema = definitions.getSchemas().getFirst(); + assertEquals("https://predic8.de/cities", schema.getTargetNamespace()); + var schemaElements = schema.getSchemaElements(); + assertEquals(2, schemaElements.size()); + var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList(); + assertEquals(List.of("getCity","getCityResponse"), schemaElementNames); + + var services = definitions.getServices(); + assertEquals(1, services.size()); + var service = services.getFirst(); + assertEquals("CityService", service.getName()); + var ports = service.getPorts(); + assertEquals(1, ports.size()); + var port = ports.getFirst(); + assertEquals("CityPort", port.getName()); + var address = port.getAddress(); + assertEquals("http://localhost:2001/services/cities", address.getLocation()); + var binding = port.getBinding(); + assertEquals("CitySoapBinding", binding.getName()); + assertEquals(DOCUMENT, binding.getStyle()); + var portType = binding.getPortType(); + assertEquals("CityPort",portType.getName()); + var operations = portType.getOperations(); + assertEquals(1,operations.size()); + var getCityOperation = operations.getFirst(); + var inputs = getCityOperation.getInputs(); + assertEquals(1, inputs.size()); + var getCityInput = inputs.getFirst(); + var getCityPart = getCityInput.getPart(); + assertEquals("getCity", getCityPart.getName()); + assertEquals("getCity", getCityPart.getElementQName().getLocalPart()); + assertEquals("https://predic8.de/cities", getCityPart.getElementQName().getNamespaceURI()); + } + } + + @Test + void includeImport() throws Exception { + var resolver = new ResolverMap(); + String uri = "classpath://ws/include/include.wsdl"; + try (InputStream is = resolver.resolve(uri)) { + var ctx = new WSDLParserContext(null,resolver,uri); + var definitions = new Definitions(ctx); + definitions.parse(is,resolver); + assertEquals(1, definitions.getSchemas().size()); + var schema = definitions.getSchemas().getFirst(); + assertEquals("http://example.com/test", schema.getTargetNamespace()); + var schemaElements = schema.getSchemaElements(); + assertEquals(3, schemaElements.size()); + var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList(); + assertEquals(List.of("test", "testResponse","du"), schemaElementNames); + + var includeMessageStructureTypes = schema.getIncludes().getFirst(); + assertEquals("xsd/inc/MessageStructureTypes2.xsd", includeMessageStructureTypes.getSchemaLocation()); + + assertEquals(1, definitions.getImportedSchemas().size()); + var importedSchema = definitions.getImportedSchemas().getFirst(); + assertEquals("http://example.com/test/data-types", importedSchema.getTargetNamespace()); + var importedSchemaElements = importedSchema.getSchemaElements(); + assertEquals(2, importedSchemaElements.size()); + var importedSchemaElementNames = importedSchemaElements.stream().map(SchemaElement::getName).toList(); + assertEquals(List.of("B5","B6"), importedSchemaElementNames); + } + } + + @Test + void abstractWSDL() throws IOException { + try (var is = getClass().getResourceAsStream("/ws/abstract-service-no-binding.wsdl")) { + var definitions = new Definitions(new WSDLParserContext(null, null, null)); + definitions.parse(is, null); + assertEquals(1, definitions.getPortTypes().size()); + } + } + +} \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/OperationTest.java b/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/OperationTest.java new file mode 100644 index 0000000000..9c3fd6f9cd --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/OperationTest.java @@ -0,0 +1,17 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.junit.jupiter.api.*; + +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 org.junit.jupiter.api.Assertions.assertTrue; + +class OperationTest { + + @Test + void direction() { + assertTrue(INPUT.matches("input")); + assertTrue(OUTPUT.matches("OutPut")); + } + +} \ No newline at end of file diff --git a/core/src/test/resources/validation/inline-anytype.wsdl b/core/src/test/resources/validation/inline-anytype.wsdl index 3f3db659a1..7e8dc4c917 100644 --- a/core/src/test/resources/validation/inline-anytype.wsdl +++ b/core/src/test/resources/validation/inline-anytype.wsdl @@ -1,76 +1,67 @@ - - + targetNamespace="http://www.examples.com/wsdl/HelloService.wsdl" + xmlns="http://schemas.xmlsoap.org/wsdl/" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:tns="http://www.examples.com/wsdl/HelloService.wsdl" +> - + + - + + + - - - + + + + + + + - - - - - + + + - + + + - + + + + + + - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - WSDL File for HelloService - - - - + + WSDL File for HelloService + + + + diff --git a/core/src/test/resources/ws/include/include.wsdl b/core/src/test/resources/ws/include/include.wsdl new file mode 100644 index 0000000000..46dbfa50ef --- /dev/null +++ b/core/src/test/resources/ws/include/include.wsdl @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/include.xsd b/core/src/test/resources/ws/include/include.xsd new file mode 100644 index 0000000000..47309d5a33 --- /dev/null +++ b/core/src/test/resources/ws/include/include.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/xsd/inc/MessageStructureTypes2.xsd b/core/src/test/resources/ws/include/xsd/inc/MessageStructureTypes2.xsd new file mode 100644 index 0000000000..4567ea00a4 --- /dev/null +++ b/core/src/test/resources/ws/include/xsd/inc/MessageStructureTypes2.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/xsd/inc/shared/xsd/DataTypes.xsd b/core/src/test/resources/ws/include/xsd/inc/shared/xsd/DataTypes.xsd new file mode 100644 index 0000000000..3e9bd480ca --- /dev/null +++ b/core/src/test/resources/ws/include/xsd/inc/shared/xsd/DataTypes.xsd @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/xsd/inc/shared/xsd/inc/BaseTypes.xsd b/core/src/test/resources/ws/include/xsd/inc/shared/xsd/inc/BaseTypes.xsd new file mode 100644 index 0000000000..74c4c2ae11 --- /dev/null +++ b/core/src/test/resources/ws/include/xsd/inc/shared/xsd/inc/BaseTypes.xsd @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/xsd/messages.xsd b/core/src/test/resources/ws/include/xsd/messages.xsd new file mode 100644 index 0000000000..530eac6fb8 --- /dev/null +++ b/core/src/test/resources/ws/include/xsd/messages.xsd @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file From ea95d52e25f94249046af3a5091e4bd10b63a974 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Sun, 8 Mar 2026 20:07:36 +0100 Subject: [PATCH 02/28] Remove unused WSDLSOAPVersionExtractor and clean up redundant WSDL parser fields - Deleted `WSDLSOAPVersionExtractor` as it is no longer in use. - Replaced redundant mutable fields in WSDL parser classes with `final` fields to ensure immutability. - Simplified WSDL parser constructors by removing unused context (`ctx`) parameters. - Enhanced code readability by improving method references and streamlining operations. - Updated comments and removed obsolete code for cleaner structure. --- .../ValidatorInterceptor.java | 9 ++-- .../WSDLMessageElementExtractor.java | 12 ++--- .../WSDLSOAPVersionExtractor.java | 46 ------------------- .../schemavalidation/WSDLSchemaExtractor.java | 3 +- .../schemavalidation/WSDLValidator.java | 20 ++------ .../core/util/wsdl/parser/Address.java | 5 +- .../core/util/wsdl/parser/Binding.java | 11 ++--- .../util/wsdl/parser/BindingOperation.java | 11 +---- .../core/util/wsdl/parser/Definitions.java | 2 +- .../core/util/wsdl/parser/Message.java | 7 +-- .../core/util/wsdl/parser/Operation.java | 13 ++---- .../membrane/core/util/wsdl/parser/Part.java | 8 +--- .../membrane/core/util/wsdl/parser/Port.java | 11 ++--- .../core/util/wsdl/parser/PortType.java | 5 +- .../core/util/wsdl/parser/Service.java | 9 +--- .../core/util/wsdl/parser/WSDLElement.java | 9 ++-- .../parser/schema/AbstractIncludeImport.java | 4 +- .../core/util/wsdl/parser/schema/Import.java | 7 +-- .../core/util/wsdl/parser/schema/Include.java | 2 +- .../core/util/wsdl/parser/schema/Schema.java | 20 ++++---- .../test/resources/ws/include/include.wsdl | 2 - 21 files changed, 57 insertions(+), 159 deletions(-) delete mode 100644 core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSOAPVersionExtractor.java diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java index 8c5cd2b473..038de9aeb8 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java @@ -16,7 +16,7 @@ import com.predic8.membrane.annot.*; import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.ReadingBodyException; +import com.predic8.membrane.core.http.*; import com.predic8.membrane.core.interceptor.*; import com.predic8.membrane.core.interceptor.schemavalidation.json.*; import com.predic8.membrane.core.proxies.*; @@ -27,14 +27,11 @@ import org.springframework.beans.*; import org.springframework.context.*; -import java.io.*; - import static com.predic8.membrane.core.exceptions.ProblemDetails.*; import static com.predic8.membrane.core.interceptor.Interceptor.Flow.*; -import static com.predic8.membrane.core.interceptor.Outcome.ABORT; import static com.predic8.membrane.core.interceptor.Outcome.*; -import static com.predic8.membrane.core.resolver.ResolverMap.*; -import static com.predic8.membrane.core.util.text.TextUtil.linkURL; +import static com.predic8.membrane.core.interceptor.Outcome.ABORT; +import static com.predic8.membrane.core.util.text.TextUtil.*; /** * Basically switches over {@link WSDLValidator}, {@link XMLSchemaValidator}, 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 389706b8a0..5a1e2f2328 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 @@ -16,8 +16,6 @@ public class WSDLMessageElementExtractor { - private static final Logger log = LoggerFactory.getLogger(WSDLMessageElementExtractor.class); - public static Set getPossibleRequestElements(Definitions definitions, String serviceName) { return getPossibleElements(definitions, INPUT, serviceName); } @@ -34,14 +32,14 @@ public static Set getPossibleElements(Definitions definitions, Direction portTypes = getPortTypesByStyle(definitions, serviceName); } - var operationNamesRPC = portTypes.portTypesRPC().stream().map(pt -> pt.getOperations()) + var operationNamesRPC = portTypes.portTypesRPC().stream().map(PortType::getOperations) .flatMap(Collection::stream) .map(op -> new QName(definitions.getTargetNamespace(), getElementNameRPC(op,direction))) .collect(toSet()); Set namesDocumentStyle = getParts(direction, portTypes.portTypesDocument()) - .map(part -> part.getElementQName()) + .map(Part::getElementQName) .filter(Objects::nonNull) .collect(Collectors.toSet()); @@ -74,7 +72,7 @@ private record PortTypesByStyle(List portTypesRPC, List port services = definitions.getServices(); } return services.stream().flatMap(s -> s.getPorts().stream()) - .map(port -> port.getBinding()).toList(); + .map(Port::getBinding).toList(); } private static String getElementNameRPC(Operation operation, Direction direction) { @@ -85,10 +83,10 @@ private static String getElementNameRPC(Operation operation, Direction direction } private static @NotNull Stream getParts(Direction direction, List result) { - return result.stream().map(pt -> pt.getOperations()) + return result.stream().map(PortType::getOperations) .flatMap(Collection::stream) .map(op -> op.getMessagesByDirection(direction)) - .flatMap(Collection::stream).toList().stream().map(message -> message.getPart()); + .flatMap(Collection::stream).toList().stream().map(Message::getPart); } } \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSOAPVersionExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSOAPVersionExtractor.java deleted file mode 100644 index 3282515dc2..0000000000 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSOAPVersionExtractor.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.predic8.membrane.core.interceptor.schemavalidation; - -import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; -import org.w3c.dom.*; - -import java.util.*; - -import static com.predic8.membrane.annot.Constants.*; -import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*; - -public class WSDLSOAPVersionExtractor { - - public static Set getSOAPVersions(Element wsdl) { - var result = new HashSet(); - var ports = wsdl.getElementsByTagNameNS(WSDL11_NS, "port"); - - // No port element, assume abstract WSDL, all versions could be valid - if (ports.getLength() == 0) { - return Set.of(SOAP_11, SOAP_12); - } - - for (int i = 0; i < ports.getLength(); i++) { - - Element port = (Element) ports.item(i); - NodeList children = port.getChildNodes(); - - for (int j = 0; j < children.getLength(); j++) { - - Node n = children.item(j); - if (n.getNodeType() != Node.ELEMENT_NODE) - continue; - - String ns = n.getNamespaceURI(); - - if ("http://schemas.xmlsoap.org/wsdl/soap/".equals(ns)) { - result.add(SOAP_11); - } - - if ("http://schemas.xmlsoap.org/wsdl/soap12/".equals(ns)) { - result.add(SOAP_12); - } - } - } - return result; - } -} diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java index 7b359a9358..e6bd234d8a 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java @@ -36,12 +36,11 @@ private WSDLSchemaExtractor() { public static List getSchemas(Element wsdl) { try { - var definitions = wsdl; //.getDocumentElement(); var result = new ArrayList(); var schemas = wsdl.getElementsByTagNameNS(XML_SCHEMA_NS, "schema"); for (int i = 0; i < schemas.getLength(); i++) { result.add(extractSchema((Element) schemas.item(i), - getNamespaceDeclarations(definitions))); + getNamespaceDeclarations(wsdl))); } return result; } catch (Exception e) { 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 fa8e3b1c23..5436d42539 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 @@ -44,30 +44,17 @@ public class WSDLValidator extends AbstractXMLSchemaValidator { private static final Logger log = LoggerFactory.getLogger(WSDLValidator.class.getName()); - /** - * A WSDL can have several definition.service elements. The serviceName is the - * name of the service against to validate - */ - private final String serviceName; - /** * List of toplevel soapElements that are valid for requests */ - private Set requestElements; + private final Set requestElements; /** * List of toplevel soapElements that are valid for responses */ - private Set responseElements; - - /** - * There might be additional toplevel Elements in a schema that are not used in - * a WSDL message. This field controls if it is checked if an element can be used as - * a request or response message - */ - private boolean checkIfSOAPElementIsUsedAsAWSDLMessage; + private final Set responseElements; - private Set versions; + private final Set versions; private final boolean skipFaults; @@ -79,7 +66,6 @@ public class WSDLValidator extends AbstractXMLSchemaValidator { public WSDLValidator(ResolverMap resourceResolver, String location, String serviceName, ValidatorInterceptor.FailureHandler failureHandler, boolean skipFaults) { super(resourceResolver, location, failureHandler); this.skipFaults = skipFaults; - this.serviceName = serviceName; try { definitions = com.predic8.membrane.core.util.wsdl.parser.Definitions.parse(resourceResolver, location); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java index 6b3b795a94..816966e053 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java @@ -4,14 +4,11 @@ public class Address extends WSDLElement { - private WSDLParserContext ctx; - - private String location; + private final String location; public Address(WSDLParserContext ctx,Node node) { super(node); location = getLocation(node); - this.ctx = ctx; } public String getLocation() { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index 950f581a37..bcd91f2a8a 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -12,7 +12,7 @@ public class Binding extends WSDLElement { - SOAPVersion soapVersion; + private SOAPVersion soapVersion; public enum Style { RPC, DOCUMENT; @@ -25,15 +25,12 @@ public static Style fromString(String style) { } } - private WSDLParserContext ctx; - private Style style; - private List operations; - private PortType portType; + private final List operations; + private final PortType portType; public Binding(WSDLParserContext ctx, Node node) { - super(node); - this.ctx = ctx; + super(ctx,node); operations = getBindingOperations(node); portType = getPortType(node); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java index b155b65f41..dc2e7a5158 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java @@ -4,18 +4,11 @@ public class BindingOperation extends WSDLElement{ - private WSDLParserContext ctx; - - private String name; - private String soapAction; + private final String soapAction; public BindingOperation(WSDLParserContext ctx, Node node) { super(node); - this.ctx = ctx; - } - - public String getName() { - return name; + soapAction = node.getAttributes().getNamedItem("soapAction").getNodeValue(); } public String getSoapAction() { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 78bbc9d774..4e5a744491 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -78,7 +78,7 @@ public List getSchemas() { } public List getSchemaElements() { - return schemas.stream().map(schema -> schema.getSchemaElement()).toList(); + return schemas.stream().map(Schema::getSchemaElement).toList(); } public List getMessages() { 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 3f242e21b5..cb57295836 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 @@ -7,13 +7,10 @@ public class Message extends WSDLElement { - private WSDLParserContext ctx; - - private Part part; + private final Part part; public Message(WSDLParserContext ctx, Node node) { - super(node); - this.ctx = ctx; + super(ctx,node); this.part = getPart(node); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index 061ad2b4c0..dc21a78163 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -12,11 +12,9 @@ public class Operation extends WSDLElement { - private WSDLParserContext ctx; - - private List inputs; - private List outputs; - private List faults; + private final List inputs; + private final List outputs; + private final List faults; public enum Direction { INPUT, OUTPUT; @@ -27,8 +25,7 @@ public boolean matches(String s) { } public Operation(WSDLParserContext ctx, Node node) { - super(node); - this.ctx = ctx; + super(ctx,node); inputs = getInputs(node); outputs = getOutputs(node); faults = getFaults(node); @@ -77,7 +74,7 @@ private List getMessagesByDirection(Node node, Direction direction) { Element io = (Element) child; String messageAttr = io.getAttribute("message"); - if (messageAttr == null || messageAttr.isEmpty()) { + if (messageAttr.isEmpty()) { continue; } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java index 869d67b927..9194a1917d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java @@ -6,19 +6,15 @@ public class Part extends WSDLElement { - private WSDLParserContext ctx; - - private QName element; - private QName type; + private final QName element; + private final QName type; public Part(WSDLParserContext ctx, Node node) { super(node); - this.ctx = ctx; this.element = getElementQName(node); this.type = getTypeQName(node); } - public QName getElementQName() { return element; } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java index d5c07bfc41..99c25331e0 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java @@ -7,14 +7,11 @@ public class Port extends WSDLElement { - private WSDLParserContext ctx; - - private Address address; - private Binding binding; + private final Address address; + private final Binding binding; public Port(WSDLParserContext ctx, Node node) { - super(node); - this.ctx = ctx; + super(ctx,node); address = getAddress(node); binding = getBinding(node); @@ -49,7 +46,7 @@ public Binding getBinding(Node node) { return null; String bindingAttr = port.getAttribute("binding"); - if (bindingAttr == null || bindingAttr.isEmpty()) + if (bindingAttr.isEmpty()) return null; var bindingQName = WSDLParserUtil.resolveQName(bindingAttr, port); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java index 05916c5f29..d86dfd7108 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java @@ -8,13 +8,10 @@ public class PortType extends WSDLElement { - private WSDLParserContext ctx; - private List operations; public PortType(WSDLParserContext ctx, Node node) { - super(node); - this.ctx = ctx; + super(ctx,node); operations = getOperations(node); ctx.getDefinitions().portTypes.add(this); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java index 3d7bf2f5de..661a5c08ae 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java @@ -9,15 +9,10 @@ public class Service extends WSDLElement { public static final String PORT = "port"; - - private final WSDLParserContext ctx; - - private String name; - private List ports; + private final List ports; public Service(WSDLParserContext ctx, Node element) { - super(element); - this.ctx = ctx; + super(ctx,element); ports = getPorts(element); ctx.getDefinitions().services.add(this); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 1b7614a5e7..26d658ce98 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -3,10 +3,13 @@ import org.w3c.dom.*; public class WSDLElement { - private Element element; - private String name; - public WSDLElement(Node node) { + protected final WSDLParserContext ctx; + protected final Element element; + protected String name; + + public WSDLElement(WSDLParserContext ctx,Node node) { + this.ctx = ctx; if (!(node instanceof Element element)) { throw new RuntimeException("Not an element: " + node.getClass()); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index 4af0cbea1f..6a4672eb95 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -11,7 +11,7 @@ public abstract class AbstractIncludeImport extends WSDLElement { protected String schemaLocation; public AbstractIncludeImport(WSDLParserContext ctx, Node node) { - super(node); + super(ctx,node); var e = (Element) node; schemaLocation = e.getAttribute("schemaLocation"); } @@ -21,8 +21,6 @@ protected Schema getSchema(WSDLParserContext ctx) { var combined = ResolverMap.combine(ctx.getBasePath(), schemaLocation); InputStream is = ctx.getResolver().resolve(combined); return new Schema(ctx.basePath(combined), WSDLParserUtil.parse(is)); - } catch (ResourceRetrievalException e) { - throw new RuntimeException(e); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index 535a62110e..d266be6cea 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -5,14 +5,11 @@ public class Import extends AbstractIncludeImport { - private WSDLParserContext ctx; - private String namespace; - - private Schema schema; + private final String namespace; + private final Schema schema; public Import(WSDLParserContext ctx,Node node) { super(ctx,node); - this.ctx = ctx; var e = (Element) node; namespace = e.getAttribute("namespace"); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java index ddc13e6f10..ca8ec18ef6 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java @@ -16,6 +16,6 @@ public Include(WSDLParserContext ctx, Node node, Schema schema) { private static @NotNull String getSchemaLocation(Node node) { if (node instanceof Element e) return e.getAttribute("schemaLocation"); - return null; + return ""; } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java index d598d117b2..a39c97dd93 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -10,17 +10,19 @@ public class Schema extends WSDLElement { - private WSDLParserContext ctx; + private final String targetNamespace; - private String targetNamespace; - private Element schema; - private List schemaElements; - private List imports; - private List includes; + /** + * DOM Element of the schema as read from the WSDL. + */ + private final Element schema; + + private final List schemaElements; + private final List imports; + private final List includes; public Schema(WSDLParserContext ctx, Node node) { - super(node); - this.ctx = ctx; + super(ctx,node); this.targetNamespace = getTargetNamespace(node); this.schema = (Element) node; this.schemaElements = getSchemaElements(schema); @@ -54,7 +56,7 @@ private String getTargetNamespace(Node node) { } String tns = schema.getAttribute("targetNamespace"); - return (tns == null || tns.isEmpty()) ? null : tns; + return (tns.isEmpty()) ? null : tns; } private List getIncludes(Node node) { diff --git a/core/src/test/resources/ws/include/include.wsdl b/core/src/test/resources/ws/include/include.wsdl index 46dbfa50ef..972b08d7d3 100644 --- a/core/src/test/resources/ws/include/include.wsdl +++ b/core/src/test/resources/ws/include/include.wsdl @@ -8,9 +8,7 @@ - - From 0b0fe16703e5246ee73fa41d451f6dd4e6260abd Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 12:07:22 +0100 Subject: [PATCH 03/28] Enhance WSDL parser with support for cyclic imports, schema inclusion, and improved validation tests - Added cyclic import handling for WSDL and XSD files, ensuring robust schema parsing in complex configurations. - Enhanced `Schema` class to include logic for merging elements from included schemas. - Streamlined `WSDLParserContext` to track and prevent duplicate resource loading. - Improved schema validation test coverage with new test cases for cyclic and imported elements. - Minor refactorings in WSDL and schema-related classes for better readability and maintainability. --- .../AbstractXMLSchemaValidator.java | 260 +++++++++--------- .../WSDLMessageElementExtractor.java | 33 ++- .../predic8/membrane/core/util/SOAPUtil.java | 4 +- .../predic8/membrane/core/util/URIUtil.java | 22 +- .../core/util/wsdl/parser/Address.java | 2 +- .../core/util/wsdl/parser/Binding.java | 16 +- .../util/wsdl/parser/BindingOperation.java | 11 +- .../core/util/wsdl/parser/Definitions.java | 18 +- .../core/util/wsdl/parser/Message.java | 21 +- .../membrane/core/util/wsdl/parser/Part.java | 2 +- .../core/util/wsdl/parser/PortType.java | 4 +- .../membrane/core/util/wsdl/parser/Type.java | 4 +- .../util/wsdl/parser/WSDLParserContext.java | 31 +-- .../util/wsdl/parser/WSDLParserException.java | 7 + .../parser/schema/AbstractIncludeImport.java | 45 ++- .../core/util/wsdl/parser/schema/Import.java | 36 ++- .../core/util/wsdl/parser/schema/Include.java | 16 +- .../core/util/wsdl/parser/schema/Schema.java | 42 +-- .../wsdl/parser/schema/SchemaElement.java | 2 +- .../SOAPMessageValidatorInterceptorTest.java | 5 +- .../WSDLMessageElementExtractorTest.java | 42 ++- .../core/util/soap/WSDLParserTest.java | 130 ++++----- .../resources/validation/inline-anytype.wsdl | 5 +- core/src/test/resources/ws/import/cyclic.wsdl | 63 +++++ .../src/test/resources/ws/import/cyclic/a.xsd | 9 + .../src/test/resources/ws/import/cyclic/b.xsd | 9 + .../ws/import/message-references-import.wsdl | 54 ++++ .../src/test/resources/ws/import/messages.xsd | 9 + .../src/test/resources/ws/include/cyclic.wsdl | 63 +++++ .../test/resources/ws/include/cyclic/a.xsd | 9 + .../test/resources/ws/include/cyclic/b.xsd | 9 + .../test/resources/ws/include/include.wsdl | 8 - .../test/resources/ws/include/multiple.wsdl | 63 +++++ .../test/resources/ws/include/multiple/a.xsd | 9 + .../test/resources/ws/include/multiple/b.xsd | 9 + .../test/resources/ws/include/multiple/c.xsd | 7 + .../xsd/inc/MessageStructureTypes2.xsd | 4 - .../xsd/inc/shared/xsd/inc/BaseTypes.xsd | 4 +- .../xsd/{messages.xsd => messages2.xsd} | 3 - 39 files changed, 721 insertions(+), 369 deletions(-) create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java create mode 100644 core/src/test/resources/ws/import/cyclic.wsdl create mode 100644 core/src/test/resources/ws/import/cyclic/a.xsd create mode 100644 core/src/test/resources/ws/import/cyclic/b.xsd create mode 100644 core/src/test/resources/ws/import/message-references-import.wsdl create mode 100644 core/src/test/resources/ws/import/messages.xsd create mode 100644 core/src/test/resources/ws/include/cyclic.wsdl create mode 100644 core/src/test/resources/ws/include/cyclic/a.xsd create mode 100644 core/src/test/resources/ws/include/cyclic/b.xsd create mode 100644 core/src/test/resources/ws/include/multiple.wsdl create mode 100644 core/src/test/resources/ws/include/multiple/a.xsd create mode 100644 core/src/test/resources/ws/include/multiple/b.xsd create mode 100644 core/src/test/resources/ws/include/multiple/c.xsd rename core/src/test/resources/ws/include/xsd/{messages.xsd => messages2.xsd} (68%) 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 716ecc98ee..c7e466e29e 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 @@ -39,139 +39,141 @@ public abstract class AbstractXMLSchemaValidator extends AbstractMessageValidator { - private static final Logger log = LoggerFactory.getLogger(AbstractXMLSchemaValidator.class.getName()); - - private ArrayBlockingQueue> validators; - protected final XOPReconstitutor xopr; - protected final String location; - protected final ResolverMap resolver; - protected final ValidatorInterceptor.FailureHandler failureHandler; - - protected final AtomicLong valid = new AtomicLong(); - protected final AtomicLong invalid = new AtomicLong(); - - public AbstractXMLSchemaValidator(ResolverMap resolver, String location, ValidatorInterceptor.FailureHandler failureHandler) { - this.location = location; - this.resolver = resolver; - this.failureHandler = failureHandler; - xopr = new XOPReconstitutor(); - } - - public void init() { - super.init(); - int concurrency = Runtime.getRuntime().availableProcessors() * 2; - validators = new ArrayBlockingQueue<>(concurrency); - for (int i = 0; i < concurrency; i++) - validators.add(createValidators()); - } - - public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Exception { - Message msg = exc.getMessage(flow); - List exceptions = new ArrayList<>(); - String preliminaryError = getPreliminaryError(xopr, msg); - if (preliminaryError == null) { - List vals = validators.take(); - try { - // the message must be valid for one schema embedded into WSDL - for (Validator validator: vals) { - SchemaValidatorErrorHandler handler = (SchemaValidatorErrorHandler)validator.getErrorHandler(); - try { + private static final Logger log = LoggerFactory.getLogger(AbstractXMLSchemaValidator.class.getName()); + protected final XOPReconstitutor xopr; + protected final String location; + protected final ResolverMap resolver; + protected final ValidatorInterceptor.FailureHandler failureHandler; + protected final AtomicLong valid = new AtomicLong(); + protected final AtomicLong invalid = new AtomicLong(); + private ArrayBlockingQueue> validators; + + public AbstractXMLSchemaValidator(ResolverMap resolver, String location, ValidatorInterceptor.FailureHandler failureHandler) { + this.location = location; + this.resolver = resolver; + this.failureHandler = failureHandler; + xopr = new XOPReconstitutor(); + } + + private static @NotNull Map createErrorEntry(Exception e) { + var error = new LinkedHashMap(); + error.put("message", e.getMessage()); + if (e instanceof SAXParseException spe) { + error.put("line", spe.getLineNumber()); + error.put("column", spe.getColumnNumber()); + } + return error; + } + + public void init() { + super.init(); + int concurrency = Runtime.getRuntime().availableProcessors() * 2; + validators = new ArrayBlockingQueue<>(concurrency); + for (int i = 0; i < concurrency; i++) + validators.add(createValidators()); + } + + public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Exception { + Message msg = exc.getMessage(flow); + List exceptions = new ArrayList<>(); + String preliminaryError = getPreliminaryError(xopr, msg); + if (preliminaryError == null) { + List vals = validators.take(); + try { + // the message must be valid for one schema embedded into WSDL + for (Validator validator : vals) { + SchemaValidatorErrorHandler handler = (SchemaValidatorErrorHandler) validator.getErrorHandler(); + try { validator.validate(getMessageBody(xopr.reconstituteIfNecessary(msg))); - if (handler.noErrors()) { - valid.incrementAndGet(); - return CONTINUE; - } - exceptions.add(handler.getException()); - } finally { - handler.reset(); - } - } - } catch (Exception e) { - exceptions.add(e); - } finally { - validators.put(vals); - } - } else { - exceptions.add(new Exception(preliminaryError)); - } - String errorMsg = getErrorMsg(exceptions); // Errors als simple String - if (failureHandler != null) { - failureHandler.handleFailure(errorMsg, exc); - } - exc.setProperty("error", errorMsg); // TODO Search for usage. If it is used rename property. See properties in class Exchange - setErrorResponse(exc,flow,exceptions); - exc.getResponse().getHeader().add(VALIDATION_ERROR_SOURCE, flow.name()); - invalid.incrementAndGet(); - return ABORT; - } - - protected List createValidators() { - var sf = SchemaFactory.newInstance(XSD_NS); - sf.setResourceResolver(resolver.toLSResourceResolver()); - var validators = new ArrayList(); - for (var schema : getSchemas()) { - log.debug("Creating validator for schema: {}", location); + if (handler.noErrors()) { + valid.incrementAndGet(); + return CONTINUE; + } + exceptions.add(handler.getException()); + } finally { + handler.reset(); + } + } + } catch (Exception e) { + exceptions.add(e); + } finally { + validators.put(vals); + } + } else { + exceptions.add(new Exception(preliminaryError)); + } + String errorMsg = getErrorMsg(exceptions); // Errors als simple String + if (failureHandler != null) { + failureHandler.handleFailure(errorMsg, exc); + } + exc.setProperty("error", errorMsg); // TODO Search for usage. If it is used rename property. See properties in class Exchange + setErrorResponse(exc, flow, exceptions); + exc.getResponse().getHeader().add(VALIDATION_ERROR_SOURCE, flow.name()); + invalid.incrementAndGet(); + return ABORT; + } + + protected List createValidators() { + var sf = SchemaFactory.newInstance(XSD_NS); + sf.setResourceResolver(resolver.toLSResourceResolver()); + var validators = new ArrayList(); + var schemas = getSchemas(); + for (int i = 0; i < schemas.size(); i++) { + var schema = schemas.get(i); + log.debug("Creating validator {}/{} for schema at: {}", i + 1, schemas.size(), location); validators.add(createValidator(schema, sf)); - } - return validators; - } + } + return validators; + } - private @NotNull Validator createValidator(Element schema, SchemaFactory sf) { + private @NotNull Validator createValidator(Element schema, SchemaFactory sf) { try { - DOMSource source = new DOMSource(schema); - source.setSystemId(location); - var validator = sf.newSchema(source).newValidator(); - validator.setErrorHandler(new SchemaValidatorErrorHandler()); - return validator; + DOMSource source = new DOMSource(schema); + source.setSystemId(location); + var validator = sf.newSchema(source).newValidator(); + validator.setErrorHandler(new SchemaValidatorErrorHandler()); + return validator; } catch (SAXException e) { - throw new ConfigurationException("Cannot read schema %s.".formatted(location),e); + throw new ConfigurationException("Cannot read schema %s.".formatted(location), e); } - } - - private String getErrorMsg(List excs) { - StringBuilder buf = new StringBuilder(); - buf.append("%s: ".formatted(getErrorTitle())); - for (Exception e : excs) { - buf.append(e); - buf.append("; "); - } - return buf.toString(); - } - - protected List> convertExceptionsToMap(List exceptions) { - return exceptions.stream().map(AbstractXMLSchemaValidator::createErrorEntry).toList(); - } - - private static @NotNull Map createErrorEntry(Exception e) { - var error = new LinkedHashMap(); - error.put("message", e.getMessage()); - if (e instanceof SAXParseException spe) { - error.put("line", spe.getLineNumber()); - error.put("column", spe.getColumnNumber()); - } - return error; - } - - @Override - public long getValid() { - return valid.get(); - } - - @Override - public long getInvalid() { - return invalid.get(); - } - - protected abstract List getSchemas(); - protected abstract Source getMessageBody(InputStream input); - - protected abstract void setErrorResponse(Exchange exchange, String message); - protected abstract void setErrorResponse(Exchange exchange, Interceptor.Flow flow,List exceptions); - - protected abstract String getPreliminaryError(XOPReconstitutor xopr, Message msg); - - @Override - public String getErrorTitle() { - return "XML message validation failed"; - } + } + + private String getErrorMsg(List excs) { + StringBuilder buf = new StringBuilder(); + buf.append("%s: ".formatted(getErrorTitle())); + for (Exception e : excs) { + buf.append(e); + buf.append("; "); + } + return buf.toString(); + } + + protected List> convertExceptionsToMap(List exceptions) { + return exceptions.stream().map(AbstractXMLSchemaValidator::createErrorEntry).toList(); + } + + @Override + public long getValid() { + return valid.get(); + } + + @Override + public long getInvalid() { + return invalid.get(); + } + + protected abstract List getSchemas(); + + protected abstract Source getMessageBody(InputStream input); + + protected abstract void setErrorResponse(Exchange exchange, String message); + + protected abstract void setErrorResponse(Exchange exchange, Interceptor.Flow flow, List exceptions); + + protected abstract String getPreliminaryError(XOPReconstitutor xopr, Message msg); + + @Override + public String getErrorTitle() { + return "XML message validation failed"; + } } 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 5a1e2f2328..4856cc239f 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 @@ -3,15 +3,13 @@ import com.predic8.membrane.core.util.wsdl.parser.*; import com.predic8.membrane.core.util.wsdl.parser.Operation.*; import org.jetbrains.annotations.*; -import org.slf4j.*; import javax.xml.namespace.*; import java.util.*; import java.util.stream.*; -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 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.*; public class WSDLMessageElementExtractor { @@ -34,8 +32,8 @@ public static Set getPossibleElements(Definitions definitions, Direction var operationNamesRPC = portTypes.portTypesRPC().stream().map(PortType::getOperations) .flatMap(Collection::stream) - .map(op -> new QName(definitions.getTargetNamespace(), getElementNameRPC(op,direction))) - .collect(toSet()); + .map(op -> new QName(definitions.getTargetNamespace(), getElementNameRPC(op, direction))) + .collect(toSet()); Set namesDocumentStyle = getParts(direction, portTypes.portTypesDocument()) @@ -55,24 +53,28 @@ public static Set getPossibleElements(Definitions definitions, Direction for (var binding : getBindings(definitions, serviceName)) { if (binding.getStyle() == RPC) { portTypesRPC.add(binding.getPortType()); + continue; } portTypesDocument.add(binding.getPortType()); } return new PortTypesByStyle(portTypesRPC, portTypesDocument); } - private record PortTypesByStyle(List portTypesRPC, List portTypesDocument) { + private static @NotNull List getBindings(Definitions definitions, String serviceName) { + return getServices(definitions, serviceName).stream() + .flatMap(s -> s.getPorts().stream()) + .map(Port::getBinding).toList(); } - private static @NotNull List getBindings(Definitions definitions, String serviceName) { - List services; + private static List getServices(Definitions definitions, String serviceName) { if (serviceName != null) { - services = List.of(definitions.getService(serviceName)); - } else { - services = definitions.getServices(); + var service = definitions.getService(serviceName); + if (service == null) { + throw new IllegalArgumentException("Unknown WSDL service: " + serviceName); + } + return List.of(service); } - return services.stream().flatMap(s -> s.getPorts().stream()) - .map(Port::getBinding).toList(); + return definitions.getServices(); } private static String getElementNameRPC(Operation operation, Direction direction) { @@ -89,4 +91,7 @@ private static String getElementNameRPC(Operation operation, Direction direction .flatMap(Collection::stream).toList().stream().map(Message::getPart); } + private record PortTypesByStyle(List portTypesRPC, List portTypesDocument) { + } + } \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java b/core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java index 9a3850e028..f9e0a0180a 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java @@ -108,10 +108,10 @@ public static SOAPAnalysisResult analyseSOAPMessage( XOPReconstitutor xopr, Mess * 2: waiting for "" */ try { - XMLInputFactory f = XMLInputFactory.newInstance(); + var f = XMLInputFactory.newInstance(); f.setProperty(IS_REPLACING_ENTITY_REFERENCES, false); f.setProperty(IS_SUPPORTING_EXTERNAL_ENTITIES, false); - XMLEventReader parser = f.createXMLEventReader(xopr.reconstituteIfNecessary(msg)); + var parser = f.createXMLEventReader(xopr.reconstituteIfNecessary(msg)); SoapVersion version = null; int state = 0; diff --git a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java index 5d3780a802..fced551357 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java @@ -15,8 +15,9 @@ import org.jetbrains.annotations.*; -import java.net.URI; import java.net.*; +import java.net.URI; +import java.nio.file.*; import java.util.*; import java.util.regex.*; @@ -35,11 +36,11 @@ public class URIUtil { * @throws URISyntaxException */ public static java.net.URI convertPath2FileURI(String path) throws URISyntaxException { - return new URI( addFilePrefix(encodePathCharactersForUri(path))); + return new URI(addFilePrefix(encodePathCharactersForUri(path))); } public static String encodePathCharactersForUri(String s) { - return s.replaceAll(" ","%20").replace("\\","/"); + return s.replaceAll(" ", "%20").replace("\\", "/"); } private static @NotNull String addFilePrefix(String path) { @@ -52,7 +53,7 @@ public static String encodePathCharactersForUri(String s) { public static String convertPath2FilePathString(String path) { path = addFilePrefix(path); - return path.replaceAll(" ","%20").replaceAll("\\\\","/"); + return path.replaceAll(" ", "%20").replaceAll("\\\\", "/"); } /** @@ -138,4 +139,17 @@ public static String normalizeSingleDot(String uri) { return sb.toString(); } + public static String normalize(String location) { + if (location == null || location.isEmpty()) + throw new IllegalArgumentException("location must not be null or empty"); + + // already absolute URI + if (location.matches("^[a-zA-Z][a-zA-Z0-9+.-]*:.*")) { + return URI.create(location).normalize().toString(); + } + + // filesystem path + return Path.of(location).toAbsolutePath().normalize().toString(); + } + } \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java index 816966e053..6ef6b75367 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java @@ -7,7 +7,7 @@ public class Address extends WSDLElement { private final String location; public Address(WSDLParserContext ctx,Node node) { - super(node); + super(ctx,node); location = getLocation(node); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index bcd91f2a8a..d1969e92c7 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -62,14 +62,13 @@ private List getBindingOperations(Node node) { if (child.getNodeType() == ELEMENT_NODE && "binding".equals(child.getLocalName())) { style = getStyle(child); - ctx.style(style); - if (child.getNamespaceURI().equals(WSDL_SOAP11_NS)) { + if (WSDL_SOAP11_NS.equals(child.getNamespaceURI())) { soapVersion = SOAPVersion.SOAP_11; - } else if (child.getNamespaceURI().equals(WSDL_SOAP12_NS)) { + ctx.getDefinitions().addSoapVersion(soapVersion); + } else if (WSDL_SOAP12_NS.equals(child.getNamespaceURI())) { soapVersion = SOAPVersion.SOAP_12; + ctx.getDefinitions().addSoapVersion(soapVersion); } - ctx.getDefinitions().addSoapVersion(soapVersion); - } } return result; @@ -95,12 +94,11 @@ private PortType getPortType(Node node) { var portTypeQName = WSDLParserUtil.resolveQName(type, bindingElement); - Element definitions = bindingElement.getOwnerDocument().getDocumentElement(); - NodeList portTypes = definitions.getElementsByTagNameNS(WSDL11_NS, "portType"); + var definitions = bindingElement.getOwnerDocument().getDocumentElement(); + var portTypes = definitions.getElementsByTagNameNS(WSDL11_NS, "portType"); for (int i = 0; i < portTypes.getLength(); i++) { - Element portType = (Element) portTypes.item(i); - + var portType = (Element) portTypes.item(i); if (portTypeQName.getLocalPart().equals(portType.getAttribute("name"))) { return new PortType(ctx, portType); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java index dc2e7a5158..93d392f889 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java @@ -7,8 +7,15 @@ public class BindingOperation extends WSDLElement{ private final String soapAction; public BindingOperation(WSDLParserContext ctx, Node node) { - super(node); - soapAction = node.getAttributes().getNamedItem("soapAction").getNodeValue(); + super(ctx,node); + soapAction = getSoapAction(node); + } + + private String getSoapAction(Node node) { + Node n = node.getAttributes().getNamedItem("soapAction"); + if (n == null) + return ""; + return n.getNodeValue(); } public String getSoapAction() { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 4e5a744491..95fdc6f210 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -24,8 +24,6 @@ public enum SOAPVersion { List bindings; List services = new ArrayList<>(); - List importedSchemas = new ArrayList<>(); - String targetNamespace; Set soapVersions = new HashSet<>(); @@ -58,15 +56,16 @@ public void parse(Element element) { public static Definitions parse(Resolver resolver, String location) throws Exception { var defs = new Definitions(); - defs.ctx = new WSDLParserContext(defs, resolver, location); - defs.parse(WSDLParserUtil.parse(resolver.resolve(location))); + defs.ctx = new WSDLParserContext(defs, resolver, location, new ArrayList<>()); + try(var is = resolver.resolve(location)) { + defs.parse(WSDLParserUtil.parse(is)); + } return defs; } public void parse(InputStream is, Resolver resolver) { try { - ctx.resolver(resolver); - //defs.ctx = new WSDLParserContext(defs, resolver,""); + ctx = ctx.resolver(resolver); parse(WSDLParserUtil.parse(is)); } catch (Exception e) { throw new RuntimeException("Could not parse WSDL", e); @@ -105,18 +104,11 @@ public String getTargetNamespace() { return targetNamespace; } - public List getImportedSchemas() { - return importedSchemas; - } public Set getSoapVersions() { return soapVersions; } - public void addImportedSchema(Schema schema) { - importedSchemas.add(schema); - } - public void addSoapVersion(SOAPVersion soapVersion) { soapVersions.add(soapVersion); } 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 cb57295836..eb4afbd214 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 @@ -1,21 +1,30 @@ package com.predic8.membrane.core.util.wsdl.parser; -import com.predic8.membrane.annot.*; import org.w3c.dom.*; -import static com.predic8.membrane.annot.Constants.WSDL11_NS; +import java.util.*; + +import static com.predic8.membrane.annot.Constants.*; public class Message extends WSDLElement { - private final Part part; + private final List parts = new ArrayList<>(); public Message(WSDLParserContext ctx, Node node) { super(ctx,node); - this.part = getPart(node); + this.parts.add(getPart(node)); } + /** + * Document style only uses one part. + * @return + */ public Part getPart() { - return part; + return parts.getFirst(); + } + + public List getParts() { + return parts; } private Part getPart(Node node) { @@ -35,6 +44,6 @@ private Part getPart(Node node) { @Override public String toString() { - return "Message [part=" + part + "]"; + return "Message [parts=" + parts + "]"; } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java index 9194a1917d..170c6abd5b 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java @@ -10,7 +10,7 @@ public class Part extends WSDLElement { private final QName type; public Part(WSDLParserContext ctx, Node node) { - super(node); + super(ctx,node); this.element = getElementQName(node); this.type = getTypeQName(node); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java index d86dfd7108..437946d99d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java @@ -1,9 +1,11 @@ package com.predic8.membrane.core.util.wsdl.parser; +import com.predic8.membrane.annot.*; import org.w3c.dom.*; import java.util.*; +import static com.predic8.membrane.annot.Constants.WSDL11_NS; import static org.w3c.dom.Node.*; public class PortType extends WSDLElement { @@ -29,7 +31,7 @@ private List getOperations(Node node) { if (child.getNodeType() == ELEMENT_NODE && "operation".equals(child.getLocalName()) -// && WSDL11_NS.equals(child.getNamespaceURI()) + && WSDL11_NS.equals(child.getNamespaceURI()) ) { operations.add(new Operation(ctx, child)); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java index 70c532e368..a8e3ef4c14 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java @@ -3,7 +3,7 @@ import org.w3c.dom.*; public class Type extends WSDLElement{ - public Type(Node node) { - super(node); + public Type(WSDLParserContext ctx, Node node) { + super(ctx,node); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java index 265e02390c..4fde961ba3 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java @@ -3,46 +3,37 @@ import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.wsdl.parser.Binding.*; +import java.util.*; + public class WSDLParserContext { private Definitions definitions; private Resolver resolver; private String basePath; - private Style style; - - public WSDLParserContext(Resolver resolver, String basePath) { - this.basePath = basePath; - this.resolver = resolver; - } + private List visitedLocations = new ArrayList<>(); - public WSDLParserContext(Definitions wsdl, Resolver resolver, String basePath) { + public WSDLParserContext(Definitions wsdl, Resolver resolver, String basePath, List visitedLocations) { this.definitions = wsdl; this.resolver = resolver; this.basePath = basePath; + this.visitedLocations = visitedLocations; } public WSDLParserContext definitions(Definitions definitions) { - this.definitions = definitions; - return this; + return new WSDLParserContext(definitions, resolver, basePath, visitedLocations); } public WSDLParserContext basePath(String basePath) { - this.basePath = basePath; - return this; + visitedLocations.add(basePath); + return new WSDLParserContext(definitions, resolver, basePath, visitedLocations); } public WSDLParserContext resolver(Resolver resolver) { - this.resolver = resolver; - return this; - } - - public WSDLParserContext style(Style style) { - this.style = style; - return this; + return new WSDLParserContext(definitions, resolver, basePath, visitedLocations); } - public Style getStyle() { - return style; + public List getVisitedLocations() { + return visitedLocations; } public Definitions getDefinitions() { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java new file mode 100644 index 0000000000..6b4074579a --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java @@ -0,0 +1,7 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +public class WSDLParserException extends RuntimeException { + public WSDLParserException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index 6a4672eb95..8f2df28e5c 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -1,32 +1,59 @@ package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.resolver.*; +import com.predic8.membrane.core.util.*; import com.predic8.membrane.core.util.wsdl.parser.*; +import org.jetbrains.annotations.*; import org.w3c.dom.*; -import java.io.*; - public abstract class AbstractIncludeImport extends WSDLElement { protected String schemaLocation; + protected Schema referensingSchema; + protected Schema schema; + + public AbstractIncludeImport(WSDLParserContext ctx, Node node, Schema referensingSchema) { + super(ctx, node); + this.referensingSchema = referensingSchema; + schemaLocation = getSchemaLocation(node); + schema = getSchema(ctx); + } - public AbstractIncludeImport(WSDLParserContext ctx, Node node) { - super(ctx,node); - var e = (Element) node; - schemaLocation = e.getAttribute("schemaLocation"); + protected abstract void registerLocation(String normalizedLocation); + + public Schema getSchema() { + return schema; } protected Schema getSchema(WSDLParserContext ctx) { try { - var combined = ResolverMap.combine(ctx.getBasePath(), schemaLocation); - InputStream is = ctx.getResolver().resolve(combined); - return new Schema(ctx.basePath(combined), WSDLParserUtil.parse(is)); + var resolved = resolve(ctx); + + // Check if the schema has already been imported or included + if (ctx.getVisitedLocations().contains(resolved)) + return null; + + registerLocation(resolved); + + try (var is = ctx.getResolver().resolve(resolved)) { + return new Schema(ctx.basePath(resolved), WSDLParserUtil.parse(is)); + } } catch (Exception e) { throw new RuntimeException(e); } } + private String resolve(WSDLParserContext ctx) { + return URIUtil.normalize(ResolverMap.combine(ctx.getBasePath(), schemaLocation)); + } + public String getSchemaLocation() { return schemaLocation; } + + private static @NotNull String getSchemaLocation(Node node) { + if (node instanceof Element e) + return e.getAttribute("schemaLocation"); + return ""; + } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index d266be6cea..f5389adc1d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -1,19 +1,39 @@ package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.util.wsdl.parser.*; +import org.jetbrains.annotations.*; import org.w3c.dom.*; public class Import extends AbstractIncludeImport { - private final String namespace; - private final Schema schema; + private String namespace; - public Import(WSDLParserContext ctx,Node node) { - super(ctx,node); - var e = (Element) node; - namespace = e.getAttribute("namespace"); + public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { + super(ctx, node, referensingSchema); - schema=getSchema(ctx); - ctx.getDefinitions().addImportedSchema(schema); + // Schema is null when it is already imported from somewhere else. + if (schema == null) + return; + + this.referensingSchema = referensingSchema; + + namespace = getNamespace(node); + + if (!schema.getTargetNamespace().equals(namespace)) { + throw new WSDLParserException("The namespace {%s} of the import does not match the targetNamespace of the imported schema {}.".formatted(namespace, schema.getTargetNamespace())); + } + } + + @Override + protected void registerLocation(String normalizedLocation) { + referensingSchema.getImports().add(this); + } + + private @NotNull String getNamespace(Node node) { + return ((Element) node).getAttribute("namespace"); + } + + public String getNamespace() { + return namespace; } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java index ca8ec18ef6..84c2b5eec8 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java @@ -1,21 +1,19 @@ package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.util.wsdl.parser.*; -import org.jetbrains.annotations.*; import org.w3c.dom.*; public class Include extends AbstractIncludeImport { - public Include(WSDLParserContext ctx, Node node, Schema schema) { - super(ctx,node); - this.schemaLocation = getSchemaLocation( node); + public Include(WSDLParserContext ctx, Node node, Schema referensingSchema) { + super(ctx,node,referensingSchema); - schema.getSchemaElements().addAll(getSchema(ctx).getSchemaElements()); + // Copy all elements from the imported Schema into the importing + referensingSchema.include(schema); } - private static @NotNull String getSchemaLocation(Node node) { - if (node instanceof Element e) - return e.getAttribute("schemaLocation"); - return ""; + @Override + protected void registerLocation(String normalizedLocation) { + referensingSchema.getIncludes().add(this); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java index a39c97dd93..39b3002234 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -18,16 +18,16 @@ public class Schema extends WSDLElement { private final Element schema; private final List schemaElements; - private final List imports; - private final List includes; + private final List imports = new ArrayList<>(); + private final List includes = new ArrayList<>(); public Schema(WSDLParserContext ctx, Node node) { - super(ctx,node); + super(ctx, node); this.targetNamespace = getTargetNamespace(node); this.schema = (Element) node; this.schemaElements = getSchemaElements(schema); - this.imports = getImports(node); - this.includes = getIncludes(node); + parseImports(node); + parseIncludes(node); } public String getTargetNamespace() { @@ -50,47 +50,53 @@ public List getIncludes() { return includes; } + /** + * Take everything from the included schema and add it to the current schema. + * At the moment only elements are added. More is not needed. + * Later other features can be added as needed. + * + * @param includedSchema the schema whose elements are to be added to the current schema + */ + public void include(Schema includedSchema) { + if (includedSchema == null) + return; + + schemaElements.addAll(includedSchema.getSchemaElements()); + imports.addAll(includedSchema.getImports()); + } + private String getTargetNamespace(Node node) { if (!(node instanceof Element schema)) { return null; } - String tns = schema.getAttribute("targetNamespace"); return (tns.isEmpty()) ? null : tns; } - private List getIncludes(Node node) { - var result = new ArrayList(); + private void parseIncludes(Node node) { var children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); if (child.getNodeType() == ELEMENT_NODE && "include".equals(child.getLocalName()) && XSD_NS.equals(child.getNamespaceURI())) { - result.add(new Include(ctx, child, this)); + new Include(ctx, child, this); } } - - return result; } - private List getImports(Node node) { - var result = new ArrayList(); + private void parseImports(Node node) { var children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); if (child.getNodeType() == ELEMENT_NODE && "import".equals(child.getLocalName()) && XSD_NS.equals(child.getNamespaceURI())) { - result.add(new Import(ctx, child)); + new Import(ctx, child,this); } } - - return result; } private List getSchemaElements(Element schema) { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java index f63802bc99..f1f8c0837f 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java @@ -8,7 +8,7 @@ public class SchemaElement extends WSDLElement { WSDLParserContext ctx; public SchemaElement(WSDLParserContext ctx,Node node) { - super(node); + super(ctx,node); this.ctx = ctx; } diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPMessageValidatorInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPMessageValidatorInterceptorTest.java index 088f259f7a..0afe29b10e 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPMessageValidatorInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPMessageValidatorInterceptorTest.java @@ -134,7 +134,6 @@ void handleRequestInvalidEmailMessageUnknownElement() throws Exception { assertEquals(ABORT, createValidatorInterceptor(E_MAIL_SERVICE_WSDL).handleRequest(exc)); var body = exc.getResponse().getBodyAsStringDecoded(); - System.out.println(body); assertTrue(body.contains(WSDL_MESSAGE_VALIDATION_FAILED)); assertTrue(body.contains("cvc-complex-type.2.4.a")); assertTrue(body.contains("line")); @@ -149,11 +148,11 @@ void inlineSchemaWithAnyType() throws Exception { assertEquals(ABORT, createValidatorInterceptor(INLINE_ANYTYPE_WSDL).handleRequest(exc)); var body = exc.getResponse().getBodyAsStringDecoded(); - System.out.println(body); assertTrue(body.contains(WSDL_MESSAGE_VALIDATION_FAILED)); assertTrue(body.contains("ValidateEmailRequest")); assertTrue(body.contains("is not a valid request element")); - assertTrue(body.contains("[{http://www.examples.com/wsdl/HelloService.wsdl}sayHello]")); + assertTrue(body.contains("http://www.examples.com/wsdl/HelloService.wsdl")); + assertTrue(body.contains("sayHello")); } private String getContent(String fileName) throws Exception { diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java index 2b27a5c906..9a5033dc41 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java @@ -2,10 +2,10 @@ import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.wsdl.parser.*; +import org.jetbrains.annotations.*; import org.junit.jupiter.api.*; import javax.xml.namespace.*; -import java.io.*; import static com.predic8.membrane.core.interceptor.schemavalidation.WSDLMessageElementExtractor.*; import static org.junit.jupiter.api.Assertions.*; @@ -22,48 +22,38 @@ class WSDLMessageElementExtractorTest { @Test void extract() throws Exception { - var location = "/ws/cities.wsdl"; - try (var wsdl = getClass().getResourceAsStream(location)) { - var doc = parseWsdl(wsdl, location); - var requestElements = getPossibleRequestElements(doc, null); + var requestElements = getPossibleRequestElements(getDefinitions("classpath:/ws/cities.wsdl"), null); - assertEquals(1, requestElements.size()); - assertTrue(requestElements.contains(GET_CITY_QNAME)); + assertEquals(1, requestElements.size()); + assertTrue(requestElements.contains(GET_CITY_QNAME)); + + var responseElements = getPossibleResponseElements(getDefinitions("classpath:/ws/cities.wsdl"), null); + assertEquals(1, responseElements.size()); + assertTrue(responseElements.contains(GET_CITYRESPONSE_QNAME)); - var responseElements = getPossibleResponseElements(doc, null); - assertEquals(1, responseElements.size()); - assertTrue(responseElements.contains(GET_CITYRESPONSE_QNAME)); - } } @Test void eMailServiceWSDL() throws Exception { - var doc = Definitions.parse(new ResolverMap(), "classpath:/validation/XWebEmailValidation.wsdl.xml"); - var requestElements = getPossibleRequestElements(doc, null); + var requestElements = getPossibleRequestElements(getDefinitions("classpath:/validation/XWebEmailValidation.wsdl.xml"), null); assertEquals(1, requestElements.size()); assertTrue(requestElements.contains(GET_EMAIL_QNAME)); - var responseElements = getPossibleResponseElements(doc, null); + var responseElements = getPossibleResponseElements(getDefinitions("classpath:/validation/XWebEmailValidation.wsdl.xml"), null); assertEquals(1, responseElements.size()); assertTrue(responseElements.contains(GET_EMAILRESPONSE_QNAME)); - } @Test void rpcStyle() throws Exception { - var location = "/validation/inline-anytype.wsdl"; - try (var wsdl = getClass().getResourceAsStream(location)) { - var doc = parseWsdl(wsdl, location); - var requestElements = getPossibleRequestElements(doc, "Hello_Service"); - assertEquals(1, requestElements.size()); - assertTrue(requestElements.contains(new QName("http://www.examples.com/wsdl/HelloService.wsdl", "sayHello"))); - } + var requestElements = getPossibleRequestElements(getDefinitions("classpath:/validation/inline-anytype.wsdl"), "Hello_Service"); + assertEquals(1, requestElements.size()); + assertTrue(requestElements.contains(new QName("http://www.examples.com/wsdl/HelloService.wsdl", "sayHello"))); } - private static Definitions parseWsdl(InputStream is, String location) { - var ctx = new WSDLParserContext(null, new ResolverMap(), location); - var definitions = new Definitions(ctx); - definitions.parse(is, null); + private static @NotNull Definitions getDefinitions(String location) throws Exception { + var definitions = Definitions.parse(new ResolverMap(), location); return definitions; } + } \ No newline at end of file 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 4383c1f18a..dc54f54218 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 @@ -8,88 +8,76 @@ import java.io.*; import java.util.*; -import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.DOCUMENT; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.*; +import static org.junit.jupiter.api.Assertions.*; class WSDLParserTest { @Test void simpleSchema() throws Exception { - try (InputStream is = getClass().getResourceAsStream("/ws/cities.wsdl")) { - var definitions = new Definitions(new WSDLParserContext(null, null, null)); - definitions.parse(is, null); - assertEquals(1,definitions.getSchemas().size()); - var schema = definitions.getSchemas().getFirst(); - assertEquals("https://predic8.de/cities", schema.getTargetNamespace()); - var schemaElements = schema.getSchemaElements(); - assertEquals(2, schemaElements.size()); - var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList(); - assertEquals(List.of("getCity","getCityResponse"), schemaElementNames); + var definitions = Definitions.parse(new ResolverMap(), "classpath:/ws/cities.wsdl"); + assertEquals(1, definitions.getSchemas().size()); + var schema = definitions.getSchemas().getFirst(); + assertEquals("https://predic8.de/cities", schema.getTargetNamespace()); + var schemaElements = schema.getSchemaElements(); + assertEquals(2, schemaElements.size()); + var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList(); + assertEquals(List.of("getCity", "getCityResponse"), schemaElementNames); - var services = definitions.getServices(); - assertEquals(1, services.size()); - var service = services.getFirst(); - assertEquals("CityService", service.getName()); - var ports = service.getPorts(); - assertEquals(1, ports.size()); - var port = ports.getFirst(); - assertEquals("CityPort", port.getName()); - var address = port.getAddress(); - assertEquals("http://localhost:2001/services/cities", address.getLocation()); - var binding = port.getBinding(); - assertEquals("CitySoapBinding", binding.getName()); - assertEquals(DOCUMENT, binding.getStyle()); - var portType = binding.getPortType(); - assertEquals("CityPort",portType.getName()); - var operations = portType.getOperations(); - assertEquals(1,operations.size()); - var getCityOperation = operations.getFirst(); - var inputs = getCityOperation.getInputs(); - assertEquals(1, inputs.size()); - var getCityInput = inputs.getFirst(); - var getCityPart = getCityInput.getPart(); - assertEquals("getCity", getCityPart.getName()); - assertEquals("getCity", getCityPart.getElementQName().getLocalPart()); - assertEquals("https://predic8.de/cities", getCityPart.getElementQName().getNamespaceURI()); - } + var services = definitions.getServices(); + assertEquals(1, services.size()); + var service = services.getFirst(); + assertEquals("CityService", service.getName()); + var ports = service.getPorts(); + assertEquals(1, ports.size()); + var port = ports.getFirst(); + assertEquals("CityPort", port.getName()); + var address = port.getAddress(); + assertEquals("http://localhost:2001/services/cities", address.getLocation()); + var binding = port.getBinding(); + assertEquals("CitySoapBinding", binding.getName()); + assertEquals(DOCUMENT, binding.getStyle()); + var portType = binding.getPortType(); + assertEquals("CityPort", portType.getName()); + var operations = portType.getOperations(); + assertEquals(1, operations.size()); + var getCityOperation = operations.getFirst(); + var inputs = getCityOperation.getInputs(); + assertEquals(1, inputs.size()); + var getCityInput = inputs.getFirst(); + var getCityPart = getCityInput.getPart(); + assertEquals("getCity", getCityPart.getName()); + assertEquals("getCity", getCityPart.getElementQName().getLocalPart()); + assertEquals("https://predic8.de/cities", getCityPart.getElementQName().getNamespaceURI()); } - @Test + @Test void includeImport() throws Exception { - var resolver = new ResolverMap(); - String uri = "classpath://ws/include/include.wsdl"; - try (InputStream is = resolver.resolve(uri)) { - var ctx = new WSDLParserContext(null,resolver,uri); - var definitions = new Definitions(ctx); - definitions.parse(is,resolver); - assertEquals(1, definitions.getSchemas().size()); - var schema = definitions.getSchemas().getFirst(); - assertEquals("http://example.com/test", schema.getTargetNamespace()); - var schemaElements = schema.getSchemaElements(); - assertEquals(3, schemaElements.size()); - var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList(); - assertEquals(List.of("test", "testResponse","du"), schemaElementNames); + var definitions = Definitions.parse(new ResolverMap(), "classpath://ws/include/include.wsdl"); + assertEquals(1, definitions.getSchemas().size()); + var embedded = definitions.getSchemas().getFirst(); + assertEquals("http://example.com/test", embedded.getTargetNamespace()); + var schemaElements = embedded.getSchemaElements(); + assertEquals(3, schemaElements.size()); + var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList(); + assertEquals(List.of("test", "testResponse", "du"), schemaElementNames); + + var includeMessageStructureTypes = embedded.getIncludes().getFirst(); + assertEquals("xsd/inc/MessageStructureTypes2.xsd", includeMessageStructureTypes.getSchemaLocation()); - var includeMessageStructureTypes = schema.getIncludes().getFirst(); - assertEquals("xsd/inc/MessageStructureTypes2.xsd", includeMessageStructureTypes.getSchemaLocation()); + assertEquals(1, embedded.getImports().size()); + var imported = embedded.getImports().getFirst(); + assertEquals("http://example.com/test/data-types", imported.getNamespace()); + var importedSchemaElements = imported.getSchema().getSchemaElements(); + assertEquals(2, importedSchemaElements.size()); + var importedSchemaElementNames = importedSchemaElements.stream().map(SchemaElement::getName).toList(); + assertEquals(List.of("B5", "B6"), importedSchemaElementNames); - assertEquals(1, definitions.getImportedSchemas().size()); - var importedSchema = definitions.getImportedSchemas().getFirst(); - assertEquals("http://example.com/test/data-types", importedSchema.getTargetNamespace()); - var importedSchemaElements = importedSchema.getSchemaElements(); - assertEquals(2, importedSchemaElements.size()); - var importedSchemaElementNames = importedSchemaElements.stream().map(SchemaElement::getName).toList(); - assertEquals(List.of("B5","B6"), importedSchemaElementNames); - } - } + } - @Test - void abstractWSDL() throws IOException { - try (var is = getClass().getResourceAsStream("/ws/abstract-service-no-binding.wsdl")) { - var definitions = new Definitions(new WSDLParserContext(null, null, null)); - definitions.parse(is, null); - assertEquals(1, definitions.getPortTypes().size()); - } - } + @Test + void abstractWSDL() throws Exception { + assertEquals(1, Definitions.parse(new ResolverMap(), "classpath:/ws/abstract-service-no-binding.wsdl").getPortTypes().size()); + } } \ No newline at end of file diff --git a/core/src/test/resources/validation/inline-anytype.wsdl b/core/src/test/resources/validation/inline-anytype.wsdl index 7e8dc4c917..ab66146966 100644 --- a/core/src/test/resources/validation/inline-anytype.wsdl +++ b/core/src/test/resources/validation/inline-anytype.wsdl @@ -3,6 +3,7 @@ xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.examples.com/wsdl/HelloService.wsdl" + xmlns:ipo="http://www.example.com/IPO" > @@ -24,11 +25,11 @@ - + - + diff --git a/core/src/test/resources/ws/import/cyclic.wsdl b/core/src/test/resources/ws/import/cyclic.wsdl new file mode 100644 index 0000000000..90f384b5c2 --- /dev/null +++ b/core/src/test/resources/ws/import/cyclic.wsdl @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/import/cyclic/a.xsd b/core/src/test/resources/ws/import/cyclic/a.xsd new file mode 100644 index 0000000000..af11188e7a --- /dev/null +++ b/core/src/test/resources/ws/import/cyclic/a.xsd @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/import/cyclic/b.xsd b/core/src/test/resources/ws/import/cyclic/b.xsd new file mode 100644 index 0000000000..507401d43a --- /dev/null +++ b/core/src/test/resources/ws/import/cyclic/b.xsd @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/import/message-references-import.wsdl b/core/src/test/resources/ws/import/message-references-import.wsdl new file mode 100644 index 0000000000..85f8b09853 --- /dev/null +++ b/core/src/test/resources/ws/import/message-references-import.wsdl @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/import/messages.xsd b/core/src/test/resources/ws/import/messages.xsd new file mode 100644 index 0000000000..467bfc4308 --- /dev/null +++ b/core/src/test/resources/ws/import/messages.xsd @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/cyclic.wsdl b/core/src/test/resources/ws/include/cyclic.wsdl new file mode 100644 index 0000000000..416a455a20 --- /dev/null +++ b/core/src/test/resources/ws/include/cyclic.wsdl @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/cyclic/a.xsd b/core/src/test/resources/ws/include/cyclic/a.xsd new file mode 100644 index 0000000000..facef884c8 --- /dev/null +++ b/core/src/test/resources/ws/include/cyclic/a.xsd @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/cyclic/b.xsd b/core/src/test/resources/ws/include/cyclic/b.xsd new file mode 100644 index 0000000000..17bcc0fb7b --- /dev/null +++ b/core/src/test/resources/ws/include/cyclic/b.xsd @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/include.wsdl b/core/src/test/resources/ws/include/include.wsdl index 972b08d7d3..9375ea8bcf 100644 --- a/core/src/test/resources/ws/include/include.wsdl +++ b/core/src/test/resources/ws/include/include.wsdl @@ -37,14 +37,6 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/multiple/a.xsd b/core/src/test/resources/ws/include/multiple/a.xsd new file mode 100644 index 0000000000..facef884c8 --- /dev/null +++ b/core/src/test/resources/ws/include/multiple/a.xsd @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/multiple/b.xsd b/core/src/test/resources/ws/include/multiple/b.xsd new file mode 100644 index 0000000000..601c940af2 --- /dev/null +++ b/core/src/test/resources/ws/include/multiple/b.xsd @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/multiple/c.xsd b/core/src/test/resources/ws/include/multiple/c.xsd new file mode 100644 index 0000000000..bb66053a6c --- /dev/null +++ b/core/src/test/resources/ws/include/multiple/c.xsd @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/xsd/inc/MessageStructureTypes2.xsd b/core/src/test/resources/ws/include/xsd/inc/MessageStructureTypes2.xsd index 4567ea00a4..9bb959d784 100644 --- a/core/src/test/resources/ws/include/xsd/inc/MessageStructureTypes2.xsd +++ b/core/src/test/resources/ws/include/xsd/inc/MessageStructureTypes2.xsd @@ -3,11 +3,7 @@ elementFormDefault="qualified"> - - - - diff --git a/core/src/test/resources/ws/include/xsd/inc/shared/xsd/inc/BaseTypes.xsd b/core/src/test/resources/ws/include/xsd/inc/shared/xsd/inc/BaseTypes.xsd index 74c4c2ae11..e8b26c8fbe 100644 --- a/core/src/test/resources/ws/include/xsd/inc/shared/xsd/inc/BaseTypes.xsd +++ b/core/src/test/resources/ws/include/xsd/inc/shared/xsd/inc/BaseTypes.xsd @@ -2,8 +2,6 @@ targetNamespace="http://example.com/test/data-types" elementFormDefault="qualified"> - - - + \ No newline at end of file diff --git a/core/src/test/resources/ws/include/xsd/messages.xsd b/core/src/test/resources/ws/include/xsd/messages2.xsd similarity index 68% rename from core/src/test/resources/ws/include/xsd/messages.xsd rename to core/src/test/resources/ws/include/xsd/messages2.xsd index 530eac6fb8..ad2f982f60 100644 --- a/core/src/test/resources/ws/include/xsd/messages.xsd +++ b/core/src/test/resources/ws/include/xsd/messages2.xsd @@ -2,9 +2,6 @@ targetNamespace="http://example.com/test" elementFormDefault="qualified"> - - - \ No newline at end of file From 1175eabc4692938ead362997e8c548dfa47db41f Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 12:20:09 +0100 Subject: [PATCH 04/28] Refactor WSDL parser code for immutability and readability - Replaced mutable fields with `final` to ensure immutability in `WSDLParserContext` and related classes. - Simplified test method `getDefinitions` by removing intermediate variable. - Removed redundant null check on the `type` attribute in `Binding`. - Enhanced code clarity in `PortType` by finalizing the `operations` list. --- .../com/predic8/membrane/core/util/wsdl/parser/Binding.java | 2 +- .../predic8/membrane/core/util/wsdl/parser/PortType.java | 2 +- .../membrane/core/util/wsdl/parser/WSDLParserContext.java | 6 +++--- .../schemavalidation/WSDLMessageElementExtractorTest.java | 3 +-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index d1969e92c7..5c0698a558 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -88,7 +88,7 @@ private PortType getPortType(Node node) { } var type = bindingElement.getAttribute("type"); - if (type == null || type.isEmpty()) { + if (type.isEmpty()) { return null; } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java index 437946d99d..96fbe07080 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java @@ -10,7 +10,7 @@ public class PortType extends WSDLElement { - private List operations; + private final List operations; public PortType(WSDLParserContext ctx, Node node) { super(ctx,node); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java index 4fde961ba3..415f3fa4f0 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java @@ -7,9 +7,9 @@ public class WSDLParserContext { - private Definitions definitions; - private Resolver resolver; - private String basePath; + private final Definitions definitions; + private final Resolver resolver; + private final String basePath; private List visitedLocations = new ArrayList<>(); public WSDLParserContext(Definitions wsdl, Resolver resolver, String basePath, List visitedLocations) { diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java index 9a5033dc41..4d6629c4c2 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java @@ -52,8 +52,7 @@ void rpcStyle() throws Exception { } private static @NotNull Definitions getDefinitions(String location) throws Exception { - var definitions = Definitions.parse(new ResolverMap(), location); - return definitions; + return Definitions.parse(new ResolverMap(), location); } } \ No newline at end of file From 1a35ae94bd698ac2cceeeb6528036df5d62e73e3 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 13:33:15 +0100 Subject: [PATCH 05/28] Refactor WSDL parser and URI utility for improved normalization, binding, and message handling - Added robust path normalization in `URIUtil`, covering relative paths, absolute paths, and various URI schemes. - Enhanced `WSDLParser` to track and validate immutable definitions, bindings, and messages. - Introduced support for handling empty schema locations and improved error handling in cyclic imports. - Updated tests to cover added functionality, ensuring reliable parsing and normalization across platforms. --- .../WSDLMessageElementExtractor.java | 18 ++-- .../predic8/membrane/core/util/URIUtil.java | 14 +++- .../core/util/wsdl/parser/Binding.java | 1 + .../core/util/wsdl/parser/Definitions.java | 6 +- .../core/util/wsdl/parser/Message.java | 1 + .../util/wsdl/parser/WSDLParserException.java | 4 + .../parser/schema/AbstractIncludeImport.java | 3 + .../core/util/wsdl/parser/schema/Import.java | 2 +- .../membrane/core/util/URIUtilTest.java | 82 +++++++++++++++++-- .../core/util/soap/WSDLParserTest.java | 9 ++ .../test/resources/ws/include/multiple.wsdl | 18 +++- 11 files changed, 136 insertions(+), 22 deletions(-) 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 4856cc239f..bf97a15741 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 @@ -23,29 +23,31 @@ public static Set getPossibleResponseElements(Definitions definitions, St } public static Set getPossibleElements(Definitions definitions, Direction direction, String serviceName) { - PortTypesByStyle portTypes; - if (definitions.getServices().isEmpty()) { - portTypes = new PortTypesByStyle(Collections.emptyList(), definitions.getPortTypes()); - } else { - portTypes = getPortTypesByStyle(definitions, serviceName); - } + var portTypes = getTypesByStyle(definitions, serviceName); var operationNamesRPC = portTypes.portTypesRPC().stream().map(PortType::getOperations) .flatMap(Collection::stream) + .filter(op -> !op.getMessagesByDirection(direction).isEmpty()) .map(op -> new QName(definitions.getTargetNamespace(), getElementNameRPC(op, direction))) .collect(toSet()); - Set namesDocumentStyle = getParts(direction, portTypes.portTypesDocument()) + var namesDocumentStyle = getParts(direction, portTypes.portTypesDocument()) .map(Part::getElementQName) .filter(Objects::nonNull) .collect(Collectors.toSet()); namesDocumentStyle.addAll(operationNamesRPC); - return namesDocumentStyle; } + private static @NotNull PortTypesByStyle getTypesByStyle(Definitions definitions, String serviceName) { + if (definitions.getServices().isEmpty()) { + return new PortTypesByStyle(Collections.emptyList(), definitions.getPortTypes()); + } + return getPortTypesByStyle(definitions, serviceName); + } + private static @NotNull PortTypesByStyle getPortTypesByStyle(Definitions definitions, String serviceName) { List portTypesRPC = new ArrayList<>(); List portTypesDocument = new ArrayList<>(); diff --git a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java index fced551357..dec0797c14 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java @@ -28,6 +28,7 @@ public class URIUtil { private static final Pattern driveLetterPattern = Pattern.compile("^(\\w)[/:|].*"); + private static final Pattern URI_SCHEME_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9+.-]*:.*"); /** * @@ -143,12 +144,23 @@ public static String normalize(String location) { if (location == null || location.isEmpty()) throw new IllegalArgumentException("location must not be null or empty"); + // Windows drive letter path (e.g., C:\foo or C:/foo) + if (location.length() >= 2 && Character.isLetter(location.charAt(0)) + && location.charAt(1) == ':' + && (location.length() == 2 || location.charAt(2) == '/' || location.charAt(2) == '\\')) { + return normalizeInternal(location); + } + // already absolute URI - if (location.matches("^[a-zA-Z][a-zA-Z0-9+.-]*:.*")) { + if (URI_SCHEME_PATTERN.matcher(location).matches()) { return URI.create(location).normalize().toString(); } // filesystem path + return normalizeInternal(location); + } + + private static @NotNull String normalizeInternal(String location) { return Path.of(location).toAbsolutePath().normalize().toString(); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index 5c0698a558..7dd02fb800 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -31,6 +31,7 @@ public static Style fromString(String style) { public Binding(WSDLParserContext ctx, Node node) { super(ctx,node); + ctx.getDefinitions().bindings.add(this); operations = getBindingOperations(node); portType = getPortType(node); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 95fdc6f210..36eed2a33e 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -19,9 +19,9 @@ public enum SOAPVersion { WSDLParserContext ctx; List schemas = new ArrayList<>(); - List messages; + List messages = new ArrayList<>(); List portTypes = new ArrayList<>(); - List bindings; + List bindings = new ArrayList<>(); List services = new ArrayList<>(); String targetNamespace; @@ -67,6 +67,8 @@ public void parse(InputStream is, Resolver resolver) { try { ctx = ctx.resolver(resolver); parse(WSDLParserUtil.parse(is)); + } catch (WSDLParserException e) { + throw e; } catch (Exception e) { throw new RuntimeException("Could not parse WSDL", e); } 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 eb4afbd214..ae55adf4d7 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 @@ -13,6 +13,7 @@ public class Message extends WSDLElement { public Message(WSDLParserContext ctx, Node node) { super(ctx,node); this.parts.add(getPart(node)); + ctx.getDefinitions().messages.add(this); } /** diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java index 6b4074579a..91015cd067 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java @@ -4,4 +4,8 @@ public class WSDLParserException extends RuntimeException { public WSDLParserException(String message) { super(message); } + + public WSDLParserException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index 8f2df28e5c..00ba6b148e 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -26,6 +26,9 @@ public Schema getSchema() { } protected Schema getSchema(WSDLParserContext ctx) { + if (schemaLocation == null || schemaLocation.isEmpty()) + return null; + try { var resolved = resolve(ctx); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index f5389adc1d..c051b2dd7d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -20,7 +20,7 @@ public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { namespace = getNamespace(node); if (!schema.getTargetNamespace().equals(namespace)) { - throw new WSDLParserException("The namespace {%s} of the import does not match the targetNamespace of the imported schema {}.".formatted(namespace, schema.getTargetNamespace())); + throw new WSDLParserException("The namespace {%s} of the import does not match the targetNamespace of the imported schema {%s}.".formatted(namespace, schema.getTargetNamespace())); } } diff --git a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java index 048f372a6f..3b38f27704 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java @@ -14,15 +14,18 @@ package com.predic8.membrane.core.util; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.condition.*; import java.io.*; import java.net.*; import java.net.URI; +import java.nio.file.*; import java.util.function.*; import static com.predic8.membrane.core.util.URIUtil.*; import static java.util.Optional.*; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.condition.OS.*; /** * Unfortunately the file: protocol is @@ -36,7 +39,7 @@ public class URIUtilTest { /** * Used to keep the tests and to try different URI or URIUtils implementations */ - static Function converter; + static Function converter; @BeforeAll static void setup() { @@ -86,17 +89,17 @@ void localhost() { } @Test - void winColon() { + void winColon() { assertEquals("C:\\foo", converter.apply("file://C:/foo")); } @Test - void winSlash() { + void winSlash() { assertEquals("C:\\foo", converter.apply("file://C/foo")); } @Test - void winSlashThree() { + void winSlashThree() { assertEquals("C:\\foo", pathFromFileURI("file:///C/foo")); } @@ -152,6 +155,7 @@ void backslashesWindows() { assertEquals("\\\\", URIUtil.slashToBackslash("//")); assertEquals("a\\b\\c", URIUtil.slashToBackslash("a/b/c")); } + @Test void driveLetterAndSlash() { assertEquals("b", removeDriveLetterAndSlash("a/b")); @@ -179,8 +183,8 @@ void normalizeSingleDot() { @Test void toFileURIStringTest() throws URISyntaxException { - assertEquals(wl("file:/C:/swig/jig","file:/swig/jig"), FileUtil.toFileURIString(new File("/swig/jig"))); - assertEquals(wl("file:/C:/jag%20sag/runt","file:/jag%20sag/runt"), FileUtil.toFileURIString(new File("/jag sag/runt"))); + assertEquals(wl("file:/C:/swig/jig", "file:/swig/jig"), FileUtil.toFileURIString(new File("/swig/jig"))); + assertEquals(wl("file:/C:/jag%20sag/runt", "file:/jag%20sag/runt"), FileUtil.toFileURIString(new File("/jag sag/runt"))); } String wl(String windows, String linux) { @@ -194,7 +198,7 @@ void toFileURIStringSpaceTest() throws URISyntaxException { assertEquals(wl( "file:/C:/chip%20clip", "file:/chip%20clip" - ), FileUtil.toFileURIString(new File("/chip clip"))); + ), FileUtil.toFileURIString(new File("/chip clip"))); } @Test @@ -206,4 +210,68 @@ void convertPath2FileURITest() throws URISyntaxException { assertEquals(new URI("file:///foo"), convertPath2FileURI("file:///foo")); assertEquals(new URI("file:/c:/foo/boo"), convertPath2FileURI("c:\\foo\\boo")); } + + @Nested + class normalize { + @Test + void throwsOnNull() { + assertThrows(IllegalArgumentException.class, () -> normalize(null)); + } + + @Test + void throwsOnEmpty() { + assertThrows(IllegalArgumentException.class, () -> normalize("")); + } + + @Test + void normalizesRelativeFilesystemPath() { + var input = "foo/../bar/test.txt"; + var expected = Path.of(input).toAbsolutePath().normalize().toString(); + assertEquals(expected, normalize(input)); + } + + @Test + void normalizesAbsoluteFilesystemPath() { + var input = Path.of("foo", "..", "bar").toAbsolutePath().toString(); + var expected = Path.of(input).toAbsolutePath().normalize().toString(); + assertEquals(expected, normalize(input)); + } + + @Test + void normalizesFileUri() { + assertEquals("file:/test.xml", normalize("file:///tmp/../test.xml")); + } + + @Test + void normalizesHttpUri() { + assertEquals("http://example.com/b/wsdl.xsd", normalize("http://example.com/a/../b/wsdl.xsd")); + } + + @Test + void classpathUri() { + // schema is the authority of the URI so .. does not apply. + assertEquals("classpath://authority/xsd/test.xsd", normalize("classpath://authority/schema/../xsd/test.xsd")); + } + + @Test + void keepsClasspathUri() { + // schema is the authority of the URI so .. does not apply. + assertEquals("classpath://authority/../xsd/test.xsd", normalize("classpath://authority/../xsd/test.xsd")); + } + + @Test + void normalizesUnixLikePath() { + var input = "/tmp/../var/data.xsd"; + var expected = Path.of(input).toAbsolutePath().normalize().toString(); + assertEquals(expected, normalize(input)); + } + + @Test + @EnabledOnOs(WINDOWS) + void handlesWindowsDriveLetterPath() { + var input = "C:\\temp\\..\\data\\test.xsd"; + var expected = Path.of(input).toAbsolutePath().normalize().toString(); + assertEquals(expected, normalize(input)); + } + } } \ No newline at end of file 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 dc54f54218..9de90e833a 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 @@ -49,6 +49,15 @@ void simpleSchema() throws Exception { assertEquals("getCity", getCityPart.getName()); assertEquals("getCity", getCityPart.getElementQName().getLocalPart()); assertEquals("https://predic8.de/cities", getCityPart.getElementQName().getNamespaceURI()); + + assertEquals(1,definitions.getBindings().size()); + var binding1 = definitions.getBindings().getFirst(); + assertEquals("CitySoapBinding", binding1.getName()); + assertEquals(DOCUMENT, binding1.getStyle()); + + assertEquals(2,definitions.getMessages().size()); + assertEquals("City", definitions.getMessages().getFirst().getName()); + assertEquals("CityResponse", definitions.getMessages().getLast().getName()); } @Test diff --git a/core/src/test/resources/ws/include/multiple.wsdl b/core/src/test/resources/ws/include/multiple.wsdl index 0577c7be0e..414e5347b5 100644 --- a/core/src/test/resources/ws/include/multiple.wsdl +++ b/core/src/test/resources/ws/include/multiple.wsdl @@ -1,21 +1,33 @@ - - + + + + + + + + + + + + From 6b14b8fa2ca4801c4714cee97da63a720d8e138a Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 14:09:45 +0100 Subject: [PATCH 06/28] Add embedded WSDL example and enhance parser robustness for URI normalization and schema imports - Included `embedded.wsdl` as a test resource for integration scenarios. - Improved `URIUtil` logic to handle query strings, fragments, and scheme-relative URIs during normalization. - Enhanced WSDL parser to handle embedded schemas and validate imports with improved error handling. - Updated tests to ensure accurate parsing and normalization in new and existing edge cases. --- .../predic8/membrane/core/util/URIUtil.java | 10 +- .../core/util/wsdl/parser/Definitions.java | 31 +---- .../parser/schema/AbstractIncludeImport.java | 3 - .../core/util/wsdl/parser/schema/Import.java | 18 ++- .../membrane/core/util/URIUtilTest.java | 23 ++++ .../test/resources/ws/import/embedded.wsdl | 117 ++++++++++++++++++ 6 files changed, 167 insertions(+), 35 deletions(-) create mode 100644 core/src/test/resources/ws/import/embedded.wsdl diff --git a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java index dec0797c14..760f79a254 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java @@ -53,8 +53,7 @@ public static String encodePathCharactersForUri(String s) { } public static String convertPath2FilePathString(String path) { - path = addFilePrefix(path); - return path.replaceAll(" ", "%20").replaceAll("\\\\", "/"); + return encodePathCharactersForUri(addFilePrefix(path)); } /** @@ -104,7 +103,7 @@ static String removeLeadingSlashes(String s) { } static Optional getPossibleDriveLetter(String p) { - Matcher m = driveLetterPattern.matcher(p); + var m = driveLetterPattern.matcher(p); if (m.matches()) { return Optional.of(m.group(1)); } @@ -156,6 +155,11 @@ public static String normalize(String location) { return URI.create(location).normalize().toString(); } + // ? (Query String) or # (Fragment) are hints of URLs, not filesystem paths + if (location.contains("?") || location.contains("#") || location.startsWith("//")) { + return URI.create(location).normalize().toString(); + } + // filesystem path return normalizeInternal(location); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 36eed2a33e..3056fd107d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -16,8 +16,6 @@ public enum SOAPVersion { SOAP_11, SOAP_12 } - WSDLParserContext ctx; - List schemas = new ArrayList<>(); List messages = new ArrayList<>(); List portTypes = new ArrayList<>(); @@ -31,11 +29,7 @@ public enum SOAPVersion { private Definitions() { } - public Definitions(WSDLParserContext ctx) { - this.ctx = ctx.definitions(this); - } - - public void parse(Element element) { + public void parse(WSDLParserContext ctx,Element element) { targetNamespace = element.getAttribute("targetNamespace"); for (var schemaElement : getSchemaElements(element)) { @@ -56,24 +50,12 @@ public void parse(Element element) { public static Definitions parse(Resolver resolver, String location) throws Exception { var defs = new Definitions(); - defs.ctx = new WSDLParserContext(defs, resolver, location, new ArrayList<>()); try(var is = resolver.resolve(location)) { - defs.parse(WSDLParserUtil.parse(is)); + defs.parse(new WSDLParserContext(defs, resolver, location, new ArrayList<>()),WSDLParserUtil.parse(is)); } return defs; } - public void parse(InputStream is, Resolver resolver) { - try { - ctx = ctx.resolver(resolver); - parse(WSDLParserUtil.parse(is)); - } catch (WSDLParserException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException("Could not parse WSDL", e); - } - } - public List getSchemas() { return schemas; } @@ -133,19 +115,16 @@ private List getSchemaElements(Element wsdl) { } } } - return schemas; } private static List getElements(Element wsdl, String name) { - var services = new ArrayList(); + var result = new ArrayList(); var list = wsdl.getElementsByTagNameNS(WSDL11_NS, name); for (int i = 0; i < list.getLength(); i++) { - services.add((Element) list.item(i)); + result.add((Element) list.item(i)); } - return services; + return result; } - - } \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index 00ba6b148e..8f2df28e5c 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -26,9 +26,6 @@ public Schema getSchema() { } protected Schema getSchema(WSDLParserContext ctx) { - if (schemaLocation == null || schemaLocation.isEmpty()) - return null; - try { var resolved = resolve(ctx); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index c051b2dd7d..3a105af21e 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -4,6 +4,8 @@ import org.jetbrains.annotations.*; import org.w3c.dom.*; +import java.util.*; + public class Import extends AbstractIncludeImport { private String namespace; @@ -15,15 +17,25 @@ public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { if (schema == null) return; - this.referensingSchema = referensingSchema; - namespace = getNamespace(node); - if (!schema.getTargetNamespace().equals(namespace)) { + if (!Objects.equals(schema.getTargetNamespace(), namespace)) { throw new WSDLParserException("The namespace {%s} of the import does not match the targetNamespace of the imported schema {%s}.".formatted(namespace, schema.getTargetNamespace())); } } + @Override + protected Schema getSchema(WSDLParserContext ctx) { + // Import of a Schema embedded in the WSDL + if (schemaLocation == null || schemaLocation.isEmpty()) { + return ctx.getDefinitions().getSchemas().stream() + .filter(s -> s.getTargetNamespace().equals(namespace)) + .findFirst().orElse(null); + } + + return super.getSchema(ctx); + } + @Override protected void registerLocation(String normalizedLocation) { referensingSchema.getImports().add(this); diff --git a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java index 3b38f27704..a69eb74568 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java @@ -273,5 +273,28 @@ void handlesWindowsDriveLetterPath() { var expected = Path.of(input).toAbsolutePath().normalize().toString(); assertEquals(expected, normalize(input)); } + + @Test + void keepsRelativeUriWithQuery() { + assertEquals("schema.xsd?version=1", normalize("schema.xsd?version=1") + ); + } + + @Test + void keepsRelativeUriWithFragment() { + assertEquals("../types.xsd#frag", normalize("../types.xsd#frag") + ); + } + + @Test + void keepsRelativeUriWithQueryAndFragment() { + assertEquals("../types.xsd?version=1#frag", normalize("../types.xsd?version=1#frag") + ); + } + + @Test + void keepsSchemeRelativeReference() { + assertEquals("/example.com/schema.xsd?version=1", normalize("/example.com/schema.xsd?version=1")); + } } } \ No newline at end of file diff --git a/core/src/test/resources/ws/import/embedded.wsdl b/core/src/test/resources/ws/import/embedded.wsdl new file mode 100644 index 0000000000..c672877666 --- /dev/null +++ b/core/src/test/resources/ws/import/embedded.wsdl @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 1e2706b26b73641b5c48d32ede00b097f598f6ca Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 14:54:10 +0100 Subject: [PATCH 07/28] Improve WSDL parser and URI utility - Streamlined XML schema declarations in `embedded.wsdl`. - Enhanced `URIUtil` to simplify Windows drive path normalization. - Refactored WSDL parser for better error handling, schema imports, and embedded schema handling. - Modernized code by replacing specific types with `var` where applicable. - Improved `URIUtilTest` to cover Windows drive-relative paths and other edge cases. --- .../WSDLMessageElementExtractor.java | 12 ++-- .../predic8/membrane/core/util/URIUtil.java | 6 +- .../core/util/wsdl/parser/Definitions.java | 62 +++++++++++++++---- .../core/util/wsdl/parser/schema/Import.java | 17 ++--- .../membrane/core/util/URIUtilTest.java | 10 ++- .../test/resources/ws/import/embedded.wsdl | 9 +-- 6 files changed, 78 insertions(+), 38 deletions(-) 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 bf97a15741..cadb77aeb6 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 @@ -49,8 +49,8 @@ public static Set getPossibleElements(Definitions definitions, Direction } private static @NotNull PortTypesByStyle getPortTypesByStyle(Definitions definitions, String serviceName) { - List portTypesRPC = new ArrayList<>(); - List portTypesDocument = new ArrayList<>(); + var portTypesRPC = new ArrayList(); + var portTypesDocument = new ArrayList(); for (var binding : getBindings(definitions, serviceName)) { if (binding.getStyle() == RPC) { @@ -68,13 +68,13 @@ public static Set getPossibleElements(Definitions definitions, Direction .map(Port::getBinding).toList(); } - private static List getServices(Definitions definitions, String serviceName) { + private static @NotNull List getServices(Definitions definitions, String serviceName) { if (serviceName != null) { var service = definitions.getService(serviceName); - if (service == null) { - throw new IllegalArgumentException("Unknown WSDL service: " + serviceName); + if (service.isEmpty()) { + throw new IllegalArgumentException("WSDL does not contain service: " + serviceName); } - return List.of(service); + return List.of(service.get()); } return definitions.getServices(); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java index 760f79a254..9c653ce833 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java @@ -144,9 +144,9 @@ public static String normalize(String location) { throw new IllegalArgumentException("location must not be null or empty"); // Windows drive letter path (e.g., C:\foo or C:/foo) - if (location.length() >= 2 && Character.isLetter(location.charAt(0)) - && location.charAt(1) == ':' - && (location.length() == 2 || location.charAt(2) == '/' || location.charAt(2) == '\\')) { + if (location.length() >= 2 + && Character.isLetter(location.charAt(0)) + && location.charAt(1) == ':') { return normalizeInternal(location); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 3056fd107d..1da1b322de 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -2,16 +2,23 @@ import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.wsdl.parser.schema.*; +import org.jetbrains.annotations.*; +import org.slf4j.*; import org.w3c.dom.*; -import java.io.*; import java.util.*; import static com.predic8.membrane.annot.Constants.*; import static org.w3c.dom.Node.*; +/** + * WSDL elements register themselves via WSDLParserContext. This is more convenient, e.g. binding is not + * directly created. + */ public class Definitions { + private static final Logger log = LoggerFactory.getLogger(Definitions.class); + public enum SOAPVersion { SOAP_11, SOAP_12 } @@ -29,29 +36,50 @@ public enum SOAPVersion { private Definitions() { } - public void parse(WSDLParserContext ctx,Element element) { + public void parse(WSDLParserContext ctx, Element element) { targetNamespace = element.getAttribute("targetNamespace"); for (var schemaElement : getSchemaElements(element)) { schemas.add(new Schema(ctx, schemaElement)); } - for (var e : getElements(element, "service")) { + importEmbeddedSchemas(); + + for (var e : getDirectChildElements(element, "service")) { + // Registers itself via WSDLParserContext new Service(ctx, e); } // Abstract WSDL without binding and service elements if (services.isEmpty()) { - for (var e : getElements(element, "portType")) { + for (var e : getDirectChildElements(element, "portType")) { + // Registers itself via WSDLParserContext new PortType(ctx, e); } } } + private void importEmbeddedSchemas() { + for (var schema : schemas) { + for (var i : schema.getImports()) { + if (i.getSchemaLocation() == null || i.getSchemaLocation().isEmpty()) { + var importedSchema = getEmbeddedSchema(i.getNamespace()); + log.debug("Importing embedded schema with namespace: {}", i.getNamespace()); + i.setSchema(importedSchema); + } + } + } + } + + private @Nullable Schema getEmbeddedSchema(String namespace) { + return schemas.stream().filter(s -> s.getTargetNamespace().equals(namespace)).findFirst().orElse(null); + } + public static Definitions parse(Resolver resolver, String location) throws Exception { + log.debug("Parsing WSDL from {}", location); var defs = new Definitions(); - try(var is = resolver.resolve(location)) { - defs.parse(new WSDLParserContext(defs, resolver, location, new ArrayList<>()),WSDLParserUtil.parse(is)); + try (var is = resolver.resolve(location)) { + defs.parse(new WSDLParserContext(defs, resolver, location, new ArrayList<>()), WSDLParserUtil.parse(is)); } return defs; } @@ -80,8 +108,8 @@ public List getServices() { return services; } - public Service getService(String name) { - return services.stream().filter(s -> s.getName().equals(name)).findFirst().orElse(null); + public Optional getService(String name) { + return services.stream().filter(s -> s.getName().equals(name)).findFirst(); } public String getTargetNamespace() { @@ -118,13 +146,21 @@ private List getSchemaElements(Element wsdl) { return schemas; } - private static List getElements(Element wsdl, String name) { + private static List getDirectChildElements(Element parent, String name) { var result = new ArrayList(); - var list = wsdl.getElementsByTagNameNS(WSDL11_NS, name); - - for (int i = 0; i < list.getLength(); i++) { - result.add((Element) list.item(i)); + var children = parent.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + var node = children.item(i); + if (node.getNodeType() == ELEMENT_NODE) { + var element = (Element) node; + if (WSDL11_NS.equals(element.getNamespaceURI()) && + name.equals(element.getLocalName())) { + result.add(element); + } + } } + return result; } } \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index 3a105af21e..5c3dc7937a 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -13,12 +13,12 @@ public class Import extends AbstractIncludeImport { public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { super(ctx, node, referensingSchema); - // Schema is null when it is already imported from somewhere else. + namespace = getNamespace(node); + + // Schema is null when it is already imported from somewhere else if (schema == null) return; - namespace = getNamespace(node); - if (!Objects.equals(schema.getTargetNamespace(), namespace)) { throw new WSDLParserException("The namespace {%s} of the import does not match the targetNamespace of the imported schema {%s}.".formatted(namespace, schema.getTargetNamespace())); } @@ -26,16 +26,17 @@ public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { @Override protected Schema getSchema(WSDLParserContext ctx) { - // Import of a Schema embedded in the WSDL if (schemaLocation == null || schemaLocation.isEmpty()) { - return ctx.getDefinitions().getSchemas().stream() - .filter(s -> s.getTargetNamespace().equals(namespace)) - .findFirst().orElse(null); + registerLocation(""); + return null; } - return super.getSchema(ctx); } + public void setSchema(Schema schema) { + this.schema = schema; + } + @Override protected void registerLocation(String normalizedLocation) { referensingSchema.getImports().add(this); diff --git a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java index a69eb74568..bf8d7dd014 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java @@ -274,6 +274,14 @@ void handlesWindowsDriveLetterPath() { assertEquals(expected, normalize(input)); } + @Test + @EnabledOnOs(WINDOWS) + void handlesWindowsDriveRelativePath() { + var input = "C:temp\\..\\data\\test.xsd"; + var expected = Path.of(input).toAbsolutePath().normalize().toString(); + assertEquals(expected, normalize(input)); + } + @Test void keepsRelativeUriWithQuery() { assertEquals("schema.xsd?version=1", normalize("schema.xsd?version=1") @@ -294,7 +302,7 @@ void keepsRelativeUriWithQueryAndFragment() { @Test void keepsSchemeRelativeReference() { - assertEquals("/example.com/schema.xsd?version=1", normalize("/example.com/schema.xsd?version=1")); + assertEquals("//example.com/schema.xsd?version=1", normalize("//example.com/schema.xsd?version=1")); } } } \ No newline at end of file diff --git a/core/src/test/resources/ws/import/embedded.wsdl b/core/src/test/resources/ws/import/embedded.wsdl index c672877666..f0af8bc1e7 100644 --- a/core/src/test/resources/ws/import/embedded.wsdl +++ b/core/src/test/resources/ws/import/embedded.wsdl @@ -38,9 +38,7 @@ - + @@ -53,11 +51,8 @@ - - + From 4a55bbc80f28458bf0f8ab63c1a162fa719d3e29 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 15:59:01 +0100 Subject: [PATCH 08/28] Add copyright headers, logging improvements, and update SOAP namespace handling - Added Apache 2.0 license headers to all modified files. - Enhanced SOAP analysis with additional debug logging in `SOAPUtil`. - Updated XPath expressions in YAML configuration to use explicit SOAP namespace references (`s11`). - Refactored several test files for consistency and improved readability. --- .../AbstractXMLSchemaValidator.java | 18 +- .../WSDLMessageElementExtractor.java | 14 ++ .../schemavalidation/WSDLValidator.java | 21 +-- .../predic8/membrane/core/util/SOAPUtil.java | 8 +- .../core/util/wsdl/parser/Address.java | 14 ++ .../core/util/wsdl/parser/Binding.java | 14 ++ .../util/wsdl/parser/BindingOperation.java | 14 ++ .../core/util/wsdl/parser/Definitions.java | 17 +- .../core/util/wsdl/parser/Message.java | 14 ++ .../core/util/wsdl/parser/Operation.java | 14 ++ .../membrane/core/util/wsdl/parser/Part.java | 14 ++ .../membrane/core/util/wsdl/parser/Port.java | 14 ++ .../core/util/wsdl/parser/PortType.java | 14 ++ .../core/util/wsdl/parser/Service.java | 14 ++ .../membrane/core/util/wsdl/parser/Type.java | 14 ++ .../core/util/wsdl/parser/WSDLElement.java | 14 ++ .../util/wsdl/parser/WSDLParserContext.java | 14 ++ .../util/wsdl/parser/WSDLParserException.java | 14 ++ .../core/util/wsdl/parser/WSDLParserUtil.java | 14 ++ .../parser/schema/AbstractIncludeImport.java | 14 ++ .../core/util/wsdl/parser/schema/Import.java | 14 ++ .../core/util/wsdl/parser/schema/Include.java | 14 ++ .../core/util/wsdl/parser/schema/Schema.java | 14 ++ .../wsdl/parser/schema/SchemaElement.java | 14 ++ .../schemavalidation/SOAPUtilTest.java | 169 ++++++++++-------- .../WSDLMessageElementExtractorTest.java | 14 ++ .../membrane/core/util/URIUtilTest.java | 4 +- .../core/util/soap/WSDLParserTest.java | 14 ++ .../core/util/wsdl/parser/OperationTest.java | 14 ++ .../interceptor/JwksRefreshTest.java | 14 ++ .../custom-error-messages/apis.yaml | 14 +- 31 files changed, 487 insertions(+), 100 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 c7e466e29e..7c4ebd77c8 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 @@ -74,15 +74,15 @@ public void init() { } public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Exception { - Message msg = exc.getMessage(flow); - List exceptions = new ArrayList<>(); - String preliminaryError = getPreliminaryError(xopr, msg); + var msg = exc.getMessage(flow); + var exceptions = new ArrayList(); + var preliminaryError = getPreliminaryError(xopr, msg); if (preliminaryError == null) { List vals = validators.take(); try { // the message must be valid for one schema embedded into WSDL - for (Validator validator : vals) { - SchemaValidatorErrorHandler handler = (SchemaValidatorErrorHandler) validator.getErrorHandler(); + for (var validator : vals) { + var handler = (SchemaValidatorErrorHandler) validator.getErrorHandler(); try { validator.validate(getMessageBody(xopr.reconstituteIfNecessary(msg))); if (handler.noErrors()) { @@ -102,7 +102,7 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep } else { exceptions.add(new Exception(preliminaryError)); } - String errorMsg = getErrorMsg(exceptions); // Errors als simple String + var errorMsg = getErrorMsg(exceptions); // Errors als simple String if (failureHandler != null) { failureHandler.handleFailure(errorMsg, exc); } @@ -128,7 +128,7 @@ protected List createValidators() { private @NotNull Validator createValidator(Element schema, SchemaFactory sf) { try { - DOMSource source = new DOMSource(schema); + var source = new DOMSource(schema); source.setSystemId(location); var validator = sf.newSchema(source).newValidator(); validator.setErrorHandler(new SchemaValidatorErrorHandler()); @@ -139,9 +139,9 @@ protected List createValidators() { } private String getErrorMsg(List excs) { - StringBuilder buf = new StringBuilder(); + var buf = new StringBuilder(); buf.append("%s: ".formatted(getErrorTitle())); - for (Exception e : excs) { + for (var e : excs) { buf.append(e); buf.append("; "); } 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 cadb77aeb6..6b09a26abe 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 @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.interceptor.schemavalidation; import com.predic8.membrane.core.util.wsdl.parser.*; 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 5436d42539..a9fb4306e1 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 @@ -35,7 +35,10 @@ import java.util.*; import static com.predic8.membrane.annot.Constants.SoapVersion.*; +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.schemavalidation.WSDLMessageElementExtractor.getPossibleRequestElements; import static com.predic8.membrane.core.util.SOAPUtil.FaultCode.*; import static com.predic8.membrane.core.util.SOAPUtil.*; import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*; @@ -80,9 +83,8 @@ public WSDLValidator(ResolverMap resourceResolver, String location, String servi """.formatted(location, e.getMessage())); } - requestElements = WSDLMessageElementExtractor.getPossibleRequestElements(definitions, serviceName); - responseElements = WSDLMessageElementExtractor.getPossibleResponseElements(definitions, serviceName); - + requestElements = getPossibleRequestElements(definitions, serviceName); + responseElements = getPossibleResponseElements(definitions, serviceName); versions = definitions.getSoapVersions(); } @@ -93,11 +95,13 @@ public String getName() { @Override public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Exception { - var msg = exc.getMessage(flow); - var result = analyseSOAPMessage(xopr, msg); + var message = exc.getMessage(flow); + var result = analyseSOAPMessage(xopr, message); if (!result.isSOAP()) { + log.error("Message: ", message); setErrorResponse(exc, "Not a valid SOAP message."); + exc.getResponse().getHeader().add(VALIDATION_ERROR_SOURCE, flow.name()); return ABORT; } @@ -117,17 +121,14 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep } } -// if (checkIfSOAPElementIsUsedAsAWSDLMessage) { - if (msg instanceof Request && !isPossibleRequestElement(result.soapElement())) { + if (message instanceof Request && !isPossibleRequestElement(result.soapElement())) { setErrorResponse(exc, "%s is not a valid request element. Possible elements are %s".formatted(result.soapElement(), requestElements)); return ABORT; } - if (msg instanceof Response && !isPossibleResponseElement(result.soapElement())) { + if (message instanceof Response && !isPossibleResponseElement(result.soapElement())) { setErrorResponse(exc, "%s is not a valid response element. Possible elements are %s".formatted(result.soapElement(), responseElements)); return ABORT; } -// } - return super.validateMessage(exc, flow); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java b/core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java index f9e0a0180a..e11d2fe478 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java @@ -102,6 +102,8 @@ public record SOAPAnalysisResult(boolean isSOAP, boolean isFault, SoapVersion ve } public static SOAPAnalysisResult analyseSOAPMessage( XOPReconstitutor xopr, Message msg) { + log.debug("Analyzing SOAP message: {}", msg); + /* * 0: waiting for "" * 1: waiting for "" (skipping any "") @@ -116,11 +118,12 @@ public static SOAPAnalysisResult analyseSOAPMessage( XOPReconstitutor xopr, Mess SoapVersion version = null; int state = 0; while (parser.hasNext()) { - XMLEvent event = parser.nextEvent(); + var event = parser.nextEvent(); if (event.isStartElement()) { - QName name = ((StartElement) event).getName(); + var name = ((StartElement) event).getName(); if (state < 2 && !isSOAP11Element(name) && !isSOAP12Element(name)) { + log.debug("Not a SOAP Element: {}", name); return NO_SOAP_RESULT; } @@ -167,6 +170,7 @@ public static SOAPAnalysisResult analyseSOAPMessage( XOPReconstitutor xopr, Mess } catch (Exception e) { log.warn("Ignoring exception: ", e); } + log.debug("No SOAP Element found."); return NO_SOAP_RESULT; } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java index 6ef6b75367..69d7b3c60d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index 7dd02fb800..3f2387dd23 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import com.predic8.membrane.annot.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java index 93d392f889..ef28262e59 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 1da1b322de..c6144fca9d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import com.predic8.membrane.core.resolver.*; @@ -72,7 +86,8 @@ private void importEmbeddedSchemas() { } private @Nullable Schema getEmbeddedSchema(String namespace) { - return schemas.stream().filter(s -> s.getTargetNamespace().equals(namespace)).findFirst().orElse(null); + return schemas.stream().filter(s -> Objects.equals(s.getTargetNamespace(), namespace)) + .findFirst().orElse(null); } public static Definitions parse(Resolver resolver, String location) throws Exception { 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 ae55adf4d7..875f29038c 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 @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index dc21a78163..976d480b74 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import com.predic8.wsdl.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java index 170c6abd5b..bf7fee1387 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java index 99c25331e0..18e852e485 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java index 96fbe07080..eef27244d4 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import com.predic8.membrane.annot.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java index 661a5c08ae..363e5ce275 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java index a8e3ef4c14..f1b1248948 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 26d658ce98..93e11d6a90 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java index 415f3fa4f0..d0eeb6d069 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import com.predic8.membrane.core.resolver.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java index 91015cd067..f55d2b8ce2 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; public class WSDLParserException extends RuntimeException { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java index 8d000f9b75..4bf947f9ef 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index 8f2df28e5c..e459b674f1 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.resolver.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index 5c3dc7937a..9576b056e3 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.util.wsdl.parser.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java index 84c2b5eec8..5fd2b97e1a 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.util.wsdl.parser.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java index 39b3002234..ea296ed50c 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.util.wsdl.parser.*; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java index f1f8c0837f..2ca2d040cb 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.util.wsdl.parser.*; diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPUtilTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPUtilTest.java index 5362b48e4c..5fad03c866 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPUtilTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPUtilTest.java @@ -31,77 +31,100 @@ public class SOAPUtilTest { - private final static String TB_NS = "http://thomas-bayer.com/blz/"; - private final static String MEMBRANE_NS = "http://membrane-api.io/"; - - @Test - void faultCheckSpecExample() throws Exception { - assertTrue(SOAPUtil.analyseSOAPMessage( new XOPReconstitutor(), getMessage("src/test/resources/wsdlValidator/soapFaultFromSpec.xml")).isFault()); - } - - @Test - void faultCustom() throws Exception { - assertTrue(SOAPUtil.analyseSOAPMessage( new XOPReconstitutor(), getMessage("src/test/resources/wsdlValidator/soapFaultCustom.xml")).isFault()); - } - - @Test - void analyseXML() { - SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage( new XOPReconstitutor(), getMessageFromString("")); - assertFalse(result.isSOAP()); - assertFalse(result.isFault()); - } - - @Test - void analyseSOAP11() { - SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage( new XOPReconstitutor(), getMessageFromString(""" - - - - 66762332 - - - - """)); - assertTrue(result.isSOAP()); - assertFalse(result.isFault()); - assertEquals(SOAP11, result.version()); - assertEquals(new QName(TB_NS,"getBank"), result.soapElement()); - } - - @Test - void analyseSOAP12() { - SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage( new XOPReconstitutor(), getMessageFromString(""" - - - - - - """)); - assertTrue(result.isSOAP()); - assertFalse(result.isFault()); - assertEquals(SOAP12, result.version()); - assertEquals(new QName(MEMBRANE_NS,"Bar"), result.soapElement()); - } - - @Test - void analyseFault11() { - SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage( new XOPReconstitutor(), getMessageFromString(""" - - - - - - """)); - assertTrue(result.isSOAP()); - assertTrue(result.isFault()); - assertEquals(SOAP11, result.version()); - } - - private Message getMessageFromString(String body) { - return ok().contentType(TEXT_XML).body(body).build(); - } - - private Message getMessage(String path) throws Exception { - return ok().contentType(TEXT_XML).body(new FileInputStream(path), true).build(); - } + private final static String TB_NS = "http://thomas-bayer.com/blz/"; + private final static String MEMBRANE_NS = "http://membrane-api.io/"; + + @Test + void faultCheckSpecExample() throws Exception { + assertTrue(SOAPUtil.analyseSOAPMessage(new XOPReconstitutor(), getMessage("src/test/resources/wsdlValidator/soapFaultFromSpec.xml")).isFault()); + } + + @Test + void faultCustom() throws Exception { + assertTrue(SOAPUtil.analyseSOAPMessage(new XOPReconstitutor(), getMessage("src/test/resources/wsdlValidator/soapFaultCustom.xml")).isFault()); + } + + @Test + void analyseXML() { + SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage(new XOPReconstitutor(), getMessageFromString("")); + assertFalse(result.isSOAP()); + assertFalse(result.isFault()); + } + + @Test + void analyseSOAP11() { + SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage(new XOPReconstitutor(), getMessageFromString(""" + + + + 66762332 + + + + """)); + assertTrue(result.isSOAP()); + assertFalse(result.isFault()); + assertEquals(SOAP11, result.version()); + assertEquals(new QName(TB_NS, "getBank"), result.soapElement()); + } + + @Test + void analyseSOAP12() { + SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage(new XOPReconstitutor(), getMessageFromString(""" + + + + + + """)); + assertTrue(result.isSOAP()); + assertFalse(result.isFault()); + assertEquals(SOAP12, result.version()); + assertEquals(new QName(MEMBRANE_NS, "Bar"), result.soapElement()); + } + + @Test + void analyseFault11() { + SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage(new XOPReconstitutor(), getMessageFromString(""" + + + + + + """)); + assertTrue(result.isSOAP()); + assertTrue(result.isFault()); + assertEquals(SOAP11, result.version()); + } + + /** + * Fault is not namespace prefixed. => Ok + */ + @Test + void analyseFault11DifferentNamespace() { + SOAPUtil.SOAPAnalysisResult result = analyseSOAPMessage(new XOPReconstitutor(), getMessageFromString(""" + + + + Client + WSDL message validation failed + + Not a valid SOAP message. + + + + + """)); + assertTrue(result.isSOAP()); + assertTrue(result.isFault()); + assertEquals(SOAP11, result.version()); + } + + private Message getMessageFromString(String body) { + return ok().contentType(TEXT_XML).body(body).build(); + } + + private Message getMessage(String path) throws Exception { + return ok().contentType(TEXT_XML).body(new FileInputStream(path), true).build(); + } } diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java index 4d6629c4c2..128f3c947e 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLMessageElementExtractorTest.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.interceptor.schemavalidation; import com.predic8.membrane.core.resolver.*; diff --git a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java index bf8d7dd014..23488144be 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java @@ -249,13 +249,13 @@ void normalizesHttpUri() { @Test void classpathUri() { - // schema is the authority of the URI so .. does not apply. + // The '..' within the path portion normalizes correctly. assertEquals("classpath://authority/xsd/test.xsd", normalize("classpath://authority/schema/../xsd/test.xsd")); } @Test void keepsClasspathUri() { - // schema is the authority of the URI so .. does not apply. + // The '..' at the start of the path (after authority) cannot traverse above the authority, so it's preserved. assertEquals("classpath://authority/../xsd/test.xsd", normalize("classpath://authority/../xsd/test.xsd")); } 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 9de90e833a..f570b4a6e4 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 @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.soap; import com.predic8.membrane.core.resolver.*; diff --git a/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/OperationTest.java b/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/OperationTest.java index 9c3fd6f9cd..36d90d1c08 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/OperationTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/OperationTest.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.core.util.wsdl.parser; import org.junit.jupiter.api.*; diff --git a/core/src/test/java/com/predic8/membrane/integration/withoutinternet/interceptor/JwksRefreshTest.java b/core/src/test/java/com/predic8/membrane/integration/withoutinternet/interceptor/JwksRefreshTest.java index 36a5acdb62..a009fe6d1f 100644 --- a/core/src/test/java/com/predic8/membrane/integration/withoutinternet/interceptor/JwksRefreshTest.java +++ b/core/src/test/java/com/predic8/membrane/integration/withoutinternet/interceptor/JwksRefreshTest.java @@ -1,3 +1,17 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + package com.predic8.membrane.integration.withoutinternet.interceptor; import com.predic8.membrane.core.interceptor.flow.ReturnInterceptor; diff --git a/distribution/examples/extending-membrane/error-handling/custom-error-messages/apis.yaml b/distribution/examples/extending-membrane/error-handling/custom-error-messages/apis.yaml index aaf1d5b213..bd6651771d 100644 --- a/distribution/examples/extending-membrane/error-handling/custom-error-messages/apis.yaml +++ b/distribution/examples/extending-membrane/error-handling/custom-error-messages/apis.yaml @@ -1,4 +1,12 @@ # yaml-language-server: $schema=https://www.membrane-api.io/v7.1.2.json +components: + ns: + xmlConfig: + namespaces: + - prefix: s11 + uri: "http://schemas.xmlsoap.org/soap/envelope/" + +--- api: port: 2000 path: @@ -24,13 +32,13 @@ api: test: isXML() flow: - if: + test: //s11:Fault language: xpath - test: //*[local-name() = 'Fault' and namespace-uri() = 'http://schemas.xmlsoap.org/soap/envelope/'] flow: - template: contentType: application/xml src: | - + e ${statusCode} SOAP Fault! @@ -44,8 +52,8 @@ api: test: statusCode >= 400 flow: - if: + test: /s11:Envelope language: xpath - test: /*[not(local-name() = 'Envelope')] flow: - template: contentType: application/xml From 466cc0520aa28963b0701bae8f7b79cd41ae767a Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 16:03:01 +0100 Subject: [PATCH 09/28] Add copyright headers, logging improvements, and update SOAP namespace handling - Added Apache 2.0 license headers to all modified files. - Enhanced SOAP analysis with additional debug logging in `SOAPUtil`. - Updated XPath expressions in YAML configuration to use explicit SOAP namespace references (`s11`). - Refactored several test files for consistency and improved readability. --- .../core/interceptor/schemavalidation/WSDLValidator.java | 3 +-- .../membrane/core/util/wsdl/parser/WSDLParserContext.java | 2 +- .../predic8/membrane/core/util/wsdl/parser/schema/Import.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) 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 a9fb4306e1..363390eef7 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 @@ -64,7 +64,7 @@ public class WSDLValidator extends AbstractXMLSchemaValidator { /** * Parsed WSDL document */ - private com.predic8.membrane.core.util.wsdl.parser.Definitions definitions; + private final com.predic8.membrane.core.util.wsdl.parser.Definitions definitions; public WSDLValidator(ResolverMap resourceResolver, String location, String serviceName, ValidatorInterceptor.FailureHandler failureHandler, boolean skipFaults) { super(resourceResolver, location, failureHandler); @@ -99,7 +99,6 @@ public Outcome validateMessage(Exchange exc, Interceptor.Flow flow) throws Excep var result = analyseSOAPMessage(xopr, message); if (!result.isSOAP()) { - log.error("Message: ", message); setErrorResponse(exc, "Not a valid SOAP message."); exc.getResponse().getHeader().add(VALIDATION_ERROR_SOURCE, flow.name()); return ABORT; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java index d0eeb6d069..d505c8757b 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java @@ -24,7 +24,7 @@ public class WSDLParserContext { private final Definitions definitions; private final Resolver resolver; private final String basePath; - private List visitedLocations = new ArrayList<>(); + private List visitedLocations; public WSDLParserContext(Definitions wsdl, Resolver resolver, String basePath, List visitedLocations) { this.definitions = wsdl; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index 9576b056e3..496920db09 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -22,7 +22,7 @@ public class Import extends AbstractIncludeImport { - private String namespace; + private final String namespace; public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { super(ctx, node, referensingSchema); From e4a9e143f4bb382cae8417b4b4e83bce9a99ce90 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 16:16:53 +0100 Subject: [PATCH 10/28] Remove unused fields and redundant logic in `WSDLInterceptor` - Deleted unused `registryWSDLRegisterURL` field and associated methods. - Removed redundant `HttpClient` logic. - Simplified `rewrite` method. - Streamlined imports and improved code readability. --- .../core/interceptor/WSDLInterceptor.java | 80 +------------------ 1 file changed, 2 insertions(+), 78 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/WSDLInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/WSDLInterceptor.java index f53e9232e1..8207b7cb9e 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/WSDLInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/WSDLInterceptor.java @@ -16,8 +16,6 @@ import com.predic8.membrane.annot.*; import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.transport.http.*; import com.predic8.membrane.core.util.*; import com.predic8.membrane.core.ws.relocator.*; import org.jetbrains.annotations.*; @@ -28,11 +26,8 @@ import java.net.*; import static com.predic8.membrane.annot.Constants.*; -import static com.predic8.membrane.core.http.Header.*; -import static com.predic8.membrane.core.http.Request.*; import static com.predic8.membrane.core.interceptor.Interceptor.Flow.Set.*; import static com.predic8.membrane.core.util.soap.WSDLUtil.*; -import static java.nio.charset.StandardCharsets.*; /** * @description

The wsdlRewriter rewrites endpoint addresses of services and XML Schema locations in WSDL documents.

@@ -47,9 +42,7 @@ public class WSDLInterceptor extends RelocatingInterceptor { public static final QName XSD_INCLUDE_QNAME = new QName(XSD_NS, "include"); public static final String LOCATION = "location"; - private String registryWSDLRegisterURL; private boolean rewriteEndpoint = true; - private HttpClient hc; /** * Path of the service location to rewrite @@ -64,7 +57,6 @@ public WSDLInterceptor() { @Override public void init() { super.init(); - hc = router.getHttpClientFactory().createClient(null); if (path != null) setPathRewriterOnWSDLInterceptor(path); @@ -94,12 +86,9 @@ public void setPathRewriterOnWSDLInterceptor(String keypath) { protected void rewrite(Exchange exc) throws Exception { log.debug("Changing endpoint address in WSDL"); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Relocator relocator = getRelocator(exc, stream); + var stream = new ByteArrayOutputStream(); + var relocator = getRelocator(exc, stream); relocator.relocate(exc.getResponse().getBodyAsStream()); - if (relocator.isWsdlFound()) { - registerWSDL(exc); - } exc.getResponse().setBodyContent(stream.toByteArray()); // Preserve existing Content-Type if present; otherwise set a sane default including charset. @@ -128,71 +117,6 @@ protected void rewrite(Exchange exc) throws Exception { getLocationPort(exc), exc.getHandler().getContextPath(exc), pathRewriter); } - private void registerWSDL(Exchange exc) { - if (registryWSDLRegisterURL == null) - return; - - StringBuilder buf = new StringBuilder(2000); - buf.append(registryWSDLRegisterURL); - buf.append("?wsdl="); - - buf.append(URLDecoder.decode(getWSDLURL(exc), US_ASCII)); - - callRegistry(buf.toString()); - - log.debug(buf.toString()); - } - - private void callRegistry(String uri) { - try { - var exchange = createExchange(uri); - hc.call(exchange); - Response res = exchange.getResponse(); - if (res.getStatusCode() != 200) - log.warn("{}", res); - } catch (Exception e) { - log.error("", e); - } - } - - private Exchange createExchange(String uri) throws MalformedURLException, URISyntaxException { - return get(getCompletePath(new URL(uri))).header(HOST, getHost(uri)).buildExchange(); - - } - - private static String getHost(String uri) throws MalformedURLException { - return new URL(uri).getHost(); - } - - private String getCompletePath(URL url) { - if (url.getQuery() == null) - return url.getPath(); - return url.getPath() + "?" + url.getQuery(); - } - - private String getWSDLURL(Exchange exc) { - StringBuilder buf = new StringBuilder(); - buf.append(getLocationProtocol()); - buf.append("://"); - buf.append(getLocationHost(exc)); - if (getLocationPort(exc) != 80) { - buf.append(":"); - buf.append(getLocationPort(exc)); - } - buf.append("/"); - buf.append(exc.getRequest().getUri()); - return buf.toString(); - } - - @MCAttribute - public void setRegistryWSDLRegisterURL(String registryWSDLRegisterURL) { - this.registryWSDLRegisterURL = registryWSDLRegisterURL; - } - - public String getRegistryWSDLRegisterURL() { - return registryWSDLRegisterURL; - } - @Override public String getShortDescription() { return "Rewrites SOAP endpoint addresses and XML Schema locations in WSDL and XSD documents."; From d2b38e7e4699fa736aa3ddbb654274bdab7be616 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Mar 2026 22:05:32 +0100 Subject: [PATCH 11/28] Remove redundant WSDL utility methods, tests, and dependencies - Removed unused methods and fields in `WSDLUtil`, `WSDLInterceptor`, and related classes for improved maintainability. - Deleted `WSDLUtilTest` and redundant SOAP operations tests. - Replaced specific types with `var` for modernized code. - Removed dependency on `soa-model-core` and updated `pom.xml` with refined dependencies for Groovy modules. - Simplified WSDL example handling by removing unused logic and streamlining `embedded.wsdl`. --- core/pom.xml | 27 +-- .../interceptor/RelocatingInterceptor.java | 2 +- .../core/interceptor/WSDLInterceptor.java | 2 +- .../schemavalidation/WSDLValidator.java | 1 - .../server/WSDLPublisherInterceptor.java | 6 +- .../soap/WebServiceExplorerInterceptor.java | 177 ++++-------------- .../membrane/core/proxies/SOAPProxy.java | 94 +++------- .../membrane/core/resolver/ResolverMap.java | 38 ---- .../ssl/TLSUnrecognizedNameException.java | 2 +- .../predic8/membrane/core/util/WSDLUtil.java | 157 ++++++++-------- .../membrane/core/util/soap/WSDLUtil.java | 45 +---- .../core/util/wsdl/parser/Binding.java | 6 +- .../core/util/wsdl/parser/Definitions.java | 24 ++- .../core/util/wsdl/parser/Message.java | 2 +- .../core/util/wsdl/parser/Operation.java | 9 +- .../membrane/core/util/wsdl/parser/Port.java | 6 +- .../core/util/wsdl/parser/PortType.java | 2 +- .../core/util/wsdl/parser/Service.java | 2 +- .../membrane/core/ws/relocator/Relocator.java | 10 +- .../membrane/core/proxies/SOAPProxyTest.java | 2 +- .../membrane/core/resolver/ResolverTest.java | 21 --- .../membrane/core/util/WSDLUtilTest.java | 89 --------- .../core/util/soap/WSDLParserTest.java | 8 +- .../withinternet/LargeBodyTest.java | 4 +- .../test/resources/ws/import/embedded.wsdl | 3 +- .../custom-error-messages/apis.yaml | 15 +- .../CustomErrorHandlingExampleTest.java | 10 +- pom.xml | 14 +- 28 files changed, 210 insertions(+), 568 deletions(-) delete mode 100644 core/src/test/java/com/predic8/membrane/core/util/WSDLUtilTest.java diff --git a/core/pom.xml b/core/pom.xml index 9c2884792e..3cecc647ce 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ---> +--> + 4.0.0 service-proxy-core @@ -89,20 +91,6 @@ com.googlecode.jatl jatl - - com.predic8 - soa-model-core - - - org.apache.httpcomponents - httpclient - - - org.slf4j - jcl-over-slf4j - - - commons-fileupload commons-fileupload @@ -230,6 +218,10 @@ org.apache.groovy groovy-templates + + org.apache.groovy + groovy-json + @@ -349,7 +341,7 @@ 5.19.1 test - + io.rest-assured rest-assured test @@ -486,7 +478,8 @@ - + diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/RelocatingInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/RelocatingInterceptor.java index 80ff036c76..ebdcd19357 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/RelocatingInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/RelocatingInterceptor.java @@ -82,7 +82,7 @@ protected String getLocationHost(Exchange exc) { if (host != null) return host; - String locHost = exc.getOriginalHostHeaderHost(); + var locHost = exc.getOriginalHostHeaderHost(); log.debug("host {}",locHost); diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/WSDLInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/WSDLInterceptor.java index 8207b7cb9e..01465e3f38 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/WSDLInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/WSDLInterceptor.java @@ -98,7 +98,7 @@ protected void rewrite(Exchange exc) throws Exception { } private @NotNull Relocator getRelocator(Exchange exc, OutputStream stream) throws Exception { - Relocator relocator = createRelocator(exc, stream); + var relocator = createRelocator(exc, stream); if (rewriteEndpoint) { relocator.getRelocatingAttributes().put(WSDL11_ADDRESS_SOAP11, LOCATION); 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 363390eef7..2bf1a7d9b4 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 @@ -22,7 +22,6 @@ import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.*; import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; -import com.predic8.wsdl.*; import org.jetbrains.annotations.*; import org.slf4j.*; import org.w3c.dom.*; diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java index e238869054..08c4054310 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java @@ -139,7 +139,7 @@ private void processDocuments(Exchange exc) { break; log.debug("processing: {}", doc); exc.setResponse(webServerInterceptor.createResponse(router.getResolverMap(), doc)); - WSDLInterceptor wi = new WSDLInterceptor(); + var wi = new WSDLInterceptor(); wi.setRewriteEndpoint(false); wi.setPathRewriter(new RelativePathRewriter(exc, doc)); wi.handleResponse(exc); @@ -203,7 +203,7 @@ private Outcome handleRequestInternal(final Exchange exc) throws Exception { exc.getResponse().getHeader().setContentType(TEXT_XML); } if (exc.getRequest().getUri().contains("?xsd=")) { - Map params = URLParamUtil.getParams(router.getConfiguration().getUriFactory(), exc, URLParamUtil.DuplicateKeyOrInvalidFormStrategy.ERROR); + var params = URLParamUtil.getParams(router.getConfiguration().getUriFactory(), exc, URLParamUtil.DuplicateKeyOrInvalidFormStrategy.ERROR); if (params.containsKey("xsd")) { int n = Integer.parseInt(params.get("xsd")); String path; @@ -220,7 +220,7 @@ private Outcome handleRequestInternal(final Exchange exc) throws Exception { } } if (resource != null) { - WSDLInterceptor wi = new WSDLInterceptor(); + var wi = new WSDLInterceptor(); wi.setRewriteEndpoint(false); wi.setPathRewriter(new RelativePathRewriter(exc, combine(router.getConfiguration().getBaseLocation(), wsdl))); wi.init(router); diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java index 80a1422a02..35f9e898af 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java @@ -15,19 +15,16 @@ import com.googlecode.jatl.*; import com.predic8.membrane.annot.*; -import com.predic8.membrane.core.config.ProxyAware; +import com.predic8.membrane.core.config.*; import com.predic8.membrane.core.exchange.*; import com.predic8.membrane.core.http.*; import com.predic8.membrane.core.interceptor.*; import com.predic8.membrane.core.interceptor.administration.*; import com.predic8.membrane.core.interceptor.rest.*; +import com.predic8.membrane.core.proxies.*; import com.predic8.membrane.core.proxies.Proxy; import com.predic8.membrane.core.resolver.*; -import com.predic8.membrane.core.proxies.*; import com.predic8.membrane.core.util.*; -import com.predic8.wsdl.*; -import com.predic8.wstool.creator.*; -import groovy.xml.MarkupBuilder; import org.jetbrains.annotations.*; import org.slf4j.*; @@ -37,7 +34,7 @@ import java.util.regex.*; import static com.predic8.membrane.annot.Constants.*; -import static com.predic8.membrane.core.http.Response.ok; +import static com.predic8.membrane.core.http.Response.*; import static com.predic8.membrane.core.interceptor.Outcome.*; import static java.util.regex.Pattern.*; @@ -47,7 +44,7 @@ @MCElement(name="webServiceExplorer") public class WebServiceExplorerInterceptor extends RESTInterceptor implements ProxyAware { - private static final Logger log = LoggerFactory.getLogger(WebServiceExplorerInterceptor.class.getName()); + private static final Logger log = LoggerFactory.getLogger(WebServiceExplorerInterceptor.class); private static final Pattern wsdlRequest = compile(".*\\?(wsdl|xsd=.*)", CASE_INSENSITIVE); @@ -55,6 +52,8 @@ public class WebServiceExplorerInterceptor extends RESTInterceptor implements Pr private String portName; private Proxy proxy; + private com.predic8.membrane.core.util.wsdl.parser.Definitions definitions; + public WebServiceExplorerInterceptor() { name = "web service explorer"; } @@ -80,7 +79,6 @@ public String getWsdl() { @MCAttribute public void setWsdl(String wsdl) { this.wsdl = wsdl; - this.parsedWSDL = null; } public String getPortName() { @@ -92,64 +90,6 @@ public void setPortName(String portName) { this.portName = portName; } - private volatile Definitions parsedWSDL; - - private Definitions getParsedWSDL() { - if (parsedWSDL != null) - return parsedWSDL; - WSDLParserContext ctx = new WSDLParserContext(); - ctx.setInput(ResolverMap.combine(router.getConfiguration().getBaseLocation(), wsdl)); - return parsedWSDL = getWsdlParser().parse(ctx); - } - - private @NotNull WSDLParser getWsdlParser() { - WSDLParser wsdlParser = new WSDLParser(); - wsdlParser.setResourceResolver(router.getResolverMap().toExternalResolver().toExternalResolver()); - return wsdlParser; - } - - @Mapping("[^?]*/operation/([^/?]+)/([^/?]+)/([^/?]+)") - public Response createOperationResponse(QueryParameter params, String relativeRootPath) { - try { - final String bindingName = params.getGroup(1); - final String portName = params.getGroup(2); - final String operationName = params.getGroup(3); - - final Service service = getService(getParsedWSDL()); - - StringWriter sw = new StringWriter(); - new StandardPage(sw, null) { - @Override - protected void createContent() { - h1().text("Service Proxy for " + service.getName()); - h2().text("Operation: " + operationName).end(); - - h3().text("Sample Request").end(); - - pre().text(generateSampleRequest(portName, operationName, bindingName, getParsedWSDL())).end(); - } - }; - return ok(sw.toString()).build(); - } catch (IllegalArgumentException e) { - log.error("", e); - return Response.internalServerError().build(); - } - } - - private Service getService(Definitions d) { - - if (proxy instanceof SOAPProxy sp) { - String serviceName = sp.getServiceName(); - if (serviceName != null) { - return WSDLUtil.getService(d, serviceName); - } - } - - if (d.getServices().size() != 1) - throw new IllegalArgumentException("WSDL needs to have exactly one service for SOAPUIInterceptor to work."); - return d.getServices().getFirst(); - } - private String getClientURL(Exchange exc) { // TODO: move this to some central location try { @@ -176,12 +116,13 @@ public Response createSOAPUIResponse(QueryParameter params, final String relativ try { final String myPath = router.getConfiguration().getUriFactory().create(exc.getRequest().getUri()).getPath(); - final Definitions w = getParsedWSDL(); - final Service service = getService(w); - final Port port = SOAPProxy.selectPort(service.getPorts(), portName); - final List ports = getPortsByLocation(service, port); + final com.predic8.membrane.core.util.wsdl.parser.Definitions w = getDefinitions(); + + final com.predic8.membrane.core.util.wsdl.parser.Service service = w.getServices().getFirst(); // TODO display all services + final com.predic8.membrane.core.util.wsdl.parser.Port port = service.getPorts().getFirst(); + final List ports = service.getPorts(); - StringWriter sw = new StringWriter(); + var sw = new StringWriter(); new StandardPage(sw, service.getName()) { @Override protected void createContent() { @@ -193,20 +134,16 @@ protected void createContent() { text("WSDL: ").a().href(wsdlLink).text(wsdlLink).end(); end(); - for (PortType pt : WSDLUtil.getPortTypes(service)) { + for (com.predic8.membrane.core.util.wsdl.parser.PortType pt : ports.stream().map(port1 -> port1.getBinding()).map(b -> b.getPortType()).toList()) { h2().text("Port Type: " + pt.getName()).end(); - Documentation d = pt.getDocumentation(); - if (d != null) { - p().text("Documentation: " + d).end(); - } +// Documentation d = pt.getDocumentation(); +// if (d != null) { +// p().text("Documentation: " + d).end(); +// } } - Binding binding = port.getBinding(); - List bindingOperations = getOperationsByBinding(w, binding); - if (bindingOperations.isEmpty()) - p().text("There are no operations defined.").end(); - else - createOperationsTable(w, bindingOperations, binding, binding.getPortType()); + com.predic8.membrane.core.util.wsdl.parser.Binding binding = port.getBinding(); + createOperationsTable(definitions, binding, binding.getPortType()); h2().text("Virtual Endpoint").end(); p().a().href(getClientURL(exc)).text(getClientURL(exc)).end().end(); @@ -218,47 +155,42 @@ protected void createContent() { createEndpointTable(service.getPorts(), ports); } - private void createOperationsTable(Definitions w, List bindingOperations, Binding binding, PortType portType) { + private void createOperationsTable(com.predic8.membrane.core.util.wsdl.parser.Definitions w, com.predic8.membrane.core.util.wsdl.parser.Binding binding, com.predic8.membrane.core.util.wsdl.parser.PortType portType) { table().cellspacing("0").cellpadding("0").border(""+1); tr(); - th().text("Operation").end(); - th().text("Input").end(); - th().text("Output").end(); + th().text("Operation").end(); + th().text("Input").end(); + th().text("Output").end(); end(); - for (Operation o : bindingOperations) { + for (com.predic8.membrane.core.util.wsdl.parser.Operation o : portType.getOperations()) { tr(); td(); - if ("HTTP".equals(getProtocolVersion(binding))) { text(o.getName()); - } else { - a().href(getLinkForOperation(binding, portType, o, myPath)).text(o.getName()).end(); - } end(); td(); - for (Part p : o.getInput().getMessage().getParts()) - text(p.getElement().getName()); - + for (com.predic8.membrane.core.util.wsdl.parser.Part p : o.getInputs().stream().map(i -> i.getPart()).toList()) + text(p.getElementQName().toString()); end(); td(); - for (Part p : o.getOutput().getMessage().getParts()) - text(p.getElement().getName()); + for (com.predic8.membrane.core.util.wsdl.parser.Part p : o.getOutputs().stream().map(i -> i.getPart()).toList()) + text(p.getElementQName().toString()); end(); end(); } end(); } - private void createEndpointTable(List ports, List matchingPorts) { + private void createEndpointTable(List ports, List matchingPorts) { table().cellspacing("0").cellpadding("0").border(""+1); tr(); th().text("Port Name").end(); th().text("Protocol").end(); th().text("URL").end(); end(); - for (Port p : ports) { + for (com.predic8.membrane.core.util.wsdl.parser.Port p : ports) { tr(); td().text(p.getName()).end(); - td().text(getProtocolVersion(p.getBinding())).end(); + td().text(p.getBinding().getSoapVersion().name()).end(); td().text(p.getAddress().getLocation()).end(); td(); if (matchingPorts.contains(p)) @@ -278,8 +210,8 @@ private void createEndpointTable(List ports, List matchingPorts) { } } - private String getLinkForOperation(Binding binding, PortType portType, Operation o, String path) { - return path + "/operation/" + binding.getName() + "/" + portType.getName() + "/" + o.getName(); + private com.predic8.membrane.core.util.wsdl.parser.Definitions getDefinitions() throws Exception { + return com.predic8.membrane.core.util.wsdl.parser.Definitions.parse(router.getResolverMap(),wsdl); } private abstract static class StandardPage extends Html { @@ -316,49 +248,6 @@ public StandardPage(Writer writer, String title) { protected abstract void createContent(); } - private List getOperationsByBinding(final Definitions w, Binding binding) { - List bindingOperations = new ArrayList<>(); - for (Operation o : w.getOperations()) - if (binding.getOperation(o.getName()) != null) - bindingOperations.add(o); - return bindingOperations; - } - - private List getPortsByLocation(Service service, Port port) { - String location = port.getAddress().getLocation(); - if (location == null) - throw new IllegalArgumentException("Location not set for port in WSDL."); - - final List ports = new ArrayList<>(); - for (Port p : service.getPorts()) - if (location.equals(p.getAddress().getLocation())) - ports.add(p); - return ports; - } - - private String getProtocolVersion(Binding binding) { - String transport = getNamespaceURI(binding); - if (WSDL_SOAP11_NS.equals(transport)) - transport = "SOAP 1.1"; - if (WSDL_SOAP12_NS.equals(transport)) - transport = "SOAP 1.2"; - if (WSDL_HTTP_NS.equals(transport)) - transport = "HTTP"; - return transport; - } - - private String getNamespaceURI(Binding binding) { - return ((javax.xml.namespace.QName) binding.getBinding().getElementName()).getNamespaceURI(); - } - - private String generateSampleRequest(final String portName, final String operationName, - final String bindingName, final Definitions w) { - StringWriter writer = new StringWriter(); - SOARequestCreator creator = new SOARequestCreator(w, new RequestTemplateCreator(), new MarkupBuilder(writer)); - creator.createRequest(portName, operationName, bindingName); - return writer.toString(); - } - @Override public String getShortDescription() { return "Displays a graphical UI describing the web service when accessed using GET requests."; diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java b/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java index 6c976d7ce1..c857ec9e2c 100644 --- a/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java +++ b/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java @@ -26,17 +26,15 @@ import com.predic8.membrane.core.router.*; import com.predic8.membrane.core.transport.http.client.*; import com.predic8.membrane.core.util.*; -import com.predic8.membrane.core.util.soap.WSDLUtil; -import com.predic8.wsdl.*; +import com.predic8.membrane.core.util.wsdl.parser.*; import org.apache.commons.lang3.*; import org.jetbrains.annotations.*; import org.slf4j.*; import java.net.*; -import java.util.*; import java.util.regex.*; -import static com.predic8.membrane.core.interceptor.InterceptorUtil.moveToFirstPosition; +import static com.predic8.membrane.core.interceptor.InterceptorUtil.*; /** * @description

@@ -59,7 +57,7 @@ @MCElement(name = "soapProxy", topLevel = true, component = false) public class SOAPProxy extends AbstractServiceProxy { - private static final Logger log = LoggerFactory.getLogger(SOAPProxy.class.getName()); + private static final Logger log = LoggerFactory.getLogger(SOAPProxy.class); // configuration attributes protected String wsdl; @@ -94,10 +92,23 @@ public void init() { } protected void configureFromWSDL() { + Definitions defs; - Definitions definitions = parseWSDLOnly(); - Service service = getService(definitions); - setProxyName(service, definitions); + try { + defs = Definitions.parse(resolverMap, wsdl); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Service service; + if (serviceName != null) + service = defs.getService(serviceName).orElseThrow( + () -> new ConfigurationException("No service with name '%s' found in WSDL %s".formatted(serviceName, wsdl)) + ); + else + service = defs.getServices().getFirst(); + + setProxyName(service, defs); String location = getLocation(service); @@ -116,19 +127,9 @@ protected void configureFromWSDL() { wsdlInterceptor.setPathRewriterOnWSDLInterceptor(key.getPath()); } - private Definitions parseWSDLOnly() { - try { - return getWsdlParser().parse(getWsdlParserContext()); - } catch (Exception e) { - String msg = "Could not parse WSDL from %s.".formatted(getWsdlParserContext().getInput()); - log.error("{}: {}", msg, e.getMessage()); - throw new ConfigurationException(msg, e); - } - } - private void prepareRouting(String location) { try { - URL url = new URL(location); + var url = new URL(location); setTarget(url); // Set target URL from WSDL location if (key.getPath() == null) { // If the config does not contain a path, use the path from the WSDL(address/@location) for the proxy key key.setUsePathPattern(true); @@ -148,7 +149,7 @@ private void configureRewritingOfPath(String targetPath) { if (targetPath == null) return; - RewriteInterceptor ri = new RewriteInterceptor(); + var ri = new RewriteInterceptor(); ri.setMappings(Lists.newArrayList(new RewriteInterceptor.Mapping("^" + Pattern.quote(key.getPath()), Matcher.quoteReplacement(targetPath), "rewrite"))); interceptors.addFirst(ri); } @@ -160,53 +161,14 @@ private void configureRewritingOfPath(String targetPath) { return url.getPath(); } - private @NotNull String getLocation(Service service) { - String location = getPort(service).getAddress().getLocation(); + private @NotNull String getLocation(com.predic8.membrane.core.util.wsdl.parser.Service service) { + var location = service.getPorts().getFirst().getAddress().getLocation(); if (location == null) throw new ConfigurationException("In the WSDL %s, there is no @location defined on the port.".formatted(wsdl)); return location; } - private @NotNull Port getPort(Service service) { - return selectPort(service.getPorts(), portName); - } - - private @NotNull WSDLParserContext getWsdlParserContext() { - WSDLParserContext ctx = new WSDLParserContext(); - ctx.setInput(ResolverMap.combine(router.getConfiguration().getBaseLocation(), wsdl)); - return ctx; - } - - private @NotNull WSDLParser getWsdlParser() { - WSDLParser wsdlParser = new WSDLParser(); - wsdlParser.setResourceResolver(resolverMap.toExternalResolver().toExternalResolver()); - return wsdlParser; - } - - private Service getService(Definitions definitions) { - List services = definitions.getServices(); - if (services.size() == 1) - return services.getFirst(); - if (serviceName == null) { - throw new SOAPProxyMultipleServicesException(this, getServiceNames(services)); - } - return getServiceByName(services, serviceName); - } - - private Service getServiceByName(List services, String serviceName) { - return services.stream() - .filter(s -> s.getName().equals(serviceName)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("No service with name %s found in the WSDL. Available services are: %s".formatted(serviceName, getServiceNames(services)))); - } - - private static @NotNull List getServiceNames(List services) { - return services.stream() - .map(WSDLElement::getName) - .toList(); - } - private void setTarget(URL url) { if (wsdl.startsWith("internal:")) { try { @@ -229,16 +191,6 @@ private void setProxyName(Service service, Definitions definitions) { name = StringUtils.isEmpty(service.getName()) ? definitions.getName() : service.getName(); } - public static Port selectPort(List ports, String portName) { - if (portName != null) { - for (Port port : ports) - if (portName.equals(port.getName())) - return port; - throw new IllegalArgumentException("No port with name '" + portName + "' found."); - } - return WSDLUtil.getPort(ports); - } - private WSDLInterceptor addAndGetWSDLInterceptor() { return moveToFirstPosition(interceptors, WSDLInterceptor.class, WSDLInterceptor::new).orElseThrow(); } diff --git a/core/src/main/java/com/predic8/membrane/core/resolver/ResolverMap.java b/core/src/main/java/com/predic8/membrane/core/resolver/ResolverMap.java index 78629f4911..b7a022debc 100644 --- a/core/src/main/java/com/predic8/membrane/core/resolver/ResolverMap.java +++ b/core/src/main/java/com/predic8/membrane/core/resolver/ResolverMap.java @@ -23,7 +23,6 @@ import com.predic8.membrane.core.transport.http.client.HttpClientConfiguration; import com.predic8.membrane.core.util.*; import com.predic8.membrane.core.util.functionalInterfaces.*; -import com.predic8.xml.util.*; import org.slf4j.*; import org.w3c.dom.ls.*; @@ -268,46 +267,9 @@ public LSResourceResolver toLSResourceResolver() { }; } - public ExternalResolverConverter toExternalResolver() { - return new ExternalResolverConverter(); - } public KubernetesSchemaResolver getKubernetesSchemaResolver() { return (KubernetesSchemaResolver) getSchemaResolver("kubernetes:"); } - public class ExternalResolverConverter { - - public ExternalResolver toExternalResolver() { - return new ExternalResolver() { - @Override - public InputStream resolveAsFile(String filename, String baseDir) { - try { - if (baseDir != null) { - return ResolverMap.this.resolve(combine(baseDir, filename)); - } - return ResolverMap.this.resolve(filename); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected InputStream resolveViaHttp(Object url) { - try { - String url2 = (String) url; - int q = url2.indexOf('?'); - if (q == -1) - url2 = url2.replaceAll("/[^/]+/\\.\\./", "/"); - else - url2 = url2.substring(0, q).replaceAll("/[^/]+/\\.\\./", "/") + url2.substring(q); - - return getSchemaResolver(url2).resolve(url2); - } catch (ResourceRetrievalException e) { - throw new RuntimeException(e); - } - } - }; - } - } } diff --git a/core/src/main/java/com/predic8/membrane/core/transport/ssl/TLSUnrecognizedNameException.java b/core/src/main/java/com/predic8/membrane/core/transport/ssl/TLSUnrecognizedNameException.java index d6bbe3c36b..dd5fc8309e 100644 --- a/core/src/main/java/com/predic8/membrane/core/transport/ssl/TLSUnrecognizedNameException.java +++ b/core/src/main/java/com/predic8/membrane/core/transport/ssl/TLSUnrecognizedNameException.java @@ -15,7 +15,7 @@ import java.io.*; -import static groovy.json.StringEscapeUtils.*; +import static org.apache.commons.text.StringEscapeUtils.*; public class TLSUnrecognizedNameException extends IOException { public TLSUnrecognizedNameException(String hostname) { diff --git a/core/src/main/java/com/predic8/membrane/core/util/WSDLUtil.java b/core/src/main/java/com/predic8/membrane/core/util/WSDLUtil.java index ce99e9507b..2129415882 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/WSDLUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/WSDLUtil.java @@ -13,97 +13,86 @@ limitations under the License. */ package com.predic8.membrane.core.util; -import com.predic8.membrane.annot.Constants; -import com.predic8.wsdl.*; import org.slf4j.*; -import javax.xml.namespace.*; -import java.util.*; - -import static com.predic8.membrane.annot.Constants.SoapVersion.*; -import static com.predic8.membrane.annot.Constants.WSDL_SOAP11_NS; -import static com.predic8.membrane.annot.Constants.WSDL_SOAP12_NS; -import static com.predic8.membrane.core.util.WSDLUtil.Direction.*; -import static com.predic8.membrane.core.util.xml.XMLUtil.groovyToJavaxQName; - public class WSDLUtil { static final Logger log = LoggerFactory.getLogger(WSDLUtil.class.getName()); - /** - * Searches a service in a WSDL. Does not consider namespaces - * - * @param definitions Parsed WSDL - * @param serviceName local name component of the attribute definitions.service.@name - * @return Service - */ - public static Service getService(Definitions definitions, String serviceName) { - List services = definitions.getServices().stream().filter(s -> s.getName().equals(serviceName)).toList(); - if (services.isEmpty()) { - String msg = "No service found with name %s in WSDL with namespace %s".formatted(serviceName, definitions.getTargetNamespace()); - log.error(msg); - throw new RuntimeException(msg); - } - return services.get(0); - } - - public static Set getPortTypes(Service service) { - Set portTypes = new HashSet<>(); - service.getPorts().forEach(port -> portTypes.add(port.getBinding().getPortType())); - return portTypes; - } +// /** +// * Searches a service in a WSDL. Does not consider namespaces +// * +// * @param definitions Parsed WSDL +// * @param serviceName local name component of the attribute definitions.service.@name +// * @return Service +// */ +// public static Service getService(Definitions definitions, String serviceName) { +// List services = definitions.getServices().stream().filter(s -> s.getName().equals(serviceName)).toList(); +// if (services.isEmpty()) { +// String msg = "No service found with name %s in WSDL with namespace %s".formatted(serviceName, definitions.getTargetNamespace()); +// log.error(msg); +// throw new RuntimeException(msg); +// } +// return services.get(0); +// } + +// public static Set getPortTypes(Service service) { +// Set portTypes = new HashSet<>(); +// service.getPorts().forEach(port -> portTypes.add(port.getBinding().getPortType())); +// return portTypes; +// } public enum Direction {REQUEST, RESPONSE} - /** - * A schema can have lots of toplevel elements but not all elements are valid toplevel elements in SOAP requests. - * Only the elements referenced from service->port->binding->porttype->msg->types-schema->element are valid - * toplevel elements in SOAP requests. - * - * @param service service for that elements should be searched - * @return Set of elements that are allowed in this service as soap elements - */ - public static Set getPossibleSOAPElements(Service service, Direction direction) { - return getPossibleSOAPElements(service.getPorts().get(0).getBinding().getPortType(), direction); - } - - public static Set getPossibleSOAPElements(PortType portType, Direction direction) { - Set elements = new HashSet<>(); - - portType.getOperations().forEach(op -> { - AbstractPortTypeMessage msg = getMessage(direction, op); - - // Does the Operation have an input/output message according to the direction parameter? - if (msg == null) - return; - - msg.getMessage().getParts().forEach(part -> elements.add(groovyToJavaxQName(part.getElement().getQname()))); - }); - - return elements; - } - - - /** - * SOAP version of a WSDL port - * @param port of definitions.service object - * @return SOAP version enum - */ - public static Constants.SoapVersion getSOAPVersion(Port port) { - if (port.getAddress().getElementName() instanceof QName qn) { - return switch (qn.getNamespaceURI()) { - case WSDL_SOAP11_NS -> SOAP11; - case WSDL_SOAP12_NS -> SOAP12; - default -> UNKNOWN; - }; - } - return UNKNOWN; - - } - - private static AbstractPortTypeMessage getMessage(Direction direction, Operation operation) { - if (direction == REQUEST) - return operation.getInput(); - return operation.getOutput(); - } +// /** +// * A schema can have lots of toplevel elements but not all elements are valid toplevel elements in SOAP requests. +// * Only the elements referenced from service->port->binding->porttype->msg->types-schema->element are valid +// * toplevel elements in SOAP requests. +// * +// * @param service service for that elements should be searched +// * @return Set of elements that are allowed in this service as soap elements +// */ +// public static Set getPossibleSOAPElements(Service service, Direction direction) { +// return getPossibleSOAPElements(service.getPorts().get(0).getBinding().getPortType(), direction); +// } +// +// public static Set getPossibleSOAPElements(PortType portType, Direction direction) { +// Set elements = new HashSet<>(); +// +// portType.getOperations().forEach(op -> { +// AbstractPortTypeMessage msg = getMessage(direction, op); +// +// // Does the Operation have an input/output message according to the direction parameter? +// if (msg == null) +// return; +// +// msg.getMessage().getParts().forEach(part -> elements.add(groovyToJavaxQName(part.getElement().getQname()))); +// }); +// +// return elements; +// } + + +// /** +// * SOAP version of a WSDL port +// * @param port of definitions.service object +// * @return SOAP version enum +// */ +// public static Constants.SoapVersion getSOAPVersion(Port port) { +// if (port.getAddress().getElementName() instanceof QName qn) { +// return switch (qn.getNamespaceURI()) { +// case WSDL_SOAP11_NS -> SOAP11; +// case WSDL_SOAP12_NS -> SOAP12; +// default -> UNKNOWN; +// }; +// } +// return UNKNOWN; +// +// } +// +// private static AbstractPortTypeMessage getMessage(Direction direction, Operation operation) { +// if (direction == REQUEST) +// return operation.getInput(); +// return operation.getOutput(); +// } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/soap/WSDLUtil.java b/core/src/main/java/com/predic8/membrane/core/util/soap/WSDLUtil.java index 6fac93896b..59e60fb531 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/soap/WSDLUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/soap/WSDLUtil.java @@ -14,20 +14,14 @@ package com.predic8.membrane.core.util.soap; -import com.predic8.wsdl.*; import org.slf4j.*; -import javax.xml.namespace.*; -import java.util.*; import java.util.regex.*; -import static com.predic8.membrane.annot.Constants.*; -import static java.util.regex.Matcher.quoteReplacement; +import static java.util.regex.Matcher.*; public class WSDLUtil { - private static final Logger log = LoggerFactory.getLogger(WSDLUtil.class.getName()); - private static final Pattern relativePathPattern = Pattern.compile("^\\./[^/?]*\\?"); public static String rewriteRelativeWsdlPath(String path, String replacementName) { @@ -35,41 +29,4 @@ public static String rewriteRelativeWsdlPath(String path, String replacementName .matcher(path) .replaceAll(quoteReplacement("./%s?".formatted(replacementName))); } - - /** - * Retrieves a SOAP port from the given list of ports. This method first attempts to find a - * port using the SOAP 1.1 namespace and, if not found, tries using the SOAP 1.2 namespace. - * - * @param ports the list of available ports to search for a SOAP/1.1 or SOAP/1.2 port - * @return the first matching SOAP port if found - * @throws IllegalArgumentException if no SOAP/1.1 or SOAP/1.2 port is found - */ - public static Port getPort(List ports) { - Port port = getPortByNamespace(ports, WSDL_SOAP11_NS); - if (port == null) - port = getPortByNamespace(ports, WSDL_SOAP12_NS); - if (port != null) - return port; - throw new IllegalArgumentException("No SOAP/1.1 or SOAP/1.2 ports found in WSDL."); - } - - private static Port getPortByNamespace(List ports, String namespace) { - for (Port port : ports) { - try { - if (port.getBinding() == null) - continue; - if (port.getBinding().getBinding() == null) - continue; - AbstractBinding binding = port.getBinding().getBinding(); - if (!"http://schemas.xmlsoap.org/soap/http".equals(binding.getProperty("transport"))) - continue; - if (!namespace.equals(((QName) binding.getElementName()).getNamespaceURI())) - continue; - return port; - } catch (Exception e) { - log.warn("Error inspecting WSDL port binding.", e); - } - } - return null; - } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index 3f2387dd23..0d4d3a6d1e 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -45,7 +45,7 @@ public static Style fromString(String style) { public Binding(WSDLParserContext ctx, Node node) { super(ctx,node); - ctx.getDefinitions().bindings.add(this); + ctx.getDefinitions().getBindings().add(this); operations = getBindingOperations(node); portType = getPortType(node); } @@ -62,6 +62,10 @@ public PortType getPortType() { return portType; } + public SOAPVersion getSoapVersion() { + return soapVersion; + } + private List getBindingOperations(Node node) { var result = new ArrayList(); var children = node.getChildNodes(); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index c6144fca9d..6e559cc521 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -37,13 +37,14 @@ public enum SOAPVersion { SOAP_11, SOAP_12 } - List schemas = new ArrayList<>(); - List messages = new ArrayList<>(); - List portTypes = new ArrayList<>(); - List bindings = new ArrayList<>(); - List services = new ArrayList<>(); + private List schemas = new ArrayList<>(); + private List messages = new ArrayList<>(); + private List portTypes = new ArrayList<>(); + private List bindings = new ArrayList<>(); + private List services = new ArrayList<>(); - String targetNamespace; + private String targetNamespace; + private String name; Set soapVersions = new HashSet<>(); @@ -52,6 +53,7 @@ private Definitions() { public void parse(WSDLParserContext ctx, Element element) { targetNamespace = element.getAttribute("targetNamespace"); + name = getName(element); for (var schemaElement : getSchemaElements(element)) { schemas.add(new Schema(ctx, schemaElement)); @@ -73,6 +75,13 @@ public void parse(WSDLParserContext ctx, Element element) { } } + private static @Nullable String getName(Element element) { + var name = element.getAttribute("name"); + if (name.isEmpty()) + return null; + return name; + } + private void importEmbeddedSchemas() { for (var schema : schemas) { for (var i : schema.getImports()) { @@ -131,6 +140,9 @@ public String getTargetNamespace() { return targetNamespace; } + public String getName() { + return name; + } public Set getSoapVersions() { return soapVersions; 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 875f29038c..2b8e0d2638 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 @@ -27,7 +27,7 @@ public class Message extends WSDLElement { public Message(WSDLParserContext ctx, Node node) { super(ctx,node); this.parts.add(getPart(node)); - ctx.getDefinitions().messages.add(this); + ctx.getDefinitions().getMessages().add(this); } /** diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index 976d480b74..cde3377940 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -14,7 +14,6 @@ package com.predic8.membrane.core.util.wsdl.parser; -import com.predic8.wsdl.*; import org.w3c.dom.*; import javax.xml.namespace.*; @@ -28,7 +27,7 @@ public class Operation extends WSDLElement { private final List inputs; private final List outputs; - private final List faults; + private final List faults; public enum Direction { INPUT, OUTPUT; @@ -59,7 +58,7 @@ public List getMessagesByDirection(Direction direction) { return outputs; } - public List getFaults() { + public List getFaults() { return faults; } @@ -71,8 +70,8 @@ private List getOutputs(Node node) { return getMessagesByDirection(node, OUTPUT); } - private List getFaults(Node node) { - return new ArrayList<>(); + private List getFaults(Node node) { + return new ArrayList<>(); // @TODO } private List getMessagesByDirection(Node node, Direction direction) { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java index 18e852e485..8ccdb26b47 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java @@ -16,8 +16,10 @@ import org.w3c.dom.*; -import static com.predic8.membrane.annot.Constants.WSDL11_NS; -import static org.w3c.dom.Node.ELEMENT_NODE; +import java.util.*; + +import static com.predic8.membrane.annot.Constants.*; +import static org.w3c.dom.Node.*; public class Port extends WSDLElement { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java index eef27244d4..f26910cf2a 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java @@ -29,7 +29,7 @@ public class PortType extends WSDLElement { public PortType(WSDLParserContext ctx, Node node) { super(ctx,node); operations = getOperations(node); - ctx.getDefinitions().portTypes.add(this); + ctx.getDefinitions().getPortTypes().add(this); } public List getOperations() { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java index 363e5ce275..1191fcf103 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java @@ -28,7 +28,7 @@ public class Service extends WSDLElement { public Service(WSDLParserContext ctx, Node element) { super(ctx,element); ports = getPorts(element); - ctx.getDefinitions().services.add(this); + ctx.getDefinitions().getServices().add(this); } public List getPorts() { diff --git a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java index 7feffa5d03..06923a3944 100644 --- a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java +++ b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java @@ -56,7 +56,7 @@ public Relocator(OutputStreamWriter osw, String protocol, String host, public static String getNewLocation(String addr, String protocol, String host, int port, String contextPath) { try { - URL oldURL = new URL(addr); + var oldURL = new URL(addr); if (port == -1) { return new URL(protocol, host, contextPath + oldURL.getFile()).toString(); } @@ -87,7 +87,7 @@ private void relocate(XMLEventReader parser) throws XMLStreamException { } private XMLEvent process(XMLEventReader parser) throws XMLStreamException { - XMLEvent event = parser.nextEvent(); + var event = parser.nextEvent(); if (!event.isStartElement()) return event; @@ -112,7 +112,7 @@ private QName getElementName(XMLEvent event) { } private XMLEvent replace(XMLEvent event, String attribute) { - StartElement start = event.asStartElement(); + var start = event.asStartElement(); return fac.createStartElement(start.getName(), new ReplaceIterator(fac, attribute, start.getAttributes()), start.getNamespaces()); @@ -147,9 +147,9 @@ public boolean hasNext() { } public Attribute next() { - Attribute atr = attrs.next(); + var atr = attrs.next(); if (atr.getName().equals(new QName(replace))) { - String value = atr.getValue(); + var value = atr.getValue(); if (pathRewriter != null) { value = pathRewriter.rewrite(value); if (value.startsWith("http")) diff --git a/core/src/test/java/com/predic8/membrane/core/proxies/SOAPProxyTest.java b/core/src/test/java/com/predic8/membrane/core/proxies/SOAPProxyTest.java index ee9f99911e..01ba3b401e 100644 --- a/core/src/test/java/com/predic8/membrane/core/proxies/SOAPProxyTest.java +++ b/core/src/test/java/com/predic8/membrane/core/proxies/SOAPProxyTest.java @@ -108,7 +108,7 @@ void parseWSDLWithMultipleServicesForAWrongService() { proxy.setServiceName("WrongService"); proxy.setWsdl("classpath:/ws/cities-2-services.wsdl"); - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(ConfigurationException.class, () -> { router.add(proxy); router.start(); }); diff --git a/core/src/test/java/com/predic8/membrane/core/resolver/ResolverTest.java b/core/src/test/java/com/predic8/membrane/core/resolver/ResolverTest.java index 62c53fc3c3..41cf1d20ab 100644 --- a/core/src/test/java/com/predic8/membrane/core/resolver/ResolverTest.java +++ b/core/src/test/java/com/predic8/membrane/core/resolver/ResolverTest.java @@ -20,8 +20,6 @@ import com.predic8.membrane.core.proxies.*; import com.predic8.membrane.core.router.*; import com.predic8.membrane.core.util.*; -import com.predic8.schema.*; -import com.predic8.wsdl.*; import org.junit.jupiter.api.*; import org.junit.jupiter.params.*; import org.junit.jupiter.params.provider.*; @@ -150,25 +148,6 @@ public void testMembraneServiceProxyCombine(BasisUrlType basisUrlType) throws IO } } - @ParameterizedTest - @MethodSource("getConfigurations") - public void testMembraneSoaModel(BasisUrlType basisUrlType) { - if (hit = !setupLocations(basisUrlType)) - return; - - try { - WSDLParserContext ctx = new WSDLParserContext(); - ctx.setInput(wsdlLocation); - WSDLParser wsdlParser = new WSDLParser(); - wsdlParser.setResourceResolver(resolverMap.toExternalResolver().toExternalResolver()); - Definitions definitions = wsdlParser.parse(ctx); - for (Schema schema : definitions.getSchemas()) - schema.getElements(); // trigger lazy-loading - } catch (Exception e) { - throw new RuntimeException("wsdlLocation = " + xsdLocation, e); - } - } - @AfterEach public void postpare() { // since a.wsdl and 2.xsd reference a HTTP resource, it should get loaded diff --git a/core/src/test/java/com/predic8/membrane/core/util/WSDLUtilTest.java b/core/src/test/java/com/predic8/membrane/core/util/WSDLUtilTest.java deleted file mode 100644 index 6fc4858459..0000000000 --- a/core/src/test/java/com/predic8/membrane/core/util/WSDLUtilTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2024 predic8 GmbH, www.predic8.com - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. */ -package com.predic8.membrane.core.util; - -import com.predic8.wsdl.*; -import org.jetbrains.annotations.*; -import org.junit.jupiter.api.*; - -import javax.xml.namespace.*; -import java.util.*; - -import static com.predic8.membrane.core.util.WSDLUtil.*; -import static com.predic8.membrane.core.util.WSDLUtil.Direction.*; -import static org.junit.jupiter.api.Assertions.*; - -public class WSDLUtilTest { - - public static final String PREDIC_NS = "https://predic8.de/"; - - static Definitions definitions; - - @BeforeAll - static void beforeAll() { - WSDLParserContext ctx = new WSDLParserContext(); - ctx.setInput("src/test/resources/ws/two-separated-services.wsdl"); - WSDLParser wsdlParser = new WSDLParser(); - definitions = wsdlParser.parse(ctx); - } - - @Test - void getServiceA() { - testGetServiceByName("ServiceA"); - } - - @Test - void getServiceB() { - testGetServiceByName("ServiceB"); - } - - @Test - void getNonExistingService() { - assertThrows(RuntimeException.class, () -> getService(definitions, "not there")); - } - - private static void testGetServiceByName(String ServiceA) { - Service service = getService(definitions, ServiceA); - assertNotNull(service); - assertEquals(ServiceA, service.getName()); - } - - @Test - void getPossibleSOAPElementsARequest() { - Set el = getSoapElements("ServiceA",REQUEST); - assertNotNull(el); - assertEquals(1,el.size()); - assertTrue(el.contains(new QName(PREDIC_NS,"a"))); - } - - @Test - void getPossibleSOAPElementsAResponse() { - Set el = getSoapElements("ServiceA", RESPONSE); - assertNotNull(el); - assertEquals(1,el.size()); - assertTrue(el.contains(new QName(PREDIC_NS,"aResponse"))); - } - - @Test - void getPossibleSOAPElementsBRequest() { - Set el = getSoapElements("ServiceB", REQUEST); - assertNotNull(el); - assertEquals(1,el.size()); - assertTrue( el.contains(new QName(PREDIC_NS,"b"))); - } - - private static @NotNull Set getSoapElements(String serviceName, Direction direction) { - return getPossibleSOAPElements(getService(definitions, serviceName), direction); - } -} \ No newline at end of file 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 f570b4a6e4..8991d87fd6 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 @@ -30,6 +30,7 @@ class WSDLParserTest { @Test void simpleSchema() throws Exception { var definitions = Definitions.parse(new ResolverMap(), "classpath:/ws/cities.wsdl"); + assertEquals("cities", definitions.getName()); assertEquals(1, definitions.getSchemas().size()); var schema = definitions.getSchemas().getFirst(); assertEquals("https://predic8.de/cities", schema.getTargetNamespace()); @@ -76,9 +77,10 @@ void simpleSchema() throws Exception { @Test void includeImport() throws Exception { - var definitions = Definitions.parse(new ResolverMap(), "classpath://ws/include/include.wsdl"); - assertEquals(1, definitions.getSchemas().size()); - var embedded = definitions.getSchemas().getFirst(); + var dn = Definitions.parse(new ResolverMap(), "classpath://ws/include/include.wsdl"); + assertNull(dn.getName()); + assertEquals(1, dn.getSchemas().size()); + var embedded = dn.getSchemas().getFirst(); assertEquals("http://example.com/test", embedded.getTargetNamespace()); var schemaElements = embedded.getSchemaElements(); assertEquals(3, schemaElements.size()); diff --git a/core/src/test/java/com/predic8/membrane/integration/withinternet/LargeBodyTest.java b/core/src/test/java/com/predic8/membrane/integration/withinternet/LargeBodyTest.java index fcb71f6856..9461779553 100644 --- a/core/src/test/java/com/predic8/membrane/integration/withinternet/LargeBodyTest.java +++ b/core/src/test/java/com/predic8/membrane/integration/withinternet/LargeBodyTest.java @@ -98,8 +98,8 @@ public static void shutdown() { public void large() throws Exception { long len = MAX_VALUE + 1L; - Exchange e = new Request.Builder().post("http://localhost:3041/foo").body(len, new ConstantInputStream(len)).buildExchange(); - try (HttpClient hc = new HttpClient(hcc)) { + var e = post("http://localhost:3041/foo").body(len, new ConstantInputStream(len)).buildExchange(); + try (var hc = new HttpClient(hcc)) { hc.call(e); } assertTrue(e.getRequest().getBody().wasStreamed()); diff --git a/core/src/test/resources/ws/import/embedded.wsdl b/core/src/test/resources/ws/import/embedded.wsdl index f0af8bc1e7..4741fd3176 100644 --- a/core/src/test/resources/ws/import/embedded.wsdl +++ b/core/src/test/resources/ws/import/embedded.wsdl @@ -5,8 +5,7 @@ xmlns:types="http://example.com/types" xmlns:common="http://example.com/common" xmlns:xs="http://www.w3.org/2001/XMLSchema" - targetNamespace="http://example.com/service" - name="TestService"> + targetNamespace="http://example.com/service"> diff --git a/distribution/examples/extending-membrane/error-handling/custom-error-messages/apis.yaml b/distribution/examples/extending-membrane/error-handling/custom-error-messages/apis.yaml index bd6651771d..7cadc07397 100644 --- a/distribution/examples/extending-membrane/error-handling/custom-error-messages/apis.yaml +++ b/distribution/examples/extending-membrane/error-handling/custom-error-messages/apis.yaml @@ -34,6 +34,7 @@ api: - if: test: //s11:Fault language: xpath + $ref: "#/components/ns" flow: - template: contentType: application/xml @@ -42,17 +43,13 @@ api: e ${statusCode} SOAP Fault! - ${property.faultstring} + ${xpath('//faultstring/text()')} - - setProperty: - name: faultstring - value: ${//faultstring/text()} - language: xpath - if: test: statusCode >= 400 flow: - if: - test: /s11:Envelope + test: not(/s11:Envelope) language: xpath flow: - template: @@ -61,12 +58,8 @@ api: d ${statusCode} - ${property.description} + ${xpath('/failure/description')} - - setProperty: - name: description - value: ${/failure/description} - language: xpath - abort: - if: test: header['X-Protection'] != null diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/custom_error_messages/CustomErrorHandlingExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/custom_error_messages/CustomErrorHandlingExampleTest.java index d49dc5ff75..d4d69b88a4 100644 --- a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/custom_error_messages/CustomErrorHandlingExampleTest.java +++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/custom_error_messages/CustomErrorHandlingExampleTest.java @@ -84,6 +84,7 @@ void caseD() { .when() .get("http://localhost:2000/service") .then() + .log().ifValidationFails() .statusCode(500) .contentType(XML) .body("error.case", equalTo("d")) @@ -98,18 +99,17 @@ void caseE() { - Verursache SOAP Fault! + Düsseldorf """.stripIndent()) .when() .post("http://localhost:2000/service") .then() + .log().ifValidationFails() .statusCode(200) - .body( - containsString("e"), - containsString("Not Found") - ); + .body("error.case", equalTo("e")) + .body("error.fault", equalTo("Resource Not Found")); } @Test diff --git a/pom.xml b/pom.xml index 2b840f8d31..e46b76adb9 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ---> +--> + 4.0.0 org.membrane-soa @@ -83,7 +84,6 @@ 4.0.29 - @@ -156,6 +156,11 @@ groovy-templates ${groovy.version} + + org.apache.groovy + groovy-json + ${groovy.version} + com.fasterxml.jackson jackson-bom @@ -224,11 +229,6 @@ 6.1.0 provided - - com.predic8 - soa-model-core - 2.2.0 - com.google.guava guava From 67678613fafb1fa7b8d56182f8a6532f0f41196f Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 10 Mar 2026 12:12:57 +0100 Subject: [PATCH 12/28] Remove `WSDLUtil` class and refactor related utilities - Deleted `WSDLUtil` and its unused methods for maintainability. - Replaced redundant logic in WSDL parser classes with utility methods. - Streamlined SOAP wsdl-related exemplars and refactored embedded schema handling. - Simplified field initializations and usage of `var` across classes. - Updated documentation to reflect these changes. --- .../AuthorizationService.java | 2 +- .../ValidatorInterceptor.java | 13 +- .../soap/WebServiceExplorerInterceptor.java | 44 +++--- .../membrane/core/proxies/SOAPProxy.java | 48 ++++--- .../predic8/membrane/core/util/WSDLUtil.java | 98 ------------- .../core/util/wsdl/parser/Binding.java | 13 +- .../util/wsdl/parser/BindingOperation.java | 2 +- .../core/util/wsdl/parser/Definitions.java | 130 +++++------------- .../core/util/wsdl/parser/Message.java | 21 +-- .../core/util/wsdl/parser/Operation.java | 15 +- .../membrane/core/util/wsdl/parser/Part.java | 8 +- .../membrane/core/util/wsdl/parser/Port.java | 13 +- .../core/util/wsdl/parser/PortType.java | 22 +-- .../core/util/wsdl/parser/Service.java | 15 +- .../core/util/wsdl/parser/WSDLElement.java | 49 ++++++- .../util/wsdl/parser/WSDLParserContext.java | 4 +- .../core/util/wsdl/parser/WSDLParserUtil.java | 2 +- .../core/util/wsdl/parser/schema/Import.java | 20 +++ .../core/util/wsdl/parser/schema/Schema.java | 17 +-- .../membrane/core/ws/relocator/Relocator.java | 4 - docs/ROADMAP.md | 6 +- 21 files changed, 197 insertions(+), 349 deletions(-) delete mode 100644 core/src/main/java/com/predic8/membrane/core/util/WSDLUtil.java diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java b/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java index 219c362249..82e2c1dce1 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java @@ -319,7 +319,7 @@ private String createClientToken(FlowContext flowContext) { } public InputStream resolve(ResolverMap rm, String baseLocation, String url) throws Exception { - url = ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, baseLocation, url); + url = ResolverMap.combine( baseLocation, url); // ask the internal httpClient (might be proxied/authenticated), if HTTP if (url.startsWith("http")) { var exc = get(url).buildExchange(); diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java index 038de9aeb8..65171a350f 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java @@ -31,6 +31,7 @@ import static com.predic8.membrane.core.interceptor.Interceptor.Flow.*; import static com.predic8.membrane.core.interceptor.Outcome.*; import static com.predic8.membrane.core.interceptor.Outcome.ABORT; +import static com.predic8.membrane.core.resolver.ResolverMap.combine; import static com.predic8.membrane.core.util.text.TextUtil.*; /** @@ -96,25 +97,25 @@ private MessageValidator getMessageValidator() throws Exception { if (wsdl != null) { if (schemaMappings != null) logIgnoringRefSchemas(); - return new WSDLValidator(resourceResolver, ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), wsdl), serviceName, createFailureHandler(), skipFaults); + return new WSDLValidator(resourceResolver, combine(router.getConfiguration().getUriFactory(), getBaseLocation(), wsdl), serviceName, createFailureHandler(), skipFaults); } if (schema != null) { if (schemaMappings != null) logIgnoringRefSchemas(); - return new XMLSchemaValidator(resourceResolver, ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), schema), createFailureHandler()); + return new XMLSchemaValidator(resourceResolver, combine(router.getConfiguration().getUriFactory(), getBaseLocation(), schema), createFailureHandler()); } if (jsonSchema != null) { - return new JSONYAMLSchemaValidator(resourceResolver, ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), jsonSchema), createFailureHandler(), schemaVersion) {{ + return new JSONYAMLSchemaValidator(resourceResolver, combine(router.getConfiguration().getUriFactory(), getBaseLocation(), jsonSchema), createFailureHandler(), schemaVersion) {{ if(schemaMappings != null) setSchemaMappings(schemaMappings.getSchemaMap()); }}; } if (schematron != null) { if (schemaMappings != null) logIgnoringRefSchemas(); - return new SchematronValidator(ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), schematron), createFailureHandler(), router, applicationContext); + return new SchematronValidator(combine(router.getConfiguration().getUriFactory(), getBaseLocation(), schematron), createFailureHandler(), router, applicationContext); } - WSDLValidator validator = getWsdlValidatorFromSOAPProxy(); + var validator = getWsdlValidatorFromSOAPProxy(); if (validator != null) return validator; throw new RuntimeException("Validator is not configured properly. must have an attribute specifying the validator."); @@ -128,7 +129,7 @@ private static void logIgnoringRefSchemas() { if(soapProxy == null) return null; wsdl = soapProxy.getWsdl(); name = "soap validator"; - return new WSDLValidator(resourceResolver, ResolverMap.combine(URIFactory.DEFAULT_URI_FACTORY, getBaseLocation(), wsdl), serviceName, createFailureHandler(), skipFaults); + return new WSDLValidator(resourceResolver, combine(router.getConfiguration().getUriFactory(), getBaseLocation(), wsdl), serviceName, createFailureHandler(), skipFaults); } private @Nullable String getBaseLocation() { diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java index 35f9e898af..c5675bb4e6 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java @@ -21,11 +21,9 @@ import com.predic8.membrane.core.interceptor.*; import com.predic8.membrane.core.interceptor.administration.*; import com.predic8.membrane.core.interceptor.rest.*; -import com.predic8.membrane.core.proxies.*; import com.predic8.membrane.core.proxies.Proxy; -import com.predic8.membrane.core.resolver.*; -import com.predic8.membrane.core.util.*; -import org.jetbrains.annotations.*; +import com.predic8.membrane.core.util.wsdl.parser.*; +import com.predic8.membrane.core.util.wsdl.parser.Message; import org.slf4j.*; import java.io.*; @@ -52,8 +50,6 @@ public class WebServiceExplorerInterceptor extends RESTInterceptor implements Pr private String portName; private Proxy proxy; - private com.predic8.membrane.core.util.wsdl.parser.Definitions definitions; - public WebServiceExplorerInterceptor() { name = "web service explorer"; } @@ -93,8 +89,8 @@ public void setPortName(String portName) { private String getClientURL(Exchange exc) { // TODO: move this to some central location try { - String uri = exc.getHandler().getContextPath(exc) + exc.getRequest().getUri(); - String host = exc.getRequest().getHeader().getHost(); + var uri = exc.getHandler().getContextPath(exc) + exc.getRequest().getUri(); + var host = exc.getRequest().getHeader().getHost(); if (host != null) { if (host.contains(":")) host = host.substring(0, host.indexOf(":")); @@ -114,13 +110,11 @@ private String getClientURL(Exchange exc) { @Mapping("(?!.*operation)([^?]*)") public Response createSOAPUIResponse(QueryParameter params, final String relativeRootPath, final Exchange exc) throws Exception { try { - final String myPath = router.getConfiguration().getUriFactory().create(exc.getRequest().getUri()).getPath(); - - final com.predic8.membrane.core.util.wsdl.parser.Definitions w = getDefinitions(); + var definitions = Definitions.parse(router.getResolverMap(), wsdl); - final com.predic8.membrane.core.util.wsdl.parser.Service service = w.getServices().getFirst(); // TODO display all services - final com.predic8.membrane.core.util.wsdl.parser.Port port = service.getPorts().getFirst(); - final List ports = service.getPorts(); + var service = definitions.getServices().getFirst(); // TODO display all services + var port = service.getPorts().getFirst(); + var ports = service.getPorts(); var sw = new StringWriter(); new StandardPage(sw, service.getName()) { @@ -128,21 +122,21 @@ public Response createSOAPUIResponse(QueryParameter params, final String relativ protected void createContent() { h1().text("Service Proxy: " + service.getName()).end(); p(); - text("Target Namespace: " + w.getTargetNamespace()); + text("Target Namespace: " + definitions.getTargetNamespace()); br().end(); String wsdlLink = getClientURL(exc) + "?wsdl"; text("WSDL: ").a().href(wsdlLink).text(wsdlLink).end(); end(); - for (com.predic8.membrane.core.util.wsdl.parser.PortType pt : ports.stream().map(port1 -> port1.getBinding()).map(b -> b.getPortType()).toList()) { + for (com.predic8.membrane.core.util.wsdl.parser.PortType pt : ports.stream().map(Port::getBinding).map(Binding::getPortType).toList()) { h2().text("Port Type: " + pt.getName()).end(); -// Documentation d = pt.getDocumentation(); +// Documentation d = pt.getDocumentation(); @TODO // if (d != null) { // p().text("Documentation: " + d).end(); // } } - com.predic8.membrane.core.util.wsdl.parser.Binding binding = port.getBinding(); + var binding = port.getBinding(); createOperationsTable(definitions, binding, binding.getPortType()); h2().text("Virtual Endpoint").end(); @@ -155,24 +149,24 @@ protected void createContent() { createEndpointTable(service.getPorts(), ports); } - private void createOperationsTable(com.predic8.membrane.core.util.wsdl.parser.Definitions w, com.predic8.membrane.core.util.wsdl.parser.Binding binding, com.predic8.membrane.core.util.wsdl.parser.PortType portType) { + private void createOperationsTable(Definitions defs, Binding binding, PortType portType) { table().cellspacing("0").cellpadding("0").border(""+1); tr(); th().text("Operation").end(); th().text("Input").end(); th().text("Output").end(); end(); - for (com.predic8.membrane.core.util.wsdl.parser.Operation o : portType.getOperations()) { + for (Operation o : portType.getOperations()) { tr(); td(); text(o.getName()); end(); td(); - for (com.predic8.membrane.core.util.wsdl.parser.Part p : o.getInputs().stream().map(i -> i.getPart()).toList()) + for (Part p : o.getInputs().stream().map(Message::getPart).toList()) text(p.getElementQName().toString()); end(); td(); - for (com.predic8.membrane.core.util.wsdl.parser.Part p : o.getOutputs().stream().map(i -> i.getPart()).toList()) + for (Part p : o.getOutputs().stream().map(Message::getPart).toList()) text(p.getElementQName().toString()); end(); end(); @@ -180,7 +174,7 @@ private void createOperationsTable(com.predic8.membrane.core.util.wsdl.parser.De end(); } - private void createEndpointTable(List ports, List matchingPorts) { + private void createEndpointTable(List ports, List matchingPorts) { table().cellspacing("0").cellpadding("0").border(""+1); tr(); th().text("Port Name").end(); @@ -210,10 +204,6 @@ private void createEndpointTable(List new ConfigurationException("No service with name '%s' found in WSDL %s".formatted(serviceName, wsdl)) - ); - else - service = defs.getServices().getFirst(); - + var defs = parseWSDL(); + var service = getService(defs); setProxyName(service, defs); - - String location = getLocation(service); + var location = getLocation(service); // Signal to the later processing that the outgoing connection is using TLS if (location.startsWith("https")) { @@ -127,6 +113,28 @@ protected void configureFromWSDL() { wsdlInterceptor.setPathRewriterOnWSDLInterceptor(key.getPath()); } + private Service getService(Definitions defs) { + if (serviceName != null) + return defs.getService(serviceName).orElseThrow( + () -> new ConfigurationException("No service with name '%s' found in WSDL %s".formatted(serviceName, wsdl)) + ); + return defs.getServices().getFirst(); + } + + private @NotNull Definitions parseWSDL() { + try { + return Definitions.parse(resolverMap, wsdl); + } catch (Exception e) { + throw new ConfigurationException(""" + Cannot parse WSDL + + API: %s + WSDL location: %s. + Error. %s + """.formatted( name, wsdl, e.getMessage())); + } + } + private void prepareRouting(String location) { try { var url = new URL(location); @@ -150,7 +158,7 @@ private void configureRewritingOfPath(String targetPath) { return; var ri = new RewriteInterceptor(); - ri.setMappings(Lists.newArrayList(new RewriteInterceptor.Mapping("^" + Pattern.quote(key.getPath()), Matcher.quoteReplacement(targetPath), "rewrite"))); + ri.setMappings(Lists.newArrayList(new Mapping("^" + Pattern.quote(key.getPath()), Matcher.quoteReplacement(targetPath), "rewrite"))); interceptors.addFirst(ri); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/WSDLUtil.java b/core/src/main/java/com/predic8/membrane/core/util/WSDLUtil.java deleted file mode 100644 index 2129415882..0000000000 --- a/core/src/main/java/com/predic8/membrane/core/util/WSDLUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -/* Copyright 2024 predic8 GmbH, www.predic8.com - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. */ -package com.predic8.membrane.core.util; - -import org.slf4j.*; - -public class WSDLUtil { - - static final Logger log = LoggerFactory.getLogger(WSDLUtil.class.getName()); - -// /** -// * Searches a service in a WSDL. Does not consider namespaces -// * -// * @param definitions Parsed WSDL -// * @param serviceName local name component of the attribute definitions.service.@name -// * @return Service -// */ -// public static Service getService(Definitions definitions, String serviceName) { -// List services = definitions.getServices().stream().filter(s -> s.getName().equals(serviceName)).toList(); -// if (services.isEmpty()) { -// String msg = "No service found with name %s in WSDL with namespace %s".formatted(serviceName, definitions.getTargetNamespace()); -// log.error(msg); -// throw new RuntimeException(msg); -// } -// return services.get(0); -// } - -// public static Set getPortTypes(Service service) { -// Set portTypes = new HashSet<>(); -// service.getPorts().forEach(port -> portTypes.add(port.getBinding().getPortType())); -// return portTypes; -// } - - public enum Direction {REQUEST, RESPONSE} - -// /** -// * A schema can have lots of toplevel elements but not all elements are valid toplevel elements in SOAP requests. -// * Only the elements referenced from service->port->binding->porttype->msg->types-schema->element are valid -// * toplevel elements in SOAP requests. -// * -// * @param service service for that elements should be searched -// * @return Set of elements that are allowed in this service as soap elements -// */ -// public static Set getPossibleSOAPElements(Service service, Direction direction) { -// return getPossibleSOAPElements(service.getPorts().get(0).getBinding().getPortType(), direction); -// } -// -// public static Set getPossibleSOAPElements(PortType portType, Direction direction) { -// Set elements = new HashSet<>(); -// -// portType.getOperations().forEach(op -> { -// AbstractPortTypeMessage msg = getMessage(direction, op); -// -// // Does the Operation have an input/output message according to the direction parameter? -// if (msg == null) -// return; -// -// msg.getMessage().getParts().forEach(part -> elements.add(groovyToJavaxQName(part.getElement().getQname()))); -// }); -// -// return elements; -// } - - -// /** -// * SOAP version of a WSDL port -// * @param port of definitions.service object -// * @return SOAP version enum -// */ -// public static Constants.SoapVersion getSOAPVersion(Port port) { -// if (port.getAddress().getElementName() instanceof QName qn) { -// return switch (qn.getNamespaceURI()) { -// case WSDL_SOAP11_NS -> SOAP11; -// case WSDL_SOAP12_NS -> SOAP12; -// default -> UNKNOWN; -// }; -// } -// return UNKNOWN; -// -// } -// -// private static AbstractPortTypeMessage getMessage(Direction direction, Operation operation) { -// if (direction == REQUEST) -// return operation.getInput(); -// return operation.getOutput(); -// } -} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index 0d4d3a6d1e..6ed3873bfc 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -14,7 +14,6 @@ package com.predic8.membrane.core.util.wsdl.parser; -import com.predic8.membrane.annot.*; import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; import org.w3c.dom.*; @@ -22,6 +21,8 @@ import static com.predic8.membrane.annot.Constants.*; import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.DOCUMENT; +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; import static org.w3c.dom.Node.*; public class Binding extends WSDLElement { @@ -72,9 +73,7 @@ private List getBindingOperations(Node node) { for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); - if ((child.getNodeType() == ELEMENT_NODE) - && "operation".equals(child.getLocalName()) - && WSDL11_NS.equals(child.getNamespaceURI())) { + if (isWSDLElementWithName(child, "operation")) { result.add(new BindingOperation(ctx, child)); } @@ -82,10 +81,10 @@ private List getBindingOperations(Node node) { && "binding".equals(child.getLocalName())) { style = getStyle(child); if (WSDL_SOAP11_NS.equals(child.getNamespaceURI())) { - soapVersion = SOAPVersion.SOAP_11; + soapVersion = SOAP_11; ctx.getDefinitions().addSoapVersion(soapVersion); } else if (WSDL_SOAP12_NS.equals(child.getNamespaceURI())) { - soapVersion = SOAPVersion.SOAP_12; + soapVersion = SOAP_12; ctx.getDefinitions().addSoapVersion(soapVersion); } } @@ -122,8 +121,6 @@ private PortType getPortType(Node node) { return new PortType(ctx, portType); } } - return null; } - } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java index ef28262e59..448c0c384c 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java @@ -26,7 +26,7 @@ public BindingOperation(WSDLParserContext ctx, Node node) { } private String getSoapAction(Node node) { - Node n = node.getAttributes().getNamedItem("soapAction"); + var n = node.getAttributes().getNamedItem("soapAction"); if (n == null) return ""; return n.getNodeValue(); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 6e559cc521..bd79bdc28f 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -16,20 +16,18 @@ import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.wsdl.parser.schema.*; -import org.jetbrains.annotations.*; import org.slf4j.*; import org.w3c.dom.*; import java.util.*; import static com.predic8.membrane.annot.Constants.*; -import static org.w3c.dom.Node.*; /** * WSDL elements register themselves via WSDLParserContext. This is more convenient, e.g. binding is not * directly created. */ -public class Definitions { +public class Definitions extends WSDLElement { private static final Logger log = LoggerFactory.getLogger(Definitions.class); @@ -38,74 +36,55 @@ public enum SOAPVersion { } private List schemas = new ArrayList<>(); - private List messages = new ArrayList<>(); - private List portTypes = new ArrayList<>(); - private List bindings = new ArrayList<>(); - private List services = new ArrayList<>(); + private final List messages = new ArrayList<>(); + private final List portTypes = new ArrayList<>(); + private final List bindings = new ArrayList<>(); + private final List services = new ArrayList<>(); private String targetNamespace; - private String name; - Set soapVersions = new HashSet<>(); + private final Set soapVersions = new HashSet<>(); - private Definitions() { + private Definitions(Resolver resolver, String location) throws Exception { + super(new WSDLParserContext(null, resolver, location, new ArrayList<>()), read(resolver, location)); + ctx = ctx.definitions(this); + parse(ctx, this.getElement()); } - public void parse(WSDLParserContext ctx, Element element) { - targetNamespace = element.getAttribute("targetNamespace"); - name = getName(element); - - for (var schemaElement : getSchemaElements(element)) { - schemas.add(new Schema(ctx, schemaElement)); + private static Node read(Resolver resolver, String location) throws Exception { + try (var is = resolver.resolve(location)) { + return WSDLParserUtil.parse(is); } + } - importEmbeddedSchemas(); + public static Definitions parse(Resolver resolver, String location) throws Exception { + log.debug("Parsing WSDL from {}", location); + return new Definitions(resolver, location); + } - for (var e : getDirectChildElements(element, "service")) { - // Registers itself via WSDLParserContext - new Service(ctx, e); - } + private void parse(WSDLParserContext ctx, Element element) { + targetNamespace = element.getAttribute("targetNamespace"); + schemas = getSchemaElements(element); + importEmbeddedSchemas(); - // Abstract WSDL without binding and service elements + instantiateWSDLChildElements(element, "service", Service.class); if (services.isEmpty()) { - for (var e : getDirectChildElements(element, "portType")) { - // Registers itself via WSDLParserContext - new PortType(ctx, e); - } + instantiateWSDLChildElements(element, "portType", PortType.class); } } - private static @Nullable String getName(Element element) { - var name = element.getAttribute("name"); - if (name.isEmpty()) - return null; - return name; - } - + /** + * Go through all embedded schemas and import all embedded schemas that are referenced there + */ private void importEmbeddedSchemas() { - for (var schema : schemas) { - for (var i : schema.getImports()) { - if (i.getSchemaLocation() == null || i.getSchemaLocation().isEmpty()) { - var importedSchema = getEmbeddedSchema(i.getNamespace()); - log.debug("Importing embedded schema with namespace: {}", i.getNamespace()); - i.setSchema(importedSchema); - } - } - } - } - - private @Nullable Schema getEmbeddedSchema(String namespace) { - return schemas.stream().filter(s -> Objects.equals(s.getTargetNamespace(), namespace)) - .findFirst().orElse(null); + schemas.forEach(s -> s.getImports() + .forEach(i -> i.importEmbeddedSchema(this))); } - public static Definitions parse(Resolver resolver, String location) throws Exception { - log.debug("Parsing WSDL from {}", location); - var defs = new Definitions(); - try (var is = resolver.resolve(location)) { - defs.parse(new WSDLParserContext(defs, resolver, location, new ArrayList<>()), WSDLParserUtil.parse(is)); - } - return defs; + public Optional getEmbeddedSchema(String namespace) { + return schemas.stream() + .filter(s -> Objects.equals(s.getTargetNamespace(), namespace)) + .findFirst(); } public List getSchemas() { @@ -140,10 +119,6 @@ public String getTargetNamespace() { return targetNamespace; } - public String getName() { - return name; - } - public Set getSoapVersions() { return soapVersions; } @@ -152,42 +127,11 @@ public void addSoapVersion(SOAPVersion soapVersion) { soapVersions.add(soapVersion); } - private List getSchemaElements(Element wsdl) { - var schemas = new ArrayList(); - - var typesList = wsdl.getElementsByTagNameNS(WSDL11_NS, "types"); - for (int i = 0; i < typesList.getLength(); i++) { - var types = (Element) typesList.item(i); - var children = types.getChildNodes(); - - for (int j = 0; j < children.getLength(); j++) { - var child = children.item(j); - - if ((child.getNodeType() == ELEMENT_NODE) - && "schema".equals(child.getLocalName()) - && XSD_NS.equals(child.getNamespaceURI())) { - schemas.add((Element) child); - } - } - } - return schemas; + private List getSchemaElements(Element wsdl) { + return instantiateXSDChildElements(getTypes(wsdl), "schema", Schema.class); } - private static List getDirectChildElements(Element parent, String name) { - var result = new ArrayList(); - var children = parent.getChildNodes(); - - for (int i = 0; i < children.getLength(); i++) { - var node = children.item(i); - if (node.getNodeType() == ELEMENT_NODE) { - var element = (Element) node; - if (WSDL11_NS.equals(element.getNamespaceURI()) && - name.equals(element.getLocalName())) { - result.add(element); - } - } - } - - return result; + private static Node getTypes(Element wsdl) { + return wsdl.getElementsByTagNameNS(WSDL11_NS, "types").item(0); } } \ No newline at end of file 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 2b8e0d2638..df0daf53a7 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 @@ -18,15 +18,13 @@ import java.util.*; -import static com.predic8.membrane.annot.Constants.*; - public class Message extends WSDLElement { - private final List parts = new ArrayList<>(); + private List parts = new ArrayList<>(); public Message(WSDLParserContext ctx, Node node) { super(ctx,node); - this.parts.add(getPart(node)); + this.parts = getParts(node); ctx.getDefinitions().getMessages().add(this); } @@ -42,19 +40,8 @@ public List getParts() { return parts; } - private Part getPart(Node node) { - NodeList children = node.getChildNodes(); - - for (int i = 0; i < children.getLength(); i++) { - Node child = children.item(i); - - if (child.getNodeType() == Node.ELEMENT_NODE - && "part".equals(child.getLocalName()) - && WSDL11_NS.equals(child.getNamespaceURI())) { - return new Part(ctx, child); - } - } - return null; + private List getParts(Node node) { + return instantiateWSDLChildElements(node, "part", Part.class); } @Override diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index cde3377940..e0b80afd91 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -21,6 +21,7 @@ import static com.predic8.membrane.annot.Constants.*; import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.*; +import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.resolveQName; import static org.w3c.dom.Node.*; public class Operation extends WSDLElement { @@ -79,40 +80,36 @@ private List getMessagesByDirection(Node node, Direction direction) { var children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { - Node child = children.item(i); + var child = children.item(i); if (child.getNodeType() == ELEMENT_NODE && direction.matches(child.getLocalName()) && WSDL11_NS.equals(child.getNamespaceURI())) { - Element io = (Element) child; - String messageAttr = io.getAttribute("message"); + var io = (Element) child; + var messageAttr = io.getAttribute("message"); if (messageAttr.isEmpty()) { continue; } - QName messageQName = WSDLParserUtil.resolveQName(messageAttr, io); - Message message = findMessage(messageQName, io.getOwnerDocument()); + var message = findMessage(resolveQName(messageAttr, io), io.getOwnerDocument()); if (message != null) { result.add(message); } } } - return result; } private Message findMessage(QName messageQName, Document document) { var definitions = document.getDocumentElement(); var messages = definitions.getElementsByTagNameNS(WSDL11_NS, "message"); - for (int i = 0; i < messages.getLength(); i++) { - Element message = (Element) messages.item(i); + var message = (Element) messages.item(i); if (messageQName.getLocalPart().equals(message.getAttribute("name"))) { return new Message(ctx, message); } } - return null; } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java index bf7fee1387..82f5efc113 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java @@ -18,6 +18,8 @@ import javax.xml.namespace.*; +import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.resolveQName; + public class Part extends WSDLElement { private final QName element; @@ -42,12 +44,12 @@ private QName getTypeQName(Node node) { return null; } - String typeAttr = partElement.getAttribute("type"); + var typeAttr = partElement.getAttribute("type"); if (typeAttr.isEmpty()) { return null; } - return WSDLParserUtil.resolveQName(typeAttr, partElement); + return resolveQName(typeAttr, partElement); } private QName getElementQName(Node node) { @@ -58,7 +60,7 @@ private QName getElementQName(Node node) { if (elementAttr.isEmpty()) { return null; } - return WSDLParserUtil.resolveQName(elementAttr, element); + return resolveQName(elementAttr, element); } @Override diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java index 8ccdb26b47..0b17276fc2 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java @@ -19,6 +19,7 @@ import java.util.*; import static com.predic8.membrane.annot.Constants.*; +import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.resolveQName; import static org.w3c.dom.Node.*; public class Port extends WSDLElement { @@ -43,10 +44,8 @@ public Binding getBinding() { public Address getAddress(Node node) { var children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - Node child = children.item(i); - + var child = children.item(i); if (child.getNodeType() == ELEMENT_NODE) { var localName = child.getLocalName(); if ("address".equals(localName)) { @@ -61,21 +60,19 @@ public Binding getBinding(Node node) { if (!(node instanceof Element port)) return null; - String bindingAttr = port.getAttribute("binding"); + var bindingAttr = port.getAttribute("binding"); if (bindingAttr.isEmpty()) return null; - var bindingQName = WSDLParserUtil.resolveQName(bindingAttr, port); + var bindingQName = resolveQName(bindingAttr, port); var bindings = getDefinitions().getElementsByTagNameNS(WSDL11_NS, "binding"); for (int i = 0; i < bindings.getLength(); i++) { - Element binding = (Element) bindings.item(i); - + var binding = (Element) bindings.item(i); if (bindingQName.getLocalPart().equals(binding.getAttribute("name"))) { return new Binding(ctx, binding); } } - return null; } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java index f26910cf2a..045cf8e064 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java @@ -14,20 +14,16 @@ package com.predic8.membrane.core.util.wsdl.parser; -import com.predic8.membrane.annot.*; import org.w3c.dom.*; import java.util.*; -import static com.predic8.membrane.annot.Constants.WSDL11_NS; -import static org.w3c.dom.Node.*; - public class PortType extends WSDLElement { private final List operations; public PortType(WSDLParserContext ctx, Node node) { - super(ctx,node); + super(ctx, node); operations = getOperations(node); ctx.getDefinitions().getPortTypes().add(this); } @@ -37,20 +33,6 @@ public List getOperations() { } private List getOperations(Node node) { - var operations = new ArrayList(); - NodeList children = node.getChildNodes(); - - for (int i = 0; i < children.getLength(); i++) { - Node child = children.item(i); - - if (child.getNodeType() == ELEMENT_NODE - && "operation".equals(child.getLocalName()) - && WSDL11_NS.equals(child.getNamespaceURI()) - ) { - operations.add(new Operation(ctx, child)); - } - } - - return operations; + return instantiateWSDLChildElements(node, "operation", Operation.class); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java index 1191fcf103..1a6e74b7e4 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java @@ -18,11 +18,8 @@ import java.util.*; -import static com.predic8.membrane.annot.Constants.*; - public class Service extends WSDLElement { - public static final String PORT = "port"; private final List ports; public Service(WSDLParserContext ctx, Node element) { @@ -36,16 +33,6 @@ public List getPorts() { } public List getPorts(Node service) { - var ports = new ArrayList(); - var children = service.getChildNodes(); - - for (int i = 0; i < children.getLength(); i++) { - var node = children.item(i); - if (node instanceof Element e && - PORT.equals(e.getLocalName()) && WSDL11_NS.equals(e.getNamespaceURI())) { - ports.add(new Port(ctx,e)); - } - } - return ports; + return instantiateWSDLChildElements(service, "port", Port.class); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 93e11d6a90..3cd7af3f87 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -16,13 +16,19 @@ import org.w3c.dom.*; +import java.util.*; +import java.util.function.*; + +import static com.predic8.membrane.annot.Constants.*; +import static org.w3c.dom.Node.*; + public class WSDLElement { - protected final WSDLParserContext ctx; + protected WSDLParserContext ctx; protected final Element element; protected String name; - public WSDLElement(WSDLParserContext ctx,Node node) { + public WSDLElement(WSDLParserContext ctx, Node node) { this.ctx = ctx; if (!(node instanceof Element element)) { throw new RuntimeException("Not an element: " + node.getClass()); @@ -42,4 +48,43 @@ public Element getDefinitions() { return element.getOwnerDocument().getDocumentElement(); } + public Element getElement() { + return element; + } + + protected List instantiateWSDLChildElements(Node node, String name, Class clazz) { + return instantiateWSDLChildElementsInternal(node, name, clazz, WSDLElement::isWSDLElementWithName); + } + + protected List instantiateXSDChildElements(Node node, String name, Class clazz) { + return instantiateWSDLChildElementsInternal(node, name, clazz, WSDLElement::isXSDElementWithName); + } + + protected List instantiateWSDLChildElementsInternal(Node node, String name, Class clazz, BiPredicate elementPredicate) { + List result = new ArrayList<>(); + var children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + var child = children.item(i); + if (elementPredicate.test(child, name)) { + try { + result.add(clazz.getConstructor(WSDLParserContext.class, Node.class).newInstance(ctx, child)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return result; + } + + protected static boolean isWSDLElementWithName(Node child, String name) { + return child.getNodeType() == ELEMENT_NODE + && name.equals(child.getLocalName()) + && WSDL11_NS.equals(child.getNamespaceURI()); + } + + protected static boolean isXSDElementWithName(Node child, String name) { + return child.getNodeType() == ELEMENT_NODE + && name.equals(child.getLocalName()) + && XSD_NS.equals(child.getNamespaceURI()); + } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java index d505c8757b..182521c5cc 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java @@ -14,8 +14,8 @@ package com.predic8.membrane.core.util.wsdl.parser; +import com.predic8.membrane.core.graphql.model.*; import com.predic8.membrane.core.resolver.*; -import com.predic8.membrane.core.util.wsdl.parser.Binding.*; import java.util.*; @@ -24,7 +24,7 @@ public class WSDLParserContext { private final Definitions definitions; private final Resolver resolver; private final String basePath; - private List visitedLocations; + private final List visitedLocations; public WSDLParserContext(Definitions wsdl, Resolver resolver, String basePath, List visitedLocations) { this.definitions = wsdl; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java index 4bf947f9ef..5cef43ba86 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java @@ -23,7 +23,7 @@ public class WSDLParserUtil { public static Element parse(InputStream is) throws Exception { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + var dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); return dbf.newDocumentBuilder().parse(is).getDocumentElement(); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index 496920db09..914068ecd4 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -16,12 +16,15 @@ import com.predic8.membrane.core.util.wsdl.parser.*; import org.jetbrains.annotations.*; +import org.slf4j.*; import org.w3c.dom.*; import java.util.*; public class Import extends AbstractIncludeImport { + private static final Logger log = LoggerFactory.getLogger(Definitions.class); + private final String namespace; public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { @@ -38,6 +41,23 @@ public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { } } + /** + * Imports an import in a schema that is embedded in the WSDL definition. + * Function is typically called from Definitions.importEmbeddedSchemas(). + * @param definitions The WSL that contains the embedded schema. + */ + public void importEmbeddedSchema(Definitions definitions) { + if (isSchemaLocationMissing()) + return; + + log.debug("Importing embedded schema with namespace: {}", namespace); + definitions.getEmbeddedSchema(namespace).ifPresent(s -> schema = s); + } + + private boolean isSchemaLocationMissing() { + return schemaLocation != null && !schemaLocation.isEmpty(); + } + @Override protected Schema getSchema(WSDLParserContext ctx) { if (schemaLocation == null || schemaLocation.isEmpty()) { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java index ea296ed50c..848c4597f6 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -19,9 +19,6 @@ import java.util.*; -import static com.predic8.membrane.annot.Constants.*; -import static org.w3c.dom.Node.*; - public class Schema extends WSDLElement { private final String targetNamespace; @@ -92,9 +89,7 @@ private void parseIncludes(Node node) { for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); - if (child.getNodeType() == ELEMENT_NODE - && "include".equals(child.getLocalName()) - && XSD_NS.equals(child.getNamespaceURI())) { + if (isXSDElementWithName(child, "include")) { new Include(ctx, child, this); } } @@ -105,9 +100,7 @@ private void parseImports(Node node) { for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); - if (child.getNodeType() == ELEMENT_NODE - && "import".equals(child.getLocalName()) - && XSD_NS.equals(child.getNamespaceURI())) { + if (isXSDElementWithName(child, "import")) { new Import(ctx, child,this); } } @@ -116,13 +109,9 @@ private void parseImports(Node node) { private List getSchemaElements(Element schema) { var result = new ArrayList(); var children = schema.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); - - if (child.getNodeType() == ELEMENT_NODE - && "element".equals(child.getLocalName()) - && XSD_NS.equals(child.getNamespaceURI())) { + if (isXSDElementWithName(child, "element")) { result.add(new SchemaElement(ctx, child)); } } diff --git a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java index 06923a3944..c11ad8eb84 100644 --- a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java +++ b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java @@ -118,10 +118,6 @@ private XMLEvent replace(XMLEvent event, String attribute) { start.getNamespaces()); } - public boolean isWsdlFound() { - return wsdlFound; - } - public Map getRelocatingAttributes() { return relocatingAttributes; } diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 394385cc2b..e21d6a157b 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -4,6 +4,8 @@ - Correct YAML example on GitHub README + + # 7.2.0 - Move SOAP samples from tutorials/transformations to ../soap TB @@ -52,7 +54,9 @@ PRIO 2: - PRIO 3: - +- WebServiceExplorerInterceptor + - Support multiple services, ports, ... + - Refactor - JMXExporter: - Tutorial - Example From c5e149f63eb049982bf10ecd158fbfcff554b23a Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 10 Mar 2026 12:25:27 +0100 Subject: [PATCH 13/28] Add WSDL fault handling and improve parser robustness - Added new WSDL file (`calculator-fault.wsdl`) with fault definition for testing. - Updated WSDL parser to support fault direction (`FAULT`) in operations handling. - Enhanced `Relocator` and related classes by removing unnecessary fields and imports for better readability. - Added unit test to validate fault handling in operations. --- .../soap/WebServiceExplorerInterceptor.java | 4 + .../membrane/core/proxies/SOAPProxy.java | 2 - .../core/util/wsdl/parser/Definitions.java | 4 +- .../core/util/wsdl/parser/Operation.java | 4 +- .../membrane/core/ws/relocator/Relocator.java | 9 -- .../core/util/soap/WSDLParserTest.java | 11 +++ .../test/resources/ws/calculator-fault.wsdl | 90 +++++++++++++++++++ 7 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 core/src/test/resources/ws/calculator-fault.wsdl diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java index c5675bb4e6..f7ce8fc9b1 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java @@ -48,6 +48,10 @@ public class WebServiceExplorerInterceptor extends RESTInterceptor implements Pr private String wsdl; private String portName; + + /** + * Field is accessed by reflection + */ private Proxy proxy; public WebServiceExplorerInterceptor() { diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java b/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java index 19122a80d9..cacd9e920d 100644 --- a/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java +++ b/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java @@ -58,8 +58,6 @@ @MCElement(name = "soapProxy", topLevel = true, component = false) public class SOAPProxy extends AbstractServiceProxy { - private static final Logger log = LoggerFactory.getLogger(SOAPProxy.class); - // configuration attributes protected String wsdl; protected String portName; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index bd79bdc28f..02325697b6 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -48,7 +48,7 @@ public enum SOAPVersion { private Definitions(Resolver resolver, String location) throws Exception { super(new WSDLParserContext(null, resolver, location, new ArrayList<>()), read(resolver, location)); ctx = ctx.definitions(this); - parse(ctx, this.getElement()); + parse(this.getElement()); } private static Node read(Resolver resolver, String location) throws Exception { @@ -62,7 +62,7 @@ public static Definitions parse(Resolver resolver, String location) throws Excep return new Definitions(resolver, location); } - private void parse(WSDLParserContext ctx, Element element) { + private void parse(Element element) { targetNamespace = element.getAttribute("targetNamespace"); schemas = getSchemaElements(element); importEmbeddedSchemas(); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index e0b80afd91..239eb2dd38 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -31,7 +31,7 @@ public class Operation extends WSDLElement { private final List faults; public enum Direction { - INPUT, OUTPUT; + INPUT, OUTPUT, FAULT; public boolean matches(String s) { return name().equalsIgnoreCase(s); @@ -72,7 +72,7 @@ private List getOutputs(Node node) { } private List getFaults(Node node) { - return new ArrayList<>(); // @TODO + return getMessagesByDirection(node, FAULT); } private List getMessagesByDirection(Node node, Direction direction) { diff --git a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java index c11ad8eb84..303810608b 100644 --- a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java +++ b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java @@ -25,8 +25,6 @@ import java.net.*; import java.util.*; -import static com.predic8.membrane.annot.Constants.*; - @NotThreadSafe public class Relocator { private static final Logger log = LoggerFactory.getLogger(Relocator.class.getName()); @@ -42,8 +40,6 @@ public class Relocator { private final Map relocatingAttributes = new HashMap<>(); - private boolean wsdlFound; - public Relocator(OutputStreamWriter osw, String protocol, String host, int port, String contextPath, PathRewriter pathRewriter) throws Exception { this.writer = XMLOutputFactory.newInstance().createXMLEventWriter(osw); @@ -91,11 +87,6 @@ private XMLEvent process(XMLEventReader parser) throws XMLStreamException { if (!event.isStartElement()) return event; - if (getElementName(event).getNamespaceURI().equals(WSDL_SOAP11_NS) - || getElementName(event).getNamespaceURI().equals(WSDL_SOAP12_NS)) { - wsdlFound = true; - } - return relocatingAttributes.entrySet().stream() .filter(e -> shouldProcess(e, event)) .findFirst() 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 8991d87fd6..5d23c5fab5 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 @@ -105,4 +105,15 @@ void abstractWSDL() throws Exception { assertEquals(1, Definitions.parse(new ResolverMap(), "classpath:/ws/abstract-service-no-binding.wsdl").getPortTypes().size()); } + @Test + void fault() throws Exception{ + var dn = Definitions.parse(new ResolverMap(),"classpath:/ws/calculator-fault.wsdl"); + var pt = dn.getPortTypes().getFirst(); + var fault = pt.getOperations().getFirst().getFaults().getFirst(); + assertNotNull(fault); + assertEquals("DivideByZeroFault", fault.getPart().getElementQName().getLocalPart()); + } + + + } \ No newline at end of file diff --git a/core/src/test/resources/ws/calculator-fault.wsdl b/core/src/test/resources/ws/calculator-fault.wsdl new file mode 100644 index 0000000000..000fbd6a16 --- /dev/null +++ b/core/src/test/resources/ws/calculator-fault.wsdl @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 0ef1e5bb8e381abda0fc1e8f7a07ae0a10bf40db Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 10 Mar 2026 14:56:16 +0100 Subject: [PATCH 14/28] Refactor WSDL parser: streamline imports, enhance schema handling, and simplify logic --- .../core/util/wsdl/parser/Binding.java | 37 +++++++++---------- .../core/util/wsdl/parser/Definitions.java | 9 +++-- .../core/util/wsdl/parser/Operation.java | 28 ++------------ .../core/util/wsdl/parser/WSDLElement.java | 21 ++++++++--- .../parser/schema/AbstractIncludeImport.java | 9 +---- .../core/util/wsdl/parser/schema/Import.java | 9 +---- .../core/util/wsdl/parser/schema/Include.java | 12 +----- .../core/util/wsdl/parser/schema/Schema.java | 36 ++++-------------- .../schemavalidation/SOAPFaultTest.java | 2 +- 9 files changed, 55 insertions(+), 108 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index 6ed3873bfc..12160b299a 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -15,14 +15,16 @@ package com.predic8.membrane.core.util.wsdl.parser; import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; +import org.jetbrains.annotations.*; import org.w3c.dom.*; import java.util.*; import static com.predic8.membrane.annot.Constants.*; -import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.DOCUMENT; -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; +import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.*; +import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*; +import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.*; +import static java.util.Optional.empty; import static org.w3c.dom.Node.*; public class Binding extends WSDLElement { @@ -48,7 +50,7 @@ public Binding(WSDLParserContext ctx, Node node) { super(ctx,node); ctx.getDefinitions().getBindings().add(this); operations = getBindingOperations(node); - portType = getPortType(node); + portType = getPortType(node).orElseThrow(() -> new WSDLParserException("No portType found for binding: " + name)); } public Style getStyle() { @@ -95,32 +97,27 @@ private List getBindingOperations(Node node) { private static Style getStyle(Node child) { var node = child.getAttributes().getNamedItem("style"); if (node == null) { + // Style is missing, assume document style. return DOCUMENT; } return Style.fromString(node.getNodeValue()); } - private PortType getPortType(Node node) { + private Optional getPortType(Node node) { if (!(node instanceof Element bindingElement)) { - return null; + return empty(); } + var portTypeQName = resolveQName(getType(bindingElement), bindingElement); + return ctx.getDefinitions().getPortTypes().stream() + .filter(pt -> portTypeQName.getLocalPart().equals(pt.getName())) + .findFirst(); + } + private @NotNull String getType(Element bindingElement) { var type = bindingElement.getAttribute("type"); if (type.isEmpty()) { - return null; - } - - var portTypeQName = WSDLParserUtil.resolveQName(type, bindingElement); - - var definitions = bindingElement.getOwnerDocument().getDocumentElement(); - var portTypes = definitions.getElementsByTagNameNS(WSDL11_NS, "portType"); - - for (int i = 0; i < portTypes.getLength(); i++) { - var portType = (Element) portTypes.item(i); - if (portTypeQName.getLocalPart().equals(portType.getAttribute("name"))) { - return new PortType(ctx, portType); - } + throw new WSDLParserException("No type attribute found for binding: " + name); } - return null; + return type; } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 02325697b6..4c84bca6e8 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -67,10 +67,9 @@ private void parse(Element element) { schemas = getSchemaElements(element); importEmbeddedSchemas(); + instantiateWSDLChildElements(element, "message", Message.class); + instantiateWSDLChildElements(element, "portType", PortType.class); instantiateWSDLChildElements(element, "service", Service.class); - if (services.isEmpty()) { - instantiateWSDLChildElements(element, "portType", PortType.class); - } } /** @@ -99,6 +98,10 @@ public List getMessages() { return messages; } + public Optional findMessage(String name) { + return messages.stream().filter(m -> m.getName().equals(name)).findFirst(); + } + public List getPortTypes() { return portTypes; } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index 239eb2dd38..998cdb78cb 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -16,13 +16,10 @@ import org.w3c.dom.*; -import javax.xml.namespace.*; import java.util.*; -import static com.predic8.membrane.annot.Constants.*; import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.*; import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.resolveQName; -import static org.w3c.dom.Node.*; public class Operation extends WSDLElement { @@ -81,35 +78,16 @@ private List getMessagesByDirection(Node node, Direction direction) { for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); - - if (child.getNodeType() == ELEMENT_NODE - && direction.matches(child.getLocalName()) - && WSDL11_NS.equals(child.getNamespaceURI())) { - + if (isWSDLElement(child) && direction.matches(child.getLocalName())) { var io = (Element) child; var messageAttr = io.getAttribute("message"); if (messageAttr.isEmpty()) { continue; } - - var message = findMessage(resolveQName(messageAttr, io), io.getOwnerDocument()); - if (message != null) { - result.add(message); - } + ctx.getDefinitions().findMessage(resolveQName(messageAttr, io).getLocalPart()) + .ifPresent(m -> result.add(m)); } } return result; } - - private Message findMessage(QName messageQName, Document document) { - var definitions = document.getDocumentElement(); - var messages = definitions.getElementsByTagNameNS(WSDL11_NS, "message"); - for (int i = 0; i < messages.getLength(); i++) { - var message = (Element) messages.item(i); - if (messageQName.getLocalPart().equals(message.getAttribute("name"))) { - return new Message(ctx, message); - } - } - return null; - } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 3cd7af3f87..c796f1c633 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -16,6 +16,7 @@ import org.w3c.dom.*; +import javax.xml.namespace.*; import java.util.*; import java.util.function.*; @@ -77,14 +78,22 @@ protected List instantiateWSDLChildElementsInternal(N } protected static boolean isWSDLElementWithName(Node child, String name) { - return child.getNodeType() == ELEMENT_NODE - && name.equals(child.getLocalName()) - && WSDL11_NS.equals(child.getNamespaceURI()); + return isWSDLElement(child) && name.equals(child.getLocalName()); } protected static boolean isXSDElementWithName(Node child, String name) { - return child.getNodeType() == ELEMENT_NODE - && name.equals(child.getLocalName()) - && XSD_NS.equals(child.getNamespaceURI()); + return isXSDElement(child) && name.equals(child.getLocalName()); + } + + protected static boolean isWSDLElement(Node node) { + return node.getNodeType() == ELEMENT_NODE && WSDL11_NS.equals(node.getNamespaceURI()); + } + + protected static boolean isXSDElement(Node node) { + return node.getNodeType() == ELEMENT_NODE && XSD_NS.equals(node.getNamespaceURI()); + } + + protected boolean hasLocalName(QName qn) { + return qn.getLocalPart().equals(name); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index e459b674f1..247777fc0a 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -23,18 +23,15 @@ public abstract class AbstractIncludeImport extends WSDLElement { protected String schemaLocation; - protected Schema referensingSchema; + protected Schema referencingSchema; protected Schema schema; - public AbstractIncludeImport(WSDLParserContext ctx, Node node, Schema referensingSchema) { + public AbstractIncludeImport(WSDLParserContext ctx, Node node) { super(ctx, node); - this.referensingSchema = referensingSchema; schemaLocation = getSchemaLocation(node); schema = getSchema(ctx); } - protected abstract void registerLocation(String normalizedLocation); - public Schema getSchema() { return schema; } @@ -47,8 +44,6 @@ protected Schema getSchema(WSDLParserContext ctx) { if (ctx.getVisitedLocations().contains(resolved)) return null; - registerLocation(resolved); - try (var is = ctx.getResolver().resolve(resolved)) { return new Schema(ctx.basePath(resolved), WSDLParserUtil.parse(is)); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index 914068ecd4..a3b5ad9681 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -27,8 +27,8 @@ public class Import extends AbstractIncludeImport { private final String namespace; - public Import(WSDLParserContext ctx, Node node, Schema referensingSchema) { - super(ctx, node, referensingSchema); + public Import(WSDLParserContext ctx, Node node) { + super(ctx, node); namespace = getNamespace(node); @@ -61,7 +61,6 @@ private boolean isSchemaLocationMissing() { @Override protected Schema getSchema(WSDLParserContext ctx) { if (schemaLocation == null || schemaLocation.isEmpty()) { - registerLocation(""); return null; } return super.getSchema(ctx); @@ -71,10 +70,6 @@ public void setSchema(Schema schema) { this.schema = schema; } - @Override - protected void registerLocation(String normalizedLocation) { - referensingSchema.getImports().add(this); - } private @NotNull String getNamespace(Node node) { return ((Element) node).getAttribute("namespace"); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java index 5fd2b97e1a..6f193cae49 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Include.java @@ -19,15 +19,7 @@ public class Include extends AbstractIncludeImport { - public Include(WSDLParserContext ctx, Node node, Schema referensingSchema) { - super(ctx,node,referensingSchema); - - // Copy all elements from the imported Schema into the importing - referensingSchema.include(schema); - } - - @Override - protected void registerLocation(String normalizedLocation) { - referensingSchema.getIncludes().add(this); + public Include(WSDLParserContext ctx, Node node) { + super(ctx,node); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java index 848c4597f6..3ea42779a1 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -29,8 +29,8 @@ public class Schema extends WSDLElement { private final Element schema; private final List schemaElements; - private final List imports = new ArrayList<>(); - private final List includes = new ArrayList<>(); + private List imports = new ArrayList<>(); + private List includes = new ArrayList<>(); public Schema(WSDLParserContext ctx, Node node) { super(ctx, node); @@ -80,42 +80,20 @@ private String getTargetNamespace(Node node) { if (!(node instanceof Element schema)) { return null; } - String tns = schema.getAttribute("targetNamespace"); + var tns = schema.getAttribute("targetNamespace"); return (tns.isEmpty()) ? null : tns; } private void parseIncludes(Node node) { - var children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - var child = children.item(i); - - if (isXSDElementWithName(child, "include")) { - new Include(ctx, child, this); - } - } + includes = instantiateXSDChildElements(node,"include",Include.class); + includes.forEach(i -> include(i.getSchema())); } private void parseImports(Node node) { - var children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - var child = children.item(i); - - if (isXSDElementWithName(child, "import")) { - new Import(ctx, child,this); - } - } + imports = instantiateXSDChildElements(node,"import",Import.class); } private List getSchemaElements(Element schema) { - var result = new ArrayList(); - var children = schema.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - var child = children.item(i); - if (isXSDElementWithName(child, "element")) { - result.add(new SchemaElement(ctx, child)); - } - } - return result; + return instantiateXSDChildElements(schema,"element", SchemaElement.class); } - } diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPFaultTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPFaultTest.java index 0d95a7f115..e0e399295a 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPFaultTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPFaultTest.java @@ -36,7 +36,7 @@ public class SOAPFaultTest { @Test public void testValidateFaults() { - ValidatorInterceptor i = createValidatorInterceptor(false); + var i = createValidatorInterceptor(false); Exchange exc = createFaultExchange(); assertEquals(ABORT, i.handleResponse(exc)); assertContainsNot("secret", exc.getResponse().toString()); From c3b08036ae498e3bfb983e0eee5c2db1b28a56cc Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 10 Mar 2026 14:58:52 +0100 Subject: [PATCH 15/28] Remove unused fields and simplify logic in WSDL parser classes - Deleted redundant `referencingSchema` field in `AbstractIncludeImport`. - Removed `hasLocalName` method from `WSDLElement`. - Simplified lambda usage in `Operation` for better readability. --- .../com/predic8/membrane/core/util/wsdl/parser/Operation.java | 2 +- .../predic8/membrane/core/util/wsdl/parser/WSDLElement.java | 4 ---- .../core/util/wsdl/parser/schema/AbstractIncludeImport.java | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index 998cdb78cb..3adf594b00 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -85,7 +85,7 @@ private List getMessagesByDirection(Node node, Direction direction) { continue; } ctx.getDefinitions().findMessage(resolveQName(messageAttr, io).getLocalPart()) - .ifPresent(m -> result.add(m)); + .ifPresent(result::add); } } return result; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index c796f1c633..91881b6bfa 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -92,8 +92,4 @@ protected static boolean isWSDLElement(Node node) { protected static boolean isXSDElement(Node node) { return node.getNodeType() == ELEMENT_NODE && XSD_NS.equals(node.getNamespaceURI()); } - - protected boolean hasLocalName(QName qn) { - return qn.getLocalPart().equals(name); - } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index 247777fc0a..cc212f1145 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -23,7 +23,6 @@ public abstract class AbstractIncludeImport extends WSDLElement { protected String schemaLocation; - protected Schema referencingSchema; protected Schema schema; public AbstractIncludeImport(WSDLParserContext ctx, Node node) { From af4b1eb299e804a0933c33d9c9f7ab15b70aeaf2 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 10 Mar 2026 17:40:08 +0100 Subject: [PATCH 16/28] Refactor WSDL parser: simplify attribute handling, improve SOAP version and style detection, and add unit tests - Replaced direct field assignments with `getAttribute` for cleaner attribute handling in `BindingOperation` and `Address`. - Added `BindingStyle` class to improve SOAP version and style handling logic. - Streamlined `Node` processing in `Binding` and `WSDLElement`. - Introduced helper methods for SOAP namespace and element checks. - Added new unit tests (`WSDLParserUtilTest`) for utility methods and `rpcStyle` test case for style detection. --- .../core/util/wsdl/parser/Address.java | 16 +---- .../core/util/wsdl/parser/Binding.java | 63 ++++--------------- .../util/wsdl/parser/BindingOperation.java | 12 +--- .../core/util/wsdl/parser/BindingStyle.java | 32 ++++++++++ .../core/util/wsdl/parser/Definitions.java | 3 +- .../core/util/wsdl/parser/WSDLElement.java | 30 +++++++-- .../core/util/wsdl/parser/WSDLParserUtil.java | 11 ++++ .../core/util/soap/WSDLParserTest.java | 8 ++- .../util/wsdl/parser/WSDLParserUtilTest.java | 15 +++++ 9 files changed, 107 insertions(+), 83 deletions(-) create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingStyle.java create mode 100644 core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtilTest.java diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java index 69d7b3c60d..3fec2b1a2f 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Address.java @@ -18,21 +18,11 @@ public class Address extends WSDLElement { - private final String location; - - public Address(WSDLParserContext ctx,Node node) { - super(ctx,node); - location = getLocation(node); + public Address(WSDLParserContext ctx, Node node) { + super(ctx, node); } public String getLocation() { - return location; - } - - public String getLocation(Node node) { - if (node instanceof Element e) { - return e.getAttribute("location"); - } - return null; + return getAttribute("location"); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index 12160b299a..c5ba0c07f9 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -15,22 +15,15 @@ package com.predic8.membrane.core.util.wsdl.parser; import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; -import org.jetbrains.annotations.*; import org.w3c.dom.*; import java.util.*; -import static com.predic8.membrane.annot.Constants.*; -import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.*; -import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*; import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.*; -import static java.util.Optional.empty; -import static org.w3c.dom.Node.*; +import static java.util.Optional.*; public class Binding extends WSDLElement { - private SOAPVersion soapVersion; - public enum Style { RPC, DOCUMENT; @@ -42,19 +35,24 @@ public static Style fromString(String style) { } } - private Style style; + private SOAPVersion soapVersion; + private final List operations; + private final BindingStyle bindingStyle; private final PortType portType; public Binding(WSDLParserContext ctx, Node node) { - super(ctx,node); + super(ctx, node); ctx.getDefinitions().getBindings().add(this); operations = getBindingOperations(node); portType = getPortType(node).orElseThrow(() -> new WSDLParserException("No portType found for binding: " + name)); + soapVersion = getBindingStyle().getSoapVersion(); + ctx.getDefinitions().addSoapVersion(soapVersion); + bindingStyle = getBindingStyle(); } public Style getStyle() { - return style; + return bindingStyle.getStyle(); } public List getOperations() { @@ -70,54 +68,19 @@ public SOAPVersion getSoapVersion() { } private List getBindingOperations(Node node) { - var result = new ArrayList(); - var children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - var child = children.item(i); - - if (isWSDLElementWithName(child, "operation")) { - result.add(new BindingOperation(ctx, child)); - } - - if (child.getNodeType() == ELEMENT_NODE - && "binding".equals(child.getLocalName())) { - style = getStyle(child); - if (WSDL_SOAP11_NS.equals(child.getNamespaceURI())) { - soapVersion = SOAP_11; - ctx.getDefinitions().addSoapVersion(soapVersion); - } else if (WSDL_SOAP12_NS.equals(child.getNamespaceURI())) { - soapVersion = SOAP_12; - ctx.getDefinitions().addSoapVersion(soapVersion); - } - } - } - return result; + return instantiateWSDLChildElements(node, "operation", BindingOperation.class); } - private static Style getStyle(Node child) { - var node = child.getAttributes().getNamedItem("style"); - if (node == null) { - // Style is missing, assume document style. - return DOCUMENT; - } - return Style.fromString(node.getNodeValue()); + private BindingStyle getBindingStyle() { + return instantiateChildElements(element, "binding", BindingStyle.class).getFirst(); } private Optional getPortType(Node node) { if (!(node instanceof Element bindingElement)) { return empty(); } - var portTypeQName = resolveQName(getType(bindingElement), bindingElement); return ctx.getDefinitions().getPortTypes().stream() - .filter(pt -> portTypeQName.getLocalPart().equals(pt.getName())) + .filter(pt -> getLocalName(getAttribute("type")).equals(pt.getName())) .findFirst(); } - - private @NotNull String getType(Element bindingElement) { - var type = bindingElement.getAttribute("type"); - if (type.isEmpty()) { - throw new WSDLParserException("No type attribute found for binding: " + name); - } - return type; - } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java index 448c0c384c..e33775dceb 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java @@ -18,21 +18,11 @@ public class BindingOperation extends WSDLElement{ - private final String soapAction; - public BindingOperation(WSDLParserContext ctx, Node node) { super(ctx,node); - soapAction = getSoapAction(node); - } - - private String getSoapAction(Node node) { - var n = node.getAttributes().getNamedItem("soapAction"); - if (n == null) - return ""; - return n.getNodeValue(); } public String getSoapAction() { - return soapAction; + return getAttribute("soapAction"); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingStyle.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingStyle.java new file mode 100644 index 0000000000..e1766cb290 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingStyle.java @@ -0,0 +1,32 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import com.predic8.membrane.core.util.wsdl.parser.Binding.*; +import com.predic8.membrane.core.util.wsdl.parser.Definitions.*; +import org.slf4j.*; +import org.w3c.dom.*; + +import static com.predic8.membrane.core.util.wsdl.parser.Definitions.SOAPVersion.*; + +public class BindingStyle extends WSDLElement { + + private static final Logger log = LoggerFactory.getLogger(BindingStyle.class); + + public BindingStyle(WSDLParserContext ctx, Node node) { + super(ctx, node); + } + + public Style getStyle() { + return Style.fromString(getAttribute("style")); + } + + public SOAPVersion getSoapVersion() { + if (isWsdlSoap11Element()) { + return SOAP_11; + } + if (isWsdlSoap12Element()) { + return SOAP_12; + } + log.info("Unknown protocol of binding element in namespace: {}", this.element.getNamespaceURI()); + return UNKNOWN; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 4c84bca6e8..770eba76b8 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -32,7 +32,7 @@ public class Definitions extends WSDLElement { private static final Logger log = LoggerFactory.getLogger(Definitions.class); public enum SOAPVersion { - SOAP_11, SOAP_12 + SOAP_11, SOAP_12, UNKNOWN; } private List schemas = new ArrayList<>(); @@ -66,7 +66,6 @@ private void parse(Element element) { targetNamespace = element.getAttribute("targetNamespace"); schemas = getSchemaElements(element); importEmbeddedSchemas(); - instantiateWSDLChildElements(element, "message", Message.class); instantiateWSDLChildElements(element, "portType", PortType.class); instantiateWSDLChildElements(element, "service", Service.class); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 91881b6bfa..80fb1dfecc 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -14,9 +14,9 @@ package com.predic8.membrane.core.util.wsdl.parser; +import org.jetbrains.annotations.*; import org.w3c.dom.*; -import javax.xml.namespace.*; import java.util.*; import java.util.function.*; @@ -53,6 +53,10 @@ public Element getElement() { return element; } + protected List instantiateChildElements(Node node, String name, Class clazz) { + return instantiateWSDLChildElementsInternal(node, name, clazz, WSDLElement::isElementWithName); + } + protected List instantiateWSDLChildElements(Node node, String name, Class clazz) { return instantiateWSDLChildElementsInternal(node, name, clazz, WSDLElement::isWSDLElementWithName); } @@ -77,12 +81,16 @@ protected List instantiateWSDLChildElementsInternal(N return result; } - protected static boolean isWSDLElementWithName(Node child, String name) { - return isWSDLElement(child) && name.equals(child.getLocalName()); + protected static boolean isElementWithName(Node node, String name) { + return node.getNodeType() == ELEMENT_NODE && name.equals(node.getLocalName()); } - protected static boolean isXSDElementWithName(Node child, String name) { - return isXSDElement(child) && name.equals(child.getLocalName()); + protected static boolean isWSDLElementWithName(Node node, String name) { + return isWSDLElement(node) && name.equals(node.getLocalName()); + } + + protected static boolean isXSDElementWithName(Node node, String name) { + return isXSDElement(node) && name.equals(node.getLocalName()); } protected static boolean isWSDLElement(Node node) { @@ -92,4 +100,16 @@ protected static boolean isWSDLElement(Node node) { protected static boolean isXSDElement(Node node) { return node.getNodeType() == ELEMENT_NODE && XSD_NS.equals(node.getNamespaceURI()); } + + protected boolean isWsdlSoap12Element() { + return WSDL_SOAP12_NS.equals(element.getNamespaceURI()); + } + + protected boolean isWsdlSoap11Element() { + return WSDL_SOAP11_NS.equals(element.getNamespaceURI()); + } + + protected @NotNull String getAttribute(String name) { + return element.getAttribute(name); + } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java index 5cef43ba86..05ff64ba58 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java @@ -28,6 +28,17 @@ public static Element parse(InputStream is) throws Exception { return dbf.newDocumentBuilder().parse(is).getDocumentElement(); } + /** + * + * @param value QName with prefix like tns:MyPortType or just MyPortType + * @return + */ + public static String getLocalName(String value) { + var pos = value.indexOf(':'); + if (pos == -1) + return value; + return value.substring(pos+1); + } public static QName resolveQName(String value, Node context) { 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 5d23c5fab5..b215864f36 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 @@ -106,7 +106,7 @@ void abstractWSDL() throws Exception { } @Test - void fault() throws Exception{ + void fault() throws Exception { var dn = Definitions.parse(new ResolverMap(),"classpath:/ws/calculator-fault.wsdl"); var pt = dn.getPortTypes().getFirst(); var fault = pt.getOperations().getFirst().getFaults().getFirst(); @@ -114,6 +114,10 @@ void fault() throws Exception{ assertEquals("DivideByZeroFault", fault.getPart().getElementQName().getLocalPart()); } - + @Test + void rpcStyle() throws Exception { + var dn = Definitions.parse(new ResolverMap(),"classpath:/validation/inline-anytype.wsdl"); + assertEquals(RPC, dn.getBindings().getFirst().getStyle()); + } } \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtilTest.java b/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtilTest.java new file mode 100644 index 0000000000..a0b484d4fe --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtilTest.java @@ -0,0 +1,15 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +class WSDLParserUtilTest { + + @Test + void typeName() { + assertEquals("myPortType", WSDLParserUtil.getLocalName("myPortType")); + assertEquals("myPortType", WSDLParserUtil.getLocalName("tns:myPortType")); + } + +} \ No newline at end of file From 99d8df0c69a73264b860cd5808efa9b7a7a0fc33 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 10 Mar 2026 17:44:49 +0100 Subject: [PATCH 17/28] Simplify `Binding` class by removing unused fields and refining `PortType` and `SOAPVersion` handling logic --- .../core/util/wsdl/parser/Binding.java | 21 ++++--------------- .../core/util/wsdl/parser/Definitions.java | 2 +- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index c5ba0c07f9..f7064390f1 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -20,7 +20,6 @@ import java.util.*; import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.*; -import static java.util.Optional.*; public class Binding extends WSDLElement { @@ -35,19 +34,14 @@ public static Style fromString(String style) { } } - private SOAPVersion soapVersion; - private final List operations; private final BindingStyle bindingStyle; - private final PortType portType; public Binding(WSDLParserContext ctx, Node node) { super(ctx, node); ctx.getDefinitions().getBindings().add(this); operations = getBindingOperations(node); - portType = getPortType(node).orElseThrow(() -> new WSDLParserException("No portType found for binding: " + name)); - soapVersion = getBindingStyle().getSoapVersion(); - ctx.getDefinitions().addSoapVersion(soapVersion); + ctx.getDefinitions().addSoapVersion(getSoapVersion()); bindingStyle = getBindingStyle(); } @@ -59,12 +53,8 @@ public List getOperations() { return operations; } - public PortType getPortType() { - return portType; - } - public SOAPVersion getSoapVersion() { - return soapVersion; + return getBindingStyle().getSoapVersion(); } private List getBindingOperations(Node node) { @@ -75,12 +65,9 @@ private BindingStyle getBindingStyle() { return instantiateChildElements(element, "binding", BindingStyle.class).getFirst(); } - private Optional getPortType(Node node) { - if (!(node instanceof Element bindingElement)) { - return empty(); - } + public PortType getPortType() { return ctx.getDefinitions().getPortTypes().stream() .filter(pt -> getLocalName(getAttribute("type")).equals(pt.getName())) - .findFirst(); + .findFirst().orElseThrow(() -> new WSDLParserException("No portType found for binding: " + name)); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 770eba76b8..4ab41dd799 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -32,7 +32,7 @@ public class Definitions extends WSDLElement { private static final Logger log = LoggerFactory.getLogger(Definitions.class); public enum SOAPVersion { - SOAP_11, SOAP_12, UNKNOWN; + SOAP_11, SOAP_12, UNKNOWN } private List schemas = new ArrayList<>(); From 4a7dd9ce4a5735012f8d4c78fe31919687368ccb Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 10 Mar 2026 23:19:34 +0100 Subject: [PATCH 18/28] Refactor WSDL parser classes for improved readability and maintainability - Simplified object initialization and attribute handling across WSDL parser classes. - Removed redundant fields (`Type`, `Service`, `PortType`, etc.) and unified child node processing logic. - Introduced `instantiateChildren()` and related helper methods for cleaner element instantiation. - Enhanced `Binding`, `Operation`, `Message`, and related classes: reduced complexity by eliminating unnecessary lists and utility methods. - Added `ProtocolOperation` class to handle SOAP operation attributes (e.g., `soapAction`). - Updated and added unit tests to validate changes. --- .../core/util/wsdl/parser/Binding.java | 25 ++++----- .../util/wsdl/parser/BindingOperation.java | 4 +- .../core/util/wsdl/parser/Definitions.java | 23 ++++---- .../core/util/wsdl/parser/Message.java | 14 ++--- .../core/util/wsdl/parser/Operation.java | 38 +++----------- .../membrane/core/util/wsdl/parser/Part.java | 42 +++------------ .../membrane/core/util/wsdl/parser/Port.java | 52 +++---------------- .../core/util/wsdl/parser/PortType.java | 10 +--- .../util/wsdl/parser/ProtocolOperation.java | 16 ++++++ .../core/util/wsdl/parser/Service.java | 10 +--- .../membrane/core/util/wsdl/parser/Type.java | 23 -------- .../core/util/wsdl/parser/WSDLElement.java | 39 +++++++++++--- .../util/wsdl/parser/WSDLParserException.java | 4 -- .../core/util/wsdl/parser/WSDLParserUtil.java | 2 + .../core/util/wsdl/parser/schema/Import.java | 21 +++----- .../core/util/wsdl/parser/schema/Schema.java | 47 ++++------------- .../core/util/soap/WSDLParserTest.java | 5 ++ 17 files changed, 122 insertions(+), 253 deletions(-) create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/ProtocolOperation.java delete mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index f7064390f1..e95b43fbfa 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -34,35 +34,30 @@ public static Style fromString(String style) { } } - private final List operations; - private final BindingStyle bindingStyle; - public Binding(WSDLParserContext ctx, Node node) { super(ctx, node); - ctx.getDefinitions().getBindings().add(this); - operations = getBindingOperations(node); - ctx.getDefinitions().addSoapVersion(getSoapVersion()); - bindingStyle = getBindingStyle(); } public Style getStyle() { - return bindingStyle.getStyle(); - } - - public List getOperations() { - return operations; + return getBindingStyle().getStyle(); } public SOAPVersion getSoapVersion() { return getBindingStyle().getSoapVersion(); } - private List getBindingOperations(Node node) { - return instantiateWSDLChildElements(node, "operation", BindingOperation.class); + public BindingOperation getBindingOperation(String name) { + return getBindingOperations().stream() + .filter(bo -> bo.getName().equals(name)) + .findFirst().orElseThrow(() -> new WSDLParserException("No bindingOperation found for name: " + name)); + } + + public List getBindingOperations() { + return instantiateWSDLChildren("operation", BindingOperation.class); } private BindingStyle getBindingStyle() { - return instantiateChildElements(element, "binding", BindingStyle.class).getFirst(); + return instantiateChildren( "binding", BindingStyle.class).getFirst(); } public PortType getPortType() { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java index e33775dceb..7d5ce81d11 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java @@ -23,6 +23,8 @@ public BindingOperation(WSDLParserContext ctx, Node node) { } public String getSoapAction() { - return getAttribute("soapAction"); + return instantiateChild("operation",ProtocolOperation.class).orElseThrow(() -> + new WSDLParserException("No operation found for binding operation: " + name) + ).getSoapAction(); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 4ab41dd799..3f2f1a6dec 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -20,6 +20,7 @@ import org.w3c.dom.*; import java.util.*; +import java.util.stream.*; import static com.predic8.membrane.annot.Constants.*; @@ -36,14 +37,14 @@ public enum SOAPVersion { } private List schemas = new ArrayList<>(); - private final List messages = new ArrayList<>(); - private final List portTypes = new ArrayList<>(); - private final List bindings = new ArrayList<>(); - private final List services = new ArrayList<>(); + private List messages = new ArrayList<>(); + private List portTypes = new ArrayList<>(); + private List bindings = new ArrayList<>(); + private List services = new ArrayList<>(); private String targetNamespace; - private final Set soapVersions = new HashSet<>(); + private Set soapVersions = new HashSet<>(); private Definitions(Resolver resolver, String location) throws Exception { super(new WSDLParserContext(null, resolver, location, new ArrayList<>()), read(resolver, location)); @@ -63,12 +64,14 @@ public static Definitions parse(Resolver resolver, String location) throws Excep } private void parse(Element element) { - targetNamespace = element.getAttribute("targetNamespace"); + targetNamespace = getAttribute("targetNamespace"); schemas = getSchemaElements(element); importEmbeddedSchemas(); - instantiateWSDLChildElements(element, "message", Message.class); - instantiateWSDLChildElements(element, "portType", PortType.class); - instantiateWSDLChildElements(element, "service", Service.class); + messages = instantiateWSDLElements(element, "message", Message.class); + portTypes = instantiateWSDLElements(element, "portType", PortType.class); + bindings = instantiateWSDLElements(element, "binding", Binding.class); + services = instantiateWSDLElements(element, "service", Service.class); + soapVersions = bindings.stream().map(b -> b.getSoapVersion()).collect(Collectors.toSet()); } /** @@ -130,7 +133,7 @@ public void addSoapVersion(SOAPVersion soapVersion) { } private List getSchemaElements(Element wsdl) { - return instantiateXSDChildElements(getTypes(wsdl), "schema", Schema.class); + return instantiateXSDElements(getTypes(wsdl), "schema", Schema.class); } private static Node getTypes(Element wsdl) { 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 df0daf53a7..df4c4a4fae 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 @@ -20,12 +20,8 @@ public class Message extends WSDLElement { - private List parts = new ArrayList<>(); - public Message(WSDLParserContext ctx, Node node) { super(ctx,node); - this.parts = getParts(node); - ctx.getDefinitions().getMessages().add(this); } /** @@ -33,19 +29,15 @@ public Message(WSDLParserContext ctx, Node node) { * @return */ public Part getPart() { - return parts.getFirst(); + return getParts().getFirst(); } public List getParts() { - return parts; - } - - private List getParts(Node node) { - return instantiateWSDLChildElements(node, "part", Part.class); + return instantiateWSDLChildren("part", Part.class); } @Override public String toString() { - return "Message [parts=" + parts + "]"; + return "Message [parts=%s]".formatted(getParts()); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index 3adf594b00..31ebf85cba 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -19,14 +19,9 @@ import java.util.*; import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.*; -import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.resolveQName; public class Operation extends WSDLElement { - private final List inputs; - private final List outputs; - private final List faults; - public enum Direction { INPUT, OUTPUT, FAULT; @@ -37,44 +32,23 @@ public boolean matches(String s) { public Operation(WSDLParserContext ctx, Node node) { super(ctx,node); - inputs = getInputs(node); - outputs = getOutputs(node); - faults = getFaults(node); } public List getInputs() { - return inputs; + return getMessagesByDirection(INPUT); } public List getOutputs() { - return outputs; - } - - public List getMessagesByDirection(Direction direction) { - if (direction == INPUT) - return inputs; - return outputs; + return getMessagesByDirection(OUTPUT); } public List getFaults() { - return faults; - } - - private List getInputs(Node node) { - return getMessagesByDirection(node, INPUT); + return getMessagesByDirection(FAULT); } - private List getOutputs(Node node) { - return getMessagesByDirection(node, OUTPUT); - } - - private List getFaults(Node node) { - return getMessagesByDirection(node, FAULT); - } - - private List getMessagesByDirection(Node node, Direction direction) { + public List getMessagesByDirection(Direction direction) { var result = new ArrayList(); - var children = node.getChildNodes(); + var children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); @@ -84,7 +58,7 @@ private List getMessagesByDirection(Node node, Direction direction) { if (messageAttr.isEmpty()) { continue; } - ctx.getDefinitions().findMessage(resolveQName(messageAttr, io).getLocalPart()) + ctx.getDefinitions().findMessage(WSDLParserUtil.resolveQName(messageAttr, io).getLocalPart()) .ifPresent(result::add); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java index 82f5efc113..addf8988f9 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Part.java @@ -14,57 +14,31 @@ package com.predic8.membrane.core.util.wsdl.parser; +import org.jetbrains.annotations.*; import org.w3c.dom.*; import javax.xml.namespace.*; -import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.resolveQName; - public class Part extends WSDLElement { - private final QName element; - private final QName type; - public Part(WSDLParserContext ctx, Node node) { - super(ctx,node); - this.element = getElementQName(node); - this.type = getTypeQName(node); - } - - public QName getElementQName() { - return element; + super(ctx, node); } public QName getTypeQName() { - return type; + return getAttributeQName("type"); } - private QName getTypeQName(Node node) { - if (!(node instanceof org.w3c.dom.Element partElement)) { - return null; - } - - var typeAttr = partElement.getAttribute("type"); - if (typeAttr.isEmpty()) { - return null; - } - - return resolveQName(typeAttr, partElement); + public QName getElementQName() { + return getAttributeQName("element"); } - private QName getElementQName(Node node) { - if (!(node instanceof Element element)) { - return null; - } - var elementAttr = element.getAttribute("element"); - if (elementAttr.isEmpty()) { - return null; - } - return resolveQName(elementAttr, element); + private @Nullable QName getAttributeQName(String attribute) { + return resolveQName(getAttribute(attribute)); } @Override public String toString() { - return "Part [element=" + element + ", type=" + type + "]"; + return "Part [element=%s, type=%s]".formatted(getElementQName(), getTypeQName()); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java index 0b17276fc2..039a3193ea 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java @@ -16,63 +16,25 @@ import org.w3c.dom.*; -import java.util.*; - -import static com.predic8.membrane.annot.Constants.*; -import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.resolveQName; -import static org.w3c.dom.Node.*; +import static com.predic8.membrane.core.util.wsdl.parser.WSDLParserUtil.getLocalName; public class Port extends WSDLElement { - private final Address address; - private final Binding binding; - public Port(WSDLParserContext ctx, Node node) { super(ctx,node); - address = getAddress(node); - binding = getBinding(node); - } public Address getAddress() { - return address; + return instantiateElements(element,"address",Address.class).getFirst(); } public Binding getBinding() { - return binding; - } - - public Address getAddress(Node node) { - var children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - var child = children.item(i); - if (child.getNodeType() == ELEMENT_NODE) { - var localName = child.getLocalName(); - if ("address".equals(localName)) { - return new Address(ctx,child); - } - } - } - return null; + return ctx.getDefinitions().getBindings().stream() + .filter(this::matchesTypeAttribute) + .findFirst().get(); } - public Binding getBinding(Node node) { - if (!(node instanceof Element port)) - return null; - - var bindingAttr = port.getAttribute("binding"); - if (bindingAttr.isEmpty()) - return null; - - var bindingQName = resolveQName(bindingAttr, port); - var bindings = getDefinitions().getElementsByTagNameNS(WSDL11_NS, "binding"); - - for (int i = 0; i < bindings.getLength(); i++) { - var binding = (Element) bindings.item(i); - if (bindingQName.getLocalPart().equals(binding.getAttribute("name"))) { - return new Binding(ctx, binding); - } - } - return null; + private boolean matchesTypeAttribute(Binding b) { + return b.getName().equals(getLocalName(getAttribute("binding"))); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java index 045cf8e064..b89ffa9f46 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/PortType.java @@ -20,19 +20,11 @@ public class PortType extends WSDLElement { - private final List operations; - public PortType(WSDLParserContext ctx, Node node) { super(ctx, node); - operations = getOperations(node); - ctx.getDefinitions().getPortTypes().add(this); } public List getOperations() { - return operations; - } - - private List getOperations(Node node) { - return instantiateWSDLChildElements(node, "operation", Operation.class); + return instantiateWSDLChildren( "operation", Operation.class); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/ProtocolOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/ProtocolOperation.java new file mode 100644 index 0000000000..6a536f6642 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/ProtocolOperation.java @@ -0,0 +1,16 @@ +package com.predic8.membrane.core.util.wsdl.parser; + +import org.w3c.dom.*; + +/** + * e.g., wsdl:binding/wsdl:operation/s11:operation + */ +public class ProtocolOperation extends WSDLElement { + public ProtocolOperation(WSDLParserContext ctx, Node node) { + super(ctx, node); + } + + public String getSoapAction() { + return getAttribute("soapAction"); + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java index 1a6e74b7e4..db32c9bb4d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Service.java @@ -20,19 +20,11 @@ public class Service extends WSDLElement { - private final List ports; - public Service(WSDLParserContext ctx, Node element) { super(ctx,element); - ports = getPorts(element); - ctx.getDefinitions().getServices().add(this); } public List getPorts() { - return ports; - } - - public List getPorts(Node service) { - return instantiateWSDLChildElements(service, "port", Port.class); + return instantiateWSDLChildren( "port", Port.class); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java deleted file mode 100644 index f1b1248948..0000000000 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Type.java +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright 2026 predic8 GmbH, www.predic8.com - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. */ - -package com.predic8.membrane.core.util.wsdl.parser; - -import org.w3c.dom.*; - -public class Type extends WSDLElement{ - public Type(WSDLParserContext ctx, Node node) { - super(ctx,node); - } -} diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 80fb1dfecc..6e3c9dd51e 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -17,6 +17,7 @@ import org.jetbrains.annotations.*; import org.w3c.dom.*; +import javax.xml.namespace.*; import java.util.*; import java.util.function.*; @@ -53,19 +54,37 @@ public Element getElement() { return element; } - protected List instantiateChildElements(Node node, String name, Class clazz) { - return instantiateWSDLChildElementsInternal(node, name, clazz, WSDLElement::isElementWithName); + protected Optional instantiateChild(String name, Class clazz) { + var children = instantiateChildren(name, clazz); + return children.isEmpty() ? Optional.empty() : Optional.of(children.get(0)); } - protected List instantiateWSDLChildElements(Node node, String name, Class clazz) { - return instantiateWSDLChildElementsInternal(node, name, clazz, WSDLElement::isWSDLElementWithName); + protected List instantiateChildren(String name, Class clazz) { + return instantiateElements(element,name,clazz); } - protected List instantiateXSDChildElements(Node node, String name, Class clazz) { - return instantiateWSDLChildElementsInternal(node, name, clazz, WSDLElement::isXSDElementWithName); + protected List instantiateElements(Node node, String name, Class clazz) { + return instantiateElementsInternal(node, name, clazz, WSDLElement::isElementWithName); } - protected List instantiateWSDLChildElementsInternal(Node node, String name, Class clazz, BiPredicate elementPredicate) { + protected List instantiateWSDLChildren(String name, Class clazz) { + return instantiateWSDLElements(element,name,clazz); + } + + + protected List instantiateWSDLElements(Node node, String name, Class clazz) { + return instantiateElementsInternal(node, name, clazz, WSDLElement::isWSDLElementWithName); + } + + protected List instantiateXSDChildren(String name, Class clazz) { + return instantiateXSDElements(element,name,clazz); + } + + protected List instantiateXSDElements(Node node, String name, Class clazz) { + return instantiateElementsInternal(node, name, clazz, WSDLElement::isXSDElementWithName); + } + + protected List instantiateElementsInternal(Node node, String name, Class clazz, BiPredicate elementPredicate) { List result = new ArrayList<>(); var children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { @@ -112,4 +131,10 @@ protected boolean isWsdlSoap11Element() { protected @NotNull String getAttribute(String name) { return element.getAttribute(name); } + + public QName resolveQName(String value) { + if (value == null) + return null; + return WSDLParserUtil.resolveQName(value, element); + } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java index f55d2b8ce2..6e6cc43df2 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserException.java @@ -18,8 +18,4 @@ public class WSDLParserException extends RuntimeException { public WSDLParserException(String message) { super(message); } - - public WSDLParserException(String message, Throwable cause) { - super(message, cause); - } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java index 05ff64ba58..beaca9c99e 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserUtil.java @@ -41,6 +41,8 @@ public static String getLocalName(String value) { } public static QName resolveQName(String value, Node context) { + if (value == null) + return null; if (!value.contains(":")) return new QName(context.lookupNamespaceURI(null), value); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index a3b5ad9681..98cda3e7fe 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -23,21 +23,17 @@ public class Import extends AbstractIncludeImport { - private static final Logger log = LoggerFactory.getLogger(Definitions.class); - - private final String namespace; + private static final Logger log = LoggerFactory.getLogger(Import.class); public Import(WSDLParserContext ctx, Node node) { super(ctx, node); - namespace = getNamespace(node); - // Schema is null when it is already imported from somewhere else if (schema == null) return; - if (!Objects.equals(schema.getTargetNamespace(), namespace)) { - throw new WSDLParserException("The namespace {%s} of the import does not match the targetNamespace of the imported schema {%s}.".formatted(namespace, schema.getTargetNamespace())); + if (!Objects.equals(schema.getTargetNamespace(), getNamespace())) { + throw new WSDLParserException("The namespace {%s} of the import does not match the targetNamespace of the imported schema {%s}.".formatted(getNamespace(), schema.getTargetNamespace())); } } @@ -50,8 +46,8 @@ public void importEmbeddedSchema(Definitions definitions) { if (isSchemaLocationMissing()) return; - log.debug("Importing embedded schema with namespace: {}", namespace); - definitions.getEmbeddedSchema(namespace).ifPresent(s -> schema = s); + log.debug("Importing embedded schema with namespace: {}", getNamespace()); + definitions.getEmbeddedSchema(getNamespace()).ifPresent(s -> schema = s); } private boolean isSchemaLocationMissing() { @@ -70,12 +66,7 @@ public void setSchema(Schema schema) { this.schema = schema; } - - private @NotNull String getNamespace(Node node) { - return ((Element) node).getAttribute("namespace"); - } - public String getNamespace() { - return namespace; + return getAttribute("namespace"); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java index 3ea42779a1..bd793451af 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -21,32 +21,24 @@ public class Schema extends WSDLElement { - private final String targetNamespace; - - /** - * DOM Element of the schema as read from the WSDL. - */ - private final Element schema; - private final List schemaElements; - private List imports = new ArrayList<>(); - private List includes = new ArrayList<>(); + private List imports; + private List includes; public Schema(WSDLParserContext ctx, Node node) { super(ctx, node); - this.targetNamespace = getTargetNamespace(node); - this.schema = (Element) node; - this.schemaElements = getSchemaElements(schema); - parseImports(node); - parseIncludes(node); + this.schemaElements = instantiateXSDChildren("element", SchemaElement.class); + imports = instantiateXSDChildren("import",Import.class); + includes = instantiateXSDChildren("include",Include.class); + includes.forEach(i -> include(i.getSchema())); } public String getTargetNamespace() { - return targetNamespace; + return getAttribute("targetNamespace"); } public Element getSchemaElement() { - return schema; + return element; } public List getSchemaElements() { @@ -75,25 +67,4 @@ public void include(Schema includedSchema) { schemaElements.addAll(includedSchema.getSchemaElements()); imports.addAll(includedSchema.getImports()); } - - private String getTargetNamespace(Node node) { - if (!(node instanceof Element schema)) { - return null; - } - var tns = schema.getAttribute("targetNamespace"); - return (tns.isEmpty()) ? null : tns; - } - - private void parseIncludes(Node node) { - includes = instantiateXSDChildElements(node,"include",Include.class); - includes.forEach(i -> include(i.getSchema())); - } - - private void parseImports(Node node) { - imports = instantiateXSDChildElements(node,"import",Import.class); - } - - private List getSchemaElements(Element schema) { - return instantiateXSDChildElements(schema,"element", SchemaElement.class); - } -} +} \ No newline at end of file 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 b215864f36..486357ebfa 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 @@ -52,6 +52,10 @@ void simpleSchema() throws Exception { var binding = port.getBinding(); assertEquals("CitySoapBinding", binding.getName()); assertEquals(DOCUMENT, binding.getStyle()); + + assertEquals("https://predic8.de/cities",binding.getBindingOperation("getCity").getSoapAction()); + + var portType = binding.getPortType(); assertEquals("CityPort", portType.getName()); var operations = portType.getOperations(); @@ -117,6 +121,7 @@ void fault() throws Exception { @Test void rpcStyle() throws Exception { var dn = Definitions.parse(new ResolverMap(),"classpath:/validation/inline-anytype.wsdl"); + dn.getServices().forEach(s -> s.getPorts().forEach(p -> p.getBinding())); assertEquals(RPC, dn.getBindings().getFirst().getStyle()); } From cd4a6da6edcdb7fea51b4ecc4c0317f9dd3e0d79 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 10 Mar 2026 23:21:47 +0100 Subject: [PATCH 19/28] Refactor WSDL parser: simplify field initialization, streamline list handling, and enhance readability - Replaced `get(0)` with `getFirst()` for better semantics in child node initialization. - Made `imports` and `includes` lists final in `Schema` for immutability. - Simplified stream operations with method references in `Definitions`. - Removed unused `addSoapVersion` method and redundant test logic. --- .../membrane/core/util/wsdl/parser/Definitions.java | 7 ++----- .../membrane/core/util/wsdl/parser/WSDLElement.java | 2 +- .../membrane/core/util/wsdl/parser/schema/Schema.java | 4 ++-- .../predic8/membrane/core/util/soap/WSDLParserTest.java | 1 - 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 3f2f1a6dec..47f359ada7 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -23,6 +23,7 @@ import java.util.stream.*; import static com.predic8.membrane.annot.Constants.*; +import static java.util.stream.Collectors.toSet; /** * WSDL elements register themselves via WSDLParserContext. This is more convenient, e.g. binding is not @@ -71,7 +72,7 @@ private void parse(Element element) { portTypes = instantiateWSDLElements(element, "portType", PortType.class); bindings = instantiateWSDLElements(element, "binding", Binding.class); services = instantiateWSDLElements(element, "service", Service.class); - soapVersions = bindings.stream().map(b -> b.getSoapVersion()).collect(Collectors.toSet()); + soapVersions = bindings.stream().map(Binding::getSoapVersion).collect(toSet()); } /** @@ -128,10 +129,6 @@ public Set getSoapVersions() { return soapVersions; } - public void addSoapVersion(SOAPVersion soapVersion) { - soapVersions.add(soapVersion); - } - private List getSchemaElements(Element wsdl) { return instantiateXSDElements(getTypes(wsdl), "schema", Schema.class); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 6e3c9dd51e..3403245e64 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -56,7 +56,7 @@ public Element getElement() { protected Optional instantiateChild(String name, Class clazz) { var children = instantiateChildren(name, clazz); - return children.isEmpty() ? Optional.empty() : Optional.of(children.get(0)); + return children.isEmpty() ? Optional.empty() : Optional.of(children.getFirst()); } protected List instantiateChildren(String name, Class clazz) { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java index bd793451af..782379bc49 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -22,8 +22,8 @@ public class Schema extends WSDLElement { private final List schemaElements; - private List imports; - private List includes; + private final List imports; + private final List includes; public Schema(WSDLParserContext ctx, Node node) { super(ctx, node); 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 486357ebfa..6344b72973 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 @@ -121,7 +121,6 @@ void fault() throws Exception { @Test void rpcStyle() throws Exception { var dn = Definitions.parse(new ResolverMap(),"classpath:/validation/inline-anytype.wsdl"); - dn.getServices().forEach(s -> s.getPorts().forEach(p -> p.getBinding())); assertEquals(RPC, dn.getBindings().getFirst().getStyle()); } From b1d679d095cda2faaa307bc2dac8f94009656114 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Wed, 11 Mar 2026 08:46:04 +0100 Subject: [PATCH 20/28] Refactor WSDL parser: simplify attribute handling, enhance fault handling, and optimize child element processing - Replaced direct field usage with `getAttribute` for cleaner logic (`getName()` in `WSDLElement` and `BindingOperation`). - Simplified and unified child node processing with `instantiateChildren()` and `instantiateChild()` methods across classes. - Added `Fault` handling for operations and updated `WebServiceExplorerInterceptor` to include fault details in table rendering. - Refactored `Operation` to improve input/output/fault message handling with streamlined classes (`Input`, `Output`, `Fault`). - Introduced `Types` class for better encapsulation of WSDL element processing. - Updated unit tests to validate refactored fault handling and schema updates. --- .../soap/WebServiceExplorerInterceptor.java | 9 ++- .../core/util/wsdl/parser/Binding.java | 2 +- .../util/wsdl/parser/BindingOperation.java | 2 +- .../core/util/wsdl/parser/Definitions.java | 28 ++++---- .../core/util/wsdl/parser/Operation.java | 64 ++++++++++++------- .../core/util/wsdl/parser/WSDLElement.java | 14 ++-- .../core/util/wsdl/parser/schema/Schema.java | 1 + .../core/util/wsdl/parser/schema/Types.java | 10 +++ .../core/util/soap/WSDLParserTest.java | 6 +- 9 files changed, 78 insertions(+), 58 deletions(-) create mode 100644 core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Types.java diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java index f7ce8fc9b1..eef5c4358e 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java @@ -23,7 +23,6 @@ import com.predic8.membrane.core.interceptor.rest.*; import com.predic8.membrane.core.proxies.Proxy; import com.predic8.membrane.core.util.wsdl.parser.*; -import com.predic8.membrane.core.util.wsdl.parser.Message; import org.slf4j.*; import java.io.*; @@ -159,6 +158,7 @@ private void createOperationsTable(Definitions defs, Binding binding, PortType p th().text("Operation").end(); th().text("Input").end(); th().text("Output").end(); + th().text("Fault").end(); end(); for (Operation o : portType.getOperations()) { tr(); @@ -166,11 +166,14 @@ private void createOperationsTable(Definitions defs, Binding binding, PortType p text(o.getName()); end(); td(); - for (Part p : o.getInputs().stream().map(Message::getPart).toList()) + for (Part p : o.getInputs().stream().map(om -> om.getMessage().getPart()).toList()) text(p.getElementQName().toString()); end(); td(); - for (Part p : o.getOutputs().stream().map(Message::getPart).toList()) + for (Part p : o.getOutputs().stream().map(om -> om.getMessage().getPart()).toList()) + text(p.getElementQName().toString()); + end(); + for (Part p : o.getFaults().stream().map(om -> om.getMessage().getPart()).toList()) text(p.getElementQName().toString()); end(); end(); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index e95b43fbfa..770c7278a0 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -63,6 +63,6 @@ private BindingStyle getBindingStyle() { public PortType getPortType() { return ctx.getDefinitions().getPortTypes().stream() .filter(pt -> getLocalName(getAttribute("type")).equals(pt.getName())) - .findFirst().orElseThrow(() -> new WSDLParserException("No portType found for binding: " + name)); + .findFirst().orElseThrow(() -> new WSDLParserException("No portType found for binding: " + getName())); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java index 7d5ce81d11..dd1e82bc10 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/BindingOperation.java @@ -24,7 +24,7 @@ public BindingOperation(WSDLParserContext ctx, Node node) { public String getSoapAction() { return instantiateChild("operation",ProtocolOperation.class).orElseThrow(() -> - new WSDLParserException("No operation found for binding operation: " + name) + new WSDLParserException("No operation found for binding operation: " + getName()) ).getSoapAction(); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index 47f359ada7..e286d06bc0 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -20,10 +20,8 @@ import org.w3c.dom.*; import java.util.*; -import java.util.stream.*; -import static com.predic8.membrane.annot.Constants.*; -import static java.util.stream.Collectors.toSet; +import static java.util.stream.Collectors.*; /** * WSDL elements register themselves via WSDLParserContext. This is more convenient, e.g. binding is not @@ -66,12 +64,12 @@ public static Definitions parse(Resolver resolver, String location) throws Excep private void parse(Element element) { targetNamespace = getAttribute("targetNamespace"); - schemas = getSchemaElements(element); + schemas = instantiateXSDElements(getTypes().element, "schema", Schema.class); importEmbeddedSchemas(); - messages = instantiateWSDLElements(element, "message", Message.class); - portTypes = instantiateWSDLElements(element, "portType", PortType.class); - bindings = instantiateWSDLElements(element, "binding", Binding.class); - services = instantiateWSDLElements(element, "service", Service.class); + messages = instantiateWSDLChildren("message", Message.class); + portTypes = instantiateWSDLChildren("portType", PortType.class); + bindings = instantiateWSDLChildren( "binding", Binding.class); + services = instantiateWSDLChildren( "service", Service.class); soapVersions = bindings.stream().map(Binding::getSoapVersion).collect(toSet()); } @@ -89,6 +87,12 @@ public Optional getEmbeddedSchema(String namespace) { .findFirst(); } + public Types getTypes() { + return instantiateChild("types", Types.class).orElseThrow(() -> { + throw new WSDLParserException("No types element found in WSDL"); + }); + } + public List getSchemas() { return schemas; } @@ -128,12 +132,4 @@ public String getTargetNamespace() { public Set getSoapVersions() { return soapVersions; } - - private List getSchemaElements(Element wsdl) { - return instantiateXSDElements(getTypes(wsdl), "schema", Schema.class); - } - - private static Node getTypes(Element wsdl) { - return wsdl.getElementsByTagNameNS(WSDL11_NS, "types").item(0); - } } \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index 31ebf85cba..5ee027c609 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -18,8 +18,6 @@ import java.util.*; -import static com.predic8.membrane.core.util.wsdl.parser.Operation.Direction.*; - public class Operation extends WSDLElement { public enum Direction { @@ -31,37 +29,55 @@ public boolean matches(String s) { } public Operation(WSDLParserContext ctx, Node node) { - super(ctx,node); + super(ctx, node); } - public List getInputs() { - return getMessagesByDirection(INPUT); + public List getInputs() { + return instantiateChildren("input", Input.class); } - public List getOutputs() { - return getMessagesByDirection(OUTPUT); + public List getOutputs() { + return instantiateChildren("output", Output.class); } - public List getFaults() { - return getMessagesByDirection(FAULT); + public List getFaults() { + return instantiateChildren("fault", Fault.class); } public List getMessagesByDirection(Direction direction) { - var result = new ArrayList(); - var children = element.getChildNodes(); - - for (int i = 0; i < children.getLength(); i++) { - var child = children.item(i); - if (isWSDLElement(child) && direction.matches(child.getLocalName())) { - var io = (Element) child; - var messageAttr = io.getAttribute("message"); - if (messageAttr.isEmpty()) { - continue; - } - ctx.getDefinitions().findMessage(WSDLParserUtil.resolveQName(messageAttr, io).getLocalPart()) - .ifPresent(result::add); - } + return switch (direction) { + case INPUT -> getInputs().stream().map(Input::getMessage).toList(); + case OUTPUT -> getOutputs().stream().map(Output::getMessage).toList(); + case FAULT -> getFaults().stream().map(Fault::getMessage).toList(); + }; + } + + public static abstract class OperationMessage extends WSDLElement { + public OperationMessage(WSDLParserContext ctx, Node node) { + super(ctx, node); + } + + public Message getMessage() { + return ctx.getDefinitions().findMessage(WSDLParserUtil.getLocalName(getAttribute("message"))) + .orElseThrow(() -> new WSDLParserException("Message not found: " + getAttribute("message"))); + } + } + + public static class Input extends OperationMessage { + public Input(WSDLParserContext ctx, Node node) { + super(ctx, node); + } + } + + public static class Output extends OperationMessage { + public Output(WSDLParserContext ctx, Node node) { + super(ctx, node); + } + } + + public static class Fault extends OperationMessage { + public Fault(WSDLParserContext ctx, Node node) { + super(ctx, node); } - return result; } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 3403245e64..25d50297c9 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -28,7 +28,6 @@ public class WSDLElement { protected WSDLParserContext ctx; protected final Element element; - protected String name; public WSDLElement(WSDLParserContext ctx, Node node) { this.ctx = ctx; @@ -36,14 +35,10 @@ public WSDLElement(WSDLParserContext ctx, Node node) { throw new RuntimeException("Not an element: " + node.getClass()); } this.element = element; - var nameNode = element.getAttributes().getNamedItem("name"); - if (nameNode != null) { - name = nameNode.getNodeValue(); - } } public String getName() { - return name; + return getAttribute("name"); } public Element getDefinitions() { @@ -71,7 +66,6 @@ protected List instantiateWSDLChildren(String name, C return instantiateWSDLElements(element,name,clazz); } - protected List instantiateWSDLElements(Node node, String name, Class clazz) { return instantiateElementsInternal(node, name, clazz, WSDLElement::isWSDLElementWithName); } @@ -84,12 +78,12 @@ protected List instantiateXSDElements(Node node, Stri return instantiateElementsInternal(node, name, clazz, WSDLElement::isXSDElementWithName); } - protected List instantiateElementsInternal(Node node, String name, Class clazz, BiPredicate elementPredicate) { - List result = new ArrayList<>(); + protected List instantiateElementsInternal(Node node, String name, Class clazz, BiPredicate predicate) { + var result = new ArrayList(); var children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { var child = children.item(i); - if (elementPredicate.test(child, name)) { + if (predicate.test(child, name)) { try { result.add(clazz.getConstructor(WSDLParserContext.class, Node.class).newInstance(ctx, child)); } catch (Exception e) { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java index 782379bc49..bffb3e3e41 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Schema.java @@ -21,6 +21,7 @@ public class Schema extends WSDLElement { + // These values are read repeatably so they are stored private final List schemaElements; private final List imports; private final List includes; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Types.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Types.java new file mode 100644 index 0000000000..b391da9ef2 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Types.java @@ -0,0 +1,10 @@ +package com.predic8.membrane.core.util.wsdl.parser.schema; + +import com.predic8.membrane.core.util.wsdl.parser.*; +import org.w3c.dom.*; + +public class Types extends WSDLElement { + public Types(WSDLParserContext ctx, Node node) { + super(ctx, node); + } +} 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 6344b72973..cae38048b3 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 @@ -64,7 +64,7 @@ void simpleSchema() throws Exception { var inputs = getCityOperation.getInputs(); assertEquals(1, inputs.size()); var getCityInput = inputs.getFirst(); - var getCityPart = getCityInput.getPart(); + var getCityPart = getCityInput.getMessage().getPart(); assertEquals("getCity", getCityPart.getName()); assertEquals("getCity", getCityPart.getElementQName().getLocalPart()); assertEquals("https://predic8.de/cities", getCityPart.getElementQName().getNamespaceURI()); @@ -82,7 +82,7 @@ void simpleSchema() throws Exception { @Test void includeImport() throws Exception { var dn = Definitions.parse(new ResolverMap(), "classpath://ws/include/include.wsdl"); - assertNull(dn.getName()); + assertNull(""); // No name is set assertEquals(1, dn.getSchemas().size()); var embedded = dn.getSchemas().getFirst(); assertEquals("http://example.com/test", embedded.getTargetNamespace()); @@ -115,7 +115,7 @@ void fault() throws Exception { var pt = dn.getPortTypes().getFirst(); var fault = pt.getOperations().getFirst().getFaults().getFirst(); assertNotNull(fault); - assertEquals("DivideByZeroFault", fault.getPart().getElementQName().getLocalPart()); + assertEquals("DivideByZeroFault", fault.getMessage().getPart().getElementQName().getLocalPart()); } @Test From 75c98048fd79ba4b4b3d8e9134b3a7852bf3df4a Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Wed, 11 Mar 2026 08:49:29 +0100 Subject: [PATCH 21/28] Improve WSDL parser exception handling and test assertions - Replaced `findFirst().get()` with `orElseThrow()` for safer binding retrieval in `Port`. - Simplified `Definitions` parsing logic by removing unused method parameters and refactoring lambda expressions. - Updated unit test assertions for better accuracy (`assertNull` replaced with `assertEquals`). --- .../membrane/core/util/wsdl/parser/Definitions.java | 9 ++++----- .../com/predic8/membrane/core/util/wsdl/parser/Port.java | 2 +- .../predic8/membrane/core/util/soap/WSDLParserTest.java | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java index e286d06bc0..cd5ae10cbc 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Definitions.java @@ -48,7 +48,7 @@ public enum SOAPVersion { private Definitions(Resolver resolver, String location) throws Exception { super(new WSDLParserContext(null, resolver, location, new ArrayList<>()), read(resolver, location)); ctx = ctx.definitions(this); - parse(this.getElement()); + parse(); } private static Node read(Resolver resolver, String location) throws Exception { @@ -62,7 +62,7 @@ public static Definitions parse(Resolver resolver, String location) throws Excep return new Definitions(resolver, location); } - private void parse(Element element) { + private void parse() { targetNamespace = getAttribute("targetNamespace"); schemas = instantiateXSDElements(getTypes().element, "schema", Schema.class); importEmbeddedSchemas(); @@ -88,9 +88,8 @@ public Optional getEmbeddedSchema(String namespace) { } public Types getTypes() { - return instantiateChild("types", Types.class).orElseThrow(() -> { - throw new WSDLParserException("No types element found in WSDL"); - }); + return instantiateChild("types", Types.class) + .orElseThrow(() -> new WSDLParserException("No types element found in WSDL")); } public List getSchemas() { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java index 039a3193ea..9f1ce0c353 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java @@ -31,7 +31,7 @@ public Address getAddress() { public Binding getBinding() { return ctx.getDefinitions().getBindings().stream() .filter(this::matchesTypeAttribute) - .findFirst().get(); + .findFirst().orElseThrow(() -> new WSDLParserException("No binding found for port: " + getName())); } private boolean matchesTypeAttribute(Binding b) { 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 cae38048b3..00f12384d5 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 @@ -82,7 +82,7 @@ void simpleSchema() throws Exception { @Test void includeImport() throws Exception { var dn = Definitions.parse(new ResolverMap(), "classpath://ws/include/include.wsdl"); - assertNull(""); // No name is set + assertEquals("",dn.getName()); // No name is set assertEquals(1, dn.getSchemas().size()); var embedded = dn.getSchemas().getFirst(); assertEquals("http://example.com/test", embedded.getTargetNamespace()); From b3f37389cf5dff6baa75e00c41ad246a7e330056 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Wed, 11 Mar 2026 09:49:56 +0100 Subject: [PATCH 22/28] Fix: include fault details in WebServiceExplorer table rendering - Added missing `td()` element to ensure proper rendering of fault details in the web service explorer table. --- .../core/interceptor/soap/WebServiceExplorerInterceptor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java index eef5c4358e..f0e65ea73c 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java @@ -173,6 +173,7 @@ private void createOperationsTable(Definitions defs, Binding binding, PortType p for (Part p : o.getOutputs().stream().map(om -> om.getMessage().getPart()).toList()) text(p.getElementQName().toString()); end(); + td(); for (Part p : o.getFaults().stream().map(om -> om.getMessage().getPart()).toList()) text(p.getElementQName().toString()); end(); From 8a4bf53664a2e586b89875523898eab3e4ee4c96 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Wed, 11 Mar 2026 11:17:24 +0100 Subject: [PATCH 23/28] Refactor WSDL parser: migrate to `record` for `WSDLParserContext`, simplify methods, and enhance exception handling - Replaced `WSDLParserContext` class with a `record` for better immutability and readability. - Updated method calls to use record getters (e.g., `ctx.definitions()` instead of `ctx.getDefinitions()`). - Enhanced exception messages in `WSDLElement` and `Binding` for better debugging. - Simplified stream and lambda usage across parser classes. - Added unit test for abstract WSDL service validation (`SOAPProxyTest`). --- .../WSDLMessageElementExtractor.java | 3 +- .../schemavalidation/WSDLValidator.java | 2 +- .../membrane/core/proxies/SOAPProxy.java | 2 ++ .../core/util/wsdl/parser/Binding.java | 7 ++-- .../core/util/wsdl/parser/Operation.java | 2 +- .../membrane/core/util/wsdl/parser/Port.java | 2 +- .../core/util/wsdl/parser/WSDLElement.java | 2 +- .../util/wsdl/parser/WSDLParserContext.java | 32 ++----------------- .../parser/schema/AbstractIncludeImport.java | 6 ++-- .../core/util/wsdl/parser/schema/Import.java | 5 ++- .../wsdl/parser/schema/SchemaElement.java | 3 -- .../membrane/core/proxies/SOAPProxyTest.java | 8 +++++ 12 files changed, 28 insertions(+), 46 deletions(-) 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 6b09a26abe..08b29bea6b 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 @@ -104,7 +104,8 @@ private static String getElementNameRPC(Operation operation, Direction direction return result.stream().map(PortType::getOperations) .flatMap(Collection::stream) .map(op -> op.getMessagesByDirection(direction)) - .flatMap(Collection::stream).toList().stream().map(Message::getPart); + .flatMap(Collection::stream) + .map(Message::getPart); } 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 2bf1a7d9b4..a36ca979a3 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 @@ -139,7 +139,7 @@ private boolean isPossibleResponseElement(javax.xml.namespace.QName name) { } private boolean isPossibleSOAPElement(Set elementNames, QName name) { - return elementNames.stream().anyMatch(qn -> qn.equals(name)); + return elementNames.contains(name); } @Override diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java b/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java index cacd9e920d..3001840812 100644 --- a/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java +++ b/core/src/main/java/com/predic8/membrane/core/proxies/SOAPProxy.java @@ -116,6 +116,8 @@ private Service getService(Definitions defs) { return defs.getService(serviceName).orElseThrow( () -> new ConfigurationException("No service with name '%s' found in WSDL %s".formatted(serviceName, wsdl)) ); + if (defs.getServices().isEmpty()) + throw new ConfigurationException("No service element found in WSDL %s".formatted(wsdl)); return defs.getServices().getFirst(); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java index 770c7278a0..67768bea2b 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Binding.java @@ -57,11 +57,14 @@ public List getBindingOperations() { } private BindingStyle getBindingStyle() { - return instantiateChildren( "binding", BindingStyle.class).getFirst(); + var binding = instantiateChildren("binding", BindingStyle.class); + if (binding.isEmpty()) + throw new WSDLParserException("No bindingStyle found for binding: " + getName()); + return binding.getFirst(); } public PortType getPortType() { - return ctx.getDefinitions().getPortTypes().stream() + return ctx.definitions().getPortTypes().stream() .filter(pt -> getLocalName(getAttribute("type")).equals(pt.getName())) .findFirst().orElseThrow(() -> new WSDLParserException("No portType found for binding: " + getName())); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java index 5ee027c609..7ef9f9a98d 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Operation.java @@ -58,7 +58,7 @@ public OperationMessage(WSDLParserContext ctx, Node node) { } public Message getMessage() { - return ctx.getDefinitions().findMessage(WSDLParserUtil.getLocalName(getAttribute("message"))) + return ctx.definitions().findMessage(WSDLParserUtil.getLocalName(getAttribute("message"))) .orElseThrow(() -> new WSDLParserException("Message not found: " + getAttribute("message"))); } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java index 9f1ce0c353..6cd78c36b3 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/Port.java @@ -29,7 +29,7 @@ public Address getAddress() { } public Binding getBinding() { - return ctx.getDefinitions().getBindings().stream() + return ctx.definitions().getBindings().stream() .filter(this::matchesTypeAttribute) .findFirst().orElseThrow(() -> new WSDLParserException("No binding found for port: " + getName())); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java index 25d50297c9..df3c035010 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLElement.java @@ -87,7 +87,7 @@ protected List instantiateElementsInternal(Node node, try { result.add(clazz.getConstructor(WSDLParserContext.class, Node.class).newInstance(ctx, child)); } catch (Exception e) { - throw new RuntimeException(e); + throw new RuntimeException("Failed to instantiate " + clazz.getSimpleName() + " for element: " + name, e); } } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java index 182521c5cc..ea27c94a21 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/WSDLParserContext.java @@ -14,24 +14,12 @@ package com.predic8.membrane.core.util.wsdl.parser; -import com.predic8.membrane.core.graphql.model.*; import com.predic8.membrane.core.resolver.*; import java.util.*; -public class WSDLParserContext { - - private final Definitions definitions; - private final Resolver resolver; - private final String basePath; - private final List visitedLocations; - - public WSDLParserContext(Definitions wsdl, Resolver resolver, String basePath, List visitedLocations) { - this.definitions = wsdl; - this.resolver = resolver; - this.basePath = basePath; - this.visitedLocations = visitedLocations; - } +public record WSDLParserContext(Definitions definitions, Resolver resolver, String basePath, + List visitedLocations) { public WSDLParserContext definitions(Definitions definitions) { return new WSDLParserContext(definitions, resolver, basePath, visitedLocations); @@ -45,20 +33,4 @@ public WSDLParserContext basePath(String basePath) { public WSDLParserContext resolver(Resolver resolver) { return new WSDLParserContext(definitions, resolver, basePath, visitedLocations); } - - public List getVisitedLocations() { - return visitedLocations; - } - - public Definitions getDefinitions() { - return definitions; - } - - public Resolver getResolver() { - return resolver; - } - - public String getBasePath() { - return basePath; - } } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index cc212f1145..ed0c56088c 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -40,10 +40,10 @@ protected Schema getSchema(WSDLParserContext ctx) { var resolved = resolve(ctx); // Check if the schema has already been imported or included - if (ctx.getVisitedLocations().contains(resolved)) + if (ctx.visitedLocations().contains(resolved)) return null; - try (var is = ctx.getResolver().resolve(resolved)) { + try (var is = ctx.resolver().resolve(resolved)) { return new Schema(ctx.basePath(resolved), WSDLParserUtil.parse(is)); } } catch (Exception e) { @@ -52,7 +52,7 @@ protected Schema getSchema(WSDLParserContext ctx) { } private String resolve(WSDLParserContext ctx) { - return URIUtil.normalize(ResolverMap.combine(ctx.getBasePath(), schemaLocation)); + return URIUtil.normalize(ResolverMap.combine(ctx.basePath(), schemaLocation)); } public String getSchemaLocation() { diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java index 98cda3e7fe..68ba64cd2c 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/Import.java @@ -15,7 +15,6 @@ package com.predic8.membrane.core.util.wsdl.parser.schema; import com.predic8.membrane.core.util.wsdl.parser.*; -import org.jetbrains.annotations.*; import org.slf4j.*; import org.w3c.dom.*; @@ -43,14 +42,14 @@ public Import(WSDLParserContext ctx, Node node) { * @param definitions The WSL that contains the embedded schema. */ public void importEmbeddedSchema(Definitions definitions) { - if (isSchemaLocationMissing()) + if (hasSchemaLocation()) return; log.debug("Importing embedded schema with namespace: {}", getNamespace()); definitions.getEmbeddedSchema(getNamespace()).ifPresent(s -> schema = s); } - private boolean isSchemaLocationMissing() { + private boolean hasSchemaLocation() { return schemaLocation != null && !schemaLocation.isEmpty(); } diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java index 2ca2d040cb..8bf4fe8d78 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java @@ -19,11 +19,8 @@ public class SchemaElement extends WSDLElement { - WSDLParserContext ctx; - public SchemaElement(WSDLParserContext ctx,Node node) { super(ctx,node); - this.ctx = ctx; } @Override diff --git a/core/src/test/java/com/predic8/membrane/core/proxies/SOAPProxyTest.java b/core/src/test/java/com/predic8/membrane/core/proxies/SOAPProxyTest.java index 01ba3b401e..2198e57c5f 100644 --- a/core/src/test/java/com/predic8/membrane/core/proxies/SOAPProxyTest.java +++ b/core/src/test/java/com/predic8/membrane/core/proxies/SOAPProxyTest.java @@ -85,6 +85,14 @@ void parseWSDLWithMultipleServicesForAGivenServiceA() throws Exception { router.start(); } + @Test + void abstractWSDL() throws Exception { + proxy.setWsdl("classpath:/ws/abstract-service-no-binding.wsdl"); + router.add(proxy); + assertThrows(ConfigurationException.class, + () -> router.start()); + } + @Test void parseWSDLWithMultipleServicesForAGivenServiceB() throws Exception { proxy.setServiceName("CityServiceB"); From 44535d03591f5d97a429064f6769eeb52cff31ed Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Wed, 11 Mar 2026 15:56:53 +0100 Subject: [PATCH 24/28] Add SOAP 1.2 WSDL file and corresponding tests for enhanced schema validation - Introduced `hello-soap12.wsdl` in test resources, implementing a SOAP 1.2 example schema for validation purposes. - Added test cases in `WSDLParserTest` to verify the parsing and validation of SOAP 1.2 bindings (`HelloService`). - Created `WSDLIncludeImportTest` to test schema include/import scenarios, including cyclic and embedded cases. - Improved `RelativePathRewriter` in `WSDLPublisherInterceptor` by refining file existence checks and path resolution. - Minor formatting fixes and refinements in existing code and tests. --- .../server/WSDLPublisherInterceptor.java | 54 +++++--- .../membrane/core/ws/relocator/Relocator.java | 2 +- .../core/util/soap/WSDLParserTest.java | 84 +++++++++++-- .../core/ws/WSDLIncludeImportTest.java | 117 ++++++++++++++++++ core/src/test/resources/ws/hello-soap12.wsdl | 74 +++++++++++ 5 files changed, 305 insertions(+), 26 deletions(-) create mode 100644 core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java create mode 100644 core/src/test/resources/ws/hello-soap12.wsdl diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java index 08c4054310..2e2af6ffe1 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java @@ -21,9 +21,12 @@ import com.predic8.membrane.core.resolver.*; import com.predic8.membrane.core.util.*; import com.predic8.membrane.core.ws.relocator.Relocator.*; +import org.jetbrains.annotations.*; import org.slf4j.*; import javax.annotation.concurrent.*; +import java.io.*; +import java.net.*; import java.util.*; import static com.predic8.membrane.core.exceptions.ProblemDetails.*; @@ -31,6 +34,7 @@ import static com.predic8.membrane.core.http.Response.*; import static com.predic8.membrane.core.interceptor.Outcome.*; import static com.predic8.membrane.core.resolver.ResolverMap.*; +import static com.predic8.membrane.core.util.URLParamUtil.DuplicateKeyOrInvalidFormStrategy.ERROR; /** * @description

@@ -76,7 +80,7 @@ public WSDLPublisherInterceptor() { */ private final class RelativePathRewriter implements PathRewriter { private final Exchange exc; - private final String resource; + private String resource; private RelativePathRewriter(Exchange exc, String resource) { this.exc = exc; @@ -89,29 +93,37 @@ public String rewrite(String path) { if (!path.contains("://") && !path.startsWith("/")) { path = combine(resource, path); } - synchronized (paths) { - if (paths_reverse.containsKey(path)) { - path = paths_reverse.get(path).toString(); - } else { - int n = paths.size() + 1; - paths.put(n, path); - paths_reverse.put(path, n); - documents_to_process.add(path); - path = Integer.toString(n); - } + + if (!new File(path).exists()) { + throw new ResourceRetrievalException("Could not find resource: " + path); } - path = "./" + URLUtil.getNameComponent(router.getConfiguration().getUriFactory(), exc.getDestinations().getFirst()) + "?xsd=" + path; + + return "./%s?xsd=%s".formatted(URLUtil.getNameComponent(router.getConfiguration().getUriFactory(), exc.getDestinations().getFirst()), resolveToNumber(path)); } catch (Exception e) { throw new RuntimeException(e); } - return path; + } + + private @NotNull String resolveToNumber(String path) { + if (!new File(path).exists()) { + throw new RuntimeException("!!!!Could not find resource: " + path); + } + synchronized (paths) { + if (paths_reverse.containsKey(path)) { + return paths_reverse.get(path).toString(); + } + int n = paths.size() + 1; + paths.put(n, path); + paths_reverse.put(path, n); + documents_to_process.add(path); + return Integer.toString(n); + } } } @Override public void init() { super.init(); - webServerInterceptor = new WebServerInterceptor(); webServerInterceptor.init(router); @@ -123,8 +135,10 @@ public void init() { @GuardedBy("paths") private final Map paths = new HashMap<>(); + @GuardedBy("paths") private final Map paths_reverse = new HashMap<>(); + @GuardedBy("paths") private final Queue documents_to_process = new LinkedList<>(); @@ -134,7 +148,7 @@ private void processDocuments(Exchange exc) { synchronized (paths) { try { while (true) { - String doc = documents_to_process.poll(); + var doc = documents_to_process.poll(); if (doc == null) break; log.debug("processing: {}", doc); @@ -203,9 +217,9 @@ private Outcome handleRequestInternal(final Exchange exc) throws Exception { exc.getResponse().getHeader().setContentType(TEXT_XML); } if (exc.getRequest().getUri().contains("?xsd=")) { - var params = URLParamUtil.getParams(router.getConfiguration().getUriFactory(), exc, URLParamUtil.DuplicateKeyOrInvalidFormStrategy.ERROR); + var params = getParams(exc); if (params.containsKey("xsd")) { - int n = Integer.parseInt(params.get("xsd")); + var n = Integer.parseInt(params.get("xsd")); String path; processDocuments(exc); synchronized (paths) { @@ -222,7 +236,7 @@ private Outcome handleRequestInternal(final Exchange exc) throws Exception { if (resource != null) { var wi = new WSDLInterceptor(); wi.setRewriteEndpoint(false); - wi.setPathRewriter(new RelativePathRewriter(exc, combine(router.getConfiguration().getBaseLocation(), wsdl))); + wi.setPathRewriter(new RelativePathRewriter(exc, combine(router.getConfiguration().getBaseLocation(), resource))); wi.init(router); wi.handleResponse(exc); return RETURN; @@ -238,6 +252,10 @@ private Outcome handleRequestInternal(final Exchange exc) throws Exception { return CONTINUE; } + private Map getParams(Exchange exc) throws URISyntaxException, IOException { + return URLParamUtil.getParams(router.getConfiguration().getUriFactory(), exc, ERROR); + } + private static boolean isRequestForWSDL(Exchange exc) { return exc.getRequest().getUri().endsWith("?wsdl") || exc.getRequest().getUri().endsWith("?WSDL"); } diff --git a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java index 303810608b..db607724c4 100644 --- a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java +++ b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java @@ -27,7 +27,7 @@ @NotThreadSafe public class Relocator { - private static final Logger log = LoggerFactory.getLogger(Relocator.class.getName()); + private static final Logger log = LoggerFactory.getLogger(Relocator.class); private final XMLEventFactory fac = XMLEventFactory.newInstance(); 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 00f12384d5..b3242a6a73 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 @@ -19,7 +19,6 @@ import com.predic8.membrane.core.util.wsdl.parser.schema.*; import org.junit.jupiter.api.*; -import java.io.*; import java.util.*; import static com.predic8.membrane.core.util.wsdl.parser.Binding.Style.*; @@ -53,7 +52,7 @@ void simpleSchema() throws Exception { assertEquals("CitySoapBinding", binding.getName()); assertEquals(DOCUMENT, binding.getStyle()); - assertEquals("https://predic8.de/cities",binding.getBindingOperation("getCity").getSoapAction()); + assertEquals("https://predic8.de/cities", binding.getBindingOperation("getCity").getSoapAction()); var portType = binding.getPortType(); @@ -69,12 +68,12 @@ void simpleSchema() throws Exception { assertEquals("getCity", getCityPart.getElementQName().getLocalPart()); assertEquals("https://predic8.de/cities", getCityPart.getElementQName().getNamespaceURI()); - assertEquals(1,definitions.getBindings().size()); + assertEquals(1, definitions.getBindings().size()); var binding1 = definitions.getBindings().getFirst(); assertEquals("CitySoapBinding", binding1.getName()); assertEquals(DOCUMENT, binding1.getStyle()); - assertEquals(2,definitions.getMessages().size()); + assertEquals(2, definitions.getMessages().size()); assertEquals("City", definitions.getMessages().getFirst().getName()); assertEquals("CityResponse", definitions.getMessages().getLast().getName()); } @@ -82,7 +81,7 @@ void simpleSchema() throws Exception { @Test void includeImport() throws Exception { var dn = Definitions.parse(new ResolverMap(), "classpath://ws/include/include.wsdl"); - assertEquals("",dn.getName()); // No name is set + assertEquals("", dn.getName()); // No name is set assertEquals(1, dn.getSchemas().size()); var embedded = dn.getSchemas().getFirst(); assertEquals("http://example.com/test", embedded.getTargetNamespace()); @@ -111,7 +110,7 @@ void abstractWSDL() throws Exception { @Test void fault() throws Exception { - var dn = Definitions.parse(new ResolverMap(),"classpath:/ws/calculator-fault.wsdl"); + var dn = Definitions.parse(new ResolverMap(), "classpath:/ws/calculator-fault.wsdl"); var pt = dn.getPortTypes().getFirst(); var fault = pt.getOperations().getFirst().getFaults().getFirst(); assertNotNull(fault); @@ -120,8 +119,79 @@ void fault() throws Exception { @Test void rpcStyle() throws Exception { - var dn = Definitions.parse(new ResolverMap(),"classpath:/validation/inline-anytype.wsdl"); + var dn = Definitions.parse(new ResolverMap(), "classpath:/validation/inline-anytype.wsdl"); assertEquals(RPC, dn.getBindings().getFirst().getStyle()); } + @Test + void soap12() throws Exception { + var definitions = Definitions.parse(new ResolverMap(), "classpath:/ws/hello-soap12.wsdl"); + + assertEquals("HelloService", definitions.getName()); + + assertEquals(1, definitions.getSchemas().size()); + var schema = definitions.getSchemas().getFirst(); + assertEquals("http://example.com/hello", schema.getTargetNamespace()); + + var schemaElements = schema.getSchemaElements(); + assertEquals(2, schemaElements.size()); + var schemaElementNames = schemaElements.stream().map(SchemaElement::getName).toList(); + assertEquals(List.of("sayHello", "sayHelloResponse"), schemaElementNames); + + var services = definitions.getServices(); + assertEquals(1, services.size()); + var service = services.getFirst(); + assertEquals("HelloService", service.getName()); + + var ports = service.getPorts(); + assertEquals(1, ports.size()); + var port = ports.getFirst(); + assertEquals("HelloPort", port.getName()); + + var address = port.getAddress(); + assertEquals("http://example.com/hello", address.getLocation()); + + var binding = port.getBinding(); + assertEquals("HelloBinding", binding.getName()); + assertEquals(DOCUMENT, binding.getStyle()); + + assertEquals("sayHello", binding.getBindingOperation("sayHello").getSoapAction()); + + var portType = binding.getPortType(); + assertEquals("HelloPortType", portType.getName()); + + var operations = portType.getOperations(); + assertEquals(1, operations.size()); + + var operation = operations.getFirst(); + assertEquals("sayHello", operation.getName()); + + var inputs = operation.getInputs(); + assertEquals(1, inputs.size()); + + var input = inputs.getFirst(); + var part = input.getMessage().getPart(); + assertEquals("parameters", part.getName()); + assertEquals("sayHello", part.getElementQName().getLocalPart()); + assertEquals("http://example.com/hello", part.getElementQName().getNamespaceURI()); + + var outputs = operation.getOutputs(); + assertEquals(1, outputs.size()); + + var output = outputs.getFirst(); + var outputPart = output.getMessage().getPart(); + assertEquals("parameters", outputPart.getName()); + assertEquals("sayHelloResponse", outputPart.getElementQName().getLocalPart()); + assertEquals("http://example.com/hello", outputPart.getElementQName().getNamespaceURI()); + + assertEquals(1, definitions.getBindings().size()); + var binding1 = definitions.getBindings().getFirst(); + assertEquals("HelloBinding", binding1.getName()); + assertEquals(DOCUMENT, binding1.getStyle()); + + assertEquals(2, definitions.getMessages().size()); + assertEquals("SayHelloRequest", definitions.getMessages().getFirst().getName()); + assertEquals("SayHelloResponse", definitions.getMessages().getLast().getName()); + } + } \ No newline at end of file diff --git a/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java b/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java new file mode 100644 index 0000000000..0ef6321714 --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java @@ -0,0 +1,117 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + +package com.predic8.membrane.core.ws; + +import com.predic8.membrane.core.interceptor.schemavalidation.*; +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.jetbrains.annotations.*; +import org.junit.jupiter.api.*; +import org.slf4j.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class WSDLIncludeImportTest { + + private static final Logger log = LoggerFactory.getLogger(WSDLIncludeImportTest.class); + + @Test + void test() throws Exception { + var defs = getDefinitions("classpath:/ws/include/include.wsdl"); + assertEquals(1, defs.getServices().size()); + assertEquals("http://example.com/test", defs.getTargetNamespace()); + assertEquals(1, defs.getSchemas().size()); + var embedded = defs.getSchemas().getFirst(); + assertEquals("http://example.com/test", embedded.getTargetNamespace()); + assertEquals(3, embedded.getSchemaElements().size()); + assertEquals(List.of("test", "testResponse", "du"), getElementNames(embedded.getSchemaElements())); + System.out.println(embedded.getImports()); + assertEquals(1, embedded.getImports().size()); + var imported = embedded.getImports().getFirst(); + assertEquals("http://example.com/test/data-types", imported.getNamespace()); + assertEquals(List.of("B5", "B6"), getElementNames(imported.getSchema().getSchemaElements())); + } + + @Test + void multiple() throws Exception { + assertEquals(List.of("test", "testResponse", "a", "b", "c"), + getElementNames(getDefinitions("classpath:/ws/include/multiple.wsdl") + .getSchemas().getFirst().getSchemaElements())); + } + + @Test + void cyclicInclude() throws Exception { + var defs = getDefinitions("classpath:/ws/include/cyclic.wsdl"); + assertEquals(1, defs.getSchemas().size()); + var embedded = defs.getSchemas().getFirst(); + assertEquals(1, embedded.getIncludes().size()); + assertEquals(List.of("test", "testResponse", "a", "b"), getElementNames(embedded.getSchemaElements())); + } + + @Test + void cyclicImport() throws Exception { + var defs = getDefinitions("classpath:/ws/import/cyclic.wsdl"); + assertEquals(1, defs.getSchemas().size()); + var embedded = defs.getSchemas().getFirst(); + var a = embedded.getImports().getFirst(); + assertEquals(List.of("a"), getElementNames(a.getSchema().getSchemaElements())); + var b = a.getSchema().getImports().getFirst(); + assertEquals(List.of("b"), getElementNames(b.getSchema().getSchemaElements())); + } + + + @Test + void messageReferencesImport() throws Exception { + var defs = getDefinitions("classpath:/ws/import/message-references-import.wsdl"); + assertEquals(1, defs.getSchemas().size()); + } + + @Test + void embeddedImports() throws Exception { + var defs = getDefinitions("classpath:/ws/import/embedded.wsdl"); + assertEquals(3, defs.getSchemas().size()); + var first = defs.getSchemas().getFirst(); + assertEquals(List.of("getCustomerRequest", "getCustomerResponse"), getElementNames(first.getSchemaElements())); + assertEquals(2, first.getImports().size()); + var firstImport = first.getImports().getFirst(); + assertEquals(List.of("from2"), getElementNames(firstImport.getSchema().getSchemaElements())); + var secondImport = first.getImports().getFirst(); + assertEquals(List.of("from2"), getElementNames(secondImport.getSchema().getSchemaElements())); + var second = defs.getSchemas().get(1); + assertEquals(List.of("from2"), getElementNames(second.getSchemaElements())); + var third = defs.getSchemas().get(2); + assertEquals(List.of("from3"), getElementNames(third.getSchemaElements())); + } + + @Test + void validator() { + var validator = new WSDLValidator(new ResolverMap(), + WSDLIncludeImportTest.class.getResource("/ws/include/include.wsdl").toString(), + "TestService", + (msg, exc) -> log.info("Validation failure: {}", msg), true); + validator.init(); + } + + private static @NotNull List getElementNames(List schemaElements) { + return schemaElements.stream().map(e -> e.getName()).toList(); + } + + private static @NotNull Definitions getDefinitions(String location) throws Exception { + return Definitions.parse(new ResolverMap(), location); + } +} diff --git a/core/src/test/resources/ws/hello-soap12.wsdl b/core/src/test/resources/ws/hello-soap12.wsdl new file mode 100644 index 0000000000..ed853491a0 --- /dev/null +++ b/core/src/test/resources/ws/hello-soap12.wsdl @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 6b6de14ed95a03e0ad3a26d7d0c7cb0ce6a010b2 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Wed, 11 Mar 2026 16:02:27 +0100 Subject: [PATCH 25/28] Refactor schema validation and WSDL parser components - Updated `WSDLMessageElementExtractor` to improve RPC element name handling, ensuring correct direction logic for `INPUT` and `OUTPUT`. - Fixed a formatting issue in `SchemaElement` constructor for better readability. - Simplified lambda usage in `getElementNames` method of `WSDLIncludeImportTest`. - Made `resource` field in `RelativePathRewriter` final for immutability. --- .../schemavalidation/WSDLMessageElementExtractor.java | 6 +++--- .../core/interceptor/server/WSDLPublisherInterceptor.java | 2 +- .../core/util/wsdl/parser/schema/SchemaElement.java | 2 +- .../com/predic8/membrane/core/ws/WSDLIncludeImportTest.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) 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 08b29bea6b..879ef7d930 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 @@ -94,10 +94,10 @@ public static Set getPossibleElements(Definitions definitions, Direction } private static String getElementNameRPC(Operation operation, Direction direction) { - if (direction == INPUT) { - return operation.getName(); + if (direction == OUTPUT) { + return operation.getName() + "Response"; } - return operation.getName() + "Response"; + return operation.getName(); } private static @NotNull Stream getParts(Direction direction, List result) { diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java index 2e2af6ffe1..dd56036ee3 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java @@ -80,7 +80,7 @@ public WSDLPublisherInterceptor() { */ private final class RelativePathRewriter implements PathRewriter { private final Exchange exc; - private String resource; + private final String resource; private RelativePathRewriter(Exchange exc, String resource) { this.exc = exc; diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java index 8bf4fe8d78..ec6d0f25df 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/SchemaElement.java @@ -19,7 +19,7 @@ public class SchemaElement extends WSDLElement { - public SchemaElement(WSDLParserContext ctx,Node node) { + public SchemaElement(WSDLParserContext ctx, Node node) { super(ctx,node); } diff --git a/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java b/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java index 0ef6321714..ed9bf5fa1c 100644 --- a/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java +++ b/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java @@ -108,7 +108,7 @@ void validator() { } private static @NotNull List getElementNames(List schemaElements) { - return schemaElements.stream().map(e -> e.getName()).toList(); + return schemaElements.stream().map(WSDLElement::getName).toList(); } private static @NotNull Definitions getDefinitions(String location) throws Exception { From 4582fdef8a5fb235fc2363f6a87b42f7724e56b1 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Wed, 11 Mar 2026 16:32:54 +0100 Subject: [PATCH 26/28] Refactor schema validation and WSDL components - Split `getPossibleElements` logic in `WSDLMessageElementExtractor` into separate methods for RPC and document styles for better clarity. - Simplified attribute relocation in `Relocator` by removing redundant `shouldProcess` method and streamlining logic. - Fixed test assertions in `WSDLIncludeImportTest` for correcting import validation (`get(1)` usage). - Updated `WSDLPublisherInterceptor` to improve path --- .../WSDLMessageElementExtractor.java | 15 ++++--- .../server/WSDLPublisherInterceptor.java | 39 +++++++------------ .../membrane/core/ws/relocator/Relocator.java | 11 +----- .../core/ws/WSDLIncludeImportTest.java | 5 +-- 4 files changed, 28 insertions(+), 42 deletions(-) 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 879ef7d930..5eab122873 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 @@ -38,21 +38,24 @@ public static Set getPossibleResponseElements(Definitions definitions, St public static Set getPossibleElements(Definitions definitions, Direction direction, String serviceName) { var portTypes = getTypesByStyle(definitions, serviceName); + var result = new HashSet<>(getElementQNameForDocumentStyle(direction, portTypes)); + result.addAll(getElementQNamesForRPCStyle(definitions, direction, portTypes)); + return result; + } - var operationNamesRPC = portTypes.portTypesRPC().stream().map(PortType::getOperations) + private static @NotNull Set getElementQNamesForRPCStyle(Definitions definitions, Direction direction, PortTypesByStyle portTypes) { + return portTypes.portTypesRPC().stream().map(PortType::getOperations) .flatMap(Collection::stream) .filter(op -> !op.getMessagesByDirection(direction).isEmpty()) .map(op -> new QName(definitions.getTargetNamespace(), getElementNameRPC(op, direction))) .collect(toSet()); + } - - var namesDocumentStyle = getParts(direction, portTypes.portTypesDocument()) + private static @NotNull Set getElementQNameForDocumentStyle(Direction direction, PortTypesByStyle portTypes) { + return getParts(direction, portTypes.portTypesDocument()) .map(Part::getElementQName) .filter(Objects::nonNull) .collect(Collectors.toSet()); - - namesDocumentStyle.addAll(operationNamesRPC); - return namesDocumentStyle; } private static @NotNull PortTypesByStyle getTypesByStyle(Definitions definitions, String serviceName) { diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java index dd56036ee3..216dcf55a6 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java @@ -34,7 +34,7 @@ import static com.predic8.membrane.core.http.Response.*; import static com.predic8.membrane.core.interceptor.Outcome.*; import static com.predic8.membrane.core.resolver.ResolverMap.*; -import static com.predic8.membrane.core.util.URLParamUtil.DuplicateKeyOrInvalidFormStrategy.ERROR; +import static com.predic8.membrane.core.util.URLParamUtil.DuplicateKeyOrInvalidFormStrategy.*; /** * @description

@@ -93,11 +93,6 @@ public String rewrite(String path) { if (!path.contains("://") && !path.startsWith("/")) { path = combine(resource, path); } - - if (!new File(path).exists()) { - throw new ResourceRetrievalException("Could not find resource: " + path); - } - return "./%s?xsd=%s".formatted(URLUtil.getNameComponent(router.getConfiguration().getUriFactory(), exc.getDestinations().getFirst()), resolveToNumber(path)); } catch (Exception e) { throw new RuntimeException(e); @@ -105,19 +100,15 @@ public String rewrite(String path) { } private @NotNull String resolveToNumber(String path) { - if (!new File(path).exists()) { - throw new RuntimeException("!!!!Could not find resource: " + path); - } - synchronized (paths) { - if (paths_reverse.containsKey(path)) { - return paths_reverse.get(path).toString(); - } - int n = paths.size() + 1; - paths.put(n, path); - paths_reverse.put(path, n); - documents_to_process.add(path); - return Integer.toString(n); + if (pathsReverse.containsKey(path)) { + return pathsReverse.get(path).toString(); } + int n = paths.size() + 1; + paths.put(n, path); + pathsReverse.put(path, n); + documentsToProcess.add(path); + return Integer.toString(n); + } } @@ -137,10 +128,10 @@ public void init() { private final Map paths = new HashMap<>(); @GuardedBy("paths") - private final Map paths_reverse = new HashMap<>(); + private final Map pathsReverse = new HashMap<>(); @GuardedBy("paths") - private final Queue documents_to_process = new LinkedList<>(); + private final Queue documentsToProcess = new LinkedList<>(); private void processDocuments(Exchange exc) { // exc.response is only temporarily used so we can call the WSDLInterceptor @@ -148,7 +139,7 @@ private void processDocuments(Exchange exc) { synchronized (paths) { try { while (true) { - var doc = documents_to_process.poll(); + var doc = documentsToProcess.poll(); if (doc == null) break; log.debug("processing: {}", doc); @@ -177,9 +168,9 @@ public void setWsdl(String wsdl) { this.wsdl = wsdl; synchronized (paths) { paths.clear(); - paths_reverse.clear(); - documents_to_process.clear(); - documents_to_process.add(wsdl); + pathsReverse.clear(); + documentsToProcess.clear(); + documentsToProcess.add(wsdl); } } diff --git a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java index db607724c4..25f0639209 100644 --- a/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java +++ b/core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java @@ -87,15 +87,8 @@ private XMLEvent process(XMLEventReader parser) throws XMLStreamException { if (!event.isStartElement()) return event; - return relocatingAttributes.entrySet().stream() - .filter(e -> shouldProcess(e, event)) - .findFirst() - .map(e -> replace(event, e.getValue())) - .orElse(event); - } - - private boolean shouldProcess(Map.Entry e, XMLEvent event) { - return getElementName(event).equals(e.getKey()); + var attr = relocatingAttributes.get(getElementName(event)); + return attr != null ? replace(event, attr) : event; } private QName getElementName(XMLEvent event) { diff --git a/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java b/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java index ed9bf5fa1c..260043161f 100644 --- a/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java +++ b/core/src/test/java/com/predic8/membrane/core/ws/WSDLIncludeImportTest.java @@ -40,7 +40,6 @@ void test() throws Exception { assertEquals("http://example.com/test", embedded.getTargetNamespace()); assertEquals(3, embedded.getSchemaElements().size()); assertEquals(List.of("test", "testResponse", "du"), getElementNames(embedded.getSchemaElements())); - System.out.println(embedded.getImports()); assertEquals(1, embedded.getImports().size()); var imported = embedded.getImports().getFirst(); assertEquals("http://example.com/test/data-types", imported.getNamespace()); @@ -90,8 +89,8 @@ void embeddedImports() throws Exception { assertEquals(2, first.getImports().size()); var firstImport = first.getImports().getFirst(); assertEquals(List.of("from2"), getElementNames(firstImport.getSchema().getSchemaElements())); - var secondImport = first.getImports().getFirst(); - assertEquals(List.of("from2"), getElementNames(secondImport.getSchema().getSchemaElements())); + var secondImport = first.getImports().get(1); + assertEquals(List.of("from3"), getElementNames(secondImport.getSchema().getSchemaElements())); var second = defs.getSchemas().get(1); assertEquals(List.of("from2"), getElementNames(second.getSchemaElements())); var third = defs.getSchemas().get(2); From 2ad68834b29c4b537a7c578fd720a89e586455cc Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Wed, 11 Mar 2026 22:53:45 +0100 Subject: [PATCH 27/28] Refactor and enhance URI handling and WSDL/XML components - Added `normalize` method in `URIUtil` for standardizing paths and URIs. - Synchronized `resolveToNumber` method in `WSDLPublisherInterceptor` for thread safety. - Replaced hardcoded schema namespace with constants in `WSDLSchemaExtractor`. - Removed redundant imports and refactored commentary in multiple classes for clarity. - Added `XMLNS_NS` constant to `Constants`. --- .../com/predic8/membrane/annot/Constants.java | 7 ++++--- .../AuthorizationService.java | 1 - .../schemavalidation/WSDLSchemaExtractor.java | 9 ++++----- .../server/WSDLPublisherInterceptor.java | 17 +++++++++-------- .../soap/WebServiceExplorerInterceptor.java | 5 +---- .../com/predic8/membrane/core/util/URIUtil.java | 10 ++++++++++ 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/annot/src/main/java/com/predic8/membrane/annot/Constants.java b/annot/src/main/java/com/predic8/membrane/annot/Constants.java index 9a7835e624..50c0d9b42c 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/Constants.java +++ b/annot/src/main/java/com/predic8/membrane/annot/Constants.java @@ -14,8 +14,7 @@ package com.predic8.membrane.annot; -import com.sun.net.httpserver.*; - +import javax.xml.*; import javax.xml.namespace.*; import java.io.*; import java.util.*; @@ -63,6 +62,8 @@ public class Constants { public static final String HTTP_VERSION_11 = "1.1"; + public static final String XMLNS_NS = "http://www.w3.org/2000/xmlns/"; + public static final String WSDL11_NS = "http://schemas.xmlsoap.org/wsdl/"; public static final String WSDL_SOAP11_NS = "http://schemas.xmlsoap.org/wsdl/soap/"; @@ -89,7 +90,7 @@ public enum SoapVersion { SOAP11, SOAP12, SOAP20, UNKNOWN } /** - * Used for {@link Request}-to-XML and XML-to-{@link Response} conversions. + * Used for Request-to-XML and XML-to-Response conversions. * See {@link REST2SOAPInterceptor}. */ public static final String HTTP_NS = "http://membrane-soa.org/schemas/http/v1/"; diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java b/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java index 82e2c1dce1..cb53c61be8 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/oauth2/authorizationservice/AuthorizationService.java @@ -27,7 +27,6 @@ import com.predic8.membrane.core.transport.http.*; import com.predic8.membrane.core.transport.http.client.*; import com.predic8.membrane.core.transport.ssl.*; -import com.predic8.membrane.core.util.*; import jakarta.mail.internet.*; import org.jose4j.jwt.*; import org.jose4j.lang.*; diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java index e6bd234d8a..029532f59f 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/WSDLSchemaExtractor.java @@ -14,6 +14,7 @@ package com.predic8.membrane.core.interceptor.schemavalidation; +import com.predic8.membrane.annot.*; import org.w3c.dom.*; import org.xml.sax.*; @@ -24,20 +25,19 @@ import java.io.*; import java.util.*; +import static com.predic8.membrane.annot.Constants.XMLNS_NS; +import static com.predic8.membrane.annot.Constants.XSD_NS; import static javax.xml.XMLConstants.*; public final class WSDLSchemaExtractor { - private static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"; // TODO - private static final String XMLNS_NS = XMLConstants.XMLNS_ATTRIBUTE_NS_URI; - private WSDLSchemaExtractor() { } public static List getSchemas(Element wsdl) { try { var result = new ArrayList(); - var schemas = wsdl.getElementsByTagNameNS(XML_SCHEMA_NS, "schema"); + var schemas = wsdl.getElementsByTagNameNS(XSD_NS, "schema"); for (int i = 0; i < schemas.getLength(); i++) { result.add(extractSchema((Element) schemas.item(i), getNamespaceDeclarations(wsdl))); @@ -62,7 +62,6 @@ private static List getNamespaceDeclarations(Element element) { private static Document extractSchema(Element originalSchema, List definitionNamespaces) throws Exception { var fac = DocumentBuilderFactory.newInstance(); - // fac.setFeature(FEATURE_SECURE_PROCESSING, true); fac.setNamespaceAware(true); var builder = fac.newDocumentBuilder(); diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java index 216dcf55a6..b859b9223b 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java @@ -100,15 +100,16 @@ public String rewrite(String path) { } private @NotNull String resolveToNumber(String path) { - if (pathsReverse.containsKey(path)) { - return pathsReverse.get(path).toString(); + synchronized (paths) { + if (pathsReverse.containsKey(path)) { + return pathsReverse.get(path).toString(); + } + int n = paths.size() + 1; + paths.put(n, path); + pathsReverse.put(path, n); + documentsToProcess.add(path); + return Integer.toString(n); } - int n = paths.size() + 1; - paths.put(n, path); - pathsReverse.put(path, n); - documentsToProcess.add(path); - return Integer.toString(n); - } } diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java index f0e65ea73c..0175031880 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/WebServiceExplorerInterceptor.java @@ -133,10 +133,7 @@ protected void createContent() { for (com.predic8.membrane.core.util.wsdl.parser.PortType pt : ports.stream().map(Port::getBinding).map(Binding::getPortType).toList()) { h2().text("Port Type: " + pt.getName()).end(); -// Documentation d = pt.getDocumentation(); @TODO -// if (d != null) { -// p().text("Documentation: " + d).end(); -// } + // @TODO show pt.getDocumentation() wsdl:documentation here when it is implemented } var binding = port.getBinding(); diff --git a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java index 9c653ce833..14a923e439 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java @@ -139,6 +139,16 @@ public static String normalizeSingleDot(String uri) { return sb.toString(); } + /** + * Normalizes the given path or URI. This method handles various formats of paths, + * including filesystem paths, URIs, and paths with potential Windows drive letters. + * The normalization involves resolving relative components, such as "." or "..", + * and ensuring the path conforms to a standardized format. + * + * @param location the path or URI string to normalize. It must not be null or empty. + * @return the normalized path or URI as a string. + * @throws IllegalArgumentException if the input location is null or empty. + */ public static String normalize(String location) { if (location == null || location.isEmpty()) throw new IllegalArgumentException("location must not be null or empty"); From 919fe8537fc819162ee0e4beed82dd95a2f7ab0d Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Thu, 12 Mar 2026 18:44:32 +0100 Subject: [PATCH 28/28] Refactor URI normalization: rename method for clarity, enhance exception handling, and update references in tests and implementations --- .../server/WSDLPublisherInterceptor.java | 2 +- .../predic8/membrane/core/util/URIUtil.java | 11 +++--- .../parser/schema/AbstractIncludeImport.java | 2 +- .../membrane/core/util/URIUtilTest.java | 37 +++++++++++-------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java index b859b9223b..ebb9d8af02 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java @@ -234,7 +234,7 @@ private Outcome handleRequestInternal(final Exchange exc) throws Exception { return RETURN; } } catch (NumberFormatException e) { - exc.setResponse(HttpUtil.setHTMLErrorResponse(Response.internalServerError(), "Bad parameter format.", "")); + exc.setResponse(HttpUtil.setHTMLErrorResponse(Response.badRequest(), "Bad parameter format.", "")); return ABORT; } catch (ResourceRetrievalException e) { exc.setResponse(notFound().build()); diff --git a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java index 14a923e439..0d9e4761a5 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/util/URIUtil.java @@ -140,16 +140,17 @@ public static String normalizeSingleDot(String uri) { } /** - * Normalizes the given path or URI. This method handles various formats of paths, + * Normalizes the given path or URI and resolves it to an absolute path. + * This method handles various formats of paths, * including filesystem paths, URIs, and paths with potential Windows drive letters. * The normalization involves resolving relative components, such as "." or "..", * and ensuring the path conforms to a standardized format. * - * @param location the path or URI string to normalize. It must not be null or empty. - * @return the normalized path or URI as a string. - * @throws IllegalArgumentException if the input location is null or empty. + * @param location the path or URI string to normalize. + * @return the normalized absolute path or URI as a string. + * @throws IllegalArgumentException if the input location is null, empty, or contains malformed URI syntax. */ - public static String normalize(String location) { + public static String getNormalizedAbsolutePathOrUri(String location) { if (location == null || location.isEmpty()) throw new IllegalArgumentException("location must not be null or empty"); diff --git a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java index ed0c56088c..722dc4aa30 100644 --- a/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java +++ b/core/src/main/java/com/predic8/membrane/core/util/wsdl/parser/schema/AbstractIncludeImport.java @@ -52,7 +52,7 @@ protected Schema getSchema(WSDLParserContext ctx) { } private String resolve(WSDLParserContext ctx) { - return URIUtil.normalize(ResolverMap.combine(ctx.basePath(), schemaLocation)); + return URIUtil.getNormalizedAbsolutePathOrUri(ResolverMap.combine(ctx.basePath(), schemaLocation)); } public String getSchemaLocation() { diff --git a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java index 23488144be..2d08c25a27 100644 --- a/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java +++ b/core/src/test/java/com/predic8/membrane/core/util/URIUtilTest.java @@ -215,55 +215,62 @@ void convertPath2FileURITest() throws URISyntaxException { class normalize { @Test void throwsOnNull() { - assertThrows(IllegalArgumentException.class, () -> normalize(null)); + assertThrows(IllegalArgumentException.class, () -> getNormalizedAbsolutePathOrUri(null)); } + @Test void throwsOnEmpty() { - assertThrows(IllegalArgumentException.class, () -> normalize("")); + assertThrows(IllegalArgumentException.class, () -> getNormalizedAbsolutePathOrUri("")); } @Test void normalizesRelativeFilesystemPath() { var input = "foo/../bar/test.txt"; var expected = Path.of(input).toAbsolutePath().normalize().toString(); - assertEquals(expected, normalize(input)); + assertEquals(expected, getNormalizedAbsolutePathOrUri(input)); } @Test void normalizesAbsoluteFilesystemPath() { var input = Path.of("foo", "..", "bar").toAbsolutePath().toString(); var expected = Path.of(input).toAbsolutePath().normalize().toString(); - assertEquals(expected, normalize(input)); + assertEquals(expected, getNormalizedAbsolutePathOrUri(input)); } @Test void normalizesFileUri() { - assertEquals("file:/test.xml", normalize("file:///tmp/../test.xml")); + assertEquals("file:/test.xml", getNormalizedAbsolutePathOrUri("file:///tmp/../test.xml")); } @Test void normalizesHttpUri() { - assertEquals("http://example.com/b/wsdl.xsd", normalize("http://example.com/a/../b/wsdl.xsd")); + assertEquals("http://example.com/b/wsdl.xsd", getNormalizedAbsolutePathOrUri("http://example.com/a/../b/wsdl.xsd")); } + @Test + void normalizesHttpUriWithQuery() { + assertEquals("http://example.com/b/wsdl.xsd?a=a/../b", getNormalizedAbsolutePathOrUri("http://example.com/a/../b/wsdl.xsd?a=a/../b")); + } + + @Test void classpathUri() { // The '..' within the path portion normalizes correctly. - assertEquals("classpath://authority/xsd/test.xsd", normalize("classpath://authority/schema/../xsd/test.xsd")); + assertEquals("classpath://authority/xsd/test.xsd", getNormalizedAbsolutePathOrUri("classpath://authority/schema/../xsd/test.xsd")); } @Test void keepsClasspathUri() { // The '..' at the start of the path (after authority) cannot traverse above the authority, so it's preserved. - assertEquals("classpath://authority/../xsd/test.xsd", normalize("classpath://authority/../xsd/test.xsd")); + assertEquals("classpath://authority/../xsd/test.xsd", getNormalizedAbsolutePathOrUri("classpath://authority/../xsd/test.xsd")); } @Test void normalizesUnixLikePath() { var input = "/tmp/../var/data.xsd"; var expected = Path.of(input).toAbsolutePath().normalize().toString(); - assertEquals(expected, normalize(input)); + assertEquals(expected, getNormalizedAbsolutePathOrUri(input)); } @Test @@ -271,7 +278,7 @@ void normalizesUnixLikePath() { void handlesWindowsDriveLetterPath() { var input = "C:\\temp\\..\\data\\test.xsd"; var expected = Path.of(input).toAbsolutePath().normalize().toString(); - assertEquals(expected, normalize(input)); + assertEquals(expected, getNormalizedAbsolutePathOrUri(input)); } @Test @@ -279,30 +286,30 @@ void handlesWindowsDriveLetterPath() { void handlesWindowsDriveRelativePath() { var input = "C:temp\\..\\data\\test.xsd"; var expected = Path.of(input).toAbsolutePath().normalize().toString(); - assertEquals(expected, normalize(input)); + assertEquals(expected, getNormalizedAbsolutePathOrUri(input)); } @Test void keepsRelativeUriWithQuery() { - assertEquals("schema.xsd?version=1", normalize("schema.xsd?version=1") + assertEquals("schema.xsd?version=1", getNormalizedAbsolutePathOrUri("schema.xsd?version=1") ); } @Test void keepsRelativeUriWithFragment() { - assertEquals("../types.xsd#frag", normalize("../types.xsd#frag") + assertEquals("../types.xsd#frag", getNormalizedAbsolutePathOrUri("../types.xsd#frag") ); } @Test void keepsRelativeUriWithQueryAndFragment() { - assertEquals("../types.xsd?version=1#frag", normalize("../types.xsd?version=1#frag") + assertEquals("../types.xsd?version=1#frag", getNormalizedAbsolutePathOrUri("../types.xsd?version=1#frag") ); } @Test void keepsSchemeRelativeReference() { - assertEquals("//example.com/schema.xsd?version=1", normalize("//example.com/schema.xsd?version=1")); + assertEquals("//example.com/schema.xsd?version=1", getNormalizedAbsolutePathOrUri("//example.com/schema.xsd?version=1")); } } } \ No newline at end of file