From 2e9dcd70293d911e82921c052b6b16f5eb0a4a13 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Thu, 5 Dec 2024 00:31:00 +0200 Subject: [PATCH 01/18] WIP --- .../io/scalecube/services/registry/ServiceRegistryImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 50ba5ae34..990303c69 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -133,7 +133,7 @@ public void registerService(ServiceInfo serviceInfo) { Reflect.isSecured(method), Reflect.executeOnScheduler(serviceMethod, schedulers)); - checkMethodInvokerDoesntExist(methodInfo); + checkMethodInvokerIsNotPresent(methodInfo); ServiceMethodInvoker methodInvoker = new ServiceMethodInvoker( @@ -151,7 +151,7 @@ public void registerService(ServiceInfo serviceInfo) { })); } - private void checkMethodInvokerDoesntExist(MethodInfo methodInfo) { + private void checkMethodInvokerIsNotPresent(MethodInfo methodInfo) { if (methodInvokers.containsKey(methodInfo.qualifier())) { LOGGER.log(Level.ERROR, "MethodInvoker already exists, methodInfo: {0}", methodInfo); throw new IllegalStateException("MethodInvoker already exists"); From d1b3159085374d85fdbb3eeef052c5c6f3fa02e7 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sat, 7 Dec 2024 18:01:44 +0200 Subject: [PATCH 02/18] WIP --- .../registry/ServiceRegistryImpl.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 990303c69..951f95d18 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -16,10 +16,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.jctools.maps.NonBlockingHashMap; import reactor.core.scheduler.Scheduler; @@ -34,9 +32,13 @@ public class ServiceRegistryImpl implements ServiceRegistry { private final Map serviceEndpoints = new NonBlockingHashMap<>(); private final Map> serviceReferencesByQualifier = new NonBlockingHashMap<>(); + private final Map methodInvokerByQualifier = + new NonBlockingHashMap<>(); + private final Map> serviceReferencesByPattern = + new NonBlockingHashMap<>(); + private final Map methodInvokerByPattern = + new NonBlockingHashMap<>(); private final List serviceInfos = new CopyOnWriteArrayList<>(); - private final ConcurrentMap methodInvokers = - new ConcurrentHashMap<>(); public ServiceRegistryImpl(Map schedulers) { this.schedulers = schedulers; @@ -69,12 +71,13 @@ public List lookupService(ServiceMessage request) { @Override public boolean registerService(ServiceEndpoint serviceEndpoint) { - boolean success = serviceEndpoints.putIfAbsent(serviceEndpoint.id(), serviceEndpoint) == null; - if (success) { + boolean putIfAbsent = + serviceEndpoints.putIfAbsent(serviceEndpoint.id(), serviceEndpoint) == null; + if (putIfAbsent) { LOGGER.log(Level.DEBUG, "ServiceEndpoint registered: {0}", serviceEndpoint); - serviceEndpoint.serviceReferences().forEach(this::populateServiceReferences); + serviceEndpoint.serviceReferences().forEach(this::addServiceReference); } - return success; + return putIfAbsent; } @Override @@ -83,13 +86,10 @@ public ServiceEndpoint unregisterService(String endpointId) { if (serviceEndpoint != null) { LOGGER.log(Level.DEBUG, "ServiceEndpoint unregistered: {0}", serviceEndpoint); - List serviceReferencesOfEndpoint = - serviceReferencesByQualifier.values().stream() - .flatMap(Collection::stream) - .filter(sr -> sr.endpointId().equals(endpointId)) - .toList(); - - serviceReferencesOfEndpoint.forEach(this::cleanServiceReferences); + serviceReferencesByQualifier.values().stream() + .flatMap(Collection::stream) + .filter(sr -> sr.endpointId().equals(endpointId)) + .forEach(this::removeServiceReference); } return serviceEndpoint; } @@ -147,12 +147,12 @@ public void registerService(ServiceInfo serviceInfo) { serviceInfo.logger(), serviceInfo.level()); - methodInvokers.put(methodInfo.qualifier(), methodInvoker); + methodInvokerByQualifier.put(methodInfo.qualifier(), methodInvoker); })); } private void checkMethodInvokerIsNotPresent(MethodInfo methodInfo) { - if (methodInvokers.containsKey(methodInfo.qualifier())) { + if (methodInvokerByQualifier.containsKey(methodInfo.qualifier())) { LOGGER.log(Level.ERROR, "MethodInvoker already exists, methodInfo: {0}", methodInfo); throw new IllegalStateException("MethodInvoker already exists"); } @@ -160,7 +160,7 @@ private void checkMethodInvokerIsNotPresent(MethodInfo methodInfo) { @Override public ServiceMethodInvoker getInvoker(String qualifier) { - return methodInvokers.get(Objects.requireNonNull(qualifier, "[getInvoker] qualifier")); + return methodInvokerByQualifier.get(qualifier); } @Override @@ -168,13 +168,13 @@ public List listServices() { return serviceInfos; } - private void populateServiceReferences(ServiceReference sr) { + private void addServiceReference(ServiceReference sr) { serviceReferencesByQualifier .computeIfAbsent(sr.qualifier(), key -> new CopyOnWriteArrayList<>()) .add(sr); } - private void cleanServiceReferences(ServiceReference sr) { + private void removeServiceReference(ServiceReference sr) { serviceReferencesByQualifier.compute( sr.qualifier(), (key, list) -> { @@ -182,7 +182,7 @@ private void cleanServiceReferences(ServiceReference sr) { return null; } list.remove(sr); - return !list.isEmpty() ? list : null; + return list.isEmpty() ? null : list; }); } } From 31c44b6414df6c3cd4e39229fe677a1df8795bf4 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sat, 7 Dec 2024 19:53:06 +0200 Subject: [PATCH 03/18] WIP --- .../services/registry/ServiceRegistryImpl.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 951f95d18..2858c9208 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -74,8 +74,8 @@ public boolean registerService(ServiceEndpoint serviceEndpoint) { boolean putIfAbsent = serviceEndpoints.putIfAbsent(serviceEndpoint.id(), serviceEndpoint) == null; if (putIfAbsent) { - LOGGER.log(Level.DEBUG, "ServiceEndpoint registered: {0}", serviceEndpoint); serviceEndpoint.serviceReferences().forEach(this::addServiceReference); + LOGGER.log(Level.DEBUG, "ServiceEndpoint registered: {0}", serviceEndpoint); } return putIfAbsent; } @@ -84,12 +84,11 @@ public boolean registerService(ServiceEndpoint serviceEndpoint) { public ServiceEndpoint unregisterService(String endpointId) { ServiceEndpoint serviceEndpoint = serviceEndpoints.remove(endpointId); if (serviceEndpoint != null) { - LOGGER.log(Level.DEBUG, "ServiceEndpoint unregistered: {0}", serviceEndpoint); - serviceReferencesByQualifier.values().stream() .flatMap(Collection::stream) .filter(sr -> sr.endpointId().equals(endpointId)) .forEach(this::removeServiceReference); + LOGGER.log(Level.DEBUG, "ServiceEndpoint unregistered: {0}", serviceEndpoint); } return serviceEndpoint; } @@ -169,9 +168,13 @@ public List listServices() { } private void addServiceReference(ServiceReference sr) { - serviceReferencesByQualifier - .computeIfAbsent(sr.qualifier(), key -> new CopyOnWriteArrayList<>()) - .add(sr); + if (sr.hasDynamicQualifier()) { + // ... + } else { + serviceReferencesByQualifier + .computeIfAbsent(sr.qualifier(), key -> new CopyOnWriteArrayList<>()) + .add(sr); + } } private void removeServiceReference(ServiceReference sr) { From d9ce1a3da39df3721adcef9633c41c514907723b Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sat, 7 Dec 2024 21:23:23 +0200 Subject: [PATCH 04/18] Added DynamicQualifier --- .../scalecube/services/ServiceReference.java | 20 ++++++- .../services/api/DynamicQualifier.java | 60 +++++++++++++++++++ .../services/api/DynamicQualifierTest.java | 41 +++++++++++++ 3 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java create mode 100644 services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java diff --git a/services-api/src/main/java/io/scalecube/services/ServiceReference.java b/services-api/src/main/java/io/scalecube/services/ServiceReference.java index 1c47ddcd0..4546a78a5 100644 --- a/services-api/src/main/java/io/scalecube/services/ServiceReference.java +++ b/services-api/src/main/java/io/scalecube/services/ServiceReference.java @@ -1,5 +1,6 @@ package io.scalecube.services; +import io.scalecube.services.api.DynamicQualifier; import io.scalecube.services.api.Qualifier; import java.util.Collections; import java.util.HashMap; @@ -21,6 +22,7 @@ public class ServiceReference { private final String action; private final Address address; private final boolean isSecured; + private final DynamicQualifier dynamicQualifier; /** * Constructor for service reference. @@ -41,6 +43,11 @@ public ServiceReference( this.qualifier = Qualifier.asString(namespace, action); this.address = serviceEndpoint.address(); this.isSecured = serviceMethodDefinition.isSecured(); + if (qualifier.contains(":")) { + dynamicQualifier = new DynamicQualifier(qualifier); + } else { + dynamicQualifier = null; + } } public String qualifier() { @@ -75,6 +82,10 @@ public boolean isSecured() { return isSecured; } + public DynamicQualifier dynamicQualifier() { + return dynamicQualifier; + } + private Map mergeTags( ServiceMethodDefinition serviceMethodDefinition, ServiceRegistration serviceRegistration, @@ -89,12 +100,15 @@ private Map mergeTags( @Override public String toString() { return new StringJoiner(", ", ServiceReference.class.getSimpleName() + "[", "]") - .add("endpointId=" + endpointId) - .add("address=" + address) - .add("qualifier=" + qualifier) + .add("qualifier='" + qualifier + "'") + .add("endpointId='" + endpointId + "'") + .add("namespace='" + namespace + "'") .add("contentTypes=" + contentTypes) .add("tags=" + tags) + .add("action='" + action + "'") + .add("address=" + address) .add("isSecured=" + isSecured) + .add("dynamicQualifier=" + dynamicQualifier) .toString(); } } diff --git a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java new file mode 100644 index 000000000..aeab1af16 --- /dev/null +++ b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java @@ -0,0 +1,60 @@ +package io.scalecube.services.api; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.regex.Pattern; + +public final class DynamicQualifier { + + private final Pattern pattern; + private final List pathVariables = new ArrayList<>(); + + public DynamicQualifier(String qualifier) { + if (!qualifier.contains(":")) { + throw new IllegalArgumentException("Illegal dynamic qualifier: " + qualifier); + } + + final StringBuilder sb = new StringBuilder(); + for (var s : qualifier.split("/")) { + if (s.startsWith(":")) { + final var pathVar = s.substring(1); + sb.append("(?<").append(pathVar).append(">.*?)"); + pathVariables.add(pathVar); + } else { + sb.append(s); + } + sb.append("/"); + } + + sb.setLength(sb.length() - 1); + pattern = Pattern.compile(sb.toString()); + } + + public Map match(String input) { + final var matcher = pattern.matcher(input); + if (!matcher.matches()) { + return null; + } else { + final var map = new LinkedHashMap(); + for (var pathVar : pathVariables) { + final var value = matcher.group(pathVar); + Objects.requireNonNull( + value, "Path variable value must not be null, path variable: " + pathVar); + map.put(pathVar, value); + } + return map; + } + } + + @Override + public String toString() { + return new StringJoiner(", ", DynamicQualifier.class.getSimpleName() + "[", "]") + .add("pattern=" + pattern) + .add("pathVariables=" + pathVariables) + .toString(); + } +} diff --git a/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java b/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java new file mode 100644 index 000000000..35f130f54 --- /dev/null +++ b/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java @@ -0,0 +1,41 @@ +package io.scalecube.services.api; + +import java.util.UUID; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DynamicQualifierTest { + + @Test + void testNoMatches() { + final var qualifier = new DynamicQualifier("v1/foo/:foo/bar/:bar"); + Assertions.assertNull(qualifier.match("v1/foo/bar")); + } + + @Test + void testIllegalArgument() { + Assertions.assertThrows( + IllegalArgumentException.class, () -> new DynamicQualifier("v1/foo/bar")); + } + + @Test + void testMatchSinglePathVariable() { + final var userName = UUID.randomUUID().toString(); + final var qualifier = new DynamicQualifier("v1/foo/bar/:userName"); + final var map = qualifier.match("v1/foo/bar/" + userName); + Assertions.assertNotNull(map); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals(userName, map.get("userName")); + } + + @Test + void testMatchMultiplePathVariables() { + final var qualifier = new DynamicQualifier("v1/foo/:foo/bar/:bar/baz/:baz"); + final var map = qualifier.match("v1/foo/123/bar/456/baz/678"); + Assertions.assertNotNull(map); + Assertions.assertEquals(3, map.size()); + Assertions.assertEquals("123", map.get("foo")); + Assertions.assertEquals("456", map.get("bar")); + Assertions.assertEquals("678", map.get("baz")); + } +} From 73ebc73a2c04b7a20c0041c7c3614096574284a1 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 11:56:05 +0200 Subject: [PATCH 05/18] Added DynamicQualifier --- .../services/api/DynamicQualifier.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java index aeab1af16..98613032b 100644 --- a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java +++ b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java @@ -17,7 +17,6 @@ public DynamicQualifier(String qualifier) { if (!qualifier.contains(":")) { throw new IllegalArgumentException("Illegal dynamic qualifier: " + qualifier); } - final StringBuilder sb = new StringBuilder(); for (var s : qualifier.split("/")) { if (s.startsWith(":")) { @@ -29,25 +28,23 @@ public DynamicQualifier(String qualifier) { } sb.append("/"); } - sb.setLength(sb.length() - 1); pattern = Pattern.compile(sb.toString()); } - public Map match(String input) { + public Map matchQualifier(String input) { final var matcher = pattern.matcher(input); if (!matcher.matches()) { return null; - } else { - final var map = new LinkedHashMap(); - for (var pathVar : pathVariables) { - final var value = matcher.group(pathVar); - Objects.requireNonNull( - value, "Path variable value must not be null, path variable: " + pathVar); - map.put(pathVar, value); - } - return map; } + final var map = new LinkedHashMap(); + for (var pathVar : pathVariables) { + final var value = matcher.group(pathVar); + Objects.requireNonNull( + value, "Path variable value must not be null, path variable: " + pathVar); + map.put(pathVar, value); + } + return map; } @Override From b920231e099267ab2535f7f510e90bc87775fd93 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 12:07:41 +0200 Subject: [PATCH 06/18] Added DynamicQualifier --- .../scalecube/services/ServiceReference.java | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/services-api/src/main/java/io/scalecube/services/ServiceReference.java b/services-api/src/main/java/io/scalecube/services/ServiceReference.java index 4546a78a5..bdcbb05c1 100644 --- a/services-api/src/main/java/io/scalecube/services/ServiceReference.java +++ b/services-api/src/main/java/io/scalecube/services/ServiceReference.java @@ -14,15 +14,15 @@ */ public class ServiceReference { - private final String qualifier; private final String endpointId; private final String namespace; + private final String action; + private final String qualifier; + private final DynamicQualifier dynamicQualifier; private final Set contentTypes; private final Map tags; - private final String action; private final Address address; private final boolean isSecured; - private final DynamicQualifier dynamicQualifier; /** * Constructor for service reference. @@ -37,21 +37,13 @@ public ServiceReference( ServiceEndpoint serviceEndpoint) { this.endpointId = serviceEndpoint.id(); this.namespace = serviceRegistration.namespace(); - this.contentTypes = Collections.unmodifiableSet(serviceEndpoint.contentTypes()); - this.tags = mergeTags(serviceMethodDefinition, serviceRegistration, serviceEndpoint); this.action = serviceMethodDefinition.action(); this.qualifier = Qualifier.asString(namespace, action); + this.dynamicQualifier = qualifier.contains(":") ? new DynamicQualifier(qualifier) : null; + this.contentTypes = Collections.unmodifiableSet(serviceEndpoint.contentTypes()); + this.tags = mergeTags(serviceMethodDefinition, serviceRegistration, serviceEndpoint); this.address = serviceEndpoint.address(); this.isSecured = serviceMethodDefinition.isSecured(); - if (qualifier.contains(":")) { - dynamicQualifier = new DynamicQualifier(qualifier); - } else { - dynamicQualifier = null; - } - } - - public String qualifier() { - return qualifier; } public String endpointId() { @@ -62,6 +54,18 @@ public String namespace() { return namespace; } + public String action() { + return action; + } + + public String qualifier() { + return qualifier; + } + + public DynamicQualifier dynamicQualifier() { + return dynamicQualifier; + } + public Set contentTypes() { return contentTypes; } @@ -70,10 +74,6 @@ public Map tags() { return tags; } - public String action() { - return action; - } - public Address address() { return this.address; } @@ -82,10 +82,6 @@ public boolean isSecured() { return isSecured; } - public DynamicQualifier dynamicQualifier() { - return dynamicQualifier; - } - private Map mergeTags( ServiceMethodDefinition serviceMethodDefinition, ServiceRegistration serviceRegistration, @@ -100,15 +96,15 @@ private Map mergeTags( @Override public String toString() { return new StringJoiner(", ", ServiceReference.class.getSimpleName() + "[", "]") - .add("qualifier='" + qualifier + "'") .add("endpointId='" + endpointId + "'") .add("namespace='" + namespace + "'") + .add("action='" + action + "'") + .add("qualifier='" + qualifier + "'") + .add("dynamicQualifier=" + dynamicQualifier) .add("contentTypes=" + contentTypes) .add("tags=" + tags) - .add("action='" + action + "'") .add("address=" + address) .add("isSecured=" + isSecured) - .add("dynamicQualifier=" + dynamicQualifier) .toString(); } } From 01a394f46bb9289e52fda906d94a479eb52acfb9 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 12:19:47 +0200 Subject: [PATCH 07/18] Added DynamicQualifier --- .../services/api/DynamicQualifier.java | 20 ++++++++++++++++++- .../services/api/DynamicQualifierTest.java | 17 +++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java index 98613032b..451a6a19f 100644 --- a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java +++ b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java @@ -10,6 +10,7 @@ public final class DynamicQualifier { + private final String qualifier; private final Pattern pattern; private final List pathVariables = new ArrayList<>(); @@ -29,7 +30,8 @@ public DynamicQualifier(String qualifier) { sb.append("/"); } sb.setLength(sb.length() - 1); - pattern = Pattern.compile(sb.toString()); + this.pattern = Pattern.compile(sb.toString()); + this.qualifier = qualifier; } public Map matchQualifier(String input) { @@ -47,6 +49,22 @@ public Map matchQualifier(String input) { return map; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return Objects.equals(qualifier, ((DynamicQualifier) o).qualifier); + } + + @Override + public int hashCode() { + return Objects.hashCode(qualifier); + } + @Override public String toString() { return new StringJoiner(", ", DynamicQualifier.class.getSimpleName() + "[", "]") diff --git a/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java b/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java index 35f130f54..47ec9f8d7 100644 --- a/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java +++ b/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java @@ -9,7 +9,7 @@ class DynamicQualifierTest { @Test void testNoMatches() { final var qualifier = new DynamicQualifier("v1/foo/:foo/bar/:bar"); - Assertions.assertNull(qualifier.match("v1/foo/bar")); + Assertions.assertNull(qualifier.matchQualifier("v1/foo/bar")); } @Test @@ -19,19 +19,26 @@ void testIllegalArgument() { } @Test - void testMatchSinglePathVariable() { + void testQualifierEquality() { + final var qualifier1 = new DynamicQualifier("v1/foo/:foo/bar/:bar"); + final var qualifier2 = new DynamicQualifier("v1/foo/:foo/bar/:bar"); + Assertions.assertEquals(qualifier1, qualifier2); + } + + @Test + void testMatchQualifierSinglePathVariable() { final var userName = UUID.randomUUID().toString(); final var qualifier = new DynamicQualifier("v1/foo/bar/:userName"); - final var map = qualifier.match("v1/foo/bar/" + userName); + final var map = qualifier.matchQualifier("v1/foo/bar/" + userName); Assertions.assertNotNull(map); Assertions.assertEquals(1, map.size()); Assertions.assertEquals(userName, map.get("userName")); } @Test - void testMatchMultiplePathVariables() { + void testMatchQualifierMultiplePathVariables() { final var qualifier = new DynamicQualifier("v1/foo/:foo/bar/:bar/baz/:baz"); - final var map = qualifier.match("v1/foo/123/bar/456/baz/678"); + final var map = qualifier.matchQualifier("v1/foo/123/bar/456/baz/678"); Assertions.assertNotNull(map); Assertions.assertEquals(3, map.size()); Assertions.assertEquals("123", map.get("foo")); From 53819aac9d89dd8341a3a1cfdc9952adee12a4cc Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 12:19:58 +0200 Subject: [PATCH 08/18] Added DynamicQualifier --- .../java/io/scalecube/services/methods/MethodInfo.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java b/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java index 51544da75..8ca26cf3f 100644 --- a/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java +++ b/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java @@ -1,6 +1,7 @@ package io.scalecube.services.methods; import io.scalecube.services.CommunicationMode; +import io.scalecube.services.api.DynamicQualifier; import io.scalecube.services.api.Qualifier; import java.lang.reflect.Type; import java.util.StringJoiner; @@ -11,6 +12,7 @@ public final class MethodInfo { private final String serviceName; private final String methodName; private final String qualifier; + private final DynamicQualifier dynamicQualifier; private final Type parameterizedReturnType; private final boolean isReturnTypeServiceMessage; private final CommunicationMode communicationMode; @@ -51,6 +53,7 @@ public MethodInfo( this.serviceName = serviceName; this.methodName = methodName; this.qualifier = Qualifier.asString(serviceName, methodName); + this.dynamicQualifier = qualifier.contains(":") ? new DynamicQualifier(qualifier) : null; this.parameterCount = parameterCount; this.requestType = requestType; this.isRequestTypeServiceMessage = isRequestTypeServiceMessage; @@ -70,6 +73,10 @@ public String qualifier() { return qualifier; } + public DynamicQualifier dynamicQualifier() { + return dynamicQualifier; + } + public Type parameterizedReturnType() { return parameterizedReturnType; } @@ -112,6 +119,7 @@ public String toString() { .add("serviceName='" + serviceName + "'") .add("methodName='" + methodName + "'") .add("qualifier='" + qualifier + "'") + .add("dynamicQualifier=" + dynamicQualifier) .add("parameterizedReturnType=" + parameterizedReturnType) .add("isReturnTypeServiceMessage=" + isReturnTypeServiceMessage) .add("communicationMode=" + communicationMode) From 2ee2c463f5f019a59307634cee15ef9955e6cebf Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 12:36:41 +0200 Subject: [PATCH 09/18] Added DynamicQualifier --- .../io/scalecube/services/api/DynamicQualifier.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java index 451a6a19f..d7179d283 100644 --- a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java +++ b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java @@ -34,6 +34,18 @@ public DynamicQualifier(String qualifier) { this.qualifier = qualifier; } + public String qualifier() { + return qualifier; + } + + public Pattern pattern() { + return pattern; + } + + public List pathVariables() { + return pathVariables; + } + public Map matchQualifier(String input) { final var matcher = pattern.matcher(input); if (!matcher.matches()) { @@ -68,6 +80,7 @@ public int hashCode() { @Override public String toString() { return new StringJoiner(", ", DynamicQualifier.class.getSimpleName() + "[", "]") + .add("qualifier='" + qualifier + "'") .add("pattern=" + pattern) .add("pathVariables=" + pathVariables) .toString(); From 8b2c12bb1d9a3caa85b344bd1be29df04280a735 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 12:50:14 +0200 Subject: [PATCH 10/18] Added DynamicQualifier --- .../io/scalecube/services/api/DynamicQualifier.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java index d7179d283..26584e88c 100644 --- a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java +++ b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java @@ -1,6 +1,7 @@ package io.scalecube.services.api; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -12,13 +13,15 @@ public final class DynamicQualifier { private final String qualifier; private final Pattern pattern; - private final List pathVariables = new ArrayList<>(); + private final List pathVariables; public DynamicQualifier(String qualifier) { if (!qualifier.contains(":")) { throw new IllegalArgumentException("Illegal dynamic qualifier: " + qualifier); } - final StringBuilder sb = new StringBuilder(); + + final var pathVariables = new ArrayList(); + final var sb = new StringBuilder(); for (var s : qualifier.split("/")) { if (s.startsWith(":")) { final var pathVar = s.substring(1); @@ -30,8 +33,10 @@ public DynamicQualifier(String qualifier) { sb.append("/"); } sb.setLength(sb.length() - 1); + this.pattern = Pattern.compile(sb.toString()); this.qualifier = qualifier; + this.pathVariables = Collections.unmodifiableList(pathVariables); } public String qualifier() { @@ -51,6 +56,7 @@ public Map matchQualifier(String input) { if (!matcher.matches()) { return null; } + final var map = new LinkedHashMap(); for (var pathVar : pathVariables) { final var value = matcher.group(pathVar); @@ -58,6 +64,7 @@ public Map matchQualifier(String input) { value, "Path variable value must not be null, path variable: " + pathVar); map.put(pathVar, value); } + return map; } From f827ef25f1cbeda6c4171b06ae63e8c07c55800f Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 14:00:55 +0200 Subject: [PATCH 11/18] WIP --- .../registry/ServiceRegistryImpl.java | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 2858c9208..67f512dd5 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -17,7 +17,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.regex.Pattern; import java.util.stream.Collectors; import org.jctools.maps.NonBlockingHashMap; import reactor.core.scheduler.Scheduler; @@ -34,9 +33,9 @@ public class ServiceRegistryImpl implements ServiceRegistry { new NonBlockingHashMap<>(); private final Map methodInvokerByQualifier = new NonBlockingHashMap<>(); - private final Map> serviceReferencesByPattern = + private final Map> serviceReferencesByPattern = new NonBlockingHashMap<>(); - private final Map methodInvokerByPattern = + private final Map methodInvokerByPattern = new NonBlockingHashMap<>(); private final List serviceInfos = new CopyOnWriteArrayList<>(); @@ -80,6 +79,7 @@ public boolean registerService(ServiceEndpoint serviceEndpoint) { return putIfAbsent; } + // TODO: refactor, clean also serviceReferencesByQualifier @Override public ServiceEndpoint unregisterService(String endpointId) { ServiceEndpoint serviceEndpoint = serviceEndpoints.remove(endpointId); @@ -146,7 +146,11 @@ public void registerService(ServiceInfo serviceInfo) { serviceInfo.logger(), serviceInfo.level()); - methodInvokerByQualifier.put(methodInfo.qualifier(), methodInvoker); + if (methodInfo.dynamicQualifier() == null) { + methodInvokerByQualifier.put(methodInfo.qualifier(), methodInvoker); + } else { + methodInvokerByPattern.put(methodInfo.qualifier(), methodInvoker); + } })); } @@ -155,28 +159,48 @@ private void checkMethodInvokerIsNotPresent(MethodInfo methodInfo) { LOGGER.log(Level.ERROR, "MethodInvoker already exists, methodInfo: {0}", methodInfo); throw new IllegalStateException("MethodInvoker already exists"); } + if (methodInfo.dynamicQualifier() != null) { + if (methodInvokerByPattern.containsKey(methodInfo.qualifier())) { + LOGGER.log(Level.ERROR, "MethodInvoker already exists, methodInfo: {0}", methodInfo); + throw new IllegalStateException("MethodInvoker already exists"); + } + } } @Override public ServiceMethodInvoker getInvoker(String qualifier) { - return methodInvokerByQualifier.get(qualifier); + final var methodInvoker = methodInvokerByQualifier.get(qualifier); + if (methodInvoker != null) { + return methodInvoker; + } + for (var entry : methodInvokerByPattern.entrySet()) { + final var invoker = entry.getValue(); + final var dynamicQualifier = invoker.methodInfo().dynamicQualifier(); + if (dynamicQualifier.matchQualifier(qualifier) != null) { + return invoker; + } + } + return null; } @Override public List listServices() { - return serviceInfos; + return new ArrayList<>(serviceInfos); } private void addServiceReference(ServiceReference sr) { - if (sr.hasDynamicQualifier()) { - // ... - } else { + if (sr.dynamicQualifier() == null) { serviceReferencesByQualifier .computeIfAbsent(sr.qualifier(), key -> new CopyOnWriteArrayList<>()) .add(sr); + } else { + serviceReferencesByPattern + .computeIfAbsent(sr.qualifier(), key -> new CopyOnWriteArrayList<>()) + .add(sr); } } + // TODO: refactor, clean also serviceReferencesByQualifier private void removeServiceReference(ServiceReference sr) { serviceReferencesByQualifier.compute( sr.qualifier(), From 54732400b2852137213eaa84eebd3187d78e561d Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 14:54:58 +0200 Subject: [PATCH 12/18] WIP --- .../registry/ServiceRegistryImpl.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 67f512dd5..591ec2129 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -13,11 +13,9 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; import org.jctools.maps.NonBlockingHashMap; import reactor.core.scheduler.Scheduler; @@ -51,21 +49,20 @@ public List listServiceEndpoints() { @Override public List listServiceReferences() { - return serviceReferencesByQualifier.values().stream() - .flatMap(Collection::stream) - .collect(Collectors.toList()); + return serviceReferencesByQualifier.values().stream().flatMap(Collection::stream).toList(); } @Override public List lookupService(ServiceMessage request) { - List list = serviceReferencesByQualifier.get(request.qualifier()); - if (list == null || list.isEmpty()) { - return Collections.emptyList(); + final var contentType = request.dataFormatOrDefault(); + final var list = serviceReferencesByQualifier.get(request.qualifier()); + if (list != null) { + return list.stream().filter(sr -> sr.contentTypes().contains(contentType)).toList(); + } + + for (var entry : serviceReferencesByPattern.entrySet()) { + final var serviceReferences = entry.getValue(); } - String contentType = request.dataFormatOrDefault(); - return list.stream() - .filter(sr -> sr.contentTypes().contains(contentType)) - .collect(Collectors.toList()); } @Override From 5a772ea52a7b2659afcc77bb9a48f0c3c3beb473 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 15:09:54 +0200 Subject: [PATCH 13/18] WIP --- .../registry/ServiceRegistryImpl.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 591ec2129..05f03666a 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -4,6 +4,7 @@ import io.scalecube.services.ServiceEndpoint; import io.scalecube.services.ServiceInfo; import io.scalecube.services.ServiceReference; +import io.scalecube.services.api.DynamicQualifier; import io.scalecube.services.api.ServiceMessage; import io.scalecube.services.methods.MethodInfo; import io.scalecube.services.methods.ServiceMethodInvoker; @@ -13,6 +14,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; @@ -31,9 +33,9 @@ public class ServiceRegistryImpl implements ServiceRegistry { new NonBlockingHashMap<>(); private final Map methodInvokerByQualifier = new NonBlockingHashMap<>(); - private final Map> serviceReferencesByPattern = + private final Map> serviceReferencesByPattern = new NonBlockingHashMap<>(); - private final Map methodInvokerByPattern = + private final Map methodInvokerByPattern = new NonBlockingHashMap<>(); private final List serviceInfos = new CopyOnWriteArrayList<>(); @@ -55,14 +57,21 @@ public List listServiceReferences() { @Override public List lookupService(ServiceMessage request) { final var contentType = request.dataFormatOrDefault(); - final var list = serviceReferencesByQualifier.get(request.qualifier()); + final var qualifier = request.qualifier(); + + final var list = serviceReferencesByQualifier.get(qualifier); if (list != null) { return list.stream().filter(sr -> sr.contentTypes().contains(contentType)).toList(); } for (var entry : serviceReferencesByPattern.entrySet()) { - final var serviceReferences = entry.getValue(); + final var dynamicQualifier = entry.getKey(); + if (dynamicQualifier.matchQualifier(qualifier) != null) { + return entry.getValue(); + } } + + return Collections.emptyList(); } @Override @@ -146,7 +155,8 @@ public void registerService(ServiceInfo serviceInfo) { if (methodInfo.dynamicQualifier() == null) { methodInvokerByQualifier.put(methodInfo.qualifier(), methodInvoker); } else { - methodInvokerByPattern.put(methodInfo.qualifier(), methodInvoker); + methodInvokerByPattern.put( + methodInfo.dynamicQualifier(), methodInvoker); } })); } @@ -157,7 +167,7 @@ private void checkMethodInvokerIsNotPresent(MethodInfo methodInfo) { throw new IllegalStateException("MethodInvoker already exists"); } if (methodInfo.dynamicQualifier() != null) { - if (methodInvokerByPattern.containsKey(methodInfo.qualifier())) { + if (methodInvokerByPattern.containsKey(methodInfo.dynamicQualifier())) { LOGGER.log(Level.ERROR, "MethodInvoker already exists, methodInfo: {0}", methodInfo); throw new IllegalStateException("MethodInvoker already exists"); } @@ -192,7 +202,7 @@ private void addServiceReference(ServiceReference sr) { .add(sr); } else { serviceReferencesByPattern - .computeIfAbsent(sr.qualifier(), key -> new CopyOnWriteArrayList<>()) + .computeIfAbsent(sr.dynamicQualifier(), key -> new CopyOnWriteArrayList<>()) .add(sr); } } From 71991bde9d383960128fb5f029e1adcfb487c630 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 15:16:43 +0200 Subject: [PATCH 14/18] WIP --- .../services/registry/ServiceRegistryImpl.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 05f03666a..b4885a95b 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -59,11 +59,15 @@ public List lookupService(ServiceMessage request) { final var contentType = request.dataFormatOrDefault(); final var qualifier = request.qualifier(); + // Match by exact-match + final var list = serviceReferencesByQualifier.get(qualifier); if (list != null) { return list.stream().filter(sr -> sr.contentTypes().contains(contentType)).toList(); } + // Match by dynamic-qualifier + for (var entry : serviceReferencesByPattern.entrySet()) { final var dynamicQualifier = entry.getKey(); if (dynamicQualifier.matchQualifier(qualifier) != null) { @@ -176,10 +180,15 @@ private void checkMethodInvokerIsNotPresent(MethodInfo methodInfo) { @Override public ServiceMethodInvoker getInvoker(String qualifier) { + // Match by exact-match + final var methodInvoker = methodInvokerByQualifier.get(qualifier); if (methodInvoker != null) { return methodInvoker; } + + // Match by dynamic-qualifier + for (var entry : methodInvokerByPattern.entrySet()) { final var invoker = entry.getValue(); final var dynamicQualifier = invoker.methodInfo().dynamicQualifier(); @@ -187,6 +196,7 @@ public ServiceMethodInvoker getInvoker(String qualifier) { return invoker; } } + return null; } From fd70a3c371d142bc6376a58d8d552bb2a33d1ac2 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 19:50:30 +0200 Subject: [PATCH 15/18] WIP --- .../registry/ServiceRegistryImpl.java | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index b4885a95b..3b2038438 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -89,15 +89,31 @@ public boolean registerService(ServiceEndpoint serviceEndpoint) { return putIfAbsent; } - // TODO: refactor, clean also serviceReferencesByQualifier @Override public ServiceEndpoint unregisterService(String endpointId) { ServiceEndpoint serviceEndpoint = serviceEndpoints.remove(endpointId); if (serviceEndpoint != null) { + // Clean exact-match service references + serviceReferencesByQualifier.values().stream() .flatMap(Collection::stream) .filter(sr -> sr.endpointId().equals(endpointId)) - .forEach(this::removeServiceReference); + .forEach( + value -> + serviceReferencesByQualifier.compute( + value.qualifier(), (key, list) -> removeServiceReference(value, list))); + + // Clean dynamic-qualifier service references + + serviceReferencesByPattern.values().stream() + .flatMap(Collection::stream) + .filter(sr -> sr.endpointId().equals(endpointId)) + .forEach( + value -> + serviceReferencesByPattern.compute( + value.dynamicQualifier(), + (key, list) -> removeServiceReference(value, list))); + LOGGER.log(Level.DEBUG, "ServiceEndpoint unregistered: {0}", serviceEndpoint); } return serviceEndpoint; @@ -217,16 +233,12 @@ private void addServiceReference(ServiceReference sr) { } } - // TODO: refactor, clean also serviceReferencesByQualifier - private void removeServiceReference(ServiceReference sr) { - serviceReferencesByQualifier.compute( - sr.qualifier(), - (key, list) -> { - if (list == null || list.isEmpty()) { - return null; - } - list.remove(sr); - return list.isEmpty() ? null : list; - }); + private static List removeServiceReference( + ServiceReference value, List list) { + if (list == null || list.isEmpty()) { + return null; + } + list.remove(value); + return list.isEmpty() ? null : list; } } From da6dc8fce91286fdb8f5aadf5e8dbd36605499b0 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 23:02:50 +0200 Subject: [PATCH 16/18] WIP --- .../registry/ServiceRegistryImpl.java | 6 +- .../registry/ServiceRegistryImplTest.java | 186 ++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 services/src/test/java/io/scalecube/services/registry/ServiceRegistryImplTest.java diff --git a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java index 3b2038438..af0da8950 100644 --- a/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java +++ b/services/src/main/java/io/scalecube/services/registry/ServiceRegistryImpl.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Stream; import org.jctools.maps.NonBlockingHashMap; import reactor.core.scheduler.Scheduler; @@ -51,7 +52,10 @@ public List listServiceEndpoints() { @Override public List listServiceReferences() { - return serviceReferencesByQualifier.values().stream().flatMap(Collection::stream).toList(); + return Stream.concat( + serviceReferencesByQualifier.values().stream().flatMap(Collection::stream), + serviceReferencesByPattern.values().stream().flatMap(Collection::stream)) + .toList(); } @Override diff --git a/services/src/test/java/io/scalecube/services/registry/ServiceRegistryImplTest.java b/services/src/test/java/io/scalecube/services/registry/ServiceRegistryImplTest.java new file mode 100644 index 000000000..1f76e1089 --- /dev/null +++ b/services/src/test/java/io/scalecube/services/registry/ServiceRegistryImplTest.java @@ -0,0 +1,186 @@ +package io.scalecube.services.registry; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import io.scalecube.services.Address; +import io.scalecube.services.ServiceEndpoint; +import io.scalecube.services.ServiceInfo; +import io.scalecube.services.ServiceMethodDefinition; +import io.scalecube.services.ServiceRegistration; +import io.scalecube.services.annotations.Service; +import io.scalecube.services.annotations.ServiceMethod; +import io.scalecube.services.api.ServiceMessage; +import io.scalecube.services.exceptions.ServiceProviderErrorMapper; +import io.scalecube.services.transport.api.ServiceMessageDataDecoder; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +class ServiceRegistryImplTest { + + private final ServiceRegistryImpl serviceRegistry = new ServiceRegistryImpl(null); + private final ServiceProviderErrorMapper errorMapper = mock(ServiceProviderErrorMapper.class); + private final ServiceMessageDataDecoder dataDecoder = mock(ServiceMessageDataDecoder.class); + + @Test + void testRegisterService() { + serviceRegistry.registerService( + ServiceInfo.fromServiceInstance(new HelloOneImpl()) + .errorMapper(errorMapper) + .dataDecoder(dataDecoder) + .build()); + serviceRegistry.registerService( + ServiceInfo.fromServiceInstance(new HelloTwoImpl()) + .errorMapper(errorMapper) + .dataDecoder(dataDecoder) + .build()); + assertEquals(2, serviceRegistry.listServices().size()); + } + + @Test + void testRegisterServiceRepeatedlyNotAllowed() { + final var helloOne = + ServiceInfo.fromServiceInstance(new HelloOneImpl()) + .errorMapper(errorMapper) + .dataDecoder(dataDecoder) + .build(); + final var helloTwo = + ServiceInfo.fromServiceInstance(new HelloTwoImpl()) + .errorMapper(errorMapper) + .dataDecoder(dataDecoder) + .build(); + serviceRegistry.registerService(helloOne); + serviceRegistry.registerService(helloTwo); + assertEquals(2, serviceRegistry.listServices().size()); + assertThrows(IllegalStateException.class, () -> serviceRegistry.registerService(helloOne)); + assertThrows(IllegalStateException.class, () -> serviceRegistry.registerService(helloTwo)); + } + + @Test + void testRegisterThenUnregisterServiceEndpoint() { + final var n = 10; + for (int i = 0; i < n; i++) { + serviceRegistry.registerService( + ServiceEndpoint.builder() + .id("endpoint" + i) + .address(Address.create("endpoint" + i, 4848)) + .contentTypes(Set.of("json")) + .serviceRegistrations( + List.of( + new ServiceRegistration( + "greeting", + new HashMap<>(), + List.of( + new ServiceMethodDefinition("hello"), + new ServiceMethodDefinition("hello/:pathVar"))))) + .build()); + } + + assertEquals(n, serviceRegistry.listServiceEndpoints().size()); + assertEquals(n << 1, serviceRegistry.listServiceReferences().size()); + + for (int i = 0; i < n; i++) { + assertNotNull(serviceRegistry.unregisterService("endpoint" + i)); + } + + assertEquals(0, serviceRegistry.listServiceEndpoints().size()); + assertEquals(0, serviceRegistry.listServiceReferences().size()); + } + + @Test + void testGetInvoker() { + serviceRegistry.registerService( + ServiceInfo.fromServiceInstance(new HelloOneImpl()) + .errorMapper(errorMapper) + .dataDecoder(dataDecoder) + .build()); + serviceRegistry.registerService( + ServiceInfo.fromServiceInstance(new HelloTwoImpl()) + .errorMapper(errorMapper) + .dataDecoder(dataDecoder) + .build()); + assertNotNull(serviceRegistry.getInvoker("greeting/hello")); + assertNotNull(serviceRegistry.getInvoker("greeting/hello/12345")); + assertNotNull(serviceRegistry.getInvoker("greeting/hello/67890")); + assertNull(serviceRegistry.getInvoker("greeting/hola/that/not/exist")); + } + + @Test + void testLookupService() { + final var n = 10; + for (int i = 0; i < n; i++) { + serviceRegistry.registerService( + ServiceEndpoint.builder() + .id("endpoint" + i) + .address(Address.create("endpoint" + i, 4848)) + .contentTypes(Set.of("application/json")) + .serviceRegistrations( + List.of( + new ServiceRegistration( + "greeting", + new HashMap<>(), + List.of( + new ServiceMethodDefinition("hello"), + new ServiceMethodDefinition("hello/:pathVar"))))) + .build()); + } + assertEquals( + n, + serviceRegistry + .lookupService(ServiceMessage.builder().qualifier("greeting/hello").build()) + .size()); + assertEquals( + n, + serviceRegistry + .lookupService(ServiceMessage.builder().qualifier("greeting/hello/12345").build()) + .size()); + assertEquals( + n, + serviceRegistry + .lookupService(ServiceMessage.builder().qualifier("greeting/hello/67890").build()) + .size()); + assertEquals( + 0, + serviceRegistry + .lookupService( + ServiceMessage.builder().qualifier("greeting/hola/that/not/exist").build()) + .size()); + } + + @Service(HelloOne.NAMESPACE) + interface HelloOne { + + String NAMESPACE = "greeting"; + + @ServiceMethod + Mono hello(); + } + + @Service(HelloTwo.NAMESPACE) + interface HelloTwo { + + String NAMESPACE = "greeting"; + + @ServiceMethod("hello/:pathVar") + Mono helloPathVar(); + } + + static class HelloOneImpl implements HelloOne { + + @Override + public Mono hello() { + return Mono.just("" + System.currentTimeMillis()); + } + } + + static class HelloTwoImpl implements HelloTwo { + + @Override + public Mono helloPathVar() { + return Mono.just("" + System.currentTimeMillis()); + } + } +} From 9deba315c6aa597b5d959dffb1cb7880e370dee8 Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 23:50:39 +0200 Subject: [PATCH 17/18] WIP --- .../services/api/DynamicQualifier.java | 23 +++++++++- .../services/api/DynamicQualifierTest.java | 44 ++++++++++++------- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java index 26584e88c..75f4870c6 100644 --- a/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java +++ b/services-api/src/main/java/io/scalecube/services/api/DynamicQualifier.java @@ -14,6 +14,7 @@ public final class DynamicQualifier { private final String qualifier; private final Pattern pattern; private final List pathVariables; + private final int size; public DynamicQualifier(String qualifier) { if (!qualifier.contains(":")) { @@ -34,9 +35,10 @@ public DynamicQualifier(String qualifier) { } sb.setLength(sb.length() - 1); - this.pattern = Pattern.compile(sb.toString()); this.qualifier = qualifier; + this.pattern = Pattern.compile(sb.toString()); this.pathVariables = Collections.unmodifiableList(pathVariables); + this.size = sizeOf(qualifier); } public String qualifier() { @@ -51,7 +53,15 @@ public List pathVariables() { return pathVariables; } + public int size() { + return size; + } + public Map matchQualifier(String input) { + if (size != sizeOf(input)) { + return null; + } + final var matcher = pattern.matcher(input); if (!matcher.matches()) { return null; @@ -68,6 +78,16 @@ public Map matchQualifier(String input) { return map; } + private static int sizeOf(String value) { + int count = 0; + for (int i = 0, length = value.length(); i < length; i++) { + if (value.charAt(i) == '/') { + count++; + } + } + return count; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -90,6 +110,7 @@ public String toString() { .add("qualifier='" + qualifier + "'") .add("pattern=" + pattern) .add("pathVariables=" + pathVariables) + .add("size=" + size) .toString(); } } diff --git a/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java b/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java index 47ec9f8d7..f06f410be 100644 --- a/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java +++ b/services-api/src/test/java/io/scalecube/services/api/DynamicQualifierTest.java @@ -1,48 +1,58 @@ package io.scalecube.services.api; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + import java.util.UUID; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class DynamicQualifierTest { + @Test + void testIllegalArgument() { + assertThrows(IllegalArgumentException.class, () -> new DynamicQualifier("v1/foo/bar")); + } + @Test void testNoMatches() { final var qualifier = new DynamicQualifier("v1/foo/:foo/bar/:bar"); - Assertions.assertNull(qualifier.matchQualifier("v1/foo/bar")); + assertNull(qualifier.matchQualifier("v1/foo/bar")); } @Test - void testIllegalArgument() { - Assertions.assertThrows( - IllegalArgumentException.class, () -> new DynamicQualifier("v1/foo/bar")); + void testStrictMatching() { + final var qualifier = new DynamicQualifier("v1/foo/:foo"); + assertNotNull(qualifier.matchQualifier("v1/foo/123")); + assertNull(qualifier.matchQualifier("v1/foo/123/bar/456/baz/678")); } @Test - void testQualifierEquality() { + void testEquality() { final var qualifier1 = new DynamicQualifier("v1/foo/:foo/bar/:bar"); final var qualifier2 = new DynamicQualifier("v1/foo/:foo/bar/:bar"); - Assertions.assertEquals(qualifier1, qualifier2); + assertEquals(qualifier1, qualifier2); } @Test - void testMatchQualifierSinglePathVariable() { + void testMatchSinglePathVariable() { final var userName = UUID.randomUUID().toString(); final var qualifier = new DynamicQualifier("v1/foo/bar/:userName"); final var map = qualifier.matchQualifier("v1/foo/bar/" + userName); - Assertions.assertNotNull(map); - Assertions.assertEquals(1, map.size()); - Assertions.assertEquals(userName, map.get("userName")); + assertNotNull(map); + assertEquals(1, map.size()); + assertEquals(userName, map.get("userName")); } @Test - void testMatchQualifierMultiplePathVariables() { + void testMatchMultiplePathVariables() { final var qualifier = new DynamicQualifier("v1/foo/:foo/bar/:bar/baz/:baz"); final var map = qualifier.matchQualifier("v1/foo/123/bar/456/baz/678"); - Assertions.assertNotNull(map); - Assertions.assertEquals(3, map.size()); - Assertions.assertEquals("123", map.get("foo")); - Assertions.assertEquals("456", map.get("bar")); - Assertions.assertEquals("678", map.get("baz")); + assertNotNull(map); + assertEquals(3, map.size()); + assertEquals("123", map.get("foo")); + assertEquals("456", map.get("bar")); + assertEquals("678", map.get("baz")); } } From 785967575eb372e66f0268d2e251b9d2094ac95a Mon Sep 17 00:00:00 2001 From: Artem Vysochyn Date: Sun, 8 Dec 2024 23:52:58 +0200 Subject: [PATCH 18/18] WIP --- .../scalecube/services/registry/ServiceRegistryImplTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/src/test/java/io/scalecube/services/registry/ServiceRegistryImplTest.java b/services/src/test/java/io/scalecube/services/registry/ServiceRegistryImplTest.java index 1f76e1089..447ef8d63 100644 --- a/services/src/test/java/io/scalecube/services/registry/ServiceRegistryImplTest.java +++ b/services/src/test/java/io/scalecube/services/registry/ServiceRegistryImplTest.java @@ -1,5 +1,6 @@ package io.scalecube.services.registry; +import static io.scalecube.services.transport.jackson.JacksonCodec.CONTENT_TYPE; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; @@ -67,7 +68,7 @@ void testRegisterThenUnregisterServiceEndpoint() { ServiceEndpoint.builder() .id("endpoint" + i) .address(Address.create("endpoint" + i, 4848)) - .contentTypes(Set.of("json")) + .contentTypes(Set.of(CONTENT_TYPE)) .serviceRegistrations( List.of( new ServiceRegistration( @@ -116,7 +117,7 @@ void testLookupService() { ServiceEndpoint.builder() .id("endpoint" + i) .address(Address.create("endpoint" + i, 4848)) - .contentTypes(Set.of("application/json")) + .contentTypes(Set.of(CONTENT_TYPE)) .serviceRegistrations( List.of( new ServiceRegistration(