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
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Component
|
@Component
|
||||||
@ConfigurationProperties(prefix = "axhub.http")
|
@ConfigurationProperties(prefix = "glow.communication.http")
|
||||||
public class AxhubHttpProperties {
|
public class AxhubHttpProperties {
|
||||||
|
|
||||||
private List<ApiDefinition> apiList = new ArrayList<>();
|
private List<ApiDefinition> apiList = new ArrayList<>();
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,7 +195,7 @@ public class ToolScaffolder {
|
|||||||
.replaceAll("(?m)^\\s*-\\(\\?:[^\\r\\n]*\\R", "")
|
.replaceAll("(?m)^\\s*-\\(\\?:[^\\r\\n]*\\R", "")
|
||||||
.replace("private String phoneNumber;", "@Schema(example = \"01012345678\")\n private String phoneNumber;")
|
.replace("private String phoneNumber;", "@Schema(example = \"01012345678\")\n private String phoneNumber;")
|
||||||
.replace("private String message;", "@Schema(example = \"테스트 메시지입니다.\")\n private String message;");
|
.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);
|
Files.writeString(dtoDir.resolve(baseName + "Request.java"), reqContent);
|
||||||
|
|
||||||
// Generate Response DTO
|
// Generate Response DTO
|
||||||
@@ -227,7 +229,7 @@ public class ToolScaffolder {
|
|||||||
// TODO: Add response fields here. Do not include PII in the Tool response.
|
// TODO: Add response fields here. Do not include PII in the Tool response.
|
||||||
}
|
}
|
||||||
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
|
""".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);
|
Files.writeString(dtoDir.resolve(baseName + "Response.java"), resContent);
|
||||||
|
|
||||||
String toolName = toToolName(moduleName, group, baseName);
|
String toolName = toToolName(moduleName, group, baseName);
|
||||||
@@ -386,7 +388,9 @@ public class ToolScaffolder {
|
|||||||
.replace("response.setResultCode(\"SUCCESS\");",
|
.replace("response.setResultCode(\"SUCCESS\");",
|
||||||
"if (resTransfer.getBody() != null) {\n response = converter.toResponse(resTransfer.getBody());\n }\n response.setResultCode(\"SUCCESS\");");
|
"if (resTransfer.getBody() != null) {\n response = converter.toResponse(resTransfer.getBody());\n }\n response.setResultCode(\"SUCCESS\");");
|
||||||
} else {
|
} else {
|
||||||
serviceImplContent = """
|
serviceImplContent = "HTTP".equalsIgnoreCase(routingType)
|
||||||
|
? httpUseCaseImplContent(bizPackage, baseName, interfaceId, author, createDate)
|
||||||
|
: """
|
||||||
package %s.usecase.impl;
|
package %s.usecase.impl;
|
||||||
|
|
||||||
import %s.dto.%sRequest;
|
import %s.dto.%sRequest;
|
||||||
@@ -420,9 +424,16 @@ public class ToolScaffolder {
|
|||||||
private final %sConverter converter;
|
private final %sConverter converter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object execute(%sRequest req) {
|
public %sResponse execute(%sRequest req) {
|
||||||
// %sLegacyRequest legacyRequest = converter.toLegacyRequest(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(
|
""".formatted(
|
||||||
@@ -439,6 +450,10 @@ public class ToolScaffolder {
|
|||||||
baseName,
|
baseName,
|
||||||
baseName,
|
baseName,
|
||||||
baseName,
|
baseName,
|
||||||
|
baseName,
|
||||||
|
baseName,
|
||||||
|
baseName,
|
||||||
|
baseName,
|
||||||
routingType, interfaceId
|
routingType, interfaceId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -644,6 +659,7 @@ public class ToolScaffolder {
|
|||||||
private String content;
|
private String content;
|
||||||
}
|
}
|
||||||
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
|
""".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);
|
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRequest.java"), legacyReqContent);
|
||||||
|
|
||||||
String legacyResContent = """
|
String legacyResContent = """
|
||||||
@@ -670,6 +686,7 @@ public class ToolScaffolder {
|
|||||||
// TODO: Add legacy response fields here
|
// TODO: Add legacy response fields here
|
||||||
}
|
}
|
||||||
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
|
""".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);
|
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyResponse.java"), legacyResContent);
|
||||||
|
|
||||||
String converterContent = """
|
String converterContent = """
|
||||||
@@ -719,6 +736,7 @@ public class ToolScaffolder {
|
|||||||
bizPackage, baseName, author, createDate, createDate, author,
|
bizPackage, baseName, author, createDate, createDate, author,
|
||||||
baseName, baseName, baseName, baseName, baseName, baseName, baseName
|
baseName, baseName, baseName, baseName, baseName, baseName, baseName
|
||||||
);
|
);
|
||||||
|
converterContent = legacyConverterContent(bizPackage, baseName);
|
||||||
Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent);
|
Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent);
|
||||||
|
|
||||||
log.append("\n=========================================\n");
|
log.append("\n=========================================\n");
|
||||||
@@ -797,7 +815,7 @@ public class ToolScaffolder {
|
|||||||
|
|
||||||
private static String dtoContent(String packageName, String className, List<FieldDefinition> fields,
|
private static String dtoContent(String packageName, String className, List<FieldDefinition> fields,
|
||||||
String author, String createDate, boolean request) {
|
String author, String createDate, boolean request) {
|
||||||
String body = fieldLines(fields);
|
String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage"));
|
||||||
if (!request) {
|
if (!request) {
|
||||||
body = " private String resultCode;\n\n private String resultMessage;\n" + body;
|
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));
|
""".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) {
|
private static String mciConverterContent(String bizPackage, String baseName, String mciPackage, String interfaceId) {
|
||||||
return """
|
return """
|
||||||
package %s.converter;
|
package %s.converter;
|
||||||
@@ -850,9 +919,43 @@ public class ToolScaffolder {
|
|||||||
baseName, interfaceId, baseName, baseName, interfaceId, baseName, interfaceId);
|
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) {
|
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();
|
StringBuilder source = new StringBuilder();
|
||||||
|
Set<String> generatedNames = new LinkedHashSet<>();
|
||||||
for (FieldDefinition field : fields == null ? List.<FieldDefinition>of() : fields) {
|
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 type = supportedType(field.type());
|
||||||
String description = field.description() == null ? "" : field.description().replace("\"", "\\\"");
|
String description = field.description() == null ? "" : field.description().replace("\"", "\\\"");
|
||||||
String example = field.example() == null ? "" : field.example().replace("\"", "\\\"");
|
String example = field.example() == null ? "" : field.example().replace("\"", "\\\"");
|
||||||
@@ -861,11 +964,10 @@ public class ToolScaffolder {
|
|||||||
if (field.required()) {
|
if (field.required()) {
|
||||||
source.append(", requiredMode = Schema.RequiredMode.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();
|
return source.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String supportedType(String type) {
|
private static String supportedType(String type) {
|
||||||
return switch (type == null ? "String" : type) {
|
return switch (type == null ? "String" : type) {
|
||||||
case "String", "Integer", "Long", "Double", "Boolean", "BigDecimal" -> type;
|
case "String", "Integer", "Long", "Double", "Boolean", "BigDecimal" -> type;
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ glow:
|
|||||||
http:
|
http:
|
||||||
connection-timeout: 5
|
connection-timeout: 5
|
||||||
read-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:
|
mci:
|
||||||
uri: /ntl_mci/dap_rcv
|
uri: /ntl_mci/dap_rcv
|
||||||
receive-uri: /itrf/mciReceive
|
receive-uri: /itrf/mciReceive
|
||||||
|
|||||||
@@ -129,4 +129,39 @@ class ToolScaffolderTest {
|
|||||||
assertTrue(Files.exists(useCaseTest));
|
assertTrue(Files.exists(useCaseTest));
|
||||||
assertTrue(Files.readString(useCaseTest).contains("class ClaimSearchUseCaseTest"));
|
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
|
url: http://localhost:8081
|
||||||
tool:
|
tool:
|
||||||
url: ${AXHUB_TOOL_URL:http://localhost:${server.port}}
|
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:
|
sol:
|
||||||
req-detail:
|
req-detail:
|
||||||
|
|||||||
Reference in New Issue
Block a user