3 Commits

Author SHA1 Message Date
jade
115e403497 fix(scaffold): apply target system code naming for MCI converter and abbreviate baseName
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 17s
2026-09-10 14:35:42 +09:00
jade
b20a4edfdd feat: abbreviate MCI scaffold source names
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 17s
2026-09-10 13:38:57 +09:00
jade
6c318ebeae feat: generate pods with Maven library dependency
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 28s
2026-09-10 11:22:27 +09:00
5 changed files with 220 additions and 62 deletions

View File

@@ -2762,7 +2762,7 @@ function initializeMciResponseNames(parsed) {
const labels = mciTransformLabels(); const labels = mciTransformLabels();
const baseName = rootName.replace(/_[OI]$/, '') || 'MciOutput'; const baseName = rootName.replace(/_[OI]$/, '') || 'MciOutput';
const dtoName = `${baseName}${labels.dto}`; const dtoName = `${baseName}${labels.dto}`;
const converterName = `${baseName}${labels.dto}Converter`; const converterName = `${baseName}Converter`;
document.getElementById('mciResponseClassName').value = dtoName; document.getElementById('mciResponseClassName').value = dtoName;
document.getElementById('mciConverterClassName').value = converterName; document.getElementById('mciConverterClassName').value = converterName;
mciResponseState.autoConverterName = converterName; mciResponseState.autoConverterName = converterName;
@@ -2999,9 +2999,14 @@ function downloadMciResponseSource(kind) {
} }
function converterNameFor(responseName) { function converterNameFor(responseName) {
return responseName && responseName.endsWith('Response') if (!responseName) return '';
? responseName.slice(0, -8) + 'Converter' if (responseName.endsWith('Response')) {
: (responseName ? responseName + 'Converter' : ''); return responseName.slice(0, -8) + 'Converter';
}
if (responseName.endsWith('Request')) {
return responseName.slice(0, -7) + 'Converter';
}
return responseName + 'Converter';
} }
function setMciResponseButtonBusy(button, busy, text) { function setMciResponseButtonBusy(button, busy, text) {

View File

@@ -73,12 +73,6 @@ public final class NewPodProjectScaffolder {
String serviceName = "was-" + shortName; String serviceName = "was-" + shortName;
write(root.resolve("settings.gradle"), """ write(root.resolve("settings.gradle"), """
rootProject.name = '%s' rootProject.name = '%s'
includeBuild('../dat-lib-datmt') {
dependencySubstitution {
substitute module('io.shinhanlife:dat-lib-datmt') using project(':dat-was-lib')
}
}
""".formatted(moduleName)); """.formatted(moduleName));
write(root.resolve("build.gradle"), """ write(root.resolve("build.gradle"), """
plugins { plugins {
@@ -94,9 +88,16 @@ public final class NewPodProjectScaffolder {
toolchain { languageVersion = JavaLanguageVersion.of(21) } toolchain { languageVersion = JavaLanguageVersion.of(21) }
} }
repositories { mavenCentral() } repositories {
// dat-lib-datmt에서 :dat-was-lib:publishToMavenLocal 실행 후
// Maven Local의 io.shinhanlife:dat-lib-datmt JAR/POM을 사용합니다.
// 신한라이프 이관 시 mavenLocal() 대신 사내 Nexus repository를 추가합니다.
mavenLocal()
mavenCentral()
}
dependencies { dependencies {
// includeBuild 없이 Maven 좌표로만 공통 MCP Server와 연동 기능을 사용합니다.
implementation 'io.shinhanlife:dat-lib-datmt:0.0.1-SNAPSHOT' implementation 'io.shinhanlife:dat-lib-datmt:0.0.1-SNAPSHOT'
} }
@@ -219,7 +220,9 @@ public final class NewPodProjectScaffolder {
## 공통 라이브러리 ## 공통 라이브러리
상위 workspace의 `dat-lib-datmt`를 Gradle composite build로 참조합니다. `io.shinhanlife:dat-lib-datmt:0.0.1-SNAPSHOT` Maven 좌표로 공통 라이브러리를 참조합니다.
로컬 개발에서는 먼저 `dat-lib-datmt`에서 `:dat-was-lib:publishToMavenLocal`을 실행한 뒤 이 프로젝트를 빌드합니다.
신한라이프 이관 후에는 같은 Maven 좌표를 사내 Nexus에서 내려받도록 `mavenLocal()`을 Nexus repository로 교체합니다.
## 빌드 ## 빌드

View File

@@ -94,7 +94,11 @@ public class ToolScaffolder {
if (tools == null || tools.isEmpty()) { if (tools == null || tools.isEmpty()) {
throw new IllegalArgumentException("At least one Tool method is required."); throw new IllegalArgumentException("At least one Tool method is required.");
} }
boolean hasMci = tools.stream().anyMatch(t -> "MCI".equalsIgnoreCase(t.routingType()));
String useCaseBaseName = toPascalCase(useCaseName); String useCaseBaseName = toPascalCase(useCaseName);
if (hasMci) {
useCaseBaseName = abbreviatedMciSourceBaseName(useCaseBaseName);
}
String group = tools.getFirst().group().toLowerCase(Locale.ROOT); String group = tools.getFirst().group().toLowerCase(Locale.ROOT);
validateToolMethods(tools, group); validateToolMethods(tools, group);
@@ -128,8 +132,10 @@ public class ToolScaffolder {
} else { } else {
writeUtf8(useCaseFile, groupedUseCaseContent(bizPackage, useCaseBaseName, moduleName, tools)); writeUtf8(useCaseFile, groupedUseCaseContent(bizPackage, useCaseBaseName, moduleName, tools));
writeUtf8(useCaseImplFile, groupedUseCaseImplContent(bizPackage, useCaseBaseName, tools)); writeUtf8(useCaseImplFile, groupedUseCaseImplContent(bizPackage, useCaseBaseName, tools));
if (!hasMci) {
writeUtf8(converterFile, groupedConverterContent(bizPackage, useCaseBaseName, tools)); writeUtf8(converterFile, groupedConverterContent(bizPackage, useCaseBaseName, tools));
} }
}
StringBuilder log = new StringBuilder("\n=========================================\n") StringBuilder log = new StringBuilder("\n=========================================\n")
.append(" Multi Tool Scaffolding Complete\n") .append(" Multi Tool Scaffolding Complete\n")
@@ -145,7 +151,9 @@ public class ToolScaffolder {
log.append("[HTTP Config] ").append(glowConfig).append("\n"); log.append("[HTTP Config] ").append(glowConfig).append("\n");
} }
} }
if (!hasMci) {
log.append("[Converter] ").append(converterFile).append("\n"); log.append("[Converter] ").append(converterFile).append("\n");
}
return log.toString(); return log.toString();
} }
@@ -206,8 +214,9 @@ public class ToolScaffolder {
private static void writeGroupedToolFiles(Path moduleRoot, Path sourceRoot, Path dtoDir, Path definitionDir, private static void writeGroupedToolFiles(Path moduleRoot, Path sourceRoot, Path dtoDir, Path definitionDir,
String bizPackage, ToolMethodDefinition tool, String bizPackage, ToolMethodDefinition tool,
String moduleName, StringBuilder log) throws IOException { String moduleName, StringBuilder log) throws IOException {
String baseName = toPascalCase(tool.baseName());
boolean mci = "MCI".equalsIgnoreCase(tool.routingType()); boolean mci = "MCI".equalsIgnoreCase(tool.routingType());
String toolBaseName = toPascalCase(tool.baseName());
String baseName = mci ? abbreviatedMciSourceBaseName(toolBaseName) : toolBaseName;
String code = mci ? formatClientSystemCode(tool.clientSystemCode(), "/") : toPackageSegment(tool.httpApiName()); String code = mci ? formatClientSystemCode(tool.clientSystemCode(), "/") : toPackageSegment(tool.httpApiName());
String ioPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." : ".infra.itrf.http.") + code.replace("/", "."); 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 clientDir = sourceRoot.resolve(Paths.get("infra", "itrf", mci ? "mci" : "http", code));
@@ -258,8 +267,20 @@ public class ToolScaffolder {
} else { } else {
writeUtf8(clientDir.resolve(baseName + "Client.java"), groupedMciClientContent(ioPackage, baseName, tool.interfaceId())); 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")), String targetPkg = mciTargetSystemPackage(tool.clientSystemCode());
groupedMciConverterContent(bizPackage, baseName, ioPackage, ioPrefix)); 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 { } else {
writeUtf8(ioDir.resolve(baseName + "HttpRequest.java"), writeUtf8(ioDir.resolve(baseName + "HttpRequest.java"),
dtoContent(ioPackage + ".io", baseName + "HttpRequest", tool.inputFields(), "", "", true)); dtoContent(ioPackage + ".io", baseName + "HttpRequest", tool.inputFields(), "", "", true));
@@ -273,7 +294,7 @@ public class ToolScaffolder {
groupedHttpConverterContent(bizPackage, baseName, ioPackage)); groupedHttpConverterContent(bizPackage, baseName, ioPackage));
} }
String toolName = toToolName(moduleName, tool.group(), baseName); String toolName = toToolName(moduleName, tool.group(), toolBaseName);
log.append("[Tool] ").append(toolName).append(" -> ").append(clientDir.resolve(baseName + "Client.java")).append("\n"); log.append("[Tool] ").append(toolName).append(" -> ").append(clientDir.resolve(baseName + "Client.java")).append("\n");
} }
@@ -282,13 +303,15 @@ public class ToolScaffolder {
StringBuilder imports = new StringBuilder(); StringBuilder imports = new StringBuilder();
StringBuilder methods = new StringBuilder(); StringBuilder methods = new StringBuilder();
for (ToolMethodDefinition tool : tools) { 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") 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(".dto.").append(baseName).append("Response;\n");
boolean isMutation = isMutationTool(baseName); boolean isMutation = isMutationTool(baseName);
ToolDefinitionOptions opts = tool.definitionOptions() == null ? new ToolDefinitionOptions(null, null, null, null, null, null, null, null) : tool.definitionOptions(); 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("\", title = \"").append(javaText(option(tool.title(), baseName)))
.append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n") .append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n")
.append(" @GrowToolHint(\n") .append(" @GrowToolHint(\n")
@@ -339,10 +362,11 @@ public class ToolScaffolder {
StringBuilder fields = new StringBuilder(); StringBuilder fields = new StringBuilder();
StringBuilder methods = new StringBuilder(); StringBuilder methods = new StringBuilder();
for (ToolMethodDefinition tool : tools) { 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;
String clientClassName; String clientClassName;
String clientVariable; String clientVariable;
boolean mci = "MCI".equalsIgnoreCase(tool.routingType());
String ioPrefix = (tool.clientSystemCode() != null && !tool.clientSystemCode().isBlank()) ? tool.clientSystemCode().toUpperCase() : tool.interfaceId(); String ioPrefix = (tool.clientSystemCode() != null && !tool.clientSystemCode().isBlank()) ? tool.clientSystemCode().toUpperCase() : tool.interfaceId();
if (mci) { if (mci) {
clientClassName = mciClientClassName(tool.clientSystemCode()); clientClassName = mciClientClassName(tool.clientSystemCode());
@@ -351,12 +375,17 @@ public class ToolScaffolder {
clientClassName = baseName + "Client"; clientClassName = baseName + "Client";
clientVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "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 converterVariable = "converter";
String integrationPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." + formatClientSystemCode(tool.clientSystemCode(), ".") String integrationPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." + formatClientSystemCode(tool.clientSystemCode(), ".")
: ".infra.itrf.http." + toPackageSegment(tool.httpApiName())); : ".infra.itrf.http." + toPackageSegment(tool.httpApiName()));
imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n") 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(".dto.").append(baseName).append("Response;\n")
.append("import ").append(bizPackage).append(".converter.").append(baseName).append("Converter;\n") .append("import ").append(converterPkg).append(".").append(converterName).append(";\n")
.append("import ").append(integrationPackage).append(".").append(clientClassName).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 + "_I;\n" : baseName + "HttpRequest;\n")
.append("import ").append(integrationPackage).append(".io.").append(mci ? ioPrefix + "_O;\n" : baseName + "HttpResponse;\n"); .append("import ").append(integrationPackage).append(".io.").append(mci ? ioPrefix + "_O;\n" : baseName + "HttpResponse;\n");
@@ -368,7 +397,7 @@ public class ToolScaffolder {
fields.append(" private final ").append(clientClassName).append(" ").append(clientVariable).append(";\n"); fields.append(" private final ").append(clientClassName).append(" ").append(clientVariable).append(";\n");
} }
if (!fields.toString().contains(" " + converterVariable + ";")) { if (!fields.toString().contains(" " + converterVariable + ";")) {
fields.append(" private final ").append(baseName).append("Converter ").append(converterVariable).append(";\n"); fields.append(" private final ").append(converterName).append(" ").append(converterVariable).append(";\n");
} }
methods.append(groupedToolMethodContent(tool, baseName, converterVariable, clientVariable, ioPrefix, mci)); methods.append(groupedToolMethodContent(tool, baseName, converterVariable, clientVariable, ioPrefix, mci));
} }
@@ -444,15 +473,15 @@ public class ToolScaffolder {
+ "public interface " + useCaseBaseName + "Converter {\n}\n"; + "public interface " + useCaseBaseName + "Converter {\n}\n";
} }
private static String groupedMciConverterContent(String bizPackage, String baseName, String ioPackage, String interfaceId) { private static String groupedMciConverterContent(String converterPackage, String bizPackage, String baseName, String ioPackage, String interfaceId, String converterName) {
return "package " + bizPackage + ".converter;\n\n" return "package " + converterPackage + ";\n\n"
+ "import " + bizPackage + ".dto." + baseName + "Request;\n" + "import " + bizPackage + ".dto." + baseName + "Request;\n"
+ "import " + bizPackage + ".dto." + baseName + "Response;\n" + "import " + bizPackage + ".dto." + baseName + "Response;\n"
+ "import " + ioPackage + ".io." + interfaceId + "_I;\n" + "import " + ioPackage + ".io." + interfaceId + "_I;\n"
+ "import " + ioPackage + ".io." + interfaceId + "_O;\n" + "import " + ioPackage + ".io." + interfaceId + "_O;\n"
+ "import org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n" + "import org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n"
+ "@Mapper(componentModel = \"spring\", unmappedTargetPolicy = ReportingPolicy.IGNORE)\n" + "@Mapper(componentModel = \"spring\", unmappedTargetPolicy = ReportingPolicy.IGNORE)\n"
+ "public interface " + baseName + "Converter {\n" + "public interface " + converterName + " {\n"
+ " " + interfaceId + "_I toRequest(" + baseName + "Request request);\n" + " " + interfaceId + "_I toRequest(" + baseName + "Request request);\n"
+ " " + baseName + "Response toResponse(" + interfaceId + "_O response);\n}\n"; + " " + baseName + "Response toResponse(" + interfaceId + "_O response);\n}\n";
} }
@@ -479,14 +508,15 @@ public class ToolScaffolder {
String useCase = Files.readString(useCaseFile, StandardCharsets.UTF_8); String useCase = Files.readString(useCaseFile, StandardCharsets.UTF_8);
String implementation = Files.readString(useCaseImplFile, StandardCharsets.UTF_8); String implementation = Files.readString(useCaseImplFile, StandardCharsets.UTF_8);
for (ToolMethodDefinition tool : tools) { 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;
String methodName = tool.methodName(); String methodName = tool.methodName();
String toolName = toToolName(moduleName, tool.group(), baseName); String toolName = toToolName(moduleName, tool.group(), toolBaseName);
if (useCase.matches("(?s).*\\b" + java.util.regex.Pattern.quote(methodName) + "\\s*\\(.*") if (useCase.matches("(?s).*\\b" + java.util.regex.Pattern.quote(methodName) + "\\s*\\(.*")
|| useCase.contains("name = \"" + toolName + "\"")) { || useCase.contains("name = \"" + toolName + "\"")) {
throw new IllegalArgumentException("Tool method or MCP Tool name already exists: " + methodName); throw new IllegalArgumentException("Tool method or MCP Tool name already exists: " + methodName);
} }
boolean mci = "MCI".equalsIgnoreCase(tool.routingType());
String integrationPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." + formatClientSystemCode(tool.clientSystemCode(), ".") String integrationPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." + formatClientSystemCode(tool.clientSystemCode(), ".")
: ".infra.itrf.http." + toPackageSegment(tool.httpApiName())); : ".infra.itrf.http." + toPackageSegment(tool.httpApiName()));
String requestType = baseName + "Request"; String requestType = baseName + "Request";
@@ -551,11 +581,16 @@ public class ToolScaffolder {
clientClassName = baseName + "Client"; clientClassName = baseName + "Client";
clientVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "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 converterVariable = "converter";
implementation = addImport(implementation, "import " + bizPackage + ".dto." + requestType + ";"); implementation = addImport(implementation, "import " + bizPackage + ".dto." + requestType + ";");
implementation = addImport(implementation, "import " + bizPackage + ".dto." + responseType + ";"); implementation = addImport(implementation, "import " + bizPackage + ".dto." + responseType + ";");
implementation = addImport(implementation, "import " + bizPackage + ".converter." + baseName + "Converter;"); implementation = addImport(implementation, "import " + converterPkg + "." + converterName + ";");
implementation = addImport(implementation, "import " + integrationPackage + "." + clientClassName + ";"); implementation = addImport(implementation, "import " + integrationPackage + "." + clientClassName + ";");
implementation = addImport(implementation, "import " + integrationPackage + ".io." + requestIo + ";"); implementation = addImport(implementation, "import " + integrationPackage + ".io." + requestIo + ";");
implementation = addImport(implementation, "import " + integrationPackage + ".io." + responseIo + ";"); implementation = addImport(implementation, "import " + integrationPackage + ".io." + responseIo + ";");
@@ -574,14 +609,15 @@ public class ToolScaffolder {
implementation = insertConstructorField(implementation, " private final " + clientClassName + " " + clientVariable + ";"); implementation = insertConstructorField(implementation, " private final " + clientClassName + " " + clientVariable + ";");
} }
if (!implementation.contains(" " + converterVariable + ";")) { if (!implementation.contains(" " + converterVariable + ";")) {
implementation = insertConstructorField(implementation, " private final " + baseName + "Converter " + converterVariable + ";"); implementation = insertConstructorField(implementation, " private final " + converterName + " " + converterVariable + ";");
} }
String method = groupedToolMethodContent(tool, baseName, converterVariable, clientVariable, ioPrefix, mci); String method = groupedToolMethodContent(tool, baseName, converterVariable, clientVariable, ioPrefix, mci);
implementation = insertBeforeLastBrace(implementation, method); implementation = insertBeforeLastBrace(implementation, method);
} }
writeUtf8(useCaseFile, useCase); writeUtf8(useCaseFile, useCase);
writeUtf8(useCaseImplFile, implementation); writeUtf8(useCaseImplFile, implementation);
if (!Files.exists(converterFile)) { boolean hasMci = tools.stream().anyMatch(t -> "MCI".equalsIgnoreCase(t.routingType()));
if (!hasMci && !Files.exists(converterFile)) {
writeUtf8(converterFile, groupedConverterContent(bizPackage, useCaseBaseName, tools)); writeUtf8(converterFile, groupedConverterContent(bizPackage, useCaseBaseName, tools));
} }
} }
@@ -736,13 +772,35 @@ public class ToolScaffolder {
String outputSchemaResource, List<FieldDefinition> inputFields, String outputSchemaResource, List<FieldDefinition> inputFields,
List<FieldDefinition> outputFields, String httpApiName, List<FieldDefinition> outputFields, String httpApiName,
ToolDefinitionOptions definitionOptions) throws IOException { ToolDefinitionOptions definitionOptions) throws IOException {
baseName = toPascalCase(baseName); return scaffoldWithAbbreviatedMciSources(baseName, interfaceId, title, description, group, routingType,
title = title == null || title.isBlank() ? baseName : title.trim(); moduleName, author, createDate, register, clientSystemCode, inputSchemaResource,
outputSchemaResource, inputFields, outputFields, httpApiName, definitionOptions);
}
/**
* Generates MCI source files from an abbreviation derived from the Base Name. The Base Name remains the MCP Tool name.
*/
private static String scaffoldWithAbbreviatedMciSources(String baseName, String interfaceId, String title,
String description, String group, String routingType,
String moduleName, String author, String createDate,
boolean register, String clientSystemCode,
String inputSchemaResource, String outputSchemaResource,
List<FieldDefinition> inputFields,
List<FieldDefinition> outputFields, String httpApiName,
ToolDefinitionOptions definitionOptions) throws IOException {
boolean isMci = "MCI".equalsIgnoreCase(routingType);
boolean isHttp = "HTTP".equalsIgnoreCase(routingType);
if (!isMci && !isHttp) {
throw new IllegalArgumentException("Unsupported routing type: " + routingType + ". Only MCI and HTTP are supported.");
}
String toolBaseName = toPascalCase(baseName);
baseName = isMci ? abbreviatedMciSourceBaseName(toolBaseName) : toolBaseName;
title = title == null || title.isBlank() ? toolBaseName : title.trim();
description = description == null ? "" : description.trim(); description = description == null ? "" : description.trim();
definitionOptions = definitionOptions == null definitionOptions = definitionOptions == null
? new ToolDefinitionOptions(null, null, null, null, null, List.of(), List.of(), null) ? new ToolDefinitionOptions(null, null, null, null, null, List.of(), List.of(), null)
: definitionOptions; : definitionOptions;
httpApiName = httpApiName == null || httpApiName.isBlank() ? toKebabCase(baseName) : httpApiName.trim(); httpApiName = httpApiName == null || httpApiName.isBlank() ? toKebabCase(toolBaseName) : httpApiName.trim();
String envSourceDir = System.getProperty("AXHUB_SOURCE_DIR"); String envSourceDir = System.getProperty("AXHUB_SOURCE_DIR");
if (envSourceDir == null) { if (envSourceDir == null) {
envSourceDir = System.getenv("AXHUB_SOURCE_DIR"); envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
@@ -754,11 +812,10 @@ public class ToolScaffolder {
Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "dto")); Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "dto"));
Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "legacy")); Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "legacy"));
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter"));
// Schema Resource 파일 경로(useSchemaResource=true일 때만 생성) // Schema Resource 파일 경로(useSchemaResource=true일 때만 생성)
boolean useSchemaResource = (inputSchemaResource != null && !inputSchemaResource.trim().isEmpty()) || (outputSchemaResource != null && !outputSchemaResource.trim().isEmpty()); boolean useSchemaResource = (inputSchemaResource != null && !inputSchemaResource.trim().isEmpty()) || (outputSchemaResource != null && !outputSchemaResource.trim().isEmpty());
String schemaBaseName = toKebabCase(baseName); String schemaBaseName = toKebabCase(toolBaseName);
String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json"; String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json";
String outputSchemaFileName = schemaBaseName + "-resource-output-schema.json"; String outputSchemaFileName = schemaBaseName + "-resource-output-schema.json";
Path schemaDir = rootDir.resolve(Paths.get(moduleName, "src/main/resources/tool-schemas", group.toLowerCase())); Path schemaDir = rootDir.resolve(Paths.get(moduleName, "src/main/resources/tool-schemas", group.toLowerCase()));
@@ -767,11 +824,6 @@ public class ToolScaffolder {
String outputSchemaClasspath = "classpath:tool-schemas/" + group.toLowerCase() + "/" + outputSchemaFileName; String outputSchemaClasspath = "classpath:tool-schemas/" + group.toLowerCase() + "/" + outputSchemaFileName;
String bizPackage = BASE_PACKAGE + ".biz." + group.toLowerCase(); String bizPackage = BASE_PACKAGE + ".biz." + group.toLowerCase();
boolean isMci = "MCI".equalsIgnoreCase(routingType);
boolean isHttp = "HTTP".equalsIgnoreCase(routingType);
if (!isMci && !isHttp) {
throw new IllegalArgumentException("Unsupported routing type: " + routingType + ". Only MCI and HTTP are supported.");
}
String ioPrefix = (clientSystemCode != null && !clientSystemCode.isBlank()) ? clientSystemCode.toUpperCase() : interfaceId; String ioPrefix = (clientSystemCode != null && !clientSystemCode.isBlank()) ? clientSystemCode.toUpperCase() : interfaceId;
String mciGroupPath = "infra/itrf/mci/" + group.toLowerCase(); String mciGroupPath = "infra/itrf/mci/" + group.toLowerCase();
String clientPrefixCap = ""; String clientPrefixCap = "";
@@ -784,6 +836,21 @@ public class ToolScaffolder {
mciClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath)); mciClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath));
} }
String converterPackage = bizPackage + ".converter";
String targetSystemPackage = isMci ? mciTargetSystemPackage(clientSystemCode) : null;
if (targetSystemPackage != null) {
converterPackage += "." + targetSystemPackage;
}
String converterClassName = (isMci && clientSystemCode != null && !clientSystemCode.isBlank())
? clientSystemCode.toUpperCase(Locale.ROOT) + "Converter"
: baseName + "Converter";
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter"));
if (targetSystemPackage != null) {
for (String segment : targetSystemPackage.split("\\.")) {
converterDir = converterDir.resolve(segment);
}
}
Path mciIoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath, "io")); Path mciIoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath, "io"));
String httpApiPackage = toPackageSegment(httpApiName); String httpApiPackage = toPackageSegment(httpApiName);
String httpApiClass = toPascalCase(httpApiName); String httpApiClass = toPascalCase(httpApiName);
@@ -886,9 +953,9 @@ public class ToolScaffolder {
writeUtf8(dtoDir.resolve(baseName + "Response.java"), resContent); writeUtf8(dtoDir.resolve(baseName + "Response.java"), resContent);
writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", outputFields); writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", outputFields);
String toolName = toToolName(moduleName, group, baseName); String toolName = toToolName(moduleName, group, toolBaseName);
boolean isMutation = isMutationTool(baseName); boolean isMutation = isMutationTool(toolBaseName);
StringBuilder sb = new StringBuilder(" @GrowToolHint(\n"); StringBuilder sb = new StringBuilder(" @GrowToolHint(\n");
if (useSchemaResource) { if (useSchemaResource) {
sb.append(" inputSchemaResource = \"").append(inputSchemaClasspath).append("\",\n"); sb.append(" inputSchemaResource = \"").append(inputSchemaClasspath).append("\",\n");
@@ -988,7 +1055,7 @@ public class ToolScaffolder {
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import %s.converter.%sConverter; import %s.%s;
import %s.%s.io.%s_I; import %s.%s.io.%s_I;
import %s.%s.io.%s_O; import %s.%s.io.%s_O;
%s %s
@@ -1013,7 +1080,7 @@ public class ToolScaffolder {
public class %sUseCaseImpl implements %sUseCase { public class %sUseCaseImpl implements %sUseCase {
%s %s
private final %sConverter converter; private final %s converter;
@Override @Override
public %sResponse execute(%sRequest req) { public %sResponse execute(%sRequest req) {
@@ -1062,7 +1129,7 @@ public class ToolScaffolder {
bizPackage, baseName, bizPackage, baseName,
bizPackage, baseName, bizPackage, baseName,
bizPackage, baseName, bizPackage, baseName,
bizPackage, baseName, converterPackage, converterClassName,
BASE_PACKAGE, mciGroupPath.replace("/", "."), ioPrefix, BASE_PACKAGE, mciGroupPath.replace("/", "."), ioPrefix,
BASE_PACKAGE, mciGroupPath.replace("/", "."), ioPrefix, BASE_PACKAGE, mciGroupPath.replace("/", "."), ioPrefix,
(clientPrefixCap.isEmpty() ? "" : "import " + BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".Mci" + clientPrefixCap + "Client;\n"), (clientPrefixCap.isEmpty() ? "" : "import " + BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".Mci" + clientPrefixCap + "Client;\n"),
@@ -1074,7 +1141,7 @@ public class ToolScaffolder {
baseName, baseName,
baseName, baseName,
(clientPrefixCap.isEmpty() ? "private final AxhubMciComponent mci;" : "private final Mci" + clientPrefixCap + "Client mci;"), (clientPrefixCap.isEmpty() ? "private final AxhubMciComponent mci;" : "private final Mci" + clientPrefixCap + "Client mci;"),
baseName, converterClassName,
baseName, baseName,
baseName, baseName,
toolName, toolName,
@@ -1279,8 +1346,8 @@ public class ToolScaffolder {
baseName, ioPrefix, baseName, ioPrefix,
baseName, ioPrefix baseName, ioPrefix
); );
converterContent = mciConverterContent(bizPackage, baseName, mciGroupPath.replace("/", "."), ioPrefix); converterContent = mciConverterContent(converterPackage, bizPackage, baseName, mciGroupPath.replace("/", "."), ioPrefix, converterClassName);
writeUtf8(converterDir.resolve(baseName + "Converter.java"), converterContent); writeUtf8(converterDir.resolve(converterClassName + ".java"), converterContent);
log.append("\n=========================================\n"); log.append("\n=========================================\n");
log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n"); log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n");
@@ -1291,7 +1358,7 @@ public class ToolScaffolder {
log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n"); log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n");
log.append("[MCI Request IO] ").append(mciIoDir.resolve(interfaceId + "_I.java")).append("\n"); log.append("[MCI Request IO] ").append(mciIoDir.resolve(interfaceId + "_I.java")).append("\n");
log.append("[MCI Response IO] ").append(mciIoDir.resolve(interfaceId + "_O.java")).append("\n"); log.append("[MCI Response IO] ").append(mciIoDir.resolve(interfaceId + "_O.java")).append("\n");
log.append("[MCI Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n"); log.append("[MCI Converter] ").append(converterDir.resolve(converterClassName + ".java")).append("\n");
if (!clientPrefixCap.isEmpty()) { if (!clientPrefixCap.isEmpty()) {
String receiveServiceId = (clientSystemCode != null && !clientSystemCode.isBlank()) ? clientSystemCode.toUpperCase() : ""; String receiveServiceId = (clientSystemCode != null && !clientSystemCode.isBlank()) ? clientSystemCode.toUpperCase() : "";
@@ -2140,9 +2207,10 @@ public class ToolScaffolder {
.replaceAll("^_+|_+$", ""); .replaceAll("^_+|_+$", "");
return normalized.isBlank() ? "http_api" : normalized; return normalized.isBlank() ? "http_api" : normalized;
} }
private static String mciConverterContent(String bizPackage, String baseName, String mciPackage, String interfaceId) { private static String mciConverterContent(String converterPackage, String bizPackage, String baseName,
String mciPackage, String interfaceId, String converterClassName) {
return """ return """
package %s.converter; package %s;
import %s.dto.%sRequest; import %s.dto.%sRequest;
import %s.dto.%sResponse; import %s.dto.%sResponse;
@@ -2153,16 +2221,24 @@ public class ToolScaffolder {
import org.mapstruct.ReportingPolicy; import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface %sConverter { public interface %s {
// Field names differ? Add mappings like this before the method. // Field names differ? Add mappings like this before the method.
// @Mapping(source = "sourceField", target = "targetField") // @Mapping(source = "sourceField", target = "targetField")
%s_I toLegacyRequest(%sRequest request); %s_I toLegacyRequest(%sRequest request);
%sRequest toRequest(%s_I mciRequest); %sRequest toRequest(%s_I mciRequest);
%sResponse toResponse(%s_O mciRes); %sResponse toResponse(%s_O mciRes);
} }
""".formatted(bizPackage, bizPackage, baseName, bizPackage, baseName, """.formatted(converterPackage, bizPackage, baseName, bizPackage, baseName,
BASE_PACKAGE, mciPackage, interfaceId, BASE_PACKAGE, mciPackage, interfaceId, BASE_PACKAGE, mciPackage, interfaceId, BASE_PACKAGE, mciPackage, interfaceId,
baseName, interfaceId, baseName, baseName, interfaceId, baseName, interfaceId); converterClassName, interfaceId, baseName, baseName, interfaceId, baseName, interfaceId);
}
private static String mciTargetSystemPackage(String clientSystemCode) {
if (clientSystemCode == null || clientSystemCode.length() != 9) {
return null;
}
String prefix = clientSystemCode.substring(1, 5).toLowerCase(Locale.ROOT);
return prefix.substring(0, 3) + "." + prefix.substring(3);
} }
private static String legacyConverterContent(String bizPackage, String baseName) { private static String legacyConverterContent(String bizPackage, String baseName) {
@@ -2364,6 +2440,19 @@ public class ToolScaffolder {
action); action);
} }
private static String abbreviatedMciSourceBaseName(String baseName) {
String[] words = baseName.split("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])");
if (words.length < 3) {
return baseName;
}
return abbreviatedWord(words[0], 4) + abbreviatedWord(words[1], 6);
}
private static String abbreviatedWord(String word, int maximumLength) {
int length = Math.min(word.length(), maximumLength);
return toPascalCase(word.substring(0, length).toLowerCase(Locale.ROOT));
}
private static String toPascalCase(String str) { private static String toPascalCase(String str) {
if (str == null || str.isEmpty()) { if (str == null || str.isEmpty()) {
return str; return str;

View File

@@ -14,7 +14,7 @@ class NewPodProjectScaffolderTest {
Path workspace; Path workspace;
@Test @Test
void createsStandaloneProjectWithCompositeBuild() throws Exception { void createsStandaloneProjectWithMavenLibraryDependency() throws Exception {
createLibraryProject(); createLibraryProject();
String result = NewPodProjectScaffolder.scaffold(workspace, "dat-was-payment", 8099, String result = NewPodProjectScaffolder.scaffold(workspace, "dat-was-payment", 8099,
"tester", "2026.09.09"); "tester", "2026.09.09");
@@ -27,10 +27,14 @@ class NewPodProjectScaffolderTest {
assertTrue(Files.exists(project.resolve("k8s/base/deployment.yaml"))); assertTrue(Files.exists(project.resolve("k8s/base/deployment.yaml")));
assertTrue(Files.exists(project.resolve("gradlew.bat"))); assertTrue(Files.exists(project.resolve("gradlew.bat")));
assertTrue(Files.exists(project.resolve("gradle/wrapper/gradle-wrapper.jar"))); assertTrue(Files.exists(project.resolve("gradle/wrapper/gradle-wrapper.jar")));
assertTrue(Files.readString(project.resolve("settings.gradle")) String settings = Files.readString(project.resolve("settings.gradle"));
.contains("includeBuild('../dat-lib-datmt')")); String build = Files.readString(project.resolve("build.gradle"));
assertTrue(Files.readString(project.resolve("build.gradle")) assertTrue(!settings.contains("includeBuild('../dat-lib-datmt')"));
.contains("io.shinhanlife:dat-lib-datmt:0.0.1-SNAPSHOT")); assertTrue(build.contains("mavenLocal()"));
assertTrue(build.contains("io.shinhanlife:dat-lib-datmt:0.0.1-SNAPSHOT"));
assertTrue(build.contains("publishToMavenLocal"));
assertTrue(Files.readString(project.resolve("README.md")).contains("publishToMavenLocal"));
assertTrue(!Files.readString(project.resolve("README.md")).contains("composite build"));
assertTrue(Files.readString(project.resolve("src/main/resources/application.yml")) assertTrue(Files.readString(project.resolve("src/main/resources/application.yml"))
.contains("port: ${PORT:8099}")); .contains("port: ${PORT:8099}"));
assertTrue(Files.readString(project.resolve("src/main/resources/application.yml")) assertTrue(Files.readString(project.resolve("src/main/resources/application.yml"))

View File

@@ -539,4 +539,61 @@ class ToolScaffolderTest {
assertTrue(implementation.contains("setResultCode(\"ERROR\")"), implementation); assertTrue(implementation.contains("setResultCode(\"ERROR\")"), implementation);
assertFalse(implementation.contains("LCHITP00001\", null, request, ONCSG1341_O.class).getBody()"), implementation); assertFalse(implementation.contains("LCHITP00001\", null, request, ONCSG1341_O.class).getBody()"), implementation);
} }
@Test
void generatesAbbreviatedMciSourcesAndTargetSystemConverterPackage() throws Exception {
String moduleName = root.resolve("dat-was-pro").toString();
ToolScaffolder.scaffold("individual customer detail inquiry", "LCHITP00001",
"개인 고객 상세 조회", "개인 고객 상세 정보를 조회합니다.",
"pro", "MCI", moduleName, "tester", "2026.09.10", false,
"ONBTA2380", null, null, List.of(), List.of(), null, null);
Path sourceRoot = root.resolve("dat-was-pro/src/main/java/io/shinhanlife/dat/mcc/biz/pro");
Path converter = sourceRoot.resolve("converter/nbt/a/ONBTA2380Converter.java");
assertTrue(Files.exists(converter));
assertFalse(Files.exists(sourceRoot.resolve("converter/IndiCustomConverter.java")));
assertFalse(Files.exists(sourceRoot.resolve("converter/nbt/a/IndiCustomConverter.java")));
assertTrue(Files.exists(sourceRoot.resolve("dto/IndiCustomRequest.java")));
assertTrue(Files.exists(sourceRoot.resolve("dto/IndiCustomResponse.java")));
assertTrue(Files.exists(sourceRoot.resolve("usecase/IndiCustomUseCase.java")));
assertTrue(Files.exists(sourceRoot.resolve("usecase/impl/IndiCustomUseCaseImpl.java")));
String converterSource = Files.readString(converter);
String useCaseSource = Files.readString(sourceRoot.resolve("usecase/IndiCustomUseCase.java"));
String implementationSource = Files.readString(sourceRoot.resolve("usecase/impl/IndiCustomUseCaseImpl.java"));
assertTrue(converterSource.contains("package io.shinhanlife.dat.mcc.biz.pro.converter.nbt.a;"), converterSource);
assertTrue(converterSource.contains("public interface ONBTA2380Converter {"), converterSource);
assertTrue(implementationSource.contains("import io.shinhanlife.dat.mcc.biz.pro.converter.nbt.a.ONBTA2380Converter;"), implementationSource);
assertTrue(implementationSource.contains("private final ONBTA2380Converter converter;"), implementationSource);
assertTrue(useCaseSource.contains("name = \"pro_individual_inquiry\""), useCaseSource);
}
@Test
void generatesAbbreviatedMciSourcesAndTargetSystemConverterInGroupMode() throws Exception {
String moduleName = root.resolve("dat-was-pro").toString();
ToolScaffolder.scaffoldUseCase("IndividualCustomerDetailInquiry", moduleName, "tester", "2026.09.10", List.of(
new ToolScaffolder.ToolMethodDefinition("IndividualCustomerDetailInquiry", "inquiry", "LCHITP00001",
"개인 고객 상세 조회", "개인 고객 상세 정보를 조회합니다.", "pro", "MCI", false,
"ONBTA2380", null, List.of(), List.of(), null)));
Path sourceRoot = root.resolve("dat-was-pro/src/main/java/io/shinhanlife/dat/mcc/biz/pro");
Path converter = sourceRoot.resolve("converter/nbt/a/ONBTA2380Converter.java");
assertTrue(Files.exists(converter));
assertFalse(Files.exists(sourceRoot.resolve("converter/IndiCustomConverter.java")));
assertTrue(Files.exists(sourceRoot.resolve("dto/IndiCustomRequest.java")));
assertTrue(Files.exists(sourceRoot.resolve("dto/IndiCustomResponse.java")));
assertTrue(Files.exists(sourceRoot.resolve("usecase/IndiCustomUseCase.java")));
assertTrue(Files.exists(sourceRoot.resolve("usecase/impl/IndiCustomUseCaseImpl.java")));
String converterSource = Files.readString(converter);
String useCaseSource = Files.readString(sourceRoot.resolve("usecase/IndiCustomUseCase.java"));
String implementationSource = Files.readString(sourceRoot.resolve("usecase/impl/IndiCustomUseCaseImpl.java"));
assertTrue(converterSource.contains("package io.shinhanlife.dat.mcc.biz.pro.converter.nbt.a;"), converterSource);
assertTrue(converterSource.contains("public interface ONBTA2380Converter {"), converterSource);
assertTrue(implementationSource.contains("import io.shinhanlife.dat.mcc.biz.pro.converter.nbt.a.ONBTA2380Converter;"), implementationSource);
assertTrue(implementationSource.contains("private final ONBTA2380Converter converter;"), implementationSource);
assertTrue(useCaseSource.contains("name = \"pro_individual_inquiry\""), useCaseSource);
}
} }