diff --git a/client/src/main/java/io/opencmw/client/OpenCmwDataSource.java b/client/src/main/java/io/opencmw/client/OpenCmwDataSource.java index 592a4358..5558a811 100644 --- a/client/src/main/java/io/opencmw/client/OpenCmwDataSource.java +++ b/client/src/main/java/io/opencmw/client/OpenCmwDataSource.java @@ -96,7 +96,10 @@ public List getRegisteredDnsResolver() { private final ZMonitor socketMonitor; private final Map subscriptions = new HashMap<>(); // NOPMD: not accessed concurrently private final URI serverUri; - private final BiPredicate subscriptionMatcher = new SubscriptionMatcher(); // + // + // Context matching differentiates subscriptions to the same path by the context keys whose Filter + // implementations are registered in FilterRegistry; keys without one are ignored. + private final BiPredicate subscriptionMatcher = SubscriptionMatcher.pathAndContext(); private final long heartbeatInterval; private Future dnsWorkerResult; private long nextReconnectAttemptTimeStamp; diff --git a/core/src/main/java/io/opencmw/filter/SubscriptionMatcher.java b/core/src/main/java/io/opencmw/filter/SubscriptionMatcher.java index 742d1123..7ecdb55d 100644 --- a/core/src/main/java/io/opencmw/filter/SubscriptionMatcher.java +++ b/core/src/main/java/io/opencmw/filter/SubscriptionMatcher.java @@ -28,6 +28,21 @@ public SubscriptionMatcher(final Class... filterConfig) { FilterRegistry.registerNewFilter(filterConfig); } + private SubscriptionMatcher(final boolean pathOnly) { + isPathOnly = pathOnly; + } + + /** + * Matcher that compares path and context, discriminating on whichever filters are registered in + * the global {@link FilterRegistry} when a match is evaluated. Use this when the filters are + * registered elsewhere, rather than naming them here. + * + * @return context-aware matcher that registers no filters of its own + */ + public static SubscriptionMatcher pathAndContext() { + return new SubscriptionMatcher(false); + } + @Override public boolean test(final @NotNull URI notified, final @NotNull URI subscriber) { return isPathOnly ? testPathOnly(notified, subscriber) : testPathAndContext(notified, subscriber); diff --git a/core/src/test/java/io/opencmw/filter/SubscriptionMatcherTest.java b/core/src/test/java/io/opencmw/filter/SubscriptionMatcherTest.java index d574b43f..dce3d677 100644 --- a/core/src/test/java/io/opencmw/filter/SubscriptionMatcherTest.java +++ b/core/src/test/java/io/opencmw/filter/SubscriptionMatcherTest.java @@ -75,6 +75,25 @@ void testMatcherTimingContentContext() { test(matcher, false, "property?ctx=FAIR.SELECTOR.ALL", "property?ctx=FAIR.SELECTOR.C=2&contentType=text/html"); } + @Test + void testMatcherPathAndContextFactory() { + // filters registered elsewhere, none named at construction + FilterRegistry.registerNewFilter(TestFilter1.class); + + final BiPredicate matcher = SubscriptionMatcher.pathAndContext(); + + // subscriptions to the same path differing only by a registered context key must not cross-match + test(matcher, false, "property/A?testKey1=41", "property/A?testKey1=42"); + test(matcher, false, "property/A?testKey1=42", "property/A?testKey1=41"); + test(matcher, true, "property/A?testKey1=42", "property/A?testKey1=42"); + + // unregistered context keys keep their existing 'ignore' semantics on either side + test(matcher, true, "property/A?testKey1=42&unknownKey=x", "property/A?testKey1=42"); + test(matcher, true, "property/A?testKey1=42", "property/A?testKey1=42&unknownKey=x"); + test(matcher, true, "property/A?testKey1=42", "property/A*"); + test(matcher, false, "property/A?testKey1=42", "property/B?testKey1=42"); + } + private static void test(final BiPredicate matcher, final boolean expected, final String notify, final String subscription) throws AssertionFailedError { final boolean actual = matcher.test(URI.create(notify), URI.create(subscription)); if (actual != expected) {