Skip to content

Commit b0cc785

Browse files
authored
Dynamic qualifiers (#871)
1 parent d704ba4 commit b0cc785

File tree

23 files changed

+837
-167
lines changed

23 files changed

+837
-167
lines changed

services-api/src/main/java/io/scalecube/services/ServiceReference.java

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.scalecube.services;
22

3+
import io.scalecube.services.api.DynamicQualifier;
34
import io.scalecube.services.api.Qualifier;
45
import java.util.Collections;
56
import java.util.HashMap;
@@ -13,12 +14,13 @@
1314
*/
1415
public class ServiceReference {
1516

16-
private final String qualifier;
1717
private final String endpointId;
1818
private final String namespace;
19+
private final String action;
20+
private final String qualifier;
21+
private final DynamicQualifier dynamicQualifier;
1922
private final Set<String> contentTypes;
2023
private final Map<String, String> tags;
21-
private final String action;
2224
private final Address address;
2325
private final boolean isSecured;
2426

@@ -35,18 +37,15 @@ public ServiceReference(
3537
ServiceEndpoint serviceEndpoint) {
3638
this.endpointId = serviceEndpoint.id();
3739
this.namespace = serviceRegistration.namespace();
38-
this.contentTypes = Collections.unmodifiableSet(serviceEndpoint.contentTypes());
39-
this.tags = mergeTags(serviceMethodDefinition, serviceRegistration, serviceEndpoint);
4040
this.action = serviceMethodDefinition.action();
4141
this.qualifier = Qualifier.asString(namespace, action);
42+
this.dynamicQualifier = qualifier.contains(":") ? new DynamicQualifier(qualifier) : null;
43+
this.contentTypes = Collections.unmodifiableSet(serviceEndpoint.contentTypes());
44+
this.tags = mergeTags(serviceMethodDefinition, serviceRegistration, serviceEndpoint);
4245
this.address = serviceEndpoint.address();
4346
this.isSecured = serviceMethodDefinition.isSecured();
4447
}
4548

46-
public String qualifier() {
47-
return qualifier;
48-
}
49-
5049
public String endpointId() {
5150
return endpointId;
5251
}
@@ -55,6 +54,18 @@ public String namespace() {
5554
return namespace;
5655
}
5756

57+
public String action() {
58+
return action;
59+
}
60+
61+
public String qualifier() {
62+
return qualifier;
63+
}
64+
65+
public DynamicQualifier dynamicQualifier() {
66+
return dynamicQualifier;
67+
}
68+
5869
public Set<String> contentTypes() {
5970
return contentTypes;
6071
}
@@ -63,10 +74,6 @@ public Map<String, String> tags() {
6374
return tags;
6475
}
6576

66-
public String action() {
67-
return action;
68-
}
69-
7077
public Address address() {
7178
return this.address;
7279
}
@@ -89,11 +96,14 @@ private Map<String, String> mergeTags(
8996
@Override
9097
public String toString() {
9198
return new StringJoiner(", ", ServiceReference.class.getSimpleName() + "[", "]")
92-
.add("endpointId=" + endpointId)
93-
.add("address=" + address)
94-
.add("qualifier=" + qualifier)
99+
.add("endpointId='" + endpointId + "'")
100+
.add("namespace='" + namespace + "'")
101+
.add("action='" + action + "'")
102+
.add("qualifier='" + qualifier + "'")
103+
.add("dynamicQualifier=" + dynamicQualifier)
95104
.add("contentTypes=" + contentTypes)
96105
.add("tags=" + tags)
106+
.add("address=" + address)
97107
.add("isSecured=" + isSecured)
98108
.toString();
99109
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package io.scalecube.services.api;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.LinkedHashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.Objects;
9+
import java.util.StringJoiner;
10+
import java.util.regex.Pattern;
11+
12+
public final class DynamicQualifier {
13+
14+
private final String qualifier;
15+
private final Pattern pattern;
16+
private final List<String> pathVariables;
17+
private final int size;
18+
19+
public DynamicQualifier(String qualifier) {
20+
if (!qualifier.contains(":")) {
21+
throw new IllegalArgumentException("Illegal dynamic qualifier: " + qualifier);
22+
}
23+
24+
final var pathVariables = new ArrayList<String>();
25+
final var sb = new StringBuilder();
26+
for (var s : qualifier.split("/")) {
27+
if (s.startsWith(":")) {
28+
final var pathVar = s.substring(1);
29+
sb.append("(?<").append(pathVar).append(">.*?)");
30+
pathVariables.add(pathVar);
31+
} else {
32+
sb.append(s);
33+
}
34+
sb.append("/");
35+
}
36+
sb.setLength(sb.length() - 1);
37+
38+
this.qualifier = qualifier;
39+
this.pattern = Pattern.compile(sb.toString());
40+
this.pathVariables = Collections.unmodifiableList(pathVariables);
41+
this.size = sizeOf(qualifier);
42+
}
43+
44+
public String qualifier() {
45+
return qualifier;
46+
}
47+
48+
public Pattern pattern() {
49+
return pattern;
50+
}
51+
52+
public List<String> pathVariables() {
53+
return pathVariables;
54+
}
55+
56+
public int size() {
57+
return size;
58+
}
59+
60+
public Map<String, String> matchQualifier(String input) {
61+
if (size != sizeOf(input)) {
62+
return null;
63+
}
64+
65+
final var matcher = pattern.matcher(input);
66+
if (!matcher.matches()) {
67+
return null;
68+
}
69+
70+
final var map = new LinkedHashMap<String, String>();
71+
for (var pathVar : pathVariables) {
72+
final var value = matcher.group(pathVar);
73+
Objects.requireNonNull(
74+
value, "Path variable value must not be null, path variable: " + pathVar);
75+
map.put(pathVar, value);
76+
}
77+
78+
return map;
79+
}
80+
81+
private static int sizeOf(String value) {
82+
int count = 0;
83+
for (int i = 0, length = value.length(); i < length; i++) {
84+
if (value.charAt(i) == '/') {
85+
count++;
86+
}
87+
}
88+
return count;
89+
}
90+
91+
@Override
92+
public boolean equals(Object o) {
93+
if (this == o) {
94+
return true;
95+
}
96+
if (o == null || getClass() != o.getClass()) {
97+
return false;
98+
}
99+
return Objects.equals(qualifier, ((DynamicQualifier) o).qualifier);
100+
}
101+
102+
@Override
103+
public int hashCode() {
104+
return Objects.hashCode(qualifier);
105+
}
106+
107+
@Override
108+
public String toString() {
109+
return new StringJoiner(", ", DynamicQualifier.class.getSimpleName() + "[", "]")
110+
.add("qualifier='" + qualifier + "'")
111+
.add("pattern=" + pattern)
112+
.add("pathVariables=" + pathVariables)
113+
.add("size=" + size)
114+
.toString();
115+
}
116+
}

services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.scalecube.services.methods;
22

33
import io.scalecube.services.CommunicationMode;
4+
import io.scalecube.services.api.DynamicQualifier;
45
import io.scalecube.services.api.Qualifier;
56
import java.lang.reflect.Type;
67
import java.util.StringJoiner;
@@ -11,6 +12,7 @@ public final class MethodInfo {
1112
private final String serviceName;
1213
private final String methodName;
1314
private final String qualifier;
15+
private final DynamicQualifier dynamicQualifier;
1416
private final Type parameterizedReturnType;
1517
private final boolean isReturnTypeServiceMessage;
1618
private final CommunicationMode communicationMode;
@@ -51,6 +53,7 @@ public MethodInfo(
5153
this.serviceName = serviceName;
5254
this.methodName = methodName;
5355
this.qualifier = Qualifier.asString(serviceName, methodName);
56+
this.dynamicQualifier = qualifier.contains(":") ? new DynamicQualifier(qualifier) : null;
5457
this.parameterCount = parameterCount;
5558
this.requestType = requestType;
5659
this.isRequestTypeServiceMessage = isRequestTypeServiceMessage;
@@ -70,6 +73,10 @@ public String qualifier() {
7073
return qualifier;
7174
}
7275

76+
public DynamicQualifier dynamicQualifier() {
77+
return dynamicQualifier;
78+
}
79+
7380
public Type parameterizedReturnType() {
7481
return parameterizedReturnType;
7582
}
@@ -112,6 +119,7 @@ public String toString() {
112119
.add("serviceName='" + serviceName + "'")
113120
.add("methodName='" + methodName + "'")
114121
.add("qualifier='" + qualifier + "'")
122+
.add("dynamicQualifier=" + dynamicQualifier)
115123
.add("parameterizedReturnType=" + parameterizedReturnType)
116124
.add("isReturnTypeServiceMessage=" + isReturnTypeServiceMessage)
117125
.add("communicationMode=" + communicationMode)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.scalecube.services.methods;
2+
3+
import java.util.Collections;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
import java.util.StringJoiner;
7+
import reactor.core.publisher.Mono;
8+
9+
public class RequestContext {
10+
11+
private final Map<String, String> headers;
12+
private final Object principal;
13+
private final Map<String, String> pathVars;
14+
15+
/**
16+
* Constructor.
17+
*
18+
* @param headers message headers
19+
* @param principal authenticated principal (optional)
20+
* @param pathVars path variables (optional)
21+
*/
22+
public RequestContext(
23+
Map<String, String> headers, Object principal, Map<String, String> pathVars) {
24+
this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
25+
this.principal = principal;
26+
this.pathVars = pathVars != null ? Map.copyOf(pathVars) : null;
27+
}
28+
29+
public Map<String, String> headers() {
30+
return headers;
31+
}
32+
33+
public String header(String name) {
34+
return headers.get(name);
35+
}
36+
37+
public <T> T principal() {
38+
//noinspection unchecked
39+
return (T) principal;
40+
}
41+
42+
public Map<String, String> pathVars() {
43+
return pathVars;
44+
}
45+
46+
public String pathVar(String name) {
47+
return pathVars != null ? pathVars.get(name) : null;
48+
}
49+
50+
public static Mono<RequestContext> deferContextual() {
51+
return Mono.deferContextual(context -> Mono.just(context.get(RequestContext.class)));
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return new StringJoiner(", ", RequestContext.class.getSimpleName() + "[", "]")
57+
.add("headers=" + headers)
58+
.add("principal=" + principal)
59+
.add("pathVars=" + pathVars)
60+
.toString();
61+
}
62+
}

0 commit comments

Comments
 (0)