10 Commits

Author SHA1 Message Date
curry772 1240b24c90 BIZ_DATA 항목 추가 2026-06-12 09:08:02 +09:00
curry772 7346c3070d eaiSvcCd 추출 로직 변경
- StandarMessage에 이미 eaiSvcCd가 설정되어 있으면 그것을 이용(비표준)
- 표준전문에서 조합하도록 수정(표준)
2026-06-12 09:07:40 +09:00
curry772 9a597a52ac afterFoundEaiMessage 함수에 프러퍼티 이용 헤더매핑 로직 추가
- DJB_IN_HTTHEAD_STDHEAD_MAPPING 프러퍼티 그룹 이용
2026-06-12 09:05:50 +09:00
curry772 5653a183eb GUID 생성 시 시스템코드 OPA 사용으로 변경 2026-06-12 09:01:27 +09:00
curry772 0e3a612bc0 TemplateAdapterErrorMsgHandler 를 이용하여 오류메시지 생성할 수 있도록 변경 2026-06-09 13:49:32 +09:00
curry772 68c29b962e feat: StandardMessageCoordinatorDJB에 callProp refValue 값 주입 기능 추가
- coordinateAfterCreateMessageByRule에서 StandardItem의 refValue가
   \${callProp.키} 패턴이면 Properties에서 값을 꺼내 setValue로 설정한다.
- \${callProp.키:기본값} 구문으로 기본값 지정 가능
- 점(.) 구분으로 Properties/Map 중첩 구조 탐색 지원
- GROUP 타입 재귀 순회, GRID/FARRAY는 건너뜀
2026-06-05 17:20:09 +09:00
curry772 dafe80f8bc fix: SiftingAppender 파일 락 해결 및 거래 로그 on/off 제어 추가
- DJErpApiAdapterController: MDC 제어를 TxSiftContext.begin/end로 교체
- TxSiftContext.end()를 별도 finally로 분리하여 항상 실행 보장
- elink-online-core: Logger SiftingAppender 개선 (submodule)
- elink-online-common: TxSiftContext 추가 (submodule)
2026-06-05 14:06:40 +09:00
curry772 9e78a0e199 ApiRequestBodyFilter 테스트를 위한 url pattern /dj/* 추가 2026-06-02 18:08:11 +09:00
curry772 3ec18b17e8 테스크 코드 이동 2026-06-02 18:06:55 +09:00
curry772 90464ee33f 성능 튜닝 2026-06-02 18:06:29 +09:00
11 changed files with 810 additions and 106 deletions
+1 -1
View File
@@ -30,7 +30,7 @@
<filter-mapping>
<filter-name>ApiRequestBodyFilter</filter-name>
<url-pattern>/api/*</url-pattern> <!-- 필요한 URL 패턴 -->
<url-pattern>/mapi/*</url-pattern> <!-- 필요한 URL 패턴 -->
<url-pattern>/dj/*</url-pattern> <!-- 필요한 URL 패턴 -->
</filter-mapping>
@@ -1,7 +1,6 @@
package com.eactive.eai.adapter.controller;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@@ -9,14 +8,10 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
// jwhong
import org.json.simple.JSONObject;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -24,25 +19,23 @@ import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterPropManager;
import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandler;
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandlerFactory;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterManager;
import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterUri;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactory;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.adapter.service.DJErpApiAdapterService;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.stdmessage.STDMessageManager;
import com.eactive.eai.common.stdmessage.STDMsgInfoAddOnVO;
import com.eactive.eai.common.util.DatetimeUtil;
import com.eactive.eai.common.util.InboundErrorLogger;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.common.util.TxFileLogger;
import com.eactive.eai.common.util.TxSiftContext;
import com.eactive.eai.common.util.UUIDGenerator;
import com.eactive.eai.inbound.error.InboundErrorInfoVO;
@@ -116,7 +109,8 @@ public class DJErpApiAdapterController implements HttpAdapterServiceKey {
Properties transactionProp = new Properties();
String uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
transactionProp.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
MDC.put(Logger.DISCRIMINATOR, transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID));
TxSiftContext.begin(uuid);
// jwhong, put api received time, eaiSvcCode
transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr);
@@ -140,27 +134,18 @@ public class DJErpApiAdapterController implements HttpAdapterServiceKey {
String errorMsg = null;
if(e instanceof HttpStatusException ) {
errorMsg = genErrorResponseMessage(adapterGroupName, adapterName,
transactionProp, e, adptMsgType, encode, errorResponseFormat);
if (e instanceof HttpStatusException) {
logger.warn("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage());
HttpStatusException e1 = (HttpStatusException) e;
errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
MessageUtil.ERROR_CODE_AP_ERROR, e1.getMessage(), errorResponseFormat);
responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(errorMsg);
}
else if( e instanceof JwtAuthException )
{
} else if (e instanceof JwtAuthException) {
logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage(), errorResponseFormat);
responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(errorMsg);
}
else
{
} else {
logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage(), errorResponseFormat);
responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(mediaType)
.body(errorMsg);
responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(mediaType).body(errorMsg);
}
responseData = errorMsg;
@@ -171,16 +156,38 @@ public class DJErpApiAdapterController implements HttpAdapterServiceKey {
*/
try {
TxFileLogger.logTxFile(transactionProp, responseData, "[IN_SEND]");
MDC.remove(Logger.DISCRIMINATOR);
servletRequest.setAttribute(TransactionContextKeys.TRANSACTION_PROP, transactionProp);
} catch (Exception e) {
logger.warn("http header db logging fail.", e);
} finally {
TxSiftContext.end();
}
}
return responseEntity;
}
private String genErrorResponseMessage(String adapterGroupName, String adapterName,
Properties callProp, Throwable e, String adptMsgType, String encode, String errorResponseFormat) {
String errorHandlerClass = AdapterPropManager.getInstance().getProperty(adapterName, "ERR_MSG_HANDLER");
if (StringUtils.isNotBlank(errorHandlerClass)) {
if (logger.isInfo())
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass);
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass);
if (handler != null) {
Object resposne;
try {
resposne = handler.generateNonStandardInboundErrorResponseMessage(adapterGroupName, adapterName,
callProp, null, null, e);
return (String)resposne;
} catch (Exception e1) {
logger.warn("AdapterErrorMessageHandler error.", e);
// handler 실패시 아래에서 처리한
}
}
}
return MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage(), errorResponseFormat);
}
/**
* url에서 뒤쪽 /이후를 제거하면서 찾는다.<br>
@@ -260,21 +267,21 @@ public class DJErpApiAdapterController implements HttpAdapterServiceKey {
InboundErrorLogger.error(errorInfoVO);
}
private String makeResponseBodyMsg() {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("result", 1);
Map<String, Object> map = new HashMap<>();
map.put("code", "200");
map.put("data", dataMap);
map.put("message", "정상 처리 되었습니다.");
JSONObject json = new JSONObject();
json.putAll(map);
return json.toJSONString();
}
// private String makeResponseBodyMsg() {
//
// Map<String, Object> dataMap = new HashMap<>();
// dataMap.put("result", 1);
// Map<String, Object> map = new HashMap<>();
//
// map.put("code", "200");
// map.put("data", dataMap);
// map.put("message", "정상 처리 되었습니다.");
//
// JSONObject json = new JSONObject();
// json.putAll(map);
//
// return json.toJSONString();
// }
/*
* private String makeResponseBodyMsg(String asyncMsgStyle) {
@@ -1,52 +1,52 @@
package com.eactive.eai.custom.adapter.handler;
import java.util.Properties;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandler;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.manager.StandardMessageManager;
import com.eactive.eai.message.service.InterfaceMapper;
import com.ext.eai.common.stdmessage.STDMessageKeys;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class LonErrorMsgHandler implements AdapterErrorMessageHandler {
@Override
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception{
if(resStandardMessage != null && isErrorMessage(resStandardMessage)) {
AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(inboudnAdapterGroupName);
if(MessageType.JSON.equals(adapterGroupVO.getMessageType())) {
ObjectMapper objectMapper = new ObjectMapper();
String requestMessage = (String) outboundRequestData;
JsonNode requestJson = objectMapper.readTree(requestMessage);
String alncInstCd = requestJson.get("dataCmn").get("alncInstCd").asText();
ObjectNode responseJson = objectMapper.createObjectNode();
ObjectNode dataCmnNode = objectMapper.createObjectNode();
responseJson.set("dataCmn", dataCmnNode);
dataCmnNode.put("fnclInstCd", "KBK");
dataCmnNode.put("alncInstCd", alncInstCd);
dataCmnNode.put("tlgrRspnsCd", "0210");
return responseJson.toString();
}
}
return "";
}
private boolean isErrorMessage(StandardMessage standardMessage) {
InterfaceMapper mapper = StandardMessageManager.getInstance().getMapper();
String responseType = mapper.getResponseType(standardMessage);
return STDMessageKeys.RESPONSE_TYPE_CODE_E.equals(responseType);
}
}
//package com.eactive.eai.custom.adapter.handler;
//
//import java.util.Properties;
//
//import com.eactive.eai.adapter.AdapterGroupVO;
//import com.eactive.eai.adapter.AdapterManager;
//import com.eactive.eai.adapter.handler.AdapterErrorMessageHandler;
//import com.eactive.eai.common.message.MessageType;
//import com.eactive.eai.message.StandardMessage;
//import com.eactive.eai.message.manager.StandardMessageManager;
//import com.eactive.eai.message.service.InterfaceMapper;
//import com.ext.eai.common.stdmessage.STDMessageKeys;
//import com.fasterxml.jackson.databind.JsonNode;
//import com.fasterxml.jackson.databind.ObjectMapper;
//import com.fasterxml.jackson.databind.node.ObjectNode;
//
//public class LonErrorMsgHandler implements AdapterErrorMessageHandler {
//
// @Override
// public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
// Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception{
//
// if(resStandardMessage != null && isErrorMessage(resStandardMessage)) {
// AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(inboudnAdapterGroupName);
// if(MessageType.JSON.equals(adapterGroupVO.getMessageType())) {
// ObjectMapper objectMapper = new ObjectMapper();
// String requestMessage = (String) outboundRequestData;
// JsonNode requestJson = objectMapper.readTree(requestMessage);
// String alncInstCd = requestJson.get("dataCmn").get("alncInstCd").asText();
//
// ObjectNode responseJson = objectMapper.createObjectNode();
// ObjectNode dataCmnNode = objectMapper.createObjectNode();
// responseJson.set("dataCmn", dataCmnNode);
// dataCmnNode.put("fnclInstCd", "KBK");
// dataCmnNode.put("alncInstCd", alncInstCd);
// dataCmnNode.put("tlgrRspnsCd", "0210");
//
// return responseJson.toString();
// }
// }
//
//
// return "";
// }
//
// private boolean isErrorMessage(StandardMessage standardMessage) {
// InterfaceMapper mapper = StandardMessageManager.getInstance().getMapper();
// String responseType = mapper.getResponseType(standardMessage);
// return STDMessageKeys.RESPONSE_TYPE_CODE_E.equals(responseType);
// }
//
//}
@@ -1,6 +1,10 @@
package com.eactive.eai.custom.inbound.processor;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
@@ -9,8 +13,13 @@ import com.eactive.eai.common.inflow.InflowTargetVO;
import com.eactive.eai.common.inflow.dual.DualBucket;
import com.eactive.eai.common.inflow.dual.DualCustomBucket;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.custom.inflow.ClientDualBucket;
import com.eactive.eai.inbound.processor.ProcessVO;
import com.eactive.eai.inbound.processor.RequestProcessor;
import com.eactive.eai.message.StandardItem;
import com.eactive.eai.message.StandardMessage;
/**
* 어댑터/인터페이스/클라이언트 유량제어 차단 시 어느 버킷(PER_SECOND/THRESHOLD)에서
@@ -24,6 +33,81 @@ import com.eactive.eai.inbound.processor.RequestProcessor;
* <p>Spring Bean 등록 후 기존 RequestProcessor 대신 이 클래스를 어댑터에 설정한다.
*/
public class DJBRequestProcessor extends RequestProcessor {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
@Override
protected void afterFoundEaiMessage(EAIMessage eaiMsg, ProcessVO vo, Properties prop) {
logger.debug("DJBRequestProcessor interface found.");
try {
this.resolveStandardHeaderMapping(eaiMsg, prop);
} catch (Exception e) {
logger.warn("DJBRequestProcessor header set failed.", e);
}
}
private void resolveStandardHeaderMapping(EAIMessage eaiMsg, Properties prop) {
StandardMessage standardMessage = eaiMsg.getStandardMessage();
HashMap<String, String> headerMapping = getPropertyMap("DJB_IN_HTTHEAD_STDHEAD_MAPPING", eaiMsg.getSngSysItfTp());
for (Entry<String, String> entry : headerMapping.entrySet()) {
String stdHeaderPath = entry.getKey();
int dotIdx = stdHeaderPath.indexOf('.');
if(dotIdx > 0) {
String firstPath = stdHeaderPath.substring(0, dotIdx);
StandardItem topLevelItem = standardMessage.findItem(firstPath);
topLevelItem.setHidden(false);
}
StandardItem item = standardMessage.findItem(entry.getKey());
String value = resolveNestedValue(prop, entry.getValue());
if (item != null && value != null)
item.setValue(value);
else
logger.debug("header mapping item not found : {} ", entry);
}
}
private HashMap<String, String> getPropertyMap(String propertyGroupName, String prefix) {
Properties prop = PropManager.getInstance().getProperties(propertyGroupName);
Set<String> keys = prop.stringPropertyNames();
HashMap<String, String> tmpToJsonArrayInterface = new HashMap<>();
for (String key : keys) {
if (StringUtils.startsWith(key, prefix + ".")) {
String propValue = prop.getProperty(key);
String replacedKey = StringUtils.replace(key, prefix + ".", "");
tmpToJsonArrayInterface.put(replacedKey, propValue);
}
}
return tmpToJsonArrayInterface;
}
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();
}
@Override
protected String checkCustomInflow(Bucket bucket, EAIMessage eaiMsg, Properties prop) {
@@ -2,6 +2,7 @@ package com.eactive.eai.custom.message;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.service.DefaultInterfaceMapper;
@@ -28,17 +29,24 @@ public class InterfaceMapperDJB extends DefaultInterfaceMapper {
// 처리결과구분코드 DJBank 값
private static final String PROCS_RSLT_SUCCESS = "S";
private static final String PROCS_RSLT_FAIL = "F";
private static String API_ID_PREFIX = StringUtils
.defaultString(PropManager.getInstance().getProperty("API_ID_PREFIX"));
@Override
public String getEaiSvcCode(StandardMessage standardMessage) {
String interfaceId = getInterfaceId(standardMessage);
if(StringUtils.isNotBlank(standardMessage.getEaiSvcCd()))
return standardMessage.getEaiSvcCd();
// String interfaceId = getInterfaceId(standardMessage);
String txId = getServiceId(standardMessage);
String sendRecvDvcd = getSendRecvDivision(standardMessage); // S | R
String inExDivision = getInExDivision(standardMessage); // 1 | 2
if(StringUtils.isEmpty(txId))
throw new RuntimeException("not found txid");
if (interfaceId == null) interfaceId = "";
interfaceId = interfaceId.trim();
return interfaceId + sendRecvDvcd + StringUtils.defaultString(inExDivision);
return API_ID_PREFIX + txId + sendRecvDvcd + StringUtils.defaultString(inExDivision);
}
// ----------------------------------------------------------------
@@ -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";
@@ -67,9 +75,15 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin
// DATA 영역 경로
private static final String DATA_SCOP_LEN = "DATA.data_scop_len";
private String[] netInfo;
@Override
public void coordinateAfterParsing(StandardMessage standardMessage, Object paramObject, Properties prop) {
// TODO 표준전문을 수신할 경우, 전문의 interface id로 가져온 eaiSvcCd가 다른 값으로 치환되어야 할 수도 있다.
// 수신한 if_id를 기준으로 api gw의 eaiSvcCd를 매핑하는 프러퍼티가 있어야 한다.
// PropGroup : DJB_IN_STDHEAD_IFID_MAPPING, DJB_IN_HTTHEAD_STDHEAD_MAPPING
// standardMessage.setEaiSvcCd(mappedIfId);
}
@Override
@@ -78,7 +92,7 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin
InterfaceMapper mapper = manager.getMapper();
// GUID 생성 (40자) 및 설정
String guid = GUIDGeneratorDJB.getGUID("EAI");
String guid = GUIDGeneratorDJB.getGUID("OPA");
mapper.setGuid(standardMessage, guid);
// GUID 진행번호 초기화 (최초=001)
@@ -112,6 +126,9 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin
if (ezdataItem != null && evaluateBlockCondition(standardMessage, ezdataItem)) {
setEzdataFields(standardMessage, ezdataItem, ipv4, mac);
}
// refValue="${callProp.키}" 필드에 Properties 값 주입
resolveCallPropRefValues(standardMessage, prop);
}
@Override
@@ -230,6 +247,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<String, StandardItem> 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"; // 검증/테스트
@@ -237,6 +315,9 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin
}
private String[] collectNetworkInfo() {
if(this.netInfo != null)
return this.netInfo;
String ipv4 = "";
String mac = "";
try {
@@ -257,7 +338,8 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin
} catch (Exception e) {
if (logger.isWarn()) logger.warn("Failed to collect network info", e);
}
return new String[]{ipv4, mac};
this.netInfo = new String[]{ipv4, mac};
return this.netInfo;
}
private void setEzdataFields(StandardMessage standardMessage, StandardItem ezdataItem, String ipv4, String mac) {
@@ -24,3 +24,4 @@ ERROR_MSG=MSG.MAIN_MSG.outp_msg_ctnt
RESPONSE_TYPE=HEAD.procs_rslt_dvcd
RETURN_VALUE=
RETURN_SERVICE_ID=
BIZ_DATA=DATA[0].BIZ_DATA
@@ -0,0 +1,380 @@
package com.eactive.eai;
import com.eactive.eai.common.EAITable;
import com.eactive.eai.common.util.MessageUtil;
import org.json.simple.JSONValue;
/**
* 테스트용 메시지 키 검증 클래스
*/
public class MessageKeyTest {
/**
* XML 또는 JSON 메시지의 성공 여부를 검증하는 테스트용 메소드
* 추출된 값을 배열로 반환
*/
public static TestResult isSuccessMessageTest(String messageType, Object message, String path, String expectedValue) {
try {
String extractedValue = "";
if(EAITable.XML_TYPE.equals(messageType)) {
if(message instanceof byte[]) {
extractedValue = extractXmlValue(new String((byte[])message), path);
} else if(message instanceof String) {
extractedValue = extractXmlValue((String)message, path);
}
}
else if(EAITable.JSON_TYPE.equals(messageType)) {
Object json = JSONValue.parse((String)message);
extractedValue = extractJsonValue(json, path);
}
if(extractedValue == null) extractedValue = "";
boolean result = compareMessageValue(extractedValue, expectedValue);
return new TestResult(result, extractedValue);
} catch(Exception e) {
e.printStackTrace();
return new TestResult(false, "오류 발생: " + e.getMessage());
}
}
// 테스트 결과를 담는 클래스
static class TestResult {
boolean success;
String extractedValue;
TestResult(boolean success, String extractedValue) {
this.success = success;
this.extractedValue = extractedValue;
}
}
/**
* 메시지 값 비교를 수행하는 메소드
*/
private static boolean compareMessageValue(String actualValue, String expectedValue) {
if(actualValue == null) actualValue = "";
if(expectedValue == null) return false;
// 예약어로 시작하는 경우
if(expectedValue.startsWith("@")) {
return compareWithReservedWord(actualValue, expectedValue);
}
// 일반 equals 비교
return actualValue.equals(expectedValue);
}
/**
* 예약어를 포함한 값 비교 처리
*/
private static boolean compareWithReservedWord(String actualValue, String definition) {
if (actualValue == null) actualValue = "";
if (definition == null) return false;
try {
// 1. 기본 비교
if (definition.startsWith("@eq:")) {
return actualValue.equals(definition.substring(4));
}
if (definition.startsWith("@ne:")) {
return !actualValue.equals(definition.substring(4));
}
if ("@null".equals(definition)) {
return actualValue.isEmpty();
}
if ("@!null".equals(definition)) {
return !actualValue.isEmpty();
}
// 2. 문자열 패턴
if (definition.startsWith("@contains:")) {
String[] values = definition.substring(10).split(",");
for (String value : values) {
if (actualValue.equals(value.trim())) {
return true;
}
}
return false;
}
if (definition.startsWith("@!contains:")) {
String[] values = definition.substring(11).split(",");
for (String value : values) {
if (actualValue.equals(value.trim())) {
return false;
}
}
return true;
}
if (definition.startsWith("@starts:")) {
return actualValue.startsWith(definition.substring(8));
}
if (definition.startsWith("@ends:")) {
return actualValue.endsWith(definition.substring(6));
}
// 3. 숫자 비교
if (definition.startsWith("@gt:") ||
definition.startsWith("@lt:") ||
definition.startsWith("@ge:") ||
definition.startsWith("@le:")) {
double actualNum = Double.parseDouble(actualValue);
if (definition.startsWith("@gt:")) {
double defNum = Double.parseDouble(definition.substring(4));
return actualNum > defNum;
}
if (definition.startsWith("@lt:")) {
double defNum = Double.parseDouble(definition.substring(4));
return actualNum < defNum;
}
if (definition.startsWith("@ge:")) {
double defNum = Double.parseDouble(definition.substring(4));
return actualNum >= defNum;
}
if (definition.startsWith("@le:")) {
double defNum = Double.parseDouble(definition.substring(4));
return actualNum <= defNum;
}
}
return false;
} catch (Exception e) {
return false;
}
}
/**
* XML 값 추출 메소드
*/
private static String extractXmlValue(String xmlMessage, String path) {
if(path == null) return "";
// XPath 형식인 경우
if(path.startsWith("//") || path.startsWith("/")) {
return MessageUtil.getXmlFldByXPath(xmlMessage, path);
}
// 단순 태그명인 경우
return MessageUtil.getXmlFld(xmlMessage, path);
}
/**
* JSON 값 추출 메소드
*/
private static String extractJsonValue(Object json, String path) {
if(path == null) return "";
// JSONPath 형식인 경우
if(path.startsWith("$.")) {
return MessageUtil.getJSONValueByPath(json, path);
}
// 단순 키인 경우
return MessageUtil.getJSONValue(json, path);
}
private static void testXml() {
// System.out.println("\n=== XML 테스트 ===");
// 단순 경로 테스트
String xml1 = "<root><status>SUCCESS</status></root>";
test("XML 단순 경로",
EAITable.XML_TYPE,
xml1,
"status",
"SUCCESS");
// 중첩 경로 테스트 (일반 방식)
String xml2 = "<root><response><header><resultCode>0000</resultCode></header></response></root>";
test("XML 중첩 경로 (일반)",
EAITable.XML_TYPE,
xml2,
"resultCode",
"0000");
// 중첩 경로 테스트 (XPath 방식)
test("XML 중첩 경로 (XPath)",
EAITable.XML_TYPE,
xml2,
"//response/header/resultCode",
"0000");
// 네임스페이스가 있는 XML
String xml3 = "<ns:root xmlns:ns=\"http://test.com\"><ns:status>OK</ns:status></ns:root>";
test("XML 네임스페이스",
EAITable.XML_TYPE,
xml3,
"//ns:status",
"OK");
// 실패 케이스
test("XML 실패 케이스",
EAITable.XML_TYPE,
xml1,
"status",
"FAIL");
// 존재하지 않는 경로
test("XML 없는 경로",
EAITable.XML_TYPE,
xml1,
"nonexistent",
"SUCCESS");
}
private static void testJson() {
// System.out.println("\n=== JSON 테스트 ===");
// 단순 경로 테스트
String json1 = "{\"status\":\"success\"}";
test("JSON 단순 경로",
EAITable.JSON_TYPE,
json1,
"status",
"success");
// 중첩 경로 테스트 (일반 방식)
String json2 = "{\"response\":{\"header\":{\"resultCode\":\"0000\"}}}";
test("JSON 중첩 경로 (일반)",
EAITable.JSON_TYPE,
json2,
"resultCode",
"0000");
// 중첩 경로 테스트 (JSONPath 방식)
test("JSON 중첩 경로 (JSONPath)",
EAITable.JSON_TYPE,
json2,
"$.response.header.resultCode",
"0000");
// 배열이 포함된 JSON
String json3 = "{\"responses\":[{\"status\":\"success\"},{\"status\":\"pending\"}]}";
test("JSON 배열 경로",
EAITable.JSON_TYPE,
json3,
"$.responses[0].status",
"success");
// 실패 케이스
test("JSON 실패 케이스",
EAITable.JSON_TYPE,
json1,
"status",
"failure");
// 존재하지 않는 경로
test("JSON 없는 경로",
EAITable.JSON_TYPE,
json1,
"nonexistent",
"success");
}
public static void main(String[] args) {
testXml();
testJson();
testReservedWords();
}
private static void testReservedWords() {
// System.out.println("\n=== 예약어 테스트 ===");
// 1. 기본 비교 테스트
test("예약어 - eq",
EAITable.XML_TYPE,
"<root><code>SUCCESS</code></root>",
"code",
"@eq:SUCCESS");
test("예약어 - ne",
EAITable.XML_TYPE,
"<root><code>SUCCESS</code></root>",
"code",
"@ne:FAIL");
test("예약어 - null",
EAITable.XML_TYPE,
"<root><code></code></root>",
"code",
"@null");
test("예약어 - !null",
EAITable.XML_TYPE,
"<root><code>SUCCESS</code></root>",
"code",
"@!null");
// 2. 문자열 패턴 테스트
test("예약어 - contains",
EAITable.JSON_TYPE,
"{\"status\":\"success\"}",
"status",
"@contains:success,ok,done");
test("예약어 - !contains",
EAITable.JSON_TYPE,
"{\"status\":\"success\"}",
"status",
"@!contains:fail,error,invalid");
test("예약어 - starts",
EAITable.JSON_TYPE,
"{\"code\":\"SUCCESS_001\"}",
"code",
"@starts:SUCCESS");
test("예약어 - ends",
EAITable.JSON_TYPE,
"{\"code\":\"ERR_001\"}",
"code",
"@ends:001");
// 3. 숫자 비교 테스트
test("예약어 - gt",
EAITable.JSON_TYPE,
"{\"value\":\"100\"}",
"value",
"@gt:50");
test("예약어 - lt",
EAITable.JSON_TYPE,
"{\"value\":\"100\"}",
"value",
"@lt:150");
test("예약어 - ge",
EAITable.JSON_TYPE,
"{\"value\":\"100\"}",
"value",
"@ge:100");
test("예약어 - le",
EAITable.JSON_TYPE,
"{\"value\":\"100\"}",
"value",
"@le:100");
// 4. 실패 케이스
test("예약어 - 잘못된 숫자",
EAITable.JSON_TYPE,
"{\"value\":\"invalid\"}",
"value",
"@gt:50");
test("예약어 - 잘못된 패턴",
EAITable.JSON_TYPE,
"{\"status\":\"success\"}",
"status",
"@invalid:pattern");
}
private static void test(String testName, String type, String message, String path, String expected) {
TestResult result = isSuccessMessageTest(type, message, path, expected);
// System.out.println(String.format( "테스트: %s\n결과: %s\n메시지: %s\n경로: %s\n기대값: %s\n추출된 값: [%s]\n",testName, result.success ? "성공" : "실패",message,path,expected,result.extractedValue ));
}
}
@@ -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<String, Object> 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);
}
}