Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
477d87d
refactor(wsdl): streamline WSDL handling, improve logging, and adjust…
predic8 Jan 8, 2026
a75983f
add: tutorial for SOAP proxy with YAML configuration and integration …
predic8 Jan 8, 2026
7ecedb3
Merge branch 'master' into soapproxy-wsdlinterceptor-yaml-fix
predic8 Jan 8, 2026
7eb7420
add: WSDL rewriting example, SOAP tutorial tests, and script refactor
predic8 Jan 8, 2026
65944d7
Merge branch 'master' into soapproxy-wsdlinterceptor-yaml-fix
predic8 Jan 8, 2026
6cd2ac3
refactor(tests): clean up SOAP test logic and streamline method naming
predic8 Jan 8, 2026
1223492
Merge branch 'master' into soapproxy-wsdlinterceptor-yaml-fix
predic8 Jan 9, 2026
6ae9edd
Merge remote-tracking branch 'origin/soapproxy-wsdlinterceptor-yaml-f…
predic8 Jan 9, 2026
aa83d00
add: WSDL message validation tutorial with example XML
predic8 Jan 9, 2026
1881d4f
add: `WSDLMessageValidationTutorialTest` to validate SOAP messages ag…
predic8 Jan 9, 2026
a17d03f
refactor(soap-proxy): enhance WSDL handling, add `getProxyEndpointNam…
predic8 Jan 10, 2026
5de76a5
Merge branch 'master' into soapproxy-wsdlinterceptor-yaml-fix
predic8 Jan 10, 2026
de95698
Merge remote-tracking branch 'origin/soapproxy-wsdlinterceptor-yaml-f…
predic8 Jan 10, 2026
433d77b
refactor(soap-proxy): modularize WSDL logic, add utilities, and impro…
predic8 Jan 10, 2026
c7041c1
refactor(wsdl-interceptor): clean up annotations, remove unused metho…
predic8 Jan 10, 2026
5047eb1
refactor(soap-proxy): remove unused method, fix typos, and improve in…
predic8 Jan 10, 2026
2921d8a
feat(soap-proxy): add manual SOAP proxy tutorial, enhance WSDL handli…
predic8 Jan 11, 2026
f38ebfc
feat: enhance WSDL proxy support and improve documentation
predic8 Jan 11, 2026
1689c03
refactor(tests): simplify method annotations in `ResponseTest`
predic8 Jan 11, 2026
09772ff
refactor(tests): standardize UTF-8 usage and modularize test structures
predic8 Jan 11, 2026
0c3e7ba
refactor(tests): simplify and standardize test behavior
predic8 Jan 11, 2026
9686220
Merge branch 'master' into soapproxy-wsdlinterceptor-yaml-fix
predic8 Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public static void main(String[] args) {
}

private static void start(String[] args) {
log.debug("CLI started with args: {}", Arrays.toString(args));
MembraneCommandLine commandLine = getMembraneCommandLine(args);
if (commandLine.getCommand().isOptionSet("h")) {
commandLine.getCommand().printHelp();
Expand Down Expand Up @@ -249,7 +250,7 @@ private static String getRulesFile(MembraneCommandLine cl) throws IOException {
try (InputStream ignored = rm.resolve(filename)) {
return filename;
} catch (ResourceRetrievalException e) {
System.err.println("Could not open Membrane's configuration file: " + filename + " not found.");
System.err.printf("Could not open Membrane's configuration file: %s not found.%n", filename);
System.exit(1);
}
}
Expand Down Expand Up @@ -339,7 +340,7 @@ private static boolean canResolveConfigurationFile(ResolverMap rm, String try1,
try (InputStream ignored = rm.resolve(try1)) {
return true;
} catch (Exception e) {
log.warn("Could not resolve path to configuration (attempt {}).", attempt, e);
log.warn("Could not resolve path to configuration (path: {} attempt: {}).", try1, attempt);
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ public boolean shouldNotContainBody() {
return header.getContentLength() == 0;
return header.getFirstValue(TRANSFER_ENCODING) == null;
}

return false;
}

Expand All @@ -185,7 +184,7 @@ public int estimateHeapSize() {

@Override
public <T extends Message> T createSnapshot(Runnable bodyUpdatedCallback, BodyCollectingMessageObserver.Strategy strategy, long limit) {
Request result = createMessageSnapshot(new Request(), bodyUpdatedCallback, strategy, limit);
var result = createMessageSnapshot(new Request(), bodyUpdatedCallback, strategy, limit);
result.setUri(getUri());
result.setMethod(getMethod());
return (T) result;
Expand All @@ -195,7 +194,7 @@ public final void writeSTOMP(OutputStream out, boolean retainBody) throws IOExce
out.write(getMethod().getBytes(UTF_8));
out.write(10);
for (HeaderField hf : header.getAllHeaderFields())
out.write((hf.getHeaderName().toString() + ":" + hf.getValue() + "\n").getBytes(UTF_8));
out.write(("%s:%s\n".formatted(hf.getHeaderName().toString(), hf.getValue())).getBytes(UTF_8));
out.write(10);
body.write(new PlainBodyTransferer(out), retainBody);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
package com.predic8.membrane.core.interceptor;

import java.util.*;
import java.util.function.*;

public class InterceptorUtil {

public static <T extends Interceptor> List<T> getInterceptors(List<Interceptor> interceptors, Class<T> clazz) {
public static <T extends Interceptor> List<T> getInterceptors(List<Interceptor> interceptors, Class<T> clazz) {
return interceptors.stream().filter(i -> i.getClass().equals(clazz))
.map(clazz::cast)
.toList();
Expand All @@ -27,5 +28,47 @@ public static <T extends Interceptor> Optional<T> getFirstInterceptorOfType(List
return getInterceptors(interceptors, type).stream().findFirst();
}

/**
* Ensures that an interceptor of the given {@code type} is in the first position.
* <p>
* Behavior:
* - If an interceptor of exactly {@code type} exists, the first occurrence is moved to index 0.
* - If none exists, a new instance is created using {@code supplier}, inserted at index 0, and returned.
* - Returns {@link Optional#empty()} only if {@code supplier} returns {@code null}.
* <p>
* Notes:
* - Matches by exact class equality (same as {@link #getInterceptors(List, Class)}).
* - Modifies the provided list in place.
*/
public static <T extends Interceptor> Optional<T> moveToFirstPosition(
List<Interceptor> interceptors,
Class<T> type,
Supplier<T> supplier
) {
// Find first exact match
for (int i = 0; i < interceptors.size(); i++) {
Interceptor current = interceptors.get(i);
if (current != null && current.getClass().equals(type)) {
@SuppressWarnings("unchecked")
T typed = (T) current;

if (i != 0) {
// Remove and re-add at front
interceptors.remove(i);
interceptors.addFirst(typed);
}
return Optional.of(typed);
}
}

// Not found: create and add
T created = supplier.get();
if (created == null) {
return Optional.empty();
}
interceptors.addFirst(created);
return Optional.of(created);
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,11 @@ public Outcome handleResponse(Exchange exc) {
return CONTINUE;
}

if (!wasGetRequest(exc)) {
if (! exc.getRequest().isGETRequest()) {
log.debug("{} HTTP method wasn't GET: No relocating done!",name);
return CONTINUE;
}
Comment thread
predic8 marked this conversation as resolved.

if (!hasContent(exc)) {
log.debug("{} No Content: No relocating done!",name);
return CONTINUE;
}

if (!exc.getResponse().isXML()) {
log.debug("{} Body contains no XML: No relocating done!",name);
return CONTINUE;
Expand All @@ -74,14 +69,6 @@ public Outcome handleResponse(Exchange exc) {

abstract void rewrite(Exchange exc) throws Exception;

private boolean hasContent(Exchange exc) {
return exc.getResponse().getHeader().getContentType() != null;
}

private boolean wasGetRequest(Exchange exc) {
return Request.METHOD_GET.equals(exc.getRequest().getMethod());
}

protected int getLocationPort(Exchange exc) {
if ("".equals(port)) {
return -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.http.*;
import com.predic8.membrane.core.transport.http.*;
import com.predic8.membrane.core.util.*;
import com.predic8.membrane.core.ws.relocator.*;
import org.jetbrains.annotations.*;
import org.slf4j.*;
Expand All @@ -30,13 +31,14 @@
import static com.predic8.membrane.core.http.Header.*;
import static com.predic8.membrane.core.http.Request.*;
import static com.predic8.membrane.core.interceptor.Interceptor.Flow.Set.*;
import static com.predic8.membrane.core.util.soap.WSDLUtil.*;
import static java.nio.charset.StandardCharsets.*;

/**
* @description <p>The <i>wsdlRewriter</i> rewrites endpoint addresses of services and XML Schema locations in WSDL documents.</p>
* @topic 5. Web Services with SOAP and WSDL
*/
@MCElement(name = "wsdlRewriter", excludeFromFlow = true)
@MCElement(name = "wsdlRewriter")
public class WSDLInterceptor extends RelocatingInterceptor {

private final static Logger log = LoggerFactory.getLogger(WSDLInterceptor.class.getName());
Expand All @@ -49,6 +51,11 @@ public class WSDLInterceptor extends RelocatingInterceptor {
private boolean rewriteEndpoint = true;
private HttpClient hc;

/**
* Path of the service location to rewrite
*/
private String path;

public WSDLInterceptor() {
name = "wsdl rewriting";
setAppliedFlow(RESPONSE_FLOW);
Expand All @@ -58,6 +65,29 @@ public WSDLInterceptor() {
public void init() {
super.init();
hc = router.getHttpClientFactory().createClient(null);

if (path != null)
setPathRewriterOnWSDLInterceptor(path);
}

public void setPathRewriterOnWSDLInterceptor(String keypath) {
if (keypath == null)
return;
setPathRewriter(generatePathRewriter(keypath));
}

Relocator.@NotNull PathRewriter generatePathRewriter(String keypath) {
return path -> {
try {
if (path.contains("://")) {
return new URL(new URL(path), keypath).toString();
}
return rewriteRelativeWsdlPath(path, URLUtil.getNameComponent(router.getConfiguration().getUriFactory(), keypath));
} catch (URISyntaxException | MalformedURLException e) {
log.error("Cannot parse URL {} - {}", path, e);
throw new RuntimeException(e);
}
};
}

@Override
Expand Down Expand Up @@ -200,8 +230,23 @@ public void setHost(String host) {
* @example 4000
*/
@MCAttribute
@Override
public void setPort(String port) {
super.setPort(port);
public void setPort(int port) {
super.setPort(Integer.toString(port));
}
Comment thread
predic8 marked this conversation as resolved.

/**
* When the wsdlRewriter is used in a SOAPProxy, the path is set to the path/uri from the SOAPProxy.
*
* @param path
* @description Path to use for the service location
* @default soapProxy/path/uri
*/
@MCAttribute
public void setPath(String path) {
this.path = path;
}

public String getPath() {
return path;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import static com.predic8.membrane.core.http.MimeType.*;
import static com.predic8.membrane.core.http.Response.*;
import static com.predic8.membrane.core.interceptor.Outcome.*;
import static com.predic8.membrane.core.resolver.ResolverMap.combine;
import static com.predic8.membrane.core.resolver.ResolverMap.*;

/**
* @description <p>
Expand All @@ -47,16 +47,18 @@ public class WSDLPublisherInterceptor extends AbstractInterceptor {

private SOAPProxy soapProxy;

private String wsdl;

public WSDLPublisherInterceptor() {
name = "wsdl publisher";
}

/**
* Note that this class fulfills two purposes:
* <p>
* * During the initial processDocuments() run, the XSDs are enumerated.
* During the initial processDocuments() run, the XSDs are enumerated.
* <p>
* * During later runs (as well as the initial run, but that's result is discarded),
* During later runs (as well as the initial run, but that result is discarded),
* the documents are rewritten.
*/
private final class RelativePathRewriter implements PathRewriter {
Expand Down Expand Up @@ -85,18 +87,31 @@ public String rewrite(String path) {
path = Integer.toString(n);
}
}
path = "./" + URLUtil.getName(router.getConfiguration().getUriFactory(), exc.getDestinations().getFirst()) + "?xsd=" + path;
path = "./" + URLUtil.getNameComponent(router.getConfiguration().getUriFactory(), exc.getDestinations().getFirst()) + "?xsd=" + path;
} catch (Exception e) {
throw new RuntimeException(e);
}
return path;
}
}

@Override
public void init() {
super.init();

webServerInterceptor = new WebServerInterceptor();
webServerInterceptor.init(router);

// inherit wsdl="..." from SoapProxy
if (wsdl != null)
return;
getWSDLFromEmbeddingSOAPProxy();
}

@GuardedBy("paths")
private final HashMap<Integer, String> paths = new HashMap<>();
private final Map<Integer, String> paths = new HashMap<>();
@GuardedBy("paths")
private final HashMap<String, Integer> paths_reverse = new HashMap<>();
private final Map<String, Integer> paths_reverse = new HashMap<>();
@GuardedBy("paths")
private final Queue<String> documents_to_process = new LinkedList<>();

Expand All @@ -109,7 +124,7 @@ private void processDocuments(Exchange exc) {
String doc = documents_to_process.poll();
if (doc == null)
break;
log.debug("WSDLPublisherInterceptor: processing {}", doc);
log.debug("processing: {}", doc);
exc.setResponse(webServerInterceptor.createResponse(router.getResolverMap(), doc));
WSDLInterceptor wi = new WSDLInterceptor();
wi.setRewriteEndpoint(false);
Expand All @@ -122,8 +137,6 @@ private void processDocuments(Exchange exc) {
}
}

private String wsdl;

public String getWsdl() {
return wsdl;
}
Expand All @@ -143,19 +156,6 @@ public void setWsdl(String wsdl) {
}
}

@Override
public void init() {
super.init();

webServerInterceptor = new WebServerInterceptor();
webServerInterceptor.init(router);

// inherit wsdl="..." from SoapProxy
if (wsdl != null)
return;
getWSDLFromEmbeddingSOAPProxy();
}

private void getWSDLFromEmbeddingSOAPProxy() {
if (soapProxy == null) {
throw new ConfigurationException("<wsdlPublisher> can only be used within a <soapProxy> or needs to declare <wsdlPublisher wsdl='...'>");
Expand All @@ -170,7 +170,7 @@ public Outcome handleRequest(final Exchange exc) {
return handleRequestInternal(exc);
} catch (Exception e) {
log.error("", e);
internal(router.getConfiguration().isProduction(),getDisplayName())
internal(router.getConfiguration().isProduction(), getDisplayName())
.detail("Could not return WSDL document!")
.exception(e)
.buildAndSetResponse(exc);
Expand Down Expand Up @@ -218,7 +218,7 @@ private Outcome handleRequestInternal(final Exchange exc) throws Exception {
exc.setResponse(HttpUtil.setHTMLErrorResponse(Response.internalServerError(), "Bad parameter format.", ""));
return ABORT;
} catch (ResourceRetrievalException e) {
exc.setResponse(Response.notFound().build());
exc.setResponse(notFound().build());
return ABORT;
}

Expand All @@ -227,7 +227,7 @@ private Outcome handleRequestInternal(final Exchange exc) throws Exception {

@Override
public String getShortDescription() {
return "Publishes the WSDL at " + wsdl + " under \"?wsdl\" (as well as its dependent schemas under similar URLs).";
return "Publishes the WSDL at %s under \"?wsdl\" (as well as its dependent schemas under similar URLs).".formatted(wsdl);
}

public void setSoapProxy(SOAPProxy soapProxy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ public abstract class AbstractProxy implements Proxy {
protected String name = "";
protected RuleKey key;

protected volatile boolean blockRequest;
protected volatile boolean blockResponse;

protected List<Interceptor> interceptors = new ArrayList<>();

private final RuleStatisticCollector ruleStatisticCollector = new RuleStatisticCollector();
Expand Down
Loading
Loading