Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c3de18cc9 | |||
| a5a072fef4 | |||
| 078331b3b8 | |||
| 428ab1103a | |||
| 4813a2ef0e | |||
| 752156b86f | |||
| 5732ddf962 | |||
| 5ffc48c039 | |||
| cbb6f33ce5 | |||
| 44093681b7 | |||
| a24b437f5c | |||
| 67dba6ca4a | |||
| f4d7ac2fad | |||
| 6ca653aa1e | |||
| 90941bff54 |
Binary file not shown.
@@ -127,6 +127,9 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
StandardMessage resStandardMessage = resEaiMsg.getStandardMessage();
|
StandardMessage resStandardMessage = resEaiMsg.getStandardMessage();
|
||||||
String templateKey = inboundAdapterGroupName + ".template";
|
String templateKey = inboundAdapterGroupName + ".template";
|
||||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(template))
|
||||||
|
template = PropManager.getInstance().getProperty(PROP_GROUP, "default.template");
|
||||||
|
|
||||||
if (StringUtils.isBlank(template)) {
|
if (StringUtils.isBlank(template)) {
|
||||||
if (logger.isWarn()) {
|
if (logger.isWarn()) {
|
||||||
@@ -320,6 +323,8 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
? expr.substring(closeIdx + 2)
|
? expr.substring(closeIdx + 2)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
|
if (msg == null) return defaultVal;
|
||||||
|
|
||||||
String key = msg.findItemValue(msgPath);
|
String key = msg.findItemValue(msgPath);
|
||||||
if (StringUtils.isBlank(key)) return defaultVal;
|
if (StringUtils.isBlank(key)) return defaultVal;
|
||||||
if (callProp == null) return defaultVal;
|
if (callProp == null) return defaultVal;
|
||||||
@@ -409,6 +414,9 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
String templateKey = inboudnAdapterGroupName + ".sys.template";
|
String templateKey = inboudnAdapterGroupName + ".sys.template";
|
||||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(template))
|
||||||
|
template = PropManager.getInstance().getProperty(PROP_GROUP, "default.sys.template");
|
||||||
|
|
||||||
if (StringUtils.isBlank(template)) {
|
if (StringUtils.isBlank(template)) {
|
||||||
if (logger.isWarn()) {
|
if (logger.isWarn()) {
|
||||||
logger.warn("TemplateAdapterErrorMsgHandler] template not found. group=" + PROP_GROUP
|
logger.warn("TemplateAdapterErrorMsgHandler] template not found. group=" + PROP_GROUP
|
||||||
@@ -456,6 +464,10 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
String templateKey = outboundadapterGroupName + ".template";
|
String templateKey = outboundadapterGroupName + ".template";
|
||||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(template))
|
||||||
|
template = PropManager.getInstance().getProperty(PROP_GROUP, "default.template");
|
||||||
|
|
||||||
if (StringUtils.isBlank(template)) {
|
if (StringUtils.isBlank(template)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -466,15 +478,27 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
String adptMsgType, String encode, Throwable e1, int httpCode) {
|
String adptMsgType, String encode, Throwable e1, int httpCode) {
|
||||||
String templateKey = adapterGroupName + ".in." + httpCode + ".template";
|
String templateKey = adapterGroupName + ".in." + httpCode + ".template";
|
||||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
if (StringUtils.isBlank(template)) {
|
||||||
|
templateKey = "default.in." + httpCode + ".template";
|
||||||
|
template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(template)) {
|
if (StringUtils.isBlank(template)) {
|
||||||
templateKey = adapterGroupName + ".in.template";
|
templateKey = adapterGroupName + ".in.template";
|
||||||
template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
if (StringUtils.isBlank(template)) {
|
|
||||||
String errorResponseFormat = httpProp.getProperty("ERROR_RESPONSE_FORMAT");
|
|
||||||
return MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
|
|
||||||
MessageUtil.ERROR_CODE_AP_ERROR, e1.getMessage(), errorResponseFormat);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(template)) {
|
||||||
|
templateKey = "default.in.template";
|
||||||
|
template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(template)) {
|
||||||
|
String errorResponseFormat = httpProp.getProperty("ERROR_RESPONSE_FORMAT");
|
||||||
|
return MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
|
||||||
|
MessageUtil.ERROR_CODE_AP_ERROR, e1.getMessage(), errorResponseFormat);
|
||||||
|
}
|
||||||
|
|
||||||
return render(template, null, callProp, e1);
|
return render(template, null, callProp, e1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -527,8 +527,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
// 이경우 정상으로 처리함
|
// 이경우 정상으로 처리함
|
||||||
} else if (status >= 200 && status <= 207) {
|
} else if (status >= 200 && status <= 207) {
|
||||||
if (useAdapterToken && StringUtils.equals(tokenErrorHttpStatusCode, "200")) {
|
if (useAdapterToken && StringUtils.equals(tokenErrorHttpStatusCode, "200")) {
|
||||||
needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues,
|
needReissue = checkTokenRetry(status, responseMessage, tokenErrorHttpStatusCode, tokenErrorCodeKey,
|
||||||
vo.getEncode());
|
tokenErrorCodeValues, vo.getEncode());
|
||||||
}
|
}
|
||||||
} else if (status == 302) {
|
} else if (status == 302) {
|
||||||
if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) {
|
if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) {
|
||||||
@@ -542,15 +542,14 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
|
|
||||||
if (status >= 400 && status < 500) {
|
if (status >= 400 && status < 500) {
|
||||||
if (useAdapterToken) {
|
if (useAdapterToken) {
|
||||||
needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues,
|
needReissue = checkTokenRetry(status, responseMessage, tokenErrorHttpStatusCode,
|
||||||
vo.getEncode());
|
tokenErrorCodeKey, tokenErrorCodeValues, vo.getEncode());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needReissue) {
|
if (!needReissue) {
|
||||||
throw new Exception(errMsg);
|
throw new Exception(errMsg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
throw new Exception(errMsg);
|
throw new Exception(errMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -882,8 +881,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
|
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues,
|
protected boolean checkTokenRetry(int status, byte[] responseMessage, String tokenErrorHttpStatusCode,
|
||||||
String encode) {
|
String tokenErrorCodeKey, String tokenErrorCodeValues, String encode) {
|
||||||
if (responseMessage == null) {
|
if (responseMessage == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactory;
|
|||||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterType;
|
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterType;
|
||||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||||
import com.eactive.eai.common.TransactionContextKeys;
|
import com.eactive.eai.common.TransactionContextKeys;
|
||||||
|
import com.eactive.eai.common.server.EAIServerManager;
|
||||||
import com.eactive.eai.common.util.Logger;
|
import com.eactive.eai.common.util.Logger;
|
||||||
import com.eactive.eai.common.util.TxSiftContext;
|
import com.eactive.eai.common.util.TxSiftContext;
|
||||||
import com.eactive.eai.common.util.UUIDGenerator;
|
import com.eactive.eai.common.util.UUIDGenerator;
|
||||||
@@ -31,13 +32,20 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
|||||||
public static final String PROPERTIES_NAME_CLIENT_ID = "clientId";
|
public static final String PROPERTIES_NAME_CLIENT_ID = "clientId";
|
||||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||||
private ObjectMapper mapper = new ObjectMapper();
|
private ObjectMapper mapper = new ObjectMapper();
|
||||||
|
EAIServerManager eaiServerManager;
|
||||||
|
String instid = null;
|
||||||
|
|
||||||
public Object service(String adptGrpName, String adptName, Object message, Properties prop,
|
public Object service(String adptGrpName, String adptName, Object message, Properties prop,
|
||||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
||||||
boolean bMDCput = false;
|
boolean bMDCput = false;
|
||||||
|
|
||||||
if(uuid == null) {
|
if(uuid == null) {
|
||||||
uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
|
if(instid == null) {
|
||||||
|
eaiServerManager = EAIServerManager.getInstance();
|
||||||
|
instid = eaiServerManager.getGroupInstId();
|
||||||
|
}
|
||||||
|
uuid = instid + UUIDGenerator.getUUID().toString().replaceAll("-", "");
|
||||||
prop.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
|
prop.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public class ApiAuthFilter implements HttpAdapterFilter {
|
|||||||
|
|
||||||
public static final String PROP_GROUP_AUTH_SERVER = "OAuthServer";
|
public static final String PROP_GROUP_AUTH_SERVER = "OAuthServer";
|
||||||
public static final String PROP_KEYSTORE_PATH = "certification.publicKeyPath";
|
public static final String PROP_KEYSTORE_PATH = "certification.publicKeyPath";
|
||||||
|
public static final String PASS_SCOPE_LIST = "pass.scope.list";
|
||||||
|
|
||||||
public static final String ERROR_AUTHENTICATION_FAIL = "E.AUTHENTICATION_FAIL";
|
public static final String ERROR_AUTHENTICATION_FAIL = "E.AUTHENTICATION_FAIL";
|
||||||
public static final String ERROR_AUTHORIZATION_FAIL = "E.AUTHORIZATION_FAIL";
|
public static final String ERROR_AUTHORIZATION_FAIL = "E.AUTHORIZATION_FAIL";
|
||||||
@@ -59,7 +60,7 @@ public class ApiAuthFilter implements HttpAdapterFilter {
|
|||||||
public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id";
|
public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id";
|
||||||
|
|
||||||
private JWSVerifier jwsVerifier;
|
private JWSVerifier jwsVerifier;
|
||||||
|
private String[] passScopeArr = {};
|
||||||
// // CA 토큰 저장소
|
// // CA 토큰 저장소
|
||||||
// private final Map<String, BearerTokenInfo> CATokenStore = new ConcurrentHashMap<>();
|
// private final Map<String, BearerTokenInfo> CATokenStore = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@@ -89,6 +90,10 @@ public class ApiAuthFilter implements HttpAdapterFilter {
|
|||||||
} catch (IOException | NoSuchAlgorithmException | RuntimeException | InvalidKeySpecException e) {
|
} catch (IOException | NoSuchAlgorithmException | RuntimeException | InvalidKeySpecException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String passScope = vo.getProperty(PASS_SCOPE_LIST, "oob,public");
|
||||||
|
passScopeArr = org.springframework.util.StringUtils.tokenizeToStringArray(passScope, ",");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -316,9 +321,6 @@ public class ApiAuthFilter implements HttpAdapterFilter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String passScope = "oob,public";
|
|
||||||
String[] passScopeArr = org.springframework.util.StringUtils.tokenizeToStringArray(passScope, ",");
|
|
||||||
|
|
||||||
for (String scope : scopeArr) {
|
for (String scope : scopeArr) {
|
||||||
for (String pScope : passScopeArr) {
|
for (String pScope : passScopeArr) {
|
||||||
if ( StringUtils.equalsAny(scope, pScope)) {
|
if ( StringUtils.equalsAny(scope, pScope)) {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.eactive.eai.adapter.http.dynamic.filter;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import com.eactive.eai.common.property.PropManager;
|
||||||
|
import com.eactive.eai.common.util.Logger;
|
||||||
|
import com.eactive.eai.util.JsonPathUtil;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
public class JsonToSetStatusFilter implements HttpAdapterFilter {
|
||||||
|
private static final String PROPGROUP = "JsonToSetStatusFilter";
|
||||||
|
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||||
|
/** HTTP 상태코드 유효 범위 (RFC 7231) */
|
||||||
|
private static final int MIN_HTTP_STATUS = 100;
|
||||||
|
private static final int MAX_HTTP_STATUS = 599;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||||
|
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
|
// do nothing
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||||
|
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
|
String fieldName = "";
|
||||||
|
String statusParam = "";
|
||||||
|
try {
|
||||||
|
JsonNode rootNode = parseJson(adptGrpName, resultMessage);
|
||||||
|
fieldName = getFieldName(adptGrpName);
|
||||||
|
if (rootNode != null && StringUtils.isNotBlank(fieldName) && rootNode.has(fieldName)) {
|
||||||
|
statusParam = rootNode.get(fieldName).asText();
|
||||||
|
int httpStatus = Integer.parseInt(statusParam);
|
||||||
|
if (isValidHttpStatus(httpStatus)) {
|
||||||
|
response.setStatus(httpStatus);
|
||||||
|
} else {
|
||||||
|
logger.warn("유효하지 않은 HTTP 상태코드. 상태코드를 설정하지 않음. fieldName={}, value={}", fieldName, statusParam);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("설정과 맞지 않는 메시지. 상태코드를 설정하지 않음. fieldName={}", fieldName);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("상태코드 추출 실패. fieldName={}, value={}", fieldName, statusParam, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HTTP 상태코드로 사용 가능한 값인지 확인한다. (100 ~ 599) */
|
||||||
|
private boolean isValidHttpStatus(int httpStatus) {
|
||||||
|
return httpStatus >= MIN_HTTP_STATUS && httpStatus <= MAX_HTTP_STATUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFieldName(String adptGrpName) {
|
||||||
|
return PropManager.getInstance().getProperty(PROPGROUP, adptGrpName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode parseJson(String adptGrpName, Object message) throws Exception {
|
||||||
|
return JsonPathUtil.toTree(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
+766
@@ -0,0 +1,766 @@
|
|||||||
|
package com.eactive.eai.adapter.http.dynamic.impl;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.servlet.ServletInputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.fileupload.FileItem;
|
||||||
|
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
|
||||||
|
import org.apache.commons.fileupload.servlet.ServletFileUpload;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.time.StopWatch;
|
||||||
|
import org.apache.mina.common.ByteBuffer;
|
||||||
|
import org.json.simple.JSONArray;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
import org.json.simple.JSONValue;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
|
||||||
|
import com.eactive.eai.adapter.AdapterManager;
|
||||||
|
import com.eactive.eai.adapter.AdapterPropManager;
|
||||||
|
import com.eactive.eai.adapter.AdapterVO;
|
||||||
|
import com.eactive.eai.adapter.Keys;
|
||||||
|
import com.eactive.eai.adapter.http.HttpMemoryLogger;
|
||||||
|
import com.eactive.eai.adapter.http.HttpMethodType;
|
||||||
|
import com.eactive.eai.adapter.http.HttpStatusException;
|
||||||
|
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
|
||||||
|
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
|
||||||
|
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||||
|
import com.eactive.eai.common.TransactionContextKeys;
|
||||||
|
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||||
|
import com.eactive.eai.common.message.MessageType;
|
||||||
|
import com.eactive.eai.common.util.CommonLib;
|
||||||
|
import com.eactive.eai.common.util.HttpAdapterExtraLogUtil;
|
||||||
|
import com.eactive.eai.common.util.Logger;
|
||||||
|
import com.eactive.eai.common.util.MessageUtil;
|
||||||
|
import com.eactive.eai.env.ElinkConfig;
|
||||||
|
import com.eactive.eai.inbound.action.ActionFactory;
|
||||||
|
import com.eactive.eai.inbound.action.RequestAction;
|
||||||
|
import com.eactive.eai.inbound.processor.Processor;
|
||||||
|
import com.eactive.eai.message.StandardMessageUtil;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kbank 가상계좌 INBOUND
|
||||||
|
* @see com.eactive.eai.custom.adapter.http.dynamic.filter.VirtualAccountCryptoFilter
|
||||||
|
* @Deprecated
|
||||||
|
*/
|
||||||
|
// FIXME : kbank - kbank에서는 Controller 방식을 사용하므로, 이 어댑터는 사용되지 않음 (VirtualAccountCryptoFilter 로 대체)
|
||||||
|
public class HttpAdapterServiceVirtualAccount extends HttpAdapterServiceSupport {
|
||||||
|
public static final String HEADER_GROUP = "HEADER_GROUP";
|
||||||
|
// HEADER_GROUP JSON에 추가할 항목 정의, 없으면 전체 header 추가
|
||||||
|
public static final String HEADER_KEYS = "HEADER_KEYS";
|
||||||
|
public static final String HTTP_STATUS = "HTTP_STATUS";
|
||||||
|
public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod";
|
||||||
|
|
||||||
|
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||||
|
private String logPrefix = "HttpAdapterServiceRest] ";
|
||||||
|
|
||||||
|
private static final String JSON_CONTENT_TYPE = "application/json";
|
||||||
|
private static final String JSON_FIELD_NAME = "json-body";
|
||||||
|
private static final String FILE_GROUP_NAME = "image-file";
|
||||||
|
private static final String UPLOAD_ROOT_PATH = "UPLOAD_ROOT_PATH";
|
||||||
|
|
||||||
|
private Properties addCryptoFilter(Properties prop) {
|
||||||
|
String cryptoFilterName = "com.eactive.eai.custom.adapter.http.dynamic.filter.VirtualAccountCryptoFilter";
|
||||||
|
String addedPreFilter = prop.getProperty(PRE_FILTERS);
|
||||||
|
String addedPostFilter = prop.getProperty(POST_FILTERS);
|
||||||
|
|
||||||
|
if(StringUtils.isBlank(addedPreFilter)) {
|
||||||
|
addedPreFilter = cryptoFilterName;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addedPreFilter = addedPreFilter + "," +cryptoFilterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(StringUtils.isBlank(addedPostFilter)) {
|
||||||
|
addedPostFilter = cryptoFilterName;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addedPostFilter = cryptoFilterName + "," +addedPostFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
prop.setProperty(PRE_FILTERS, addedPreFilter);
|
||||||
|
prop.setProperty(POST_FILTERS, addedPostFilter);
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readMultipartBody(HttpServletRequest request) throws Exception {
|
||||||
|
String jsonString = null;
|
||||||
|
// Create a factory for disk-based file items
|
||||||
|
DiskFileItemFactory factory = new DiskFileItemFactory();
|
||||||
|
|
||||||
|
// Set the maximum size of the files to be uploaded
|
||||||
|
factory.setSizeThreshold(1024 * 1024);
|
||||||
|
|
||||||
|
// Set the temporary directory to store uploaded files
|
||||||
|
File tempDir = (File) request.getSession().getServletContext().getAttribute("javax.servlet.context.tempdir");
|
||||||
|
factory.setRepository(tempDir);
|
||||||
|
|
||||||
|
ServletFileUpload upload = new ServletFileUpload(factory);
|
||||||
|
Map<String, String> fileMap = new HashMap<>();
|
||||||
|
|
||||||
|
InputStream fin = null;
|
||||||
|
try {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int read = 0;
|
||||||
|
|
||||||
|
List<FileItem> items = upload.parseRequest(request);
|
||||||
|
for (FileItem item : items) {
|
||||||
|
if (!item.isFormField()) {
|
||||||
|
// file
|
||||||
|
String fieldName = item.getFieldName();
|
||||||
|
String fileName = item.getName();
|
||||||
|
fin = item.getInputStream();
|
||||||
|
ByteArrayOutputStream fo = new ByteArrayOutputStream();
|
||||||
|
while ((read = fin.read(buffer)) > 0) {
|
||||||
|
fo.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
int fileSize = fo.size();
|
||||||
|
byte[] fileBytes = fo.toByteArray();
|
||||||
|
String fileContents = new String(fileBytes);
|
||||||
|
if (logger.isInfo()) {
|
||||||
|
logger.info("[FILE]-------------------------------------------------->");
|
||||||
|
logger.info("Field name = " + fieldName);
|
||||||
|
logger.info("File name = " + fileName + " contents length = " + fileSize);
|
||||||
|
logger.info("File Contents [" + fileContents + "]");
|
||||||
|
logger.info("[FILE]<--------------------------------------------------");
|
||||||
|
}
|
||||||
|
fileMap.put(fileName, fileContents);
|
||||||
|
fin.close();
|
||||||
|
} else {
|
||||||
|
// regular form field
|
||||||
|
String fieldName = item.getFieldName();
|
||||||
|
String fieldValue = item.getString();
|
||||||
|
if (logger.isInfo()) {
|
||||||
|
logger.info("[FIELD] " + fieldName + " [" + fieldValue + "]");
|
||||||
|
}
|
||||||
|
if (JSON_CONTENT_TYPE.equalsIgnoreCase(item.getContentType())
|
||||||
|
|| JSON_FIELD_NAME.equals(fieldName)) {
|
||||||
|
jsonString = fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isInfo()) {
|
||||||
|
logger.info("Json body [" + jsonString + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonString == null) {
|
||||||
|
jsonString = "{}";
|
||||||
|
} else {
|
||||||
|
// parsing json & add file contents
|
||||||
|
JSONObject jsonObject = (JSONObject) JSONValue.parse(jsonString);
|
||||||
|
if (jsonObject == null) {
|
||||||
|
jsonString = "{}";
|
||||||
|
} else {
|
||||||
|
JSONObject fileGroup = new JSONObject();
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : fileMap.entrySet()) {
|
||||||
|
fileGroup.put("fileName", entry.getKey());
|
||||||
|
fileGroup.put("fileContents", entry.getValue());
|
||||||
|
}
|
||||||
|
jsonObject.put(FILE_GROUP_NAME, fileGroup);
|
||||||
|
jsonString = jsonObject.toJSONString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isInfo()) {
|
||||||
|
logger.info("Json with file [" + jsonString + "]");
|
||||||
|
}
|
||||||
|
return jsonString;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Read multipart body error.", e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
if (fin != null) {
|
||||||
|
try {
|
||||||
|
fin.close();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkRootPath(String path) {
|
||||||
|
if (StringUtils.isEmpty(path)) {
|
||||||
|
logger.info("upload dir not set(UPLOAD_ROOT_PATH), use system temp " + path);
|
||||||
|
return System.getProperty("java.io.tmpdir");
|
||||||
|
}
|
||||||
|
File file = new File(path);
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.mkdirs();
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String uploadMultipartBody(HttpServletRequest request, String uploadRootPath) throws Exception {
|
||||||
|
String jsonString = null;
|
||||||
|
// Create a factory for disk-based file items
|
||||||
|
DiskFileItemFactory factory = new DiskFileItemFactory();
|
||||||
|
|
||||||
|
// Set the maximum size of the files to be uploaded
|
||||||
|
factory.setSizeThreshold(1024 * 1024);
|
||||||
|
|
||||||
|
// Set the temporary directory to store uploaded files
|
||||||
|
File tempDir = (File) request.getSession().getServletContext().getAttribute("javax.servlet.context.tempdir");
|
||||||
|
factory.setRepository(tempDir);
|
||||||
|
|
||||||
|
ServletFileUpload upload = new ServletFileUpload(factory);
|
||||||
|
try {
|
||||||
|
// if not exist, create folders
|
||||||
|
String uploadDir = checkRootPath(uploadRootPath);
|
||||||
|
List<FileItem> items = upload.parseRequest(request);
|
||||||
|
for (FileItem item : items) {
|
||||||
|
if (!item.isFormField()) {
|
||||||
|
// file
|
||||||
|
String fieldName = item.getFieldName();
|
||||||
|
String fileName = item.getName();
|
||||||
|
String uploadFilePath = uploadDir + File.separator + fileName;
|
||||||
|
File uploadFile = new File(uploadFilePath);
|
||||||
|
item.write(uploadFile);
|
||||||
|
|
||||||
|
if (logger.isInfo()) {
|
||||||
|
logger.info("[FILE]-------------------------------------------------->");
|
||||||
|
logger.info("Field name = " + fieldName);
|
||||||
|
logger.info("File name = " + fileName + " path = " + uploadFile.getAbsolutePath());
|
||||||
|
logger.info("[FILE]<--------------------------------------------------");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// regular form field
|
||||||
|
String fieldName = item.getFieldName();
|
||||||
|
String fieldValue = item.getString();
|
||||||
|
if (logger.isInfo()) {
|
||||||
|
logger.info("[FIELD] " + fieldName + " [" + fieldValue + "]");
|
||||||
|
}
|
||||||
|
if (JSON_CONTENT_TYPE.equalsIgnoreCase(item.getContentType())
|
||||||
|
|| JSON_FIELD_NAME.equals(fieldName)) {
|
||||||
|
jsonString = fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isInfo()) {
|
||||||
|
logger.info("Json body [" + jsonString + "]");
|
||||||
|
}
|
||||||
|
return jsonString;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Read multipart body error.", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "deprecation" })
|
||||||
|
public void service(String adptGrpName, String adptName, HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
int traceLevel = 0;
|
||||||
|
|
||||||
|
AdapterVO adptVO = null;
|
||||||
|
AdapterPropManager manager = null;
|
||||||
|
|
||||||
|
Properties httpProp = null;
|
||||||
|
String responseType = null;
|
||||||
|
String urlDecodeYn = null;
|
||||||
|
String encode = null;
|
||||||
|
|
||||||
|
String traceLevelTemp = null;
|
||||||
|
String relayRequestHeaderKeys = null;
|
||||||
|
String headerGroupName = null;
|
||||||
|
|
||||||
|
boolean isParameterType = false;
|
||||||
|
String message = null;
|
||||||
|
|
||||||
|
StopWatch stopWatch = null;
|
||||||
|
Properties prop = null;
|
||||||
|
String paramValue = null;
|
||||||
|
String adptMsgType = null;
|
||||||
|
String errorResponseFormat = null;
|
||||||
|
String uploadRootPath = null;
|
||||||
|
try {
|
||||||
|
AdapterManager adapterManager = AdapterManager.getInstance();
|
||||||
|
adptVO = adapterManager.getAdapterVO(adptGrpName, adptName);
|
||||||
|
if (adptVO == null) {
|
||||||
|
throw new Exception("Adapter not found error");
|
||||||
|
}
|
||||||
|
|
||||||
|
manager = AdapterPropManager.getInstance();
|
||||||
|
|
||||||
|
httpProp = manager.getProperties(adptVO.getPropGroupName());
|
||||||
|
responseType = httpProp.getProperty(RESPONSE_TYPE, "SYNC");
|
||||||
|
urlDecodeYn = httpProp.getProperty(URL_DECODE_YN, "N");
|
||||||
|
// encode = httpProp.getProperty(ENCODE, "UTF-8");
|
||||||
|
encode = StringUtils.defaultIfBlank(adapterManager.getAdapterGroupVO(adptGrpName).getMessageEncode(),
|
||||||
|
"UTF-8");
|
||||||
|
traceLevelTemp = httpProp.getProperty(TRACE_LEVEL, "0");
|
||||||
|
relayRequestHeaderKeys = httpProp.getProperty(HEADER_KEYS);
|
||||||
|
headerGroupName = httpProp.getProperty(HEADER_GROUP);
|
||||||
|
errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT);
|
||||||
|
uploadRootPath = httpProp.getProperty(UPLOAD_ROOT_PATH);
|
||||||
|
prop = new Properties();
|
||||||
|
prop.put(INBOUND_METHOD, request.getMethod());
|
||||||
|
prop.put(INBOUND_URI, request.getRequestURI());
|
||||||
|
prop.put(INBOUND_HEADER, getHeaders(request));
|
||||||
|
prop.put(INBOUND_EXTPARAMS, StringUtils.defaultString(request.getQueryString()));
|
||||||
|
if (StringUtils.equals(adptVO.getAdapterGroupVO().getType(), Keys.TYPE_REST)
|
||||||
|
|| StringUtils.equals(adptVO.getAdapterGroupVO().getType(), Keys.TYPE_HTTP_CUSTOM)) {
|
||||||
|
// /api/v1/public/getUserInfo.svc
|
||||||
|
String extUrl = StringUtils.removeStart(request.getRequestURI(), request.getContextPath());
|
||||||
|
prop.put(INBOUND_EXTURI, extUrl);
|
||||||
|
} else {
|
||||||
|
prop.put(INBOUND_EXTURI, getExtUri(request));
|
||||||
|
}
|
||||||
|
prop.put(Processor.REQUEST_ACTION, adptVO.getAdapterGroupVO().getRefClass());
|
||||||
|
prop.put(API_PATH, httpProp.getProperty(API_PATH, ""));
|
||||||
|
prop.put(PRE_FILTERS, httpProp.getProperty(PRE_FILTERS, ""));
|
||||||
|
prop.put(POST_FILTERS, httpProp.getProperty(POST_FILTERS, ""));
|
||||||
|
prop.put(PROPERTIES_NAME_HTTP_REQUEST_METHOD, request.getMethod());
|
||||||
|
prop.put(ALLOW_IP, httpProp.getProperty(ALLOW_IP, ""));
|
||||||
|
|
||||||
|
isParameterType = false;
|
||||||
|
try {
|
||||||
|
traceLevel = Integer.parseInt(traceLevelTemp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
traceLevel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch = new StopWatch();
|
||||||
|
stopWatch.start();
|
||||||
|
|
||||||
|
logger.debug("시작 >> encode = [" + encode + "]");
|
||||||
|
|
||||||
|
switch (HttpMethodType.getValue(request.getMethod())) {
|
||||||
|
case GET:
|
||||||
|
case DELETE:
|
||||||
|
isParameterType = true;
|
||||||
|
break;
|
||||||
|
case POST:
|
||||||
|
case PUT:
|
||||||
|
if (StringUtils.contains(request.getContentType(), "application/x-www-form-urlencoded")) {
|
||||||
|
isParameterType = true;
|
||||||
|
} else {
|
||||||
|
isParameterType = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isParameterType) {
|
||||||
|
paramValue = request.getQueryString();
|
||||||
|
if (paramValue == null)
|
||||||
|
paramValue = "";
|
||||||
|
if (traceLevel >= 3) {
|
||||||
|
HttpMemoryLogger.txlog(adptGrpName + adptName,
|
||||||
|
"RECV " + "[" + paramValue + "]" + CommonLib.getDumpMessage(paramValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
// json으로 변환
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("{");
|
||||||
|
Map<String, String[]> paramMap = assignParameterMap(request, adptGrpName, adptName, null, prop);
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||||
|
if (i > 0) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append("\"").append(entry.getKey()).append("\":");
|
||||||
|
String[] values = entry.getValue();
|
||||||
|
if (values.length > 1) {
|
||||||
|
// ["111", "222"]
|
||||||
|
sb.append("[");
|
||||||
|
for (int j = 0; j < values.length; j++) {
|
||||||
|
if (j > 0) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append("\"").append(JSONValue.escape(values[j])).append("\"");
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
} else {
|
||||||
|
sb.append("\"").append(JSONValue.escape(values[0])).append("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("}");
|
||||||
|
|
||||||
|
paramValue = sb.toString();
|
||||||
|
} else {
|
||||||
|
if (ServletFileUpload.isMultipartContent(request)) {
|
||||||
|
// TODO : 아래의 로직은 업무에 맞게 수정이 필요함.
|
||||||
|
// 불필요할 경우 제거
|
||||||
|
// if(StringUtils.isEmpty(uploadRootPath)) {
|
||||||
|
// uploadRootPath = System.getProperty("java.io.tmpdir");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 임시로직 : UPLOAD_ROOT_PATH 가 없는 경우에는 JSON에 추가
|
||||||
|
if (StringUtils.isEmpty(uploadRootPath)) {
|
||||||
|
paramValue = readMultipartBody(request);
|
||||||
|
} else {
|
||||||
|
paramValue = uploadMultipartBody(request, uploadRootPath);
|
||||||
|
}
|
||||||
|
// TEST : 테스트용 임시코드
|
||||||
|
// response.setCharacterEncoding(encode);
|
||||||
|
// response.getWriter().print(paramValue);
|
||||||
|
// return;
|
||||||
|
} else {
|
||||||
|
ServletInputStream sis = request.getInputStream();
|
||||||
|
ByteBuffer bb = ByteBuffer.allocate(1024).setAutoExpand(true);
|
||||||
|
int i = 0;
|
||||||
|
byte[] cbuf = new byte[1024];
|
||||||
|
while ((i = sis.read(cbuf, 0, 1024)) != -1) {
|
||||||
|
if (i == 1024) {
|
||||||
|
bb.put(cbuf);
|
||||||
|
} else {
|
||||||
|
byte[] tail = new byte[i];
|
||||||
|
System.arraycopy(cbuf, 0, tail, 0, i);
|
||||||
|
bb.put(tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] data = new byte[bb.position()];
|
||||||
|
bb.position(0);
|
||||||
|
bb.get(data);
|
||||||
|
paramValue = new String(data, encode);
|
||||||
|
|
||||||
|
if (traceLevel >= 3) {
|
||||||
|
HttpMemoryLogger.txlog(adptGrpName + adptName,
|
||||||
|
"RECV " + "[" + paramValue + "]" + CommonLib.getDumpMessage(paramValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramValue == null) { // parameter가 없는 경우때문에 처리
|
||||||
|
paramValue = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
logger.debug("HttpAdapterServiceRest] RECV (" + adptGrpName + ") = [" + paramValue + "]\n"
|
||||||
|
+ CommonLib.getDumpMessage(paramValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("Y".equals(urlDecodeYn) && isParameterType) {
|
||||||
|
message = URLDecoder.decode(paramValue);
|
||||||
|
} else {
|
||||||
|
message = paramValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
String[] msgArgs = new String[2];
|
||||||
|
msgArgs[0] = adptGrpName;
|
||||||
|
msgArgs[1] = message;
|
||||||
|
String resMsg = ExceptionUtil.make("RICEAIAHA005", msgArgs);
|
||||||
|
logger.debug(logPrefix + resMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
adptMsgType = adptVO.getAdapterGroupVO().getMessageType();
|
||||||
|
if (StringUtils.equals(adptMsgType, MessageType.JSON)) {
|
||||||
|
response.setContentType(JSON_CONTENT_TYPE+"; charset="+encode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEADER_GROUP 셋팅
|
||||||
|
if (MessageType.JSON.equals(adptMsgType) && StringUtils.isNotBlank(headerGroupName)
|
||||||
|
&& StringUtils.isNotBlank(relayRequestHeaderKeys)) {
|
||||||
|
JSONObject jsonMessage = (JSONObject) JSONValue.parse(message);
|
||||||
|
JSONObject headerJson = new JSONObject();
|
||||||
|
if (StringUtils.equalsIgnoreCase(relayRequestHeaderKeys, "ALL")) {
|
||||||
|
for (Enumeration<String> e = request.getHeaderNames(); e.hasMoreElements();) {
|
||||||
|
String key = e.nextElement();
|
||||||
|
headerJson.put(key, request.getHeader(key));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String[] relayKeyArr = org.springframework.util.StringUtils
|
||||||
|
.tokenizeToStringArray(relayRequestHeaderKeys, ",");
|
||||||
|
|
||||||
|
for (String key : relayKeyArr) {
|
||||||
|
String headerValue = request.getHeader(key);
|
||||||
|
if (StringUtils.isNotBlank(headerValue)) {
|
||||||
|
headerJson.put(key, headerValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerJson.size() > 0) {
|
||||||
|
jsonMessage.put(headerGroupName, headerJson);
|
||||||
|
message = jsonMessage.toJSONString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message == null) {
|
||||||
|
message = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// com.eactive.eai.custom.adapter.http.dynamic.filter.VirtualAccountCryptoFilter
|
||||||
|
prop = addCryptoFilter(prop);
|
||||||
|
|
||||||
|
// 로컬 서비스 호출 ,encoding 처리 추가
|
||||||
|
String result = (String) service(adptGrpName, adptName, message, prop, request, response);
|
||||||
|
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
logger.debug("HttpAdapterServiceRest] result " + encode + " (" + adptGrpName + ") = [" + result + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.stop();
|
||||||
|
|
||||||
|
String responseData = "";
|
||||||
|
if (RESPONSE_TYPE_ASYNC.equals(responseType)) {
|
||||||
|
if (stopWatch.getTime() > slowTranTime && (logger.isInfo())) {
|
||||||
|
logger.info("HttpAdapterServiceRest] dummy response time = " + stopWatch.toString() + ", message = "
|
||||||
|
+ message);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setCharacterEncoding(encode);
|
||||||
|
response.getWriter().print(responseData);
|
||||||
|
if (traceLevel >= 3) {
|
||||||
|
HttpMemoryLogger.txlog(adptGrpName + adptName,
|
||||||
|
"SEND " + "[" + responseData + "]" + CommonLib.getDumpMessage(responseData));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responseData = result;
|
||||||
|
logger.info("종료 >> encode = [" + encode + "]");
|
||||||
|
|
||||||
|
JSONObject dataObject = null;
|
||||||
|
if (MessageType.JSON.equals(adptMsgType)) {
|
||||||
|
dataObject = (JSONObject) JSONValue.parse(responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEADER_GROUP 하위 필드를 response Header에 세팅한다.
|
||||||
|
HashMap<String, String> header = new HashMap<>();
|
||||||
|
boolean redirect = assignHttpHeaders(header, dataObject, headerGroupName);
|
||||||
|
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
logger.debug("HttpAdapterServiceRest] response header field (" + adptGrpName + ") = ["
|
||||||
|
+ header.toString() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
response.setStatus(302);
|
||||||
|
logger.debug("HttpAdapterServiceRest] set response status_code: 302");
|
||||||
|
} else {
|
||||||
|
String httpStatus = header.get(HTTP_STATUS);
|
||||||
|
if (httpStatus != null && !"".equals(httpStatus))
|
||||||
|
response.setStatus(Integer.parseInt(httpStatus));
|
||||||
|
header.remove(HTTP_STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// response header 셋팅
|
||||||
|
for (Map.Entry<String, String> entry : header.entrySet()) {
|
||||||
|
response.setHeader(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataObject != null) {
|
||||||
|
responseData = dataObject.toJSONString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI와 통신시(UTF-8) 변환오류로 ENCODE 제거
|
||||||
|
response.setCharacterEncoding(encode);
|
||||||
|
response.getWriter().print(responseData);
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
logger.debug("HttpAdapterServiceRest] SEND (" + adptGrpName + ") = [" + responseData + "]");
|
||||||
|
logger.debug("HttpAdapterServiceRest] SEND (" + adptGrpName + ") = "
|
||||||
|
+ CommonLib.getDumpMessage(responseData));
|
||||||
|
}
|
||||||
|
if (traceLevel >= 3) {
|
||||||
|
HttpMemoryLogger.txlog(adptGrpName + adptName,
|
||||||
|
"SEND " + "[" + responseData + "]" + CommonLib.getDumpMessage(responseData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (HttpStatusException e) {
|
||||||
|
if (traceLevel >= 3) {
|
||||||
|
HttpMemoryLogger.error(adptGrpName + adptName, e.toString(), e);
|
||||||
|
}
|
||||||
|
logger.warn("HttpAdapter] " + adptGrpName + "-" + adptName + ">>" + e.getMessage());
|
||||||
|
response.setStatus(e.getStatus());
|
||||||
|
try {
|
||||||
|
String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, e.getCode(),
|
||||||
|
e.getMessage(), errorResponseFormat);
|
||||||
|
response.getWriter().println(errorMsg);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// IGNORE
|
||||||
|
}
|
||||||
|
} catch (JwtAuthException e) {
|
||||||
|
if (traceLevel >= 3) {
|
||||||
|
HttpMemoryLogger.error(adptGrpName + adptName, e.toString(), e);
|
||||||
|
}
|
||||||
|
logger.error(logPrefix + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
try {
|
||||||
|
String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, e.getCode(),
|
||||||
|
e.getMessage(), errorResponseFormat);
|
||||||
|
response.getWriter().println(errorMsg);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// IGNORE
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (traceLevel >= 3) {
|
||||||
|
HttpMemoryLogger.error(adptGrpName + adptName, e.toString(), e);
|
||||||
|
}
|
||||||
|
logger.error(logPrefix + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||||
|
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||||
|
try {
|
||||||
|
response.getWriter().println(e.getMessage());
|
||||||
|
String errCode = ExceptionUtil.getErrorCode(e, "RECEAIAHA003");
|
||||||
|
throw new Exception(errCode);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// IGNORE
|
||||||
|
logger.warn(logPrefix + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
||||||
|
String url = prop.getProperty(HttpClientAdapterServiceKey.INBOUND_EXTURI);
|
||||||
|
String method = prop.getProperty(HttpClientAdapterServiceKey.INBOUND_METHOD);
|
||||||
|
String adapterGroupName = prop.getProperty(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
|
||||||
|
String adapterName = prop.getProperty(HttpClientAdapterServiceKey.ADAPTER_NAME);
|
||||||
|
int httpStatusCode = response.getStatus();
|
||||||
|
HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, 400, adapterGroupName, adapterName, new HashMap<>(), url, method, httpStatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String[]> assignParameterMap(HttpServletRequest request, String adptGrpName, String adptName,
|
||||||
|
Object requestBytes, Properties prop) {
|
||||||
|
// PathVariable 체크
|
||||||
|
if (StringUtils.equalsAnyIgnoreCase(request.getMethod(), HttpMethod.GET.name(), HttpMethod.DELETE.name())
|
||||||
|
&& StringUtils.isBlank(request.getQueryString())) {
|
||||||
|
try {
|
||||||
|
String actionName = prop.getProperty(Processor.REQUEST_ACTION);
|
||||||
|
RequestAction action = ActionFactory.createAction(actionName);
|
||||||
|
action.setAdapterInfo(adptGrpName, adptName, prop);
|
||||||
|
String[] keys = action.perform(requestBytes);
|
||||||
|
String requestPath = keys[0];
|
||||||
|
|
||||||
|
// PathVariable 지원 추가
|
||||||
|
String ruledPath = StandardMessageUtil.getMatchedKey(requestPath, actionName);
|
||||||
|
if (!StringUtils.equals(requestPath, ruledPath) && StringUtils.contains(ruledPath, "{")) {
|
||||||
|
Map<String, String> paramMap = new AntPathMatcher().extractUriTemplateVariables(ruledPath,
|
||||||
|
requestPath);
|
||||||
|
if (paramMap != null && paramMap.size() > 0) {
|
||||||
|
Map<String, String[]> returnMap = new HashMap<>();
|
||||||
|
for (String key : paramMap.keySet()) {
|
||||||
|
if (StringUtils.equalsIgnoreCase(key, "method")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
returnMap.put(key, new String[] { paramMap.get(key) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.getParameterMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateServiceAndAdapter(String adptGrpName, String adptName, byte[] requestBytes, Properties prop)
|
||||||
|
throws JwtAuthException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request header 를 hashmap으로 조립
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private Properties getHeaders(HttpServletRequest request) {
|
||||||
|
Properties prop = new Properties();
|
||||||
|
|
||||||
|
Enumeration<String> headerNames = request.getHeaderNames();
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
String key = headerNames.nextElement();
|
||||||
|
String value = request.getHeader(key);
|
||||||
|
prop.setProperty(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adapter property의 HEADER_GROUP으로 정의된 (MFE_HEADER) 그룹의 하위 필드를 http Header에
|
||||||
|
* 세팅한다.
|
||||||
|
*
|
||||||
|
* @param header
|
||||||
|
* @param object
|
||||||
|
*/
|
||||||
|
private boolean assignHttpHeaders(HashMap<String, String> header, Object msg, String headerGroupName) {
|
||||||
|
boolean redirect = false;
|
||||||
|
|
||||||
|
if (msg == null || StringUtils.isBlank(headerGroupName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg instanceof JSONObject) {
|
||||||
|
JSONObject headerObject = (JSONObject) ((JSONObject) msg).get(headerGroupName);
|
||||||
|
|
||||||
|
if (headerObject == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List<String> headerKeys = new ArrayList<>();
|
||||||
|
for (Object key : headerObject.keySet()) {
|
||||||
|
|
||||||
|
Object obj = headerObject.get(key);
|
||||||
|
if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.put((String) key, (String) obj);
|
||||||
|
|
||||||
|
if (StringUtils.equalsIgnoreCase((String) key, "Location")) {
|
||||||
|
redirect = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
((JSONObject) msg).remove(headerGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 어댑터 명 이후의 URI값을 가져온다.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String getExtUri(HttpServletRequest request) {
|
||||||
|
String orgUri = request.getRequestURI().replaceAll(request.getContextPath(), "");
|
||||||
|
String uri = getExtUri(orgUri, 3);
|
||||||
|
if (uri != null && uri.trim().length() > 0) {
|
||||||
|
return "/" + uri;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getExtUri(String url, int length) {
|
||||||
|
String[] urls = url.split("/");
|
||||||
|
List<String> newUrls = new ArrayList<>();
|
||||||
|
Collections.addAll(newUrls, urls);
|
||||||
|
return StringUtils.join(newUrls.subList(length, urls.length).toArray(), "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static void main(String[] args) throws Exception {
|
||||||
|
// String orgUri = "/HTT/CbsInNetSys/abcd/123456";
|
||||||
|
// String result = "";
|
||||||
|
// result = getExtUri(orgUri, 3);
|
||||||
|
// System.out.println(result);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -163,7 +163,14 @@ public class EncryptionManager implements Lifecycle {
|
|||||||
if (this.isEncrypted(dbData)) {
|
if (this.isEncrypted(dbData)) {
|
||||||
if ("DAMO".equals(dbEncryptSolutionName)) {
|
if ("DAMO".equals(dbEncryptSolutionName)) {
|
||||||
com.eactive.ext.djb.DamoManager damoManager = new com.eactive.ext.djb.DamoManager();
|
com.eactive.ext.djb.DamoManager damoManager = new com.eactive.ext.djb.DamoManager();
|
||||||
return damoManager.decrypt(dbData);
|
try {
|
||||||
|
return damoManager.decrypt(dbData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 복호화 실패시 원본 반환
|
||||||
|
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
|
||||||
|
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
|
||||||
|
return dbData;
|
||||||
|
}
|
||||||
} else if ("SAFEDB".equals(dbEncryptSolutionName)) {
|
} else if ("SAFEDB".equals(dbEncryptSolutionName)) {
|
||||||
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
|
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ package com.eactive.eai.common.util;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -63,13 +61,17 @@ public class HttpAdapterExtraLogUtil {
|
|||||||
httpAdapterExtraLogVo.setHttpMethod(httpMethod);
|
httpAdapterExtraLogVo.setHttpMethod(httpMethod);
|
||||||
|
|
||||||
for (HttpAdapterExtraHeaderVo httpAdapterExtraHeaderVo : headerVoList) {
|
for (HttpAdapterExtraHeaderVo httpAdapterExtraHeaderVo : headerVoList) {
|
||||||
// String name = httpAdapterExtraHeaderVo.getName();
|
String name = httpAdapterExtraHeaderVo.getName();
|
||||||
// if(StringUtils.isNotBlank(name) && "authorization".equals(name.toLowerCase())) {
|
// if(StringUtils.isNotBlank(name) && "authorization".equals(name.toLowerCase())) {
|
||||||
// httpAdapterExtraHeaderVo.setValue("{hidden}");
|
// httpAdapterExtraHeaderVo.setValue("{hidden}");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if(StringUtils.isNotBlank(name) && BODY_FIELD_NAME.equals(name))
|
||||||
|
continue;
|
||||||
|
|
||||||
String value = httpAdapterExtraHeaderVo.getValue();
|
String value = httpAdapterExtraHeaderVo.getValue();
|
||||||
if(StringUtils.isNotBlank(value) && value.length() > MAX_HEADER_VALUE_SIZE) {
|
if(StringUtils.isNotBlank(value) && value.length() > MAX_HEADER_VALUE_SIZE) {
|
||||||
value = value.substring(0, 400) + "...";
|
value = value.substring(0, MAX_HEADER_VALUE_SIZE) + "...";
|
||||||
httpAdapterExtraHeaderVo.setValue(value);
|
httpAdapterExtraHeaderVo.setValue(value);
|
||||||
}else if(value == null){
|
}else if(value == null){
|
||||||
httpAdapterExtraHeaderVo.setValue(" ");
|
httpAdapterExtraHeaderVo.setValue(" ");
|
||||||
@@ -116,7 +118,7 @@ public class HttpAdapterExtraLogUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<HttpAdapterExtraHeaderVo> convertHeaderToListOfHttpAdapterExtraHeaderVo(Header[] headers) {
|
public static List<HttpAdapterExtraHeaderVo> convertHeaderToListOfHttpAdapterExtraHeaderVo(Header[] headers) {
|
||||||
Set<String> seenNames = new HashSet<>();
|
// Set<String> seenNames = new HashSet<>();
|
||||||
return Arrays.stream(headers)
|
return Arrays.stream(headers)
|
||||||
// .filter(header -> seenNames.add(header.getName())) // 중복된 이름을 스킵
|
// .filter(header -> seenNames.add(header.getName())) // 중복된 이름을 스킵
|
||||||
.map(header -> new HttpAdapterExtraHeaderVo(header.getName(), header.getValue()))
|
.map(header -> new HttpAdapterExtraHeaderVo(header.getName(), header.getValue()))
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ public class RequestProcessor extends RequestProcessorSupport {
|
|||||||
|
|
||||||
// UUID 생성 : UUID에서 - 없는 32자리
|
// UUID 생성 : UUID에서 - 없는 32자리
|
||||||
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
||||||
uuid = uuid == null ? UUIDGenerator.getUUID().toString().replaceAll("-", "") : uuid;
|
uuid = uuid == null ? instid+UUIDGenerator.getUUID().toString().replaceAll("-", "") : uuid;
|
||||||
// UUID 생성 : UUID = server구분4자리 + UUID
|
// UUID 생성 : UUID = server구분4자리 + UUID
|
||||||
/*
|
/*
|
||||||
String uuid = "";
|
String uuid = "";
|
||||||
|
|||||||
@@ -704,6 +704,18 @@ public abstract class DefaultProcess extends Process {
|
|||||||
Object responseObj = adapterErrorMessageHandler.generateNonStandardErrorResponseMessage(inboudnAdapterGroupName, inboudnAdapterName, this.callProp, this.tgtTranObject, this.resEaiMsg);
|
Object responseObj = adapterErrorMessageHandler.generateNonStandardErrorResponseMessage(inboudnAdapterGroupName, inboudnAdapterName, this.callProp, this.tgtTranObject, this.resEaiMsg);
|
||||||
if(responseObj != null) {
|
if(responseObj != null) {
|
||||||
resStandardMessage.setBizData(responseObj, inboundAdapterGroupVO.getMessageEncode());
|
resStandardMessage.setBizData(responseObj, inboundAdapterGroupVO.getMessageEncode());
|
||||||
|
} else {
|
||||||
|
if ((com.eactive.eai.adapter.Keys.IF_STANDARD.equals(this.adptrMsgPtrnCd)
|
||||||
|
||com.eactive.eai.adapter.Keys.IF_SUBSTANDARD.equals(this.adptrMsgPtrnCd))) {
|
||||||
|
String errorCode = mapper.getErrorCode(resStandardMessage);
|
||||||
|
String errorMsg = StringUtils.trim(mapper.getErrorMsg(resStandardMessage));
|
||||||
|
String errorDesc = StringUtils.trim(resStandardMessage.findItemValue("MSG.MAIN_MSG.outp_msg_desc"));
|
||||||
|
this.resEaiMsg.setRspErr("RECEAIINA001", String.format("[%s] %s (%s)", errorCode, errorMsg, errorDesc));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.resEaiMsg.setRspErr("RECEAIINA001", "비표준 오류 응답 수신");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resEaiMsg.setRspErrCd(EAIMessageKeys.BWK_FAILMSG_CODE, false);
|
this.resEaiMsg.setRspErrCd(EAIMessageKeys.BWK_FAILMSG_CODE, false);
|
||||||
|
|||||||
+424
@@ -1383,4 +1383,428 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// default.* 템플릿 폴백
|
||||||
|
//
|
||||||
|
// 그룹 전용 키가 미설정/공백이면 "default." 로 시작하는 공통 키를 재조회한다.
|
||||||
|
// generateNonStandardErrorResponseMessage : {group}.template → default.template
|
||||||
|
// generateOutboundErrorResponseMessage : {group}.template → default.template
|
||||||
|
// generateNonStandardInternalErrorResponseMessage : {group}.sys.template → default.sys.template
|
||||||
|
// generateNonStandardInboundErrorResponseMessage : {group}.in.{code}.template
|
||||||
|
// → default.in.{code}.template
|
||||||
|
// → {group}.in.template
|
||||||
|
// → default.in.template
|
||||||
|
// → MessageUtil 폴백
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("default.* 템플릿 폴백")
|
||||||
|
class DefaultTemplateFallback {
|
||||||
|
|
||||||
|
private static final String G = TemplateAdapterErrorMsgHandler.PROP_GROUP;
|
||||||
|
|
||||||
|
private PropManager mockPropManager;
|
||||||
|
private AdapterManager mockAdapterManager;
|
||||||
|
private AdapterPropManager mockAdapterPropManager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpDefaults() throws Exception {
|
||||||
|
mockPropManager = Mockito.mock(PropManager.class);
|
||||||
|
mockAdapterManager = Mockito.mock(AdapterManager.class);
|
||||||
|
mockAdapterPropManager = Mockito.mock(AdapterPropManager.class);
|
||||||
|
|
||||||
|
// 명시적으로 stub 하지 않은 모든 키는 미설정(null) 로 간주
|
||||||
|
Mockito.when(mockPropManager.getProperty(anyString(), anyString())).thenReturn(null);
|
||||||
|
Mockito.when(mockPropManager.getProperty(anyString(), anyString(), anyString()))
|
||||||
|
.thenAnswer(invocation -> invocation.getArgument(2));
|
||||||
|
|
||||||
|
AdapterGroupVO adapterGroupVO = Mockito.mock(AdapterGroupVO.class);
|
||||||
|
AdapterVO adapterVO = Mockito.mock(AdapterVO.class);
|
||||||
|
Mockito.when(adapterGroupVO.getMessageType()).thenReturn("JSON");
|
||||||
|
Mockito.when(adapterGroupVO.getMessageEncode()).thenReturn("UTF-8");
|
||||||
|
Mockito.when(adapterVO.getPropGroupName()).thenReturn("TEST_PROP");
|
||||||
|
Mockito.when(adapterVO.getAdapterGroupVO()).thenReturn(adapterGroupVO);
|
||||||
|
Mockito.when(mockAdapterManager.getAdapterGroupVO("MY_GROUP")).thenReturn(adapterGroupVO);
|
||||||
|
Mockito.when(mockAdapterManager.getAdapterVO("MY_GROUP", "MY_ADAPTER")).thenReturn(adapterVO);
|
||||||
|
|
||||||
|
Properties httpProp = new Properties();
|
||||||
|
httpProp.setProperty("ERROR_RESPONSE_FORMAT", "");
|
||||||
|
Mockito.when(mockAdapterPropManager.getProperties("TEST_PROP")).thenReturn(httpProp);
|
||||||
|
|
||||||
|
ApplicationContext mockCtx = Mockito.mock(ApplicationContext.class);
|
||||||
|
Mockito.when(mockCtx.getBean(PropManager.class)).thenReturn(mockPropManager);
|
||||||
|
Mockito.when(mockCtx.getBean(AdapterManager.class)).thenReturn(mockAdapterManager);
|
||||||
|
Mockito.when(mockCtx.getBean(AdapterPropManager.class)).thenReturn(mockAdapterPropManager);
|
||||||
|
Field ctxField = ApplicationContextProvider.class.getDeclaredField("context");
|
||||||
|
ctxField.setAccessible(true);
|
||||||
|
ctxField.set(null, mockCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prop(String key, String value) {
|
||||||
|
Mockito.when(mockPropManager.getProperty(eq(G), eq(key))).thenReturn(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// generateNonStandardErrorResponseMessage : default.template
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("generateNonStandardErrorResponseMessage – default.template")
|
||||||
|
class NonStandardError {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹 템플릿이 있으면 default.template 을 조회하지 않는다")
|
||||||
|
void 그룹_템플릿_우선() throws Exception {
|
||||||
|
prop("MY_GROUP.template", "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E001", "오류", "", null)));
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"E001\"}", result);
|
||||||
|
Mockito.verify(mockPropManager, Mockito.never()).getProperty(G, "default.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹 템플릿 미설정 시 default.template 으로 폴백")
|
||||||
|
void default_폴백() throws Exception {
|
||||||
|
prop("default.template", "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"src\":\"default\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E777", "오류", "", null)));
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"E777\",\"src\":\"default\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹 템플릿이 공백 문자열이어도 default.template 으로 폴백")
|
||||||
|
void 공백_템플릿도_폴백() throws Exception {
|
||||||
|
prop("MY_GROUP.template", " ");
|
||||||
|
prop("default.template", "{\"src\":\"default\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E001", "오류", "", null)));
|
||||||
|
|
||||||
|
assertEquals("{\"src\":\"default\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹/default 둘 다 없으면 기존대로 null 반환")
|
||||||
|
void 둘다_없으면_null() throws Exception {
|
||||||
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E001", "오류", "", null)));
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
// 폴백 조회가 실제로 시도되었는지 확인
|
||||||
|
Mockito.verify(mockPropManager).getProperty(G, "MY_GROUP.template");
|
||||||
|
Mockito.verify(mockPropManager).getProperty(G, "default.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("조회 순서: {group}.template → default.template")
|
||||||
|
void 조회_순서() throws Exception {
|
||||||
|
handler.generateNonStandardErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E001", "오류", "", null)));
|
||||||
|
|
||||||
|
InOrder inOrder = Mockito.inOrder(mockPropManager);
|
||||||
|
inOrder.verify(mockPropManager).getProperty(G, "MY_GROUP.template");
|
||||||
|
inOrder.verify(mockPropManager).getProperty(G, "default.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("default.template 에서도 callProp/foreach 치환이 동일하게 동작")
|
||||||
|
void default_템플릿_전체_치환() throws Exception {
|
||||||
|
prop("default.template",
|
||||||
|
"{\"adapter\":\"${callprop.ADAPTER_NAME}\"," +
|
||||||
|
"\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"," +
|
||||||
|
"\"errors\":[{{#foreach MSG.MSG_LIST}}{\"c\":\"${outp_msg_cd}\"}{{/foreach}}]}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", props("ADAPTER_NAME", "REAL"), null,
|
||||||
|
toEaiMessage(buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{{"E001", "오류A", "", ""}, {"E002", "오류B", "", ""}})));
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"{\"adapter\":\"REAL\",\"code\":\"E001\"," +
|
||||||
|
"\"errors\":[{\"c\":\"E001\"},{\"c\":\"E002\"}]}",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// generateNonStandardInternalErrorResponseMessage : default.sys.template
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("generateNonStandardInternalErrorResponseMessage – default.sys.template")
|
||||||
|
class InternalError {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹 sys 템플릿이 있으면 default.sys.template 을 조회하지 않는다")
|
||||||
|
void 그룹_템플릿_우선() throws Exception {
|
||||||
|
prop("MY_GROUP.sys.template", "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E500", "내부오류", "", null)));
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"E500\"}", result);
|
||||||
|
Mockito.verify(mockPropManager, Mockito.never()).getProperty(G, "default.sys.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹 sys 템플릿 미설정 시 default.sys.template 으로 폴백")
|
||||||
|
void default_폴백() throws Exception {
|
||||||
|
prop("default.sys.template",
|
||||||
|
"{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"msg\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E500", "시스템오류", "", null)));
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"E500\",\"msg\":\"시스템오류\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("내부오류는 default.template 이 아니라 default.sys.template 을 조회한다")
|
||||||
|
void sys_전용_키_사용() throws Exception {
|
||||||
|
// default.template 만 설정된 상태 → 내부오류에는 적용되지 않아야 한다
|
||||||
|
prop("default.template", "{\"src\":\"non-sys\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E500", "시스템오류", "", null)));
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
Mockito.verify(mockPropManager).getProperty(G, "default.sys.template");
|
||||||
|
Mockito.verify(mockPropManager, Mockito.never()).getProperty(G, "default.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹/default 둘 다 없으면 기존대로 null 반환")
|
||||||
|
void 둘다_없으면_null() throws Exception {
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E500", "오류", "", null)));
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("조회 순서: {group}.sys.template → default.sys.template")
|
||||||
|
void 조회_순서() throws Exception {
|
||||||
|
handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E500", "오류", "", null)));
|
||||||
|
|
||||||
|
InOrder inOrder = Mockito.inOrder(mockPropManager);
|
||||||
|
inOrder.verify(mockPropManager).getProperty(G, "MY_GROUP.sys.template");
|
||||||
|
inOrder.verify(mockPropManager).getProperty(G, "default.sys.template");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// generateOutboundErrorResponseMessage : default.template
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("generateOutboundErrorResponseMessage – default.template")
|
||||||
|
class OutboundError {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹 템플릿이 있으면 default.template 을 조회하지 않는다")
|
||||||
|
void 그룹_템플릿_우선() throws Exception {
|
||||||
|
prop("OUT_GROUP.template", "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateOutboundErrorResponseMessage(
|
||||||
|
"OUT_GROUP", "OUT_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E001", "오류", "", null)));
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"E001\"}", result);
|
||||||
|
Mockito.verify(mockPropManager, Mockito.never()).getProperty(G, "default.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹 템플릿 미설정 시 default.template 으로 폴백")
|
||||||
|
void default_폴백() throws Exception {
|
||||||
|
prop("default.template", "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"src\":\"default\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateOutboundErrorResponseMessage(
|
||||||
|
"OUT_GROUP", "OUT_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E888", "오류", "", null)));
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"E888\",\"src\":\"default\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("아웃바운드/인바운드가 동일한 default.template 을 공유한다")
|
||||||
|
void inbound_outbound_default_공유() throws Exception {
|
||||||
|
prop("default.template", "{\"shared\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}");
|
||||||
|
|
||||||
|
Object outbound = handler.generateOutboundErrorResponseMessage(
|
||||||
|
"OUT_GROUP", "OUT_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E001", "오류", "", null)));
|
||||||
|
Object inbound = handler.generateNonStandardErrorResponseMessage(
|
||||||
|
"IN_GROUP", "IN_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E001", "오류", "", null)));
|
||||||
|
|
||||||
|
// 두 경로 모두 같은 키를 쓰므로 결과가 동일하다.
|
||||||
|
// 방향별로 다른 기본 응답이 필요하면 그룹 전용 키를 설정해야 한다.
|
||||||
|
assertEquals("{\"shared\":\"E001\"}", outbound);
|
||||||
|
assertEquals(outbound, inbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹/default 둘 다 없으면 기존대로 null 반환")
|
||||||
|
void 둘다_없으면_null() throws Exception {
|
||||||
|
Object result = handler.generateOutboundErrorResponseMessage(
|
||||||
|
"OUT_GROUP", "OUT_ADAPTER", null, null,
|
||||||
|
toEaiMessage(buildMsg("E001", "오류", "", null)));
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// generateNonStandardInboundErrorResponseMessage : default.in.*
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("generateNonStandardInboundErrorResponseMessage – default.in.*")
|
||||||
|
class InboundError {
|
||||||
|
|
||||||
|
/** 500 으로 매핑되는 일반 예외로 호출한다. */
|
||||||
|
private Object call500(Properties callProp) throws Exception {
|
||||||
|
return handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", callProp, null, null,
|
||||||
|
new RuntimeException("처리 중 오류"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1순위: {group}.in.{code}.template 이 있으면 그것을 사용")
|
||||||
|
void 그룹_코드별_템플릿_우선() throws Exception {
|
||||||
|
prop("MY_GROUP.in.500.template", "{\"src\":\"group-code\"}");
|
||||||
|
prop("default.in.500.template", "{\"src\":\"default-code\"}");
|
||||||
|
prop("MY_GROUP.in.template", "{\"src\":\"group-common\"}");
|
||||||
|
prop("default.in.template", "{\"src\":\"default-common\"}");
|
||||||
|
|
||||||
|
assertEquals("{\"src\":\"group-code\"}", call500(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2순위: {group}.in.{code} 가 없으면 default.in.{code}.template 사용")
|
||||||
|
void default_코드별_템플릿() throws Exception {
|
||||||
|
prop("default.in.500.template", "{\"src\":\"default-code\"}");
|
||||||
|
prop("MY_GROUP.in.template", "{\"src\":\"group-common\"}");
|
||||||
|
prop("default.in.template", "{\"src\":\"default-common\"}");
|
||||||
|
|
||||||
|
// 주의: 현재 구현은 코드별 default 가 그룹 공통보다 우선한다.
|
||||||
|
assertEquals("{\"src\":\"default-code\"}", call500(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3순위: 코드별 키가 모두 없으면 {group}.in.template 사용")
|
||||||
|
void 그룹_공통_템플릿() throws Exception {
|
||||||
|
prop("MY_GROUP.in.template", "{\"src\":\"group-common\"}");
|
||||||
|
prop("default.in.template", "{\"src\":\"default-common\"}");
|
||||||
|
|
||||||
|
assertEquals("{\"src\":\"group-common\"}", call500(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("4순위: 앞의 3개가 모두 없으면 default.in.template 사용")
|
||||||
|
void default_공통_템플릿() throws Exception {
|
||||||
|
prop("default.in.template", "{\"src\":\"default-common\"}");
|
||||||
|
|
||||||
|
assertEquals("{\"src\":\"default-common\"}", call500(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("4개 키가 모두 없으면 MessageUtil 폴백")
|
||||||
|
void 전부_없으면_MessageUtil_폴백() throws Exception {
|
||||||
|
Object result = call500(null);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
Mockito.verify(mockPropManager).getProperty(G, "MY_GROUP.in.500.template");
|
||||||
|
Mockito.verify(mockPropManager).getProperty(G, "default.in.500.template");
|
||||||
|
Mockito.verify(mockPropManager).getProperty(G, "MY_GROUP.in.template");
|
||||||
|
Mockito.verify(mockPropManager).getProperty(G, "default.in.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("조회 순서: group.code → default.code → group.common → default.common")
|
||||||
|
void 조회_순서_4단계() throws Exception {
|
||||||
|
handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null, null,
|
||||||
|
new HttpStatusException("Service Unavailable", 503));
|
||||||
|
|
||||||
|
InOrder inOrder = Mockito.inOrder(mockPropManager);
|
||||||
|
inOrder.verify(mockPropManager).getProperty(G, "MY_GROUP.in.503.template");
|
||||||
|
inOrder.verify(mockPropManager).getProperty(G, "default.in.503.template");
|
||||||
|
inOrder.verify(mockPropManager).getProperty(G, "MY_GROUP.in.template");
|
||||||
|
inOrder.verify(mockPropManager).getProperty(G, "default.in.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("코드별 default 키 형식은 'default.in.{code}.template' (점 누락 회귀 방지)")
|
||||||
|
void default_코드별_키_형식_검증() throws Exception {
|
||||||
|
handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null, null,
|
||||||
|
new HttpStatusException("Bad Request", 400));
|
||||||
|
|
||||||
|
Mockito.verify(mockPropManager).getProperty(G, "default.in.400.template");
|
||||||
|
// 점이 빠진 잘못된 키로 조회하지 않아야 한다
|
||||||
|
Mockito.verify(mockPropManager, Mockito.never()).getProperty(G, "default.in400.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("JwtAuthException 은 default.in.401.template 로 폴백")
|
||||||
|
void jwt_401_default_폴백() throws Exception {
|
||||||
|
prop("default.in.401.template", "{\"code\":\"${exception.code}\",\"msg\":\"${exception.message}\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null, null,
|
||||||
|
new JwtAuthException("JWT_EXPIRED", "토큰이 만료되었습니다"));
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"JWT_EXPIRED\",\"msg\":\"토큰이 만료되었습니다\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("default.in.template 에서 exception/callProp 치환이 동일하게 동작")
|
||||||
|
void default_공통_템플릿_치환() throws Exception {
|
||||||
|
prop("default.in.template",
|
||||||
|
"{\"adapter\":\"${callprop.ADAPTER_NAME}\",\"msg\":\"${exception.message}\"}");
|
||||||
|
|
||||||
|
Object result = call500(props("ADAPTER_NAME", "REAL"));
|
||||||
|
|
||||||
|
assertEquals("{\"adapter\":\"REAL\",\"msg\":\"처리 중 오류\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("default.in.template 에 ${callprop[경로]} 를 써도 msg=null 로 NPE 없이 기본값 반환")
|
||||||
|
void default_공통_템플릿_간접참조_NPE_회귀방지() throws Exception {
|
||||||
|
// 인바운드 경로는 render(template, null, ...) 로 호출되므로
|
||||||
|
// 간접 참조의 표준전문 경로 조회 대상이 없다 → 기본값으로 처리되어야 한다.
|
||||||
|
prop("default.in.template", "{\"key\":\"${callprop[MSG.cp_key]:NO_MSG}\"}");
|
||||||
|
|
||||||
|
Object result = call500(props("ADAPTER_NAME", "REAL"));
|
||||||
|
|
||||||
|
assertEquals("{\"key\":\"NO_MSG\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("그룹 코드별 템플릿이 공백이면 default 로 폴백")
|
||||||
|
void 공백_템플릿도_폴백() throws Exception {
|
||||||
|
prop("MY_GROUP.in.500.template", " ");
|
||||||
|
prop("default.in.500.template", "{\"src\":\"default-code\"}");
|
||||||
|
|
||||||
|
assertEquals("{\"src\":\"default-code\"}", call500(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+319
@@ -0,0 +1,319 @@
|
|||||||
|
package com.eactive.eai.adapter.http.dynamic.filter;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
|
|
||||||
|
import com.eactive.eai.common.property.PropManager;
|
||||||
|
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JsonToSetStatusFilter 단위 테스트.
|
||||||
|
*
|
||||||
|
* 상태코드 필드명은 PropManager(그룹: JsonToSetStatusFilter, 키: 어댑터그룹명)에서 조회하므로
|
||||||
|
* PropManager 를 Mock 으로 등록하고 필터 로직만 검증한다.
|
||||||
|
*
|
||||||
|
* 4번 그룹은 "현재 구현의 동작을 그대로 고정(characterization)"한 테스트로,
|
||||||
|
* 개선 여부 판단용이다. 구현을 보완하면 해당 테스트도 함께 수정해야 한다.
|
||||||
|
*/
|
||||||
|
@TestMethodOrder(MethodOrderer.DisplayName.class)
|
||||||
|
class JsonToSetStatusFilterTest {
|
||||||
|
|
||||||
|
private static final String GRP = "TEST_GRP";
|
||||||
|
private static final String ADPT = "TEST_ADPT";
|
||||||
|
private static final String FIELD = "apiRsltCd";
|
||||||
|
private static final String PROP_GROUP = "JsonToSetStatusFilter";
|
||||||
|
|
||||||
|
private static GenericApplicationContext ctx;
|
||||||
|
private static PropManager mockPropManager;
|
||||||
|
private static JsonToSetStatusFilter filter;
|
||||||
|
|
||||||
|
private HttpServletRequest mockRequest;
|
||||||
|
private HttpServletResponse mockResponse;
|
||||||
|
private Properties prop;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setUpClass() {
|
||||||
|
mockPropManager = mock(PropManager.class);
|
||||||
|
|
||||||
|
ctx = new GenericApplicationContext();
|
||||||
|
ctx.getBeanFactory().registerSingleton("propManager", mockPropManager);
|
||||||
|
ctx.registerBeanDefinition("applicationContextProvider",
|
||||||
|
BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
|
||||||
|
.getBeanDefinition());
|
||||||
|
ctx.refresh();
|
||||||
|
|
||||||
|
filter = new JsonToSetStatusFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDownClass() {
|
||||||
|
if (ctx != null) ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
reset(mockPropManager);
|
||||||
|
mockRequest = mock(HttpServletRequest.class);
|
||||||
|
mockResponse = mock(HttpServletResponse.class);
|
||||||
|
prop = new Properties();
|
||||||
|
|
||||||
|
when(mockPropManager.getProperty(PROP_GROUP, GRP)).thenReturn(FIELD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String body(String rsltCd) {
|
||||||
|
return "{\"" + FIELD + "\":\"" + rsltCd + "\",\"msg\":\"OK\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 1. 상태코드 정상 반영
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-1. 문자열 body의 상태코드 필드값을 HTTP 상태코드로 설정한다")
|
||||||
|
void testPostFilter_string_setsStatus() throws Exception {
|
||||||
|
Object result = filter.doPostFilter(GRP, ADPT, body("404"), prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse).setStatus(404);
|
||||||
|
assertEquals(body("404"), result, "원 메시지를 그대로 반환해야 한다");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-2. 숫자 타입 필드값도 상태코드로 설정한다")
|
||||||
|
void testPostFilter_numericNode_setsStatus() throws Exception {
|
||||||
|
filter.doPostFilter(GRP, ADPT, "{\"" + FIELD + "\":503}", prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse).setStatus(503);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-3. 업무팀 주 사용 케이스 - 2xx 상태코드를 설정한다")
|
||||||
|
void testPostFilter_successStatusCodes() throws Exception {
|
||||||
|
for (int status : new int[] { 200, 201, 202, 204 }) {
|
||||||
|
reset(mockResponse);
|
||||||
|
|
||||||
|
filter.doPostFilter(GRP, ADPT, body(String.valueOf(status)), prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse).setStatus(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-4. 어댑터 그룹마다 다른 필드명을 사용할 수 있다")
|
||||||
|
void testPostFilter_perGroupFieldName() throws Exception {
|
||||||
|
when(mockPropManager.getProperty(PROP_GROUP, GRP)).thenReturn("rspCd");
|
||||||
|
|
||||||
|
filter.doPostFilter(GRP, ADPT, "{\"rspCd\":\"403\",\"" + FIELD + "\":\"500\"}", prop,
|
||||||
|
mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse).setStatus(403);
|
||||||
|
verify(mockResponse, never()).setStatus(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-5. JSONObject 타입 응답도 처리한다")
|
||||||
|
void testPostFilter_jsonObjectMessage_setsStatus() throws Exception {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put(FIELD, "404");
|
||||||
|
|
||||||
|
Object result = filter.doPostFilter(GRP, ADPT, json, prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse).setStatus(404);
|
||||||
|
assertSame(json, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-6. JsonNode 타입 응답도 처리한다")
|
||||||
|
void testPostFilter_jsonNodeMessage_setsStatus() throws Exception {
|
||||||
|
JsonNode node = new ObjectMapper().readTree(body("401"));
|
||||||
|
|
||||||
|
Object result = filter.doPostFilter(GRP, ADPT, node, prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse).setStatus(401);
|
||||||
|
assertSame(node, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-7. 경계값 100 / 599는 설정한다")
|
||||||
|
void testPostFilter_boundaryValues() throws Exception {
|
||||||
|
filter.doPostFilter(GRP, ADPT, body("100"), prop, mockRequest, mockResponse);
|
||||||
|
verify(mockResponse).setStatus(100);
|
||||||
|
|
||||||
|
reset(mockResponse);
|
||||||
|
filter.doPostFilter(GRP, ADPT, body("599"), prop, mockRequest, mockResponse);
|
||||||
|
verify(mockResponse).setStatus(599);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 2. 상태코드를 변경하지 않는 경우
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-1. 상태코드 필드가 없으면 setStatus를 호출하지 않는다")
|
||||||
|
void testPostFilter_fieldAbsent_noStatusChange() throws Exception {
|
||||||
|
filter.doPostFilter(GRP, ADPT, "{\"msg\":\"OK\"}", prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-2. 상태코드 필드값이 빈 문자열이면 setStatus를 호출하지 않는다")
|
||||||
|
void testPostFilter_emptyValue_noStatusChange() throws Exception {
|
||||||
|
filter.doPostFilter(GRP, ADPT, body(""), prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-3. 프로퍼티에 필드명 설정이 없으면(null) 예외 없이 통과한다")
|
||||||
|
void testPostFilter_noConfiguredField_noStatusChange() throws Exception {
|
||||||
|
when(mockPropManager.getProperty(PROP_GROUP, GRP)).thenReturn(null);
|
||||||
|
|
||||||
|
Object result = filter.doPostFilter(GRP, ADPT, body("404"), prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
assertEquals(body("404"), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-4. 상태코드 필드값이 숫자가 아니면 로그만 남기고 상태코드를 변경하지 않는다")
|
||||||
|
void testPostFilter_nonNumericValue_noStatusChange() throws Exception {
|
||||||
|
Object result = filter.doPostFilter(GRP, ADPT, body("E0001"), prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
assertEquals(body("E0001"), result, "파싱 실패해도 원 메시지는 그대로 반환되어야 한다");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-5. 업무결과코드 0000은 유효한 상태코드가 아니므로 설정하지 않는다")
|
||||||
|
void testPostFilter_businessCode0000_noStatusChange() throws Exception {
|
||||||
|
filter.doPostFilter(GRP, ADPT, body("0000"), prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-6. HTTP 상태코드 범위(100~599)를 벗어난 값은 설정하지 않는다")
|
||||||
|
void testPostFilter_outOfRangeStatus_noStatusChange() throws Exception {
|
||||||
|
for (String value : new String[] { "0", "99", "600", "9999", "-200" }) {
|
||||||
|
reset(mockResponse);
|
||||||
|
|
||||||
|
filter.doPostFilter(GRP, ADPT, body(value), prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-7. JSON 배열 응답이면 상태코드를 변경하지 않는다")
|
||||||
|
void testPostFilter_jsonArray_noStatusChange() throws Exception {
|
||||||
|
filter.doPostFilter(GRP, ADPT, "[{\"" + FIELD + "\":\"404\"}]", prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-8. 필드값이 JSON null이면 상태코드를 변경하지 않는다")
|
||||||
|
void testPostFilter_jsonNullValue_noStatusChange() throws Exception {
|
||||||
|
filter.doPostFilter(GRP, ADPT, "{\"" + FIELD + "\":null}", prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 3. 비정상 입력에서도 예외를 던지지 않는다 (거래 실패 방지)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-1. JSON 형식이 아닌 응답이어도 예외 없이 통과한다")
|
||||||
|
void testPostFilter_nonJsonBody_noException() throws Exception {
|
||||||
|
String xml = "<xml><rslt>0000</rslt></xml>";
|
||||||
|
|
||||||
|
Object result = assertDoesNotThrow(
|
||||||
|
() -> filter.doPostFilter(GRP, ADPT, xml, prop, mockRequest, mockResponse));
|
||||||
|
|
||||||
|
assertSame(xml, result);
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-2. 깨진 JSON 응답이어도 예외 없이 통과한다")
|
||||||
|
void testPostFilter_malformedJson_noException() {
|
||||||
|
String broken = "{\"" + FIELD + "\":\"404\"";
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> filter.doPostFilter(GRP, ADPT, broken, prop, mockRequest, mockResponse));
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-3. null 응답이어도 예외 없이 null을 그대로 반환한다")
|
||||||
|
void testPostFilter_nullMessage_noException() {
|
||||||
|
Object result = assertDoesNotThrow(
|
||||||
|
() -> filter.doPostFilter(GRP, ADPT, null, prop, mockRequest, mockResponse));
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-4. 빈 문자열 응답이어도 예외 없이 통과한다")
|
||||||
|
void testPostFilter_emptyBody_noException() {
|
||||||
|
assertDoesNotThrow(() -> filter.doPostFilter(GRP, ADPT, "", prop, mockRequest, mockResponse));
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-5. 프로퍼티 그룹 미등록(PropManager가 null 반환)이어도 예외 없이 통과한다")
|
||||||
|
void testPostFilter_propGroupMissing_noException() {
|
||||||
|
when(mockPropManager.getProperty(anyString(), anyString())).thenReturn(null);
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> filter.doPostFilter(GRP, ADPT, body("404"), prop, mockRequest, mockResponse));
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 4. doPreFilter / 현재 동작 고정 (개선 검토 대상)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("4-1. doPreFilter는 아무 것도 하지 않고 요청 메시지를 그대로 반환한다")
|
||||||
|
void testPreFilter_doesNothing() throws Exception {
|
||||||
|
String message = body("404");
|
||||||
|
|
||||||
|
Object result = filter.doPreFilter(GRP, ADPT, message, prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
assertSame(message, result);
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
verifyNoInteractions(mockPropManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("4-2. [확인필요] byte[] 응답은 JSON으로 파싱되지 않아 상태코드가 설정되지 않는다")
|
||||||
|
void testPostFilter_byteArray_notSupported() throws Exception {
|
||||||
|
byte[] msg = body("404").getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
Object result = filter.doPostFilter(GRP, ADPT, msg, prop, mockRequest, mockResponse);
|
||||||
|
|
||||||
|
// JsonPathUtil.toTree 가 byte[] 를 toString() 처리하므로 "[B@..." 가 되어 파싱에 실패한다.
|
||||||
|
verify(mockResponse, never()).setStatus(anyInt());
|
||||||
|
assertSame(msg, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user