Skip to content
This repository has been archived by the owner on Dec 18, 2022. It is now read-only.

[#31] resolve dynamic url mapping #34

Closed
wants to merge 1 commit into from
Closed
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
Expand Up @@ -38,7 +38,7 @@ private DssRestPathResolver dssRestPathResolver(List<DssRestActorService> servic
final DssRestPathResolver.Builder builder = DssRestPathResolver.builder();
serviceList.forEach(service -> {
final ActorRef<DssRestServiceActorCommand> serviceActor = context.spawn(DssRestServiceActor.create(service), service.getName());
builder.addServiceActor(service.getMethodType(), service.getPath(), serviceActor);
builder.addService(service, serviceActor);
});
return builder.build();
}
Expand All @@ -54,7 +54,7 @@ private Behavior<DssRestMasterActorCommand> handlingDssRestMasterActorCommandReq

context.getLog().info("DssRestMasterActorCommandRequest: {}", request);

final Optional<ActorRef<DssRestServiceActorCommand>> optional = dssRestPathResolver.getStaticServiceActor(request.getMethodType(), request.getPath());
final Optional<ActorRef<DssRestServiceActorCommand>> optional = dssRestPathResolver.getPathServiceActor(request.getMethodType(), request.getPath());
if (optional.isPresent()) {
optional.get().tell(new DssRestServiceActorCommandRequest(request));
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.ztkmkoo.dss.core.actor.rest;

import akka.actor.typed.ActorRef;
import com.ztkmkoo.dss.core.actor.rest.entity.DssRestPath;
import com.ztkmkoo.dss.core.actor.rest.entity.DssRestPathComposition;
import com.ztkmkoo.dss.core.actor.rest.service.DssRestActorService;
import com.ztkmkoo.dss.core.actor.rest.util.DssRestPathUtils;
import com.ztkmkoo.dss.core.exception.DssRestServiceMappingException;
import com.ztkmkoo.dss.core.message.rest.DssRestServiceActorCommand;
import com.ztkmkoo.dss.core.network.rest.enumeration.DssRestMethodType;
Expand All @@ -17,19 +21,56 @@
public class DssRestPathResolver {

private static final Logger logger = LoggerFactory.getLogger(DssRestPathResolver.class);
private final Map<DssRestMethodType, Map<String, ActorRef<DssRestServiceActorCommand>>> staticServiceActorMap;

private final Map<DssRestMethodType, Map<String, DssRestPathComposition>> staticPathCompositionMap;
private final Map<DssRestMethodType, List<DssRestPathComposition>> dynamicPathCompositionMap;

private DssRestPathResolver(Builder builder) {
this.staticServiceActorMap = Collections.unmodifiableMap(builder.staticServiceActorMap);
this.staticPathCompositionMap = Collections.unmodifiableMap(builder.staticPathCompositionMap);
this.dynamicPathCompositionMap = Collections.unmodifiableMap(builder.dynamicPathCompositionMap);
}

public Optional<ActorRef<DssRestServiceActorCommand>> getPathServiceActor(DssRestMethodType methodType, String path) {
final String fixedPath = DssRestPathUtils.getFixedPath(path);
final Optional<ActorRef<DssRestServiceActorCommand>> fixedOptional = getStaticPathServiceActor(methodType, fixedPath);
if (fixedOptional.isPresent()) {
return fixedOptional;
}

return getDynamicPathServiceActor(methodType, fixedPath);
}

private Optional<ActorRef<DssRestServiceActorCommand>> getStaticPathServiceActor(DssRestMethodType methodType, String fixedPath) {
if (!staticPathCompositionMap.containsKey(methodType)) {
return Optional.empty();
}

final Map<String, DssRestPathComposition> map = staticPathCompositionMap.get(methodType);

if (!map.containsKey(fixedPath)) {
return Optional.empty();
}

return Optional.of(map.get(fixedPath).getServiceActor());
}

public Optional<ActorRef<DssRestServiceActorCommand>> getStaticServiceActor(DssRestMethodType methodType, String path) {
return Optional
.ofNullable(
staticServiceActorMap
.getOrDefault(methodType, Collections.emptyMap())
.get(path)
);
private Optional<ActorRef<DssRestServiceActorCommand>> getDynamicPathServiceActor(DssRestMethodType methodType, String fixedPath) {
if (!dynamicPathCompositionMap.containsKey(methodType)) {
return Optional.empty();
}

final List<DssRestPathComposition> list = dynamicPathCompositionMap.get(methodType);
if (list.isEmpty()) {
return Optional.empty();
}

for (DssRestPathComposition composition : list) {
if(composition.getDssRestPath().match(fixedPath)) {
return Optional.of(composition.getServiceActor());
}
}

return Optional.empty();
}

public static Builder builder() {
Expand All @@ -38,33 +79,104 @@ public static Builder builder() {

public static class Builder {

private Map<DssRestMethodType, Map<String, ActorRef<DssRestServiceActorCommand>>> staticServiceActorMap = new EnumMap<>(DssRestMethodType.class);
private final Map<DssRestMethodType, Map<String, DssRestPathComposition>> staticPathCompositionMap = new EnumMap<>(DssRestMethodType.class);
private final Map<DssRestMethodType, List<DssRestPathComposition>> dynamicPathCompositionMap = new EnumMap<>(DssRestMethodType.class);

public Builder addServiceActor(DssRestMethodType methodType, String path, ActorRef<DssRestServiceActorCommand> serviceActor) {
Objects.requireNonNull(methodType);
Objects.requireNonNull(path);
public Builder addService(DssRestActorService service, ActorRef<DssRestServiceActorCommand> serviceActor) {
Objects.requireNonNull(service);
Objects.requireNonNull(service.getMethodType());
Objects.requireNonNull(service.getPath());
Objects.requireNonNull(serviceActor);

if (!staticServiceActorMap.containsKey(methodType)) {
staticServiceActorMap.put(methodType, new HashMap<>());
final String fixedPath = DssRestPathUtils.getFixedPath(service.getPath());
if (DssRestPathUtils.isDynamicPath(fixedPath)) {
addDynamicPathService(service, serviceActor);
} else {
addStaticPathService(service, serviceActor);
}

return this;
}

private void addStaticPathService(DssRestActorService service, ActorRef<DssRestServiceActorCommand> serviceActor) {
if(!staticPathCompositionMap.containsKey(service.getMethodType())) {
staticPathCompositionMap.put(service.getMethodType(), new HashMap<>());
}

if (staticServiceActorMap.get(methodType).containsKey(path)) {
final Map<String, DssRestPathComposition> map = staticPathCompositionMap.get(service.getMethodType());
final String path = service.getPath();
if (map.containsKey(path)) {
throw new DssRestServiceMappingException("Cannot map same path: " + path);
}
staticServiceActorMap.get(methodType).put(path, serviceActor);
return this;

map.put(
service.getPath(),
DssRestPathComposition
.builder()
.serviceActor(serviceActor)
.dssRestPath(
DssRestPath
.builder()
.path(path)
.build()
)
.build()
);
}

public DssRestPathResolver build() {
if (!staticServiceActorMap.isEmpty()) {
staticServiceActorMap.forEach((methodType, map) -> map.forEach((path, actorRef) ->
logger.info("Add mapping {} {} to {}",
methodType.name(), path, actorRef.path().name())
));
private void addDynamicPathService(DssRestActorService service, ActorRef<DssRestServiceActorCommand> serviceActor) {
if(!dynamicPathCompositionMap.containsKey(service.getMethodType())) {
dynamicPathCompositionMap.put(service.getMethodType(), new ArrayList<>());
}

final List<DssRestPathComposition> list = dynamicPathCompositionMap.get(service.getMethodType());
final String path = service.getPath();

list.add(
DssRestPathComposition
.builder()
.serviceActor(serviceActor)
.dssRestPath(
DssRestPath
.builder()
.path(path)
.build()
)
.build()
);
}

public DssRestPathResolver build() {
logStaticMappingInfo();
logDynamicMappingInfo();

return new DssRestPathResolver(this);
}

private void logStaticMappingInfo() {
if (staticPathCompositionMap.isEmpty()) {
return;
}

staticPathCompositionMap.forEach((methodType, map) ->
map.forEach((path, composition) ->
logger.info("Add static mapping: [{}] {} to {}",
methodType, path, composition.getServiceActor().path().name())
)
);
}

private void logDynamicMappingInfo() {
if (dynamicPathCompositionMap.isEmpty()) {
return;
}

dynamicPathCompositionMap.forEach((methodType, map) ->
map.forEach(composition ->
logger.info("Add static mapping: [{}] {} to {}", methodType,
composition.getDssRestPath().getRowPath(), composition.getServiceActor().path().name())
)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.ztkmkoo.dss.core.actor.rest.entity;

import lombok.Builder;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Project: dss
* Created by: @ztkmkoo(ztkmkoo@gmail.com)
* Date: 20. 4. 13. 오전 2:06
*/
@Getter
public class DssRestPath implements Serializable {

private static final long serialVersionUID = 8287023454874773276L;

private final transient Logger logger = LoggerFactory.getLogger(DssRestPath.class);

private static final String PATH_SEPARATOR_REG_EX = "\\/";
private static final String PATH_NORMAL_REG_EX = "[^\\/]+";
private static final String PATH_DIGITAL_REG_EX = "(\\d+)";
private static final String PATH_VARIABLE_REG_EX = "\\{[^\\/]+\\}";
private static final Pattern PATH_NORMAL_PATTERN = Pattern.compile(PATH_NORMAL_REG_EX);
private static final Pattern PATH_VARIABLE_PATTERN = Pattern.compile(PATH_VARIABLE_REG_EX);

private final Pattern pattern;
private final String rowPath;
private final Map<Integer, String> staticPathMap = new HashMap<>();

@Builder
private DssRestPath(String path, Map<String, Class<? extends Object>> classMap) {
final String regex = getRegEx(path, Objects.nonNull(classMap) ? classMap : Collections.emptyMap());
this.pattern = Pattern.compile(regex);
this.rowPath = path;
logger.info("Regex: {}", regex);
}

private String getRegEx(String path, Map<String, Class<? extends Object>> classMap) {
final StringBuilder sb = new StringBuilder();

final List<String> pathElements = pathElements(path);
for (int i = 0; i < pathElements.size(); i++) {
final String pathElement = pathElements.get(i);
if (isPathVariablePattern(pathElement)) {
final String pathVariableName = pathElement.substring(1, pathElement.length() - 1);

final Class<? extends Object> tClass = classMap.getOrDefault(pathVariableName, String.class);
final String regEx = regExFromClassType(tClass);
sb.append(PATH_SEPARATOR_REG_EX).append(regEx);
} else {
sb
.append(PATH_SEPARATOR_REG_EX)
.append(PATH_NORMAL_REG_EX);
staticPathMap.put(i, pathElement);
}
}

return sb.append("$").toString();
}

public boolean match(String path) {
Objects.requireNonNull(pattern);
final String fixedPath = getFixedPath(path);
final Matcher matcher = pattern.matcher(fixedPath);
if (!matcher.find()) {
logger.debug("Path regEx not matched: {}", path);
return false;
}

logger.debug("Path regEx matched, try to check static path element: {}", path);
return matchStaticPath(fixedPath, staticPathMap);
}

private boolean matchStaticPath(String path, Map<Integer, String> staticPathMap) {
final List<String> pathElements = pathElements(path);
logger.debug("user request path: {}, expected path: {}", pathElements, rowPath);

for (Map.Entry<Integer, String> entry : staticPathMap.entrySet()) {
final int index = entry.getKey();
final String staticPathElement = entry.getValue();

if (index >= pathElements.size()) {
return false;
}

final String pathElement = pathElements.get(index);
if (!staticPathElement.equals(pathElement)) {
return false;
}
}

return true;
}

private static List<String> pathElements(String path) {
final List<String> list = new ArrayList<>();

final String fixedPath = getFixedPath(path);

final Matcher matcher = PATH_NORMAL_PATTERN.matcher(fixedPath);
while (matcher.find()) {
list.add(matcher.group());
}

return list;
}

private static boolean isPathVariablePattern(String pathElement) {
final Matcher matcher = PATH_VARIABLE_PATTERN.matcher(pathElement);
return matcher.find();
}

private static String regExFromClassType(Class<? extends Object> tClass) {
if (Number.class.isAssignableFrom(tClass)) {
return PATH_DIGITAL_REG_EX;
} else {
return PATH_NORMAL_REG_EX;
}
}

public static String getFixedPath(String path) {
if (path.contains("?")) {
return path.substring(0, path.indexOf('?'));
} else {
return path;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.ztkmkoo.dss.core.actor.rest.entity;

import akka.actor.typed.ActorRef;
import com.ztkmkoo.dss.core.message.rest.DssRestServiceActorCommand;
import lombok.Builder;
import lombok.Getter;

import java.io.Serializable;

/**
* Project: dss
* Created by: @ztkmkoo(ztkmkoo@gmail.com)
* Date: 20. 4. 18. 오전 4:09
*/
@Getter
public class DssRestPathComposition implements Serializable {
private static final long serialVersionUID = 6447860854358860483L;

private final DssRestPath dssRestPath;
private final ActorRef<DssRestServiceActorCommand> serviceActor;

@Builder
private DssRestPathComposition(DssRestPath dssRestPath, ActorRef<DssRestServiceActorCommand> serviceActor) {
this.dssRestPath = dssRestPath;
this.serviceActor = serviceActor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ztkmkoo.dss.core.actor.rest.util;

/**
* Project: dss
* Created by: @ztkmkoo(ztkmkoo@gmail.com)
* Date: 20. 4. 18. 오전 4:11
*/
public class DssRestPathCompositionUtils {

private DssRestPathCompositionUtils() {}
}
Loading