From 9774ecb2917c019d5212d9e5fdb0920139b025b4 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Fri, 29 Nov 2024 13:09:36 +0100 Subject: [PATCH 01/21] [plugins/zdl-to-openapi] adds support for patch method --- plugins/zdl-to-openapi/README.md | 1 + .../sdk/plugins/ZDLToOpenAPIGenerator.java | 53 +++++++++++-------- .../EntitiesToSchemasConverter.java | 26 ++++++--- .../io/zenwave360/sdk/zdl/ZDLHttpUtils.java | 7 +-- .../sdk/resources/zdl/customer-address.zdl | 3 ++ 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/plugins/zdl-to-openapi/README.md b/plugins/zdl-to-openapi/README.md index 1a592010..ff5a87c7 100644 --- a/plugins/zdl-to-openapi/README.md +++ b/plugins/zdl-to-openapi/README.md @@ -19,6 +19,7 @@ jbang zw -p io.zenwave360.sdk.plugins.ZDLToOpenAPIPlugin \ | **Option** | **Description** | **Type** | **Default** | **Values** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------------------------|------------| | `specFile` | Spec file to parse | String | | | +| `baseOpenapi` | OpenAPI file to use as base. Generated API will be merged with this file. | String | | | | `targetFolder` | Target folder to generate code to. If left empty, it will print to stdout. | File | | | | `targetFile` | Target file | String | openapi.yml | | | `title` | API Title | String | | | diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java index ec3c53e2..677584a9 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java @@ -31,6 +31,12 @@ public class ZDLToOpenAPIGenerator implements Generator { @DocumentedOption(description = "API Title") public String title; + @DocumentedOption(description = "OpenAPI file to use as base. Generated API will be merged with this file.") + public String baseOpenapi; + + @DocumentedOption(description = "DTO Suffix used for schemas in PATCH operations") + public String dtoPatchSuffix = "Patch"; + @DocumentedOption(description = "Target file") public String targetFile = "openapi.yml"; @DocumentedOption(description = "Extension property referencing original zdl entity in components schemas (default: x-business-entity)") @@ -52,6 +58,7 @@ public class ZDLToOpenAPIGenerator implements Generator { "get", 200, "post", 201, "put", 200, + "patch", 200, "delete", 204 ); @@ -114,16 +121,35 @@ public List generate(Map contextModel) { JSONPath.set(oasSchemas, "components.schemas", schemas); EntitiesToSchemasConverter converter = new EntitiesToSchemasConverter().withIdType(idType, idTypeFormat).withZdlBusinessEntityProperty(zdlBusinessEntityProperty); - var methodsWithRest = JSONPath.get(zdlModel, "$.services[*].methods[*][?(@.options.get || @.options.post || @.options.put || @.options.delete || @.options.patch)]", Collections.emptyList()); List> entities = filterSchemasToInclude(zdlModel, methodsWithRest); + generateAndAddSchemas(entities, converter, zdlModel, schemas, listedEntities, paginatedEntities, ""); + + EntitiesToSchemasConverter converterForPatchOperation = new EntitiesToSchemasConverter().withIdType(idType, idTypeFormat).withUseNullableForAllFields(true, dtoPatchSuffix).withZdlBusinessEntityProperty(zdlBusinessEntityProperty); + var methodsWithPatch = JSONPath.get(zdlModel, "$.services[*].methods[*][?(@.options.patch)]", Collections.emptyList()); + List> entitiesForPatch = filterSchemasToInclude(zdlModel, methodsWithPatch); + generateAndAddSchemas(entitiesForPatch, converterForPatchOperation, zdlModel, schemas, listedEntities, paginatedEntities, dtoPatchSuffix); + + String openAPISchemasString = null; + try { + openAPISchemasString = mapper.writeValueAsString(oasSchemas); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + // remove first line + openAPISchemasString = openAPISchemasString.substring(openAPISchemasString.indexOf("\n") + 1); + + return List.of(generateTemplateOutput(contextModel, zdlToOpenAPITemplate, zdlModel, openAPISchemasString)); + } + + protected void generateAndAddSchemas(List> entities, EntitiesToSchemasConverter converter, Map zdlModel, Map schemas, List listedEntities, List paginatedEntities, String dtoPatchSuffix) { for (Map entity : entities) { String entityName = (String) entity.get("name"); Map openAPISchema = converter.convertToSchema(entity, zdlModel); - schemas.put(entityName, openAPISchema); + schemas.put(entityName + dtoPatchSuffix, openAPISchema); if(listedEntities.contains(entityName)) { - Map listSchema = Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName)); + Map listSchema = Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName + dtoPatchSuffix)); schemas.put(entityName + "List", listSchema); } @@ -134,27 +160,10 @@ public List generate(Map contextModel) { Map.of(zdlBusinessEntityPaginatedProperty, entityName), Map.of("properties", Map.of("content", - Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName)))))); - schemas.put(entityName + "Paginated", paginatedSchema); + Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName + dtoPatchSuffix)))))); + schemas.put(entityName + dtoPatchSuffix + "Paginated", paginatedSchema); } } - -// List> enums = JSONPath.get(zdlModel, "$.enums[*]", emptyList()); -// for (Map enumValue : enums) { -// Map enumSchema = converter.convertToSchema(enumValue, zdlModel); -// schemas.put((String) enumValue.get("name"), enumSchema); -// } - - String openAPISchemasString = null; - try { - openAPISchemasString = mapper.writeValueAsString(oasSchemas); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - // remove first line - openAPISchemasString = openAPISchemasString.substring(openAPISchemasString.indexOf("\n") + 1); - - return List.of(generateTemplateOutput(contextModel, zdlToOpenAPITemplate, zdlModel, openAPISchemasString)); } protected List> filterSchemasToInclude(Map model, List methodsWithCommands) { diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/EntitiesToSchemasConverter.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/EntitiesToSchemasConverter.java index 419082cc..e352c1e8 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/EntitiesToSchemasConverter.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/EntitiesToSchemasConverter.java @@ -14,6 +14,9 @@ public class EntitiesToSchemasConverter { public String idType = "string"; public String idTypeFormat = null; + public boolean useNullableForAllFields = false; + public String dtoPatchSuffix = ""; + public boolean includeVersion = true; public EntitiesToSchemasConverter withIdType(String idType) { @@ -41,6 +44,12 @@ public EntitiesToSchemasConverter withZdlBusinessEntityProperty(String zdlBusine return this; } + public EntitiesToSchemasConverter withUseNullableForAllFields(boolean useNullableForAllFields, String dtoPatchSuffix) { + this.useNullableForAllFields = useNullableForAllFields; + this.dtoPatchSuffix = dtoPatchSuffix; + return this; + } + public Map convertToSchema(Map entityOrEnum, Map zdlModel) { boolean isEnum = entityOrEnum.get("values") != null; return isEnum ? convertEnumToSchema(entityOrEnum, zdlModel) : convertEntityToSchema(entityOrEnum, zdlModel); @@ -89,15 +98,20 @@ public Map convertEntityToSchema(Map entity, Map || JSONPath.get(zdlModel, "$.events." + field.get("type")) != null; if (isComplexType) { - property.put("$ref", "#/components/schemas/" + field.get("type")); + property.put("$ref", "#/components/schemas/" + field.get("type") + dtoPatchSuffix); } else { - property.putAll(schemaTypeAndFormat((String) field.get("type"))); + property.putAll(schemaTypeAndFormat((String) field.get("type") + dtoPatchSuffix)); } - String required = JSONPath.get(field, "$.validations.required.value"); - if (required != null) { - requiredProperties.add((String) field.get("name")); + if(useNullableForAllFields) { + property.put("nullable", true); + } else { + String required = JSONPath.get(field, "$.validations.required.value"); + if (required != null) { + requiredProperties.add((String) field.get("name")); + } } + String minlength = JSONPath.get(field, "$.validations.minlength.value"); if (minlength != null) { property.put("minLength", asNumber(minlength)); @@ -143,7 +157,7 @@ public Map convertEntityToSchema(Map entity, Map var readOnlyWarning = isAddRelationshipById ? "(read-only) " : ""; // TODO desc+$ref: property.put("description", readOnlyWarning + relationship.getOrDefault("comment", "")); } - property.put("$ref", "#/components/schemas/" + relationship.get("otherEntityName")); + property.put("$ref", "#/components/schemas/" + relationship.get("otherEntityName") + dtoPatchSuffix); if (isCollection) { property = Maps.of("type", "array", "items", property); } diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java index ca1d6ca6..ea4585b8 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java @@ -96,11 +96,12 @@ public static String getRequestBodyType(Map method, Map apiModel public static Map getHttpOption(Map method) { var get = JSONPath.get(method, "$.options.get"); - var put = JSONPath.get(method, "$.options.put"); var post = JSONPath.get(method, "$.options.post"); + var put = JSONPath.get(method, "$.options.put"); + var patch = JSONPath.get(method, "$.options.patch"); var delete = JSONPath.get(method, "$.options.delete"); - var httpOptions = ObjectUtils.firstNonNull(get, put, post, delete); - var httpMethod = get != null? "get" : put != null? "put" : post != null? "post" : delete != null? "delete" : null; + var httpOptions = ObjectUtils.firstNonNull(get, put, post, patch, delete); + var httpMethod = get != null? "get" : put != null? "put" : post != null? "post" : delete != null? "delete" : patch != null? "patch" : null; if (httpMethod == null) { return null; } diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl index 87704883..e03f1686 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl @@ -86,6 +86,9 @@ service CustomerService for (Customer) { @asyncapi({ api: ThirdPartyAPI, operationId: "doUpdateCustomer"}) updateCustomer(id, CustomerInput) Customer? withEvents CustomerEvent CustomerUpdated /** update customer javadoc comment */ + @patch("/customers/{customerId}") + patchCustomer(id, CustomerInput) Customer? withEvents CustomerEvent CustomerUpdated /** update customer javadoc comment */ + @delete("/customers/{customerId}") deleteCustomer(id) withEvents CustomerDeleted From 2be3c8162314cd6fbe118d7ae9261e0df4ae0117 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Sun, 1 Dec 2024 05:39:52 +0100 Subject: [PATCH 02/21] [plugins/openapi-controllers] **breaking** `useOptional` for method parameters defaults to false --- plugins/openapi-controllers/README.md | 4 ++-- .../zenwave360/sdk/plugins/OpenAPIControllersGenerator.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/openapi-controllers/README.md b/plugins/openapi-controllers/README.md index d707d0b7..9c4f820b 100644 --- a/plugins/openapi-controllers/README.md +++ b/plugins/openapi-controllers/README.md @@ -46,8 +46,8 @@ Default options for https://openapi-generator.tech/docs/generators/spring/ have true none - false - true + true + false true true true diff --git a/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java b/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java index 36fec994..7a67ac1a 100644 --- a/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java +++ b/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java @@ -51,6 +51,7 @@ public class OpenAPIControllersGenerator extends AbstractOpenAPIGenerator { @DocumentedOption(description = "Programming Style") public ProgrammingStyle style = ProgrammingStyle.imperative; + public boolean useOptional = false; public boolean simpleDomainPackaging = false; @DocumentedOption(description = "JSONPath list to search for response DTO schemas for list or paginated results. User '$.items' for lists or '$.properties..items' for paginated results.") @@ -382,7 +383,7 @@ protected int compareParamsByRequire(Map param1, Map param) { boolean isOptional = isOptional(param); String javaType = getJavaType(param); - return isOptional ? "Optional<" + javaType + ">" : javaType; + return useOptional && isOptional ? "Optional<" + javaType + ">" : javaType; } protected String getJavaType(Map param) { From 30511325e9b18f5303b16b936d44ad11537514ca Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Sun, 1 Dec 2024 11:47:33 +0100 Subject: [PATCH 03/21] [plugins/zdl-to-openapi] in GET endpoints include service.method parameter as query params --- .../zenwave360/sdk/plugins/PathsProcessor.java | 8 ++++---- .../ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs | 2 +- .../java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java | 16 ++++++++++++++-- .../AbstractZDLProjectGeneratorTest.java | 2 +- .../sdk/resources/zdl/customer-address.zdl | 13 +++++++++++-- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java index f8202420..9d715944 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java @@ -23,9 +23,9 @@ public class PathsProcessor extends AbstractBaseProcessor implements Processor { @Override public Map process(Map contextModel) { - Map apiModel = targetProperty != null ? (Map) contextModel.get(targetProperty) : (Map) contextModel; + Map zdl = targetProperty != null ? (Map) contextModel.get(targetProperty) : (Map) contextModel; - var services = JSONPath.get(apiModel, "$.services", Map.of()); + var services = JSONPath.get(zdl, "$.services", Map.of()); services.values().forEach(service -> { var restOption = JSONPath.get(service, "$.options.rest"); if(restOption != null) { @@ -42,7 +42,7 @@ public Map process(Map contextModel) { // var params = httpOption.get("params"); var pathParams = ZDLHttpUtils.getPathParams(path); var pathParamsMap = ZDLHttpUtils.getPathParamsAsObject(method, idType, idTypeFormat); - var queryParamsMap = ZDLHttpUtils.getQueryParamsAsObject(method); + var queryParamsMap = ZDLHttpUtils.getQueryParamsAsObject(method, zdl); var hasParams = !pathParams.isEmpty() || !queryParamsMap.isEmpty() || paginated != null; paths.appendTo(path, (String) methodVerb, new FluentMap() .with("operationId", methodName) @@ -54,7 +54,7 @@ public Map process(Map contextModel) { .with("pathParams", pathParams) .with("pathParamsMap", pathParamsMap) .with("queryParamsMap", queryParamsMap) - .with("requestBody", ZDLHttpUtils.getRequestBodyType(method, apiModel)) + .with("requestBody", ZDLHttpUtils.getRequestBodyType(method, zdl)) .with("responseBody", method.get("returnType")) .with("isResponseBodyArray", method.get("returnTypeIsArray")) .with("paginated", paginated) diff --git a/plugins/zdl-to-openapi/src/main/resources/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs b/plugins/zdl-to-openapi/src/main/resources/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs index 72cf1e76..b93dd836 100644 --- a/plugins/zdl-to-openapi/src/main/resources/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs +++ b/plugins/zdl-to-openapi/src/main/resources/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs @@ -70,7 +70,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/{{operation.requestBody}}" + $ref: "#/components/schemas/{{operation.requestBody}}{{ifTruthy (eq operation.httpMethod 'patch') dtoPatchSuffix ""}}" {{~/if}} responses: "{{httpResponseStatus operation}}": diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java index ea4585b8..fcab82cc 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java @@ -42,10 +42,22 @@ public static List> getPathParamsAsObject(Map method, String }).toList(); } - public static List> getQueryParamsAsObject(Map method) { + public static List> getQueryParamsAsObject(Map method, Map zdl) { var pathParams = getPathParamsFromMethod(method); var httpOption = getHttpOption(method); - var params = JSONPath.get(httpOption, "$.httpOptions.params", Map.of()); + var params = new LinkedHashMap(JSONPath.get(httpOption, "$.httpOptions.params", Map.of())); + if ("get".equals(httpOption.get("httpMethod"))) { + var methodParameterType = (String) method.get("parameter"); + var parameterEntity = JSONPath.get(zdl, "$.allEntitiesAndEnums." + methodParameterType); + if (parameterEntity != null) { + var fields = JSONPath.get(parameterEntity, "$.fields", Map.of()); + for (var field : fields.values()) { + if (!JSONPath.get(field, "$.isComplexType", false)) { + params.put((String) field.get("name"), field.get("type")); + } + } + } + } return (List) params.entrySet().stream().filter(entry -> !pathParams.contains(entry.getKey())).map(entry -> { var type = entry.getValue(); var typeAndFormat = EntitiesToSchemasConverter.schemaTypeAndFormat((String) type); diff --git a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/generators/AbstractZDLProjectGeneratorTest.java b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/generators/AbstractZDLProjectGeneratorTest.java index 25f074c9..7e059965 100644 --- a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/generators/AbstractZDLProjectGeneratorTest.java +++ b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/generators/AbstractZDLProjectGeneratorTest.java @@ -56,7 +56,7 @@ protected boolean isGenerateEntity(java.util.Map entity) { void testAbstractZDLProjectGenerator() throws IOException { var model = loadZDL("classpath:io/zenwave360/sdk/resources/zdl/customer-address.zdl"); var outputList = zdlProjectGenerator.generate(model); - Assertions.assertEquals(11, outputList.size()); + Assertions.assertEquals(12, outputList.size()); } @Test diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl index e03f1686..0299a189 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl @@ -70,6 +70,13 @@ input CustomerAddressId { address Address } +input CustomerSearchCriteria { + name String + email String + city String + state String +} + /** Service javadoc comment */ @@ -95,10 +102,12 @@ service CustomerService for (Customer) { @get("/customers/{customerId}") getCustomer(id) Customer? - @get({path: "/customers", params: {search: String}}) - @paginated + @get({path: "/customers", params: {search: String}}) @paginated listCustomers() Customer[] + @get({path: "/customers/search"}) @paginated + searchCustomers(CustomerSearchCriteria) Customer[] + @post("/customers/{customerId}/address") addCustomerAddress(CustomerAddressId) Customer? } From bfaf37c63ab737441b0002d42ab02211fcca4826 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Sun, 1 Dec 2024 12:00:22 +0100 Subject: [PATCH 04/21] [plugins/openapi-controllers] **breaking** `useOptional` for method parameters defaults to false --- .../projects/customer-address-postgres-json/pom.xml | 2 +- .../projects/customer-address-relational/pom.xml | 2 +- .../modules/orders/pom.xml | 4 ++-- .../projects/online-food-delivery-mongo/pom.xml | 2 +- .../projects/simple-domain-packaging/pom.xml | 2 +- .../main/java/web/mvc/ServiceApiController.java.hbs | 12 ++++++++++++ .../src/test/resources/pom.xml | 2 +- 7 files changed, 19 insertions(+), 7 deletions(-) diff --git a/e2e/src/test/resources/projects/customer-address-postgres-json/pom.xml b/e2e/src/test/resources/projects/customer-address-postgres-json/pom.xml index 27a5f1be..44c16fe5 100644 --- a/e2e/src/test/resources/projects/customer-address-postgres-json/pom.xml +++ b/e2e/src/test/resources/projects/customer-address-postgres-json/pom.xml @@ -170,7 +170,7 @@ true none false - true + false true true true diff --git a/e2e/src/test/resources/projects/customer-address-relational/pom.xml b/e2e/src/test/resources/projects/customer-address-relational/pom.xml index 65c04ea7..b033f288 100644 --- a/e2e/src/test/resources/projects/customer-address-relational/pom.xml +++ b/e2e/src/test/resources/projects/customer-address-relational/pom.xml @@ -166,7 +166,7 @@ true none false - true + false true true true diff --git a/e2e/src/test/resources/projects/online-food-delivery-mongo/modules/orders/pom.xml b/e2e/src/test/resources/projects/online-food-delivery-mongo/modules/orders/pom.xml index cd0df813..53874b9c 100644 --- a/e2e/src/test/resources/projects/online-food-delivery-mongo/modules/orders/pom.xml +++ b/e2e/src/test/resources/projects/online-food-delivery-mongo/modules/orders/pom.xml @@ -133,7 +133,7 @@ true none false - true + false true @@ -158,7 +158,7 @@ true none false - true + false true diff --git a/e2e/src/test/resources/projects/online-food-delivery-mongo/pom.xml b/e2e/src/test/resources/projects/online-food-delivery-mongo/pom.xml index 7029e79c..dd6c7c98 100644 --- a/e2e/src/test/resources/projects/online-food-delivery-mongo/pom.xml +++ b/e2e/src/test/resources/projects/online-food-delivery-mongo/pom.xml @@ -157,7 +157,7 @@ true none false - true + false true true true diff --git a/e2e/src/test/resources/projects/simple-domain-packaging/pom.xml b/e2e/src/test/resources/projects/simple-domain-packaging/pom.xml index 50d6137d..71deb556 100644 --- a/e2e/src/test/resources/projects/simple-domain-packaging/pom.xml +++ b/e2e/src/test/resources/projects/simple-domain-packaging/pom.xml @@ -166,7 +166,7 @@ true none false - true + false true true true diff --git a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs index 7cd79abe..9b015551 100644 --- a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs +++ b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs @@ -101,6 +101,7 @@ public class {{serviceName}}ApiController implements {{serviceName}}Api { } {{~/each}} +{{~#if useOptional~}} protected Pageable pageOf(Optional page, Optional limit, Optional> sort) { Sort sortOrder = sort.map(s -> Sort.by(s.stream().map(sortParam -> { String[] parts = sortParam.split(":"); @@ -110,4 +111,15 @@ public class {{serviceName}}ApiController implements {{serviceName}}Api { }).toList())).orElse(Sort.unsorted()); return PageRequest.of(page.orElse(0), limit.orElse(10), sortOrder); } +{{~else}} + protected Pageable pageOf(Integer page, Integer limit, List sort) { + Sort sortOrder = sort != null ? Sort.by(sort.stream().map(sortParam -> { + String[] parts = sortParam.split(":"); + String property = parts[0]; + Sort.Direction direction = parts.length > 1 ? Sort.Direction.fromString(parts[1]) : Sort.Direction.ASC; + return new Sort.Order(direction, property); + }).toList()) : Sort.unsorted(); + return PageRequest.of(page != null ? page : 0, limit != null ? limit : 10, sortOrder); + } +{{~/if}} } diff --git a/plugins/openapi-spring-webtestclient/src/test/resources/pom.xml b/plugins/openapi-spring-webtestclient/src/test/resources/pom.xml index fc5ae319..0047368e 100644 --- a/plugins/openapi-spring-webtestclient/src/test/resources/pom.xml +++ b/plugins/openapi-spring-webtestclient/src/test/resources/pom.xml @@ -137,7 +137,7 @@ none false - true + false true true false From 66017491a58968e948274946cd5e6113d242672d Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Mon, 2 Dec 2024 10:55:24 +0100 Subject: [PATCH 05/21] [e2e] adds project test for features `patch` and `naturalId` --- .../sdk/e2e/TestPatchAndNaturalIdProject.java | 105 ++++++++ .../patch-and-natural-id/docker-compose.yml | 16 ++ .../patch-and-natural-id.zdl | 69 +++++ .../projects/patch-and-natural-id/pom.xml | 244 ++++++++++++++++++ .../io/zenwave360/example/Application.java | 13 + .../example/config/DatabaseConfiguration.java | 84 ++++++ .../example/config/SecurityConfiguration.java | 49 ++++ .../config/SpringSecurityAuditorAware.java | 44 ++++ .../src/main/resources/application-local.yml | 1 + .../src/main/resources/application.yml | 63 +++++ .../src/test/resources/application-test.yml | 1 + 11 files changed, 689 insertions(+) create mode 100644 e2e/src/test/java/io/zenwave360/sdk/e2e/TestPatchAndNaturalIdProject.java create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/docker-compose.yml create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/pom.xml create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/Application.java create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/DatabaseConfiguration.java create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/SecurityConfiguration.java create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/SpringSecurityAuditorAware.java create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/src/main/resources/application-local.yml create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/src/main/resources/application.yml create mode 100644 e2e/src/test/resources/projects/patch-and-natural-id/src/test/resources/application-test.yml diff --git a/e2e/src/test/java/io/zenwave360/sdk/e2e/TestPatchAndNaturalIdProject.java b/e2e/src/test/java/io/zenwave360/sdk/e2e/TestPatchAndNaturalIdProject.java new file mode 100644 index 00000000..54fbcb2c --- /dev/null +++ b/e2e/src/test/java/io/zenwave360/sdk/e2e/TestPatchAndNaturalIdProject.java @@ -0,0 +1,105 @@ +package io.zenwave360.sdk.e2e; + +import io.zenwave360.sdk.MainGenerator; +import io.zenwave360.sdk.Plugin; +import io.zenwave360.sdk.options.DatabaseType; +import io.zenwave360.sdk.options.PersistenceType; +import io.zenwave360.sdk.options.ProgrammingStyle; +import io.zenwave360.sdk.plugins.BackendApplicationDefaultPlugin; +import io.zenwave360.sdk.plugins.OpenAPIControllersPlugin; +import io.zenwave360.sdk.plugins.ZDLToAsyncAPIPlugin; +import io.zenwave360.sdk.plugins.ZDLToOpenAPIPlugin; +import io.zenwave360.sdk.testutils.MavenCompiler; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import java.io.File; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class TestPatchAndNaturalIdProject { + private String basePackage = "io.zenwave360.example"; + + @Test + public void testCustomerAddressPostgresJson() throws Exception { + String sourceFolder = "src/test/resources/projects/patch-and-natural-id/"; + String targetFolder = "target/patch-and-natural-id/"; + String zdlFile = targetFolder + "/patch-and-natural-id.zdl"; + + // copy whole dir from sourceFolder to targetFolder + FileUtils.deleteDirectory(new File(targetFolder)); + FileUtils.forceMkdir(new File(targetFolder)); + FileUtils.copyDirectory(new File(sourceFolder), new File(targetFolder)); + Assertions.assertTrue(new File(targetFolder).exists()); + + Plugin plugin = null; + int exitCode = 0; + + plugin = new ZDLToOpenAPIPlugin() + .withZdlFile(zdlFile) + .withOption("idType", "integer") + .withOption("idTypeFormat", "int64") + .withOption("targetFile", "/src/main/resources/apis/openapi.yml") + .withTargetFolder(targetFolder); + new MainGenerator().generate(plugin); + + plugin = new ZDLToAsyncAPIPlugin() + .withZdlFile(zdlFile) + .withOption("asyncapiVersion", "v3") + .withOption("idType", "integer") + .withOption("idTypeFormat", "int64") + .withOption("targetFile", "/src/main/resources/apis/asyncapi.yml") + .withTargetFolder(targetFolder); + new MainGenerator().generate(plugin); + + plugin = new BackendApplicationDefaultPlugin() + .withZdlFile(zdlFile) + .withTargetFolder(targetFolder) + .withOption("basePackage", basePackage) + .withOption("persistence", PersistenceType.jpa) + .withOption("databaseType", DatabaseType.postgresql) + .withOption("style", ProgrammingStyle.imperative) + .withOption("useLombok", true) + .withOption("includeEmitEventsImplementation", true) + .withOption("forceOverwrite", true) + .withOption("haltOnFailFormatting", false); + + new MainGenerator().generate(plugin); + + TextUtils.replaceInFile(new File(targetFolder + "/src/main/java/io/zenwave360/example/core/implementation/mappers/EventsMapper.java"), + "io.zenwave360.example.core.outbound.events.dtos.Customer asCustomer\\(Customer customer\\);", + """ + @org.mapstruct.Mapping(target = "extraProperties", ignore = true) + io.zenwave360.example.core.outbound.events.dtos.Customer asCustomer(Customer customer); + """); + + exitCode = MavenCompiler.compile(new File(targetFolder)); + Assertions.assertEquals(0, exitCode); + + plugin = new OpenAPIControllersPlugin() + .withApiFile(targetFolder + "/src/main/resources/apis/openapi.yml") + .withTargetFolder(targetFolder) + .withOption("zdlFile", zdlFile) + .withOption("basePackage", basePackage) + .withOption("controllersPackage", "{{basePackage}}.adapters.web") + .withOption("openApiApiPackage", "{{basePackage}}.adapters.web") + .withOption("openApiModelPackage", "{{basePackage}}.adapters.web.model") + .withOption("openApiModelNameSuffix", "DTO") + .withOption("style", ProgrammingStyle.imperative) + .withOption("haltOnFailFormatting", false); + new MainGenerator().generate(plugin); + + TextUtils.replaceInFile(new File(targetFolder + "/src/main/java/io/zenwave360/example/adapters/web/mappers/CustomerDTOsMapper.java"), + "// request mappings", + """ + // request mappings + default Map map(Object value) { return new HashMap();} + """); + + exitCode = MavenCompiler.compile(new File(targetFolder)); + Assertions.assertEquals(0, exitCode); + } + +} diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/docker-compose.yml b/e2e/src/test/resources/projects/patch-and-natural-id/docker-compose.yml new file mode 100644 index 00000000..9fcc2a6e --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3.8' +services: + mariadb: + image: mariadb:10.7.1 + # volumes: + # - ~/volumes/mysql/:/var/lib/mysql/ + environment: + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=zenwave-playground + ports: + - 127.0.0.1:3306:3306 + command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4 --explicit_defaults_for_timestamp + kafka: + image: bashj79/kafka-kraft + ports: + - '9092:9092' diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl b/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl new file mode 100644 index 00000000..947a1e03 --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl @@ -0,0 +1,69 @@ +/** + * Sample ZenWave Model Definition. + * Use zenwave-scripts.zdl to generate your code from this model definition. + */ + +/** +* Customer entity +*/ +@aggregate +@auditing // adds auditing fields to the entity +entity Customer { + @naturalId + customerId Long required + name String required maxlength(254) /** Customer name */ + email String required maxlength(254) + /** Customer Addresses can be stored in a JSON column in the database. */ + @json addresses Address[] minlength(1) maxlength(5) { + street String required maxlength(254) + city String required maxlength(254) + } +} + +@auditing +entity PaymentMethod { + type PaymentMethodType required + cardNumber String required +} + +enum PaymentMethodType { VISA(1), MASTERCARD(2) } + +relationship OneToMany { + @eager + Customer{paymentMethods required maxlength(3)} to PaymentMethod{customer required} +} + +// you can create 'inputs' as dtos for your service methods, or use entities directly +input CustomerSearchCriteria { + name String + email String + city String + state String +} + +@rest("/customers") +service CustomerService for (Customer) { + @post + createCustomer(Customer) Customer withEvents CustomerEvent + @naturalId + @get("/{customerId}") + getCustomer(id) Customer? + @naturalId + @put("/{customerId}") + updateCustomer(id, Customer) Customer? withEvents CustomerEvent + @patch("/{id}") + patchCustomer(id, Customer) Customer? withEvents CustomerEvent + @delete("/{id}") + deleteCustomer(id) withEvents CustomerEvent + @get("/search") @paginated + searchCustomers(CustomerSearchCriteria) Customer[] +} + +@copy(Customer) +@asyncapi({ channel: "CustomersChannel", topic: "customers" }) +event CustomerEvent { + id Long + version Integer + // all fields from Customer are copied here, but not relationships + paymentMethods PaymentMethod[] +} diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/pom.xml b/e2e/src/test/resources/projects/patch-and-natural-id/pom.xml new file mode 100644 index 00000000..31507a84 --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/pom.xml @@ -0,0 +1,244 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.0 + + + io.zenwave360.example + zenwave-playground + 0.0.1-SNAPSHOT + ${project.groupId}:${project.artifactId} + ZenWave-SDK Examples Playground + + 17 + ${project.parent.version} + 2022.0.4 + + 1.7.0-SNAPSHOT + + 3.0.2 + 1.5.3.Final + 1.0.1 + 2.2.1.RELEASE + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.cloud + spring-cloud-starter-stream-kafka + + + org.springframework.cloud + spring-cloud-stream-schema + ${spring-cloud-stream-schema.version} + + + + org.projectlombok + lombok + true + + + jakarta.validation + jakarta.validation-api + ${jakarta.validation-api.version} + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + org.apache.commons + commons-lang3 + + + + org.mariadb.jdbc + mariadb-java-client + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.testcontainers + junit-jupiter + test + + + com.tngtech.archunit + archunit-junit5-api + ${archunit-junit5.version} + test + + + + + com.tngtech.archunit + archunit-junit5-engine + ${archunit-junit5.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.openapitools + openapi-generator-maven-plugin + 7.8.0 + + + + generate + + generate-sources + + ${project.basedir}/src/main/resources/apis/openapi.yml + spring + io.zenwave360.example.adapters.web + io.zenwave360.example.adapters.web.model + DTO + true + false + + Double=java.math.BigDecimal + + + true + none + false + false + true + true + true + false + false + + + + + + + io.github.zenwave360.zenwave-sdk + zenwave-sdk-maven-plugin + ${zenwave.version} + + ${pom.basedir}/src/main/resources/apis/asyncapi.yml + false + true + true + + + + + generate-asyncapi-dtos + generate-sources + + generate + + + jsonschema2pojo + + io.zenwave360.example.core.outbound.events.dtos + + true + true + + + + + + generate-asyncapi + generate-sources + + generate + + + spring-cloud-streams3 + + provider + + + io.zenwave360.example.core.outbound.events.dtos + io.zenwave360.example.core.outbound.events + io.zenwave360.example.adapters.commands + + + + + + + io.github.zenwave360.zenwave-sdk.plugins + asyncapi-spring-cloud-streams3 + ${zenwave.version} + + + io.github.zenwave360.zenwave-sdk.plugins + asyncapi-jsonschema2pojo + ${zenwave.version} + + + + + + + diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/Application.java b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/Application.java new file mode 100644 index 00000000..9e6f9c94 --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/Application.java @@ -0,0 +1,13 @@ +package io.zenwave360.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/DatabaseConfiguration.java b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/DatabaseConfiguration.java new file mode 100644 index 00000000..b76c0f8e --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/DatabaseConfiguration.java @@ -0,0 +1,84 @@ +package io.zenwave360.example.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.SQLException; + + +@Configuration +@EnableJpaRepositories({ "io.zenwave360.example.core.outbound.jpa" }) +@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") +@EnableTransactionManagement +public class DatabaseConfiguration { + + private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class); + + private final Environment env; + + public DatabaseConfiguration(Environment env) { + this.env = env; + } + + /** + * Open the TCP port for the H2 database, so it is available remotely. + * + * @return the H2 database TCP server. + * @throws SQLException if the server failed to start. + */ + @Bean(initMethod = "start", destroyMethod = "stop") + @Profile("dev") + public Object h2TCPServer() throws SQLException { + String port = getValidPortForH2(); + log.debug("H2 database is available on port {}", port); + return createServer(port); + } + + private Object createServer(String port) throws SQLException { + try { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Class serverClass = Class.forName("org.h2.tools.Server", true, loader); + Method createServer = serverClass.getMethod("createTcpServer", String[].class); + return createServer.invoke(null, new Object[]{new String[]{"-tcp", "-tcpAllowOthers", "-tcpPort", port}}); + + } catch (ClassNotFoundException | LinkageError e) { + throw new RuntimeException("Failed to load and initialize org.h2.tools.Server", e); + + } catch (SecurityException | NoSuchMethodException e) { + throw new RuntimeException("Failed to get method org.h2.tools.Server.createTcpServer()", e); + + } catch (IllegalAccessException | IllegalArgumentException e) { + throw new RuntimeException("Failed to invoke org.h2.tools.Server.createTcpServer()", e); + + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + if (t instanceof SQLException) { + throw (SQLException) t; + } + throw new RuntimeException("Unchecked exception in org.h2.tools.Server.createTcpServer()", t); + } + } + + private String getValidPortForH2() { + int port = Integer.parseInt(env.getProperty("server.port")); + if (port < 10000) { + port = 10000 + port; + } else { + if (port < 63536) { + port = port + 2000; + } else { + port = port - 2000; + } + } + return String.valueOf(port); + } +} diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/SecurityConfiguration.java b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/SecurityConfiguration.java new file mode 100644 index 00000000..107ef960 --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/SecurityConfiguration.java @@ -0,0 +1,49 @@ +package io.zenwave360.example.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfiguration { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf(CsrfConfigurer::disable) + .authorizeHttpRequests(auth -> + auth.anyRequest().authenticated() + ) + .cors(Customizer.withDefaults()) + .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) + .httpBasic(Customizer.withDefaults()); + // @formatter:on + return http.build(); + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowCredentials(true); + configuration.addAllowedHeader("Authorization"); + configuration.setAllowedOrigins(List.of("*")); + configuration.setAllowedMethods(List.of("*")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/SpringSecurityAuditorAware.java b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/SpringSecurityAuditorAware.java new file mode 100644 index 00000000..fceb72f2 --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/java/io/zenwave360/example/config/SpringSecurityAuditorAware.java @@ -0,0 +1,44 @@ +package io.zenwave360.example.config; + +import java.util.Optional; +import org.springframework.data.domain.AuditorAware; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +/** + * Implementation of {@link AuditorAware} based on Spring Security. + */ +@Component("springSecurityAuditorAware") +public class SpringSecurityAuditorAware implements AuditorAware { + + @Override + public Optional getCurrentAuditor() { + return Optional.of(getCurrentUserLogin().orElse("system")); + } + + /** + * Get the login of the current user. + * + * @return the login of the current user. + */ + public static Optional getCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication())); + } + + private static String extractPrincipal(Authentication authentication) { + if (authentication == null) { + return null; + } else if (authentication.getPrincipal() instanceof UserDetails) { + UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); + return springSecurityUser.getUsername(); + } else if (authentication.getPrincipal() instanceof String) { + return (String) authentication.getPrincipal(); + } + return null; + } + +} diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/src/main/resources/application-local.yml b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/resources/application-local.yml new file mode 100644 index 00000000..8cd70bcd --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/resources/application-local.yml @@ -0,0 +1 @@ +MONGODB_URI: mongodb://localhost:27017/customers diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/src/main/resources/application.yml b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/resources/application.yml new file mode 100644 index 00000000..ab7b07fa --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/src/main/resources/application.yml @@ -0,0 +1,63 @@ +logging: + level: + io.zenwave360.example: DEBUG + org.springframework.security: DEBUG +# org.apache.kafka: DEBUG +spring: + security: + user: + name: user + password: password + roles: USER + jpa.hibernate.ddl-auto: update + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mariadb://localhost:3306/zenwave-playground?useLegacyDatetimeCode=false&serverTimezone=UTC + username: root + password: + hikari: + poolName: Hikari + auto-commit: false + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + kafka: + bootstrap-servers: localhost:9092 + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + consumer: + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + cloud: + stream: + function: + definition: do-create-customer;on-create-customer-customer-event;on-create-customer-customer-created;on-create-customer-customer-created-failed;on-update-customer-customer-event;on-update-customer-customer-updated;on-delete-customer-customer-deleted + bindings: + on-create-customer-customer-event-in-0: + destination: customer.events + content-type: application/json + on-create-customer-customer-created-out-0: + destination: customer.events + content-type: application/json + on-create-customer-customer-created-failed-out-0: + destination: customer.events + content-type: application/json + on-update-customer-customer-event-out-0: + destination: customer.events + content-type: application/json + on-update-customer-customer-updated-out-0: + destination: customer.events + content-type: application/json + on-delete-customer-customer-deleted-out-0: + destination: customer.events + content-type: application/json + do-create-customer-in-0: + destination: customer.requests + content-type: application/json + dead-letter-queue-error-map: > + { + 'jakarta.validation.ValidationException': 'do-create-customer-validation-error-out-0', + 'java.lang.Exception': 'do-create-customer-error-out-0' + } + diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/src/test/resources/application-test.yml b/e2e/src/test/resources/projects/patch-and-natural-id/src/test/resources/application-test.yml new file mode 100644 index 00000000..8cd70bcd --- /dev/null +++ b/e2e/src/test/resources/projects/patch-and-natural-id/src/test/resources/application-test.yml @@ -0,0 +1 @@ +MONGODB_URI: mongodb://localhost:27017/customers From 2390f67e9a714ea8ddcf836d05a9b4dfc2be87e7 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Mon, 2 Dec 2024 10:56:25 +0100 Subject: [PATCH 06/21] [plugins/openapi-controllers] support for search GET parameters --- .../sdk/plugins/OpenAPIControllersGenerator.java | 14 ++++++++++---- .../java/web/mappers/ServiceDTOsMapper.java.hbs | 7 ++++++- .../java/web/mvc/ServiceApiController.java.hbs | 2 ++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java b/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java index 7a67ac1a..6d605bda 100644 --- a/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java +++ b/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java @@ -136,6 +136,8 @@ public List generate(Map contextModel) { responseDtoName = innerArrayDTO != null ? openApiModelNamePrefix + innerArrayDTO + openApiModelNameSuffix : responseDtoName; } + var methodParameters = methodParameters(operation); + serviceOperations.add(Maps.of( "operationId", operation.get("operationId"), "operation", operation, @@ -143,7 +145,7 @@ public List generate(Map contextModel) { "statusCode", statusCode(operation), "requestBodySchema", requestDto, "requestDtoName", requestDtoName, - "methodParameters", methodParameters(operation), + "methodParameters", methodParameters, "methodParameterInstances", methodParameterInstances(operation), "methodParameterPlaceholders", methodParameterPlaceholders(operation), "reqBodyVariableName", reqBodyVariableName(method, zdlModel), @@ -162,12 +164,16 @@ public List generate(Map contextModel) { "isResponsePaginated", isResponsePaginated )); - if(requestDto != null && inputType != null) { + if (requestDto != null && inputType != null) { var requestKey = format("%s_%s", requestDtoName, inputType); Maps.getOrCreateDefault(mapperRequestDtoEntity, requestKey, new HashMap<>()) .putAll(Map.of("requestDto", requestDtoName, "inputType", inputType)); + } else if (inputType != null && StringUtils.isNotBlank(methodParameters)) { + var requestKey = format("%s_%s", methodParameters, inputType); + Maps.getOrCreateDefault(mapperRequestDtoEntity, requestKey, new HashMap<>()) + .putAll(Map.of("methodParameters", methodParameters, "inputType", inputType)); } - if(responseSchemaName != null && outputType != null) { + if (responseSchemaName != null && outputType != null) { var responseKey = format("%s_%s_%s_%s", responseDtoName, outputType, isResponseArray, isResponsePaginated); Maps.getOrCreateDefault(mapperResponseDtoEntity, responseKey, new HashMap<>()) .putAll(Map.of("responseDto", responseDtoName, "responseEntityName", responseEntityName, "outputType", outputType, @@ -216,7 +222,7 @@ private String methodParameters(Map operation) { return StringUtils.join(methodParams, ", "); } - private Object methodParameterInstances(Map operation) { + private String methodParameterInstances(Map operation) { var methodParameters = methodParameters(operation); if(methodParameters.isEmpty()) { return ""; diff --git a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mappers/ServiceDTOsMapper.java.hbs b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mappers/ServiceDTOsMapper.java.hbs index 521dd92c..fede0f13 100644 --- a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mappers/ServiceDTOsMapper.java.hbs +++ b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mappers/ServiceDTOsMapper.java.hbs @@ -18,7 +18,12 @@ public interface {{serviceName}}DTOsMapper { // request mappings {{~#each mapperRequestDtoEntity as |entry|}} - {{inputType}} as{{inputType}}({{requestDto}} dto); + {{~#if requestDto}} + {{inputType}} as{{inputType}}({{requestDto}} dto); + {{~/if}} + {{~#if methodParameters}} + {{inputType}} as{{inputType}}({{{methodParameters}}}); + {{~/if}} {{~/each}} // response mappings diff --git a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs index 9b015551..a9ddacc1 100644 --- a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs +++ b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs @@ -69,6 +69,8 @@ public class {{serviceName}}ApiController implements {{serviceName}}Api { log.debug("REST request to {{operationId}}: {{methodParameterPlaceholders}}", {{methodParameterInstances}}); {{~#if requestBodySchema~}} var {{mappedInputVariable}} = mapper.as{{serviceMethodParameter}}({{reqBodyVariableName}}); + {{~else if serviceMethodParameter}} + var {{mappedInputVariable}} = mapper.as{{serviceMethodParameter}}({{methodParameterInstances}}); {{/if~}} {{~#if isResponsePaginated ~}} var {{{asInstanceName methodReturnType}}}Page = {{TODO}} {{asInstanceName serviceMethod.serviceName}}.{{serviceMethodCall}}; From 618abf16004f7b62850edf63694d24b1373d9598 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Tue, 3 Dec 2024 16:07:10 +0100 Subject: [PATCH 07/21] Revert "[plugins/zdl-to-openapi] adds support for patch method" This reverts commit 9774ecb2917c019d5212d9e5fdb0920139b025b4. --- plugins/zdl-to-openapi/README.md | 1 - .../sdk/plugins/ZDLToOpenAPIGenerator.java | 53 ++++++++----------- .../EntitiesToSchemasConverter.java | 26 +++------ .../io/zenwave360/sdk/zdl/ZDLHttpUtils.java | 7 ++- .../sdk/resources/zdl/customer-address.zdl | 3 -- 5 files changed, 31 insertions(+), 59 deletions(-) diff --git a/plugins/zdl-to-openapi/README.md b/plugins/zdl-to-openapi/README.md index ff5a87c7..1a592010 100644 --- a/plugins/zdl-to-openapi/README.md +++ b/plugins/zdl-to-openapi/README.md @@ -19,7 +19,6 @@ jbang zw -p io.zenwave360.sdk.plugins.ZDLToOpenAPIPlugin \ | **Option** | **Description** | **Type** | **Default** | **Values** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------------------------|------------| | `specFile` | Spec file to parse | String | | | -| `baseOpenapi` | OpenAPI file to use as base. Generated API will be merged with this file. | String | | | | `targetFolder` | Target folder to generate code to. If left empty, it will print to stdout. | File | | | | `targetFile` | Target file | String | openapi.yml | | | `title` | API Title | String | | | diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java index 677584a9..ec3c53e2 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java @@ -31,12 +31,6 @@ public class ZDLToOpenAPIGenerator implements Generator { @DocumentedOption(description = "API Title") public String title; - @DocumentedOption(description = "OpenAPI file to use as base. Generated API will be merged with this file.") - public String baseOpenapi; - - @DocumentedOption(description = "DTO Suffix used for schemas in PATCH operations") - public String dtoPatchSuffix = "Patch"; - @DocumentedOption(description = "Target file") public String targetFile = "openapi.yml"; @DocumentedOption(description = "Extension property referencing original zdl entity in components schemas (default: x-business-entity)") @@ -58,7 +52,6 @@ public class ZDLToOpenAPIGenerator implements Generator { "get", 200, "post", 201, "put", 200, - "patch", 200, "delete", 204 ); @@ -121,35 +114,16 @@ public List generate(Map contextModel) { JSONPath.set(oasSchemas, "components.schemas", schemas); EntitiesToSchemasConverter converter = new EntitiesToSchemasConverter().withIdType(idType, idTypeFormat).withZdlBusinessEntityProperty(zdlBusinessEntityProperty); + var methodsWithRest = JSONPath.get(zdlModel, "$.services[*].methods[*][?(@.options.get || @.options.post || @.options.put || @.options.delete || @.options.patch)]", Collections.emptyList()); List> entities = filterSchemasToInclude(zdlModel, methodsWithRest); - generateAndAddSchemas(entities, converter, zdlModel, schemas, listedEntities, paginatedEntities, ""); - - EntitiesToSchemasConverter converterForPatchOperation = new EntitiesToSchemasConverter().withIdType(idType, idTypeFormat).withUseNullableForAllFields(true, dtoPatchSuffix).withZdlBusinessEntityProperty(zdlBusinessEntityProperty); - var methodsWithPatch = JSONPath.get(zdlModel, "$.services[*].methods[*][?(@.options.patch)]", Collections.emptyList()); - List> entitiesForPatch = filterSchemasToInclude(zdlModel, methodsWithPatch); - generateAndAddSchemas(entitiesForPatch, converterForPatchOperation, zdlModel, schemas, listedEntities, paginatedEntities, dtoPatchSuffix); - - String openAPISchemasString = null; - try { - openAPISchemasString = mapper.writeValueAsString(oasSchemas); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - // remove first line - openAPISchemasString = openAPISchemasString.substring(openAPISchemasString.indexOf("\n") + 1); - - return List.of(generateTemplateOutput(contextModel, zdlToOpenAPITemplate, zdlModel, openAPISchemasString)); - } - - protected void generateAndAddSchemas(List> entities, EntitiesToSchemasConverter converter, Map zdlModel, Map schemas, List listedEntities, List paginatedEntities, String dtoPatchSuffix) { for (Map entity : entities) { String entityName = (String) entity.get("name"); Map openAPISchema = converter.convertToSchema(entity, zdlModel); - schemas.put(entityName + dtoPatchSuffix, openAPISchema); + schemas.put(entityName, openAPISchema); if(listedEntities.contains(entityName)) { - Map listSchema = Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName + dtoPatchSuffix)); + Map listSchema = Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName)); schemas.put(entityName + "List", listSchema); } @@ -160,10 +134,27 @@ protected void generateAndAddSchemas(List> entities, Entitie Map.of(zdlBusinessEntityPaginatedProperty, entityName), Map.of("properties", Map.of("content", - Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName + dtoPatchSuffix)))))); - schemas.put(entityName + dtoPatchSuffix + "Paginated", paginatedSchema); + Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName)))))); + schemas.put(entityName + "Paginated", paginatedSchema); } } + +// List> enums = JSONPath.get(zdlModel, "$.enums[*]", emptyList()); +// for (Map enumValue : enums) { +// Map enumSchema = converter.convertToSchema(enumValue, zdlModel); +// schemas.put((String) enumValue.get("name"), enumSchema); +// } + + String openAPISchemasString = null; + try { + openAPISchemasString = mapper.writeValueAsString(oasSchemas); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + // remove first line + openAPISchemasString = openAPISchemasString.substring(openAPISchemasString.indexOf("\n") + 1); + + return List.of(generateTemplateOutput(contextModel, zdlToOpenAPITemplate, zdlModel, openAPISchemasString)); } protected List> filterSchemasToInclude(Map model, List methodsWithCommands) { diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/EntitiesToSchemasConverter.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/EntitiesToSchemasConverter.java index e352c1e8..419082cc 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/EntitiesToSchemasConverter.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/EntitiesToSchemasConverter.java @@ -14,9 +14,6 @@ public class EntitiesToSchemasConverter { public String idType = "string"; public String idTypeFormat = null; - public boolean useNullableForAllFields = false; - public String dtoPatchSuffix = ""; - public boolean includeVersion = true; public EntitiesToSchemasConverter withIdType(String idType) { @@ -44,12 +41,6 @@ public EntitiesToSchemasConverter withZdlBusinessEntityProperty(String zdlBusine return this; } - public EntitiesToSchemasConverter withUseNullableForAllFields(boolean useNullableForAllFields, String dtoPatchSuffix) { - this.useNullableForAllFields = useNullableForAllFields; - this.dtoPatchSuffix = dtoPatchSuffix; - return this; - } - public Map convertToSchema(Map entityOrEnum, Map zdlModel) { boolean isEnum = entityOrEnum.get("values") != null; return isEnum ? convertEnumToSchema(entityOrEnum, zdlModel) : convertEntityToSchema(entityOrEnum, zdlModel); @@ -98,20 +89,15 @@ public Map convertEntityToSchema(Map entity, Map || JSONPath.get(zdlModel, "$.events." + field.get("type")) != null; if (isComplexType) { - property.put("$ref", "#/components/schemas/" + field.get("type") + dtoPatchSuffix); + property.put("$ref", "#/components/schemas/" + field.get("type")); } else { - property.putAll(schemaTypeAndFormat((String) field.get("type") + dtoPatchSuffix)); + property.putAll(schemaTypeAndFormat((String) field.get("type"))); } - if(useNullableForAllFields) { - property.put("nullable", true); - } else { - String required = JSONPath.get(field, "$.validations.required.value"); - if (required != null) { - requiredProperties.add((String) field.get("name")); - } + String required = JSONPath.get(field, "$.validations.required.value"); + if (required != null) { + requiredProperties.add((String) field.get("name")); } - String minlength = JSONPath.get(field, "$.validations.minlength.value"); if (minlength != null) { property.put("minLength", asNumber(minlength)); @@ -157,7 +143,7 @@ public Map convertEntityToSchema(Map entity, Map var readOnlyWarning = isAddRelationshipById ? "(read-only) " : ""; // TODO desc+$ref: property.put("description", readOnlyWarning + relationship.getOrDefault("comment", "")); } - property.put("$ref", "#/components/schemas/" + relationship.get("otherEntityName") + dtoPatchSuffix); + property.put("$ref", "#/components/schemas/" + relationship.get("otherEntityName")); if (isCollection) { property = Maps.of("type", "array", "items", property); } diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java index fcab82cc..71ecb8cb 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java @@ -108,12 +108,11 @@ public static String getRequestBodyType(Map method, Map apiModel public static Map getHttpOption(Map method) { var get = JSONPath.get(method, "$.options.get"); - var post = JSONPath.get(method, "$.options.post"); var put = JSONPath.get(method, "$.options.put"); - var patch = JSONPath.get(method, "$.options.patch"); + var post = JSONPath.get(method, "$.options.post"); var delete = JSONPath.get(method, "$.options.delete"); - var httpOptions = ObjectUtils.firstNonNull(get, put, post, patch, delete); - var httpMethod = get != null? "get" : put != null? "put" : post != null? "post" : delete != null? "delete" : patch != null? "patch" : null; + var httpOptions = ObjectUtils.firstNonNull(get, put, post, delete); + var httpMethod = get != null? "get" : put != null? "put" : post != null? "post" : delete != null? "delete" : null; if (httpMethod == null) { return null; } diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl index 0299a189..2d59a07c 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl @@ -93,9 +93,6 @@ service CustomerService for (Customer) { @asyncapi({ api: ThirdPartyAPI, operationId: "doUpdateCustomer"}) updateCustomer(id, CustomerInput) Customer? withEvents CustomerEvent CustomerUpdated /** update customer javadoc comment */ - @patch("/customers/{customerId}") - patchCustomer(id, CustomerInput) Customer? withEvents CustomerEvent CustomerUpdated /** update customer javadoc comment */ - @delete("/customers/{customerId}") deleteCustomer(id) withEvents CustomerDeleted From d88cdd44ce7bbb82f71b0bced5d6feb18b2823a5 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Tue, 3 Dec 2024 16:34:09 +0100 Subject: [PATCH 08/21] [plugins/zdl-to-openapi] adds support for patch method --- plugins/openapi-controllers/README.md | 2 +- .../sdk/plugins/ZDLToOpenAPIGenerator.java | 16 +++++++++++----- .../java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java | 7 ++++--- .../sdk/resources/zdl/customer-address.zdl | 3 +++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/plugins/openapi-controllers/README.md b/plugins/openapi-controllers/README.md index 9c4f820b..9fa33f50 100644 --- a/plugins/openapi-controllers/README.md +++ b/plugins/openapi-controllers/README.md @@ -46,7 +46,7 @@ Default options for https://openapi-generator.tech/docs/generators/spring/ have true none - true + false false true true diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java index ec3c53e2..753d716a 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java @@ -31,6 +31,9 @@ public class ZDLToOpenAPIGenerator implements Generator { @DocumentedOption(description = "API Title") public String title; + @DocumentedOption(description = "DTO Suffix used for schemas in PATCH operations") + public String dtoPatchSuffix = "Patch"; + @DocumentedOption(description = "Target file") public String targetFile = "openapi.yml"; @DocumentedOption(description = "Extension property referencing original zdl entity in components schemas (default: x-business-entity)") @@ -52,6 +55,7 @@ public class ZDLToOpenAPIGenerator implements Generator { "get", 200, "post", 201, "put", 200, + "patch", 200, "delete", 204 ); @@ -139,11 +143,13 @@ public List generate(Map contextModel) { } } -// List> enums = JSONPath.get(zdlModel, "$.enums[*]", emptyList()); -// for (Map enumValue : enums) { -// Map enumSchema = converter.convertToSchema(enumValue, zdlModel); -// schemas.put((String) enumValue.get("name"), enumSchema); -// } + var methodsWithPatch = JSONPath.get(zdlModel, "$.services[*].methods[*][?(@.options.patch)]", Collections.emptyList()); + List entitiesForPatch = methodsWithPatch.stream().map(method -> (String) method.get("parameter")).collect(Collectors.toList()); + for (String entityName : entitiesForPatch) { + if (entityName != null) { + schemas.put(entityName + dtoPatchSuffix, Map.of("allOf", List.of(Map.of("$ref", "#/components/schemas/" + entityName)))); + } + } String openAPISchemasString = null; try { diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java index 71ecb8cb..fcab82cc 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java @@ -108,11 +108,12 @@ public static String getRequestBodyType(Map method, Map apiModel public static Map getHttpOption(Map method) { var get = JSONPath.get(method, "$.options.get"); - var put = JSONPath.get(method, "$.options.put"); var post = JSONPath.get(method, "$.options.post"); + var put = JSONPath.get(method, "$.options.put"); + var patch = JSONPath.get(method, "$.options.patch"); var delete = JSONPath.get(method, "$.options.delete"); - var httpOptions = ObjectUtils.firstNonNull(get, put, post, delete); - var httpMethod = get != null? "get" : put != null? "put" : post != null? "post" : delete != null? "delete" : null; + var httpOptions = ObjectUtils.firstNonNull(get, put, post, patch, delete); + var httpMethod = get != null? "get" : put != null? "put" : post != null? "post" : delete != null? "delete" : patch != null? "patch" : null; if (httpMethod == null) { return null; } diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl index 2d59a07c..0299a189 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl @@ -93,6 +93,9 @@ service CustomerService for (Customer) { @asyncapi({ api: ThirdPartyAPI, operationId: "doUpdateCustomer"}) updateCustomer(id, CustomerInput) Customer? withEvents CustomerEvent CustomerUpdated /** update customer javadoc comment */ + @patch("/customers/{customerId}") + patchCustomer(id, CustomerInput) Customer? withEvents CustomerEvent CustomerUpdated /** update customer javadoc comment */ + @delete("/customers/{customerId}") deleteCustomer(id) withEvents CustomerDeleted From 11ef2e5bf6ac7d7d69e443a735791c100f627e7d Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Tue, 3 Dec 2024 19:06:56 +0100 Subject: [PATCH 09/21] [plugins/zdl-to-openapi] adds support for patch method --- .../sdk/e2e/TestPatchAndNaturalIdProject.java | 2 +- .../projects/patch-and-natural-id/pom.xml | 7 +++++-- .../BackendApplicationDefaultHelpers.java | 3 +++ .../partials/jpa/entities-methodBody.hbs | 12 +++++++++++ .../partials/serviceMethodSignature.hbs | 2 +- .../plugins/OpenAPIControllersGenerator.java | 20 +++++++++++++++---- .../web/mvc/ServiceApiController.java.hbs | 3 ++- .../templating/CustomHandlebarsHelpers.java | 4 ++++ .../sdk/zdl/ZDLJavaSignatureUtils.java | 4 ++++ .../sdk/templates/HandlebarsEngineTest.java | 1 + .../sdk/templating/handlebars-test.hbs | 4 ++++ 11 files changed, 53 insertions(+), 9 deletions(-) diff --git a/e2e/src/test/java/io/zenwave360/sdk/e2e/TestPatchAndNaturalIdProject.java b/e2e/src/test/java/io/zenwave360/sdk/e2e/TestPatchAndNaturalIdProject.java index 54fbcb2c..98fa5a48 100644 --- a/e2e/src/test/java/io/zenwave360/sdk/e2e/TestPatchAndNaturalIdProject.java +++ b/e2e/src/test/java/io/zenwave360/sdk/e2e/TestPatchAndNaturalIdProject.java @@ -23,7 +23,7 @@ public class TestPatchAndNaturalIdProject { private String basePackage = "io.zenwave360.example"; @Test - public void testCustomerAddressPostgresJson() throws Exception { + public void testPatchAndNaturalIdProject() throws Exception { String sourceFolder = "src/test/resources/projects/patch-and-natural-id/"; String targetFolder = "target/patch-and-natural-id/"; String zdlFile = targetFolder + "/patch-and-natural-id.zdl"; diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/pom.xml b/e2e/src/test/resources/projects/patch-and-natural-id/pom.xml index 31507a84..e459c479 100644 --- a/e2e/src/test/resources/projects/patch-and-natural-id/pom.xml +++ b/e2e/src/test/resources/projects/patch-and-natural-id/pom.xml @@ -21,7 +21,7 @@ 1.7.0-SNAPSHOT 3.0.2 - 1.5.3.Final + 1.6.3 1.0.1 2.2.1.RELEASE @@ -144,7 +144,7 @@ org.openapitools openapi-generator-maven-plugin - 7.8.0 + 7.10.0 @@ -162,6 +162,9 @@ Double=java.math.BigDecimal + + CustomerPatch=java.util.Map + true none diff --git a/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java b/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java index 0313998b..8d5a20f9 100644 --- a/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java +++ b/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java @@ -133,6 +133,9 @@ public Collection findServiceInputs(Map service, Options options) { var inputDTOSuffix = (String) options.get("inputDTOSuffix"); Set inputs = new HashSet(); inputs.addAll(JSONPath.get(service, "$.methods[*].parameter")); + if(JSONPath.get(service, "$.methods[*][?(@.options.patch)]", List.of()).size() > 0) { + inputs.add("java.util.Map"); + } // inputs.addAll(JSONPath.get(zdl, "$.services[*][?('" + aggregateName + "' in @.aggregates)].methods[*].returnType")); // inputs.add(aggregateName + inputDTOSuffix); inputs = inputs.stream().filter(Objects::nonNull).collect(Collectors.toSet()); diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs index 6ad2ac27..87c0e57b 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs @@ -12,6 +12,18 @@ var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); return {{wrapWithMapper entity}}; {{~/if}} +{{!-- Optional patch(id, Map) --}} +{{~else if (and entity method.options.patch method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} + var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> { + return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); + }) + .map({{entity.instanceName}}Repository::save) + {{~#unless (eq entity.name method.returnType)}} + .map({{asInstanceName service.name}}Mapper::as{{returnType}}) + {{~/unless}} + ; + {{~> (partial '../withEvents')}} + return {{entity.instanceName}}; {{!-- Optional update(id, Entity) --}} {{~else if (and entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> { diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/serviceMethodSignature.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/serviceMethodSignature.hbs index edad9d64..974b8b20 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/serviceMethodSignature.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/serviceMethodSignature.hbs @@ -1,2 +1,2 @@ - public {{{returnType method}}} {{method.name}}({{methodParametersSignature method}}) + public {{{returnType method}}} {{method.name}}({{{methodParametersSignature method}}}) diff --git a/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java b/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java index 6d605bda..a35f864c 100644 --- a/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java +++ b/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java @@ -122,6 +122,7 @@ public List generate(Map contextModel) { for (Map operation : operationByServiceEntry.getValue()) { var method = ZDLFindUtils.findServiceMethod((String) operation.get("operationId"), zdlModel); + String httpVerb = JSONPath.get(operation, "x--httpVerb"); String requestDto = JSONPath.get(operation, "$.x--request-dto"); String requestDtoName = requestDto != null ? openApiModelNamePrefix + requestDto + openApiModelNameSuffix : null; String inputType = method != null? ZDLHttpUtils.getRequestBodyType(method, zdlModel) : "Entity"; @@ -141,6 +142,7 @@ public List generate(Map contextModel) { serviceOperations.add(Maps.of( "operationId", operation.get("operationId"), "operation", operation, + "httpMethod", httpVerb, "serviceMethod", method, "statusCode", statusCode(operation), "requestBodySchema", requestDto, @@ -164,7 +166,9 @@ public List generate(Map contextModel) { "isResponsePaginated", isResponsePaginated )); - if (requestDto != null && inputType != null) { + if ("patch".equals(httpVerb)) { + // skip mapper for Map input + } else if (requestDto != null && inputType != null) { var requestKey = format("%s_%s", requestDtoName, inputType); Maps.getOrCreateDefault(mapperRequestDtoEntity, requestKey, new HashMap<>()) .putAll(Map.of("requestDto", requestDtoName, "inputType", inputType)); @@ -216,8 +220,12 @@ private String methodParameters(Map operation) { return javaType + " " + name; }).collect(Collectors.toList()); if (operation.containsKey("x--request-dto")) { - var dto = (String) operation.get("x--request-dto"); - methodParams.add(format("%s%s%s %s", openApiModelNamePrefix, dto, openApiModelNameSuffix, "reqBody")); + if("patch".equals(JSONPath.get(operation, "x--httpVerb"))) { + methodParams.add("Map input"); + } else { + var dto = (String) operation.get("x--request-dto"); + methodParams.add(format("%s%s%s %s", openApiModelNamePrefix, dto, openApiModelNameSuffix, "reqBody")); + } } return StringUtils.join(methodParams, ", "); } @@ -351,7 +359,11 @@ public List generateTemplateOutput(Map contextMo return javaType + " " + name + " = null;"; }).collect(Collectors.toList()); if (operation.containsKey("x--request-dto")) { - methodParams.add(format("%s%s%s %s = null;", openApiModelNamePrefix, operation.get("x--request-dto"), openApiModelNameSuffix, "reqBody")); + if("patch".equals(operation.get("x--httpVerb"))) { + methodParams.add("Map reqBody = null;"); + } else { + methodParams.add(format("%s%s%s %s = null;", openApiModelNamePrefix, operation.get("x--request-dto"), openApiModelNameSuffix, "reqBody")); + } } return StringUtils.join(methodParams, "\n"); } diff --git a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs index a9ddacc1..c75b45ac 100644 --- a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs +++ b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs @@ -67,7 +67,8 @@ public class {{serviceName}}ApiController implements {{serviceName}}Api { @Override public ResponseEntity<{{{responseEntityExpression}}}> {{operationId}}({{{methodParameters}}}) { log.debug("REST request to {{operationId}}: {{methodParameterPlaceholders}}", {{methodParameterInstances}}); - {{~#if requestBodySchema~}} + {{~#if (eq httpMethod 'patch')~}} + {{~else if requestBodySchema~}} var {{mappedInputVariable}} = mapper.as{{serviceMethodParameter}}({{reqBodyVariableName}}); {{~else if serviceMethodParameter}} var {{mappedInputVariable}} = mapper.as{{serviceMethodParameter}}({{methodParameterInstances}}); diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java index c3b1f364..f6803907 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java @@ -109,6 +109,10 @@ public static boolean eq(Object first, Options options) throws IOException { return StringUtils.equals(String.valueOf(first), String.valueOf(second)); } + public static boolean neq(Object first, Options options) throws IOException { + return !eq(first, options); + } + public static boolean startsWith(String first, Options options) throws IOException { String second = options.param(0); return StringUtils.startsWith(first, second); diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java index 0fedf3d7..2eca7bff 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java @@ -14,6 +14,10 @@ public static String javaType(Map field) { } public static String methodParameterType(Map method, Map zdl, String inputDTOSuffix) { + var isPatch = JSONPath.get(method, "options.patch") != null; + if(isPatch) { + return "Map"; + } var parameterName = (String) method.get("parameter"); var isEntity = JSONPath.get(zdl, "$.entities." + parameterName) != null; return String.format("%s%s", parameterName, isEntity? inputDTOSuffix : ""); diff --git a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/templates/HandlebarsEngineTest.java b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/templates/HandlebarsEngineTest.java index 0ecd6a66..797d072b 100644 --- a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/templates/HandlebarsEngineTest.java +++ b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/templates/HandlebarsEngineTest.java @@ -57,6 +57,7 @@ public void testHandlebarsEngine() throws IOException { Assertions.assertTrue(templateOutput.getContent().contains("ifTruthy false: false")); Assertions.assertTrue(templateOutput.getContent().contains("Inside if 1")); Assertions.assertTrue(templateOutput.getContent().contains("Inside else 2")); + Assertions.assertTrue(templateOutput.getContent().contains("Inside if 3")); Assertions.assertTrue(templateOutput.getContent().contains("path: 'api/v1/users'")); Assertions.assertTrue(templateOutput.getContent().contains("This is from partial")); Assertions.assertTrue(templateOutput.getContent().contains("Starts with: true")); diff --git a/zenwave-sdk-cli/src/test/resources/io/zenwave360/sdk/templating/handlebars-test.hbs b/zenwave-sdk-cli/src/test/resources/io/zenwave360/sdk/templating/handlebars-test.hbs index f2306e16..352d66df 100644 --- a/zenwave-sdk-cli/src/test/resources/io/zenwave360/sdk/templating/handlebars-test.hbs +++ b/zenwave-sdk-cli/src/test/resources/io/zenwave360/sdk/templating/handlebars-test.hbs @@ -35,6 +35,10 @@ or false: {{or "false" "false" "false"}} Inside else 2 {{/if}} +{{#if (neq var "not equals")}} + Inside if 3 +{{/if}} + path: '{{path 'api' 'v1' 'users'}}' {{> (partial 'partial')}} From 6a0729d22710d56de0ee87ad1104bf61d0497397 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Wed, 4 Dec 2024 16:57:24 +0100 Subject: [PATCH 10/21] [plugins/openapi-controllers] fix for inline parameters --- .../plugins/OpenAPIControllersGenerator.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java b/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java index a35f864c..a915157a 100644 --- a/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java +++ b/plugins/openapi-controllers/src/main/java/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator.java @@ -138,6 +138,7 @@ public List generate(Map contextModel) { } var methodParameters = methodParameters(operation); + var serviceMethodParameter = serviceMethodParameter(method, zdlModel); serviceOperations.add(Maps.of( "operationId", operation.get("operationId"), @@ -151,7 +152,7 @@ public List generate(Map contextModel) { "methodParameterInstances", methodParameterInstances(operation), "methodParameterPlaceholders", methodParameterPlaceholders(operation), "reqBodyVariableName", reqBodyVariableName(method, zdlModel), - "serviceMethodParameter", serviceMethodParameter(method, zdlModel), + "serviceMethodParameter", serviceMethodParameter, "serviceMethodCall", serviceMethodCall(method, operation, zdlModel), "mappedInputVariable", mappedInputVariable(method), "inputType", inputType, @@ -172,10 +173,10 @@ public List generate(Map contextModel) { var requestKey = format("%s_%s", requestDtoName, inputType); Maps.getOrCreateDefault(mapperRequestDtoEntity, requestKey, new HashMap<>()) .putAll(Map.of("requestDto", requestDtoName, "inputType", inputType)); - } else if (inputType != null && StringUtils.isNotBlank(methodParameters)) { + } else if (inputType != null && StringUtils.isNotBlank(serviceMethodParameter)) { var requestKey = format("%s_%s", methodParameters, inputType); Maps.getOrCreateDefault(mapperRequestDtoEntity, requestKey, new HashMap<>()) - .putAll(Map.of("methodParameters", methodParameters, "inputType", inputType)); + .putAll(Map.of("methodParameters", methodParameters, "inputType", serviceMethodParameter)); } if (responseSchemaName != null && outputType != null) { var responseKey = format("%s_%s_%s_%s", responseDtoName, outputType, isResponseArray, isResponsePaginated); @@ -272,7 +273,22 @@ private String reqBodyVariableName(Map serviceMethod, Map zdl) { private String serviceMethodParameter(Map method, Map zdlModel) { if(method == null) { return "Entity"; } - return ZDLHttpUtils.getRequestBodyType(method, zdlModel); + var methodParameterType = (String) method.get("parameter"); + var parameterEntity = JSONPath.get(zdlModel, "$.allEntitiesAndEnums." + methodParameterType); + if(parameterEntity == null) { + return null; + } + var isInline = JSONPath.get(parameterEntity, "$.options.inline", false); + if (isInline) { + var fields = JSONPath.get(parameterEntity, "$.fields", Map.of()); + for (Map field : fields.values()) { + if (JSONPath.get(field, "$.isComplexType", false)) { + return JSONPath.get(field, "$.type"); + } + } + return null; + } + return methodParameterType; } private String mappedInputVariable(Map method) { From 5f237e3d88697d2a644063da65459157794437d1 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Tue, 10 Dec 2024 20:08:04 +0100 Subject: [PATCH 11/21] =?UTF-8?q?[plugins/asyncapi-spring-cloud-streams3]?= =?UTF-8?q?=20adds=20support=20for=20Spring=20Modulith=20Events:=20outbox?= =?UTF-8?q?=20and=20listener.=20=F0=9F=9A=80=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/SpringCloudStreams3Generator.java | 5 +- .../consumer/imperative/Consumer.java.hbs | 7 ++ .../outbox/modulith/Producer.java.hbs | 87 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 plugins/asyncapi-spring-cloud-streams3/src/main/resources/io/zenwave360/sdk/plugins/SpringCloudStream3Generator/producer/outbox/modulith/Producer.java.hbs diff --git a/plugins/asyncapi-spring-cloud-streams3/src/main/java/io/zenwave360/sdk/plugins/SpringCloudStreams3Generator.java b/plugins/asyncapi-spring-cloud-streams3/src/main/java/io/zenwave360/sdk/plugins/SpringCloudStreams3Generator.java index 5e955f04..8f3d17d5 100644 --- a/plugins/asyncapi-spring-cloud-streams3/src/main/java/io/zenwave360/sdk/plugins/SpringCloudStreams3Generator.java +++ b/plugins/asyncapi-spring-cloud-streams3/src/main/java/io/zenwave360/sdk/plugins/SpringCloudStreams3Generator.java @@ -22,7 +22,7 @@ public class SpringCloudStreams3Generator extends AbstractAsyncapiGenerator { private Logger log = LoggerFactory.getLogger(getClass()); public enum TransactionalOutboxType { - none, mongodb, jdbc + none, mongodb, jdbc, modulith } @DocumentedOption(description = "Programming style") @@ -31,6 +31,9 @@ public enum TransactionalOutboxType { @DocumentedOption(description = "Transactional outbox type for message producers.") public TransactionalOutboxType transactionalOutbox = TransactionalOutboxType.none; + @DocumentedOption(description = "Include ApplicationEvent listener for consuming messages within the modulith.") + public boolean includeApplicationEventListener = false; + @DocumentedOption(description = "Generate only the producer interface and skip the implementation.") public boolean skipProducerImplementation = false; diff --git a/plugins/asyncapi-spring-cloud-streams3/src/main/resources/io/zenwave360/sdk/plugins/SpringCloudStream3Generator/consumer/imperative/Consumer.java.hbs b/plugins/asyncapi-spring-cloud-streams3/src/main/resources/io/zenwave360/sdk/plugins/SpringCloudStream3Generator/consumer/imperative/Consumer.java.hbs index e79ce812..9a5d52e8 100644 --- a/plugins/asyncapi-spring-cloud-streams3/src/main/resources/io/zenwave360/sdk/plugins/SpringCloudStream3Generator/consumer/imperative/Consumer.java.hbs +++ b/plugins/asyncapi-spring-cloud-streams3/src/main/resources/io/zenwave360/sdk/plugins/SpringCloudStream3Generator/consumer/imperative/Consumer.java.hbs @@ -52,6 +52,13 @@ public class {{consumerName operation.x--operationIdCamelCase}} implements Consu } {{~/if}} +{{~#if includeApplicationEventListener}} + @org.springframework.modulith.events.ApplicationModuleListener + public void on(Message<{{messageType operation}}> message) { + accept(message); + } +{{/if}} + @Override public void accept(Message<{{messageType operation}}> message) { log.debug("Received message: {}", message); diff --git a/plugins/asyncapi-spring-cloud-streams3/src/main/resources/io/zenwave360/sdk/plugins/SpringCloudStream3Generator/producer/outbox/modulith/Producer.java.hbs b/plugins/asyncapi-spring-cloud-streams3/src/main/resources/io/zenwave360/sdk/plugins/SpringCloudStream3Generator/producer/outbox/modulith/Producer.java.hbs new file mode 100644 index 00000000..9cbd67a6 --- /dev/null +++ b/plugins/asyncapi-spring-cloud-streams3/src/main/resources/io/zenwave360/sdk/plugins/SpringCloudStream3Generator/producer/outbox/modulith/Producer.java.hbs @@ -0,0 +1,87 @@ +package {{producerApiPackage}}; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; + +{{#if modelPackage}} +import {{modelPackage}}.*; +{{/if}} + +/** + * {{asyncapi.description}} + */ +@Component("{{bindingPrefix}}{{apiClassName serviceName operationRoleType}}") +@jakarta.annotation.Generated(value = "io.zenwave360.sdk.plugins.SpringCloudStreams3Plugin", date = "{{date}}") +public class {{apiClassName serviceName operationRoleType}} implements I{{apiClassName serviceName operationRoleType}} { + + protected Logger log = LoggerFactory.getLogger(getClass()); + + protected final ApplicationEventPublisher applicationEventPublisher; + protected final ApplicationContext applicationContext; +{{~#each operations as |operation|}} + public String {{operation.operationId}}BindingName = "{{bindingPrefix}}{{operation.x--operationIdKebabCase}}-out{{bindingSuffix}}"; +{{~/each}} + +{{~#if useEnterpriseEnvelope}} + public EnvelopeWrapper envelopeWrapper; +{{~/if}} + + public {{apiClassName serviceName operationRoleType}}(ApplicationEventPublisher applicationEventPublisher, ApplicationContext applicationContext) { + this.applicationEventPublisher = applicationEventPublisher; + this.applicationContext = applicationContext; + } + +{{~#if useEnterpriseEnvelope}} + @Autowired(required = false) + public void setEnvelopeWrapper(EnvelopeWrapper envelopeWrapper) { + this.envelopeWrapper = envelopeWrapper; + } +{{~/if}} + +{{~#each operations as |operation|}} + {{#each operation.x--messages as |message|}} + /** + * {{{operation.summary}}} + */ + public boolean {{operation.operationId}}{{methodSuffix message operation producer=true}}({{message.x--javaType}} payload, {{message.x--javaTypeSimpleName}}Headers headers) { + log.debug("Sending message (via applicationEventPublisher) to scs binding: {}", {{operation.operationId}}BindingName); + {{~#if (hasRuntimeHeaders message)}} + headers = headers != null ? headers : new {{message.x--javaTypeSimpleName}}Headers(); + processRuntimeHeaders(payload, headers, {{message.x--javaTypeSimpleName}}Headers._runtimeheaders); + {{~/if}} + headers.put("spring.cloud.stream.sendto.destination", {{operation.operationId}}BindingName); + Message message = MessageBuilder.createMessage({{#if (hasEnterpriseEnvelope operation)}}wrap(wrapNullPayload(payload)){{else}}wrapNullPayload(payload){{/if}}, new MessageHeaders(headers)); + applicationEventPublisher.publishEvent(message); + return true; + } + + {{/each}} +{{/each}} + +{{~#if useEnterpriseEnvelope}} + protected Object wrap(Object payload) { + if(envelopeWrapper != null) { + return envelopeWrapper.wrap(payload); + } + return payload; + } + + public interface EnvelopeWrapper { + public Object wrap(Object payload); + } +{{~/if}} + +{{> (partial '../../partials/wrapNullPayload')}} +{{> (partial '../../partials/runtime-headers')}} + +} From 141becca3428a633bc5ce45994ccf0524562ca84 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Wed, 11 Dec 2024 19:13:08 +0100 Subject: [PATCH 12/21] epic: adds `@naturalId` support for entities and service id parameter. --- .../patch-and-natural-id.zdl | 15 +++--- .../BackendApplicationDefaultHelpers.java | 46 +++++++++++++++++++ .../partials/jpa/entities-crud-methodBody.hbs | 18 +++++++- .../partials/jpa/entities-methodBody.hbs | 10 ++-- .../mongodb/entities-crud-methodBody.hbs | 17 ++++++- .../partials/mongodb/entities-methodBody.hbs | 8 ++-- .../jpa/imperative/EntityRepository.java.hbs | 11 ++++- .../imperative/EntityRepository.java.hbs | 2 + .../jpa/imperative/testMethodBody.hbs | 18 ++++---- .../mongodb/imperative/testMethodBody.hbs | 18 ++++---- .../EntityRepositoryInMemory.java.hbs | 9 ++++ .../EntityRepositoryInMemory.java.hbs | 9 ++++ .../sdk/plugins/PathsProcessor.java | 15 +++++- .../templating/CustomHandlebarsHelpers.java | 8 ++++ .../io/zenwave360/sdk/zdl/ZDLFindUtils.java | 4 ++ .../io/zenwave360/sdk/zdl/ZDLHttpUtils.java | 12 ++--- .../sdk/zdl/ZDLJavaSignatureUtils.java | 34 +++++++++++++- .../sdk/zdl/ZDLJavaSignatureUtilsTest.java | 17 +++++++ .../sdk/templating/handlebars-test.hbs | 5 ++ .../sdk/resources/zdl/natural-ids.zdl | 22 +++++++++ 20 files changed, 251 insertions(+), 47 deletions(-) create mode 100644 zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/natural-ids.zdl diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl b/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl index 947a1e03..ffc555a6 100644 --- a/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl +++ b/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl @@ -11,6 +11,9 @@ entity Customer { @naturalId customerId Long required + @naturalId + anotherId String required + name String required maxlength(254) /** Customer name */ email String required maxlength(254) /** Customer Addresses can be stored in a JSON column in the database. */ @@ -45,16 +48,16 @@ input CustomerSearchCriteria { service CustomerService for (Customer) { @post createCustomer(Customer) Customer withEvents CustomerEvent - @naturalId - @get("/{customerId}") + + @get("/{customerId}/{anotherId}") @naturalId(Customer) getCustomer(id) Customer? - @naturalId - @put("/{customerId}") + @put("/{customerId}/{anotherId}") @naturalId(Customer) updateCustomer(id, Customer) Customer? withEvents CustomerEvent - @patch("/{id}") + @patch("/{customerId}/{anotherId}") @naturalId(Customer) patchCustomer(id, Customer) Customer? withEvents CustomerEvent - @delete("/{id}") + @delete("/{customerId}/{anotherId}") @naturalId(Customer) deleteCustomer(id) withEvents CustomerEvent + @get("/search") @paginated searchCustomers(CustomerSearchCriteria) Customer[] } diff --git a/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java b/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java index 8d5a20f9..52006a6f 100644 --- a/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java +++ b/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java @@ -128,6 +128,52 @@ public String findEntityAggregate(String entityName, Options options) { return aggregateNames.isEmpty()? null : (String) aggregateNames.get(0); } + public List naturalIdFields(Map entity, Options options) { + return ZDLFindUtils.naturalIdFields(entity); + } + + public String naturalIdsRepoMethodSignature(Map entity, Options options) { + return ZDLJavaSignatureUtils.naturalIdsRepoMethodSignature(entity); + } + + public String naturalIdsRepoMethodCallSignature(Map entity, Options options) { + return ZDLJavaSignatureUtils.naturalIdsRepoMethodCallSignature(entity); + } + + public String findById(Map method, Options options) { + var zdl = options.get("zdl"); + var naturalIdEntity = JSONPath.get(method, "$.options.naturalId"); + if(naturalIdEntity != null) { + var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity); + return ZDLJavaSignatureUtils.naturalIdsRepoMethodCallSignature(entity); + } + return "findById(id)"; + } + + public String idFieldInitialization(Map method, Options options) { + var zdl = options.get("zdl"); + var naturalIdEntity = JSONPath.get(method, "$.options.naturalId"); + if(naturalIdEntity != null) { + var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity); + List fields = ZDLFindUtils.naturalIdFields(entity); + return fields.stream().map(field -> String.format("var %s = %s;", field.get("name"), ZDLJavaSignatureUtils.populateField(field))) + .collect(Collectors.joining("\n")); + } + return generator.getIdJavaType() + " id = null;"; + } + + public String idParamsCallSignature(Map method, Options options) { + var zdl = options.get("zdl"); + var naturalIdEntity = JSONPath.get(method, "$.options.naturalId"); + if(naturalIdEntity != null) { + var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity); + var fields = ZDLFindUtils.naturalIdFields(entity); + return ZDLJavaSignatureUtils.fieldsParamsCallSignature(fields); + } + return "id"; + } + + public Collection findServiceInputs(Map service, Options options) { var zdl = options.get("zdl"); var inputDTOSuffix = (String) options.get("inputDTOSuffix"); diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs index ca96634f..994f5adc 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs @@ -1,4 +1,7 @@ {{~assign 'entity' aggregateCommandsForMethod.entity }} +{{~assign 'hasNaturalId' (isTruthy method.options.naturalId) }} +{{~assign 'notHasNaturalId' (isFalsy hasNaturalId) }} +// hasNaturalId: {{hasNaturalId}}, notHasNaturalId: {{notHasNaturalId}} {{~#if (isCrudMethod 'create' method=method entity=entity )}} log.debug("[CRUD] Request to save {{entity.className}}: {}", input); var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); @@ -21,13 +24,24 @@ // TODO implement this search by criteria var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(pageable); return {{wrapWithMapper entity}}; -{{~else if (isCrudMethod 'get' method=method entity=entity )}} +{{~else if (and (isCrudMethod 'get' method=method entity=entity ) notHasNaturalId)}} log.debug("[CRUD] Request to get {{entity.className}} : {}", id); var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id); return {{wrapWithMapper entity}}; -{{~else if (isCrudMethod 'delete' method=method entity=entity )}} +{{~else if (and (isCrudMethod 'get' method=method entity=entity ) hasNaturalId)}} + {{{logMethodCall method}}} + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}}; + return {{wrapWithMapper entity}}; +{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) notHasNaturalId)}} log.debug("[CRUD] Request to delete {{entity.className}} : {}", id); {{entity.instanceName}}Repository.deleteById(id); {{~> (partial '../withEvents')}} +{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) hasNaturalId)}} + {{{logMethodCall method}}} + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}}; + if({{entity.instanceName}}.isPresent()) { + {{entity.instanceName}}Repository.delete({{entity.instanceName}}.get()); + {{~> (partial '../withEvents')}} + } {{~/if}} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs index 87c0e57b..dd31b6d5 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs @@ -14,7 +14,7 @@ {{~/if}} {{!-- Optional patch(id, Map) --}} {{~else if (and entity method.options.patch method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> { + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }) .map({{entity.instanceName}}Repository::save) @@ -26,7 +26,7 @@ return {{entity.instanceName}}; {{!-- Optional update(id, Entity) --}} {{~else if (and entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> { + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }) .map({{entity.instanceName}}Repository::save) @@ -38,7 +38,7 @@ return {{entity.instanceName}}; {{!-- Entity update(id, Entity) --}} {{~else if (and entity method.paramId method.parameter method.returnType)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> { + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }) .map({{entity.instanceName}}Repository::save) @@ -48,10 +48,10 @@ {{!-- Optional get(id) --}} {{~else if (and entity method.paramId method.returnType method.returnTypeIsOptional)}} {{~assign 'needMapping' (not (eq entity.name method.returnType))}} - return {{entity.instanceName}}Repository.findById(id){{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}}; + return {{entity.instanceName}}Repository.{{{findById method}}}{{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}}; {{!-- Entity get(id) --}} {{~else if (and entity method.paramId method.returnType)}} - return {{entity.instanceName}}Repository.findById(id); + return {{entity.instanceName}}Repository.{{{findById method}}}; {{!-- Optional get(MyEntity) --}} {{~else if (and entity method.parameter method.returnType method.returnTypeIsOptional)}} var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs index ed8d0c4f..b7f520db 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs @@ -1,4 +1,6 @@ {{assign 'entity' aggregateCommandsForMethod.entity }} +{{~assign 'hasNaturalId' (isTruthy method.options.naturalId) }} +{{~assign 'notHasNaturalId' (isFalsy hasNaturalId) }} {{~#if (isCrudMethod 'create' method=method entity=entity )}} log.debug("[CRUD] Request to save {{entity.className}}: {}", input); var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); @@ -20,13 +22,24 @@ // TODO implement this search by criteria var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(pageable); return {{wrapWithMapper entity}}; -{{~else if (isCrudMethod 'get' method=method entity=entity )}} +{{~else if (and (isCrudMethod 'get' method=method entity=entity ) notHasNaturalId)}} log.debug("[CRUD] Request to get {{entity.className}} : {}", id); var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id); return {{wrapWithMapper entity}}; -{{~else if (isCrudMethod 'delete' method=method entity=entity )}} +{{~else if (and (isCrudMethod 'get' method=method entity=entity ) hasNaturalId)}} + {{{logMethodCall method}}} + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}}; + return {{wrapWithMapper entity}}; +{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) notHasNaturalId)}} log.debug("[CRUD] Request to delete {{entity.className}} : {}", id); {{entity.instanceName}}Repository.deleteById(id); {{~> (partial '../withEvents')}} +{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) hasNaturalId)}} + {{{logMethodCall method}}} + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}}; + if({{entity.instanceName}}.isPresent()) { + {{entity.instanceName}}Repository.delete({{entity.instanceName}}.get()); + {{~> (partial '../withEvents')}} + } {{~/if}} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-methodBody.hbs index 46fff1c8..d0ca7d76 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-methodBody.hbs @@ -14,7 +14,7 @@ {{~/if}} {{!-- Optional update(id, Entity) --}} {{~else if (and entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> { + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }) .map({{entity.instanceName}}Repository::save) @@ -26,7 +26,7 @@ return {{entity.instanceName}}; {{!-- Entity update(id, Entity) --}} {{~else if (and entity method.paramId method.parameter method.returnType)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> { + var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }).map({{entity.instanceName}}Repository::save).orElseThrow(); {{~> (partial '../withEvents')}} @@ -34,10 +34,10 @@ {{!-- Optional get(id) --}} {{~else if (and entity method.paramId method.returnType method.returnTypeIsOptional)}} {{~assign 'needMapping' (not (eq entity.name method.returnType))}} - return {{entity.instanceName}}Repository.findById(id){{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}}; + return {{entity.instanceName}}Repository.{{{findById method}}}{{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}}; {{!-- Entity get(id) --}} {{~else if (and entity method.paramId method.returnType)}} - return {{entity.instanceName}}Repository.findById(id); + return {{entity.instanceName}}Repository.{{{findById method}}}; {{!-- Optional get(MyEntity) --}} {{~else if (and entity method.parameter method.returnType method.returnTypeIsOptional)}} var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/jpa/imperative/EntityRepository.java.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/jpa/imperative/EntityRepository.java.hbs index 6646d105..d8398f04 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/jpa/imperative/EntityRepository.java.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/jpa/imperative/EntityRepository.java.hbs @@ -9,4 +9,13 @@ import org.springframework.stereotype.Repository; */ @SuppressWarnings("unused") @Repository -public interface {{entity.className}}Repository extends JpaRepository<{{entity.className}}, {{idJavaType}}> {} +public interface {{entity.className}}Repository extends JpaRepository<{{entity.className}}, {{idJavaType}}> { + +{{~#if aggregate}} + default Optional<{{aggregate}}> find{{aggregate}}ById({{idJavaType}} id) { + return findById(id).map({{aggregate}}::new); + } +{{~/if}} + +{{~#if (naturalIdFields entity)}}{{{naturalIdsRepoMethodSignature entity}}};{{/if}} +} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/mongodb/imperative/EntityRepository.java.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/mongodb/imperative/EntityRepository.java.hbs index 5f97ae4c..4ebfd818 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/mongodb/imperative/EntityRepository.java.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/mongodb/imperative/EntityRepository.java.hbs @@ -23,4 +23,6 @@ public interface {{entity.className}}Repository extends MongoRepository<{{entity return findById(id).map({{aggregate}}::new); } {{~/if}} + +{{~#if (naturalIdFields entity)}}{{{naturalIdsRepoMethodSignature entity}}};{{/if}} } diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/core/implementation/jpa/imperative/testMethodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/core/implementation/jpa/imperative/testMethodBody.hbs index 249b53ba..4ac77a77 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/core/implementation/jpa/imperative/testMethodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/core/implementation/jpa/imperative/testMethodBody.hbs @@ -9,28 +9,28 @@ assertNotNull({{entity.instanceName}}.getId()); assertTrue({{entity.instanceName}}Repository.containsEntity({{entity.instanceName}})); {{~else if (isCrudMethod 'update' method=method entity=entity )}} - var id = 1L; // TODO fill id + {{{idFieldInitialization method}}} var input = new {{methodParameterType method}}(); // TODO fill input data {{~#each entity.fields as |field|}} // input.set{{capitalize field.name}}({{{populateField field}}}); {{~/each}} - assertTrue({{entity.instanceName}}Repository.containsKey(id)); - var {{entity.instanceName}} = {{serviceInstance}}.update{{entity.className}}(id, input); + // assertTrue({{entity.instanceName}}Repository.containsKey(id)); + var {{entity.instanceName}} = {{serviceInstance}}.update{{entity.className}}({{idParamsCallSignature method}}, input); assertTrue({{entity.instanceName}}.isPresent()); assertTrue({{entity.instanceName}}Repository.containsEntity({{entity.instanceName}}.get())); {{~else if (isCrudMethod 'list' method=method entity=entity) }} // var results = {{serviceInstance}}.list{{entity.classNamePlural}}(PageRequest.of(0, 10)); // assertNotNull(results); {{~else if (isCrudMethod 'get' method=method entity=entity )}} - var id = 1L; // TODO fill id - var {{entity.instanceName}} = {{serviceInstance}}.get{{entity.className}}(id); + {{{idFieldInitialization method}}} + var {{entity.instanceName}} = {{serviceInstance}}.get{{entity.className}}({{idParamsCallSignature method}}); assertTrue({{entity.instanceName}}.isPresent()); {{~else if (isCrudMethod 'delete' method=method entity=entity )}} - var id = 1L; // TODO fill id - assertTrue({{entity.instanceName}}Repository.containsKey(id)); - {{serviceInstance}}.delete{{entity.className}}(id); - assertFalse({{entity.instanceName}}Repository.containsKey(id)); + {{{idFieldInitialization method}}} + // assertTrue({{entity.instanceName}}Repository.containsKey(id)); + {{serviceInstance}}.delete{{entity.className}}({{idParamsCallSignature method}}); + // assertFalse({{entity.instanceName}}Repository.containsKey(id)); {{~else~}} // TODO: implement this test {{~/if}} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/core/implementation/mongodb/imperative/testMethodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/core/implementation/mongodb/imperative/testMethodBody.hbs index f844d1aa..4ac77a77 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/core/implementation/mongodb/imperative/testMethodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/core/implementation/mongodb/imperative/testMethodBody.hbs @@ -9,28 +9,28 @@ assertNotNull({{entity.instanceName}}.getId()); assertTrue({{entity.instanceName}}Repository.containsEntity({{entity.instanceName}})); {{~else if (isCrudMethod 'update' method=method entity=entity )}} - var id = "1"; // TODO fill id + {{{idFieldInitialization method}}} var input = new {{methodParameterType method}}(); // TODO fill input data {{~#each entity.fields as |field|}} // input.set{{capitalize field.name}}({{{populateField field}}}); {{~/each}} - assertTrue({{entity.instanceName}}Repository.containsKey(id)); - var {{entity.instanceName}} = {{serviceInstance}}.update{{entity.className}}(id, input); + // assertTrue({{entity.instanceName}}Repository.containsKey(id)); + var {{entity.instanceName}} = {{serviceInstance}}.update{{entity.className}}({{idParamsCallSignature method}}, input); assertTrue({{entity.instanceName}}.isPresent()); assertTrue({{entity.instanceName}}Repository.containsEntity({{entity.instanceName}}.get())); {{~else if (isCrudMethod 'list' method=method entity=entity) }} // var results = {{serviceInstance}}.list{{entity.classNamePlural}}(PageRequest.of(0, 10)); // assertNotNull(results); {{~else if (isCrudMethod 'get' method=method entity=entity )}} - var id = "1"; // TODO fill id - var {{entity.instanceName}} = {{serviceInstance}}.get{{entity.className}}(id); + {{{idFieldInitialization method}}} + var {{entity.instanceName}} = {{serviceInstance}}.get{{entity.className}}({{idParamsCallSignature method}}); assertTrue({{entity.instanceName}}.isPresent()); {{~else if (isCrudMethod 'delete' method=method entity=entity )}} - var id = "1"; // TODO fill id - assertTrue({{entity.instanceName}}Repository.containsKey(id)); - {{serviceInstance}}.delete{{entity.className}}(id); - assertFalse({{entity.instanceName}}Repository.containsKey(id)); + {{{idFieldInitialization method}}} + // assertTrue({{entity.instanceName}}Repository.containsKey(id)); + {{serviceInstance}}.delete{{entity.className}}({{idParamsCallSignature method}}); + // assertFalse({{entity.instanceName}}Repository.containsKey(id)); {{~else~}} // TODO: implement this test {{~/if}} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/infrastructure/jpa/imperative/inmemory/EntityRepositoryInMemory.java.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/infrastructure/jpa/imperative/inmemory/EntityRepositoryInMemory.java.hbs index a55e97ab..5ebfd8ed 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/infrastructure/jpa/imperative/inmemory/EntityRepositoryInMemory.java.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/infrastructure/jpa/imperative/inmemory/EntityRepositoryInMemory.java.hbs @@ -4,4 +4,13 @@ import {{entitiesPackage}}.*; import {{outboundRepositoryPackage}}.{{entity.className}}Repository; public class {{entity.className}}RepositoryInMemory extends InMemoryJpaRepository<{{entity.className}}> implements {{entity.className}}Repository { + +{{~#if (naturalIdFields entity)}} + @Override + public {{{naturalIdsRepoMethodSignature entity}}} { + return getEntities().values().stream().filter(e -> + {{#joinWithTemplate (naturalIdFields entity) delimiter='&&'}} isSameValue({{name}}, readField(e, "{{name}}")) {{/joinWithTemplate}} + ).findFirst(); + } +{{~/if}} } diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/infrastructure/mongodb/imperative/inmemory/EntityRepositoryInMemory.java.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/infrastructure/mongodb/imperative/inmemory/EntityRepositoryInMemory.java.hbs index 39ded41a..b97b8627 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/infrastructure/mongodb/imperative/inmemory/EntityRepositoryInMemory.java.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/test/java/infrastructure/mongodb/imperative/inmemory/EntityRepositoryInMemory.java.hbs @@ -4,4 +4,13 @@ import {{entitiesPackage}}.*; import {{outboundRepositoryPackage}}.{{entity.className}}Repository; public class {{entity.className}}RepositoryInMemory extends InMemoryMongodbRepository<{{entity.className}}> implements {{entity.className}}Repository { + +{{~#if (naturalIdFields entity)}} + @Override + public {{{naturalIdsRepoMethodSignature entity}}} { + return getEntities().values().stream().filter(e -> + {{#joinWithTemplate (naturalIdFields entity) delimiter='&&'}} isSameValue({{name}}, readField(e, "{{name}}")) {{/joinWithTemplate}} + ).findFirst(); + } +{{~/if}} } diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java index 9d715944..d472b71a 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java @@ -5,8 +5,11 @@ import io.zenwave360.sdk.processors.Processor; import io.zenwave360.sdk.utils.FluentMap; import io.zenwave360.sdk.utils.JSONPath; +import io.zenwave360.sdk.zdl.ZDLFindUtils; import io.zenwave360.sdk.zdl.ZDLHttpUtils; +import java.util.HashMap; +import java.util.List; import java.util.Map; public class PathsProcessor extends AbstractBaseProcessor implements Processor { @@ -36,12 +39,22 @@ public Map process(Map contextModel) { var paginated = JSONPath.get(method, "$.options.paginated"); var httpOption = ZDLHttpUtils.getHttpOption((Map) method); if(httpOption != null) { + var naturalId = JSONPath.get(method, "$.options.naturalId"); + var naturalIdEntity = (Map) JSONPath.get(zdl, "$.entities." + naturalId); + List naturalIdFields = ZDLFindUtils.naturalIdFields(naturalIdEntity); + Map naturalIdTypes = new HashMap(); + if(naturalIdFields != null) { + for (Map idField : naturalIdFields) { + naturalIdTypes.put(idField.get("name"), idField.get("type")); + } + } + var methodVerb = httpOption.get("httpMethod"); var methodPath = ZDLHttpUtils.getPathFromMethod(method); var path = basePath + methodPath; // var params = httpOption.get("params"); var pathParams = ZDLHttpUtils.getPathParams(path); - var pathParamsMap = ZDLHttpUtils.getPathParamsAsObject(method, idType, idTypeFormat); + var pathParamsMap = ZDLHttpUtils.getPathParamsAsObject(method, naturalIdTypes, idType, idTypeFormat); var queryParamsMap = ZDLHttpUtils.getQueryParamsAsObject(method, zdl); var hasParams = !pathParams.isEmpty() || !queryParamsMap.isEmpty() || paginated != null; paths.appendTo(path, (String) methodVerb, new FluentMap() diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java index f6803907..85396ab7 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java @@ -156,6 +156,14 @@ public static boolean and(Object first, Options options) throws IOException { return isTruthy(first) && Stream.of(options.params).allMatch(CustomHandlebarsHelpers::isTruthy); } + public static boolean isTruthy(Object value, Options options) throws IOException { + return isTruthy(value); + } + + public static boolean isFalsy(Object value, Options options) throws IOException { + return !isTruthy(value); + } + private static boolean isTruthy(Object value) { if (value == null) { return false; diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLFindUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLFindUtils.java index fcb5fe0b..fd110d0c 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLFindUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLFindUtils.java @@ -14,6 +14,10 @@ public static List findAllServiceFacingEntities(Map mode return ZDLFindUtils.findDependentEntities(model, serviceEntities); } + public static List naturalIdFields(Map entity) { + return JSONPath.get(entity, "$.fields[*][?(@.options.naturalId)]", List.of()); + } + public static List> methodsWithEvents(Map model) { return JSONPath.get(model, "$.services[*].methods[*][?(@.withEvents.length() > 0)]", Collections.>emptyList()); } diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java index fcab82cc..c69a07dd 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java @@ -5,10 +5,7 @@ import io.zenwave360.sdk.utils.Maps; import org.apache.commons.lang3.ObjectUtils; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -27,10 +24,11 @@ public static String getPathFromMethodOptions(Map httpOption) { return httpOptions instanceof String? (String) httpOptions : JSONPath.get(httpOptions, "$.path", ""); } - public static List> getPathParamsAsObject(Map method, String idType, String idTypeFormat) { + public static List> getPathParamsAsObject(Map method, Map naturalIdTypes, String idType, String idTypeFormat) { var path = getPathFromMethod(method); var httpOption = getHttpOption(method); - var params = JSONPath.get(httpOption, "$.httpOptions.params", Map.of()); + var params = new HashMap(naturalIdTypes); + params.putAll(JSONPath.get(httpOption, "$.httpOptions.params", Map.of())); return (List) getPathParams(path).stream().map(param -> { var type = params.getOrDefault(param, "String"); var typeAndFormat = EntitiesToSchemasConverter.schemaTypeAndFormat((String) type); @@ -124,6 +122,6 @@ public static Map getHttpOption(Map method) { else if(httpOptions instanceof Map) { optionsMap.putAll((Map) httpOptions); } - return Map.of("httpMethod", httpMethod, "httpOptions", optionsMap); + return Maps.of("httpMethod", httpMethod, "httpOptions", optionsMap); } } diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java index 2eca7bff..9446f829 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java @@ -2,6 +2,7 @@ import io.zenwave360.sdk.parsers.ZDLParser; import io.zenwave360.sdk.utils.JSONPath; +import io.zenwave360.sdk.utils.NamingUtils; import org.apache.commons.lang3.StringUtils; import java.util.*; @@ -23,10 +24,41 @@ public static String methodParameterType(Map method, Map zdl, String inputDTOSuf return String.format("%s%s", parameterName, isEntity? inputDTOSuffix : ""); } + public static String fieldsParamsSignature(List fields) { + return fields.stream() + .map(f -> String.format("%s %s", f.get("type"), f.get("name"))) + .collect(Collectors.joining(", ")); + } + + public static String fieldsParamsCallSignature(List fields) { + return fields.stream().map(f -> f.get("name").toString()).collect(Collectors.joining(", ")); + } + + public static String naturalIdsRepoMethodSignature(Map entity) { + List fields = ZDLFindUtils.naturalIdFields(entity); + var params = fieldsParamsSignature(fields); + var fieldNames = fields.stream().map(f -> NamingUtils.camelCase((String) f.get("name"))).toList(); + return String.format("java.util.Optional<%s> findBy%s(%s)", entity.get("name"), StringUtils.join(fieldNames, "And"), params); + } + + public static String naturalIdsRepoMethodCallSignature(Map entity) { + List fields = ZDLFindUtils.naturalIdFields(entity); + var params = fieldsParamsCallSignature(fields); + var fieldNames = fields.stream().map(f -> NamingUtils.camelCase((String) f.get("name"))).toList(); + return String.format("findBy%s(%s)", StringUtils.join(fieldNames, "And"), params); + } + + public static String methodParametersSignature(String idJavaType, Map method, Map zdl, String inputDTOSuffix) { var params = new ArrayList(); if(JSONPath.get(method, "paramId") != null) { - params.add(idJavaType + " id"); + var naturalIdEntity = JSONPath.get(method, "options.naturalId"); + if (naturalIdEntity != null) { + var fields = ZDLFindUtils.naturalIdFields(JSONPath.get(zdl, "$.entities." + naturalIdEntity)); + params.add(ZDLJavaSignatureUtils.fieldsParamsSignature(fields)); + } else { + params.add(idJavaType + " id"); + } } if(JSONPath.get(method, "parameter") != null) { params.addAll(methodInputSignature(method, zdl, inputDTOSuffix)); diff --git a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtilsTest.java b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtilsTest.java index eb80c231..2a5a0ef6 100644 --- a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtilsTest.java +++ b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtilsTest.java @@ -17,6 +17,23 @@ private Map loadZDL(String resource) throws IOException { return (Map) new ZDLProcessor().process(model).get("zdl"); } + @Test + void naturalIdsRepoMethodSignature() throws IOException { + var model = loadZDL("classpath:io/zenwave360/sdk/resources/zdl/natural-ids.zdl"); + var entity = JSONPath.get(model, "$.entities.Customer", Map.of()); + var signature = ZDLJavaSignatureUtils.naturalIdsRepoMethodSignature(entity); + Assertions.assertEquals("java.util.Optional findByCustomerIdAndAnotherId(Long customerId, String anotherId)", signature); + } + + @Test + void naturalIdsRepoMethodCallSignature() throws IOException { + var model = loadZDL("classpath:io/zenwave360/sdk/resources/zdl/natural-ids.zdl"); + var entity = JSONPath.get(model, "$.entities.Customer", Map.of()); + var signature = ZDLJavaSignatureUtils.naturalIdsRepoMethodCallSignature(entity); + Assertions.assertEquals("findByCustomerIdAndAnotherId(customerId, anotherId)", signature); + } + + @Test void methodParameterType() throws IOException { var model = loadZDL("classpath:io/zenwave360/sdk/resources/zdl/order-faults-attachments-model.zdl"); diff --git a/zenwave-sdk-cli/src/test/resources/io/zenwave360/sdk/templating/handlebars-test.hbs b/zenwave-sdk-cli/src/test/resources/io/zenwave360/sdk/templating/handlebars-test.hbs index 352d66df..30a14be8 100644 --- a/zenwave-sdk-cli/src/test/resources/io/zenwave360/sdk/templating/handlebars-test.hbs +++ b/zenwave-sdk-cli/src/test/resources/io/zenwave360/sdk/templating/handlebars-test.hbs @@ -14,6 +14,11 @@ jsonPath {{jsonPath entities "entity1.name"}} ifTruthy true: {{ifTruthy "true" "true" "false"}} ifTruthy false: {{ifTruthy "false" "true" "false"}} +isTruthy true: {{isTruthy "true" "true" "false"}} +isTruthy false: {{isTruthy "false" "true" "false"}} +isFalsy false: {{isFalsy "true" "true" "false"}} +isFalsy true: {{isFalsy "false" "true" "false"}} + and true: {{and "true" "true" "true"}} and false: {{and "true" "false" "true"}} or true: {{or "true" "true" "true"}} diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/natural-ids.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/natural-ids.zdl new file mode 100644 index 00000000..25ab8852 --- /dev/null +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/natural-ids.zdl @@ -0,0 +1,22 @@ +@aggregate +entity Customer { + @naturalId + customerId Long required + @naturalId + anotherId String required + + name String required maxlength(254) /** Customer name */ + +} + +@rest("/customers") +service CustomerService for (Customer) { + + @get("/{customerId}/{anotherId}") @naturalId(Customer) + getCustomer(id) Customer? + + @put("/{customerId}/{anotherId}") @naturalId(Customer) + updateCustomer(id, Customer) Customer? + +} + From 5a7c6e64d77419fbdfb33b4a927fc8d7db769139 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Thu, 12 Dec 2024 11:26:15 +0100 Subject: [PATCH 13/21] [plugins/zdl-to-openapi] adds support for `operationIdsToInclude` and `operationIdsToExclude` supporting ant-style wildcard matching. --- plugins/zdl-to-openapi/README.md | 26 +++++----- .../sdk/plugins/PathsProcessor.java | 24 ++++++++- .../sdk/plugins/ZDLToOpenAPIGenerator.java | 50 +++++++++++++++---- .../sdk/plugins/ZDLToOpenAPIPlugin.java | 2 +- .../sdk/plugins/PathsProcessorTest.java | 31 ++++++++++++ .../plugins/ZDLToOpenAPIGeneratorTest.java | 22 ++++++++ .../src/test/resources/inline-parameters.zdl | 37 ++++++++++++++ .../zenwave360/sdk/utils/AntStyleMatcher.java | 15 ++++++ .../io/zenwave360/sdk/zdl/ZDLHttpUtils.java | 13 +++-- 9 files changed, 191 insertions(+), 29 deletions(-) create mode 100644 plugins/zdl-to-openapi/src/test/java/io/zenwave360/sdk/plugins/PathsProcessorTest.java create mode 100644 plugins/zdl-to-openapi/src/test/resources/inline-parameters.zdl create mode 100644 zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/utils/AntStyleMatcher.java diff --git a/plugins/zdl-to-openapi/README.md b/plugins/zdl-to-openapi/README.md index 1a592010..6b660931 100644 --- a/plugins/zdl-to-openapi/README.md +++ b/plugins/zdl-to-openapi/README.md @@ -16,18 +16,20 @@ jbang zw -p io.zenwave360.sdk.plugins.ZDLToOpenAPIPlugin \ ## Options -| **Option** | **Description** | **Type** | **Default** | **Values** | -|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------------------------|------------| -| `specFile` | Spec file to parse | String | | | -| `targetFolder` | Target folder to generate code to. If left empty, it will print to stdout. | File | | | -| `targetFile` | Target file | String | openapi.yml | | -| `title` | API Title | String | | | -| `idType` | JsonSchema type for id fields and parameters. | String | string | | -| `idTypeFormat` | JsonSchema type format for id fields and parameters. | String | | | -| `zdlBusinessEntityProperty` | Extension property referencing original zdl entity in components schemas (default: x-business-entity) | String | x-business-entity | | -| `zdlBusinessEntityPaginatedProperty` | Extension property referencing original zdl entity in components schemas for paginated lists | String | x-business-entity-paginated | | -| `paginatedDtoItemsJsonPath` | JSONPath list to search for response DTO schemas for list or paginated results. Examples: '$.items' for lists or '$.properties..items' for paginated results. | List | [$.items, $.properties.content.items] | | -| `continueOnZdlError` | Continue even when ZDL contains fatal errors | boolean | true | | +| **Option** | **Description** | **Type** | **Default** | **Values** | +|-------------------------|------------------------------------------------------------------------------------------------------------------------------------|----------|-------------|------------| +| `zdlFile` | ZDL file to parse | String | | | +| `zdlFiles` | ZDL files to parse | List | [] | | +| `title` | API Title | String | | | +| `targetFolder` | Target folder to generate code to. If left empty, it will print to stdout. | File | | | +| `targetFile` | Target file | String | openapi.yml | | +| `idType` | JsonSchema type for id fields and parameters. | String | string | | +| `idTypeFormat` | JsonSchema type format for id fields and parameters. | String | | | +| `dtoPatchSuffix` | DTO Suffix used for schemas in PATCH operations | String | Patch | | +| `operationIdsToInclude` | Operation IDs to include. If empty, all operations will be included. (Supports Ant-style wildcards) | List | | | +| `operationIdsToExclude` | Operation IDs to exclude. If not empty it will be applied to the processed operationIds to include. (Supports Ant-style wildcards) | List | | | +| `continueOnZdlError` | Continue even when ZDL contains fatal errors | boolean | true | | + ## Getting Help diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java index d472b71a..e9023aed 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java @@ -3,6 +3,7 @@ import io.zenwave360.sdk.doc.DocumentedOption; import io.zenwave360.sdk.processors.AbstractBaseProcessor; import io.zenwave360.sdk.processors.Processor; +import io.zenwave360.sdk.utils.AntStyleMatcher; import io.zenwave360.sdk.utils.FluentMap; import io.zenwave360.sdk.utils.JSONPath; import io.zenwave360.sdk.zdl.ZDLFindUtils; @@ -20,6 +21,11 @@ public class PathsProcessor extends AbstractBaseProcessor implements Processor { @DocumentedOption(description = "JsonSchema type format for id fields and parameters.") public String idTypeFormat = null; + public List operationIdsToInclude; + + public List operationIdsToExclude; + + { targetProperty = "zdl"; } @@ -49,6 +55,10 @@ public Map process(Map contextModel) { } } + var operationId = (String) httpOption.getOrDefault("operationId", methodName); + if(!isIncludeOperation(operationId)) { + return; + } var methodVerb = httpOption.get("httpMethod"); var methodPath = ZDLHttpUtils.getPathFromMethod(method); var path = basePath + methodPath; @@ -58,7 +68,7 @@ public Map process(Map contextModel) { var queryParamsMap = ZDLHttpUtils.getQueryParamsAsObject(method, zdl); var hasParams = !pathParams.isEmpty() || !queryParamsMap.isEmpty() || paginated != null; paths.appendTo(path, (String) methodVerb, new FluentMap() - .with("operationId", methodName) + .with("operationId", operationId) .with("httpMethod", methodVerb) .with("tags", new String[]{(String) ((Map)service).get("name")}) .with("summary", method.get("javadoc")) @@ -82,4 +92,16 @@ public Map process(Map contextModel) { return contextModel; } + + protected boolean isIncludeOperation(String operationId) { + if (operationIdsToInclude != null && !operationIdsToInclude.isEmpty()) { + if (operationIdsToInclude.stream().noneMatch(include -> AntStyleMatcher.match(include, operationId))) { + return false; + } + } + if (operationIdsToExclude != null && !operationIdsToExclude.isEmpty()) { + return operationIdsToExclude.stream().noneMatch(exclude -> AntStyleMatcher.match(exclude, operationId)); + } + return true; + } } diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java index 753d716a..227ef2ce 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator.java @@ -11,6 +11,7 @@ import io.zenwave360.sdk.generators.AbstractZDLGenerator; import io.zenwave360.sdk.generators.EntitiesToSchemasConverter; import io.zenwave360.sdk.generators.Generator; +import io.zenwave360.sdk.utils.AntStyleMatcher; import io.zenwave360.sdk.zdl.ZDLFindUtils; import io.zenwave360.sdk.templating.HandlebarsEngine; import io.zenwave360.sdk.templating.OutputFormatType; @@ -36,14 +37,6 @@ public class ZDLToOpenAPIGenerator implements Generator { @DocumentedOption(description = "Target file") public String targetFile = "openapi.yml"; - @DocumentedOption(description = "Extension property referencing original zdl entity in components schemas (default: x-business-entity)") - public String zdlBusinessEntityProperty = "x-business-entity"; - - @DocumentedOption(description = "Extension property referencing original zdl entity in components schemas for paginated lists") - public String zdlBusinessEntityPaginatedProperty = "x-business-entity-paginated"; - - @DocumentedOption(description = "JSONPath list to search for response DTO schemas for list or paginated results. Examples: '$.items' for lists or '$.properties..items' for paginated results.") - public List paginatedDtoItemsJsonPath = List.of("$.items", "$.properties.content.items"); @DocumentedOption(description = "JsonSchema type for id fields and parameters.") public String idType = "string"; @@ -51,6 +44,21 @@ public class ZDLToOpenAPIGenerator implements Generator { @DocumentedOption(description = "JsonSchema type format for id fields and parameters.") public String idTypeFormat = null; + @DocumentedOption(description = "Operation IDs to include. If empty, all operations will be included. (Supports Ant-style wildcards)") + public List operationIdsToInclude; + + @DocumentedOption(description = "Operation IDs to exclude. If not empty it will be applied to the processed operationIds to include. (Supports Ant-style wildcards)") + public List operationIdsToExclude; + + // @DocumentedOption(description = "Extension property referencing original zdl entity in components schemas (default: x-business-entity)") + // public String zdlBusinessEntityProperty = "x-business-entity"; + // + // @DocumentedOption(description = "Extension property referencing original zdl entity in components schemas for paginated lists") + // public String zdlBusinessEntityPaginatedProperty = "x-business-entity-paginated"; + + // @DocumentedOption(description = "JSONPath list to search for response DTO schemas for list or paginated results. Examples: '$.items' for lists or '$.properties..items' for paginated results.") + // public List paginatedDtoItemsJsonPath = List.of("$.items", "$.properties.content.items"); + protected Map httpStatusCodes = Map.of( "get", 200, "post", 201, @@ -117,10 +125,14 @@ public List generate(Map contextModel) { Map schemas = new LinkedHashMap<>(); JSONPath.set(oasSchemas, "components.schemas", schemas); - EntitiesToSchemasConverter converter = new EntitiesToSchemasConverter().withIdType(idType, idTypeFormat).withZdlBusinessEntityProperty(zdlBusinessEntityProperty); + var paths = JSONPath.get(zdlModel, "$.services[*].paths", List.of()); + + EntitiesToSchemasConverter converter = new EntitiesToSchemasConverter().withIdType(idType, idTypeFormat); var methodsWithRest = JSONPath.get(zdlModel, "$.services[*].methods[*][?(@.options.get || @.options.post || @.options.put || @.options.delete || @.options.patch)]", Collections.emptyList()); + methodsWithRest = filterOperationsToInclude(methodsWithRest); List> entities = filterSchemasToInclude(zdlModel, methodsWithRest); + for (Map entity : entities) { String entityName = (String) entity.get("name"); Map openAPISchema = converter.convertToSchema(entity, zdlModel); @@ -135,7 +147,6 @@ public List generate(Map contextModel) { Map paginatedSchema = new HashMap<>(); paginatedSchema.put("allOf", List.of( Map.of("$ref", "#/components/schemas/Page"), - Map.of(zdlBusinessEntityPaginatedProperty, entityName), Map.of("properties", Map.of("content", Maps.of("type", "array", "items", Map.of("$ref", "#/components/schemas/" + entityName)))))); @@ -144,7 +155,9 @@ public List generate(Map contextModel) { } var methodsWithPatch = JSONPath.get(zdlModel, "$.services[*].methods[*][?(@.options.patch)]", Collections.emptyList()); + methodsWithPatch = filterOperationsToInclude(methodsWithPatch); List entitiesForPatch = methodsWithPatch.stream().map(method -> (String) method.get("parameter")).collect(Collectors.toList()); + for (String entityName : entitiesForPatch) { if (entityName != null) { schemas.put(entityName + dtoPatchSuffix, Map.of("allOf", List.of(Map.of("$ref", "#/components/schemas/" + entityName)))); @@ -163,6 +176,23 @@ public List generate(Map contextModel) { return List.of(generateTemplateOutput(contextModel, zdlToOpenAPITemplate, zdlModel, openAPISchemasString)); } + protected List filterOperationsToInclude(List methods) { + List includedMethods = methods; + if (operationIdsToInclude != null && !operationIdsToInclude.isEmpty()) { + includedMethods = methods.stream() + .filter(method -> operationIdsToInclude.stream() + .anyMatch(include -> AntStyleMatcher.match(include, (String) method.get("name")))) + .toList(); + } + if (operationIdsToExclude != null && !operationIdsToExclude.isEmpty()) { + includedMethods = includedMethods.stream() + .filter(method -> operationIdsToExclude.stream() + .noneMatch(exclude -> AntStyleMatcher.match(exclude, (String) method.get("name")))) + .toList(); + } + return includedMethods; + } + protected List> filterSchemasToInclude(Map model, List methodsWithCommands) { Map allEntitiesAndEnums = (Map) model.get("allEntitiesAndEnums"); Map relationships = (Map) model.get("relationships"); diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIPlugin.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIPlugin.java index d6d32c15..1aa01628 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIPlugin.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIPlugin.java @@ -7,7 +7,7 @@ import io.zenwave360.sdk.writers.TemplateFileWriter; import io.zenwave360.sdk.writers.TemplateStdoutWriter; -@DocumentedPlugin(value = "Generates a draft OpenAPI definitions from your ZDL entities and services.", shortCode = "zdl-to-openapi") +@DocumentedPlugin(value = "Generates a draft OpenAPI definitions from your ZDL entities and services.", shortCode = "zdl-to-openapi", hiddenOptions = {"apiFile, apiFiles"}) public class ZDLToOpenAPIPlugin extends Plugin { public ZDLToOpenAPIPlugin() { diff --git a/plugins/zdl-to-openapi/src/test/java/io/zenwave360/sdk/plugins/PathsProcessorTest.java b/plugins/zdl-to-openapi/src/test/java/io/zenwave360/sdk/plugins/PathsProcessorTest.java new file mode 100644 index 00000000..4165b90c --- /dev/null +++ b/plugins/zdl-to-openapi/src/test/java/io/zenwave360/sdk/plugins/PathsProcessorTest.java @@ -0,0 +1,31 @@ +package io.zenwave360.sdk.plugins; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.zenwave360.sdk.parsers.ZDLParser; +import io.zenwave360.sdk.processors.ZDLProcessor; +import io.zenwave360.sdk.templating.TemplateOutput; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +public class PathsProcessorTest { + + + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + private Map loadZDLModelFromResource(String resource) throws Exception { + Map model = new ZDLParser().withZdlFile(resource).parse(); + model = new ZDLProcessor().process(model); + return model; + } + + @Test + public void test_process_inline_parameters() throws Exception { + Map model = loadZDLModelFromResource("classpath:inline-parameters.zdl"); + model = new PathsProcessor().process(model); + List> paths = (List>) model.get("paths"); + } +} diff --git a/plugins/zdl-to-openapi/src/test/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGeneratorTest.java b/plugins/zdl-to-openapi/src/test/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGeneratorTest.java index e744cbbf..79c13fa4 100644 --- a/plugins/zdl-to-openapi/src/test/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGeneratorTest.java +++ b/plugins/zdl-to-openapi/src/test/java/io/zenwave360/sdk/plugins/ZDLToOpenAPIGeneratorTest.java @@ -35,6 +35,28 @@ public void test_customer_address_zdl_to_openapi() throws Exception { System.out.println(outputTemplates.get(0).getContent()); } + @Test + public void test_operationIdsToIncludeExclude() throws Exception { + Map model = loadZDLModelFromResource("classpath:io/zenwave360/sdk/resources/zdl/customer-address.zdl"); + ZDLToOpenAPIGenerator generator = new ZDLToOpenAPIGenerator(); + generator.operationIdsToInclude = List.of("getCustomer", "listCustomers"); + generator.operationIdsToExclude = List.of("getCustomer"); + var processor = new PathsProcessor(); + processor.operationIdsToInclude = generator.operationIdsToInclude; + processor.operationIdsToExclude = generator.operationIdsToExclude; + model = processor.process(model); + + List outputTemplates = generator.generate(model); + Assertions.assertEquals(1, outputTemplates.size()); + var apiText = outputTemplates.get(0).getContent(); + + System.out.println(apiText); + + Assertions.assertTrue(apiText.contains("listCustomers")); + Assertions.assertFalse(apiText.contains("getCustomer")); + Assertions.assertFalse(apiText.contains("updateCustomer")); + } + @Test public void test_order_faults_zdl_to_openapi() throws Exception { Map model = loadZDLModelFromResource("classpath:io/zenwave360/sdk/resources/zdl/order-faults-attachments-model.zdl"); diff --git a/plugins/zdl-to-openapi/src/test/resources/inline-parameters.zdl b/plugins/zdl-to-openapi/src/test/resources/inline-parameters.zdl new file mode 100644 index 00000000..3e40221c --- /dev/null +++ b/plugins/zdl-to-openapi/src/test/resources/inline-parameters.zdl @@ -0,0 +1,37 @@ +@aggregate +entity MetricRecord { + +} + + +@inline +input MetricsSummarySearchCriteria { + hisNumber String required maxlength(100) +} + +@inline +input MetricsSearchCriteria { + hisNumber String required maxlength(100) + dateFrom LocalDateTime + dateTo LocalDateTime +} + +output Metric { + +} + +@rest("/metrics") +service MedicalRecordService for (MetricRecord) { + // esto es para la webapp + @get("/{hisNumber}/daily") @paginated + getDailyMetrics(MetricsSearchCriteria) Metric[] + // esto es para la webapp + @get("/{hisNumber}/frequently") @paginated + getFrequestMetrics(MetricsSearchCriteria) Metric[] + + /** + * Summary metrics for mobile + */ + @get("/{hisNumber}/summary") + getMetricsSummary(MetricsSummarySearchCriteria) Metric[] +} diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/utils/AntStyleMatcher.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/utils/AntStyleMatcher.java new file mode 100644 index 00000000..03cd7a88 --- /dev/null +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/utils/AntStyleMatcher.java @@ -0,0 +1,15 @@ +package io.zenwave360.sdk.utils; + +import java.util.regex.Pattern; + +public class AntStyleMatcher { + + public static boolean match(String pattern, String filePath) { + // Convert Ant-style pattern to regex + String regex = pattern + .replace("**", ".*") + .replace("*", "[^/]*") + .replace("?", "."); + return Pattern.matches(regex, filePath); + } +} diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java index c69a07dd..5e7e2291 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLHttpUtils.java @@ -56,11 +56,14 @@ public static List> getQueryParamsAsObject(Map method, Map z } } } - return (List) params.entrySet().stream().filter(entry -> !pathParams.contains(entry.getKey())).map(entry -> { - var type = entry.getValue(); - var typeAndFormat = EntitiesToSchemasConverter.schemaTypeAndFormat((String) type); - return Maps.of("name", entry.getKey(),"type", typeAndFormat.get("type"), "format", typeAndFormat.get("format")); - }).toList(); + return (List) params.entrySet().stream() + .filter(entry -> !pathParams.contains(entry.getKey())) + .map(entry -> { + var type = entry.getValue(); + var typeAndFormat = EntitiesToSchemasConverter.schemaTypeAndFormat((String) type); + return Maps.of("name", entry.getKey(), "type", typeAndFormat.get("type"), "format", typeAndFormat.get("format")); + }) + .toList(); } public static List getPathParamsFromMethod(Map method) { var path = getPathFromMethod(method); From 7413cf860371f5316393ec0d7ac98c43883af6c4 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Fri, 13 Dec 2024 13:56:55 +0100 Subject: [PATCH 14/21] epic: refactors `@naturalId` to favor new native format in zdl. --- .../BackendApplicationDefaultHelpers.java | 19 +++---- .../main/java/core/domain/jpa/Entity.java.hbs | 1 + .../partials/jpa/entities-crud-methodBody.hbs | 3 +- .../mongodb/entities-crud-methodBody.hbs | 2 +- .../sdk/plugins/PathsProcessor.java | 6 +-- pom.xml | 2 +- .../sdk/processors/ZDLProcessor.java | 41 +++++++++++++- .../io/zenwave360/sdk/zdl/ZDLFindUtils.java | 54 ++----------------- .../sdk/zdl/ZDLJavaSignatureUtils.java | 12 +++-- .../sdk/processors/ZDLProcessorTest.java | 7 +++ .../zdl/customer-address-local-events.zdl | 4 +- .../customer-address-one-to-one-maps-id.zdl | 4 +- .../zdl/customer-address-problems.zdl | 4 +- .../zdl/customer-address-relational.zdl | 3 +- .../sdk/resources/zdl/customer-address.zdl | 4 +- .../sdk/resources/zdl/natural-ids.zdl | 12 +++-- 16 files changed, 94 insertions(+), 84 deletions(-) diff --git a/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java b/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java index 52006a6f..4cb01ea6 100644 --- a/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java +++ b/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java @@ -60,6 +60,7 @@ public Map findAggregateCommandsForMethod(Map method, Options options) { if(entity != null) { return Map.of("templateFile", "entities-methodBody", "entity", entity); } + return Map.of("templateFile", "entities-methodBody"); } return Map.of("templateFile", "aggregates-commands-methodBody", "aggregatesCommandsForMethod", aggregatesCommandsForMethod); } @@ -142,9 +143,9 @@ public String naturalIdsRepoMethodCallSignature(Map entity, Options options) { public String findById(Map method, Options options) { var zdl = options.get("zdl"); - var naturalIdEntity = JSONPath.get(method, "$.options.naturalId"); - if(naturalIdEntity != null) { - var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity); + var hasNaturalId = JSONPath.get(method, "$.naturalId", false); + if(hasNaturalId) { + var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + method.get("entity")); return ZDLJavaSignatureUtils.naturalIdsRepoMethodCallSignature(entity); } return "findById(id)"; @@ -152,9 +153,9 @@ public String findById(Map method, Options options) { public String idFieldInitialization(Map method, Options options) { var zdl = options.get("zdl"); - var naturalIdEntity = JSONPath.get(method, "$.options.naturalId"); - if(naturalIdEntity != null) { - var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity); + var hasNaturalId = JSONPath.get(method, "$.naturalId", false); + if(hasNaturalId) { + var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + method.get("entity")); List fields = ZDLFindUtils.naturalIdFields(entity); return fields.stream().map(field -> String.format("var %s = %s;", field.get("name"), ZDLJavaSignatureUtils.populateField(field))) .collect(Collectors.joining("\n")); @@ -164,9 +165,9 @@ public String idFieldInitialization(Map method, Options options) { public String idParamsCallSignature(Map method, Options options) { var zdl = options.get("zdl"); - var naturalIdEntity = JSONPath.get(method, "$.options.naturalId"); - if(naturalIdEntity != null) { - var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity); + var hasNaturalId = JSONPath.get(method, "$.naturalId", false); + if(hasNaturalId) { + var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + method.get("entity")); var fields = ZDLFindUtils.naturalIdFields(entity); return ZDLJavaSignatureUtils.fieldsParamsCallSignature(fields); } diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/domain/jpa/Entity.java.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/domain/jpa/Entity.java.hbs index 5a7e9461..11789094 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/domain/jpa/Entity.java.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/domain/jpa/Entity.java.hbs @@ -80,6 +80,7 @@ public {{abstractClass entity}} class {{entity.className}} {{addExtends entity}} {{/if}} {{~/if~}} {{~#if field.options.transient}} @javax.persistence.Transient {{/if}} + {{~#if field.options.naturalId}} @org.hibernate.annotations.NaturalId {{/if}} private {{{fieldType field}}} {{field.name}} {{{fieldTypeInitializer field}}}; {{/each}} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs index 994f5adc..77e60bb1 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs @@ -1,7 +1,6 @@ {{~assign 'entity' aggregateCommandsForMethod.entity }} -{{~assign 'hasNaturalId' (isTruthy method.options.naturalId) }} +{{~assign 'hasNaturalId' (isTruthy method.naturalId) }} {{~assign 'notHasNaturalId' (isFalsy hasNaturalId) }} -// hasNaturalId: {{hasNaturalId}}, notHasNaturalId: {{notHasNaturalId}} {{~#if (isCrudMethod 'create' method=method entity=entity )}} log.debug("[CRUD] Request to save {{entity.className}}: {}", input); var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs index b7f520db..92c3fe31 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs @@ -1,5 +1,5 @@ {{assign 'entity' aggregateCommandsForMethod.entity }} -{{~assign 'hasNaturalId' (isTruthy method.options.naturalId) }} +{{~assign 'hasNaturalId' (isTruthy method.naturalId) }} {{~assign 'notHasNaturalId' (isFalsy hasNaturalId) }} {{~#if (isCrudMethod 'create' method=method entity=entity )}} log.debug("[CRUD] Request to save {{entity.className}}: {}", input); diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java index e9023aed..289ec9e0 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java @@ -45,9 +45,9 @@ public Map process(Map contextModel) { var paginated = JSONPath.get(method, "$.options.paginated"); var httpOption = ZDLHttpUtils.getHttpOption((Map) method); if(httpOption != null) { - var naturalId = JSONPath.get(method, "$.options.naturalId"); - var naturalIdEntity = (Map) JSONPath.get(zdl, "$.entities." + naturalId); - List naturalIdFields = ZDLFindUtils.naturalIdFields(naturalIdEntity); + var methodEntityName = JSONPath.get(method, "$.options.entity"); + var methodEntity = (Map) JSONPath.get(zdl, "$.entities." + methodEntityName); + List naturalIdFields = ZDLFindUtils.naturalIdFields(methodEntity); Map naturalIdTypes = new HashMap(); if(naturalIdFields != null) { for (Map idField : naturalIdFields) { diff --git a/pom.xml b/pom.xml index 86997b8c..3493a25b 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 4.3.1 2.9.0 0.8.7 - 1.2.2 + 1.3.0-SNAPSHOT 19.2 1.7 2.38.0 diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/processors/ZDLProcessor.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/processors/ZDLProcessor.java index 25bc5a16..f90501f8 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/processors/ZDLProcessor.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/processors/ZDLProcessor.java @@ -3,11 +3,9 @@ import io.zenwave360.sdk.utils.JSONPath; import io.zenwave360.sdk.utils.Maps; import org.apache.commons.lang3.StringUtils; -import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -27,6 +25,7 @@ public Map process(Map contextModel) { processServiceName(zdlModel); processServiceAsyncMethods(zdlModel); + processMethodEntity(zdlModel); contextModel = new ZDL2JDLProcessor().process(contextModel); // FIXME: why here in the middle of the process? @@ -46,6 +45,44 @@ public Map process(Map contextModel) { return contextModel; } + public void processMethodEntity(Map zdlModel) { + var methods = JSONPath.get(zdlModel, "$.services[*].methods[*]", List.of()); + for (Map method : methods) { + var serviceAggregates = JSONPath.get(zdlModel, "$.services." + method.get("serviceName") + ".aggregates", List.of()); + String entity = null; + String aggregate = null; + if(serviceAggregates.size() == 1) { + entity = serviceAggregates.get(0); + } else { + var returnType = JSONPath.get(method, "$.returnType"); + if(serviceAggregates.contains(returnType)) { + entity = (String) returnType; + } else { + var entityForId = JSONPath.get(method, "$.options.entityForId"); + if(entityForId != null) { + entity = (String) entityForId; + } + } + } + + // check if entity is in fact and aggregate + var aggregateRoot = (String) JSONPath.get(zdlModel, "$.allEntitiesAndEnums." + entity + ".aggregateRoot"); + if(aggregateRoot != null) { + aggregate = entity; + entity = aggregateRoot; + } + + if(entity != null) { + method.put("entity", entity); + method.put("aggregate", aggregate); + } else { + if(method.get("paramId") != null) { + log.error("⚠️ We could not determine the 'entity' for the method {}. Please use `@entityForId(Entity)` annotation.", method.get("name")); + } + } + } + } + public void processServiceName(Map zdlModel) { var services = JSONPath.get(zdlModel, "$.services", Map.of()); for (Map.Entry service : services.entrySet()) { diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLFindUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLFindUtils.java index fd110d0c..67abb844 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLFindUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLFindUtils.java @@ -129,58 +129,14 @@ public static List methodEventsFlatList(Map method) { public static List> findAggregateCommandsForMethod(Map zdl, Map method) { var serviceAggregateNames = JSONPath.get(zdl, "$.services." + method.get("serviceName") + ".aggregates", List.of()); - var methodAnnotatedAggregates = JSONPath.get(method, "$.options.aggregates", List.of()); var returnType = JSONPath.get(method, "$.returnType"); - if (methodAnnotatedAggregates.isEmpty()) { - String aggregateName = null; - String entityName = null; - String crudMethod = null; - String commandName = null; - if(serviceAggregateNames.size() == 1) { - aggregateName = serviceAggregateNames.get(0); - entityName = JSONPath.get(zdl, "$.allEntitiesAndEnums." + aggregateName + ".aggregateRoot"); - if (entityName == null) { - // if entityName is null we need to swap entityName and aggregateName b/c the 'aggregateName' is actually just an entity - entityName = aggregateName; - aggregateName = null; - } - commandName = findAggregateCommand(zdl, method, aggregateName); - if (commandName == null) { - crudMethod = findCrudMethod(zdl, method, entityName); - if (crudMethod != null) { - aggregateName = null; - } - } - } else { - for (String serviceAggregate : serviceAggregateNames) { - aggregateName = serviceAggregate; - entityName = JSONPath.get(zdl, "$.allEntitiesAndEnums." + serviceAggregate + ".aggregateRoot"); - if (entityName == null) { - // if entityName is null we need to swap entityName and aggregateName b/c the 'aggregateName' is actually just an entity - entityName = aggregateName; - aggregateName = null; - } - if(Objects.equals(returnType, entityName) || Objects.equals(returnType, aggregateName)) { - commandName = findAggregateCommand(zdl, method, aggregateName); - if(commandName != null) { - break; - } - } - crudMethod = findCrudMethod(zdl, method, entityName); - if(crudMethod != null || Objects.equals(returnType, entityName)) { - aggregateName = null; - break; - } - } - } - if(commandName == null && Objects.equals(returnType, entityName)) { - aggregateName = null; // if we didn't find an aggregate's command, we don't want to return an aggregate - } + String aggregateName = (String) method.get("aggregate"); + String entityName = (String) method.get("entity"); + String commandName = findAggregateCommand(zdl, method, aggregateName); + String crudMethod = findCrudMethod(zdl, method, entityName); - return List.of(methodAggregateCommand(zdl, aggregateName, commandName, entityName, crudMethod)); - } - return null; + return List.of(methodAggregateCommand(zdl, aggregateName, commandName, entityName, crudMethod)); } private static Map methodAggregateCommand(Map zdl, String aggregateName, String commandName, String entityName, String crudMethod) { diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java index 9446f829..c68859fe 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/zdl/ZDLJavaSignatureUtils.java @@ -25,12 +25,18 @@ public static String methodParameterType(Map method, Map zdl, String inputDTOSuf } public static String fieldsParamsSignature(List fields) { + if(fields == null) { + return ""; + } return fields.stream() .map(f -> String.format("%s %s", f.get("type"), f.get("name"))) .collect(Collectors.joining(", ")); } public static String fieldsParamsCallSignature(List fields) { + if(fields == null) { + return ""; + } return fields.stream().map(f -> f.get("name").toString()).collect(Collectors.joining(", ")); } @@ -52,9 +58,9 @@ public static String naturalIdsRepoMethodCallSignature(Map entity) { public static String methodParametersSignature(String idJavaType, Map method, Map zdl, String inputDTOSuffix) { var params = new ArrayList(); if(JSONPath.get(method, "paramId") != null) { - var naturalIdEntity = JSONPath.get(method, "options.naturalId"); - if (naturalIdEntity != null) { - var fields = ZDLFindUtils.naturalIdFields(JSONPath.get(zdl, "$.entities." + naturalIdEntity)); + var hasNaturalId = JSONPath.get(method, "naturalId", false); + if (hasNaturalId) { + var fields = ZDLFindUtils.naturalIdFields(JSONPath.get(zdl, "$.entities." + method.get("entity"))); params.add(ZDLJavaSignatureUtils.fieldsParamsSignature(fields)); } else { params.add(idJavaType + " id"); diff --git a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/processors/ZDLProcessorTest.java b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/processors/ZDLProcessorTest.java index ea3383db..41064ebf 100644 --- a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/processors/ZDLProcessorTest.java +++ b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/processors/ZDLProcessorTest.java @@ -40,4 +40,11 @@ public void testProcessZDL_ProcessAsyncMethods() throws Exception { Assertions.assertEquals(2, syncMethods.size()); } + @Test + public void testProcessZDL_EntityForId() throws Exception { + var model = loadZDL("classpath:io/zenwave360/sdk/resources/zdl/natural-ids.zdl"); + var attachmentInput = JSONPath.get(model, "$.zdl.inputs.AddressInputCopy", Map.of()); + } + + } diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-local-events.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-local-events.zdl index 9bbeb180..3064386d 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-local-events.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-local-events.zdl @@ -1,8 +1,8 @@ +MAX_LENGTH=100 /** * Global javadoc comment */ - -MAX_LENGTH=100 +config {} apis { asyncapi(provider) CustomerAddressAPI { diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-one-to-one-maps-id.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-one-to-one-maps-id.zdl index c3aeeb06..3687f2b4 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-one-to-one-maps-id.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-one-to-one-maps-id.zdl @@ -1,8 +1,8 @@ +MAX_LENGTH=100 /** * Global javadoc comment */ - -MAX_LENGTH=100 +config {} /** * Customer javadoc comment diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-problems.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-problems.zdl index 14d0b55b..f5e6801f 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-problems.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-problems.zdl @@ -1,8 +1,8 @@ +MAX_LENGTH=100 /** * Global javadoc comment */ - -MAX_LENGTH=100 +config {} /** * Customer javadoc comment diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-relational.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-relational.zdl index 8b7deeb3..a1394408 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-relational.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address-relational.zdl @@ -1,8 +1,9 @@ +MAX_LENGTH=100 /** * Global javadoc comment */ +config {} -MAX_LENGTH=100 /** * Customer javadoc comment diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl index 0299a189..84830464 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/customer-address.zdl @@ -1,9 +1,7 @@ +MAX_LENGTH=100 /** * Global javadoc comment */ - -MAX_LENGTH=100 - config { basePackage "com.example.zenwave" plugins { diff --git a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/natural-ids.zdl b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/natural-ids.zdl index 25ab8852..5f602063 100644 --- a/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/natural-ids.zdl +++ b/zenwave-sdk-test-resources/src/main/resources/io/zenwave360/sdk/resources/zdl/natural-ids.zdl @@ -9,14 +9,18 @@ entity Customer { } +output CustomerProfile { + name String required maxlength(254) +} + @rest("/customers") service CustomerService for (Customer) { - @get("/{customerId}/{anotherId}") @naturalId(Customer) - getCustomer(id) Customer? + @get("/{customerId}/{anotherId}") + getCustomer(@natural id) Customer? - @put("/{customerId}/{anotherId}") @naturalId(Customer) - updateCustomer(id, Customer) Customer? + @put("/{customerId}/{anotherId}") + updateCustomer(@natural id, Customer) CustomerProfile? } From a62bc558b47c9ae5b58376282e144f56e4270064 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Fri, 13 Dec 2024 19:30:10 +0100 Subject: [PATCH 15/21] refactors HandlebarsEngine `context.model()` and `assign` helper. --- .../sdk/templating/CustomHandlebarsHelpers.java | 5 +++-- .../sdk/templating/HandlebarsEngine.java | 16 ++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java index 85396ab7..a7270008 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java @@ -186,15 +186,16 @@ public static Object uncapFirst(String text, Options options) throws IOException } public static Object assign(final String variableName, final Options options) throws IOException { + var model = (Map) options.context.model(); if (options.params.length == 1) { if (options.param(0) != null) { - options.context.combine(Map.of(variableName, options.param(0))); + model.put(variableName, options.param(0)); } else { } } else { CharSequence finalValue = options.apply(options.fn); - options.context.combine(Map.of(variableName, finalValue.toString().trim())); + model.put(variableName, finalValue.toString().trim()); } return null; } diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/HandlebarsEngine.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/HandlebarsEngine.java index 3704ec3f..197568b6 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/HandlebarsEngine.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/HandlebarsEngine.java @@ -47,12 +47,14 @@ public List processTemplates(Map model, List processTemplates(String modelPrefix, Map apiModel, List templateInputs) { - Context context = Context.newBuilder(this.context).build(); - if (modelPrefix != null) { - context.combine(modelPrefix, apiModel); - } else { - context.combine(apiModel); - } + var currentModel = new HashMap((Map)context.model()); + ((Map) context.model()).putAll(apiModel); +// Context context = Context.newBuilder(this.context).build(); +// if (modelPrefix != null) { +// context.combine(modelPrefix, apiModel); +// } else { +// context.combine(apiModel); +// } List templateOutputList = new ArrayList<>(); templateInputs.forEach(templateInput -> { if (templateInput.getSkip() == null || !Boolean.TRUE.equals(templateInput.getSkip().apply(apiModel))) { @@ -66,6 +68,8 @@ public List processTemplates(String modelPrefix, Map) context.model()).clear(); + ((Map) context.model()).putAll(currentModel); return templateOutputList; } } From 743032f7c8c73f45886d14ada7c7b69ffce13cd7 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Fri, 13 Dec 2024 19:31:01 +0100 Subject: [PATCH 16/21] epic: refactors `@naturalId` to favor new native format in zdl. --- .../plugins/BackendApplicationDefaultHelpers.java | 4 ++-- .../io/zenwave360/sdk/processors/ZDLProcessor.java | 10 ++++++++-- .../sdk/zdl/ZDLFindUtilsMethodAggregatesTest.java | 12 ++++++------ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java b/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java index 4cb01ea6..69d5c511 100644 --- a/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java +++ b/plugins/backend-application-default/src/main/java/io/zenwave360/sdk/plugins/BackendApplicationDefaultHelpers.java @@ -51,8 +51,8 @@ public Map findAggregateCommandsForMethod(Map method, Options options) { if(aggregate != null && command != null) { return Map.of("templateFile", "aggregates-commands-methodBody", "aggregatesCommandsForMethod", aggregatesCommandsForMethod); } - if(aggregate != null && crudMethod != null) { - return Map.of("templateFile", "aggregates-crud-methodBody", "aggregatesCommandsForMethod", aggregatesCommandsForMethod); + if (aggregate != null && crudMethod != null) { + // TODO: return Map.of("templateFile", "aggregates-crud-methodBody", "aggregatesCommandsForMethod", aggregatesCommandsForMethod); } if(entity != null && crudMethod != null) { return Map.of("templateFile", "entities-crud-methodBody", "entity", entity, "crudMethod", crudMethod); diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/processors/ZDLProcessor.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/processors/ZDLProcessor.java index f90501f8..afcb8411 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/processors/ZDLProcessor.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/processors/ZDLProcessor.java @@ -49,13 +49,14 @@ public void processMethodEntity(Map zdlModel) { var methods = JSONPath.get(zdlModel, "$.services[*].methods[*]", List.of()); for (Map method : methods) { var serviceAggregates = JSONPath.get(zdlModel, "$.services." + method.get("serviceName") + ".aggregates", List.of()); + var entitiesForServices = serviceAggregates.stream().map(e -> (String) JSONPath.get(zdlModel, "$.aggregates." + e + ".aggregateRoot")).toList(); String entity = null; String aggregate = null; if(serviceAggregates.size() == 1) { entity = serviceAggregates.get(0); } else { var returnType = JSONPath.get(method, "$.returnType"); - if(serviceAggregates.contains(returnType)) { + if(serviceAggregates.contains(returnType) || entitiesForServices.contains(returnType)) { entity = (String) returnType; } else { var entityForId = JSONPath.get(method, "$.options.entityForId"); @@ -66,11 +67,16 @@ public void processMethodEntity(Map zdlModel) { } // check if entity is in fact and aggregate - var aggregateRoot = (String) JSONPath.get(zdlModel, "$.allEntitiesAndEnums." + entity + ".aggregateRoot"); + var aggregateRoot = (String) JSONPath.get(zdlModel, "$.aggregates." + entity + ".aggregateRoot"); if(aggregateRoot != null) { aggregate = entity; entity = aggregateRoot; } + // check if entity is the root of an aggregate and this service is for the aggregate itself + var aggregateEntity = JSONPath.get(zdlModel, "$.aggregates[*][?(@.aggregateRoot == '" + entity + "')].name", List.of()); + if(aggregateEntity.size() == 1 && serviceAggregates.contains(aggregateEntity.get(0))) { + aggregate = aggregateEntity.get(0); + } if(entity != null) { method.put("entity", entity); diff --git a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/zdl/ZDLFindUtilsMethodAggregatesTest.java b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/zdl/ZDLFindUtilsMethodAggregatesTest.java index e024a7ce..86873f2d 100644 --- a/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/zdl/ZDLFindUtilsMethodAggregatesTest.java +++ b/zenwave-sdk-cli/src/test/java/io/zenwave360/sdk/zdl/ZDLFindUtilsMethodAggregatesTest.java @@ -17,7 +17,7 @@ public void should_return_single_service_entity() throws Exception { var aggregatesMapForMethod = aggregateCommandsForMethod("MyServiceForEntity", "someMethod"); Assertions.assertEquals(1, aggregatesMapForMethod.size()); - assertEquals(null, "$[0].aggregate", aggregatesMapForMethod); + assertEquals(null, "$[0].aggregate.name", aggregatesMapForMethod); assertEquals("MyEntity", "$[0].entity.name", aggregatesMapForMethod); } @@ -26,7 +26,7 @@ public void should_return_single_service_entity_crud() throws Exception { var aggregatesMapForMethod = aggregateCommandsForMethod("MyServiceForEntity", "createMyEntity"); Assertions.assertEquals(1, aggregatesMapForMethod.size()); - assertEquals(null, "$[0].aggregate", aggregatesMapForMethod); + assertEquals(null, "$[0].aggregate.name", aggregatesMapForMethod); assertEquals("MyEntity", "$[0].entity.name", aggregatesMapForMethod); assertEquals("createMyEntity", "$[0].crudMethod", aggregatesMapForMethod); } @@ -36,7 +36,7 @@ public void should_return_command_returnType() throws Exception { var aggregatesMapForMethod = aggregateCommandsForMethod("MyServiceForEntities", "shouldResolveByReturnType"); Assertions.assertEquals(1, aggregatesMapForMethod.size()); - assertEquals(null, "$[0].aggregate", aggregatesMapForMethod); + assertEquals(null, "$[0].aggregate.name", aggregatesMapForMethod); assertEquals("MyEntity2", "$[0].entity.name", aggregatesMapForMethod); } @@ -45,7 +45,7 @@ public void should_return_command_returnType_entity_crud() throws Exception { var aggregatesMapForMethod = aggregateCommandsForMethod("MyServiceForAggregate", "createMyEntity"); Assertions.assertEquals(1, aggregatesMapForMethod.size()); - assertEquals(null, "$[0].aggregate", aggregatesMapForMethod); + assertEquals("MyAggregate", "$[0].aggregate.name", aggregatesMapForMethod); assertEquals("MyEntity", "$[0].entity.name", aggregatesMapForMethod); assertEquals("createMyEntity", "$[0].crudMethod", aggregatesMapForMethod); } @@ -55,9 +55,9 @@ public void should_return_command_returnType_aggregate_crud() throws Exception { var aggregatesMapForMethod = aggregateCommandsForMethod("MyServiceForAggregate", "createMyAggregate"); Assertions.assertEquals(1, aggregatesMapForMethod.size()); - assertEquals(null, "$[0].aggregate.name", aggregatesMapForMethod); assertEquals(null, "$[0].crudMethod", aggregatesMapForMethod); assertEquals("MyEntity", "$[0].entity.name", aggregatesMapForMethod); + assertEquals("MyAggregate", "$[0].aggregate.name", aggregatesMapForMethod); } @Test @@ -65,7 +65,7 @@ public void should_return_command_returnType_aggregate() throws Exception { var aggregatesMapForMethod = aggregateCommandsForMethod("MyServiceForAggregates", "shouldResolveByReturnType"); Assertions.assertEquals(1, aggregatesMapForMethod.size()); - assertEquals(null, "$[0].aggregate", aggregatesMapForMethod); + assertEquals("MyAggregate2", "$[0].aggregate.name", aggregatesMapForMethod); assertEquals("MyEntity2", "$[0].entity.name", aggregatesMapForMethod); } From b74a7b832d64eb8eac6ffb8a1d9d0925e3d1fa1c Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Fri, 13 Dec 2024 19:41:51 +0100 Subject: [PATCH 17/21] fixes for new ZDL rules (global javadoc goes on top of config section) --- .../projects/patch-and-natural-id/patch-and-natural-id.zdl | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl b/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl index ffc555a6..41fd2b0d 100644 --- a/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl +++ b/e2e/src/test/resources/projects/patch-and-natural-id/patch-and-natural-id.zdl @@ -2,6 +2,7 @@ * Sample ZenWave Model Definition. * Use zenwave-scripts.zdl to generate your code from this model definition. */ +config {} /** * Customer entity From 35271c42a1a47850dc8eaf3d2f0ecb699012720f Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Mon, 16 Dec 2024 14:34:50 +0100 Subject: [PATCH 18/21] fix problem with assigning to the current context in handlebars partials --- .../partials/jpa/entities-crud-methodBody.hbs | 62 ++++++------ .../partials/jpa/entities-methodBody.hbs | 78 +++++++-------- .../mongodb/entities-crud-methodBody.hbs | 65 ++++++------- .../partials/mongodb/entities-methodBody.hbs | 94 +++++++++++-------- .../templating/CustomHandlebarsHelpers.java | 6 +- 5 files changed, 162 insertions(+), 143 deletions(-) diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs index 77e60bb1..bc48b6ba 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-crud-methodBody.hbs @@ -1,45 +1,45 @@ -{{~assign 'entity' aggregateCommandsForMethod.entity }} +{{~assign '_entity' aggregateCommandsForMethod.entity }} {{~assign 'hasNaturalId' (isTruthy method.naturalId) }} {{~assign 'notHasNaturalId' (isFalsy hasNaturalId) }} -{{~#if (isCrudMethod 'create' method=method entity=entity )}} - log.debug("[CRUD] Request to save {{entity.className}}: {}", input); - var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); - {{entity.instanceName}} = {{entity.instanceName}}Repository.save({{entity.instanceName}}); +{{~#if (isCrudMethod 'create' method=method entity=_entity )}} + log.debug("[CRUD] Request to save {{_entity.className}}: {}", input); + var {{_entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{_entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); + {{_entity.instanceName}} = {{_entity.instanceName}}Repository.save({{_entity.instanceName}}); // TODO: may need to reload the entity to fetch relationships 'mapped by id' {{~> (partial '../withEvents')}} - return {{wrapWithMapper entity}}; -{{~else if (isCrudMethod 'list' method=method entity=entity )}} + return {{wrapWithMapper _entity}}; +{{~else if (isCrudMethod 'list' method=method entity=_entity )}} {{~#if method.options.paginated}} - log.debug("[CRUD] Request list of {{entity.classNamePlural}}: {}", pageable); - var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(pageable); - return {{wrapWithMapper entity}}; + log.debug("[CRUD] Request list of {{_entity.classNamePlural}}: {}", pageable); + var {{_entity.instanceNamePlural}} = {{_entity.instanceName}}Repository.findAll(pageable); + return {{wrapWithMapper _entity}}; {{~else}} - log.debug("Request list of {{entity.classNamePlural}}"); - var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(); - return {{wrapWithMapper entity}}; + log.debug("Request list of {{_entity.classNamePlural}}"); + var {{_entity.instanceNamePlural}} = {{_entity.instanceName}}Repository.findAll(); + return {{wrapWithMapper _entity}}; {{~/if}} -{{~else if (isCrudMethod 'search' method=method entity=entity )}} - log.debug("[CRUD] Request to search {{entity.classNamePlural}}: {} - {}", input, pageable); +{{~else if (isCrudMethod 'search' method=method entity=_entity )}} + log.debug("[CRUD] Request to search {{_entity.classNamePlural}}: {} - {}", input, pageable); // TODO implement this search by criteria - var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(pageable); - return {{wrapWithMapper entity}}; -{{~else if (and (isCrudMethod 'get' method=method entity=entity ) notHasNaturalId)}} - log.debug("[CRUD] Request to get {{entity.className}} : {}", id); - var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id); - return {{wrapWithMapper entity}}; -{{~else if (and (isCrudMethod 'get' method=method entity=entity ) hasNaturalId)}} + var {{_entity.instanceNamePlural}} = {{_entity.instanceName}}Repository.findAll(pageable); + return {{wrapWithMapper _entity}}; +{{~else if (and (isCrudMethod 'get' method=method entity=_entity ) notHasNaturalId)}} + log.debug("[CRUD] Request to get {{_entity.className}} : {}", id); + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.findById(id); + return {{wrapWithMapper _entity}}; +{{~else if (and (isCrudMethod 'get' method=method entity=_entity ) hasNaturalId)}} {{{logMethodCall method}}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}}; - return {{wrapWithMapper entity}}; -{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) notHasNaturalId)}} - log.debug("[CRUD] Request to delete {{entity.className}} : {}", id); - {{entity.instanceName}}Repository.deleteById(id); + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature _entity}}}; + return {{wrapWithMapper _entity}}; +{{~else if (and (isCrudMethod 'delete' method=method entity=_entity ) notHasNaturalId)}} + log.debug("[CRUD] Request to delete {{_entity.className}} : {}", id); + {{_entity.instanceName}}Repository.deleteById(id); {{~> (partial '../withEvents')}} -{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) hasNaturalId)}} +{{~else if (and (isCrudMethod 'delete' method=method entity=_entity ) hasNaturalId)}} {{{logMethodCall method}}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}}; - if({{entity.instanceName}}.isPresent()) { - {{entity.instanceName}}Repository.delete({{entity.instanceName}}.get()); + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature _entity}}}; + if({{_entity.instanceName}}.isPresent()) { + {{_entity.instanceName}}Repository.delete({{_entity.instanceName}}.get()); {{~> (partial '../withEvents')}} } {{~/if}} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs index dd31b6d5..0c8bf5da 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpa/entities-methodBody.hbs @@ -1,78 +1,78 @@ -{{~assign "entity" aggregateCommandsForMethod.entity}} +{{~assign "_entity" aggregateCommandsForMethod.entity}} {{~assign "returnEntity" (methodReturnEntity method)}} {{{logMethodCall method}}} {{!-- @Async --}} {{~#if method.options.async}} {{#if method.returnType}}return CompletableFuture.completedFuture({{/if}} {{method.name}}Sync({{methodParametersCallSignature method}}){{#if method.returnType}}){{/if}}; {{!-- list search --}} -{{~else if (and entity method.returnType method.returnTypeIsArray)}} - {{~#if (eq entity.name returnEntity)}} - return {{entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); +{{~else if (and _entity method.returnType method.returnTypeIsArray)}} + {{~#if (eq _entity.name returnEntity)}} + return {{_entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); {{~else}} - var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); - return {{wrapWithMapper entity}}; + var {{_entity.instanceNamePlural}} = {{_entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); + return {{wrapWithMapper _entity}}; {{~/if}} {{!-- Optional patch(id, Map) --}} -{{~else if (and entity method.options.patch method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { - return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); +{{~else if (and _entity method.options.patch method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{findById method}}}.map(existing{{_entity.className}} -> { + return {{asInstanceName service.name}}Mapper.update(existing{{_entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }) - .map({{entity.instanceName}}Repository::save) - {{~#unless (eq entity.name method.returnType)}} + .map({{_entity.instanceName}}Repository::save) + {{~#unless (eq _entity.name method.returnType)}} .map({{asInstanceName service.name}}Mapper::as{{returnType}}) {{~/unless}} ; {{~> (partial '../withEvents')}} - return {{entity.instanceName}}; + return {{_entity.instanceName}}; {{!-- Optional update(id, Entity) --}} -{{~else if (and entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { - return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); +{{~else if (and _entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{findById method}}}.map(existing{{_entity.className}} -> { + return {{asInstanceName service.name}}Mapper.update(existing{{_entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }) - .map({{entity.instanceName}}Repository::save) - {{~#unless (eq entity.name method.returnType)}} + .map({{_entity.instanceName}}Repository::save) + {{~#unless (eq _entity.name method.returnType)}} .map({{asInstanceName service.name}}Mapper::as{{returnType}}) {{~/unless}} ; {{~> (partial '../withEvents')}} - return {{entity.instanceName}}; + return {{_entity.instanceName}}; {{!-- Entity update(id, Entity) --}} -{{~else if (and entity method.paramId method.parameter method.returnType)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { - return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); +{{~else if (and _entity method.paramId method.parameter method.returnType)}} + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{findById method}}}.map(existing{{_entity.className}} -> { + return {{asInstanceName service.name}}Mapper.update(existing{{_entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }) - .map({{entity.instanceName}}Repository::save) + .map({{_entity.instanceName}}Repository::save) .orElseThrow(); {{~> (partial '../withEvents')}} - return {{wrapWithMapper entity}}; + return {{wrapWithMapper _entity}}; {{!-- Optional get(id) --}} -{{~else if (and entity method.paramId method.returnType method.returnTypeIsOptional)}} - {{~assign 'needMapping' (not (eq entity.name method.returnType))}} - return {{entity.instanceName}}Repository.{{{findById method}}}{{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}}; +{{~else if (and _entity method.paramId method.returnType method.returnTypeIsOptional)}} + {{~assign 'needMapping' (not (eq _entity.name method.returnType))}} + return {{_entity.instanceName}}Repository.{{{findById method}}}{{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}}; {{!-- Entity get(id) --}} -{{~else if (and entity method.paramId method.returnType)}} - return {{entity.instanceName}}Repository.{{{findById method}}}; +{{~else if (and _entity method.paramId method.returnType)}} + return {{_entity.instanceName}}Repository.{{{findById method}}}; {{!-- Optional get(MyEntity) --}} -{{~else if (and entity method.parameter method.returnType method.returnTypeIsOptional)}} - var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); +{{~else if (and _entity method.parameter method.returnType method.returnTypeIsOptional)}} + var {{_entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{_entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); // TODO: implement this method {{~> (partial '../withEvents')}} - return Optional.ofNullable({{wrapWithMapper entity}}); + return Optional.ofNullable({{wrapWithMapper _entity}}); {{!-- Optional get(MyEntity) --}} -{{~else if (and entity method.parameter method.returnType)}} - var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); +{{~else if (and _entity method.parameter method.returnType)}} + var {{_entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{_entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); // TODO: implement this method {{~> (partial '../withEvents')}} - return {{wrapWithMapper entity}}; + return {{wrapWithMapper _entity}}; {{!-- Optional get() --}} -{{~else if (and entity method.returnType)}} - var {{entity.instanceName}} = new {{entity.className}}(); +{{~else if (and _entity method.returnType)}} + var {{_entity.instanceName}} = new {{_entity.className}}(); // TODO: implement this method {{~> (partial '../withEvents')}} - return {{wrapWithMapper entity}}; + return {{wrapWithMapper _entity}}; {{!-- void get() --}} -{{~else if (and entity)}} - var {{entity.instanceName}} = new {{entity.className}}(); +{{~else if (and _entity)}} + var {{_entity.instanceName}} = new {{_entity.className}}(); // TODO: implement this method {{~> (partial '../withEvents')}} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs index 92c3fe31..f7717540 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-crud-methodBody.hbs @@ -1,45 +1,46 @@ -{{assign 'entity' aggregateCommandsForMethod.entity }} +{{~assign '_entity' aggregateCommandsForMethod.entity }} {{~assign 'hasNaturalId' (isTruthy method.naturalId) }} {{~assign 'notHasNaturalId' (isFalsy hasNaturalId) }} -{{~#if (isCrudMethod 'create' method=method entity=entity )}} - log.debug("[CRUD] Request to save {{entity.className}}: {}", input); - var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); - {{entity.instanceName}} = {{entity.instanceName}}Repository.save({{entity.instanceName}}); +{{~#if (isCrudMethod 'create' method=method entity=_entity )}} + log.debug("[CRUD] Request to save {{_entity.className}}: {}", input); + var {{_entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{_entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); + {{_entity.instanceName}} = {{_entity.instanceName}}Repository.save({{_entity.instanceName}}); + // TODO: may need to reload the entity to fetch relationships 'mapped by id' {{~> (partial '../withEvents')}} - return {{wrapWithMapper entity}}; -{{~else if (isCrudMethod 'list' method=method entity=entity )}} + return {{wrapWithMapper _entity}}; +{{~else if (isCrudMethod 'list' method=method entity=_entity )}} {{~#if method.options.paginated}} - log.debug("[CRUD] Request list of {{entity.classNamePlural}}: {}", pageable); - var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(pageable); - return {{wrapWithMapper entity}}; + log.debug("[CRUD] Request list of {{_entity.classNamePlural}}: {}", pageable); + var {{_entity.instanceNamePlural}} = {{_entity.instanceName}}Repository.findAll(pageable); + return {{wrapWithMapper _entity}}; {{~else}} - log.debug("Request list of {{entity.classNamePlural}}"); - var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(); - return {{wrapWithMapper entity}}; + log.debug("Request list of {{_entity.classNamePlural}}"); + var {{_entity.instanceNamePlural}} = {{_entity.instanceName}}Repository.findAll(); + return {{wrapWithMapper _entity}}; {{~/if}} -{{~else if (isCrudMethod 'search' method=method entity=entity )}} - log.debug("[CRUD] Request to search {{entity.classNamePlural}}: {} - {}", input, pageable); +{{~else if (isCrudMethod 'search' method=method entity=_entity )}} + log.debug("[CRUD] Request to search {{_entity.classNamePlural}}: {} - {}", input, pageable); // TODO implement this search by criteria - var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(pageable); - return {{wrapWithMapper entity}}; -{{~else if (and (isCrudMethod 'get' method=method entity=entity ) notHasNaturalId)}} - log.debug("[CRUD] Request to get {{entity.className}} : {}", id); - var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id); - return {{wrapWithMapper entity}}; -{{~else if (and (isCrudMethod 'get' method=method entity=entity ) hasNaturalId)}} + var {{_entity.instanceNamePlural}} = {{_entity.instanceName}}Repository.findAll(pageable); + return {{wrapWithMapper _entity}}; +{{~else if (and (isCrudMethod 'get' method=method entity=_entity ) notHasNaturalId)}} + log.debug("[CRUD] Request to get {{_entity.className}} : {}", id); + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.findById(id); + return {{wrapWithMapper _entity}}; +{{~else if (and (isCrudMethod 'get' method=method entity=_entity ) hasNaturalId)}} {{{logMethodCall method}}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}}; - return {{wrapWithMapper entity}}; -{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) notHasNaturalId)}} - log.debug("[CRUD] Request to delete {{entity.className}} : {}", id); - {{entity.instanceName}}Repository.deleteById(id); + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature _entity}}}; + return {{wrapWithMapper _entity}}; +{{~else if (and (isCrudMethod 'delete' method=method entity=_entity ) notHasNaturalId)}} + log.debug("[CRUD] Request to delete {{_entity.className}} : {}", id); + {{_entity.instanceName}}Repository.deleteById(id); {{~> (partial '../withEvents')}} -{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) hasNaturalId)}} +{{~else if (and (isCrudMethod 'delete' method=method entity=_entity ) hasNaturalId)}} {{{logMethodCall method}}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}}; - if({{entity.instanceName}}.isPresent()) { - {{entity.instanceName}}Repository.delete({{entity.instanceName}}.get()); - {{~> (partial '../withEvents')}} + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature _entity}}}; + if({{_entity.instanceName}}.isPresent()) { + {{_entity.instanceName}}Repository.delete({{_entity.instanceName}}.get()); + {{~> (partial '../withEvents')}} } {{~/if}} diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-methodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-methodBody.hbs index d0ca7d76..5be449f5 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-methodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/mongodb/entities-methodBody.hbs @@ -1,72 +1,86 @@ -{{~assign "entity" aggregateCommandsForMethod.entity}} +{{~assign "_entity" aggregateCommandsForMethod.entity}} {{~assign "returnEntity" (methodReturnEntity method)}} - {{{logMethodCall method}}} +{{{logMethodCall method}}} {{!-- @Async --}} {{~#if method.options.async}} {{#if method.returnType}}return CompletableFuture.completedFuture({{/if}} {{method.name}}Sync({{methodParametersCallSignature method}}){{#if method.returnType}}){{/if}}; {{!-- list search --}} -{{~else if (and entity method.returnType method.returnTypeIsArray)}} - {{~#if (eq entity.name returnEntity)}} - return {{entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); +{{~else if (and _entity method.returnType method.returnTypeIsArray)}} + {{~#if (eq _entity.name returnEntity)}} + return {{_entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); {{~else}} - var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); - return {{wrapWithMapper entity}}; + var {{_entity.instanceNamePlural}} = {{_entity.instanceName}}Repository.findAll({{#if method.options.paginated}}pageable{{/if}}); + return {{wrapWithMapper _entity}}; {{~/if}} +{{!-- Optional patch(id, Map) --}} +{{~else if (and _entity method.options.patch method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{findById method}}}.map(existing{{_entity.className}} -> { + return {{asInstanceName service.name}}Mapper.update(existing{{_entity.className}}, {{{mapperInputCallSignature method.parameter}}}); + }) + .map({{_entity.instanceName}}Repository::save) + {{~#unless (eq _entity.name method.returnType)}} + .map({{asInstanceName service.name}}Mapper::as{{returnType}}) + {{~/unless}} + ; + {{~> (partial '../withEvents')}} + return {{_entity.instanceName}}; {{!-- Optional update(id, Entity) --}} -{{~else if (and entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { - return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); +{{~else if (and _entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}} + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{findById method}}}.map(existing{{_entity.className}} -> { + return {{asInstanceName service.name}}Mapper.update(existing{{_entity.className}}, {{{mapperInputCallSignature method.parameter}}}); }) - .map({{entity.instanceName}}Repository::save) - {{~#unless (eq entity.name method.returnType)}} - .map({{asInstanceName service.name}}Mapper::as{{returnType}}) + .map({{_entity.instanceName}}Repository::save) + {{~#unless (eq _entity.name method.returnType)}} + .map({{asInstanceName service.name}}Mapper::as{{returnType}}) {{~/unless}} ; {{~> (partial '../withEvents')}} - return {{entity.instanceName}}; + return {{_entity.instanceName}}; {{!-- Entity update(id, Entity) --}} -{{~else if (and entity method.paramId method.parameter method.returnType)}} - var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> { - return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}}); - }).map({{entity.instanceName}}Repository::save).orElseThrow(); +{{~else if (and _entity method.paramId method.parameter method.returnType)}} + var {{_entity.instanceName}} = {{_entity.instanceName}}Repository.{{{findById method}}}.map(existing{{_entity.className}} -> { + return {{asInstanceName service.name}}Mapper.update(existing{{_entity.className}}, {{{mapperInputCallSignature method.parameter}}}); + }) + .map({{_entity.instanceName}}Repository::save) + .orElseThrow(); {{~> (partial '../withEvents')}} - return {{wrapWithMapper entity}}; + return {{wrapWithMapper _entity}}; {{!-- Optional get(id) --}} -{{~else if (and entity method.paramId method.returnType method.returnTypeIsOptional)}} - {{~assign 'needMapping' (not (eq entity.name method.returnType))}} - return {{entity.instanceName}}Repository.{{{findById method}}}{{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}}; +{{~else if (and _entity method.paramId method.returnType method.returnTypeIsOptional)}} + {{~assign 'needMapping' (not (eq _entity.name method.returnType))}} + return {{_entity.instanceName}}Repository.{{{findById method}}}{{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}}; {{!-- Entity get(id) --}} -{{~else if (and entity method.paramId method.returnType)}} - return {{entity.instanceName}}Repository.{{{findById method}}}; +{{~else if (and _entity method.paramId method.returnType)}} + return {{_entity.instanceName}}Repository.{{{findById method}}}; {{!-- Optional get(MyEntity) --}} -{{~else if (and entity method.parameter method.returnType method.returnTypeIsOptional)}} - var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); +{{~else if (and _entity method.parameter method.returnType method.returnTypeIsOptional)}} + var {{_entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{_entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); // TODO: implement this method {{~> (partial '../withEvents')}} - return Optional.ofNullable({{wrapWithMapper entity}}); + return Optional.ofNullable({{wrapWithMapper _entity}}); {{!-- Optional get(MyEntity) --}} -{{~else if (and entity method.parameter method.returnType)}} - var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); +{{~else if (and _entity method.parameter method.returnType)}} + var {{_entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{_entity.className}}(), {{{mapperInputCallSignature method.parameter}}}); // TODO: implement this method {{~> (partial '../withEvents')}} - return {{wrapWithMapper entity}}; + return {{wrapWithMapper _entity}}; {{!-- Optional get() --}} -{{~else if (and entity method.returnType)}} - var {{entity.instanceName}} = new {{entity.className}}(); +{{~else if (and _entity method.returnType)}} + var {{_entity.instanceName}} = new {{_entity.className}}(); // TODO: implement this method {{~> (partial '../withEvents')}} - return {{wrapWithMapper entity}}; + return {{wrapWithMapper _entity}}; {{!-- void get() --}} -{{~else if (and entity)}} - var {{entity.instanceName}} = new {{entity.className}}(); +{{~else if (and _entity)}} + var {{_entity.instanceName}} = new {{_entity.className}}(); // TODO: implement this method {{~> (partial '../withEvents')}} -{{!-- we know nothing (???) --}} -{{~else~}} + {{!-- we know nothing (???) --}} + {{~else~}} // TODO: implement this method {{~> (partial '../withEvents')}} - {{~#if method.returnType}} - return null; - {{~/if}} + {{~#if method.returnType}} + return null; + {{~/if}} {{~/if}} diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java index a7270008..7bc05843 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java @@ -186,7 +186,11 @@ public static Object uncapFirst(String text, Options options) throws IOException } public static Object assign(final String variableName, final Options options) throws IOException { - var model = (Map) options.context.model(); + var context = options.context; + while(context.parent() != null && context.parent().model() instanceof Map) { + context = context.parent(); + } + var model = (Map) context.model(); if (options.params.length == 1) { if (options.param(0) != null) { model.put(variableName, options.param(0)); From e862bb56700f030579b33f08bbec0ccfa0c3f1ec Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Thu, 19 Dec 2024 18:35:37 +0100 Subject: [PATCH 19/21] fix with corner cases in templates --- .../src/main/java/web/mvc/ServiceApiController.java.hbs | 2 +- .../io/zenwave360/sdk/generators/JsonSchemaToJsonFaker.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs index c75b45ac..79b4677d 100644 --- a/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs +++ b/plugins/openapi-controllers/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIControllersGenerator/src/main/java/web/mvc/ServiceApiController.java.hbs @@ -66,7 +66,7 @@ public class {{serviceName}}ApiController implements {{serviceName}}Api { @Override public ResponseEntity<{{{responseEntityExpression}}}> {{operationId}}({{{methodParameters}}}) { - log.debug("REST request to {{operationId}}: {{methodParameterPlaceholders}}", {{methodParameterInstances}}); + log.debug("REST request to {{operationId}}: {{methodParameterPlaceholders}}"{{#if methodParameters}}, {{methodParameterInstances}}{{/if}}); {{~#if (eq httpMethod 'patch')~}} {{~else if requestBodySchema~}} var {{mappedInputVariable}} = mapper.as{{serviceMethodParameter}}({{reqBodyVariableName}}); diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/JsonSchemaToJsonFaker.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/JsonSchemaToJsonFaker.java index c3956273..a6106e4e 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/JsonSchemaToJsonFaker.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/generators/JsonSchemaToJsonFaker.java @@ -79,6 +79,9 @@ protected Object generateObject(String propertyName, Map schema) protected Object generateValue(String propertyName, Map schemaNode) { String type = (String) schemaNode.get("type"); + if ((type == null)) { + return null; + } return switch (type) { case "string" -> generateString(propertyName, schemaNode); From a2e2f479f2129f3a677ebffc67246caa0d52a3f1 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Thu, 19 Dec 2024 18:36:10 +0100 Subject: [PATCH 20/21] adds .editorconfig and .gitattributes --- .editorconfig | 17 +++++++++++++++++ .gitattributes | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7b3df3db --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..bfdaff8c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,33 @@ +# Enforce LF line endings for all text files by default +* text=auto eol=lf + +# Explicit exceptions for CRLF line endings +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +# Explicitly mark binary files +# (Git automatically detects these, but this is a safeguard for clarity) +*.pdf binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.tif binary +*.tiff binary +*.ico binary +*.svg binary +*.eps binary +*.class binary +*.jar binary +*.war binary +*.7z binary +*.gz binary +*.rar binary +*.tar binary +*.zip binary +*.ttf binary +*.eot binary +*.otf binary +*.woff binary +*.woff2 binary From 1b84faebc35ffc4187d474a7f5afce591abf33d2 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Mon, 30 Dec 2024 09:39:14 +0100 Subject: [PATCH 21/21] zdl-jvm.version > 1.2.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3493a25b..f30bf9aa 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 4.3.1 2.9.0 0.8.7 - 1.3.0-SNAPSHOT + 1.2.3 19.2 1.7 2.38.0