diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/CommonCodeResolver.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/CommonCodeResolver.java
new file mode 100644
index 000000000..ea4c2a88f
--- /dev/null
+++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/CommonCodeResolver.java
@@ -0,0 +1,180 @@
+package io.shinhanlife.dat.lib.util;
+
+import jakarta.annotation.PostConstruct;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * @package io.shinhanlife.dat.lib.util
+ * @className CommonCodeResolver
+ * @description AX HUB 시스템 처리 클래스
+ * @author 0986406
+ * @create 2026.09.01
+ *
+ * ---------- 개정이력 ----------
+ * 수정일 수정자 수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01 0986406 최초생성
+ *
+ *
+ */
+@Slf4j
+@Component
+public class CommonCodeResolver {
+
+ private static final String DEFAULT_RESOURCE_PATH = "codes.csv";
+
+ // Group -> (Code -> Name)
+ private static final Map> CODE_TO_NAME_MAP = new ConcurrentHashMap<>();
+ // Group -> (Normalized Name or Code -> Code)
+ private static final Map> NAME_TO_CODE_MAP = new ConcurrentHashMap<>();
+
+ static {
+ loadCodes(DEFAULT_RESOURCE_PATH);
+ }
+
+ @PostConstruct
+ public void init() {
+ loadCodes(DEFAULT_RESOURCE_PATH);
+ }
+
+ public static synchronized void reload() {
+ loadCodes(DEFAULT_RESOURCE_PATH);
+ }
+
+ public static synchronized void loadCodes(String resourcePath) {
+ ClassLoader classLoader = CommonCodeResolver.class.getClassLoader();
+ try (InputStream is = classLoader.getResourceAsStream(resourcePath)) {
+ if (is == null) {
+ log.warn("[CommonCodeResolver] 리소스 파일을 찾을 수 없습니다: {}", resourcePath);
+ return;
+ }
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ CODE_TO_NAME_MAP.clear();
+ NAME_TO_CODE_MAP.clear();
+
+ String line;
+ boolean firstLine = true;
+ while ((line = reader.readLine()) != null) {
+ if (firstLine) {
+ firstLine = false;
+ if (line.startsWith("\uFEFF")) {
+ line = line.substring(1);
+ }
+ if (line.contains("그룹") || line.contains("group")) {
+ continue;
+ }
+ }
+ line = line.trim();
+ if (line.isEmpty() || line.startsWith("#")) {
+ continue;
+ }
+ String[] parts = line.split(",", -1);
+ if (parts.length < 3) {
+ continue;
+ }
+ String group = parts[0].trim().toUpperCase(Locale.ROOT);
+ String code = parts[1].trim();
+ String name = parts[2].trim();
+
+ if (group.isEmpty() || code.isEmpty()) {
+ continue;
+ }
+
+ Map codeToName = CODE_TO_NAME_MAP.computeIfAbsent(group, k -> new ConcurrentHashMap<>());
+ Map nameToCode = NAME_TO_CODE_MAP.computeIfAbsent(group, k -> new ConcurrentHashMap<>());
+
+ codeToName.put(code, name);
+ nameToCode.put(code, code);
+ nameToCode.put(name, code);
+ nameToCode.put(name.toLowerCase(Locale.ROOT), code);
+ nameToCode.put(name.replace(" ", ""), code);
+ }
+ log.info("[CommonCodeResolver] 공통코드 로드 완료: {}개 그룹", CODE_TO_NAME_MAP.size());
+ }
+ } catch (Exception e) {
+ log.error("[CommonCodeResolver] 공통코드 로드 중 오류 발생: {}", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 입력값(명칭 또는 코드)을 처리계 표준 코드값으로 변환합니다.
+ *
+ * @param group 코드 그룹명 (예: COMPANY, STATUS)
+ * @param input 사용자가 입력한 명칭("삼성전자") 또는 코드("005930")
+ * @return 매핑된 코드값(예: "005930"). 매핑되지 않으면 원본 입력값 반환.
+ */
+ public static String resolve(String group, String input) {
+ return resolve(group, input, input);
+ }
+
+ /**
+ * 입력값(명칭 또는 코드)을 처리계 표준 코드값으로 변환합니다.
+ *
+ * @param group 코드 그룹명
+ * @param input 입력값
+ * @param defaultValue 매핑 실패 시 반환할 기본값
+ * @return 매핑된 코드값 또는 defaultValue
+ */
+ public static String resolve(String group, String input, String defaultValue) {
+ if (input == null || input.isBlank()) {
+ return defaultValue;
+ }
+ if (group == null) {
+ return input.trim();
+ }
+ String normalizedGroup = group.trim().toUpperCase(Locale.ROOT);
+ Map nameToCode = NAME_TO_CODE_MAP.get(normalizedGroup);
+ if (nameToCode == null) {
+ return defaultValue;
+ }
+ String trimmed = input.trim();
+ String matched = nameToCode.get(trimmed);
+ if (matched != null) {
+ return matched;
+ }
+ matched = nameToCode.get(trimmed.toLowerCase(Locale.ROOT));
+ if (matched != null) {
+ return matched;
+ }
+ matched = nameToCode.get(trimmed.replace(" ", ""));
+ if (matched != null) {
+ return matched;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * 코드값에 해당하는 표준 명칭을 반환합니다. (응답 변환 시 사용)
+ */
+ public static String getName(String group, String code) {
+ if (group == null || code == null) {
+ return code;
+ }
+ String normalizedGroup = group.trim().toUpperCase(Locale.ROOT);
+ Map codeToName = CODE_TO_NAME_MAP.get(normalizedGroup);
+ if (codeToName != null) {
+ return codeToName.getOrDefault(code.trim(), code);
+ }
+ return code;
+ }
+
+ /**
+ * 특정 그룹의 전체 코드-명칭 맵을 반환합니다.
+ */
+ public static Map getCodeMap(String group) {
+ if (group == null) {
+ return Collections.emptyMap();
+ }
+ Map map = CODE_TO_NAME_MAP.get(group.trim().toUpperCase(Locale.ROOT));
+ return map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap();
+ }
+}
\ No newline at end of file
diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java
index 94ad3ee57..680acaafc 100644
--- a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java
+++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java
@@ -8,6 +8,7 @@ import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@@ -46,6 +47,23 @@ public class ToolScaffolder {
private static final String BASE_PACKAGE = "io.shinhanlife.dat.mcc";
private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dat/mcc";
+ public enum PagingMode {
+ NONE,
+ SCROLL,
+ PAGE_NUMBER;
+
+ static PagingMode from(String value) {
+ if (value == null || value.isBlank()) {
+ return NONE;
+ }
+ try {
+ return PagingMode.valueOf(value.trim().toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException ignored) {
+ return NONE;
+ }
+ }
+ }
+
public record FieldDefinition(String name, String type, String description, List examples, String pattern, boolean required,
List enumValues, String itemType, List itemFields) {
public FieldDefinition(String name, String type, String description, List examples, String pattern, boolean required) {
@@ -68,7 +86,48 @@ public class ToolScaffolder {
String displayDescription,
List exampleQueries,
List tags,
- String ownerOrg) {
+ String ownerOrg,
+ Long timeoutMillis,
+ Integer retryMaxAttempts,
+ String pagingMode) {
+
+ public ToolDefinitionOptions(String functionDescription, String whenToUse, String whenNotToUse,
+ String ioLimits, String displayDescription, List exampleQueries,
+ List tags, String ownerOrg, Long timeoutMillis,
+ Integer retryMaxAttempts) {
+ this(functionDescription, whenToUse, whenNotToUse, ioLimits, displayDescription, exampleQueries, tags,
+ ownerOrg, timeoutMillis, retryMaxAttempts, null);
+ }
+
+ public ToolDefinitionOptions(String functionDescription, String whenToUse, String whenNotToUse,
+ String ioLimits, String displayDescription, List exampleQueries,
+ List tags, String ownerOrg) {
+ this(functionDescription, whenToUse, whenNotToUse, ioLimits, displayDescription, exampleQueries, tags,
+ ownerOrg, null, null, null);
+ }
+
+ public long timeoutMillisOrDefault() {
+ return timeoutMillis != null && timeoutMillis > 0 ? timeoutMillis : 5000L;
+ }
+
+ public int retryMaxAttemptsOrDefault() {
+ return retryMaxAttempts != null && retryMaxAttempts > 0 ? retryMaxAttempts : 3;
+ }
+
+ public PagingMode pagingModeOrNone() {
+ return PagingMode.from(pagingMode);
+ }
+ }
+
+ public static String businessProposalGuide() {
+ return """
+ [업무 제안서 작성 가이드]
+ 통합계약원부 조회
+ 1. 조회 대상·식별 기준: 무엇을 어떤 값으로 찾는지
+ 계약번호가 있으면 상세 원부를 조회
+ 2. 조회 목적·사용처: 어떤 업무 상황에서 쓰는지
+ 해지·대출·보험금 상세는 별도 Tool
+ """;
}
public static String scaffoldUseCase(String useCaseName, String moduleName, String author,
@@ -76,7 +135,11 @@ public class ToolScaffolder {
if (tools == null || tools.isEmpty()) {
throw new IllegalArgumentException("At least one Tool method is required.");
}
+ boolean hasMci = tools.stream().anyMatch(t -> "MCI".equalsIgnoreCase(t.routingType()));
String useCaseBaseName = toPascalCase(useCaseName);
+ if (hasMci) {
+ useCaseBaseName = abbreviatedMciSourceBaseName(useCaseBaseName);
+ }
String group = tools.getFirst().group().toLowerCase(Locale.ROOT);
validateToolMethods(tools, group);
@@ -93,13 +156,11 @@ public class ToolScaffolder {
Path dtoDir = sourceRoot.resolve(Paths.get("biz", group, "dto"));
Path converterDir = sourceRoot.resolve(Paths.get("biz", group, "converter"));
Path definitionDir = moduleRoot.resolve(Paths.get("src", "main", "resources", "tool-definitions", group));
- Path mockDir = moduleRoot.resolve(Paths.get("src", "main", "resources", "mock-responses"));
Files.createDirectories(useCaseDir);
Files.createDirectories(implDir);
Files.createDirectories(dtoDir);
Files.createDirectories(converterDir);
Files.createDirectories(definitionDir);
- Files.createDirectories(mockDir);
String bizPackage = BASE_PACKAGE + ".biz." + group;
Path useCaseFile = useCaseDir.resolve(useCaseBaseName + "UseCase.java");
@@ -112,7 +173,9 @@ public class ToolScaffolder {
} else {
writeUtf8(useCaseFile, groupedUseCaseContent(bizPackage, useCaseBaseName, moduleName, tools));
writeUtf8(useCaseImplFile, groupedUseCaseImplContent(bizPackage, useCaseBaseName, tools));
- writeUtf8(converterFile, groupedConverterContent(bizPackage, useCaseBaseName, tools));
+ if (!hasMci) {
+ writeUtf8(converterFile, groupedConverterContent(bizPackage, useCaseBaseName, tools));
+ }
}
StringBuilder log = new StringBuilder("\n=========================================\n")
@@ -122,13 +185,16 @@ public class ToolScaffolder {
.append("[Usecase Interface] ").append(useCaseFile).append("\n")
.append("[Usecase Impl] ").append(useCaseImplFile).append("\n");
for (ToolMethodDefinition tool : tools) {
- writeGroupedToolFiles(moduleRoot, sourceRoot, dtoDir, definitionDir, mockDir, bizPackage, tool, moduleName, log);
+ writeGroupedToolFiles(moduleRoot, sourceRoot, dtoDir, definitionDir, bizPackage, tool, moduleName, log);
if ("HTTP".equalsIgnoreCase(tool.routingType())) {
- ensureLocalHttpApiConfiguration(moduleRoot, tool.httpApiName(),
+ Path glowConfig = ensureLocalHttpApiConfiguration(moduleRoot, tool.httpApiName(),
toToolName(moduleName, tool.group(), toPascalCase(tool.baseName())));
+ log.append("[HTTP Config] ").append(glowConfig).append("\n");
}
}
- log.append("[Converter] ").append(converterFile).append("\n");
+ if (!hasMci) {
+ log.append("[Converter] ").append(converterFile).append("\n");
+ }
return log.toString();
}
@@ -162,41 +228,119 @@ public class ToolScaffolder {
}
}
+ private static String formatClientSystemCode(String code, String delimiter) {
+ if (code == null) return "";
+ if (code.length() == 9) {
+ return code.substring(1, 4).toLowerCase(Locale.ROOT) + delimiter + code.substring(4, 5).toLowerCase(Locale.ROOT);
+ } else if (code.length() == 4) {
+ return code.substring(0, 3).toLowerCase(Locale.ROOT) + delimiter + code.substring(3).toLowerCase(Locale.ROOT);
+ }
+ return code.toLowerCase(Locale.ROOT);
+ }
+
+ private static String mciClientPrefix(String clientSystemCode) {
+ String normalized = clientSystemCode == null ? "" : clientSystemCode.trim().toLowerCase(Locale.ROOT);
+ return normalized.length() == 9 ? normalized.substring(1, 5) : normalized;
+ }
+
+ private static String mciClientClassName(String clientSystemCode) {
+ return "Mci" + toPascalCase(mciClientPrefix(clientSystemCode)) + "Client";
+ }
+
+ private static String mciClientVariable(String clientSystemCode) {
+ String prefix = toPascalCase(mciClientPrefix(clientSystemCode));
+ return "mci" + prefix + "Client";
+ }
+
private static void writeGroupedToolFiles(Path moduleRoot, Path sourceRoot, Path dtoDir, Path definitionDir,
- Path mockDir, String bizPackage, ToolMethodDefinition tool,
+ String bizPackage, ToolMethodDefinition tool,
String moduleName, StringBuilder log) throws IOException {
- String baseName = toPascalCase(tool.baseName());
boolean mci = "MCI".equalsIgnoreCase(tool.routingType());
- String code = mci ? (tool.clientSystemCode().length() == 4 ? tool.clientSystemCode().substring(0,3).toLowerCase(Locale.ROOT) + "/" + tool.clientSystemCode().substring(3).toLowerCase(Locale.ROOT) : tool.clientSystemCode().toLowerCase(Locale.ROOT)) : toPackageSegment(tool.httpApiName());
+ String toolBaseName = toPascalCase(tool.baseName());
+ String baseName = mci ? abbreviatedMciSourceBaseName(toolBaseName) : toolBaseName;
+ String code = mci ? formatClientSystemCode(tool.clientSystemCode(), "/") : toPackageSegment(tool.httpApiName());
String ioPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." : ".infra.itrf.http.") + code.replace("/", ".");
Path clientDir = sourceRoot.resolve(Paths.get("infra", "itrf", mci ? "mci" : "http", code));
Path ioDir = clientDir.resolve("io");
Files.createDirectories(ioDir);
+ ToolDefinitionOptions options = tool.definitionOptions() == null
+ ? new ToolDefinitionOptions(null, null, null, null, null, List.of(), List.of(), null)
+ : tool.definitionOptions();
+ PagingMode pagingMode = mci ? options.pagingModeOrNone() : PagingMode.NONE;
+ String ioPrefix = (tool.clientSystemCode() != null && !tool.clientSystemCode().isBlank()) ? tool.clientSystemCode().toUpperCase() : tool.interfaceId();
+ if (pagingMode != PagingMode.NONE) {
+ String group = bizPackage.substring(bizPackage.lastIndexOf('.') + 1);
+ Path pagingDir = moduleRoot.resolve(Paths.get(BASE_PACKAGE_PATH, "biz", group, "paging"));
+ String clientCls = (tool.clientSystemCode() != null && (tool.clientSystemCode().length() == 4 || tool.clientSystemCode().length() == 9))
+ ? mciClientClassName(tool.clientSystemCode())
+ : "AxhubMciComponent";
+ String targetPkg = mciTargetSystemPackage(tool.clientSystemCode());
+ String convPkg = bizPackage + ".converter" + (targetPkg != null ? "." + targetPkg : "");
+ String convName = (tool.clientSystemCode() != null && !tool.clientSystemCode().isBlank())
+ ? tool.clientSystemCode().toUpperCase(Locale.ROOT) + "Converter"
+ : baseName + "Converter";
+ String recSvcId = tool.clientSystemCode() != null ? tool.clientSystemCode().toUpperCase() : "";
+ writePagingComponents(pagingDir, bizPackage, baseName, pagingMode, ioPackage, ioPrefix, clientCls, convPkg, convName, tool.interfaceId(), recSvcId);
+ log.append("Generated ").append(pagingMode).append(" paging components for ").append(baseName).append(".\n");
+ }
writeUtf8(dtoDir.resolve(baseName + "Request.java"),
- dtoContent(bizPackage + ".dto", baseName + "Request", tool.inputFields(), "", "", true));
+ dtoContent(bizPackage + ".dto", baseName + "Request", tool.inputFields(), "", "", true, pagingMode));
writeUtf8(dtoDir.resolve(baseName + "Response.java"),
- dtoContent(bizPackage + ".dto", baseName + "Response", tool.outputFields(), "", "", false));
+ dtoContent(bizPackage + ".dto", baseName + "Response", tool.outputFields(), "", "", false, pagingMode));
writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Request", tool.inputFields());
writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", tool.outputFields());
if (mci) {
- writeUtf8(ioDir.resolve(tool.interfaceId() + "_I.java"),
- mciIoContent("infra.itrf.mci." + code.replace("/", "."), tool.interfaceId() + "_I", tool.inputFields(), "", ""));
- writeUtf8(ioDir.resolve(tool.interfaceId() + "_O.java"),
- mciIoContent("infra.itrf.mci." + code.replace("/", "."), tool.interfaceId() + "_O", tool.outputFields(), "", ""));
- writeStructuredFieldTypes(ioDir, ioPackage + ".io", tool.interfaceId() + "_I", tool.inputFields());
- writeStructuredFieldTypes(ioDir, ioPackage + ".io", tool.interfaceId() + "_O", tool.outputFields());
+ writeUtf8(ioDir.resolve(ioPrefix + "_I.java"),
+ mciIoContent("infra.itrf.mci." + code.replace("/", "."), ioPrefix + "_I", tool.inputFields(), "", "", pagingMode));
+ writeUtf8(ioDir.resolve(ioPrefix + "_O.java"),
+ mciIoContent("infra.itrf.mci." + code.replace("/", "."), ioPrefix + "_O", tool.outputFields(), "", "", pagingMode));
+ writeStructuredFieldTypes(ioDir, ioPackage + ".io", ioPrefix + "_I", tool.inputFields());
+ writeStructuredFieldTypes(ioDir, ioPackage + ".io", ioPrefix + "_O", tool.outputFields());
String sysCode = tool.clientSystemCode();
- if (sysCode != null && sysCode.length() == 4) {
- String normalizedSysCode = sysCode.toLowerCase(Locale.ROOT);
- String clientCap = normalizedSysCode.substring(0, 1).toUpperCase(Locale.ROOT)
- + normalizedSysCode.substring(1);
+ if (sysCode != null && (sysCode.length() == 4 || sysCode.length() == 9)) {
+ String clientCap = toPascalCase(mciClientPrefix(sysCode));
+ String recSvcId = sysCode.toUpperCase();
+ String itrfId = tool.interfaceId();
writeUtf8(clientDir.resolve("Mci" + clientCap + "Client.java"),
- "package " + ioPackage + ";\n\nimport io.shinhanlife.dat.lib.integration.mci.component.AxhubMciComponent;\nimport io.shinhanlife.glow.communication.dto.Transfer;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n\n@Component\n@RequiredArgsConstructor\npublic class Mci" + clientCap + "Client {\n private final AxhubMciComponent mci;\n\n public Transfer callTo(String interfaceId, String dummy, Object mciReq, Class resType) throws Exception {\n return mci.callTo(interfaceId, dummy, mciReq, resType);\n }\n}\n");
+ "package " + ioPackage + ";\n\n"
+ + "import io.shinhanlife.dat.lib.integration.mci.component.AxhubMciComponent;\n"
+ + "import io.shinhanlife.glow.communication.dto.Transfer;\n"
+ + "import " + ioPackage + ".io." + ioPrefix + "_I;\n"
+ + "import " + ioPackage + ".io." + ioPrefix + "_O;\n"
+ + "import lombok.RequiredArgsConstructor;\n"
+ + "import org.springframework.stereotype.Component;\n\n"
+ + "@Component\n"
+ + "@RequiredArgsConstructor\n"
+ + "public class Mci" + clientCap + "Client {\n\n"
+ + " private static final String INTERFACE_ID = \"" + itrfId + "\";\n"
+ + " private static final String RECEIVE_SERVICE_ID = \"" + recSvcId + "\";\n\n"
+ + " private final AxhubMciComponent mciComponent;\n\n"
+ + " public Transfer callTo(String interfaceId, String receiveServiceId, Object mciReq, Class resType) {\n"
+ + " return mciComponent.callTo(interfaceId, receiveServiceId, mciReq, resType);\n"
+ + " }\n\n"
+ + " public " + ioPrefix + "_O call" + toPascalCase(ioPrefix) + "(" + ioPrefix + "_I request) {\n"
+ + " Transfer<" + ioPrefix + "_O> transfer = mciComponent.callTo(\n"
+ + " INTERFACE_ID, RECEIVE_SERVICE_ID, request, " + ioPrefix + "_O.class);\n"
+ + " return transfer == null ? null : transfer.getBody();\n"
+ + " }\n"
+ + "}\n");
} else {
writeUtf8(clientDir.resolve(baseName + "Client.java"), groupedMciClientContent(ioPackage, baseName, tool.interfaceId()));
}
- writeUtf8(sourceRoot.resolve(Paths.get("biz", tool.group().toLowerCase(Locale.ROOT), "converter", baseName + "Converter.java")),
- groupedMciConverterContent(bizPackage, baseName, ioPackage, tool.interfaceId()));
+ String targetPkg = mciTargetSystemPackage(tool.clientSystemCode());
+ String converterPkg = bizPackage + ".converter" + (targetPkg != null ? "." + targetPkg : "");
+ Path targetConverterDir = sourceRoot.resolve(Paths.get("biz", tool.group().toLowerCase(Locale.ROOT), "converter"));
+ if (targetPkg != null) {
+ for (String seg : targetPkg.split("\\.")) {
+ targetConverterDir = targetConverterDir.resolve(seg);
+ }
+ }
+ Files.createDirectories(targetConverterDir);
+ String converterName = (tool.clientSystemCode() != null && !tool.clientSystemCode().isBlank())
+ ? tool.clientSystemCode().toUpperCase(Locale.ROOT) + "Converter"
+ : baseName + "Converter";
+ writeUtf8(targetConverterDir.resolve(converterName + ".java"),
+ groupedMciConverterContent(converterPkg, bizPackage, baseName, ioPackage, ioPrefix, converterName));
} else {
writeUtf8(ioDir.resolve(baseName + "HttpRequest.java"),
dtoContent(ioPackage + ".io", baseName + "HttpRequest", tool.inputFields(), "", "", true));
@@ -210,8 +354,7 @@ public class ToolScaffolder {
groupedHttpConverterContent(bizPackage, baseName, ioPackage));
}
- String toolName = toToolName(moduleName, tool.group(), baseName);
- writeUtf8(mockDir.resolve(toolName + ".json"), mockResponseContent(tool.outputFields()));
+ String toolName = toToolName(moduleName, tool.group(), toolBaseName);
log.append("[Tool] ").append(toolName).append(" -> ").append(clientDir.resolve(baseName + "Client.java")).append("\n");
}
@@ -220,20 +363,22 @@ public class ToolScaffolder {
StringBuilder imports = new StringBuilder();
StringBuilder methods = new StringBuilder();
for (ToolMethodDefinition tool : tools) {
- String baseName = toPascalCase(tool.baseName());
+ boolean mci = "MCI".equalsIgnoreCase(tool.routingType());
+ String toolBaseName = toPascalCase(tool.baseName());
+ String baseName = mci ? abbreviatedMciSourceBaseName(toolBaseName) : toolBaseName;
imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n");
boolean isMutation = isMutationTool(baseName);
ToolDefinitionOptions opts = tool.definitionOptions() == null ? new ToolDefinitionOptions(null, null, null, null, null, null, null, null) : tool.definitionOptions();
-
- methods.append(" @McpTool(name = \"").append(toToolName(moduleName, tool.group(), baseName))
+
+ methods.append(" @McpTool(name = \"").append(toToolName(moduleName, tool.group(), toolBaseName))
.append("\", title = \"").append(javaText(option(tool.title(), baseName)))
.append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n")
.append(" @GrowToolHint(\n")
.append(" requiresApproval = ").append(isMutation).append(",\n")
.append(" categoryKey = \"").append(tool.group().toLowerCase(Locale.ROOT)).append("\",\n")
- .append(" timeoutMillis = 5000L,\n")
- .append(" retryMaxAttempts = 3,\n");
+ .append(" timeoutMillis = ").append(opts.timeoutMillisOrDefault()).append("L,\n")
+ .append(" retryMaxAttempts = ").append(opts.retryMaxAttemptsOrDefault()).append(",\n");
if (tool.interfaceId() != null && !tool.interfaceId().isBlank()) {
methods.append(" mappingId = \"").append(javaText(tool.interfaceId())).append("\",\n");
}
@@ -242,7 +387,7 @@ public class ToolScaffolder {
.append(" whenNotToUse = \"").append(javaText(opts.whenNotToUse() != null && !opts.whenNotToUse().isBlank() ? opts.whenNotToUse() : "정보 변경이나 실행 작업에는 사용하지 않습니다.")).append("\",\n")
.append(" ioLimits = \"").append(javaText(opts.ioLimits() != null && !opts.ioLimits().isBlank() ? opts.ioLimits() : "정의된 입력 항목만 허용하며 업무 결과만 반환합니다.")).append("\",\n")
.append(" displayDescription = \"").append(javaText(opts.displayDescription() != null && !opts.displayDescription().isBlank() ? opts.displayDescription() : option(tool.title(), baseName) + " 정보를 처리합니다.")).append("\",\n");
-
+
List examples = opts.exampleQueries();
if (examples == null || examples.isEmpty()) {
examples = List.of(option(tool.title(), baseName) + " 정보를 보여줘", option(tool.title(), baseName) + " 확인해줘", "현재 " + option(tool.title(), baseName) + " 알려줘");
@@ -252,7 +397,7 @@ public class ToolScaffolder {
.append("},\n")
.append(" destructive = ").append(isMutation).append(",\n")
.append(" idempotent = ").append(!isMutation).append(",\n");
-
+
List tags = opts.tags();
if (tags == null || tags.isEmpty()) {
tags = List.of(tool.group().toLowerCase(Locale.ROOT), isMutation ? "처리" : "조회");
@@ -277,63 +422,322 @@ public class ToolScaffolder {
StringBuilder fields = new StringBuilder();
StringBuilder methods = new StringBuilder();
for (ToolMethodDefinition tool : tools) {
- String baseName = toPascalCase(tool.baseName());
- String clientClassName;
- String clientVariable;
boolean mci = "MCI".equalsIgnoreCase(tool.routingType());
- if (mci) {
- String sysCode = tool.clientSystemCode().toLowerCase(Locale.ROOT);
- clientClassName = "Mci" + sysCode.substring(0, 1).toUpperCase(Locale.ROOT) + sysCode.substring(1) + "Client";
- clientVariable = "mci" + sysCode.substring(0, 1).toUpperCase(Locale.ROOT) + sysCode.substring(1) + "Client";
+ String toolBaseName = toPascalCase(tool.baseName());
+ String baseName = mci ? abbreviatedMciSourceBaseName(toolBaseName) : toolBaseName;
+ ToolDefinitionOptions options = tool.definitionOptions() == null
+ ? new ToolDefinitionOptions(null, null, null, null, null, List.of(), List.of(), null)
+ : tool.definitionOptions();
+ PagingMode pagingMode = mci ? options.pagingModeOrNone() : PagingMode.NONE;
+
+ if (pagingMode != PagingMode.NONE) {
+ String pagingSuffix = pagingMode == PagingMode.SCROLL ? "ScrollPaging" : "PageNumberPaging";
+ String pagingInfoType = pagingMode == PagingMode.SCROLL ? "ScrollPagingInfo" : "PgNumPagingInfo";
+ String pagingInterface = baseName + pagingSuffix;
+ String pagingVar = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + pagingSuffix;
+
+ imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
+ .append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n")
+ .append("import ").append(bizPackage).append(".paging.").append(pagingInterface).append(";\n")
+ .append("import io.shinhanlife.dat.lib.paging.MciPage;\n")
+ .append("import io.shinhanlife.dat.lib.paging.").append(pagingInfoType).append(";\n");
+
+ if (!fields.toString().contains(" " + pagingVar + ";")) {
+ fields.append(" private final ").append(pagingInterface).append(" ").append(pagingVar).append(";\n");
+ }
+ methods.append(groupedToolPagingMethodContent(tool, baseName, pagingVar, pagingMode, pagingInfoType));
} else {
- clientClassName = baseName + "Client";
- clientVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Client";
+ String clientClassName;
+ String clientVariable;
+ String ioPrefix = (tool.clientSystemCode() != null && !tool.clientSystemCode().isBlank()) ? tool.clientSystemCode().toUpperCase() : tool.interfaceId();
+ if (mci) {
+ clientClassName = mciClientClassName(tool.clientSystemCode());
+ clientVariable = "mci";
+ } else {
+ clientClassName = baseName + "Client";
+ clientVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Client";
+ }
+ String targetPkg = mci ? mciTargetSystemPackage(tool.clientSystemCode()) : null;
+ String converterPkg = bizPackage + ".converter" + (targetPkg != null ? "." + targetPkg : "");
+ String converterName = (mci && tool.clientSystemCode() != null && !tool.clientSystemCode().isBlank())
+ ? tool.clientSystemCode().toUpperCase(Locale.ROOT) + "Converter"
+ : baseName + "Converter";
+ String converterVariable = "converter";
+ String integrationPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." + formatClientSystemCode(tool.clientSystemCode(), ".")
+ : ".infra.itrf.http." + toPackageSegment(tool.httpApiName()));
+ imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
+ .append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n")
+ .append("import ").append(converterPkg).append(".").append(converterName).append(";\n")
+ .append("import ").append(integrationPackage).append(".").append(clientClassName).append(";\n")
+ .append("import ").append(integrationPackage).append(".io.").append(mci ? ioPrefix + "_I;\n" : baseName + "HttpRequest;\n")
+ .append("import ").append(integrationPackage).append(".io.").append(mci ? ioPrefix + "_O;\n" : baseName + "HttpResponse;\n");
+ if (mci) {
+ imports.append("import io.shinhanlife.glow.communication.dto.Transfer;\n");
+ }
+ if (!fields.toString().contains(" " + clientVariable + ";")) {
+ fields.append(" private final ").append(clientClassName).append(" ").append(clientVariable).append(";\n");
+ }
+ if (!fields.toString().contains(" " + converterVariable + ";")) {
+ fields.append(" private final ").append(converterName).append(" ").append(converterVariable).append(";\n");
+ }
+ methods.append(groupedToolMethodContent(tool, baseName, converterVariable, clientVariable, ioPrefix, mci));
}
- String converterVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Converter";
- String integrationPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." + (tool.clientSystemCode().length() == 4 ? tool.clientSystemCode().substring(0,3).toLowerCase(Locale.ROOT) + "." + tool.clientSystemCode().substring(3).toLowerCase(Locale.ROOT) : tool.clientSystemCode().toLowerCase(Locale.ROOT))
- : ".infra.itrf.http." + toPackageSegment(tool.httpApiName()));
- imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
- .append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n")
- .append("import ").append(bizPackage).append(".converter.").append(baseName).append("Converter;\n")
- .append("import ").append(integrationPackage).append(".").append(clientClassName).append(";\n")
- .append("import ").append(integrationPackage).append(".io.").append(mci ? tool.interfaceId() + "_I;\n" : baseName + "HttpRequest;\n")
- .append("import ").append(integrationPackage).append(".io.").append(mci ? tool.interfaceId() + "_O;\n" : baseName + "HttpResponse;\n");
- if (!fields.toString().contains(" " + clientVariable + ";")) {
- fields.append(" private final ").append(clientClassName).append(" ").append(clientVariable).append(";\n");
- }
- fields.append(" private final ").append(baseName).append("Converter ").append(converterVariable).append(";\n");
- methods.append(" @Override\n public ").append(baseName).append("Response ").append(tool.methodName())
- .append("(").append(baseName).append("Request req) {\n")
- .append(" ").append(mci ? tool.interfaceId() + "_I" : baseName + "HttpRequest").append(" request = ").append(converterVariable).append(".toRequest(req);\n")
- .append(" ").append(mci ? tool.interfaceId() + "_O" : baseName + "HttpResponse").append(" response = ").append(clientVariable)
- .append(mci ? ".callTo(\"" + tool.interfaceId() + "\", null, request, " + tool.interfaceId() + "_O.class).getBody();\n" : ".call(request, " + baseName + "HttpResponse.class);\n")
- .append(" ").append(baseName).append("Response toolResponse = ").append(converterVariable).append(".toResponse(response);\n")
- .append(" if (toolResponse == null) toolResponse = new ").append(baseName).append("Response();\n")
- .append(" toolResponse.setResultCode(\"SUCCESS\");\n")
- .append(" return toolResponse;\n }\n\n");
+ }
+ boolean hasMciOrPaging = tools.stream().anyMatch(t -> "MCI".equalsIgnoreCase(t.routingType()));
+ String slf4jAnno = hasMciOrPaging ? "@Slf4j\n" : "";
+ if (hasMciOrPaging) {
+ imports.append("import lombok.extern.slf4j.Slf4j;\n");
+ }
+ boolean hasPaging = tools.stream().anyMatch(t -> t.definitionOptions() != null && t.definitionOptions().pagingModeOrNone() != PagingMode.NONE);
+ if (hasPaging) {
+ methods.append("""
+
+ private void mergeResponseData(Object target, Object src) {
+ if (target == null || src == null) return;
+ for (java.lang.reflect.Field field : target.getClass().getDeclaredFields()) {
+ if (java.util.List.class.isAssignableFrom(field.getType())) {
+ field.setAccessible(true);
+ try {
+ java.util.List> srcList = (java.util.List>) field.get(src);
+ if (srcList != null && !srcList.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ java.util.List