feat: 모든 변수 표현식에 :기본값 문법 추가 및 JSON 필드 파싱 지원
- ${callprop.KEY:default} / ${callprop[path]:default} / ${MSG.path:default} 기본값 문법
- ${DATA.BIZDATA.acctNo} : StandardMessage String 값이 JSON이면 Gson으로 필드 추출
(중첩 경로 지원, 없는 필드는 빈 문자열 또는 기본값)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,8 @@ import java.util.regex.Pattern;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.message.StandardItem;
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
@@ -168,10 +170,55 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
||||
int colonIdx = expr.indexOf(':');
|
||||
String path = colonIdx >= 0 ? expr.substring(0, colonIdx) : expr;
|
||||
String defaultVal = colonIdx >= 0 ? expr.substring(colonIdx + 1) : "";
|
||||
String value = msg.findItemValue(path);
|
||||
String value = resolveMessagePath(msg, path);
|
||||
return StringUtils.isNotEmpty(value) ? value : defaultVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* StandardMessage 경로를 해석한다.
|
||||
* 1. findItemValue(path) 로 직접 조회한다.
|
||||
* 2. null 이면 경로를 뒤에서부터 분리하며, 부모 경로 값이 JSON 문자열인 경우
|
||||
* 나머지 경로(subPath)를 키로 JSON 필드를 추출한다.
|
||||
* 예) DATA.BIZDATA.acctNo → findItemValue("DATA.BIZDATA") → JSON 파싱 → acctNo
|
||||
* DATA.BIZDATA.addr.city → findItemValue("DATA.BIZDATA") → JSON 파싱 → addr.city
|
||||
*/
|
||||
private String resolveMessagePath(StandardMessage msg, String path) {
|
||||
String value = msg.findItemValue(path);
|
||||
if (value != null) return value;
|
||||
|
||||
int dotIdx = path.lastIndexOf('.');
|
||||
while (dotIdx > 0) {
|
||||
String parentPath = path.substring(0, dotIdx);
|
||||
String subPath = path.substring(dotIdx + 1);
|
||||
String parentVal = msg.findItemValue(parentPath);
|
||||
if (StringUtils.isNotBlank(parentVal)) {
|
||||
String extracted = extractFromJson(parentVal, subPath);
|
||||
if (extracted != null) return extracted;
|
||||
}
|
||||
dotIdx = parentPath.lastIndexOf('.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열에서 dot 구분 경로로 필드 값을 추출한다.
|
||||
* 중간 경로가 JSON 오브젝트면 계속 탐색하고, 최종 값이 primitive 면 문자열로,
|
||||
* 오브젝트/배열이면 JSON 문자열 그대로 반환한다. 파싱 실패 시 null 반환.
|
||||
*/
|
||||
private String extractFromJson(String json, String fieldPath) {
|
||||
try {
|
||||
JsonElement el = new JsonParser().parse(json);
|
||||
for (String part : fieldPath.split("\\.", -1)) {
|
||||
if (!el.isJsonObject()) return null;
|
||||
el = el.getAsJsonObject().get(part);
|
||||
if (el == null) return null;
|
||||
}
|
||||
return el.isJsonPrimitive() ? el.getAsString() : el.toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ${callprop[경로]} 또는 ${callprop[경로]:기본값} 처리.
|
||||
* 1. 표준전문 경로로 callProp 키를 동적으로 결정한다.
|
||||
|
||||
@@ -418,6 +418,90 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
// 배열 반복 {{#foreach path}}...{{/foreach}}
|
||||
// ================================================================
|
||||
|
||||
// ================================================================
|
||||
// DATA 영역 변수 치환 ${DATA.xxx}
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("DATA 영역 변수 치환 ${DATA.xxx}")
|
||||
class DataSectionSubstitution {
|
||||
|
||||
/**
|
||||
* DATA > BIZDATA(FIELD, JSON String value) 구조의 StandardMessage 생성.
|
||||
* BIZDATA 는 파싱 없이 JSON 문자열 그대로 세팅되는 형태.
|
||||
*/
|
||||
private StandardMessage buildMsgWithBizData(String bizDataJson) {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
StandardItem dataGroup = group("DATA");
|
||||
dataGroup.addItem(field("BIZDATA", bizDataJson));
|
||||
msg.addItem(dataGroup);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA JSON 필드 치환 – 단일 depth")
|
||||
void DATA_BIZDATA_JSON_필드_치환() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"acctNo\":\"1234567890\",\"custNm\":\"홍길동\"}");
|
||||
String template = "{\"acctNo\":\"${DATA.BIZDATA.acctNo}\",\"custNm\":\"${DATA.BIZDATA.custNm}\"}";
|
||||
|
||||
assertEquals("{\"acctNo\":\"1234567890\",\"custNm\":\"홍길동\"}",
|
||||
handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA JSON 필드 치환 – 중첩 depth")
|
||||
void DATA_BIZDATA_JSON_중첩_필드_치환() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"addr\":{\"city\":\"서울\",\"zip\":\"12345\"}}");
|
||||
|
||||
assertEquals("서울", handler.render("${DATA.BIZDATA.addr.city}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA 원본 JSON 문자열 치환")
|
||||
void DATA_BIZDATA_원본_치환() {
|
||||
String rawJson = "{\"acctNo\":\"1234567890\"}";
|
||||
StandardMessage msg = buildMsgWithBizData(rawJson);
|
||||
|
||||
assertEquals(rawJson, handler.render("${DATA.BIZDATA}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA 와 MSG 영역 혼합 치환")
|
||||
void DATA_MSG_혼합_치환() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"acctNo\":\"9876543210\"}");
|
||||
String template = "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"acctNo\":\"${DATA.BIZDATA.acctNo}\"}";
|
||||
|
||||
assertEquals("{\"code\":\"E001\",\"acctNo\":\"9876543210\"}",
|
||||
handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA JSON 에 없는 필드는 빈 문자열")
|
||||
void DATA_BIZDATA_없는_JSON_필드_빈문자열() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"acctNo\":\"1234567890\"}");
|
||||
|
||||
assertEquals("", handler.render("${DATA.BIZDATA.noneField}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA JSON 에 없는 필드에 기본값 지정")
|
||||
void DATA_BIZDATA_없는_JSON_필드_기본값() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"acctNo\":\"1234567890\"}");
|
||||
|
||||
assertEquals("UNKNOWN", handler.render("${DATA.BIZDATA.noneField:UNKNOWN}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA 영역 미설정 시 기본값 반환")
|
||||
void DATA_없으면_기본값() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("{}", handler.render("${DATA.BIZDATA:{}}", msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("배열 반복 블록 {{#foreach}}")
|
||||
class ForeachBlock {
|
||||
|
||||
Reference in New Issue
Block a user