feat: scaffold Glow HTTP tool integration
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 0s

This commit is contained in:
jade
2026-08-11 13:22:48 +09:00
parent 041f939c33
commit 8312eee1d1
5 changed files with 153 additions and 17 deletions

View File

@@ -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<ApiDefinition> apiList = new ArrayList<>();

View File

@@ -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<FieldDefinition> 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<FieldDefinition> fields) {
return fieldLines(fields, Set.of());
}
private static String fieldLines(List<FieldDefinition> fields, Set<String> excludedNames) {
StringBuilder source = new StringBuilder();
Set<String> generatedNames = new LinkedHashSet<>();
for (FieldDefinition field : fields == null ? List.<FieldDefinition>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;

View File

@@ -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

View File

@@ -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<ToolScaffolder.FieldDefinition> 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<ToolScaffolder.FieldDefinition> 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);
}
}

View File

@@ -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: