feat: scaffold Glow HTTP tool integration
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 0s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 0s
This commit is contained in:
@@ -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<>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user