From ce94e262351928aeb70b74afdff948b2f8c989b4 Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sat, 1 Nov 2025 17:02:04 +0100
Subject: [PATCH 01/16] feat: XML namespace declarations for XPath
---
.../predic8/membrane/core/http/Request.java | 6 +
.../core/interceptor/XMLNamespaceSupport.java | 10 ++
.../extractors/ApiKeyExpressionExtractor.java | 28 +++--
.../balancer/PolyglotSessionIdExtractor.java | 6 +-
.../core/interceptor/flow/ForInterceptor.java | 2 +-
.../core/interceptor/flow/IfInterceptor.java | 20 +++-
.../core/interceptor/flow/choice/Case.java | 28 +++--
.../idempotency/IdempotencyInterceptor.java | 21 +---
...AbstractExchangeExpressionInterceptor.java | 3 +-
.../lang/AbstractLanguageInterceptor.java | 20 +++-
.../lang/AbstractSetterInterceptor.java | 15 ++-
.../core/interceptor/lang/Namespaces.java | 103 ++++++++++++++++++
.../ratelimit/RateLimitInterceptor.java | 2 +-
.../soap/SampleSoapServiceInterceptor.java | 2 +-
.../core/lang/ExchangeExpression.java | 41 ++++++-
.../core/lang/TemplateExchangeExpression.java | 13 ++-
.../lang/groovy/GroovyExchangeExpression.java | 4 +-
.../lang/xpath/XPathExchangeExpression.java | 31 +++++-
.../core/openapi/serviceproxy/APIProxy.java | 22 +++-
.../core/proxies/AbstractServiceProxy.java | 23 +++-
.../membrane/core/util/ExceptionUtil.java | 12 ++
.../predic8/membrane/core/util/SOAPUtil.java | 2 +-
.../membrane/core/util/{ => xml}/XMLUtil.java | 2 +-
.../util/xml/parser/HardenedXmlParser.java | 89 +++++++++++++++
.../util/xml/parser/XmlParseException.java | 34 ++++++
.../core/util/xml/parser/XmlParser.java | 34 ++++++
.../core/interceptor/lang/NamespacesTest.java | 42 +++++++
.../lang/AbstractExchangeExpressionTest.java | 6 +-
.../lang/TemplateExchangeExpressionTest.java | 4 +-
.../JsonpathExchangeExpressionTest.java | 4 +-
.../xpath/XPathExchangeExpressionTest.java | 26 +++++
.../membrane/core/util/XMLUtilTest.java | 1 +
.../examples/templating/json/README.md | 6 +-
.../examples/xml/namespaces/README.md | 16 +++
.../examples/xml/namespaces/membrane.cmd | 17 +++
.../examples/xml/namespaces/membrane.sh | 21 ++++
.../examples/xml/namespaces/person.xml | 6 +
.../examples/xml/namespaces/proxies.xml | 54 +++++++++
.../examples/xml/namespaces/requests.http | 9 ++
.../test/VersioningSoapXsltExampleTest.java | 3 +
.../xml/namespaces/NamespacesExampleTest.java | 31 ++++++
docs/ROADMAP.md | 2 +
42 files changed, 736 insertions(+), 85 deletions(-)
create mode 100644 core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java
create mode 100644 core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
rename core/src/main/java/com/predic8/membrane/core/util/{ => xml}/XMLUtil.java (98%)
create mode 100644 core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
create mode 100644 core/src/main/java/com/predic8/membrane/core/util/xml/parser/XmlParseException.java
create mode 100644 core/src/main/java/com/predic8/membrane/core/util/xml/parser/XmlParser.java
create mode 100644 core/src/test/java/com/predic8/membrane/core/interceptor/lang/NamespacesTest.java
create mode 100644 distribution/examples/xml/namespaces/README.md
create mode 100644 distribution/examples/xml/namespaces/membrane.cmd
create mode 100755 distribution/examples/xml/namespaces/membrane.sh
create mode 100644 distribution/examples/xml/namespaces/person.xml
create mode 100644 distribution/examples/xml/namespaces/proxies.xml
create mode 100644 distribution/examples/xml/namespaces/requests.http
create mode 100644 distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
diff --git a/core/src/main/java/com/predic8/membrane/core/http/Request.java b/core/src/main/java/com/predic8/membrane/core/http/Request.java
index 7c3bd0d700..3f3724ee77 100644
--- a/core/src/main/java/com/predic8/membrane/core/http/Request.java
+++ b/core/src/main/java/com/predic8/membrane/core/http/Request.java
@@ -311,6 +311,12 @@ public Builder json(String body) {
return this;
}
+ public Builder xml(String body) {
+ req.setBodyContent(body.getBytes(UTF_8));
+ req.header.setContentType(APPLICATION_XML);
+ return this;
+ }
+
public Builder post(URIFactory uriFactory, String url) throws URISyntaxException {
return method(Request.METHOD_POST).url(uriFactory, url);
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java b/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java
new file mode 100644
index 0000000000..27bc7780ad
--- /dev/null
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java
@@ -0,0 +1,10 @@
+package com.predic8.membrane.core.interceptor;
+
+import com.predic8.membrane.core.interceptor.lang.*;
+
+public interface XMLNamespaceSupport {
+
+ void setNamespaces(Namespaces namespaces);
+
+ Namespaces getNamespaces();
+}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
index 80e4ab7fbb..6b73367a93 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
@@ -13,13 +13,13 @@
limitations under the License. */
package com.predic8.membrane.core.interceptor.apikey.extractors;
-import com.predic8.membrane.annot.MCAttribute;
-import com.predic8.membrane.annot.MCElement;
+import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.Router;
import com.predic8.membrane.core.exchange.Exchange;
-import com.predic8.membrane.core.interceptor.lang.Polyglot;
-import com.predic8.membrane.core.lang.ExchangeExpression;
-import com.predic8.membrane.core.lang.ExchangeExpression.Language;
+import com.predic8.membrane.core.interceptor.*;
+import com.predic8.membrane.core.interceptor.lang.*;
+import com.predic8.membrane.core.lang.*;
+import com.predic8.membrane.core.lang.ExchangeExpression.*;
import java.util.Optional;
@@ -45,15 +45,16 @@
* @topic 3. Security and Validation
*/
@MCElement(name="expressionExtractor", topLevel = false)
-public class ApiKeyExpressionExtractor implements ApiKeyExtractor, Polyglot {
+public class ApiKeyExpressionExtractor implements ApiKeyExtractor, Polyglot, XMLNamespaceSupport {
private String expression = "";
private Language language = SPEL;
private ExchangeExpression exchangeExpression;
+ private Namespaces namespaces;
@Override
public void init(Router router) {
- exchangeExpression = ExchangeExpression.newInstance(router, language, expression);
+ exchangeExpression = ExchangeExpression.newInstance(new InterceptorAdapter(router,namespaces), language, expression);
}
@Override
@@ -93,4 +94,17 @@ public String getExpression() {
public void setExpression(String expression) {
this.expression = expression;
}
+
+ /**
+ * Declaration of XML namespaces for XPath expressions.
+ * @param namespaces
+ */
+ @MCChildElement(allowForeign = true)
+ public void setNamespaces(Namespaces namespaces) {
+ this.namespaces = namespaces;
+ }
+
+ public Namespaces getNamespaces() {
+ return namespaces;
+ }
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
index f3ec2b41c1..b69e3b19ff 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
@@ -21,8 +21,8 @@
import com.predic8.membrane.core.exchange.Exchange;
import com.predic8.membrane.core.interceptor.Interceptor.Flow;
import com.predic8.membrane.core.interceptor.lang.Polyglot;
-import com.predic8.membrane.core.lang.ExchangeExpression;
-import com.predic8.membrane.core.lang.ExchangeExpression.Language;
+import com.predic8.membrane.core.lang.*;
+import com.predic8.membrane.core.lang.ExchangeExpression.*;
@MCElement(name = "sessionIdExtractor")
public class PolyglotSessionIdExtractor extends AbstractXmlElement implements SessionIdExtractor, Polyglot {
@@ -33,7 +33,7 @@ public class PolyglotSessionIdExtractor extends AbstractXmlElement implements Se
public void init(Router router) {
if (sessionSource != null && !sessionSource.isEmpty()) {
- exchangeExpression = ExchangeExpression.newInstance(router, language, sessionSource);
+ exchangeExpression = ExchangeExpression.newInstance(new InterceptorAdapter(router), language, sessionSource);
}
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java
index 49ad0385bf..fb0dd0e199 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java
@@ -56,7 +56,7 @@ public class ForInterceptor extends AbstractFlowWithChildrenInterceptor {
public void init() {
super.init();
try {
- exchangeExpression = ExchangeExpression.newInstance(router, language, in);
+ exchangeExpression = ExchangeExpression.newInstance(this, language, in);
} catch (ConfigurationException ce) {
throw new ConfigurationException(ce.getMessage() + """
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
index 881bf33231..77223fce99 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
@@ -17,8 +17,10 @@
import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.interceptor.*;
+import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
+import com.predic8.membrane.core.lang.xpath.*;
import org.slf4j.*;
import static com.predic8.membrane.core.exceptions.ProblemDetails.*;
@@ -37,7 +39,7 @@
* @topic 1. Proxies and Flow
*/
@MCElement(name = "if")
-public class IfInterceptor extends AbstractFlowWithChildrenInterceptor {
+public class IfInterceptor extends AbstractFlowWithChildrenInterceptor implements XMLNamespaceSupport {
private static final Logger log = LoggerFactory.getLogger(IfInterceptor.class);
private String test;
@@ -52,7 +54,7 @@ public IfInterceptor() {
@Override
public void init() {
super.init();
- exchangeExpression = ExchangeExpression.newInstance(router, language, test);
+ exchangeExpression = ExchangeExpression.newInstance(this, language, test);
}
@Override
@@ -130,4 +132,18 @@ public String getShortDescription() {
ret.append("
}");
return ret.toString();
}
+
+ /**
+ * XML namespaces to be used in expressions.
+ */
+ protected Namespaces namespaces;
+
+ @MCChildElement(allowForeign = true,order = 10)
+ public void setNamespaces(Namespaces namespaces) {
+ this.namespaces = namespaces;
+ }
+
+ public Namespaces getNamespaces() {
+ return namespaces;
+ }
}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
index b71b80dfe6..21ae81686c 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
@@ -13,30 +13,31 @@
limitations under the License. */
package com.predic8.membrane.core.interceptor.flow.choice;
-import com.predic8.membrane.annot.MCAttribute;
-import com.predic8.membrane.annot.MCElement;
-import com.predic8.membrane.annot.Required;
+import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.Router;
import com.predic8.membrane.core.exchange.Exchange;
+import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.interceptor.Interceptor.Flow;
-import com.predic8.membrane.core.lang.ExchangeExpression;
-import com.predic8.membrane.core.lang.ExchangeExpression.Language;
+import com.predic8.membrane.core.interceptor.lang.*;
+import com.predic8.membrane.core.lang.*;
+import com.predic8.membrane.core.lang.ExchangeExpression.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL;
@MCElement(name = "case", topLevel = false)
-public class Case extends InterceptorContainer {
+public class Case extends InterceptorContainer implements XMLNamespaceSupport {
private static final Logger log = LoggerFactory.getLogger(Case.class);
private String test;
private Language language = SPEL;
private ExchangeExpression exchangeExpression;
+ private Namespaces namespaces;
public void init(Router router) {
- exchangeExpression = ExchangeExpression.newInstance(router, language, test);
+ exchangeExpression = ExchangeExpression.newInstance( new InterceptorAdapter(router,namespaces), language, test);
}
boolean evaluate(Exchange exc, Flow flow) {
@@ -76,4 +77,17 @@ public String getTest() {
public void setTest(String test) {
this.test = test;
}
+
+ /**
+ * Declaration of XML namespaces for XPath expressions.
+ * @param namespaces
+ */
+ @MCChildElement(allowForeign = true,order = 10)
+ public void setNamespaces(Namespaces namespaces) {
+ this.namespaces = namespaces;
+ }
+
+ public Namespaces getNamespaces() {
+ return namespaces;
+ }
}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
index 6fe142e3fe..d86962230f 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
@@ -22,6 +22,7 @@
import com.predic8.membrane.core.exchange.Exchange;
import com.predic8.membrane.core.interceptor.AbstractInterceptor;
import com.predic8.membrane.core.interceptor.Outcome;
+import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression;
import com.predic8.membrane.core.lang.ExchangeExpression.Language;
@@ -43,18 +44,17 @@
* @topic 3. Security and Validation
*/
@MCElement(name = "idempotency")
-public class IdempotencyInterceptor extends AbstractInterceptor {
+public class IdempotencyInterceptor extends AbstractLanguageInterceptor {
private String key;
private ExchangeExpression exchangeExpression;
- private Language language = SPEL;
private int expiration = 3600;
private Cache processedKeys;
@Override
public void init() {
super.init();
- exchangeExpression = ExchangeExpression.newInstance(router, language, key);
+ exchangeExpression = ExchangeExpression.newInstance(this, language, key);
processedKeys = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(expiration, TimeUnit.SECONDS)
@@ -104,17 +104,6 @@ public void setKey(String key) {
this.key = key;
}
- /**
- * @description Language used to interpret the expression (e.g., spel, jsonpath, xpath).
- * Determines how the key string will be evaluated.
- * @default SpEL
- * @example jsonpath
- */
- @MCAttribute
- public void setLanguage(Language language) {
- this.language = language;
- }
-
/**
* @description Time in seconds after which idempotency keys automatically expire.
* Useful to avoid memory leaks in long-running systems.
@@ -136,10 +125,6 @@ public String getKey() {
return key;
}
- public Language getLanguage() {
- return language;
- }
-
public int getExpiration() {
return expiration;
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractExchangeExpressionInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractExchangeExpressionInterceptor.java
index 4cd140f45a..250ea95d73 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractExchangeExpressionInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractExchangeExpressionInterceptor.java
@@ -27,7 +27,6 @@ public void init() {
}
protected ExchangeExpression getExchangeExpression() {
- return TemplateExchangeExpression.newInstance(router, language, expression);
+ return TemplateExchangeExpression.newInstance(this, language, expression);
}
-
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
index d1122a2627..39df993242 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
@@ -14,15 +14,15 @@
package com.predic8.membrane.core.interceptor.lang;
-import com.predic8.membrane.annot.MCAttribute;
-import com.predic8.membrane.core.interceptor.AbstractInterceptor;
+import com.predic8.membrane.annot.*;
+import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.lang.ExchangeExpression.Language;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL;
-abstract class AbstractLanguageInterceptor extends AbstractInterceptor implements Polyglot{
+public abstract class AbstractLanguageInterceptor extends AbstractInterceptor implements Polyglot, XMLNamespaceSupport {
private static final Logger log = LoggerFactory.getLogger(AbstractLanguageInterceptor.class);
@@ -30,6 +30,7 @@ abstract class AbstractLanguageInterceptor extends AbstractInterceptor implement
* SpEL is default
*/
protected Language language = SPEL;
+ protected Namespaces namespaces;
public String getLanguage() {
return language.name();
@@ -44,4 +45,17 @@ public String getLanguage() {
public void setLanguage(Language language) {
this.language = language;
}
+
+ /**
+ * Declaration of XML namespaces for XPath expressions.
+ * @param namespaces
+ */
+ @MCChildElement(allowForeign = true)
+ public void setNamespaces(Namespaces namespaces) {
+ this.namespaces = namespaces;
+ }
+
+ public Namespaces getNamespaces() {
+ return namespaces;
+ }
}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java
index ba8eb9f272..e89e2c4e28 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java
@@ -17,12 +17,13 @@
import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.interceptor.*;
+import com.predic8.membrane.core.util.*;
import org.slf4j.*;
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.interceptor.Outcome.ABORT;
public abstract class AbstractSetterInterceptor extends AbstractExchangeExpressionInterceptor {
@@ -50,19 +51,21 @@ private Outcome handleInternal(Exchange exchange, Flow flow) {
try {
setValue(exchange, flow, exchangeExpression.evaluate(exchange, flow, getExpressionReturnType()));
} catch (Exception e) {
- log.error("While evaluating expression {} for field {}: {}", expression, fieldName,e.getMessage());
+ var root = ExceptionUtil.getRootCause(e);
+ var message = "While evaluating expression %s for field %s: %s".formatted(expression, fieldName, root.getMessage());
+ log.info(message);
+
if (failOnError) {
- internal(getRouter().isProduction(),getDisplayName())
+ internal(getRouter().isProduction(), getDisplayName())
.title("Error evaluating expression!")
.internal("field", fieldName)
.internal("value", expression)
- .exception(e)
+ .exception(root)
.stacktrace(false)
.buildAndSetResponse(exchange);
return ABORT;
- } else {
- log.info("Error evaluating {} but 'FailOnError' is false therefore ignoring. Exception :{}", expression,e);
}
+ log.info("Error evaluating {} but 'FailOnError' is false therefore ignoring: {}", expression, message);
}
return CONTINUE;
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
new file mode 100644
index 0000000000..9d07f2c267
--- /dev/null
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
@@ -0,0 +1,103 @@
+/* Copyright 2025 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.lang;
+
+import com.predic8.membrane.annot.*;
+
+import javax.xml.namespace.*;
+import java.util.*;
+
+import static javax.xml.XMLConstants.NULL_NS_URI;
+
+@MCElement(name="namespaces", topLevel = true)
+public class Namespaces {
+
+ //NamespaceContext nsContext;
+ private List namespaces;
+ private NamespaceContextImpl nsContext = new NamespaceContextImpl();
+
+ public NamespaceContext getNamespaceContext() {
+ return nsContext;
+ }
+
+ /**
+ * @description Defines a regex and a replacement for the rewriting of the URI.
+ */
+ @MCChildElement(allowForeign = false)
+ public void setNamespace(List namespace) {
+ this.namespaces = namespace;
+ }
+
+ public List getNamespace() {
+ return namespaces;
+ }
+
+ @MCElement(name = "namespace", topLevel = false, id = "xml-namespace")
+ public static class Namespace {
+
+ public String prefix;
+ public String uri;
+
+ public Namespace() {
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ @MCAttribute
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ @MCAttribute
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+ }
+
+ class NamespaceContextImpl implements NamespaceContext {
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ return namespaces.stream()
+ .filter(ns -> prefix.equals(ns.prefix))
+ .findFirst()
+ .map(ns -> ns.uri)
+ .orElse(NULL_NS_URI);
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ return namespaces.stream()
+ .filter(ns -> namespaceURI.equals(ns.uri))
+ .map(ns -> ns.prefix)
+ .findFirst()
+ .orElse(null);
+ }
+
+ @Override
+ public Iterator getPrefixes(String namespaceURI) {
+ return namespaces.stream()
+ .filter(ns -> namespaceURI.equals(ns.uri))
+ .map(ns -> ns.prefix)
+ .iterator();
+ }
+ }
+}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java
index c89b52315c..46335c1c85 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java
@@ -93,7 +93,7 @@ protected ExchangeExpression getExchangeExpression() {
// If there is no expression use the client IP
if (expression.isEmpty())
return null;
- return ExchangeExpression.newInstance(router, language, expression);
+ return ExchangeExpression.newInstance(this, language, expression);
}
@Override
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/SampleSoapServiceInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/SampleSoapServiceInterceptor.java
index 8231c2e4b8..49f6dd1385 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/soap/SampleSoapServiceInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/soap/SampleSoapServiceInterceptor.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.openapi.util.Utils.*;
-import static com.predic8.membrane.core.util.XMLUtil.*;
+import static com.predic8.membrane.core.util.xml.XMLUtil.*;
import static java.util.Objects.*;
import static javax.xml.stream.XMLStreamConstants.*;
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
index e6f1651c62..f451522e44 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
@@ -17,6 +17,7 @@
import com.predic8.membrane.core.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.interceptor.*;
+import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.groovy.*;
import com.predic8.membrane.core.lang.jsonpath.*;
import com.predic8.membrane.core.lang.spel.*;
@@ -48,12 +49,46 @@ enum Language {GROOVY, SPEL, XPATH, JSONPATH}
*/
T evaluate(Exchange exchange, Interceptor.Flow flow, Class type) throws ExchangeExpressionException;
- static ExchangeExpression newInstance(Router router, Language language, String expression) {
+ /**
+ * Clients of this class should pass an interceptor if possible. Otherwise use the InterceptorAdapter to wrap it.
+ * There is no convenience method on purpose to make the clients pass the interceptor. From the interceptor you can always get the router.
+ * @param interceptor
+ * @param language
+ * @param expression
+ * @return
+ */
+ static ExchangeExpression newInstance(Interceptor interceptor, Language language, String expression) {
return switch (language) {
- case GROOVY -> new GroovyExchangeExpression(router, expression);
+ case GROOVY -> new GroovyExchangeExpression(interceptor, expression);
case SPEL -> new SpELExchangeExpression(expression,null);
- case XPATH -> new XPathExchangeExpression(expression);
+ case XPATH -> new XPathExchangeExpression(interceptor,expression);
case JSONPATH -> new JsonpathExchangeExpression(expression);
};
}
+ /**
+ * Allows to pass an Interceptor as an argument where there is no interceptor e.g. Target
+ */
+ class InterceptorAdapter extends AbstractInterceptor implements XMLNamespaceSupport{
+
+ private Namespaces namespaces;
+
+ public InterceptorAdapter(Router router) {
+ this.router = router;
+ }
+
+ public InterceptorAdapter(Router router, Namespaces namespaces) {
+ this.router = router;
+ this.namespaces = namespaces;
+ }
+
+ @Override
+ public void setNamespaces(Namespaces namespaces) {
+ this.namespaces = namespaces;
+ }
+
+ @Override
+ public Namespaces getNamespaces() {
+ return namespaces;
+ }
+ }
}
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java
index 87d5d521d2..a2d94deecb 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java
@@ -15,6 +15,7 @@
import com.predic8.membrane.core.*;
import com.predic8.membrane.core.exchange.*;
+import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.interceptor.Interceptor.*;
import com.predic8.membrane.core.lang.spel.*;
@@ -36,17 +37,17 @@ public class TemplateExchangeExpression extends AbstractExchangeExpression {
private final List tokens;
- public static ExchangeExpression newInstance(Router router, Language language, String expression) {
+ public static ExchangeExpression newInstance(Interceptor interceptor, Language language, String expression) {
// SpEL comes with its own templating
if (language == SPEL) {
return new SpELExchangeExpression(expression, new SpELExchangeExpression.DollarBracketTemplateParserContext());
}
- return new TemplateExchangeExpression(router, language, expression);
+ return new TemplateExchangeExpression(interceptor, language, expression);
}
- protected TemplateExchangeExpression(Router router, Language language, String expression) {
+ protected TemplateExchangeExpression(Interceptor interceptor, Language language, String expression) {
super(expression);
- tokens = parseTokens(router,language, expression);
+ tokens = parseTokens(interceptor,language, expression);
}
@Override
@@ -83,7 +84,7 @@ private String evaluateToString(Exchange exchange, Flow flow) {
return line.toString();
}
- protected static List parseTokens(Router router, Language language, String expression) {
+ protected static List parseTokens(Interceptor interceptor, Language language, String expression) {
log.debug("Parsing: {}",expression);
List tokens = new ArrayList<>();
@@ -95,7 +96,7 @@ protected static List parseTokens(Router router, Language language, Strin
}
String expr = m.group(3);
if (expr != null) {
- tokens.add(new Expression(ExchangeExpression.newInstance(router, language, expr)));
+ tokens.add(new Expression(ExchangeExpression.newInstance(interceptor, language, expr)));
}
}
log.debug("Tokens: {}", tokens);
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/groovy/GroovyExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/groovy/GroovyExchangeExpression.java
index 58bb78b38f..feab6d92f1 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/groovy/GroovyExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/groovy/GroovyExchangeExpression.java
@@ -35,9 +35,9 @@ public class GroovyExchangeExpression extends AbstractExchangeExpression {
private final Function
*/
@MCElement(name = "target", topLevel = false)
- public static class Target {
+ public static class Target implements XMLNamespaceSupport {
private String host;
private int port = -1;
private String method;
@@ -107,8 +110,13 @@ public static class Target {
private SSLParser sslParser;
+ protected Namespaces namespaces;
+
public void init(Router router) {
- if (url != null) exchangeExpression = TemplateExchangeExpression.newInstance(router, language, url);
+ if (url != null) {
+ exchangeExpression = TemplateExchangeExpression.newInstance(new InterceptorAdapter(router,namespaces), language, url);
+ }
+
}
public String compileUrl(Exchange exc, Interceptor.Flow flow) {
@@ -228,6 +236,15 @@ public ExchangeExpression.Language getLanguage() {
public void setLanguage(ExchangeExpression.Language language) {
this.language = language;
}
+
+ @MCChildElement(allowForeign = true, order = 100)
+ public void setNamespaces(Namespaces namespaces) {
+ this.namespaces = namespaces;
+ }
+
+ public Namespaces getNamespaces() {
+ return namespaces;
+ }
}
protected Target target = new Target();
diff --git a/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java b/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
index 71dd4b53b6..6b2584542b 100644
--- a/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
+++ b/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
@@ -13,6 +13,10 @@
limitations under the License. */
package com.predic8.membrane.core.util;
+import com.predic8.membrane.core.util.xml.parser.*;
+
+import java.util.*;
+
public class ExceptionUtil {
public static String concatMessageAndCauseMessages(Throwable throwable) {
StringBuilder sb = new StringBuilder();
@@ -25,4 +29,12 @@ public static String concatMessageAndCauseMessages(Throwable throwable) {
} while (throwable != null);
return sb.toString();
}
+
+ public static Throwable getRootCause(Throwable t) {
+ Throwable cause = t;
+ while (cause.getCause() != null && cause.getCause() != cause) {
+ cause = cause.getCause();
+ }
+ return cause;
+ }
}
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 6c36a5bf77..09a33dc352 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
@@ -29,7 +29,7 @@
import static com.predic8.membrane.core.Constants.*;
import static com.predic8.membrane.core.http.MimeType.*;
-import static com.predic8.membrane.core.util.XMLUtil.*;
+import static com.predic8.membrane.core.util.xml.XMLUtil.*;
import static java.nio.charset.StandardCharsets.*;
import static javax.xml.stream.XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES;
import static javax.xml.stream.XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES;
diff --git a/core/src/main/java/com/predic8/membrane/core/util/XMLUtil.java b/core/src/main/java/com/predic8/membrane/core/util/xml/XMLUtil.java
similarity index 98%
rename from core/src/main/java/com/predic8/membrane/core/util/XMLUtil.java
rename to core/src/main/java/com/predic8/membrane/core/util/xml/XMLUtil.java
index d321fd6d8e..d1023b4896 100644
--- a/core/src/main/java/com/predic8/membrane/core/util/XMLUtil.java
+++ b/core/src/main/java/com/predic8/membrane/core/util/xml/XMLUtil.java
@@ -11,7 +11,7 @@
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;
+package com.predic8.membrane.core.util.xml;
import com.predic8.membrane.core.http.*;
import org.jetbrains.annotations.*;
diff --git a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
new file mode 100644
index 0000000000..2477c765d7
--- /dev/null
+++ b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
@@ -0,0 +1,89 @@
+/* Copyright 2025 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.xml.parser;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import javax.xml.parsers.*;
+import java.io.StringReader;
+
+import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
+
+/**
+ * Thread-safe, XXE-hardened XML parser implementation.
+ * A fresh {@link DocumentBuilder} is created for each call.
+ * Instances are immutable and can safely be shared across threads.
+ */
+public final class HardenedXmlParser implements XmlParser {
+
+ private final DocumentBuilderFactory factory = createFactory(true);
+
+ private static XmlParser INSTANCE;
+
+ private HardenedXmlParser() {}
+
+ public static XmlParser getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new HardenedXmlParser();
+ return INSTANCE;
+ }
+
+ return INSTANCE;
+ }
+
+ private static DocumentBuilderFactory createFactory(boolean namespaceAware) {
+ DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+ f.setNamespaceAware(namespaceAware);
+
+ try {
+ // XXE protection and secure defaults
+ f.setFeature(FEATURE_SECURE_PROCESSING, true);
+ f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ f.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ f.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ } catch (ParserConfigurationException e) {
+ throw new IllegalStateException("Secure XML parser features not supported", e);
+ }
+
+ try { f.setXIncludeAware(false); } catch (UnsupportedOperationException ignore) {}
+ f.setExpandEntityReferences(false);
+ return f;
+ }
+
+ private DocumentBuilder newBuilder() {
+ try {
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ // Prevent any external entity resolution
+ builder.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
+ return builder;
+ } catch (ParserConfigurationException e) {
+ throw new XmlParseException("Failed to create XML parser: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public Document parse(InputSource source) {
+ if (source == null)
+ throw new IllegalArgumentException("source must not be null");
+
+ try {
+ return newBuilder().parse(source);
+ } catch (Exception e) {
+ throw new XmlParseException("Could not parse XML document: " + e.getMessage()); // No stacktrace needed
+ }
+ }
+}
+
diff --git a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/XmlParseException.java b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/XmlParseException.java
new file mode 100644
index 0000000000..e8dec533c8
--- /dev/null
+++ b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/XmlParseException.java
@@ -0,0 +1,34 @@
+/* Copyright 2025 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.xml.parser;
+
+
+/**
+ * Unchecked exception thrown when XML parsing fails.
+ * This avoids forcing callers to handle {@link org.xml.sax.SAXException}
+ * or {@link java.io.IOException} explicitly.
+ */
+public class XmlParseException extends RuntimeException {
+
+ public XmlParseException(String message) {
+ super(message);
+ }
+
+ public XmlParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
+
diff --git a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/XmlParser.java b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/XmlParser.java
new file mode 100644
index 0000000000..d78d826715
--- /dev/null
+++ b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/XmlParser.java
@@ -0,0 +1,34 @@
+/* Copyright 2025 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.xml.parser;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+/**
+ * Strategy interface for XML parsing.
+ * Implementations are expected to be thread-safe and XXE-hardened.
+ */
+public interface XmlParser {
+
+ /**
+ * Parses the given XML input source into a DOM document.
+ *
+ * @param source the XML input source to parse
+ * @return a DOM {@link Document}
+ * @throws XmlParseException if parsing fails
+ */
+ Document parse(InputSource source) throws XmlParseException;
+}
diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/lang/NamespacesTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/lang/NamespacesTest.java
new file mode 100644
index 0000000000..88d6c78d41
--- /dev/null
+++ b/core/src/test/java/com/predic8/membrane/core/interceptor/lang/NamespacesTest.java
@@ -0,0 +1,42 @@
+/* Copyright 2025 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.lang;
+
+import com.predic8.membrane.core.util.*;
+import org.junit.jupiter.api.*;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class NamespacesTest {
+
+ Namespaces namespaces = new Namespaces();
+
+ @BeforeEach
+ void setup() {
+ Namespaces.Namespace ns = new Namespaces.Namespace();
+ ns.prefix = "p8";
+ ns.uri = "https://predic8.de";
+ namespaces.setNamespace(List.of(ns));
+ }
+
+ @Test
+ void namespaceContext() {
+ assertEquals("https://predic8.de", namespaces.getNamespaceContext().getNamespaceURI("p8"));
+ assertEquals("p8", namespaces.getNamespaceContext().getPrefix("https://predic8.de"));
+ assertEquals("p8", CollectionsUtil.toList(namespaces.getNamespaceContext().getPrefixes("https://predic8.de")).getFirst());
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/AbstractExchangeExpressionTest.java b/core/src/test/java/com/predic8/membrane/core/lang/AbstractExchangeExpressionTest.java
index a771ac995c..bec1dc058f 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/AbstractExchangeExpressionTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/AbstractExchangeExpressionTest.java
@@ -65,17 +65,17 @@ static void tearDown() {
protected abstract Language getLanguage();
protected Object evalObject(String expression) {
- return ExchangeExpression.newInstance(router, getLanguage(),expression)
+ return ExchangeExpression.newInstance(new InterceptorAdapter(router), getLanguage(),expression)
.evaluate(exchange,flow, Object.class);
}
protected boolean evalBool(String expression) {
- return ExchangeExpression.newInstance(router, getLanguage(),expression)
+ return ExchangeExpression.newInstance(new InterceptorAdapter(router), getLanguage(),expression)
.evaluate(exchange,flow, Boolean.class);
}
protected String evalString(String expression) {
- return ExchangeExpression.newInstance(router, getLanguage(),expression)
+ return ExchangeExpression.newInstance(new InterceptorAdapter(router), getLanguage(),expression)
.evaluate(exchange,flow, String.class);
}
}
\ No newline at end of file
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/TemplateExchangeExpressionTest.java b/core/src/test/java/com/predic8/membrane/core/lang/TemplateExchangeExpressionTest.java
index fd2cf94b52..886472d2d1 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/TemplateExchangeExpressionTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/TemplateExchangeExpressionTest.java
@@ -42,7 +42,7 @@ static void setUp() throws Exception {
@Test
void text() {
- assertIterableEquals(List.of(new Text("aaa")), parseTokens(router,language,"aaa"));
+ assertIterableEquals(List.of(new Text("aaa")), parseTokens(new InterceptorAdapter(router),language,"aaa"));
}
@Test
@@ -66,6 +66,6 @@ void multiple() {
}
private static String eval(String expr) {
- return new TemplateExchangeExpression(router, language, expr).evaluate(exc, REQUEST,String.class);
+ return new TemplateExchangeExpression(new InterceptorAdapter(router), language, expr).evaluate(exc, REQUEST,String.class);
}
}
\ No newline at end of file
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpressionTest.java b/core/src/test/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpressionTest.java
index 204784747f..abe4c9d1f0 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpressionTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpressionTest.java
@@ -116,12 +116,12 @@ void emptyBodyForBoolean() throws URISyntaxException {
@Test
void wrongContentType() throws URISyntaxException {
- assertEquals("", ExchangeExpression.newInstance(router, JSONPATH, "$")
+ assertEquals("", ExchangeExpression.newInstance(new InterceptorAdapter(router), JSONPATH, "$")
.evaluate(Request.post("/foo").contentType(TEXT_XML).buildExchange(), REQUEST, String.class));
}
private static T evaluateWithEmptyBodyFor(Class type) throws URISyntaxException {
- return ExchangeExpression.newInstance(router, JSONPATH, "$")
+ return ExchangeExpression.newInstance(new InterceptorAdapter(router), JSONPATH, "$")
.evaluate(get("/foo").buildExchange(), REQUEST, type);
}
}
\ No newline at end of file
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpressionTest.java b/core/src/test/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpressionTest.java
index 1b26eeb77b..4cd6ed1732 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpressionTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpressionTest.java
@@ -14,6 +14,9 @@
package com.predic8.membrane.core.lang.xpath;
+import com.predic8.membrane.core.exchange.*;
+import com.predic8.membrane.core.http.*;
+import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
import org.junit.jupiter.api.*;
@@ -23,7 +26,9 @@
import static com.predic8.membrane.core.http.MimeType.*;
import static com.predic8.membrane.core.http.Request.*;
+import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST;
import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
+import static com.predic8.membrane.core.lang.ExchangeExpression.newInstance;
import static org.junit.jupiter.api.Assertions.*;
class XPathExchangeExpressionTest extends AbstractExchangeExpressionTest {
@@ -110,4 +115,25 @@ void wrongContentType() {
assertEquals("John Doe",evalString("/persons/name[1]"));
}
+ @Nested
+ class Namespaces {
+
+ Exchange pExc;
+
+ @BeforeEach
+ void setup() throws URISyntaxException {
+ pExc = Request.post("/person").xml("""
+
+ Trevor
+
+ """).buildExchange();
+ }
+
+ @Test
+ void localName() {
+ assertEquals("Trevor", newInstance( new InterceptorAdapter(router),getLanguage(),
+ "//*[local-name()='firstname']")
+ .evaluate(pExc, REQUEST, String.class));
+ }
+ }
}
\ No newline at end of file
diff --git a/core/src/test/java/com/predic8/membrane/core/util/XMLUtilTest.java b/core/src/test/java/com/predic8/membrane/core/util/XMLUtilTest.java
index f87ea6c06d..03a45102f9 100644
--- a/core/src/test/java/com/predic8/membrane/core/util/XMLUtilTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/util/XMLUtilTest.java
@@ -13,6 +13,7 @@
limitations under the License. */
package com.predic8.membrane.core.util;
+import com.predic8.membrane.core.util.xml.*;
import org.junit.jupiter.api.*;
import javax.xml.namespace.*;
diff --git a/distribution/examples/templating/json/README.md b/distribution/examples/templating/json/README.md
index 95f8f9a2ad..90bf35fd30 100644
--- a/distribution/examples/templating/json/README.md
+++ b/distribution/examples/templating/json/README.md
@@ -12,9 +12,9 @@ Execute the following steps:
2. Have a look at `proxies.xml`.
-2. Open a commandline and execute `membrane.sh` or `membrane.cmd`
+3. Open a commandline and execute `membrane.sh` or `membrane.cmd`
-3. Run this command from a second commandline:
+4. Run this command from a second commandline:
```bash
curl "http://localhost:2000/json?answer=42"
@@ -26,7 +26,7 @@ Execute the following steps:
{ "answer": 42 }
```
-4. Then execute:
+5. Then execute:
```bash
curl -d '{"city":"Berlin"}' -H "Content-Type: application/json" "http://localhost:2000"
diff --git a/distribution/examples/xml/namespaces/README.md b/distribution/examples/xml/namespaces/README.md
new file mode 100644
index 0000000000..eae89885bd
--- /dev/null
+++ b/distribution/examples/xml/namespaces/README.md
@@ -0,0 +1,16 @@
+# XML Namespaces Example
+
+How to declare and use namespaces with setProperty, if, ...
+
+## Running the Sample
+***Note:*** *The requests are also available in the requests.http file.*
+
+1. **Navigate** to the `examples/xml/namespaces` directory.
+2. **Start** the API Gateway by executing `membrane.sh` (Linux/Mac) or `membrane.cmd` (Windows).
+3. **Run**:
+ - Send:
+ ```
+ curl -d @person.xml localhost:2000
+ ```
+4. **Understand**:
+ Take a look at the `proxies.xml` file.
diff --git a/distribution/examples/xml/namespaces/membrane.cmd b/distribution/examples/xml/namespaces/membrane.cmd
new file mode 100644
index 0000000000..33f755ee0b
--- /dev/null
+++ b/distribution/examples/xml/namespaces/membrane.cmd
@@ -0,0 +1,17 @@
+@echo off
+setlocal
+set "dir=%CD%"
+:loop
+if exist "%dir%\starter.jar" if exist "%dir%\scripts\run-membrane.cmd" goto found
+for %%A in ("%dir%\..") do set "next=%%~fA"
+if /I "%next%"=="%dir%" goto notfound
+set "dir=%next%"
+if "%dir%"=="%SystemDrive%\" goto notfound
+goto loop
+:found
+set "ROOT=%dir%"
+call "%ROOT%\scripts\run-membrane.cmd" %*
+exit /b %ERRORLEVEL%
+:notfound
+>&2 echo Could not locate Membrane root. Ensure directory structure is correct.
+exit /b 1
diff --git a/distribution/examples/xml/namespaces/membrane.sh b/distribution/examples/xml/namespaces/membrane.sh
new file mode 100755
index 0000000000..96054f8c85
--- /dev/null
+++ b/distribution/examples/xml/namespaces/membrane.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Default: ./proxies.xml (next to this script); fallback -> $MEMBRANE_HOME/conf/proxies.xml
+# JAVA_OPTS: relative -D paths are auto-resolved against $MEMBRANE_HOME (absolute/URI unchanged).
+# Examples:
+# export JAVA_OPTS='-Dlog4j.configurationFile=examples/logging/access/log4j2_access.xml'
+# export JAVA_OPTS='-Dlog4j.configurationFile=/abs/path/log4j2.xml'
+
+SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
+
+dir="$SCRIPT_DIR"
+while [ "$dir" != "/" ]; do
+ if [ -f "$dir/starter.jar" ] && [ -f "$dir/scripts/run-membrane.sh" ]; then
+ export MEMBRANE_HOME="$dir"
+ export MEMBRANE_CALLER_DIR="$SCRIPT_DIR"
+ exec sh "$dir/scripts/run-membrane.sh" "$@"
+ fi
+ dir=$(dirname "$dir")
+done
+
+echo "Could not locate Membrane root. Ensure directory structure is correct." >&2
+exit 1
\ No newline at end of file
diff --git a/distribution/examples/xml/namespaces/person.xml b/distribution/examples/xml/namespaces/person.xml
new file mode 100644
index 0000000000..5365e065d8
--- /dev/null
+++ b/distribution/examples/xml/namespaces/person.xml
@@ -0,0 +1,6 @@
+
+ Hans
+
+ Cologne
+
+
\ No newline at end of file
diff --git a/distribution/examples/xml/namespaces/proxies.xml b/distribution/examples/xml/namespaces/proxies.xml
new file mode 100644
index 0000000000..059d75cb83
--- /dev/null
+++ b/distribution/examples/xml/namespaces/proxies.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+ /person
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${property.name} from ${property.city}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distribution/examples/xml/namespaces/requests.http b/distribution/examples/xml/namespaces/requests.http
new file mode 100644
index 0000000000..e270a2349f
--- /dev/null
+++ b/distribution/examples/xml/namespaces/requests.http
@@ -0,0 +1,9 @@
+POST http://localhost:2000
+Content-Type: text/xml
+
+
+ Hans
+
+ Cologne
+
+
\ No newline at end of file
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/VersioningSoapXsltExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/VersioningSoapXsltExampleTest.java
index 5116e0abaa..616816771e 100644
--- a/distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/VersioningSoapXsltExampleTest.java
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/VersioningSoapXsltExampleTest.java
@@ -14,11 +14,13 @@
package com.predic8.membrane.examples.withinternet.test;
import com.predic8.membrane.examples.util.*;
+import io.restassured.filter.log.*;
import org.junit.jupiter.api.*;
import java.io.*;
import static io.restassured.RestAssured.*;
+import static io.restassured.filter.log.LogDetail.ALL;
import static org.hamcrest.Matchers.*;
public class VersioningSoapXsltExampleTest extends DistributionExtractingTestcase {
@@ -53,6 +55,7 @@ public void test() throws Exception {
.body(request_old)
.post("http://localhost:2000/city-service")
.then()
+ .log().ifValidationFails(ALL)
.statusCode(200)
.body("Envelope.Body.getCityResponse.country", equalTo("Germany"));
// @formatter:on
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
new file mode 100644
index 0000000000..2bef1bc6c4
--- /dev/null
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
@@ -0,0 +1,31 @@
+/* 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.examples.withoutinternet.test.xml.namespaces;
+
+import com.predic8.membrane.examples.util.*;
+import org.junit.jupiter.api.*;
+
+import static io.restassured.RestAssured.*;
+
+public class NamespacesExampleTest extends AbstractSampleMembraneStartStopTestcase {
+
+ @Override
+ protected String getExampleDirName() {
+ return "xml/namespaces";
+ }
+
+ // TODO Implement
+
+
+}
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index bc4b0a1dce..48bb4e5647 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -31,6 +31,7 @@
# 6.4.0
+- SessionManagerTest: refactor, too slow for Unittest. Move to integration tests.
- Refactor: Cookie maybe centralize Cookie Handling in a Cookie class
- Loadbalancing description with pacemaker
- JSONBody
@@ -45,6 +46,7 @@
- public abstract void init() throws Exception;
- getEndSessionEndpoint() throws Exception
- doDynamicRegistration(List callbackURLs) throws Exception
+
## Release Notes:
- JSON Schema validation support for JSON Schema 2019-09 and 2020-12 (via networknt json-schema-validator).
From 7b0b78b88ebadd4d89b48315806e4b59a0e70fb3 Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sat, 1 Nov 2025 17:07:26 +0100
Subject: [PATCH 02/16] refactor: minor
---
.../idempotency/IdempotencyInterceptor.java | 24 +-
.../lang/AbstractLanguageInterceptor.java | 2 -
.../core/interceptor/lang/Namespaces.java | 2 +-
.../core/proxies/AbstractServiceProxy.java | 2 +-
.../predic8/membrane/core/util/WSDLUtil.java | 2 +-
.../util/xml/parser/HardenedXmlParser.java | 6 +-
distribution/conf/apis.yaml | 509 ++++++++++++++++++
distribution/conf/log4j2.xml | 2 +-
distribution/conf/simple.yaml | 27 +
.../test/VersioningSoapXsltExampleTest.java | 3 +-
.../xml/namespaces/NamespacesExampleTest.java | 3 -
11 files changed, 553 insertions(+), 29 deletions(-)
create mode 100644 distribution/conf/apis.yaml
create mode 100644 distribution/conf/simple.yaml
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
index d86962230f..7c1026216c 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
@@ -14,25 +14,19 @@
package com.predic8.membrane.core.interceptor.idempotency;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import com.predic8.membrane.annot.MCAttribute;
-import com.predic8.membrane.annot.MCElement;
-import com.predic8.membrane.annot.Required;
-import com.predic8.membrane.core.exchange.Exchange;
-import com.predic8.membrane.core.interceptor.AbstractInterceptor;
-import com.predic8.membrane.core.interceptor.Outcome;
+import com.google.common.cache.*;
+import com.predic8.membrane.annot.*;
+import com.predic8.membrane.core.exchange.*;
+import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.interceptor.lang.*;
-import com.predic8.membrane.core.lang.ExchangeExpression;
-import com.predic8.membrane.core.lang.ExchangeExpression.Language;
+import com.predic8.membrane.core.lang.*;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
-import static com.predic8.membrane.core.exceptions.ProblemDetails.user;
-import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST;
+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.*;
import static com.predic8.membrane.core.interceptor.Outcome.ABORT;
-import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE;
-import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL;
/**
* @description Prevents duplicate request processing based on a dynamic idempotency key.
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
index 39df993242..dd3f1c4172 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
@@ -24,8 +24,6 @@
public abstract class AbstractLanguageInterceptor extends AbstractInterceptor implements Polyglot, XMLNamespaceSupport {
- private static final Logger log = LoggerFactory.getLogger(AbstractLanguageInterceptor.class);
-
/**
* SpEL is default
*/
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
index 9d07f2c267..baebe54f0c 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
@@ -26,7 +26,7 @@ public class Namespaces {
//NamespaceContext nsContext;
private List namespaces;
- private NamespaceContextImpl nsContext = new NamespaceContextImpl();
+ private final NamespaceContextImpl nsContext = new NamespaceContextImpl();
public NamespaceContext getNamespaceContext() {
return nsContext;
diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java b/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java
index 8d498517aa..b26eedcce8 100644
--- a/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java
+++ b/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java
@@ -120,7 +120,7 @@ public void init(Router router) {
}
public String compileUrl(Exchange exc, Interceptor.Flow flow) {
- /**
+ /*
* Will always evaluate on every call. This is fine as SpEL is fast enough and performs its own optimizations.
* 1.000.000 calls ~10ms
*/
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 b824d2adbd..e8c8e658c6 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
@@ -24,7 +24,7 @@
import static com.predic8.membrane.core.Constants.WSDL_SOAP11_NS;
import static com.predic8.membrane.core.Constants.WSDL_SOAP12_NS;
import static com.predic8.membrane.core.util.WSDLUtil.Direction.*;
-import static com.predic8.membrane.core.util.XMLUtil.groovyToJavaxQName;
+import static com.predic8.membrane.core.util.xml.XMLUtil.groovyToJavaxQName;
public class WSDLUtil {
diff --git a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
index 2477c765d7..7c56237656 100644
--- a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
+++ b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
@@ -29,7 +29,7 @@
*/
public final class HardenedXmlParser implements XmlParser {
- private final DocumentBuilderFactory factory = createFactory(true);
+ private final DocumentBuilderFactory factory = createFactory();
private static XmlParser INSTANCE;
@@ -44,9 +44,9 @@ public static XmlParser getInstance() {
return INSTANCE;
}
- private static DocumentBuilderFactory createFactory(boolean namespaceAware) {
+ private static DocumentBuilderFactory createFactory() {
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
- f.setNamespaceAware(namespaceAware);
+ f.setNamespaceAware(true);
try {
// XXE protection and secure defaults
diff --git a/distribution/conf/apis.yaml b/distribution/conf/apis.yaml
new file mode 100644
index 0000000000..7113e23a84
--- /dev/null
+++ b/distribution/conf/apis.yaml
@@ -0,0 +1,509 @@
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+kind: api
+spec:
+ port: 2000
+ target:
+ url: https://api.predic8.de
+
+---
+
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+kind: api
+spec:
+ port: 2001
+ path:
+ uri: /fruit/{id}
+ target:
+ url: https://api.predic8.de/shop/v2/products/${pathParam.id}
+
+---
+
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2002
+ specs:
+ - openapi:
+ location: "fruitshop-api.yml"
+ validateRequests: yes
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2003
+ flow:
+ - log:
+ message: "Headers: ${header}"
+ target:
+ url: https://api.predic8.de
+
+---
+
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2004
+ path:
+ uri: /shop
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2005
+ specs:
+ - openapi:
+ location: "fruitshop-api.yml"
+ validateRequests: yes
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2007
+ method: POST
+ flow:
+ - response:
+ - static:
+ src: |
+ POST is blocked!
+ - return:
+ statusCode: 405
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2007
+ path:
+ uri: /shop/v2/products/.*
+ isRegExp: true
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2007
+ path:
+ uri: /shop
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2007
+ host: www.predic8.de
+ flow:
+ - response:
+ - static:
+ src: Homepage
+ - return:
+ statusCode: 200
+ path:
+ uri: /shop
+ target:
+ url: https://api.predic8.de
+
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2008
+ test: params.city == 'Paris'
+ flow:
+ - response:
+ - static:
+ src: Oui!
+ - return:
+ statusCode: 200
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2009
+ path:
+ uri: /health
+ flow:
+ - response:
+ - static:
+ src: I'm good.
+ - return:
+ statusCode: 200
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2010
+ path:
+ uri: /nothing
+ flow:
+ - response:
+ - static:
+ src: "Nothing to see here."
+ - return:
+ statusCode: 404
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2011
+ flow:
+ - rewriter:
+ - map:
+ from: ^/fruitshop/(.*)
+ to: /shop/v2/$1
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2012
+ flow:
+ - groovy:
+ src: |
+ println "I'm executed in the ${flow} flow"
+ println "HTTP Headers:\n${header}"
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2013
+ flow:
+ - groovy:
+ src: |
+ sites = ["https://api.predic8.de","https://membrane-api.io","https://predic8.de"]
+ Collections.shuffle sites
+ exchange.destinations = sites
+ target: {}
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2014
+ flow:
+ - groovy:
+ src: |
+ Response.ok()
+ .contentType("application/json")
+ .header("X-Foo", "bar")
+ .body("""
+ {
+ "success": true
+ }""")
+ .build()
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2015
+ flow:
+ - javascript:
+ src: |
+ console.log("------------ Headers: -------------");
+ var fields = header.getAllHeaderFields();
+ for (var i = 0; i < fields.length; i++) {
+ console.log(fields[i]);
+ }
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2016
+ flow:
+ - javascript:
+ src: |
+ var body = JSON.stringify({
+ foo: 7,
+ bar: 42
+ });
+ Response.ok(body).contentType("application/json").build();
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2017
+ flow:
+ - response:
+ - setHeader:
+ name: Access-Control-Allow-Origin
+ value: "*"
+ - setHeader:
+ name: Access-Control-Allow-Methods
+ value: GET
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2018
+ flow:
+ - response:
+ - setHeader:
+ name: X-Product-Id
+ value: ${jsonPath('$.id')}
+ - setHeader:
+ name: X-Product-Name
+ value: ${$.name}
+ language: jsonpath
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2019
+ flow:
+ - response:
+ - headerFilter:
+ rules:
+ - include:
+ pattern: "X-XSS-Protection"
+ - exclude:
+ pattern: "X-.*"
+ target:
+ url: https://www.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2020
+ flow:
+ - request:
+ - template:
+ contentType: application/json
+ pretty: true
+ src: |
+ { "answer": ${params.answer} }
+ - return:
+ statusCode: 200
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2021
+ flow:
+ - request:
+ - template:
+ contentType: text/plain
+ src: |
+ City: ${json.city}
+ - return:
+ statusCode: 200
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2022
+ flow:
+ - request:
+ - template:
+ contentType: application/json
+ src: |
+ {
+ "destination": "${json.city}"
+ }
+ - return:
+ statusCode: 200
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2023
+ flow:
+ - request:
+ - template:
+ contentType: application/xml
+ src: |
+
+ ${json.city}
+
+ - return:
+ statusCode: 200
+
+---
+spec:
+ port: 2024
+ flow:
+ - request:
+ - javascript:
+ src: |
+ function convertDate(d) {
+ return d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0"+d.getDate()).slice(-2);
+ }
+
+ ({
+ id: json.id,
+ date: convertDate(new Date(json.date)),
+ client: json.customer,
+ total: json.items.map(i => i.quantity * i.price).reduce((a,b) => a+b),
+ positions: json.items.map(i => ({
+ pieces: i.quantity,
+ price: i.price,
+ article: i.description
+ }))
+ })
+ - return:
+ statusCode: 200
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2025
+ flow:
+ - response:
+ - beautifier: {} # Response flow is reversed
+ - template:
+ contentType: application/xml
+ src: |
+ Baz
+ - return:
+ statusCode: 200
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2026
+ flow:
+ - response:
+ - if:
+ test: statusCode matches '4\d\d'
+ flow:
+ - static:
+ src: "Stupid User!"
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2027
+ flow:
+ - apiKey:
+ stores:
+ - keys:
+ - secret:
+ value: abc123
+ - secret:
+ value: secret123
+ - return:
+ statusCode: 200
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2028
+ flow:
+ - jwtAuth:
+ expectedAud: "api://2axxxx16-xxxx-xxxx-xxxx-faxxxxxxxxf0"
+ jwks:
+ jwksUris: https://login.microsoftonline.com/common/discovery/keys
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2029
+ flow:
+ - oauth2Resource2:
+ membrane:
+ src: https://accounts.google.com
+ clientId: INSERT_CLIENT_ID
+ clientSecret: INSERT_CLIENT_SECRET
+ scope: email profile
+ subject: sub
+ - groovy:
+ src: |
+ // Get email from OAuth2 and forward it to the backend
+ def oauth2 = exc.properties.'membrane.oauth2'
+ header.setValue('X-EMAIL',oauth2.userinfo.email)
+ target:
+ url: https://backend
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+#spec:
+# port: 2030
+# flow:
+# - oauth2authserver:
+# location: logindialog
+# issuer: http://localhost:2000
+# consentFile: consent.json
+# staticUserDataProvider:
+# users:
+# - user:
+# username: john
+# password: secret
+# email: john@predic8.de
+# bearerJwtToken: {}
+# claims:
+# scopes:
+# - scope:
+# id: username
+# target:
+# url: http://backend
+
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2031
+ flow:
+ - basicAuthentication:
+ users:
+ - user:
+ username: bob
+ password: secret
+ - user:
+ username: alice
+ password: secret
+ target:
+ host: localhost
+ port: 8080
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2032
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 443
+ ssl:
+ keystore:
+ location: membrane.p12
+ password: secret
+ keyPassword: secret
+ target:
+ host: localhost
+ port: 8080
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2033
+ flow:
+ - xmlProtection:
+ maxElementNameLength: 100
+ target:
+ url: https://api.predic8.de
+
+---
+# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
+spec:
+ port: 2034
+ flow:
+ - jsonProtection:
+ maxDepth: 5
+ maxKeyLength: 30
+ maxStringLength: 100000
+ target:
+ url: https://api.predic8.de
\ No newline at end of file
diff --git a/distribution/conf/log4j2.xml b/distribution/conf/log4j2.xml
index 81af7c9bbd..d8b1a5e9f7 100644
--- a/distribution/conf/log4j2.xml
+++ b/distribution/conf/log4j2.xml
@@ -9,7 +9,7 @@
-
+
diff --git a/distribution/conf/simple.yaml b/distribution/conf/simple.yaml
new file mode 100644
index 0000000000..fbf4b5c7aa
--- /dev/null
+++ b/distribution/conf/simple.yaml
@@ -0,0 +1,27 @@
+openapi: 3.1.0
+info:
+ title: Simple Parameters API
+ version: "1.0.0"
+servers:
+ - url: http://localhost
+
+paths:
+ /foo:
+ get:
+ parameters:
+ - name: bar
+ in: query
+ explode: true
+ schema:
+ type: array
+ items:
+ type: string
+ - name: baz
+ in: query
+ explode: true
+ schema:
+ type: integer
+ responses:
+ "200":
+ description: OK
+
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/VersioningSoapXsltExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/VersioningSoapXsltExampleTest.java
index 616816771e..1c5b8d9488 100644
--- a/distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/VersioningSoapXsltExampleTest.java
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withinternet/test/VersioningSoapXsltExampleTest.java
@@ -14,13 +14,12 @@
package com.predic8.membrane.examples.withinternet.test;
import com.predic8.membrane.examples.util.*;
-import io.restassured.filter.log.*;
import org.junit.jupiter.api.*;
import java.io.*;
import static io.restassured.RestAssured.*;
-import static io.restassured.filter.log.LogDetail.ALL;
+import static io.restassured.filter.log.LogDetail.*;
import static org.hamcrest.Matchers.*;
public class VersioningSoapXsltExampleTest extends DistributionExtractingTestcase {
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
index 2bef1bc6c4..fe4ee4b54f 100644
--- a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
@@ -14,9 +14,6 @@
package com.predic8.membrane.examples.withoutinternet.test.xml.namespaces;
import com.predic8.membrane.examples.util.*;
-import org.junit.jupiter.api.*;
-
-import static io.restassured.RestAssured.*;
public class NamespacesExampleTest extends AbstractSampleMembraneStartStopTestcase {
From f0df650c67ccc27b3abbd8f302cb21ace186c2db Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sun, 2 Nov 2025 18:44:05 +0100
Subject: [PATCH 03/16] refactor: minor
---
.../core/interceptor/lang/Namespaces.java | 1 -
.../lang/xpath/XPathExchangeExpression.java | 4 +-
.../membrane/core/util/ExceptionUtil.java | 3 ++
.../util/xml/parser/HardenedXmlParser.java | 6 ++-
.../examples/xml/namespaces/README.md | 15 +++++-
.../xml/namespaces/NamespacesExampleTest.java | 53 ++++++++++++++++++-
6 files changed, 74 insertions(+), 8 deletions(-)
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
index baebe54f0c..a077c53022 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
@@ -24,7 +24,6 @@
@MCElement(name="namespaces", topLevel = true)
public class Namespaces {
- //NamespaceContext nsContext;
private List namespaces;
private final NamespaceContextImpl nsContext = new NamespaceContextImpl();
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
index 6c74dc8189..15a76b95a1 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
@@ -33,7 +33,7 @@ public class XPathExchangeExpression extends AbstractExchangeExpression {
private static final Logger log = LoggerFactory.getLogger(XPathExchangeExpression.class.getName());
- private static XmlParser parser;
+ private static XmlParser parser = HardenedXmlParser.getInstance();
private Namespaces namespaces;
@@ -46,8 +46,6 @@ public XPathExchangeExpression(Interceptor interceptor, String xpath) {
if (interceptor instanceof XMLNamespaceSupport xns) {
namespaces = xns.getNamespaces();
}
-
- parser = HardenedXmlParser.getInstance();
}
@Override
diff --git a/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java b/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
index 6b2584542b..26c44510d1 100644
--- a/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
+++ b/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
@@ -18,6 +18,7 @@
import java.util.*;
public class ExceptionUtil {
+
public static String concatMessageAndCauseMessages(Throwable throwable) {
StringBuilder sb = new StringBuilder();
do {
@@ -31,6 +32,8 @@ public static String concatMessageAndCauseMessages(Throwable throwable) {
}
public static Throwable getRootCause(Throwable t) {
+ if (t == null)
+ return null;
Throwable cause = t;
while (cause.getCause() != null && cause.getCause() != cause) {
cause = cause.getCause();
diff --git a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
index 7c56237656..5785977e4c 100644
--- a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
+++ b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
@@ -31,7 +31,11 @@ public final class HardenedXmlParser implements XmlParser {
private final DocumentBuilderFactory factory = createFactory();
- private static XmlParser INSTANCE;
+ /**
+ * Singleton instance. volatile is needed to against a reordering of instructions
+ * allowing another thread to see a partially constructed object
+ */
+ private static volatile XmlParser INSTANCE;
private HardenedXmlParser() {}
diff --git a/distribution/examples/xml/namespaces/README.md b/distribution/examples/xml/namespaces/README.md
index eae89885bd..c8b456b9f9 100644
--- a/distribution/examples/xml/namespaces/README.md
+++ b/distribution/examples/xml/namespaces/README.md
@@ -1,6 +1,17 @@
# XML Namespaces Example
-How to declare and use namespaces with setProperty, if, ...
+Namespaces are a powerful XML feature. Unfortunately, they can be a bit tricky to use. This example shows
+how to declare and use namespaces. The namespace decarations can be used in:
+
+- setProperty
+- setHeader
+- if
+- case
+- target url
+- api test
+- call url
+
+The example shows how to declare and use them with XPath expressions.
## Running the Sample
***Note:*** *The requests are also available in the requests.http file.*
@@ -9,7 +20,7 @@ How to declare and use namespaces with setProperty, if, ...
2. **Start** the API Gateway by executing `membrane.sh` (Linux/Mac) or `membrane.cmd` (Windows).
3. **Run**:
- Send:
- ```
+ ```bash
curl -d @person.xml localhost:2000
```
4. **Understand**:
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
index fe4ee4b54f..d0c7f20a81 100644
--- a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
@@ -14,6 +14,12 @@
package com.predic8.membrane.examples.withoutinternet.test.xml.namespaces;
import com.predic8.membrane.examples.util.*;
+import org.junit.jupiter.api.*;
+
+import static com.predic8.membrane.core.http.MimeType.TEXT_XML;
+import static io.restassured.RestAssured.given;
+import static io.restassured.filter.log.LogDetail.ALL;
+import static org.hamcrest.Matchers.containsString;
public class NamespacesExampleTest extends AbstractSampleMembraneStartStopTestcase {
@@ -22,7 +28,52 @@ protected String getExampleDirName() {
return "xml/namespaces";
}
- // TODO Implement
+ // TODO implement other cases as well
+
+ @Test
+ void namespaceAwareXPathExtraction() throws Exception {
+ String xmlBody = """
+
+ Hans
+
+ Cologne
+
+
+ """;
+
+ // @formatter:off
+ given()
+ .contentType(TEXT_XML)
+ .body(xmlBody)
+ .post("http://localhost:2000")
+ .then()
+ .log().ifValidationFails(ALL)
+ .statusCode(200)
+ .body(containsString("Hans from Cologne"));
+ // @formatter:on
+ }
+
+ @Test
+ void differentCity() throws Exception {
+ String xmlBody = """
+
+ Maria
+
+ Berlin
+
+
+ """;
+ // @formatter:off
+ given()
+ .contentType(TEXT_XML)
+ .body(xmlBody)
+ .post("http://localhost:2000")
+ .then()
+ .log().ifValidationFails(ALL)
+ .statusCode(200)
+ .body(containsString("Maria from Berlin"));
+ // @formatter:on
+ }
}
From 6347d21f56ba7e45986b4609f6a99a8cc78d1ba4 Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sun, 2 Nov 2025 18:50:07 +0100
Subject: [PATCH 04/16] refactor: minor
---
.../core/interceptor/lang/AbstractLanguageInterceptor.java | 4 ++--
.../membrane/core/lang/xpath/XPathExchangeExpression.java | 2 +-
.../test/xml/namespaces/NamespacesExampleTest.java | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
index dd3f1c4172..e8212141ee 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
@@ -30,8 +30,8 @@ public abstract class AbstractLanguageInterceptor extends AbstractInterceptor im
protected Language language = SPEL;
protected Namespaces namespaces;
- public String getLanguage() {
- return language.name();
+ public Language getLanguage() {
+ return language;
}
/**
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
index 15a76b95a1..139ced5d93 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
@@ -33,7 +33,7 @@ public class XPathExchangeExpression extends AbstractExchangeExpression {
private static final Logger log = LoggerFactory.getLogger(XPathExchangeExpression.class.getName());
- private static XmlParser parser = HardenedXmlParser.getInstance();
+ private static final XmlParser parser = HardenedXmlParser.getInstance();
private Namespaces namespaces;
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
index d0c7f20a81..9ce7c07f7b 100644
--- a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
@@ -31,7 +31,7 @@ protected String getExampleDirName() {
// TODO implement other cases as well
@Test
- void namespaceAwareXPathExtraction() throws Exception {
+ void namespaceAwareXPathExtraction() {
String xmlBody = """
Hans
@@ -54,7 +54,7 @@ void namespaceAwareXPathExtraction() throws Exception {
}
@Test
- void differentCity() throws Exception {
+ void differentCity() {
String xmlBody = """
Maria
From 74ddf93c6a49865a0637fbfb3ac1f70bf27d8e40 Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sun, 2 Nov 2025 19:10:05 +0100
Subject: [PATCH 05/16] refactor: minor
---
.../core/interceptor/XMLNamespaceSupport.java | 14 ++++++++++++++
.../lang/AbstractLanguageInterceptor.java | 6 ++----
.../predic8/membrane/core/util/ExceptionUtil.java | 4 ----
.../core/util/xml/parser/HardenedXmlParser.java | 9 ++++++---
distribution/examples/xml/namespaces/README.md | 4 ++--
5 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java b/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java
index 27bc7780ad..b0910b164f 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java
@@ -1,3 +1,17 @@
+/* Copyright 2025 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;
import com.predic8.membrane.core.interceptor.lang.*;
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
index e8212141ee..50516a2dd5 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
@@ -16,11 +16,9 @@
import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.interceptor.*;
-import com.predic8.membrane.core.lang.ExchangeExpression.Language;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.predic8.membrane.core.lang.ExchangeExpression.*;
-import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL;
+import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
public abstract class AbstractLanguageInterceptor extends AbstractInterceptor implements Polyglot, XMLNamespaceSupport {
diff --git a/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java b/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
index 26c44510d1..aa1bcd3a72 100644
--- a/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
+++ b/core/src/main/java/com/predic8/membrane/core/util/ExceptionUtil.java
@@ -13,10 +13,6 @@
limitations under the License. */
package com.predic8.membrane.core.util;
-import com.predic8.membrane.core.util.xml.parser.*;
-
-import java.util.*;
-
public class ExceptionUtil {
public static String concatMessageAndCauseMessages(Throwable throwable) {
diff --git a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
index 5785977e4c..ca27f80be4 100644
--- a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
+++ b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
@@ -41,10 +41,13 @@ private HardenedXmlParser() {}
public static XmlParser getInstance() {
if (INSTANCE == null) {
- INSTANCE = new HardenedXmlParser();
- return INSTANCE;
+ // Multiple threads can reach this point at the same time, therefore we need to synchronize
+ synchronized (HardenedXmlParser.class) {
+ if (INSTANCE == null) {
+ INSTANCE = new HardenedXmlParser();
+ }
+ }
}
-
return INSTANCE;
}
diff --git a/distribution/examples/xml/namespaces/README.md b/distribution/examples/xml/namespaces/README.md
index c8b456b9f9..ee5c2e1395 100644
--- a/distribution/examples/xml/namespaces/README.md
+++ b/distribution/examples/xml/namespaces/README.md
@@ -1,7 +1,7 @@
# XML Namespaces Example
-Namespaces are a powerful XML feature. Unfortunately, they can be a bit tricky to use. This example shows
-how to declare and use namespaces. The namespace decarations can be used in:
+Namespaces are a powerful XML feature. Unfortunately, they can be a bit tricky to use. This example shows
+how to declare and use namespaces. The namespace declarations can be used in:
- setProperty
- setHeader
From bb7c9dc1770190b94d29e6e74fb1876e4a284730 Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sun, 2 Nov 2025 19:26:16 +0100
Subject: [PATCH 06/16] refactor: minor
---
.../extractors/ApiKeyExpressionExtractor.java | 6 +-----
.../balancer/PolyglotSessionIdExtractor.java | 6 +-----
.../core/interceptor/flow/ForInterceptor.java | 6 +-----
.../core/interceptor/flow/IfInterceptor.java | 6 +-----
.../core/interceptor/flow/choice/Case.java | 7 ++-----
.../idempotency/IdempotencyInterceptor.java | 15 ++++---------
.../ratelimit/RateLimitInterceptor.java | 6 +-----
.../core/lang/ExchangeExpression.java | 6 +-----
.../core/lang/TemplateExchangeExpression.java | 6 +-----
.../core/openapi/serviceproxy/APIProxy.java | 6 +-----
.../lang/AbstractExchangeExpressionTest.java | 21 +++----------------
.../JsonpathExchangeExpressionTest.java | 18 ++++------------
.../xpath/XPathExchangeExpressionTest.java | 4 ++--
13 files changed, 23 insertions(+), 90 deletions(-)
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
index 9434ce704e..1def43cd74 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
@@ -56,11 +56,7 @@ public class ApiKeyExpressionExtractor implements ApiKeyExtractor, Polyglot, XML
@Override
public void init(Router router) {
-<<<<<<< HEAD
- exchangeExpression = ExchangeExpression.newInstance(new InterceptorAdapter(router,namespaces), language, expression);
-=======
- exchangeExpression = expression(router, language, expression);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ exchangeExpression = expression(new InterceptorAdapter(router,namespaces), language, expression);
}
@Override
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
index 1e49110213..2b8969341e 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
@@ -35,11 +35,7 @@ public class PolyglotSessionIdExtractor extends AbstractXmlElement implements Se
public void init(Router router) {
if (sessionSource != null && !sessionSource.isEmpty()) {
-<<<<<<< HEAD
- exchangeExpression = ExchangeExpression.newInstance(new InterceptorAdapter(router), language, sessionSource);
-=======
- exchangeExpression = expression(router, language, sessionSource);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ exchangeExpression = ExchangeExpression.expression(new InterceptorAdapter(router), language, sessionSource);
}
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java
index 47456375cc..cdafbeb686 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ForInterceptor.java
@@ -57,11 +57,7 @@ public class ForInterceptor extends AbstractFlowWithChildrenInterceptor {
public void init() {
super.init();
try {
-<<<<<<< HEAD
- exchangeExpression = ExchangeExpression.newInstance(this, language, in);
-=======
- exchangeExpression = expression(router, language, in);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ exchangeExpression = expression(this, language, in);
} catch (ConfigurationException ce) {
throw new ConfigurationException(ce.getMessage() + """
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
index 9a8a0077b1..8feb81af59 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
@@ -55,11 +55,7 @@ public IfInterceptor() {
@Override
public void init() {
super.init();
-<<<<<<< HEAD
- exchangeExpression = ExchangeExpression.newInstance(this, language, test);
-=======
- exchangeExpression = expression(router, language, test);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ exchangeExpression = expression(this, language, test);
}
@Override
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
index 20a0fc2b1b..52a6129df0 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
@@ -15,6 +15,7 @@
import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.Router;
+import com.predic8.membrane.core.config.spring.*;
import com.predic8.membrane.core.exchange.Exchange;
import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.interceptor.Interceptor.Flow;
@@ -38,11 +39,7 @@ public class Case extends InterceptorContainer implements XMLNamespaceSupport {
private Namespaces namespaces;
public void init(Router router) {
-<<<<<<< HEAD
- exchangeExpression = ExchangeExpression.newInstance( new InterceptorAdapter(router,namespaces), language, test);
-=======
- exchangeExpression = expression(router, language, test);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ exchangeExpression = ExchangeExpression.expression( new InterceptorAdapter(router,namespaces), language, test);
}
boolean evaluate(Exchange exc, Flow flow) {
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
index c8e41cdd0a..6c7c380fc9 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/idempotency/IdempotencyInterceptor.java
@@ -27,12 +27,8 @@
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;
-<<<<<<< HEAD
-=======
-import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE;
-import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL;
-import static com.predic8.membrane.core.lang.ExchangeExpression.expression;
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+import static com.predic8.membrane.core.lang.ExchangeExpression.*;
+
/**
* @description Prevents duplicate request processing based on a dynamic idempotency key.
@@ -54,11 +50,8 @@ public class IdempotencyInterceptor extends AbstractLanguageInterceptor {
@Override
public void init() {
super.init();
-<<<<<<< HEAD
- exchangeExpression = ExchangeExpression.newInstance(this, language, key);
-=======
- exchangeExpression = expression(router, language, key);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+
+ exchangeExpression = expression(this, language, key);
processedKeys = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(expiration, TimeUnit.SECONDS)
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java
index f5ef2e88f8..429dfbc8b0 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/ratelimit/RateLimitInterceptor.java
@@ -94,11 +94,7 @@ protected ExchangeExpression getExchangeExpression() {
// If there is no expression use the client IP
if (expression.isEmpty())
return null;
-<<<<<<< HEAD
- return ExchangeExpression.newInstance(this, language, expression);
-=======
- return expression(router, language, expression);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ return expression(this, language, expression);
}
@Override
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
index 5400d854a9..e3721021f1 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
@@ -49,7 +49,6 @@ enum Language {GROOVY, SPEL, XPATH, JSONPATH}
*/
T evaluate(Exchange exchange, Interceptor.Flow flow, Class type) throws ExchangeExpressionException;
-<<<<<<< HEAD
/**
* Clients of this class should pass an interceptor if possible. Otherwise use the InterceptorAdapter to wrap it.
* There is no convenience method on purpose to make the clients pass the interceptor. From the interceptor you can always get the router.
@@ -58,10 +57,7 @@ enum Language {GROOVY, SPEL, XPATH, JSONPATH}
* @param expression
* @return
*/
- static ExchangeExpression newInstance(Interceptor interceptor, Language language, String expression) {
-=======
- static ExchangeExpression expression(Router router, Language language, String expression) {
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ static ExchangeExpression expression(Interceptor interceptor, Language language, String expression) {
return switch (language) {
case GROOVY -> new GroovyExchangeExpression(interceptor, expression);
case SPEL -> new SpELExchangeExpression(expression,null);
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java
index 5ed8bca53e..546b4ac591 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java
@@ -97,11 +97,7 @@ protected static List parseTokens(Interceptor interceptor, Language langu
}
String expr = m.group(3);
if (expr != null) {
-<<<<<<< HEAD
- tokens.add(new Expression(ExchangeExpression.newInstance(interceptor, language, expr)));
-=======
- tokens.add(new Expression(expression(router, language, expr)));
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ tokens.add(new Expression(expression(interceptor, language, expr)));
}
}
log.debug("Tokens: {}", tokens);
diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java
index c83c503e70..c8772620e2 100644
--- a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java
+++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java
@@ -84,11 +84,7 @@ public void setSpecs(List specs) {
public void init() {
super.init();
if (test != null && !test.isEmpty()) {
-<<<<<<< HEAD
- exchangeExpression = ExchangeExpression.newInstance(new InterceptorAdapter(router,namespaces), language, test);
-=======
- exchangeExpression = expression(router, language, test);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ exchangeExpression = expression(new InterceptorAdapter(router,namespaces), language, test);
}
key = new APIProxyKey(key, exchangeExpression, !specs.isEmpty());
initOpenAPI();
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/AbstractExchangeExpressionTest.java b/core/src/test/java/com/predic8/membrane/core/lang/AbstractExchangeExpressionTest.java
index e9ac44ec54..9a111388e2 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/AbstractExchangeExpressionTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/AbstractExchangeExpressionTest.java
@@ -66,29 +66,14 @@ static void tearDown() {
protected abstract Language getLanguage();
protected Object evalObject(String expression) {
-<<<<<<< HEAD
- return ExchangeExpression.newInstance(new InterceptorAdapter(router), getLanguage(),expression)
- .evaluate(exchange,flow, Object.class);
+ return expression(new InterceptorAdapter(router), getLanguage(),expression).evaluate(exchange,flow, Object.class);
}
protected boolean evalBool(String expression) {
- return ExchangeExpression.newInstance(new InterceptorAdapter(router), getLanguage(),expression)
- .evaluate(exchange,flow, Boolean.class);
+ return expression(new InterceptorAdapter(router), getLanguage(),expression).evaluate(exchange,flow, Boolean.class);
}
protected String evalString(String expression) {
- return ExchangeExpression.newInstance(new InterceptorAdapter(router), getLanguage(),expression)
- .evaluate(exchange,flow, String.class);
-=======
- return expression(router, getLanguage(),expression).evaluate(exchange,flow, Object.class);
- }
-
- protected boolean evalBool(String expression) {
- return expression(router, getLanguage(),expression).evaluate(exchange,flow, Boolean.class);
- }
-
- protected String evalString(String expression) {
- return expression(router, getLanguage(),expression).evaluate(exchange,flow, String.class);
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
+ return expression(new InterceptorAdapter(router), getLanguage(),expression).evaluate(exchange,flow, String.class);
}
}
\ No newline at end of file
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpressionTest.java b/core/src/test/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpressionTest.java
index e9ae8f688a..03e67954bd 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpressionTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/jsonpath/JsonpathExchangeExpressionTest.java
@@ -117,33 +117,23 @@ void emptyBodyForBoolean() throws URISyntaxException {
@Test
void wrongContentType() throws URISyntaxException {
-<<<<<<< HEAD
- assertEquals("", ExchangeExpression.newInstance(new InterceptorAdapter(router), JSONPATH, "$")
+ assertEquals("", expression(new InterceptorAdapter(router), JSONPATH, "$")
.evaluate(Request.post("/foo").contentType(TEXT_XML).buildExchange(), REQUEST, String.class));
}
- private static T evaluateWithEmptyBodyFor(Class type) throws URISyntaxException {
- return ExchangeExpression.newInstance(new InterceptorAdapter(router), JSONPATH, "$")
-=======
- assertEquals("", expression(router, JSONPATH, "$")
- .evaluate(post("/foo").contentType(TEXT_XML).buildExchange(), REQUEST, String.class));
- }
-
@Test
void array() throws URISyntaxException {
- var expr = expression(router, JSONPATH, "$[0]");
+ var expr = expression( new InterceptorAdapter(router), JSONPATH, "$[0]");
assertEquals(1, expr.evaluate(post("/foo").json("[1,2,3]").buildExchange(), REQUEST, Integer.class));
}
@Test
void number() throws URISyntaxException {
- var expr = expression(router, JSONPATH, "$");
+ var expr = expression(new InterceptorAdapter(router), JSONPATH, "$");
assertEquals(314, expr.evaluate(post("/foo").json("314").buildExchange(), REQUEST, Integer.class));
}
private static T evaluateWithEmptyBodyFor(Class type) throws URISyntaxException {
- return expression(router, JSONPATH, "$")
->>>>>>> f78bb2c5a6937831602bd693024f03f9f2acad2d
- .evaluate(get("/foo").buildExchange(), REQUEST, type);
+ return expression(new InterceptorAdapter(router), JSONPATH, "$").evaluate(get("/foo").buildExchange(), REQUEST, type);
}
}
\ No newline at end of file
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpressionTest.java b/core/src/test/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpressionTest.java
index 4cd6ed1732..cd54d324a8 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpressionTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpressionTest.java
@@ -28,7 +28,7 @@
import static com.predic8.membrane.core.http.Request.*;
import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST;
import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
-import static com.predic8.membrane.core.lang.ExchangeExpression.newInstance;
+import static com.predic8.membrane.core.lang.ExchangeExpression.expression;
import static org.junit.jupiter.api.Assertions.*;
class XPathExchangeExpressionTest extends AbstractExchangeExpressionTest {
@@ -131,7 +131,7 @@ void setup() throws URISyntaxException {
@Test
void localName() {
- assertEquals("Trevor", newInstance( new InterceptorAdapter(router),getLanguage(),
+ assertEquals("Trevor", expression( new InterceptorAdapter(router),getLanguage(),
"//*[local-name()='firstname']")
.evaluate(pExc, REQUEST, String.class));
}
From a3075f972caee02cf0139fb6138994c4da1293c9 Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sun, 2 Nov 2025 19:38:03 +0100
Subject: [PATCH 07/16] refactor: minor
---
.../balancer/PolyglotSessionIdExtractor.java | 16 +++++++---------
.../core/interceptor/flow/choice/Case.java | 14 ++++++--------
2 files changed, 13 insertions(+), 17 deletions(-)
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
index 2b8969341e..f718be179a 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/balancer/PolyglotSessionIdExtractor.java
@@ -13,14 +13,12 @@
limitations under the License. */
package com.predic8.membrane.core.interceptor.balancer;
-import com.predic8.membrane.annot.MCAttribute;
-import com.predic8.membrane.annot.MCElement;
-import com.predic8.membrane.annot.Required;
-import com.predic8.membrane.core.Router;
-import com.predic8.membrane.core.config.AbstractXmlElement;
-import com.predic8.membrane.core.exchange.Exchange;
-import com.predic8.membrane.core.interceptor.Interceptor.Flow;
-import com.predic8.membrane.core.interceptor.lang.Polyglot;
+import com.predic8.membrane.annot.*;
+import com.predic8.membrane.core.*;
+import com.predic8.membrane.core.config.*;
+import com.predic8.membrane.core.exchange.*;
+import com.predic8.membrane.core.interceptor.Interceptor.*;
+import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
@@ -35,7 +33,7 @@ public class PolyglotSessionIdExtractor extends AbstractXmlElement implements Se
public void init(Router router) {
if (sessionSource != null && !sessionSource.isEmpty()) {
- exchangeExpression = ExchangeExpression.expression(new InterceptorAdapter(router), language, sessionSource);
+ exchangeExpression = expression(new InterceptorAdapter(router), language, sessionSource);
}
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
index 52a6129df0..9447442183 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
@@ -14,18 +14,16 @@
package com.predic8.membrane.core.interceptor.flow.choice;
import com.predic8.membrane.annot.*;
-import com.predic8.membrane.core.Router;
-import com.predic8.membrane.core.config.spring.*;
-import com.predic8.membrane.core.exchange.Exchange;
+import com.predic8.membrane.core.*;
+import com.predic8.membrane.core.exchange.*;
+import com.predic8.membrane.core.interceptor.Interceptor.*;
import com.predic8.membrane.core.interceptor.*;
-import com.predic8.membrane.core.interceptor.Interceptor.Flow;
import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.slf4j.*;
-import static com.predic8.membrane.core.lang.ExchangeExpression.Language.SPEL;
+import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
import static com.predic8.membrane.core.lang.ExchangeExpression.expression;
@MCElement(name = "case", topLevel = false)
@@ -39,7 +37,7 @@ public class Case extends InterceptorContainer implements XMLNamespaceSupport {
private Namespaces namespaces;
public void init(Router router) {
- exchangeExpression = ExchangeExpression.expression( new InterceptorAdapter(router,namespaces), language, test);
+ exchangeExpression = expression( new InterceptorAdapter(router,namespaces), language, test);
}
boolean evaluate(Exchange exc, Flow flow) {
From b80838264d2853ffb27f1ba1909578436975a22d Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sun, 2 Nov 2025 20:02:41 +0100
Subject: [PATCH 08/16] refactor: minor
---
.../com/predic8/membrane/core/lang/ExchangeExpression.java | 1 +
.../membrane/core/util/xml/parser/HardenedXmlParser.java | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
index e3721021f1..d6f795d07a 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
@@ -65,6 +65,7 @@ static ExchangeExpression expression(Interceptor interceptor, Language language,
case JSONPATH -> new JsonpathExchangeExpression(expression);
};
}
+
/**
* Allows to pass an Interceptor as an argument where there is no interceptor e.g. Target
*/
diff --git a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
index ca27f80be4..51313588ff 100644
--- a/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
+++ b/core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedXmlParser.java
@@ -70,6 +70,10 @@ private static DocumentBuilderFactory createFactory() {
return f;
}
+ /**
+ * Creates a new DocumentBuilder for XML parsing. Access is not thread-safe!
+ * @return
+ */
private DocumentBuilder newBuilder() {
try {
DocumentBuilder builder = factory.newDocumentBuilder();
From 7a65c481a6256b72e52f90312fd322a9c5dfbc4b Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Sun, 2 Nov 2025 20:15:34 +0100
Subject: [PATCH 09/16] refactor: minor
---
.../SetPropertyInterceptorXPathTest.java | 127 +++++
distribution/conf/apis.yaml | 509 ------------------
.../conf/convert-request-to-new-version.xslt | 25 -
.../conf/convert-response-to-old-version.xslt | 20 -
distribution/conf/k8s-proxies.xml | 0
distribution/conf/security-api-v1.yml | 23 -
distribution/conf/simple.yaml | 27 -
7 files changed, 127 insertions(+), 604 deletions(-)
create mode 100644 core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
delete mode 100644 distribution/conf/apis.yaml
delete mode 100644 distribution/conf/convert-request-to-new-version.xslt
delete mode 100644 distribution/conf/convert-response-to-old-version.xslt
delete mode 100644 distribution/conf/k8s-proxies.xml
delete mode 100644 distribution/conf/security-api-v1.yml
delete mode 100644 distribution/conf/simple.yaml
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java b/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
new file mode 100644
index 0000000000..7473c41b3d
--- /dev/null
+++ b/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
@@ -0,0 +1,127 @@
+/* Copyright 2025 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.lang.xpath;
+
+import com.fasterxml.jackson.databind.*;
+import com.predic8.membrane.core.*;
+import com.predic8.membrane.core.exchange.*;
+import com.predic8.membrane.core.interceptor.lang.*;
+import org.jetbrains.annotations.*;
+import org.junit.jupiter.api.*;
+
+import java.util.*;
+
+import static com.predic8.membrane.core.http.Request.*;
+import static com.predic8.membrane.core.interceptor.Outcome.*;
+import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SetPropertyInterceptorXPathTest {
+
+ private ObjectMapper om = new ObjectMapper();
+ private Exchange exc;
+
+ @Nested
+ class WithoutNamespaces {
+
+ @BeforeEach
+ void setup() throws Exception {
+ exc = post("/person").xml("""
+
+ Trevor
+
+ """).buildExchange();
+ }
+
+ @Test
+ void normal() {
+ var interceptor = getInterceptor(null,"${//firstname}");
+ assertEquals(CONTINUE, interceptor.handleRequest(exc));
+ assertEquals("Trevor", exc.getProperty("firstname").toString());
+ }
+ }
+
+ @Nested
+ class WithNamespaces {
+
+ @BeforeEach
+ void setup() throws Exception {
+ exc = post("/person").xml("""
+
+ Trevor
+ baz
+
+ """).buildExchange();
+ }
+
+ @Test
+ void noNamespacesDeclaredQueryWithPrefix() {
+ var interceptor = getInterceptor(null,"${//p8:firstname}");
+ assertEquals(CONTINUE, interceptor.handleRequest(exc));
+ assertEquals("", exc.getProperty("firstname").toString());
+ }
+
+ @Test
+ void noNamespacesDeclaredQueryWithLocalName() {
+ var interceptor = getInterceptor(null,"${//*[local-name() = 'firstname']}");
+ assertEquals(CONTINUE, interceptor.handleRequest(exc));
+ assertEquals("Trevor", exc.getProperty("firstname").toString());
+ }
+
+ @Test
+ void nsUri() {
+ var interceptor = getInterceptor(null,"${//*[namespace-uri() = 'https://predic8.de/other']}");
+ assertEquals(CONTINUE, interceptor.handleRequest(exc));
+ assertEquals("baz", exc.getProperty("firstname").toString());
+ }
+
+ @Test
+ void nsPrefixed() {
+ var interceptor = getInterceptor(getNamespaces(),"${//p8:firstname}");
+ assertEquals(CONTINUE, interceptor.handleRequest(exc));
+ assertEquals("Trevor", exc.getProperty("firstname").toString());
+ }
+
+ @Test
+ void unknownPrefix() throws Exception {
+ var interceptor = getInterceptor(getNamespaces(),"${//unknown:firstname}");
+ assertEquals(ABORT, interceptor.handleRequest(exc));
+ assertEquals(500, exc.getResponse().getStatusCode());
+ String body = exc.getResponse().getBodyAsStringDecoded();
+ assertTrue(body.contains("${//unknown:firstname}"));
+ assertTrue(body.contains("unknown"));
+ }
+ }
+
+ private static @NotNull SetPropertyInterceptor getInterceptor(Namespaces namespaces, String value) {
+ var i = new SetPropertyInterceptor();
+ i.setNamespaces(namespaces);
+ i.setLanguage(XPATH);
+ i.setFieldName("firstname");
+ i.setValue(value);
+ i.init(new Router());
+ return i;
+ }
+
+ private static @NotNull Namespaces getNamespaces() {
+ var p8 = new Namespaces.Namespace();
+ p8.prefix = "p8";
+ p8.uri = "https://predic8.de";
+ var ns = new Namespaces();
+ ns.setNamespace(List.of(p8));
+ return ns;
+ }
+}
diff --git a/distribution/conf/apis.yaml b/distribution/conf/apis.yaml
deleted file mode 100644
index 7113e23a84..0000000000
--- a/distribution/conf/apis.yaml
+++ /dev/null
@@ -1,509 +0,0 @@
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-kind: api
-spec:
- port: 2000
- target:
- url: https://api.predic8.de
-
----
-
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-kind: api
-spec:
- port: 2001
- path:
- uri: /fruit/{id}
- target:
- url: https://api.predic8.de/shop/v2/products/${pathParam.id}
-
----
-
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2002
- specs:
- - openapi:
- location: "fruitshop-api.yml"
- validateRequests: yes
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2003
- flow:
- - log:
- message: "Headers: ${header}"
- target:
- url: https://api.predic8.de
-
----
-
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2004
- path:
- uri: /shop
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2005
- specs:
- - openapi:
- location: "fruitshop-api.yml"
- validateRequests: yes
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2007
- method: POST
- flow:
- - response:
- - static:
- src: |
- POST is blocked!
- - return:
- statusCode: 405
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2007
- path:
- uri: /shop/v2/products/.*
- isRegExp: true
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2007
- path:
- uri: /shop
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2007
- host: www.predic8.de
- flow:
- - response:
- - static:
- src: Homepage
- - return:
- statusCode: 200
- path:
- uri: /shop
- target:
- url: https://api.predic8.de
-
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2008
- test: params.city == 'Paris'
- flow:
- - response:
- - static:
- src: Oui!
- - return:
- statusCode: 200
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2009
- path:
- uri: /health
- flow:
- - response:
- - static:
- src: I'm good.
- - return:
- statusCode: 200
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2010
- path:
- uri: /nothing
- flow:
- - response:
- - static:
- src: "Nothing to see here."
- - return:
- statusCode: 404
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2011
- flow:
- - rewriter:
- - map:
- from: ^/fruitshop/(.*)
- to: /shop/v2/$1
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2012
- flow:
- - groovy:
- src: |
- println "I'm executed in the ${flow} flow"
- println "HTTP Headers:\n${header}"
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2013
- flow:
- - groovy:
- src: |
- sites = ["https://api.predic8.de","https://membrane-api.io","https://predic8.de"]
- Collections.shuffle sites
- exchange.destinations = sites
- target: {}
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2014
- flow:
- - groovy:
- src: |
- Response.ok()
- .contentType("application/json")
- .header("X-Foo", "bar")
- .body("""
- {
- "success": true
- }""")
- .build()
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2015
- flow:
- - javascript:
- src: |
- console.log("------------ Headers: -------------");
- var fields = header.getAllHeaderFields();
- for (var i = 0; i < fields.length; i++) {
- console.log(fields[i]);
- }
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2016
- flow:
- - javascript:
- src: |
- var body = JSON.stringify({
- foo: 7,
- bar: 42
- });
- Response.ok(body).contentType("application/json").build();
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2017
- flow:
- - response:
- - setHeader:
- name: Access-Control-Allow-Origin
- value: "*"
- - setHeader:
- name: Access-Control-Allow-Methods
- value: GET
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2018
- flow:
- - response:
- - setHeader:
- name: X-Product-Id
- value: ${jsonPath('$.id')}
- - setHeader:
- name: X-Product-Name
- value: ${$.name}
- language: jsonpath
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2019
- flow:
- - response:
- - headerFilter:
- rules:
- - include:
- pattern: "X-XSS-Protection"
- - exclude:
- pattern: "X-.*"
- target:
- url: https://www.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2020
- flow:
- - request:
- - template:
- contentType: application/json
- pretty: true
- src: |
- { "answer": ${params.answer} }
- - return:
- statusCode: 200
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2021
- flow:
- - request:
- - template:
- contentType: text/plain
- src: |
- City: ${json.city}
- - return:
- statusCode: 200
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2022
- flow:
- - request:
- - template:
- contentType: application/json
- src: |
- {
- "destination": "${json.city}"
- }
- - return:
- statusCode: 200
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2023
- flow:
- - request:
- - template:
- contentType: application/xml
- src: |
-
- ${json.city}
-
- - return:
- statusCode: 200
-
----
-spec:
- port: 2024
- flow:
- - request:
- - javascript:
- src: |
- function convertDate(d) {
- return d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0"+d.getDate()).slice(-2);
- }
-
- ({
- id: json.id,
- date: convertDate(new Date(json.date)),
- client: json.customer,
- total: json.items.map(i => i.quantity * i.price).reduce((a,b) => a+b),
- positions: json.items.map(i => ({
- pieces: i.quantity,
- price: i.price,
- article: i.description
- }))
- })
- - return:
- statusCode: 200
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2025
- flow:
- - response:
- - beautifier: {} # Response flow is reversed
- - template:
- contentType: application/xml
- src: |
- Baz
- - return:
- statusCode: 200
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2026
- flow:
- - response:
- - if:
- test: statusCode matches '4\d\d'
- flow:
- - static:
- src: "Stupid User!"
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2027
- flow:
- - apiKey:
- stores:
- - keys:
- - secret:
- value: abc123
- - secret:
- value: secret123
- - return:
- statusCode: 200
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2028
- flow:
- - jwtAuth:
- expectedAud: "api://2axxxx16-xxxx-xxxx-xxxx-faxxxxxxxxf0"
- jwks:
- jwksUris: https://login.microsoftonline.com/common/discovery/keys
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2029
- flow:
- - oauth2Resource2:
- membrane:
- src: https://accounts.google.com
- clientId: INSERT_CLIENT_ID
- clientSecret: INSERT_CLIENT_SECRET
- scope: email profile
- subject: sub
- - groovy:
- src: |
- // Get email from OAuth2 and forward it to the backend
- def oauth2 = exc.properties.'membrane.oauth2'
- header.setValue('X-EMAIL',oauth2.userinfo.email)
- target:
- url: https://backend
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-#spec:
-# port: 2030
-# flow:
-# - oauth2authserver:
-# location: logindialog
-# issuer: http://localhost:2000
-# consentFile: consent.json
-# staticUserDataProvider:
-# users:
-# - user:
-# username: john
-# password: secret
-# email: john@predic8.de
-# bearerJwtToken: {}
-# claims:
-# scopes:
-# - scope:
-# id: username
-# target:
-# url: http://backend
-
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2031
- flow:
- - basicAuthentication:
- users:
- - user:
- username: bob
- password: secret
- - user:
- username: alice
- password: secret
- target:
- host: localhost
- port: 8080
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2032
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 443
- ssl:
- keystore:
- location: membrane.p12
- password: secret
- keyPassword: secret
- target:
- host: localhost
- port: 8080
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2033
- flow:
- - xmlProtection:
- maxElementNameLength: 100
- target:
- url: https://api.predic8.de
-
----
-# yaml-language-server: $schema=../../core/target/classes/com/predic8/membrane/core/config/json/membrane.schema.json
-spec:
- port: 2034
- flow:
- - jsonProtection:
- maxDepth: 5
- maxKeyLength: 30
- maxStringLength: 100000
- target:
- url: https://api.predic8.de
\ No newline at end of file
diff --git a/distribution/conf/convert-request-to-new-version.xslt b/distribution/conf/convert-request-to-new-version.xslt
deleted file mode 100644
index 3123616690..0000000000
--- a/distribution/conf/convert-request-to-new-version.xslt
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/distribution/conf/convert-response-to-old-version.xslt b/distribution/conf/convert-response-to-old-version.xslt
deleted file mode 100644
index f7ae624409..0000000000
--- a/distribution/conf/convert-response-to-old-version.xslt
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/distribution/conf/k8s-proxies.xml b/distribution/conf/k8s-proxies.xml
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/distribution/conf/security-api-v1.yml b/distribution/conf/security-api-v1.yml
deleted file mode 100644
index 9f2bcae245..0000000000
--- a/distribution/conf/security-api-v1.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-openapi: '3.0.2'
-info:
- title: Security Demo API
- version: '1.0'
-
-servers:
- - url: http://localhost:2000/security-api/v1/
-
-paths:
- /:
- get:
- security:
- - apikey: []
- responses:
- 200:
- description: OK
-
-components:
- securitySchemes:
- apikey:
- type: apiKey
- name: X-Api-Keyz
- in: query
\ No newline at end of file
diff --git a/distribution/conf/simple.yaml b/distribution/conf/simple.yaml
deleted file mode 100644
index fbf4b5c7aa..0000000000
--- a/distribution/conf/simple.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-openapi: 3.1.0
-info:
- title: Simple Parameters API
- version: "1.0.0"
-servers:
- - url: http://localhost
-
-paths:
- /foo:
- get:
- parameters:
- - name: bar
- in: query
- explode: true
- schema:
- type: array
- items:
- type: string
- - name: baz
- in: query
- explode: true
- schema:
- type: integer
- responses:
- "200":
- description: OK
-
From c9e9734dcab5155c6eae5fb0b015c02791b9c082 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christian=20G=C3=B6rdes?=
Date: Mon, 3 Nov 2025 15:27:50 +0100
Subject: [PATCH 10/16] add test cases
---
.../xml/namespaces/NamespacesExampleTest.java | 117 +++++++++++++++++-
1 file changed, 115 insertions(+), 2 deletions(-)
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
index 9ce7c07f7b..4ad0a5c519 100644
--- a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
@@ -28,8 +28,6 @@ protected String getExampleDirName() {
return "xml/namespaces";
}
- // TODO implement other cases as well
-
@Test
void namespaceAwareXPathExtraction() {
String xmlBody = """
@@ -76,4 +74,119 @@ void differentCity() {
// @formatter:on
}
+ @Test
+ void differentPrefixesStillMatch() {
+ String xmlBody = """
+
+ Kim
+
+ Bonn
+
+
+ """;
+
+ // @formatter:off
+ given()
+ .contentType(TEXT_XML)
+ .body(xmlBody)
+ .post("http://localhost:2000")
+ .then()
+ .log().ifValidationFails(ALL)
+ .statusCode(200)
+ .body(containsString("Kim from Bonn"));
+ // @formatter:on
+ }
+
+ @Test
+ void defaultNamespaceOnPerson() {
+ String xmlBody = """
+
+ Udo
+
+ Hamburg
+
+
+ """;
+
+ // @formatter:off
+ given()
+ .contentType(TEXT_XML)
+ .body(xmlBody)
+ .post("http://localhost:2000")
+ .then()
+ .log().ifValidationFails(ALL)
+ .statusCode(200)
+ .body(containsString("Udo from Hamburg"));
+ // @formatter:on
+ }
+
+ @Test
+ void noNamespacesShouldNotMatch() {
+ String xmlBody = """
+
+ Max
+
+ Cologne
+
+
+ """;
+
+ // @formatter:off
+ given()
+ .contentType(TEXT_XML)
+ .body(xmlBody)
+ .post("http://localhost:2000")
+ .then()
+ .log().ifValidationFails(ALL)
+ .statusCode(200)
+ .body(containsString("from"));
+ // @formatter:on
+ }
+
+ @Test
+ void wrongNamespaceOnPerson() {
+ String xmlBody = """
+
+ Hans
+
+ Cologne
+
+
+ """;
+
+ // @formatter:off
+ given()
+ .contentType(TEXT_XML)
+ .body(xmlBody)
+ .post("http://localhost:2000")
+ .then()
+ .log().ifValidationFails(ALL)
+ .statusCode(200)
+ .body(containsString("from Cologne"));
+ // @formatter:on
+ }
+
+ @Test
+ void wrongNamespaceOnAddress() {
+ String xmlBody = """
+
+ Hans
+
+ Cologne
+
+
+ """;
+
+ // @formatter:off
+ given()
+ .contentType(TEXT_XML)
+ .body(xmlBody)
+ .post("http://localhost:2000")
+ .then()
+ .log().ifValidationFails(ALL)
+ .statusCode(200)
+ .body(containsString("Hans from"));
+ // @formatter:on
+ }
+
}
From 4aebbb07550a7e69abaeb71f4b06c5c91d97681d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christian=20G=C3=B6rdes?=
Date: Mon, 3 Nov 2025 16:09:17 +0100
Subject: [PATCH 11/16] add test case for header (fails)
---
.../xml/namespaces/NamespacesExampleTest.java | 50 ++++++-------------
1 file changed, 15 insertions(+), 35 deletions(-)
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
index 4ad0a5c519..375afdf5b0 100644
--- a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
@@ -19,6 +19,7 @@
import static com.predic8.membrane.core.http.MimeType.TEXT_XML;
import static io.restassured.RestAssured.given;
import static io.restassured.filter.log.LogDetail.ALL;
+import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.containsString;
public class NamespacesExampleTest extends AbstractSampleMembraneStartStopTestcase {
@@ -120,35 +121,13 @@ void defaultNamespaceOnPerson() {
// @formatter:on
}
- @Test
- void noNamespacesShouldNotMatch() {
- String xmlBody = """
-
- Max
-
- Cologne
-
-
- """;
-
- // @formatter:off
- given()
- .contentType(TEXT_XML)
- .body(xmlBody)
- .post("http://localhost:2000")
- .then()
- .log().ifValidationFails(ALL)
- .statusCode(200)
- .body(containsString("from"));
- // @formatter:on
- }
@Test
- void wrongNamespaceOnPerson() {
+ void wrongNamespaceOnAddress() {
String xmlBody = """
-
+
Hans
-
+
Cologne
@@ -162,20 +141,20 @@ void wrongNamespaceOnPerson() {
.then()
.log().ifValidationFails(ALL)
.statusCode(200)
- .body(containsString("from Cologne"));
+ .body(containsString("Hans from"));
// @formatter:on
}
@Test
- void wrongNamespaceOnAddress() {
+ void headerIdIsSet() {
String xmlBody = """
-
- Hans
-
- Cologne
-
-
- """;
+
+ Hans
+
+ Cologne
+
+
+ """;
// @formatter:off
given()
@@ -185,7 +164,8 @@ void wrongNamespaceOnAddress() {
.then()
.log().ifValidationFails(ALL)
.statusCode(200)
- .body(containsString("Hans from"));
+ .header("id", equalTo("77"))
+ .body(containsString("Hans from Cologne"));
// @formatter:on
}
From aa3ad0d8803a0f0bcee60d0b3828cba578cb5d5e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christian=20G=C3=B6rdes?=
Date: Mon, 3 Nov 2025 16:41:35 +0100
Subject: [PATCH 12/16] add test case for header
---
distribution/examples/xml/namespaces/proxies.xml | 2 ++
.../test/xml/namespaces/NamespacesExampleTest.java | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/distribution/examples/xml/namespaces/proxies.xml b/distribution/examples/xml/namespaces/proxies.xml
index 059d75cb83..836e0ce1ce 100644
--- a/distribution/examples/xml/namespaces/proxies.xml
+++ b/distribution/examples/xml/namespaces/proxies.xml
@@ -35,12 +35,14 @@
+
+
${property.name} from ${property.city}
diff --git a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
index 375afdf5b0..8f6f5bb6a1 100644
--- a/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
+++ b/distribution/src/test/java/com/predic8/membrane/examples/withoutinternet/test/xml/namespaces/NamespacesExampleTest.java
@@ -164,7 +164,7 @@ void headerIdIsSet() {
.then()
.log().ifValidationFails(ALL)
.statusCode(200)
- .header("id", equalTo("77"))
+ .header("x-seen-request-id", equalTo("77"))
.body(containsString("Hans from Cologne"));
// @formatter:on
}
From 429e21adaee5dfea171919efe6695013d6116399 Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Mon, 3 Nov 2025 20:35:06 +0100
Subject: [PATCH 13/16] refactor: minor
---
.../core/lang/xpath/SetPropertyInterceptorXPathTest.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java b/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
index 7473c41b3d..265d01023b 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
@@ -31,7 +31,6 @@
class SetPropertyInterceptorXPathTest {
- private ObjectMapper om = new ObjectMapper();
private Exchange exc;
@Nested
@@ -96,7 +95,7 @@ void nsPrefixed() {
}
@Test
- void unknownPrefix() throws Exception {
+ void unknownPrefix() {
var interceptor = getInterceptor(getNamespaces(),"${//unknown:firstname}");
assertEquals(ABORT, interceptor.handleRequest(exc));
assertEquals(500, exc.getResponse().getStatusCode());
From 8e27a36144e6af4268c8cf14c6e0284f0168941c Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Wed, 5 Nov 2025 12:25:50 +0100
Subject: [PATCH 14/16] refactor: xmlConfig instead of just namespaces
---
.../membrane/core/config/XMLElement.java | 4 +--
.../lang => config/xml}/Namespaces.java | 4 +--
.../membrane/core/config/xml/XmlConfig.java | 18 +++++++++++++
...LNamespaceSupport.java => XMLSupport.java} | 8 +++---
.../extractors/ApiKeyExpressionExtractor.java | 21 +++++++++-------
.../core/interceptor/flow/IfInterceptor.java | 21 ++++++++--------
.../core/interceptor/flow/choice/Case.java | 22 ++++++++--------
.../lang/AbstractLanguageInterceptor.java | 21 +++++++++-------
.../core/lang/ExchangeExpression.java | 24 +++++++++++-------
.../lang/xpath/XPathExchangeExpression.java | 16 ++++++------
.../core/openapi/serviceproxy/APIProxy.java | 24 ++++++++++--------
.../core/proxies/AbstractServiceProxy.java | 25 +++++++++++--------
.../core/interceptor/lang/NamespacesTest.java | 1 +
.../SetPropertyInterceptorXPathTest.java | 7 ++++--
14 files changed, 131 insertions(+), 85 deletions(-)
rename core/src/main/java/com/predic8/membrane/core/{interceptor/lang => config/xml}/Namespaces.java (96%)
create mode 100644 core/src/main/java/com/predic8/membrane/core/config/xml/XmlConfig.java
rename core/src/main/java/com/predic8/membrane/core/interceptor/{XMLNamespaceSupport.java => XMLSupport.java} (79%)
diff --git a/core/src/main/java/com/predic8/membrane/core/config/XMLElement.java b/core/src/main/java/com/predic8/membrane/core/config/XMLElement.java
index 2e91bb7f46..49ba71b217 100644
--- a/core/src/main/java/com/predic8/membrane/core/config/XMLElement.java
+++ b/core/src/main/java/com/predic8/membrane/core/config/XMLElement.java
@@ -20,8 +20,8 @@
public interface XMLElement {
- public abstract XMLElement parse(XMLStreamReader token) throws Exception;
+ XMLElement parse(XMLStreamReader token) throws Exception;
- public abstract void write(XMLStreamWriter out) throws XMLStreamException;
+ void write(XMLStreamWriter out) throws XMLStreamException;
}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java b/core/src/main/java/com/predic8/membrane/core/config/xml/Namespaces.java
similarity index 96%
rename from core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
rename to core/src/main/java/com/predic8/membrane/core/config/xml/Namespaces.java
index a077c53022..8f230f2996 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/Namespaces.java
+++ b/core/src/main/java/com/predic8/membrane/core/config/xml/Namespaces.java
@@ -12,7 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License. */
-package com.predic8.membrane.core.interceptor.lang;
+package com.predic8.membrane.core.config.xml;
import com.predic8.membrane.annot.*;
@@ -21,7 +21,7 @@
import static javax.xml.XMLConstants.NULL_NS_URI;
-@MCElement(name="namespaces", topLevel = true)
+@MCElement(name="namespaces", topLevel = false)
public class Namespaces {
private List namespaces;
diff --git a/core/src/main/java/com/predic8/membrane/core/config/xml/XmlConfig.java b/core/src/main/java/com/predic8/membrane/core/config/xml/XmlConfig.java
new file mode 100644
index 0000000000..7561059b5d
--- /dev/null
+++ b/core/src/main/java/com/predic8/membrane/core/config/xml/XmlConfig.java
@@ -0,0 +1,18 @@
+package com.predic8.membrane.core.config.xml;
+
+import com.predic8.membrane.annot.*;
+
+@MCElement(name="xmlConfig",topLevel = true)
+public class XmlConfig {
+
+ private Namespaces namespaces;
+
+ @MCChildElement
+ public void setNamespaces(Namespaces namespaces) {
+ this.namespaces = namespaces;
+ }
+
+ public Namespaces getNamespaces() {
+ return namespaces;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java b/core/src/main/java/com/predic8/membrane/core/interceptor/XMLSupport.java
similarity index 79%
rename from core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java
rename to core/src/main/java/com/predic8/membrane/core/interceptor/XMLSupport.java
index b0910b164f..58acf075ff 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/XMLNamespaceSupport.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/XMLSupport.java
@@ -14,11 +14,11 @@
package com.predic8.membrane.core.interceptor;
-import com.predic8.membrane.core.interceptor.lang.*;
+import com.predic8.membrane.core.config.xml.*;
-public interface XMLNamespaceSupport {
+public interface XMLSupport {
- void setNamespaces(Namespaces namespaces);
+ void setXmlConfig(XmlConfig xmlConfig);
- Namespaces getNamespaces();
+ XmlConfig getXmlConfig();
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
index 1def43cd74..4a1ae88d9e 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/apikey/extractors/ApiKeyExpressionExtractor.java
@@ -15,6 +15,7 @@
import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.Router;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.exchange.Exchange;
import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.interceptor.lang.*;
@@ -47,16 +48,16 @@
* @topic 3. Security and Validation
*/
@MCElement(name="expressionExtractor", topLevel = false)
-public class ApiKeyExpressionExtractor implements ApiKeyExtractor, Polyglot, XMLNamespaceSupport {
+public class ApiKeyExpressionExtractor implements ApiKeyExtractor, Polyglot, XMLSupport {
private String expression = "";
private Language language = SPEL;
private ExchangeExpression exchangeExpression;
- private Namespaces namespaces;
+ private XmlConfig xmlConfig;
@Override
public void init(Router router) {
- exchangeExpression = expression(new InterceptorAdapter(router,namespaces), language, expression);
+ exchangeExpression = expression(new InterceptorAdapter(router, xmlConfig), language, expression);
}
@Override
@@ -98,15 +99,17 @@ public void setExpression(String expression) {
}
/**
- * Declaration of XML namespaces for XPath expressions.
- * @param namespaces
+ * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ...
+ * @param xmlConfig
*/
+ @Override
@MCChildElement(allowForeign = true)
- public void setNamespaces(Namespaces namespaces) {
- this.namespaces = namespaces;
+ public void setXmlConfig(XmlConfig xmlConfig) {
+ this.xmlConfig = xmlConfig;
}
- public Namespaces getNamespaces() {
- return namespaces;
+ @Override
+ public XmlConfig getXmlConfig() {
+ return xmlConfig;
}
}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
index 8feb81af59..f00c84e7f6 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/IfInterceptor.java
@@ -15,12 +15,11 @@
package com.predic8.membrane.core.interceptor.flow;
import com.predic8.membrane.annot.*;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.interceptor.*;
-import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
-import com.predic8.membrane.core.lang.xpath.*;
import org.slf4j.*;
import static com.predic8.membrane.core.exceptions.ProblemDetails.*;
@@ -40,13 +39,14 @@
* @topic 1. Proxies and Flow
*/
@MCElement(name = "if")
-public class IfInterceptor extends AbstractFlowWithChildrenInterceptor implements XMLNamespaceSupport {
+public class IfInterceptor extends AbstractFlowWithChildrenInterceptor implements XMLSupport {
private static final Logger log = LoggerFactory.getLogger(IfInterceptor.class);
private String test;
private Language language = SPEL;
private ExchangeExpression exchangeExpression;
+ private XmlConfig xmlConfig;
public IfInterceptor() {
name = "if";
@@ -135,16 +135,17 @@ public String getShortDescription() {
}
/**
- * XML namespaces to be used in expressions.
+ * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ...
+ * @param xmlConfig
*/
- protected Namespaces namespaces;
-
+ @Override
@MCChildElement(allowForeign = true,order = 10)
- public void setNamespaces(Namespaces namespaces) {
- this.namespaces = namespaces;
+ public void setXmlConfig(XmlConfig xmlConfig) {
+ this.xmlConfig = xmlConfig;
}
- public Namespaces getNamespaces() {
- return namespaces;
+ @Override
+ public XmlConfig getXmlConfig() {
+ return xmlConfig;
}
}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
index 9447442183..58cd7dc1d3 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/choice/Case.java
@@ -15,10 +15,10 @@
import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.*;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.interceptor.Interceptor.*;
import com.predic8.membrane.core.interceptor.*;
-import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
import org.slf4j.*;
@@ -27,17 +27,17 @@
import static com.predic8.membrane.core.lang.ExchangeExpression.expression;
@MCElement(name = "case", topLevel = false)
-public class Case extends InterceptorContainer implements XMLNamespaceSupport {
+public class Case extends InterceptorContainer implements XMLSupport {
private static final Logger log = LoggerFactory.getLogger(Case.class);
private String test;
private Language language = SPEL;
private ExchangeExpression exchangeExpression;
- private Namespaces namespaces;
+ private XmlConfig xmlConfig;
public void init(Router router) {
- exchangeExpression = expression( new InterceptorAdapter(router,namespaces), language, test);
+ exchangeExpression = expression( new InterceptorAdapter(router,xmlConfig), language, test);
}
boolean evaluate(Exchange exc, Flow flow) {
@@ -79,15 +79,17 @@ public void setTest(String test) {
}
/**
- * Declaration of XML namespaces for XPath expressions.
- * @param namespaces
+ * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ...
+ * @param xmlConfig
*/
+ @Override
@MCChildElement(allowForeign = true,order = 10)
- public void setNamespaces(Namespaces namespaces) {
- this.namespaces = namespaces;
+ public void setXmlConfig(XmlConfig xmlConfig) {
+ this.xmlConfig = xmlConfig;
}
- public Namespaces getNamespaces() {
- return namespaces;
+ @Override
+ public XmlConfig getXmlConfig() {
+ return xmlConfig;
}
}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
index 50516a2dd5..6ec9d75bcf 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractLanguageInterceptor.java
@@ -15,18 +15,19 @@
package com.predic8.membrane.core.interceptor.lang;
import com.predic8.membrane.annot.*;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
-public abstract class AbstractLanguageInterceptor extends AbstractInterceptor implements Polyglot, XMLNamespaceSupport {
+public abstract class AbstractLanguageInterceptor extends AbstractInterceptor implements Polyglot, XMLSupport {
/**
* SpEL is default
*/
protected Language language = SPEL;
- protected Namespaces namespaces;
+ protected XmlConfig xmlConfig;
public Language getLanguage() {
return language;
@@ -43,15 +44,17 @@ public void setLanguage(Language language) {
}
/**
- * Declaration of XML namespaces for XPath expressions.
- * @param namespaces
+ * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ...
+ * @param xmlConfig
*/
- @MCChildElement(allowForeign = true)
- public void setNamespaces(Namespaces namespaces) {
- this.namespaces = namespaces;
+ @Override
+ @MCChildElement(allowForeign = true,order = 10)
+ public void setXmlConfig(XmlConfig xmlConfig) {
+ this.xmlConfig = xmlConfig;
}
- public Namespaces getNamespaces() {
- return namespaces;
+ @Override
+ public XmlConfig getXmlConfig() {
+ return xmlConfig;
}
}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
index d6f795d07a..b96947bc32 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java
@@ -14,10 +14,11 @@
package com.predic8.membrane.core.lang;
+import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.*;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.interceptor.*;
-import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.groovy.*;
import com.predic8.membrane.core.lang.jsonpath.*;
import com.predic8.membrane.core.lang.spel.*;
@@ -69,27 +70,32 @@ static ExchangeExpression expression(Interceptor interceptor, Language language,
/**
* Allows to pass an Interceptor as an argument where there is no interceptor e.g. Target
*/
- class InterceptorAdapter extends AbstractInterceptor implements XMLNamespaceSupport{
+ class InterceptorAdapter extends AbstractInterceptor implements XMLSupport {
- private Namespaces namespaces;
+ private XmlConfig xmlConfig;
public InterceptorAdapter(Router router) {
this.router = router;
}
- public InterceptorAdapter(Router router, Namespaces namespaces) {
+ public InterceptorAdapter(Router router, XmlConfig xmlConfig) {
this.router = router;
- this.namespaces = namespaces;
+ this.xmlConfig = xmlConfig;
}
+ /**
+ * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ...
+ * @param xmlConfig
+ */
@Override
- public void setNamespaces(Namespaces namespaces) {
- this.namespaces = namespaces;
+ @MCChildElement(allowForeign = true,order = 10)
+ public void setXmlConfig(XmlConfig xmlConfig) {
+ this.xmlConfig = xmlConfig;
}
@Override
- public Namespaces getNamespaces() {
- return namespaces;
+ public XmlConfig getXmlConfig() {
+ return xmlConfig;
}
}
}
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
index 139ced5d93..0dc662d887 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
@@ -14,10 +14,10 @@
package com.predic8.membrane.core.lang.xpath;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.http.*;
import com.predic8.membrane.core.interceptor.*;
-import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.*;
import com.predic8.membrane.core.util.xml.*;
import com.predic8.membrane.core.util.xml.parser.*;
@@ -35,7 +35,7 @@ public class XPathExchangeExpression extends AbstractExchangeExpression {
private static final XmlParser parser = HardenedXmlParser.getInstance();
- private Namespaces namespaces;
+ private XmlConfig xmlConfig;
// Let all expressions share the same XPathFactory.
private static final XPathFactory factory = XPathFactory.newInstance();
@@ -43,8 +43,8 @@ public class XPathExchangeExpression extends AbstractExchangeExpression {
public XPathExchangeExpression(Interceptor interceptor, String xpath) {
super(xpath);
- if (interceptor instanceof XMLNamespaceSupport xns) {
- namespaces = xns.getNamespaces();
+ if (interceptor instanceof XMLSupport xns) {
+ xmlConfig = xns.getXmlConfig();
}
}
@@ -86,14 +86,14 @@ private Object evalutateAndCast(Message msg, QName xmlType) throws XPathExpressi
// XPath is not thread safe! Therefore, every time the factory is called!
XPath xPath = factory.newXPath();
- if (namespaces != null) {
- xPath.setNamespaceContext(namespaces.getNamespaceContext());
+ if (xmlConfig != null) {
+ xPath.setNamespaceContext(xmlConfig.getNamespaces().getNamespaceContext());
}
return xPath.evaluate(expression, parser.parse(XMLUtil.getInputSource(msg)), xmlType);
}
- public void setNamespaces(Namespaces namespaces) {
- this.namespaces = namespaces;
+ public void setXmlConfig(XmlConfig xmlConfig) {
+ this.xmlConfig = xmlConfig;
}
}
diff --git a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java
index c8772620e2..022e8e1aa4 100644
--- a/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java
+++ b/core/src/main/java/com/predic8/membrane/core/openapi/serviceproxy/APIProxy.java
@@ -20,6 +20,8 @@
import com.predic8.membrane.annot.MCChildElement;
import com.predic8.membrane.annot.MCElement;
import com.predic8.membrane.annot.MCTextContent;
+import com.predic8.membrane.core.config.xml.*;
+import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
@@ -27,6 +29,7 @@
import com.predic8.membrane.core.proxies.ServiceProxy;
import com.predic8.membrane.core.util.ConfigurationException;
import com.predic8.membrane.core.util.URIFactory;
+import com.predic8.membrane.core.util.xml.*;
import io.swagger.v3.oas.models.servers.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,7 +45,7 @@
* @topic 1. Proxies and Flow
*/
@MCElement(name = "api")
-public class APIProxy extends ServiceProxy implements Polyglot {
+public class APIProxy extends ServiceProxy implements Polyglot, XMLSupport {
private static final Logger log = LoggerFactory.getLogger(APIProxy.class.getName());
@@ -58,7 +61,7 @@ public class APIProxy extends ServiceProxy implements Polyglot {
private String test;
private String id;
private ApiDescription description;
- private Namespaces namespaces;
+ private XmlConfig xmlConfig;
protected Map apiRecords = new LinkedHashMap<>();
@@ -84,7 +87,7 @@ public void setSpecs(List specs) {
public void init() {
super.init();
if (test != null && !test.isEmpty()) {
- exchangeExpression = expression(new InterceptorAdapter(router,namespaces), language, test);
+ exchangeExpression = expression(new InterceptorAdapter(router, xmlConfig), language, test);
}
key = new APIProxyKey(key, exchangeExpression, !specs.isEmpty());
initOpenAPI();
@@ -239,15 +242,16 @@ public String getContent() {
}
/**
- * Declaration of XML namespaces for XPath expressions.
- * @param namespaces
+ * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ...
+ * @param xmlConfig
*/
- @MCChildElement(allowForeign = true, order = 10)
- public void setNamespaces(Namespaces namespaces) {
- this.namespaces = namespaces;
+ @MCChildElement(allowForeign = true,order = 10)
+ public void setXmlConfig(XmlConfig xmlConfig) {
+ this.xmlConfig = xmlConfig;
}
- public Namespaces getNamespaces() {
- return namespaces;
+ public XmlConfig getXmlConfig() {
+ return xmlConfig;
}
+
}
\ No newline at end of file
diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java b/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java
index b26eedcce8..8af45f2e19 100644
--- a/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java
+++ b/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java
@@ -18,13 +18,12 @@
import com.predic8.membrane.core.Router;
import com.predic8.membrane.core.config.*;
import com.predic8.membrane.core.config.security.*;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.exchange.Exchange;
import com.predic8.membrane.core.interceptor.*;
-import com.predic8.membrane.core.interceptor.lang.*;
import com.predic8.membrane.core.lang.ExchangeExpression;
import com.predic8.membrane.core.lang.ExchangeExpression.*;
import com.predic8.membrane.core.lang.TemplateExchangeExpression;
-import com.predic8.membrane.core.lang.xpath.*;
import com.predic8.membrane.core.transport.ssl.*;
import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
@@ -99,7 +98,7 @@ public void setPath(Path path) {
*
*/
@MCElement(name = "target", topLevel = false)
- public static class Target implements XMLNamespaceSupport {
+ public static class Target implements XMLSupport {
private String host;
private int port = -1;
private String method;
@@ -110,11 +109,11 @@ public static class Target implements XMLNamespaceSupport {
private SSLParser sslParser;
- protected Namespaces namespaces;
+ protected XmlConfig xmlConfig;
public void init(Router router) {
if (url != null) {
- exchangeExpression = TemplateExchangeExpression.newInstance(new InterceptorAdapter(router,namespaces), language, url);
+ exchangeExpression = TemplateExchangeExpression.newInstance(new InterceptorAdapter(router,xmlConfig), language, url);
}
}
@@ -237,13 +236,19 @@ public void setLanguage(ExchangeExpression.Language language) {
this.language = language;
}
- @MCChildElement(allowForeign = true, order = 100)
- public void setNamespaces(Namespaces namespaces) {
- this.namespaces = namespaces;
+ /**
+ * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ...
+ * @param xmlConfig
+ */
+ @Override
+ @MCChildElement(allowForeign = true,order = 10)
+ public void setXmlConfig(XmlConfig xmlConfig) {
+ this.xmlConfig = xmlConfig;
}
- public Namespaces getNamespaces() {
- return namespaces;
+ @Override
+ public XmlConfig getXmlConfig() {
+ return xmlConfig;
}
}
diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/lang/NamespacesTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/lang/NamespacesTest.java
index 88d6c78d41..232e27576a 100644
--- a/core/src/test/java/com/predic8/membrane/core/interceptor/lang/NamespacesTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/interceptor/lang/NamespacesTest.java
@@ -14,6 +14,7 @@
package com.predic8.membrane.core.interceptor.lang;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.util.*;
import org.junit.jupiter.api.*;
diff --git a/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java b/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
index 265d01023b..915149fb6e 100644
--- a/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
+++ b/core/src/test/java/com/predic8/membrane/core/lang/xpath/SetPropertyInterceptorXPathTest.java
@@ -14,9 +14,10 @@
package com.predic8.membrane.core.lang.xpath;
-import com.fasterxml.jackson.databind.*;
import com.predic8.membrane.core.*;
+import com.predic8.membrane.core.config.xml.*;
import com.predic8.membrane.core.exchange.*;
+import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.interceptor.lang.*;
import org.jetbrains.annotations.*;
import org.junit.jupiter.api.*;
@@ -107,7 +108,9 @@ void unknownPrefix() {
private static @NotNull SetPropertyInterceptor getInterceptor(Namespaces namespaces, String value) {
var i = new SetPropertyInterceptor();
- i.setNamespaces(namespaces);
+ XmlConfig xc = new XmlConfig();
+ xc.setNamespaces(namespaces);
+ i.setXmlConfig(xc);
i.setLanguage(XPATH);
i.setFieldName("firstname");
i.setValue(value);
From d05683eb884757c04ae302bd426d8a0204fb0e3c Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Wed, 5 Nov 2025 13:23:13 +0100
Subject: [PATCH 15/16] refactor: minor
---
.../java/com/predic8/membrane/core/config/xml/Namespaces.java | 2 +-
.../membrane/core/lang/xpath/XPathExchangeExpression.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/core/src/main/java/com/predic8/membrane/core/config/xml/Namespaces.java b/core/src/main/java/com/predic8/membrane/core/config/xml/Namespaces.java
index 8f230f2996..75bf5ddc77 100644
--- a/core/src/main/java/com/predic8/membrane/core/config/xml/Namespaces.java
+++ b/core/src/main/java/com/predic8/membrane/core/config/xml/Namespaces.java
@@ -32,7 +32,7 @@ public NamespaceContext getNamespaceContext() {
}
/**
- * @description Defines a regex and a replacement for the rewriting of the URI.
+ * @description Defines XML namespace mappings (prefix to URI) for use in XPath expressions.
*/
@MCChildElement(allowForeign = false)
public void setNamespace(List namespace) {
diff --git a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
index 0dc662d887..ee19ebeb8e 100644
--- a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
+++ b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
@@ -86,7 +86,7 @@ private Object evalutateAndCast(Message msg, QName xmlType) throws XPathExpressi
// XPath is not thread safe! Therefore, every time the factory is called!
XPath xPath = factory.newXPath();
- if (xmlConfig != null) {
+ if (xmlConfig != null && xmlConfig.getNamespaces() != null) {
xPath.setNamespaceContext(xmlConfig.getNamespaces().getNamespaceContext());
}
From 04a6490b2b425660b28103e84d7351cc3306b789 Mon Sep 17 00:00:00 2001
From: Thomas Bayer
Date: Wed, 5 Nov 2025 13:47:23 +0100
Subject: [PATCH 16/16] refactor: minor
---
.../examples/xml/namespaces/proxies.xml | 24 ++++++++++++-------
.../examples/xml/namespaces/requests.http | 2 +-
2 files changed, 17 insertions(+), 9 deletions(-)
diff --git a/distribution/examples/xml/namespaces/proxies.xml b/distribution/examples/xml/namespaces/proxies.xml
index 836e0ce1ce..519c3ece52 100644
--- a/distribution/examples/xml/namespaces/proxies.xml
+++ b/distribution/examples/xml/namespaces/proxies.xml
@@ -4,11 +4,13 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://membrane-soa.org/proxies/1/ http://membrane-soa.org/schemas/proxies-1.xsd">
-
-
-
-
-
+
+
+
+
+
+
+
@@ -19,12 +21,18 @@
-
-
+
+
+
+
+
+
+
+
-
+
diff --git a/distribution/examples/xml/namespaces/requests.http b/distribution/examples/xml/namespaces/requests.http
index e270a2349f..e9e79af326 100644
--- a/distribution/examples/xml/namespaces/requests.http
+++ b/distribution/examples/xml/namespaces/requests.http
@@ -2,7 +2,7 @@ POST http://localhost:2000
Content-Type: text/xml
- Hans
+ Herbert
Cologne