From 8312eee1d10e9b0ed35eb800f64fd454f37503bd Mon Sep 17 00:00:00 2001 From: jade Date: Tue, 11 Aug 2026 13:22:48 +0900 Subject: [PATCH] feat: scaffold Glow HTTP tool integration --- .../http/component/AxhubHttpProperties.java | 2 +- .../dap/lib/util/ToolScaffolder.java | 118 ++++++++++++++++-- .../main/resources/glow/application-glow.yml | 7 ++ .../dap/lib/util/ToolScaffolderTest.java | 35 ++++++ .../src/main/resources/application-local.yml | 8 -- 5 files changed, 153 insertions(+), 17 deletions(-) diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java index 8237898c..f183fa05 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Component; @Getter @Setter @Component -@ConfigurationProperties(prefix = "axhub.http") +@ConfigurationProperties(prefix = "glow.communication.http") public class AxhubHttpProperties { private List apiList = new ArrayList<>(); diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java index c60409ae..19d7ec8d 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java @@ -7,8 +7,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.Scanner; /** @@ -193,7 +195,7 @@ public class ToolScaffolder { .replaceAll("(?m)^\\s*-\\(\\?:[^\\r\\n]*\\R", "") .replace("private String phoneNumber;", "@Schema(example = \"01012345678\")\n private String phoneNumber;") .replace("private String message;", "@Schema(example = \"테스트 메시지입니다.\")\n private String message;"); - reqContent = dtoContent(bizPackage, baseName + "Request", inputFields, author, createDate, true); + reqContent = dtoContent(bizPackage + ".dto", baseName + "Request", inputFields, author, createDate, true); Files.writeString(dtoDir.resolve(baseName + "Request.java"), reqContent); // Generate Response DTO @@ -227,7 +229,7 @@ public class ToolScaffolder { // TODO: Add response fields here. Do not include PII in the Tool response. } """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); - resContent = dtoContent(bizPackage, baseName + "Response", outputFields, author, createDate, false); + resContent = dtoContent(bizPackage + ".dto", baseName + "Response", outputFields, author, createDate, false); Files.writeString(dtoDir.resolve(baseName + "Response.java"), resContent); String toolName = toToolName(moduleName, group, baseName); @@ -386,7 +388,9 @@ public class ToolScaffolder { .replace("response.setResultCode(\"SUCCESS\");", "if (resTransfer.getBody() != null) {\n response = converter.toResponse(resTransfer.getBody());\n }\n response.setResultCode(\"SUCCESS\");"); } else { - serviceImplContent = """ + serviceImplContent = "HTTP".equalsIgnoreCase(routingType) + ? httpUseCaseImplContent(bizPackage, baseName, interfaceId, author, createDate) + : """ package %s.usecase.impl; import %s.dto.%sRequest; @@ -420,9 +424,16 @@ public class ToolScaffolder { private final %sConverter converter; @Override - public Object execute(%sRequest req) { + public %sResponse execute(%sRequest req) { // %sLegacyRequest legacyRequest = converter.toLegacyRequest(req); - return executeLegacy("%s", "%s", req); // Or pass legacyRequest + Object legacyResponse = executeLegacy("%s", "%s", req); // Or pass legacyRequest + if (legacyResponse instanceof %sResponse response) { + return response; + } + %sResponse response = new %sResponse(); + response.setResultCode("SUCCESS"); + response.setResultMessage("Legacy call completed."); + return response; } } """.formatted( @@ -439,6 +450,10 @@ public class ToolScaffolder { baseName, baseName, baseName, + baseName, + baseName, + baseName, + baseName, routingType, interfaceId ); } @@ -644,6 +659,7 @@ public class ToolScaffolder { private String content; } """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + legacyReqContent = dtoContent(bizPackage + ".legacy", baseName + "LegacyRequest", inputFields, author, createDate, true); Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRequest.java"), legacyReqContent); String legacyResContent = """ @@ -670,6 +686,7 @@ public class ToolScaffolder { // TODO: Add legacy response fields here } """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + legacyResContent = dtoContent(bizPackage + ".legacy", baseName + "LegacyResponse", outputFields, author, createDate, true); Files.writeString(legacyDtoDir.resolve(baseName + "LegacyResponse.java"), legacyResContent); String converterContent = """ @@ -719,6 +736,7 @@ public class ToolScaffolder { bizPackage, baseName, author, createDate, createDate, author, baseName, baseName, baseName, baseName, baseName, baseName, baseName ); + converterContent = legacyConverterContent(bizPackage, baseName); Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent); log.append("\n=========================================\n"); @@ -797,7 +815,7 @@ public class ToolScaffolder { private static String dtoContent(String packageName, String className, List fields, String author, String createDate, boolean request) { - String body = fieldLines(fields); + String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage")); if (!request) { body = " private String resultCode;\n\n private String resultMessage;\n" + body; } @@ -829,6 +847,57 @@ public class ToolScaffolder { """.formatted(BASE_PACKAGE, packageSuffix, className, fieldLines(fields)); } + private static String httpUseCaseImplContent(String bizPackage, String baseName, String interfaceId, String author, String createDate) { + return """ + package %s.usecase.impl; + + import %s.converter.%sConverter; + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.legacy.%sLegacyRequest; + import %s.legacy.%sLegacyResponse; + import %s.usecase.%sUseCase; + import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent; + import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpDomain; + import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase; + import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; + import org.springframework.stereotype.Service; + + /** + * HTTP Tool implementation. Calls the Glow HTTP adapter through AxhubHttpComponent. + * Configure the target domain in AxhubHttpDomain and glow.communication.http.api-list before use. + */ + @Slf4j + @Service + @RequiredArgsConstructor + public class %sUseCaseImpl extends AbstractMcpToolUseCase implements %sUseCase { + + private final %sConverter converter; + private final AxhubHttpComponent http; + + @Override + public %sResponse execute(%sRequest req) { + %sLegacyRequest legacyRequest = converter.toLegacyRequest(req); + %sLegacyResponse legacyResponse = http.call( + AxhubHttpDomain.SAMPLE, + "/%s", + legacyRequest, + %sLegacyResponse.class + ); + + %sResponse response = converter.toResponse(legacyResponse); + response.setResultCode("SUCCESS"); + response.setResultMessage("HTTP API call completed."); + return response; + } + } + """.formatted( + bizPackage, bizPackage, baseName, bizPackage, baseName, bizPackage, baseName, + bizPackage, baseName, bizPackage, baseName, bizPackage, baseName, + baseName, baseName, baseName, baseName, baseName, baseName, baseName, + interfaceId, baseName, baseName); + } private static String mciConverterContent(String bizPackage, String baseName, String mciPackage, String interfaceId) { return """ package %s.converter; @@ -850,9 +919,43 @@ public class ToolScaffolder { baseName, interfaceId, baseName, baseName, interfaceId, baseName, interfaceId); } + private static String legacyConverterContent(String bizPackage, String baseName) { + return """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.legacy.%sLegacyRequest; + import %s.legacy.%sLegacyResponse; + import org.mapstruct.Mapper; + import org.mapstruct.ReportingPolicy; + + @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) + public interface %sConverter { + %sLegacyRequest toLegacyRequest(%sRequest request); + %sRequest toRequest(%sLegacyRequest legacyRequest); + %sResponse toResponse(%sLegacyResponse legacyResponse); + } + """.formatted( + bizPackage, bizPackage, baseName, bizPackage, baseName, + bizPackage, baseName, bizPackage, baseName, + baseName, baseName, baseName, baseName, baseName, baseName, baseName); + } private static String fieldLines(List fields) { + return fieldLines(fields, Set.of()); + } + + private static String fieldLines(List fields, Set excludedNames) { StringBuilder source = new StringBuilder(); + Set generatedNames = new LinkedHashSet<>(); for (FieldDefinition field : fields == null ? List.of() : fields) { + if (field == null || field.name() == null || field.name().isBlank()) { + continue; + } + String fieldName = field.name().trim(); + if (excludedNames.contains(fieldName) || !generatedNames.add(fieldName)) { + continue; + } String type = supportedType(field.type()); String description = field.description() == null ? "" : field.description().replace("\"", "\\\""); String example = field.example() == null ? "" : field.example().replace("\"", "\\\""); @@ -861,11 +964,10 @@ public class ToolScaffolder { if (field.required()) { source.append(", requiredMode = Schema.RequiredMode.REQUIRED"); } - source.append(")\n private ").append(type).append(' ').append(field.name()).append(";\n\n"); + source.append(")\n private ").append(type).append(' ').append(fieldName).append(";\n\n"); } return source.toString(); } - private static String supportedType(String type) { return switch (type == null ? "String" : type) { case "String", "Integer", "Long", "Double", "Boolean", "BigDecimal" -> type; diff --git a/dap-was-lib/src/main/resources/glow/application-glow.yml b/dap-was-lib/src/main/resources/glow/application-glow.yml index e73d3223..e5925405 100644 --- a/dap-was-lib/src/main/resources/glow/application-glow.yml +++ b/dap-was-lib/src/main/resources/glow/application-glow.yml @@ -21,6 +21,13 @@ glow: http: connection-timeout: 5 read-timeout: 5 + # HTTP Tool target catalog. Replace or add entries after the business endpoint is agreed. + api-list: + - name: sample + domain: ${AXHUB_SAMPLE_HTTP_DOMAIN:http://localhost:8099} + path: "" + method: GET + biz-pod: false mci: uri: /ntl_mci/dap_rcv receive-uri: /itrf/mciReceive diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java index 9e83b09f..5989594a 100644 --- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java @@ -129,4 +129,39 @@ class ToolScaffolderTest { assertTrue(Files.exists(useCaseTest)); assertTrue(Files.readString(useCaseTest).contains("class ClaimSearchUseCaseTest")); } + + @Test + void generatesDtoPackageAndRemovesDuplicateResponseFields() throws Exception { + String moduleName = root.resolve("dap-was-http").toString(); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("resultCode", "String", "API result", "SUCCESS", true), + new ToolScaffolder.FieldDefinition("employeeName", "String", "Employee name", "Hong Gildong", false), + new ToolScaffolder.FieldDefinition("employeeName", "String", "Duplicate name", "Duplicate", false)); + + List inputFields = List.of( + new ToolScaffolder.FieldDefinition("employeeId", "String", "Employee identifier", "EMP10001", true)); + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "Employee search", "smp", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, inputFields, outputFields); + + Path dtoRoot = root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto"); + String request = Files.readString(dtoRoot.resolve("EmployeeSearchRequest.java")); + String response = Files.readString(dtoRoot.resolve("EmployeeSearchResponse.java")); + + assertTrue(request.contains("package io.shinhanlife.dap.mcc.biz.smp.dto;"), request); + assertTrue(response.contains("package io.shinhanlife.dap.mcc.biz.smp.dto;"), response); + assertTrue(response.indexOf("private String resultCode;") == response.lastIndexOf("private String resultCode;"), response); + assertTrue(response.indexOf("private String employeeName;") == response.lastIndexOf("private String employeeName;"), response); + String converter = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/converter/EmployeeSearchConverter.java")); + String legacyRequest = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/legacy/EmployeeSearchLegacyRequest.java")); + assertFalse(converter.contains("phoneNumber"), converter); + assertTrue(converter.contains("unmappedTargetPolicy = ReportingPolicy.IGNORE"), converter); + assertTrue(legacyRequest.contains("private String employeeId;"), legacyRequest); + String implementation = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/EmployeeSearchUseCaseImpl.java")); + assertTrue(implementation.contains("public EmployeeSearchResponse execute(EmployeeSearchRequest req)"), implementation); + assertTrue(implementation.contains("import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent;"), implementation); + assertTrue(implementation.contains("private final AxhubHttpComponent http;"), implementation); + assertTrue(implementation.contains("AxhubHttpDomain.SAMPLE,"), implementation); + assertTrue(implementation.contains("\"/HR_EMPLOYEE_SEARCH\""), implementation); + assertFalse(implementation.contains("executeLegacy(\"HTTP\""), implementation); + } } diff --git a/dap-was-oth/src/main/resources/application-local.yml b/dap-was-oth/src/main/resources/application-local.yml index b0854d89..1e73a06d 100644 --- a/dap-was-oth/src/main/resources/application-local.yml +++ b/dap-was-oth/src/main/resources/application-local.yml @@ -43,14 +43,6 @@ axhub: url: http://localhost:8081 tool: url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} - # Registered outbound HTTP APIs. Tool code selects the domain name, never a free-form URL. - http: - api-list: - - name: sample - domain: ${AXHUB_SAMPLE_HTTP_DOMAIN:http://localhost:8099} - path: "" - method: GET - biz-pod: false sol: req-detail: