From 68c29b962efa85a736071a255bfbaeae23cc98e1 Mon Sep 17 00:00:00 2001 From: curry772 Date: Fri, 5 Jun 2026 17:20:09 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20StandardMessageCoordinatorDJB=EC=97=90?= =?UTF-8?q?=20callProp=20refValue=20=EA=B0=92=20=EC=A3=BC=EC=9E=85=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - coordinateAfterCreateMessageByRule에서 StandardItem의 refValue가 \${callProp.키} 패턴이면 Properties에서 값을 꺼내 setValue로 설정한다. - \${callProp.키:기본값} 구문으로 기본값 지정 가능 - 점(.) 구분으로 Properties/Map 중첩 구조 탐색 지원 - GROUP 타입 재귀 순회, GRID/FARRAY는 건너뜀 --- .../StandardMessageCoordinatorDJB.java | 72 +++++++++ .../StandardMessageCoordinatorDJBTest.java | 142 ++++++++++++++++++ 2 files changed, 214 insertions(+) diff --git a/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java b/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java index 494115f..ec1a214 100644 --- a/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java +++ b/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java @@ -5,7 +5,10 @@ import java.net.NetworkInterface; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.LinkedHashMap; +import java.util.Map; import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; @@ -15,6 +18,7 @@ import com.eactive.eai.common.util.MessageUtil; import com.eactive.eai.inbound.processor.ProcessVO; import com.eactive.eai.message.StandardItem; import com.eactive.eai.message.StandardMessage; +import com.eactive.eai.message.StandardType; import com.eactive.eai.message.manager.StandardMessageManager; import com.eactive.eai.message.service.DefaultStandardMessageCoordinator; import com.eactive.eai.message.service.InterfaceMapper; @@ -36,6 +40,10 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin private static final DateTimeFormatter FMT_DATE = DateTimeFormatter.ofPattern("yyyyMMdd"); private static final DateTimeFormatter FMT_TIME_MILLIS = DateTimeFormatter.ofPattern("HHmmssSSS"); + // ${callProp.key} 또는 ${callProp.key:기본값} 패턴 + private static final Pattern CALL_PROP_PATTERN = + Pattern.compile("\\$\\{callProp\\.([^}:]+)(?::([^}]*))?\\}"); + // HEAD 필드 경로 private static final String HEAD_FRST_MESG_DMAN_DT = "HEAD.frst_mesg_dman_dt"; private static final String HEAD_FRST_MESG_DMAN_TIME = "HEAD.frst_mesg_dman_time"; @@ -114,6 +122,9 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin if (ezdataItem != null && evaluateBlockCondition(standardMessage, ezdataItem)) { setEzdataFields(standardMessage, ezdataItem, ipv4, mac); } + + // refValue="${callProp.키}" 필드에 Properties 값 주입 + resolveCallPropRefValues(standardMessage, prop); } @Override @@ -232,6 +243,67 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin // private helpers // ---------------------------------------------------------------- + private void resolveCallPropRefValues(StandardMessage standardMessage, Properties prop) { + if (prop == null) return; + resolveCallPropInChilds(standardMessage.getChilds(), prop); + } + + private void resolveCallPropInChilds(LinkedHashMap childs, Properties prop) { + if (childs == null) return; + for (StandardItem item : childs.values()) { + int type = item.getType(); + if (type == StandardType.FIELD) { + String refValue = item.getRefValue(); + if (refValue == null) continue; + Matcher m = CALL_PROP_PATTERN.matcher(refValue); + if (m.matches()) { + String key = m.group(1); + String defaultVal = m.group(2) != null ? m.group(2) : ""; + String resolved = resolveCallPropValue(prop, key); + item.setValue(resolved != null ? resolved : defaultVal); + } + } else if (type == StandardType.GROUP) { + resolveCallPropInChilds(item.getChilds(), prop); + } + // GRID, FARRAY, BIZDATA: 배열/업무데이터 영역은 건너뜀 + } + } + + /** + * Properties/Map 중첩 구조에서 점(.) 구분 keyPath로 값을 탐색한다. + * Properties는 Map을 구현하므로 Map 단일 메서드로 통일한다. + * - "aaa.bbb" : map.get("aaa")가 Map(Properties 포함)이면 그 안에서 "bbb" 재귀 조회 + * - 중간 값이 Map이 아니면 전체 keyPath로 폴백 조회 + * - 값을 찾지 못하면 null 반환 + */ + private String resolveCallPropValue(Properties props, String keyPath) { + return resolveNestedValue(props, keyPath); + } + + @SuppressWarnings("unchecked") + private String resolveNestedValue(Map map, String keyPath) { + int dotIdx = keyPath.indexOf('.'); + if (dotIdx < 0) { + Object val = map.get(keyPath); + if (val == null) return null; + if (val instanceof String) return (String) val; + return val.toString(); + } + + String first = keyPath.substring(0, dotIdx); + String rest = keyPath.substring(dotIdx + 1); + Object val = map.get(first); + if (val instanceof Map) { + return resolveNestedValue((Map) val, rest); + } + + // 폴백: 전체 keyPath를 단일 키로 조회 + Object direct = map.get(keyPath); + if (direct == null) return null; + if (direct instanceof String) return (String) direct; + return direct.toString(); + } + private String getSysEnvDvcd(EAIServerManager server) { if (server.isPEAIServer()) return "P"; // 운영 if (server.isSEAIServer()) return "T"; // 검증/테스트 diff --git a/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java b/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java index 05ba3d1..d78e251 100644 --- a/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java @@ -6,6 +6,9 @@ import static org.mockito.ArgumentMatchers.anyString; import java.lang.reflect.Field; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -20,6 +23,7 @@ import com.eactive.eai.common.monitor.EAIServiceMonitor; import com.eactive.eai.common.property.PropManager; import com.eactive.eai.common.server.EAIServerManager; import com.eactive.eai.common.util.ApplicationContextProvider; +import com.eactive.eai.message.StandardItem; import com.eactive.eai.message.StandardMessage; import com.eactive.eai.message.manager.StandardMessageManager; import com.eactive.eai.message.service.InterfaceMapper; @@ -354,4 +358,142 @@ class StandardMessageCoordinatorDJBTest { assertNotNull(scopLen, "msg_scop_len이 null"); assertTrue(Integer.parseInt(scopLen.trim()) >= 0, "msg_scop_len은 0 이상이어야 함: " + scopLen); } + + // ================================================================ + // resolveCallPropRefValues — ${callProp.키} 치환 + // ================================================================ + + @Test + void callProp_refValue_단순키_값_주입() throws Exception { + // chnl_dtls_clcd: CSV 기본값 없음, refValue를 ${callProp.CHNL_CD}로 주입 + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.CHNL_CD}"); + + Properties prop = new Properties(); + prop.setProperty("CHNL_CD", "MOB"); + coordinator.coordinateAfterCreateMessageByRule(message, null, prop); + + assertEquals("MOB", message.findItemValue("HEAD.chnl_dtls_clcd")); + } + + @Test + void callProp_refValue_기본값_키_없을때_기본값_사용() throws Exception { + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.MISSING:IB1}"); + + coordinator.coordinateAfterCreateMessageByRule(message, null, new Properties()); + + assertEquals("IB1", message.findItemValue("HEAD.chnl_dtls_clcd")); + } + + @Test + void callProp_refValue_기본값_키_있을때_prop값으로_덮어씀() throws Exception { + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.CHNL_CD:IB1}"); + + Properties prop = new Properties(); + prop.setProperty("CHNL_CD", "ATM"); + coordinator.coordinateAfterCreateMessageByRule(message, null, prop); + + assertEquals("ATM", message.findItemValue("HEAD.chnl_dtls_clcd")); + } + + @Test + void callProp_refValue_prop_null이면_setValue_호출_안함() throws Exception { + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.CHNL_CD}"); + + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + String val = message.findItemValue("HEAD.chnl_dtls_clcd"); + assertTrue(val == null || val.trim().isEmpty(), + "prop=null이면 callProp refValue 치환하지 않아야 함: " + val); + } + + @Test + void callProp_refValue_패턴_불일치_필드는_skip() throws Exception { + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "PLAIN"); + + Properties prop = new Properties(); + prop.setProperty("chnl_dtls_clcd", "SHOULD_NOT_SET"); + coordinator.coordinateAfterCreateMessageByRule(message, null, prop); + + assertNotEquals("SHOULD_NOT_SET", message.findItemValue("HEAD.chnl_dtls_clcd"), + "패턴 불일치 refValue는 치환하지 않아야 함"); + } + + @Test + void callProp_refValue_여러_필드_동시_주입() throws Exception { + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.CHNL_CD}"); + setRefValue(message.findItem("HEAD.xtis_cd"), "${callProp.XTIS_CD}"); + + Properties prop = new Properties(); + prop.setProperty("CHNL_CD", "IB1"); + prop.setProperty("XTIS_CD", "OBK"); + coordinator.coordinateAfterCreateMessageByRule(message, null, prop); + + assertEquals("IB1", message.findItemValue("HEAD.chnl_dtls_clcd")); + assertEquals("OBK", message.findItemValue("HEAD.xtis_cd")); + } + + @Test + void callProp_중첩_Properties_점경로_조회() throws Exception { + // prop.get("OUTBOUND") = subProps, subProps.getProperty("IF_ID") = "SVC001" + // → ${callProp.OUTBOUND.IF_ID} → "SVC001" + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.OUTBOUND.IF_CD}"); + + Properties subProps = new Properties(); + subProps.setProperty("IF_CD", "IB1"); + Properties prop = new Properties(); + prop.put("OUTBOUND", subProps); + coordinator.coordinateAfterCreateMessageByRule(message, null, prop); + + assertEquals("IB1", message.findItemValue("HEAD.chnl_dtls_clcd")); + } + + @Test + void callProp_중첩_Map_점경로_조회() throws Exception { + // prop.get("CTX") = Map{"CHNL":"MOB"} + // → ${callProp.CTX.CHNL} → "MOB" + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.CTX.CHNL}"); + + Map ctxMap = new HashMap<>(); + ctxMap.put("CHNL", "MOB"); + Properties prop = new Properties(); + prop.put("CTX", ctxMap); + coordinator.coordinateAfterCreateMessageByRule(message, null, prop); + + assertEquals("MOB", message.findItemValue("HEAD.chnl_dtls_clcd")); + } + + @Test + void callProp_중간값_비Properties_폴백_전체키_조회() throws Exception { + // prop.get("aaa") = "plain" (Properties가 아님) → 전체 키 "aaa.bbb" 폴백 + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.aaa.bbb}"); + + Properties prop = new Properties(); + prop.setProperty("aaa", "plain"); + prop.setProperty("aaa.bbb", "ATM"); + coordinator.coordinateAfterCreateMessageByRule(message, null, prop); + + assertEquals("ATM", message.findItemValue("HEAD.chnl_dtls_clcd")); + } + + @Test + void callProp_중첩_키_없을때_기본값_사용() throws Exception { + setRefValue(message.findItem("HEAD.chnl_dtls_clcd"), "${callProp.OUTBOUND.MISSING:IB1}"); + + Properties subProps = new Properties(); // MISSING 키 없음 + Properties prop = new Properties(); + prop.put("OUTBOUND", subProps); + coordinator.coordinateAfterCreateMessageByRule(message, null, prop); + + assertEquals("IB1", message.findItemValue("HEAD.chnl_dtls_clcd")); + } + + // ---------------------------------------------------------------- + // 헬퍼 + // ---------------------------------------------------------------- + + private static void setRefValue(StandardItem item, String refValue) throws Exception { + Field f = StandardItem.class.getDeclaredField("refValue"); + f.setAccessible(true); + f.set(item, refValue); + } }