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

Merged
merged 26 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -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,62 @@
package io.scalecube.services.methods;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
import reactor.core.publisher.Mono;

public class RequestContext {

private final Map<String, String> headers;
private final Object principal;
private final Map<String, String> pathVars;

/**
* Constructor.
*
* @param headers message headers
* @param principal authenticated principal (optional)
* @param pathVars path variables (optional)
*/
public RequestContext(
Map<String, String> headers, Object principal, Map<String, String> pathVars) {
this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
this.principal = principal;
this.pathVars = pathVars != null ? Map.copyOf(pathVars) : null;
}

public Map<String, String> headers() {
return headers;
}

public String header(String name) {
return headers.get(name);
}

public <T> T principal() {
//noinspection unchecked
return (T) principal;
}

public Map<String, String> pathVars() {
return pathVars;
}

public String pathVar(String name) {
return pathVars != null ? pathVars.get(name) : null;
}

public static Mono<RequestContext> deferContextual() {
return Mono.deferContextual(context -> Mono.just(context.get(RequestContext.class)));
}

@Override
public String toString() {
return new StringJoiner(", ", RequestContext.class.getSimpleName() + "[", "]")
.add("headers=" + headers)
.add("principal=" + principal)
.add("pathVars=" + pathVars)
.toString();
}
}
Loading
Loading