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

Support composite id with autodispatch resource handler #29

Merged
merged 10 commits into from
Apr 6, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public enum TemplateParameter implements ParameterKey {
XOOM_INITIALIZER_CLASS("xoomInitializerClass"),
PROJECTION_DISPATCHER_PROVIDER_NAME("projectionDispatcherProviderName"),
PROVIDERS("providers"),
EXCHANGE_BOOTSTRAP_NAME("exchangeBootstrapName");
EXCHANGE_BOOTSTRAP_NAME("exchangeBootstrapName"),
COMPOSITE_ID("compositeId");

public final String key;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,32 @@
import io.vlingo.xoom.codegen.parameter.CodeGenerationParameter;
import io.vlingo.xoom.turbo.annotation.codegen.Label;

import java.util.List;
import java.util.Optional;

import static io.vlingo.xoom.turbo.annotation.codegen.autodispatch.RouteDetail.extractCompositeIdFrom;
import static java.util.stream.Collectors.toList;

public class AggregateDetail {
private static final String COMPOSITE_ID_DECLARATION_PATTERN = "final String";

public static CodeGenerationParameter methodWithName(final CodeGenerationParameter aggregate, final String methodName) {
return findMethod(aggregate, methodName).orElseThrow(() -> new IllegalArgumentException("Method " + methodName + " not found"));
}

public static String resolveCompositeIdFields(CodeGenerationParameter aggregate) {
final String routePath = aggregate.retrieveRelatedValue(Label.URI_ROOT);

final List<String> compositeIdFields = extractCompositeIdFrom(routePath)
.stream()
.map(field -> String.format("%s %s", COMPOSITE_ID_DECLARATION_PATTERN, field))
.collect(toList());

if(compositeIdFields.isEmpty())
return "";

return String.format("%s, ", String.join(", ", compositeIdFields));
}
private static Optional<CodeGenerationParameter> findMethod(final CodeGenerationParameter aggregate, final String methodName) {
return aggregate.retrieveAllRelated(Label.AGGREGATE_METHOD)
.filter(method -> methodName.equals(method.value) || method.value.startsWith(methodName + "("))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ public String resolveRouteHandlerInvocation(final CodeGenerationParameter parent
final Method httpMethod =
routeSignatureParameter.retrieveRelatedValue(Label.ROUTE_METHOD, Method::from);

final String defaultParameter = httpMethod.isGET() ? QUERIES_PARAMETER : DEFAULT_FACTORY_METHOD_PARAMETER;
final String compositeIdParameter = RouteDetail.resolveCompositeIdParameterFrom(routeSignatureParameter);

String queriesParameters = QUERIES_PARAMETER;
if(!compositeIdParameter.isEmpty())
queriesParameters += String.format(", %s", compositeIdParameter);

final String defaultParameter = httpMethod.isGET() ? queriesParameters : DEFAULT_FACTORY_METHOD_PARAMETER;

return resolve(Label.ROUTE_HANDLER_INVOCATION, Label.USE_CUSTOM_ROUTE_HANDLER_PARAM, defaultParameter, parentParameter, routeSignatureParameter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.stream.Stream;

import static io.vlingo.xoom.codegen.template.ParameterKey.Defaults.PACKAGE_NAME;
import static io.vlingo.xoom.turbo.annotation.codegen.TemplateParameter.COMPOSITE_ID;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

Expand Down Expand Up @@ -78,6 +79,7 @@ private AutoDispatchResourceHandlerTemplateData(final CodeElementFormatter codeE
.and(TemplateParameter.AUTO_DISPATCH_MAPPING_NAME, restResourceName).and(TemplateParameter.USE_AUTO_DISPATCH, true)
.and(TemplateParameter.STATE_DATA_OBJECT_NAME, AnnotationBasedTemplateStandard.DATA_OBJECT.resolveClassname(aggregateProtocolClassName))
.and(TemplateParameter.USE_CQRS, context.parameterOf(Label.CQRS, Boolean::valueOf))
.and(COMPOSITE_ID, AggregateDetail.resolveCompositeIdFields(autoDispatchParameter))
.addImports(resolveImports(context, autoDispatchParameter, queryStoreProviderName));

this.dependOn(RouteMethodTemplateData.from(autoDispatchParameter, parameters));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
import io.vlingo.xoom.turbo.annotation.codegen.AnnotationBasedTemplateStandard;
import io.vlingo.xoom.turbo.annotation.codegen.Label;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -26,6 +29,7 @@ public class RouteDetail {
private static final String METHOD_PARAMETER_PATTERN = "final %s %s";
private static final String METHOD_SIGNATURE_PATTERN = "%s(%s)";
private static final List<Method> BODY_SUPPORTED_HTTP_METHODS = Arrays.asList(POST, PUT, PATCH);
private static final String COMPOSITE_ID_TYPE = "String";

public static String resolveBodyName(final CodeGenerationParameter route) {
final Method httpMethod = route.retrieveRelatedValue(ROUTE_METHOD, Method::from);
Expand Down Expand Up @@ -66,10 +70,13 @@ public static String resolveMethodSignature(final CodeGenerationParameter routeS

final String arguments = parameters.map(field -> {
final String fieldType = FieldDetail.typeOf(field.parent(Label.AGGREGATE), field.value);
return String.format("final %s %s", fieldType, field.value);
return String.format(METHOD_PARAMETER_PATTERN, fieldType, field.value);
}).collect(Collectors.joining(", "));

return String.format(METHOD_SIGNATURE_PATTERN, routeSignature.value, arguments);
final String compositeIdParameter = String.join(",", compositeIdParameterFrom(routeSignature));

final String params = formatParameters(Stream.of(compositeIdParameter, arguments));
return String.format(METHOD_SIGNATURE_PATTERN, routeSignature.value, params);
}

return resolveMethodSignatureWithParams(routeSignature);
Expand All @@ -80,16 +87,76 @@ private static String resolveMethodSignatureWithParams(final CodeGenerationParam
routeSignature.retrieveRelatedValue(REQUIRE_ENTITY_LOADING, Boolean::valueOf) ?
String.format(METHOD_PARAMETER_PATTERN, "String", "id") : "";

final String compositeIdParameter = String.join(",", compositeIdParameterFrom(routeSignature));

final CodeGenerationParameter method = AggregateDetail.methodWithName(routeSignature.parent(), routeSignature.value);
final String dataClassname = AnnotationBasedTemplateStandard.DATA_OBJECT.resolveClassname(routeSignature.parent().value);
final String dataParameterDeclaration = String.format(METHOD_PARAMETER_PATTERN, dataClassname, "data");
final String dataParameter = method.hasAny(METHOD_PARAMETER) ? dataParameterDeclaration : "";
final String parameters =
Stream.of(idParameter, dataParameter).filter(param -> !param.isEmpty())
.collect(Collectors.joining(", "));

final String parameters = formatParameters(Stream.of(compositeIdParameter, idParameter, dataParameter));
return String.format(METHOD_SIGNATURE_PATTERN, routeSignature.value, parameters);
}

private static List<String> compositeIdParameterFrom(CodeGenerationParameter routeSignature) {
String routePath = resolveRoutePathFrom(routeSignature);
final List<String> compositeIds = extractCompositeIdFrom(routePath);

return compositeIds.stream()
.map(compositeId -> String.format(METHOD_PARAMETER_PATTERN, COMPOSITE_ID_TYPE, compositeId))
.collect(Collectors.toList());
}

public static List<String> extractCompositeIdFrom(String routePath) {
return extractAllPathVariablesFrom(routePath)
.stream().filter(pathVar -> !pathVar.equals("id"))
.collect(Collectors.toList());
}

public static String resolveCompositeIdFields(CodeGenerationParameter routeSignature) {
String routePath = resolveRoutePathFrom(routeSignature);
final String compositeId = String.join(",", extractCompositeIdFrom(routePath));

return !compositeId.isEmpty() && !compositeId.equals("id")? compositeId + ", " : "";
}

public static String resolveCompositeIdParameterFrom(CodeGenerationParameter routeSignature) {
String routePath = resolveRoutePathFrom(routeSignature);
final String compositeId = String.join(", ", extractAllPathVariablesFrom(routePath));

return !compositeId.isEmpty()? compositeId : "";
}

private static List<String> extractAllPathVariablesFrom(String routePath) {
final List<String> result = new ArrayList<>();

final String regex = "\\{(.*?)\\}";

final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
final Matcher matcher = pattern.matcher(routePath);

while (matcher.find()) {
result.add(matcher.group(1));
}

return result;
}

private static String resolveRoutePathFrom(CodeGenerationParameter routeSignature) {
String routePath = routeSignature.retrieveRelatedValue(Label.ROUTE_PATH);
if(!routePath.startsWith(routeSignature.parent().retrieveRelatedValue(Label.URI_ROOT))) {
routePath = routeSignature.parent().retrieveRelatedValue(Label.URI_ROOT) + routePath;
}
return routePath;
}

private static String formatParameters(Stream<String> arguments) {
return arguments
.distinct()
.filter(param -> !param.isEmpty())
.collect(Collectors.joining(", "));
}

private static boolean hasValidMethodSignature(final String signature) {
return signature.contains("(") && signature.contains(")");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
Expand Down Expand Up @@ -69,7 +70,8 @@ private RouteMethodTemplateData(final CodeElementFormatter codeElementFormatter,
.and(ADAPTER_HANDLER_INVOCATION, adapterHandlerInvocation)
.and(VALUE_OBJECT_INITIALIZERS, Collections.emptyList())
.and(ROUTE_HANDLER_INVOCATION, routeHandlerInvocation)
.and(ID_NAME, resolveIdName(routeSignatureParameter));
.and(ID_NAME, resolveIdName(routeSignatureParameter))
.and(COMPOSITE_ID, RouteDetail.resolveCompositeIdFields(routeSignatureParameter));

parentParameters.addImports(resolveImports(mainParameter, routeSignatureParameter));
}
Expand All @@ -87,16 +89,29 @@ private Set<String> resolveImports(final CodeGenerationParameter mainParameter,
}

private Boolean resolveEntityLoading(final CodeGenerationParameter routeSignatureParameter) {
return routeSignatureParameter.hasAny(Label.ID) ||
return (routeSignatureParameter.hasAny(Label.ID) && routeSignatureParameter.retrieveAllRelated(Label.ID).anyMatch(this::idIsNotCompositeId)) ||
(routeSignatureParameter.hasAny(Label.REQUIRE_ENTITY_LOADING) &&
routeSignatureParameter.retrieveRelatedValue(Label.REQUIRE_ENTITY_LOADING, Boolean::valueOf));
}

private boolean idIsNotCompositeId(CodeGenerationParameter idParameter) {
return !idParameter.parent().retrieveRelatedValue(Label.ROUTE_PATH).isEmpty() &&
!RouteDetail.extractCompositeIdFrom(idParameter.parent().retrieveRelatedValue(Label.ROUTE_PATH))
.contains(idParameter.value);
}

private String resolveIdName(final CodeGenerationParameter routeSignatureParameter) {
if (!routeSignatureParameter.hasAny(Label.ID)) {
return DEFAULT_ID_NAME;
}
return routeSignatureParameter.retrieveRelatedValue(Label.ID);

Optional<CodeGenerationParameter> id = routeSignatureParameter.retrieveAllRelated(Label.ID)
.filter(this::idIsNotCompositeId)
.findFirst();
if(id.isPresent())
return id.get().value;

return DEFAULT_ID_NAME;
}

private String retrieveIdTypeQualifiedName(final CodeGenerationParameter routeSignatureParameter) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/codegen/java/RestResource.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ public class ${resourceName} extends DynamicResourceHandler {
return ContentType.of("application/json", "charset=UTF-8");
}

private String location(final String id) {
return "${locationPath}" + id;
private String location(${compositeId}final String id) {
return "${locationPath?replace("{", "\" + ")?replace("}", " + \"")}" + id;
}

<#if modelProtocol?has_content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ public Completes<Response> ${routeSignature} {
${initializer}
</#list>
return ${routeHandlerInvocation}
.andThenTo(state -> Completes.withSuccess(entityResponseOf(Created, ResponseHeader.headers(ResponseHeader.of(Location, location(state.id))), serialized(${adapterHandlerInvocation})))
.andThenTo(state -> Completes.withSuccess(entityResponseOf(Created, ResponseHeader.headers(ResponseHeader.of(Location, location(${compositeId}state.id))), serialized(${adapterHandlerInvocation})))
.otherwise(arg -> Response.of(NotFound))
.recoverFrom(e -> Response.of(InternalServerError, e.getMessage())));
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
@AutoDispatch(path = "/dummies/", handlers = DummyHandlers.class)
public interface DummyModelResource {

@Route(method = PUT, path = "/{dummyId}/name/", handler = CHANGE_NAME)
@Route(method = PUT, path = "/{id}/name/", handler = CHANGE_NAME)
@ResponseAdapter(handler = ADAPT_STATE)
Completes<Response> changeDummyName(@Id String dummyId, @Body DummyData dummyData);
Completes<Response> changeDummyName(@Id String id, @Body DummyData dummyData);

}
Loading