Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic qualifiers #871

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,12 +14,13 @@
*/
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<String> contentTypes;
private final Map<String, String> tags;
private final String action;
private final Address address;
private final boolean isSecured;

Expand All @@ -35,18 +37,15 @@ 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();
}

public String qualifier() {
return qualifier;
}

public String endpointId() {
return endpointId;
}
Expand All @@ -55,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<String> contentTypes() {
return contentTypes;
}
Expand All @@ -63,10 +74,6 @@ public Map<String, String> tags() {
return tags;
}

public String action() {
return action;
}

public Address address() {
return this.address;
}
Expand All @@ -89,11 +96,14 @@ private Map<String, String> mergeTags(
@Override
public String toString() {
return new StringJoiner(", ", ServiceReference.class.getSimpleName() + "[", "]")
.add("endpointId=" + endpointId)
.add("address=" + address)
.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("address=" + address)
.add("isSecured=" + isSecured)
.toString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.regex.Pattern;

public final class DynamicQualifier {

private final String qualifier;
private final Pattern pattern;
private final List<String> pathVariables;
private final int size;

public DynamicQualifier(String qualifier) {
if (!qualifier.contains(":")) {
throw new IllegalArgumentException("Illegal dynamic qualifier: " + qualifier);
}

final var pathVariables = new ArrayList<String>();
final var 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);

this.qualifier = qualifier;
this.pattern = Pattern.compile(sb.toString());
this.pathVariables = Collections.unmodifiableList(pathVariables);
this.size = sizeOf(qualifier);
}

public String qualifier() {
return qualifier;
}

public Pattern pattern() {
return pattern;
}

public List<String> pathVariables() {
return pathVariables;
}

public int size() {
return size;
}

public Map<String, String> matchQualifier(String input) {
if (size != sizeOf(input)) {
return null;
}

final var matcher = pattern.matcher(input);
if (!matcher.matches()) {
return null;
}

final var map = new LinkedHashMap<String, String>();
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;
}

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) {
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() + "[", "]")
.add("qualifier='" + qualifier + "'")
.add("pattern=" + pattern)
.add("pathVariables=" + pathVariables)
.add("size=" + size)
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -70,6 +73,10 @@ public String qualifier() {
return qualifier;
}

public DynamicQualifier dynamicQualifier() {
return dynamicQualifier;
}

public Type parameterizedReturnType() {
return parameterizedReturnType;
}
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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.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");
assertNull(qualifier.matchQualifier("v1/foo/bar"));
}

@Test
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 testEquality() {
final var qualifier1 = new DynamicQualifier("v1/foo/:foo/bar/:bar");
final var qualifier2 = new DynamicQualifier("v1/foo/:foo/bar/:bar");
assertEquals(qualifier1, qualifier2);
}

@Test
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);
assertNotNull(map);
assertEquals(1, map.size());
assertEquals(userName, map.get("userName"));
}

@Test
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");
assertNotNull(map);
assertEquals(3, map.size());
assertEquals("123", map.get("foo"));
assertEquals("456", map.get("bar"));
assertEquals("678", map.get("baz"));
}
}
Loading
Loading