diff --git a/.gitignore b/.gitignore index 75edf7c..df1df98 100644 --- a/.gitignore +++ b/.gitignore @@ -235,4 +235,3 @@ gradle-app.setting # End of https://www.gitignore.io/api/java,macos,gradle,intellij /EAISIMWeb/ /.apt_generated_tests/ -doc/ \ No newline at end of file diff --git a/WebContent/META-INF/context.xml b/WebContent/META-INF/context.xml index da65f86..2df0867 100644 --- a/WebContent/META-INF/context.xml +++ b/WebContent/META-INF/context.xml @@ -29,6 +29,6 @@ maxTotal="10" initialSize="10" maxIdle="10" maxActive="10" maxWaitMillis="10000" poolPreparedStatements="true" validationQuery="SELECT 1" validationInterval="600000" username="apigw" password="apigw" driverClassName="com.mysql.jdbc.Driver" - url="jdbc:mysql://192.168.240.177:3306/apigw"/> + url="jdbc:mysql://192.168.8.30:13306/apigw"/> \ No newline at end of file diff --git a/WebContent/WEB-INF/mq/applicationContext-activemq.xml b/WebContent/WEB-INF/mq/applicationContext-activemq.xml index 69a7f75..3cb2a3f 100644 --- a/WebContent/WEB-INF/mq/applicationContext-activemq.xml +++ b/WebContent/WEB-INF/mq/applicationContext-activemq.xml @@ -20,14 +20,14 @@ "> - + - + - + - + diff --git a/WebContent/WEB-INF/properties/env.D.properties b/WebContent/WEB-INF/properties/env.D.properties index 23aa93b..c69e742 100644 --- a/WebContent/WEB-INF/properties/env.D.properties +++ b/WebContent/WEB-INF/properties/env.D.properties @@ -10,7 +10,7 @@ hibernate.dialect=org.hibernate.dialect.MySQL5Dialect # set System property in real system #inst.Name=${HOSTNAME} inst.Name=elinkSvr11 -eai.jdbc.Name=java:comp/env/NDAPIGW_APP_NXA +eai.jdbc.Name=NDAPIGW_APP_NXA eai.rmiport=10211 eai.rmiserviceport=10212 eai.systemmode=D diff --git a/build.gradle b/build.gradle index 8833caa..e26de8f 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,14 @@ def queryDslVersion = "5.0.0" def hibernateVersion = "5.6.15.Final" def generatedJavaDir = "$buildDir/generated/java" +def nexusUrl = "https://nexus.eactive.synology.me:8090" +repositories { + maven { + url "${nexusUrl}/repository/maven-public/" + allowInsecureProtocol = true + } +} java { toolchain { diff --git a/libs/activation-1.1.1.jar b/libs/activation-1.1.1.jar new file mode 100644 index 0000000..1b703ab Binary files /dev/null and b/libs/activation-1.1.1.jar differ diff --git a/libs/javax.mail-1.6.2.jar b/libs/javax.mail-1.6.2.jar new file mode 100644 index 0000000..0cd0528 Binary files /dev/null and b/libs/javax.mail-1.6.2.jar differ diff --git a/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBase64.java b/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBase64.java new file mode 100644 index 0000000..c49db01 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBase64.java @@ -0,0 +1,897 @@ +package com.eactive.eai.custom.adapter.http.client.impl; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.StopWatch; +import org.apache.commons.codec.binary.Base64; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.*; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.http.protocol.BasicHttpContext; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.net.URIBuilder; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import com.eactive.eai.adapter.http.HttpMemoryLogger; +import com.eactive.eai.adapter.http.HttpMethodType; +import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport; +import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; +import com.eactive.eai.adapter.http.client.HttpClientAdapterVO; +import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; +import com.eactive.eai.common.TransactionContextKeys; +import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB; +import com.eactive.eai.common.exception.ExceptionUtil; +import com.eactive.eai.common.message.MessageType; +import com.eactive.eai.common.util.CommonLib; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.openbanking.eai.common.token.AccessTokenManager; +import com.openbanking.eai.common.token.AccessTokenVO; +import com.openbanking.eai.common.token.OAuth2AccessTokenVO; + +import com.eactive.eai.common.property.PropManager; + +public class HttpClient5AdapterServiceBase64 extends HttpClient5AdapterServiceSupport + implements HttpClientAdapterServiceKey { + + public static final String TYPE_VARIABLE_URL_REQUEST = "variableUrlRequest"; + public static final String TYPE_SIMPLE_REQUEST = "simpleRequest"; + public static final String REST_OPTION = "REST_OPTION"; + //public static final String HEADER_CONTENT_TYPE = "Content-Type"; + public static final String HEADER_GROUP = "HEADER_GROUP"; + public static final String HTTP_STATUS = "HTTP_STATUS"; + public static final String HEADER_KEYS = "HEADER_KEYS"; + public static final String AUTH_TOKEN = "AUTH_TOKEN"; + + /** + * 1. 기능 : REST API 통신에 사용
+ * 2. 처리 개요 : - 속성 정보를 설정 하고 수동 시스템 서비스를 호출 한다.
+ * 3. 주의사항
+ * + * @param prop Http Adapter 속성 정보 + * @return 반환 된 Object + * @exception Exception 수동 시스템 간 통신 중 발생 + **/ + @SuppressWarnings({ "unchecked" }) + public Object execute(Properties prop, Object data, Properties tempProp) throws Exception { + HttpClientAdapterVO vo = super.setting(prop, tempProp); + + // 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정 + String bizCode = ""; + String authToken = ""; + try { + Properties authProp = PropManager.getInstance().getProperties(AUTH_TOKEN); //Authorization + bizCode = vo.getAdapterGroupName().substring(1, 4); + authToken = authProp.getProperty(bizCode, ""); + } catch (Exception e) { + logger.debug("Property Group AUTH_TOKEN is not Exist !!"); + } + + logger.debug("authToken :::::::::::::::::::::::::::::::::::" + authToken); + + String headerGroupName = prop.getProperty(HEADER_GROUP); + String relayResponseHeaderKeys = prop.getProperty(HEADER_KEYS, ""); + boolean useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y"); + + boolean useForwardProxy = StringUtils.equalsIgnoreCase(prop.getProperty("FORWARD_PROXY_USE_YN", "N"), "Y"); + String forwardProxyUrl = prop.getProperty("FORWARD_PROXY_URL"); + + // ex) $.dataHeader.GW_RSLT_CD + String tokenErrorCodeKey = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_KEY"); + String tokenErrorCodeValues = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_VALUES"); + String tokenErrorHttpStatusCode = prop.getProperty("ADAPTER_TOKEN_ERROR_HTTP_STATUS_CODE"); // 200 or 400번대 + + // 레이아웃 메시지 타입 REST URL 추출 시 활용. Default JSON + String messageType = prop.getProperty("MESSAGE_TYPE", MessageType.JSON); + String inboundMethod = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_METHOD, "POST"); + + /** + * @formatter:off + * TSEAIHE02.RESTOPTION 정보 => JSON 형태로 구성 + * - type : simpleRequest, variableUrlRequest + * - extraPath : 어댑터 프로퍼티 URL에 추가될 HTTP REST URL + * - method : get, delete, post, put + * - contentType: application/json(default), application/x-www-form-urlencoded + * - adapterTokenUseYn: Y, N(default) + * ex) + * {"type":"simpleRequest","extraPath":"v2.0/accout/balance","method":"post"} + * @formatter:on + */ + + String restOptionData = tempProp.getProperty(REST_OPTION); + + JSONObject restOptionObject = parseJson(restOptionData); + if (restOptionObject == null) { + throw new Exception("OptionData parsing result is NULL"); + } + + String rmethod = (String) restOptionObject.getOrDefault("method", inboundMethod); + String contentType = (String) restOptionObject.getOrDefault("contentType", "application/json"); + String adapterTokenUseYn = (String) restOptionObject.get("adapterTokenUseYn"); + if (StringUtils.isNotBlank(adapterTokenUseYn)) { // interface 에 설정된게 우선한다. + useAdapterToken = StringUtils.equalsIgnoreCase(adapterTokenUseYn, "Y"); + } + + HttpClient mclient = this.client; + String sendData = ""; + if (data instanceof String) { + sendData = (String) data; + } else if (data instanceof byte[]) { + sendData = new String((byte[]) data, vo.getEncode()); + } + + ////mclient.getParams().setContentCharset(vo.getEncode()); + + JSONObject dataObject = null; + + Object httpHeader = null; + Object dataContent = null; + + if (MessageType.JSON.equals(messageType)) { + + Object parsed = parseJsonGeneric(sendData); + + if(parsed instanceof JSONObject) { + dataObject = parseJson(sendData); + if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) { + httpHeader = dataObject.get(headerGroupName); + dataObject.remove(headerGroupName); + } + + dataContent = dataObject; + if (dataObject.containsKey("innerList")) { + JSONArray innerList = (JSONArray) dataObject.get("innerList"); + dataContent = innerList; + } + } + } + + logger.debug("HttpClientAdapterServiceRest] dataObject1 = [" + dataObject + "]"); + logger.debug("HttpClientAdapterServiceRest] dataContent = [" + dataContent + "]"); + + // interface 에 설정된게 우선한다. + String uri = null; + if (dataObject == null) { + uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData); + } else { + uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject); + } + +// ////if (vo.getConnectionTimeout() != 0) { +// mclient.getHttpConnectionManager().getParams().setConnectionTimeout(vo.getConnectionTimeout()); +// } + + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") URL=[" + vo.getUrl() + "] "); + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") PARAMETER_NAME=[" + vo.getParameterName() + "] "); + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") ENCODE=[" + vo.getEncode() + "] "); + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") RESPONSE_TYPE=[" + vo.getResponseType() + "] "); + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") URL_ENCODE_YN=[" + vo.getUrlEncodeYn() + "] "); + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") CONNECTION_TIMEOUT=[" + vo.getConnectionTimeout() + "] "); + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") REST_OPTION=[" + restOptionData + "] "); + } + + HttpUriRequestBase method = null; + + // 메소드 타입에 따라 HttpRequestBase 인스턴스 생성 + switch (HttpMethodType.getValue(rmethod)) { + case GET: + method = new HttpGet(uri); + break; + case DELETE: + method = new HttpDelete(uri); + break; + case POST: + method = new HttpPost(uri); + break; + case PUT: + method = new HttpPut(uri); + break; + default: + method = new HttpGet(uri); + break; + } + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + + if(useForwardProxy) { + java.net.URL url = new java.net.URL(forwardProxyUrl); + + // 프로토콜, 호스트, 포트 추출 + String protocol = url.getProtocol(); + String host = url.getHost(); + int port = url.getPort(); + HttpHost proxy = new HttpHost(protocol,host, port); + requestConfigBuilder.setProxy(proxy); + } + + configureHttpClient(requestConfigBuilder, vo, method); + + switch (HttpMethodType.getValue(rmethod)) { + case GET: + case DELETE: + HashMap h = getParameters(dataObject); + URIBuilder uriBuilder = new URIBuilder(uri); + for (Map.Entry entry : h.entrySet()) { + uriBuilder.addParameter(entry.getKey(), entry.getValue()); + } + URI fullUri = uriBuilder.build(); + if (method instanceof HttpGet) { + ((HttpGet) method).setUri(fullUri); + } else if (method instanceof HttpDelete) { + ((HttpDelete) method).setUri(fullUri); + } + if (logger.isDebug()) { + logger.debug("HttpClientAdapterServiceRest] (get Method) QueryString = [" + fullUri.getQuery() + "]"); + } + break; + case PUT: + case POST: + //assignPostBody(dataObject, contentType, vo.getEncode(), method); + assignPostBody(dataContent, contentType, vo.getEncode(), method); + break; + default: + break; + } + + if (logger.isDebugEnabled()) { + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") method.getParams()=[" + + method.getEntity() + "] "); + } + + OAuth2AccessTokenVO accessToken = null; + if (useAdapterToken) { + AccessTokenManagerByDB tokenManager = AccessTokenManagerByDB.getInstance(); + accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName()); + + // 토큰이 없거나 만료 됐으면 재발급 + if (accessToken == null || accessToken.isExpired()) { + String oldToken = accessToken == null ? null : accessToken.getAccessToken(); + accessToken = (OAuth2AccessTokenVO) tokenManager.retryAccessTokenVO(vo.getAdapterGroupName(), prop, + oldToken); + } + + if (logger.isDebug()) { + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") TOKEN = [" + + accessToken + "]"); + } + + setAuthHeaders(method, accessToken); + + if (logger.isDebug()) { + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + + ") RequestHeader [Authorization=" + method.getHeader("Authorization") + "]"); + } + } + + // 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정 + if(!StringUtils.isBlank(authToken)) { + setAuthHeaders(method, authToken); + } + + // 전달 header 셋팅 + // Adapter 레벨 header 보다 data로 넘어온 httpHeader를 우선 적용(header명이 같다면 덮어씀) + assignRequestHeaders(method, httpHeader); + + int status = -1; + + try { + byte[] responseMessage = null; + Header[] responseHeaders; + + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + if (dataContent != null) { //dataObject + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = [" + + dataContent.toString() + "]"); //dataObject.toJSONString() + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = " + + CommonLib.getDumpMessage(dataContent.toString())); //dataObject.toJSONString() + } else { + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = [" + sendData + + "]"); + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = " + + CommonLib.getDumpMessage(sendData)); + } + } + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(), + "SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData)); + } + + String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID); + HttpContext context = new BasicHttpContext(); + context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid); + context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME, vo.getAdapterGroupName()); + context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME, vo.getAdapterName()); + Integer logProcessNo = (Integer) tempProp.get(HttpClientAdapterServiceKey.LOG_PROCESS_NO); + if (logProcessNo != null && logProcessNo > 0) { + context.setAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO, logProcessNo); + } + + try { + if (logger.isDebug()) { + logger.debug("[method getName]" + method.getMethod()); + logger.debug("[method getEntity]" + method.getEntity()); + logger.debug("[method getRequestHeaders]" + method.getHeaders().toString()); + logger.debug("[method getURI]" + method.getPath()); + } + try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method, context)) { + status = response.getCode(); + + if (status >= 200 && status <= 207) { + status = 200; + } + +// if (status == 404) { +// status = 200; +// } + +// responseMessage = EntityUtils.toByteArray(response.getEntity()); + responseHeaders = response.getHeaders(); + // 2025/08/06 Response body를 Base64로 Encode 해서 Json으로 만들어서 전달하도록 수정 + HttpEntity entity = response.getEntity(); + if (entity != null) { + byte[] entityBytes = EntityUtils.toByteArray(response.getEntity()); + String b64 = Base64.encodeBase64String(entityBytes); + JSONObject resultJson = new JSONObject(); + resultJson.put("base64_file_data", b64); + responseMessage = resultJson.toString().getBytes(); + } else { + responseMessage = EntityUtils.toByteArray(response.getEntity()); + } + + + if (logger.isDebug()) { + logger.debug("[received status]" + status); + logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = [" + responseMessage + "]"); + } + } + } catch (ConnectException e) { + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceRest] responseType =" + vo.getResponseType()); + } + throw new Exception("excuteMethod java.net.ConnectException = " + e.getMessage()); + } + + stopWatch.stop(); + + /** + * @formatter:off + * OAuth 토큰 응답 체크(Adapter properties로 설정) + * 응답코드에 따라 유효한 토큰 확인(토큰 재발급 여부 확인) + * API 서비스 마다 정책이 다름(보통 400대에서 체크하나 200에서도 체크할 수 있음) + * + * TOKEN_ERROR_CODE_KEY: 응답 json error code key + * __ex) $.dataHeader.resultCode + * TOKEN_ERROR_CODE_VALUES: 토큰 재발급이 필요한 응답 error codes(ex: O0001,O0002,O0003) + * TOKEN_ERROR_HTTP_STATUS_CODE: 보통 400대에서 체크하나 API 제공자에 따라 200에서도 체클할수 있음 + * ex) TOKEN_ERROR_HTTP_STATUS_CODE = 200 + * @formatter:on + */ + boolean needReissue = false; + + Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders); + String responseString = new String(responseMessage, vo.getEncode()); + + Map map = new HashMap(); + tempProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map); + map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, responseHeaderProp); + map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE, responseString); + map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_STATUS, status); + + if (status >= 200 && status <= 207) { + if (useAdapterToken && StringUtils.equals(tokenErrorHttpStatusCode, "200")) { + needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues, + vo.getEncode()); + } + } else if (status == 302) { + if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) { + relayResponseHeaderKeys += ",Location"; + } + } else { + String errMsg = String.format( + "http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s", + status, vo.getAdapterGroupName(), vo.getAdapterName(), uri, rmethod, responseString); +// logger.error(errMsg); + + if (status >= 400 && status < 500) { + if (useAdapterToken) { + needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues, + vo.getEncode()); + } + +// if (!needReissue) { +// throw new Exception(errMsg); +// } + } else { + + throw new Exception(errMsg); + } + } + + // ---------------------------------------------------------- + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceRest] responseType =" + vo.getResponseType()); + logger.debug("HttpClientAdapterServiceRest] RECV " + vo.getEncode() + "(" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + "]"); +// logger.debug("HttpClientAdapterServiceRest] RECV MS949 (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, "MS949") + "]"); +// logger.debug("HttpClientAdapterServiceRest] RECV euc-kr (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, "euc-kr") + "]"); + logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = " + CommonLib.getDumpMessage(responseMessage)); + } + + if (useAdapterToken) { + if (responseMessage == null) { + throw new Exception("responseMessage is NULL, HttpStatus=" + status); + } + + // OAuth 토큰 재요청 코드 확인 + if (needReissue) { + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceRest] retry access token response code = [" + + vo.getResponseType() + "] message = [" + new String(responseMessage, vo.getEncode()) + + "]"); + } + + // 토큰 재발급 + AccessTokenManager tokenManager = AccessTokenManager.getInstance(); + String oldToken = accessToken == null ? null : accessToken.getAccessToken(); + OAuth2AccessTokenVO newaccessToken = (OAuth2AccessTokenVO) tokenManager + .retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken); + method.setHeader("Authorization", newaccessToken.getAuthorization()); + + setAuthHeaders(method, newaccessToken); + + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + + ") RETRY TOKEN = [" + newaccessToken + "]"); + } + + try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) { + status = response.getCode(); + responseMessage = EntityUtils.toByteArray(response.getEntity()); + + if (logger.isDebug()) { + logger.debug("[received status]" + status); + logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + "]"); + } + }catch (IOException e){ + throw new Exception("retry excuteMethod Exceptioin = " + e.getMessage()); + } + + + if (status == 302) { + if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) { + relayResponseHeaderKeys += ",Location"; + } + } else { + String errMsg = String.format( + "http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s", + status, vo.getAdapterGroupName(), vo.getAdapterName(), uri, rmethod, responseMessage); + throw new Exception(errMsg); + } + + // ---------------------------------------------------------- + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceRest] RETRY responseType =" + vo.getResponseType()); + logger.debug("HttpClientAdapterServiceRest] RETRY RECV " + vo.getEncode() + "(" + + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + + "]"); + logger.debug("HttpClientAdapterServiceRest] RETRY RECV (" + vo.getAdapterGroupName() + ") = " + + CommonLib.getDumpMessage(responseMessage)); + } + // ---------------------------------------------------------- + } + } + + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(), + "RECV [" + new String(responseMessage, vo.getEncode()) + "]" + + CommonLib.getDumpMessage(responseMessage)); + } + + + return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), messageType, + relayResponseHeaderKeys, status, responseHeaderProp); + } catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우 + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), + "HttpClientAdapterServiceRest] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : " + + ste.toString(), + ste); + } + logger.error("HttpClientAdapterServiceRest] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : " + + ste.toString(), ste); + throw ste; + } catch (ConnectException ce) { + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), + "HttpClientAdapterServiceRest] Connection Exception (" + vo.getAdapterGroupName() + ") : " + + ce.toString(), + ce); + } + logger.error("HttpClientAdapterServiceRest] Connection Exception (" + vo.getAdapterGroupName() + ") : " + + ce.toString(), ce); + throw ce; + } catch (Exception e) { + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), e.toString(), e); + } + logger.error("HttpClientAdapterServiceRest] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(), + e); + throw e; + } finally { + if (status != HttpStatus.SC_OK) { + String[] msgArgs = new String[1]; + msgArgs[0] = String.valueOf(status); + String resMsg = ExceptionUtil.make("RDCEAIAHA013", msgArgs); + if (logger.isDebug()) { + logger.debug(resMsg); + } + } + } + } + + private Properties assignRelayDataToInbound(Properties prop, Header[] responseHeaders) { + Properties headerProp = new Properties(); + if (responseHeaders == null || responseHeaders.length == 0) { + return headerProp; + } + + for (Header header : responseHeaders) { + headerProp.put(header.getName(), header.getValue()); + } + + return headerProp; + } + + /** + * 현재 1단계 레이아웃에만 적용되도록 구현됨. + * + * @param method + * @param responseMessage + * @param headerGroupName + * @param encode + * @param messageType + * @param relayHeaderKeys + * @return + * @throws Exception + */ + @SuppressWarnings("unchecked") + private String assignResponseHeaders(HttpUriRequestBase method, byte[] responseMessage, String headerGroupName, + String encode, String messageType, String relayHeaderKeys, int status, Properties responseHeaderProp) throws Exception { + if (!MessageType.JSON.equals(messageType) || StringUtils.isBlank(headerGroupName) + || StringUtils.isBlank(relayHeaderKeys)) { + return new String(responseMessage, encode); + } + + String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ","); + JSONObject headerJson = new JSONObject(); + for (String key : relayKeyArr) { + String value = responseHeaderProp.getProperty(key); + if (StringUtils.isNotBlank(value)) { + headerJson.put(key, value); + } + } + + headerJson.put(HTTP_STATUS, String.valueOf(status)); + + if (headerJson.size() <= 0) { + return new String(responseMessage, encode); + } + + JSONObject message = null; + if (responseMessage == null || responseMessage.length == 0) { + message = new JSONObject(); + } else { + message = parseJson(new String(responseMessage, encode)); + if (message == null) { + message = new JSONObject(); + message.put("Malformed_Response_Message", new String(responseMessage, encode)); + } + } + + if (StringUtils.isNotBlank(headerGroupName) && message != null) + message.put(headerGroupName, headerJson); + + logger.info("------------- message --------------- : {} ", message); + + return message.toJSONString(); + } + + // 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정 + private void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception { + method.setHeader("Authorization", + String.format("%s", authorization)); + } + + private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception { + method.setHeader("Authorization", + String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken())); + } + + private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues, + String encode) { + if (responseMessage == null) { + return false; + } + + if (StringUtils.isBlank(tokenErrorCodeKey)) { + return false; + } + + if (StringUtils.isBlank(tokenErrorCodeValues)) { + return false; + } + + try { + DocumentContext jsonContext = JsonPath.parse(new String(responseMessage, encode)); + String responseCode = jsonContext.read(tokenErrorCodeKey); + if (StringUtils.isBlank(responseCode)) { + return false; + } + + String[] arr = org.springframework.util.StringUtils.tokenizeToStringArray(tokenErrorCodeValues, ","); + return ArrayUtils.contains(arr, responseCode); + } catch (Exception e) { + logger.error("HttpClientAdapterServiceRest] checkTokenRetry error=" + e.getMessage()); + } + + return false; + } + + private void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader) { + if (httpHeader == null) { + return; + } + + if (httpHeader instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) httpHeader; + for (Object key : jsonObject.keySet()) { + Object obj = jsonObject.get(key); + if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) { + continue; + } + + method.setHeader((String) key, (String) obj); + + if (logger.isDebugEnabled()) { + logger.debug("Request Header :" + (String) key + "=[" + (String) obj + "]"); + } + + } + } + } + + @SuppressWarnings("deprecation") + private void assignPostBody(Object eaiBody, String contentType, String charset, HttpUriRequestBase method) { + if (eaiBody == null) { + return; + } + + if (StringUtils.contains(contentType, "application/x-www-form-urlencoded")) { + if (eaiBody instanceof JSONObject) { + List params = new ArrayList<>(); + JSONObject jsonObject = (JSONObject) eaiBody; + for (Object key : jsonObject.keySet()) { + params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key))); + } + + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, StandardCharsets.UTF_8); + method.setEntity(entity); + } + + } else { + ContentType contentTypeObj = ContentType.create(contentType, charset); + + // 요청 본문을 StringEntity 객체로 생성 + StringEntity entity = new StringEntity(eaiBody.toString(), contentTypeObj); + + // 요청에 본문 추가 + method.setEntity(entity); + } + } + + private HashMap getParameters(Object message) throws Exception { + + HashMap result = new HashMap(); + + if (message != null) { + if (message instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) message; + for (Object key : jsonObject.keySet()) { + Object obj = jsonObject.get(key); + if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) { + continue; + } + result.put((String) key, getStringValue(obj)); + } + } else { + String[] messages = ((String) message).split("&"); + String[] data = null; + + for (int i = 0; i < messages.length; i++) { + data = messages[i].split("=", 2); + if (data.length == 2) { + result.put(data[0], data[1]); + } + } + } + } + + return result; + } + + public String getStringValue(Object obj) { + if (obj == null) { + return null; + } + + if (obj instanceof String) { + return (String) obj; + } else if (obj instanceof Long) { + return Long.toString((Long) obj); + } else if (obj instanceof BigDecimal) { + return ((BigDecimal) obj).toPlainString(); + } else { + return (String) obj; + } + } + + public static Object parseJsonGeneric(String jsonData) { + if (StringUtils.isBlank(jsonData)) { + return null; + } + + try { + JSONParser parser = new JSONParser(); + Object parsedObj = parser.parse(jsonData); + return parsedObj; + } catch (ParseException e) { + e.printStackTrace(); + return null; + } + } + + @SuppressWarnings("deprecation") + private JSONObject parseJson(String message) throws Exception { + if (message == null) { + return null; + } + + return (JSONObject) JSONValue.parse(message); + } + + private Document convertXmlDocument(String message) throws Exception { + SAXReader builder = new SAXReader(); + Document document = builder.read(new StringReader(message)); + return document; + } + + @SuppressWarnings("deprecation") + private String changeUrl(String messageType, String url, String restOption, Object sendData) { + if ((MessageType.JSON.equals(messageType) || MessageType.XML.equals(messageType))) { + try { + if (StringUtils.isBlank(restOption)) { + return url; + } + List urlVariableList = new ArrayList(); + JSONObject restOptionObject = parseJson(restOption); + + if (restOptionObject == null) + throw new Exception("restOption is NULL"); + + String type = (String) restOptionObject.get("type"); + String requestExtraPath = (String) restOptionObject.get("extraPath"); + url = getUrl(url, requestExtraPath); + if (TYPE_VARIABLE_URL_REQUEST.equals(type)) { + Pattern p = Pattern.compile("\\{(.*?)\\}"); + Matcher m = p.matcher(requestExtraPath); + List uriVariables = new ArrayList<>(); + while(m.find()) { + String fieldName = m.group(1); + uriVariables.add(fieldName); + } + if (MessageType.JSON.equals(messageType)) { + JSONObject jsonObject; + if (sendData instanceof JSONObject) { + jsonObject = (JSONObject) sendData; + } else { + jsonObject = (JSONObject) JSONValue.parse((String) sendData); + } + for (Object tempObject : uriVariables) { + String urlVaribleId = (String) tempObject; + String uriVariable = (String) jsonObject.get(urlVaribleId); + urlVariableList.add(uriVariable); + jsonObject.remove(urlVaribleId); + } + } else if (MessageType.XML.equals(messageType)) { + Document doc; + if (sendData instanceof Document) { + doc = (Document) sendData; + } else { + doc = convertXmlDocument((String) sendData); + } + for (Object tempObject : uriVariables) { + String urlVaribleId = (String) tempObject; + Element element = (Element) doc.selectSingleNode("//" + urlVaribleId); + urlVariableList.add(element.getText()); + if (doc.getRootElement() == element) { + doc = null; + } else { + element.getParent().remove(element); + } + } + } + if (urlVariableList.size() > 0) { + UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build(); + Object[] urls = urlVariableList.toArray(); + url = uriComponents.expand(urls).toUriString(); + logger.debug("HttpClientAdapterServiceRest] after extand url=[" + url + "] "); + } + } + } catch (Exception e) { + logger.error("Failed to change url", e); + } + } + return url; + } + + private String getUrl(String baseUrl, String extraPath) { + if (StringUtils.isBlank(extraPath)) { + return baseUrl; + } + + if (StringUtils.contains(extraPath, "http://") || StringUtils.contains(extraPath, "https://")) { + return extraPath; + } + + String targetUrl = baseUrl; + if (!targetUrl.endsWith("/")) { + targetUrl += "/"; + } + if (extraPath.startsWith("/")) { + targetUrl += extraPath.substring(1); + } else { + targetUrl += extraPath; + } + + logger.debug("HttpClientAdapterServiceRest] after concatenate url=[" + targetUrl + "] "); + return targetUrl; + } + +} diff --git a/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceMultipart.java b/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceMultipart.java new file mode 100644 index 0000000..6452102 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceMultipart.java @@ -0,0 +1,1074 @@ +package com.eactive.eai.custom.adapter.http.client.impl; + +import java.io.*; +import java.math.BigDecimal; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.StopWatch; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.entity.mime.ByteArrayBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.*; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.http.protocol.BasicHttpContext; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.net.URIBuilder; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.mail.BodyPart; +import javax.mail.internet.MimeMultipart; +import javax.mail.util.ByteArrayDataSource; + +import com.eactive.eai.adapter.http.HttpMemoryLogger; +import com.eactive.eai.adapter.http.HttpMethodType; +import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport; +import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; +import com.eactive.eai.adapter.http.client.HttpClientAdapterVO; +import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; +import com.eactive.eai.common.TransactionContextKeys; +import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB; +import com.eactive.eai.common.exception.ExceptionUtil; +import com.eactive.eai.common.message.MessageType; +import com.eactive.eai.common.util.CommonLib; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.openbanking.eai.common.token.AccessTokenManager; +import com.openbanking.eai.common.token.AccessTokenVO; +import com.openbanking.eai.common.token.OAuth2AccessTokenVO; + +import com.eactive.eai.common.property.PropManager; + +public class HttpClient5AdapterServiceMultipart extends HttpClient5AdapterServiceSupport + implements HttpClientAdapterServiceKey { + + public static final String TYPE_VARIABLE_URL_REQUEST = "variableUrlRequest"; + public static final String TYPE_SIMPLE_REQUEST = "simpleRequest"; + public static final String REST_OPTION = "REST_OPTION"; + //public static final String HEADER_CONTENT_TYPE = "Content-Type"; + public static final String HEADER_GROUP = "HEADER_GROUP"; + public static final String HTTP_STATUS = "HTTP_STATUS"; + public static final String HEADER_KEYS = "HEADER_KEYS"; + public static final String AUTH_TOKEN = "AUTH_TOKEN"; + + /** + * 1. 기능 : REST API 통신에 사용
+ * 2. 처리 개요 : - 속성 정보를 설정 하고 수동 시스템 서비스를 호출 한다.
+ * 3. 주의사항
+ * + * @param prop Http Adapter 속성 정보 + * @return 반환 된 Object + * @exception Exception 수동 시스템 간 통신 중 발생 + **/ + @SuppressWarnings({ "unchecked" }) + public Object execute(Properties prop, Object data, Properties tempProp) throws Exception { + HttpClientAdapterVO vo = super.setting(prop, tempProp); + + // 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정 + String bizCode = ""; + String authToken = ""; + try { + Properties authProp = PropManager.getInstance().getProperties(AUTH_TOKEN); //Authorization + bizCode = vo.getAdapterGroupName().substring(1, 4); + authToken = authProp.getProperty(bizCode, ""); + } catch (Exception e) { + logger.debug("Property Group AUTH_TOKEN is not Exist !!"); + } + + logger.debug("authToken :::::::::::::::::::::::::::::::::::" + authToken); + + String headerGroupName = prop.getProperty(HEADER_GROUP); + String relayResponseHeaderKeys = prop.getProperty(HEADER_KEYS, ""); + boolean useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y"); + + boolean useForwardProxy = StringUtils.equalsIgnoreCase(prop.getProperty("FORWARD_PROXY_USE_YN", "N"), "Y"); + String forwardProxyUrl = prop.getProperty("FORWARD_PROXY_URL"); + + // ex) $.dataHeader.GW_RSLT_CD + String tokenErrorCodeKey = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_KEY"); + String tokenErrorCodeValues = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_VALUES"); + String tokenErrorHttpStatusCode = prop.getProperty("ADAPTER_TOKEN_ERROR_HTTP_STATUS_CODE"); // 200 or 400번대 + + // 레이아웃 메시지 타입 REST URL 추출 시 활용. Default JSON + String messageType = prop.getProperty("MESSAGE_TYPE", MessageType.JSON); + String inboundMethod = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_METHOD, "POST"); + + // Multipart 파일 저장 + String saveFilePath = prop.getProperty("SAVE_FILE_PATH",""); + + /** + * @formatter:off + * TSEAIHE02.RESTOPTION 정보 => JSON 형태로 구성 + * - type : simpleRequest, variableUrlRequest + * - extraPath : 어댑터 프로퍼티 URL에 추가될 HTTP REST URL + * - method : get, delete, post, put + * - contentType: application/json(default), application/x-www-form-urlencoded + * - adapterTokenUseYn: Y, N(default) + * ex) + * {"type":"simpleRequest","extraPath":"v2.0/accout/balance","method":"post"} + * @formatter:on + */ + + String restOptionData = tempProp.getProperty(REST_OPTION); + + JSONObject restOptionObject = parseJson(restOptionData); + if (restOptionObject == null) { + throw new Exception("OptionData parsing result is NULL"); + } + + String rmethod = (String) restOptionObject.getOrDefault("method", inboundMethod); + String contentType = (String) restOptionObject.getOrDefault("contentType", "application/json"); + String adapterTokenUseYn = (String) restOptionObject.get("adapterTokenUseYn"); + if (StringUtils.isNotBlank(adapterTokenUseYn)) { // interface 에 설정된게 우선한다. + useAdapterToken = StringUtils.equalsIgnoreCase(adapterTokenUseYn, "Y"); + } + + HttpClient mclient = this.client; + String sendData = ""; + if (data instanceof String) { + sendData = (String) data; + } else if (data instanceof byte[]) { + sendData = new String((byte[]) data, vo.getEncode()); + } + + ////mclient.getParams().setContentCharset(vo.getEncode()); + + JSONObject dataObject = null; + + Object httpHeader = null; + Object dataContent = null; +// MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + // Json에서 Header 변환 부분 추출 & Root 없는 Json Array 처리 + if (MessageType.JSON.equals(messageType)) { + Object parsed = parseJsonGeneric(sendData); + if(parsed instanceof JSONObject) { + dataObject = parseJson(sendData); + if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) { + httpHeader = dataObject.get(headerGroupName); + dataObject.remove(headerGroupName); + } + + dataContent = dataObject; + if (dataObject.containsKey("innerList")) { + JSONArray innerList = (JSONArray) dataObject.get("innerList"); + dataContent = innerList; + } + } + } + + logger.debug("HttpClientAdapterServiceMultipart] dataObject1 = [" + dataObject + "]"); + logger.debug("HttpClientAdapterServiceMultipart] dataContent = [" + dataContent + "]"); + + // interface 에 설정된게 우선한다. + String uri = null; + if (dataObject == null) { + uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData); + } else { + uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject); + } + +// ////if (vo.getConnectionTimeout() != 0) { +// mclient.getHttpConnectionManager().getParams().setConnectionTimeout(vo.getConnectionTimeout()); +// } + + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") URL=[" + vo.getUrl() + "] "); + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") PARAMETER_NAME=[" + vo.getParameterName() + "] "); + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") ENCODE=[" + vo.getEncode() + "] "); + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") RESPONSE_TYPE=[" + vo.getResponseType() + "] "); + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") URL_ENCODE_YN=[" + vo.getUrlEncodeYn() + "] "); + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") CONNECTION_TIMEOUT=[" + vo.getConnectionTimeout() + "] "); + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") REST_OPTION=[" + restOptionData + "] "); + } + + HttpUriRequestBase method = null; + + // 메소드 타입에 따라 HttpRequestBase 인스턴스 생성 + switch (HttpMethodType.getValue(rmethod)) { + case GET: + method = new HttpGet(uri); + break; + case DELETE: + method = new HttpDelete(uri); + break; + case POST: + method = new HttpPost(uri); + break; + case PUT: + method = new HttpPut(uri); + break; + default: + method = new HttpGet(uri); + break; + } + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + + if(useForwardProxy) { + java.net.URL url = new java.net.URL(forwardProxyUrl); + + // 프로토콜, 호스트, 포트 추출 + String protocol = url.getProtocol(); + String host = url.getHost(); + int port = url.getPort(); + HttpHost proxy = new HttpHost(protocol,host, port); + requestConfigBuilder.setProxy(proxy); + } + + configureHttpClient(requestConfigBuilder, vo, method); + + switch (HttpMethodType.getValue(rmethod)) { + case GET: + case DELETE: + HashMap h = getParameters(dataObject); + URIBuilder uriBuilder = new URIBuilder(uri); + for (Map.Entry entry : h.entrySet()) { + uriBuilder.addParameter(entry.getKey(), entry.getValue()); + } + URI fullUri = uriBuilder.build(); + if (method instanceof HttpGet) { + ((HttpGet) method).setUri(fullUri); + } else if (method instanceof HttpDelete) { + ((HttpDelete) method).setUri(fullUri); + } + if (logger.isDebug()) { + logger.debug("HttpClientAdapterServiceMultipart] (get Method) QueryString = [" + fullUri.getQuery() + "]"); + } + break; + case PUT: + case POST: + //assignPostBody(dataObject, contentType, vo.getEncode(), method); + assignPostBody(dataContent, contentType, vo.getEncode(), method); + break; + default: + break; + } + + if (logger.isDebugEnabled()) { + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") method.getParams()=[" + + method.getEntity() + "] "); + } + + OAuth2AccessTokenVO accessToken = null; + if (useAdapterToken) { + AccessTokenManagerByDB tokenManager = AccessTokenManagerByDB.getInstance(); + accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName()); + + // 토큰이 없거나 만료 됐으면 재발급 + if (accessToken == null || accessToken.isExpired()) { + String oldToken = accessToken == null ? null : accessToken.getAccessToken(); + accessToken = (OAuth2AccessTokenVO) tokenManager.retryAccessTokenVO(vo.getAdapterGroupName(), prop, + oldToken); + } + + if (logger.isDebug()) { + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") TOKEN = [" + + accessToken + "]"); + } + + setAuthHeaders(method, accessToken); + + if (logger.isDebug()) { + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + + ") RequestHeader [Authorization=" + method.getHeader("Authorization") + "]"); + } + } + + // 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정 + if(!StringUtils.isBlank(authToken)) { + setAuthHeaders(method, authToken); + } + + // 전달 header 셋팅 + // Adapter 레벨 header 보다 data로 넘어온 httpHeader를 우선 적용(header명이 같다면 덮어씀) + assignRequestHeaders(method, httpHeader); + + int status = -1; + + try { + byte[] responseMessage = null; + Header[] responseHeaders; + String responseContentType = null; + + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + if (dataContent != null) { //dataObject + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") = [" + + dataContent.toString() + "]"); //dataObject.toJSONString() + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") = " + + CommonLib.getDumpMessage(dataContent.toString())); //dataObject.toJSONString() + } else { + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") = [" + sendData + + "]"); + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + ") = " + + CommonLib.getDumpMessage(sendData)); + } + } + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(), + "SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData)); + } + + String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID); + HttpContext context = new BasicHttpContext(); + context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid); + context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME, vo.getAdapterGroupName()); + context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME, vo.getAdapterName()); + Integer logProcessNo = (Integer) tempProp.get(HttpClientAdapterServiceKey.LOG_PROCESS_NO); + if (logProcessNo != null && logProcessNo > 0) { + context.setAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO, logProcessNo); + } + + try { + if (logger.isDebug()) { + logger.debug("[method getName]" + method.getMethod()); + logger.debug("[method getEntity]" + method.getEntity()); + logger.debug("[method getRequestHeaders]" + method.getHeaders().toString()); + logger.debug("[method getURI]" + method.getPath()); + } + try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method, context)) { + status = response.getCode(); + + if (status >= 200 && status <= 207) { + status = 200; + } + +// if (status == 404) { +// status = 200; +// } + + // multipart 응답 받았을 때 처리 + responseContentType = response.getEntity().getContentType(); + HttpEntity entity = response.getEntity(); + if (StringUtils.contains(responseContentType, "multipart/form-data") && entity != null) { + responseMessage = processMultipartResponse(response, contentType, saveFilePath); + } + else { + responseMessage = EntityUtils.toByteArray(response.getEntity()); + } + responseHeaders = response.getHeaders(); + + if (logger.isDebug()) { + logger.debug("[received status]" + status); + logger.debug("HttpClientAdapterServiceMultipart] RECV (" + vo.getAdapterGroupName() + ") = [" + responseMessage + "]"); + } + } + } catch (ConnectException e) { + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceMultipart] responseType =" + vo.getResponseType()); + } + throw new Exception("excuteMethod java.net.ConnectException = " + e.getMessage()); + } + + stopWatch.stop(); + + /** + * @formatter:off + * OAuth 토큰 응답 체크(Adapter properties로 설정) + * 응답코드에 따라 유효한 토큰 확인(토큰 재발급 여부 확인) + * API 서비스 마다 정책이 다름(보통 400대에서 체크하나 200에서도 체크할 수 있음) + * + * TOKEN_ERROR_CODE_KEY: 응답 json error code key + * __ex) $.dataHeader.resultCode + * TOKEN_ERROR_CODE_VALUES: 토큰 재발급이 필요한 응답 error codes(ex: O0001,O0002,O0003) + * TOKEN_ERROR_HTTP_STATUS_CODE: 보통 400대에서 체크하나 API 제공자에 따라 200에서도 체클할수 있음 + * ex) TOKEN_ERROR_HTTP_STATUS_CODE = 200 + * @formatter:on + */ + boolean needReissue = false; + + Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders); + String responseString = new String(responseMessage, vo.getEncode()); + + Map map = new HashMap(); + tempProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map); + map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, responseHeaderProp); + map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE, responseString); + map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_STATUS, status); + + if (status >= 200 && status <= 207) { + if (useAdapterToken && StringUtils.equals(tokenErrorHttpStatusCode, "200")) { + needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues, + vo.getEncode()); + } + } else if (status == 302) { + if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) { + relayResponseHeaderKeys += ",Location"; + } + } else { + String errMsg = String.format( + "http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s", + status, vo.getAdapterGroupName(), vo.getAdapterName(), uri, rmethod, responseString); +// logger.error(errMsg); + + if (status >= 400 && status < 500) { + if (useAdapterToken) { + needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues, + vo.getEncode()); + } + +// if (!needReissue) { +// throw new Exception(errMsg); +// } + } else { + + throw new Exception(errMsg); + } + } + + // ---------------------------------------------------------- + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceMultipart] responseType =" + vo.getResponseType()); + logger.debug("HttpClientAdapterServiceMultipart] RECV " + vo.getEncode() + "(" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + "]"); +// logger.debug("HttpClientAdapterServiceMultipart] RECV MS949 (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, "MS949") + "]"); +// logger.debug("HttpClientAdapterServiceMultipart] RECV euc-kr (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, "euc-kr") + "]"); + logger.debug("HttpClientAdapterServiceMultipart] RECV (" + vo.getAdapterGroupName() + ") = " + CommonLib.getDumpMessage(responseMessage)); + } + + if (useAdapterToken) { + if (responseMessage == null) { + throw new Exception("responseMessage is NULL, HttpStatus=" + status); + } + + // OAuth 토큰 재요청 코드 확인 + if (needReissue) { + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceMultipart] retry access token response code = [" + + vo.getResponseType() + "] message = [" + new String(responseMessage, vo.getEncode()) + + "]"); + } + + // 토큰 재발급 + AccessTokenManager tokenManager = AccessTokenManager.getInstance(); + String oldToken = accessToken == null ? null : accessToken.getAccessToken(); + OAuth2AccessTokenVO newaccessToken = (OAuth2AccessTokenVO) tokenManager + .retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken); + method.setHeader("Authorization", newaccessToken.getAuthorization()); + + setAuthHeaders(method, newaccessToken); + + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceMultipart] SEND (" + vo.getAdapterGroupName() + + ") RETRY TOKEN = [" + newaccessToken + "]"); + } + + try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) { + status = response.getCode(); + responseMessage = EntityUtils.toByteArray(response.getEntity()); + + if (logger.isDebug()) { + logger.debug("[received status]" + status); + logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + "]"); + } + }catch (IOException e){ + throw new Exception("retry excuteMethod Exceptioin = " + e.getMessage()); + } + + + if (status == 302) { + if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) { + relayResponseHeaderKeys += ",Location"; + } + } else { + String errMsg = String.format( + "http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s", + status, vo.getAdapterGroupName(), vo.getAdapterName(), uri, rmethod, responseMessage); + throw new Exception(errMsg); + } + + // ---------------------------------------------------------- + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClientAdapterServiceMultipart] RETRY responseType =" + vo.getResponseType()); + logger.debug("HttpClientAdapterServiceMultipart] RETRY RECV " + vo.getEncode() + "(" + + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + + "]"); + logger.debug("HttpClientAdapterServiceMultipart] RETRY RECV (" + vo.getAdapterGroupName() + ") = " + + CommonLib.getDumpMessage(responseMessage)); + } + // ---------------------------------------------------------- + } + } + + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(), + "RECV [" + new String(responseMessage, vo.getEncode()) + "]" + + CommonLib.getDumpMessage(responseMessage)); + } + + return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), messageType, + relayResponseHeaderKeys, status, responseHeaderProp); + } catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우 + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), + "HttpClientAdapterServiceMultipart] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : " + + ste.toString(), + ste); + } + logger.error("HttpClientAdapterServiceMultipart] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : " + + ste.toString(), ste); + throw ste; + } catch (ConnectException ce) { + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), + "HttpClientAdapterServiceMultipart] Connection Exception (" + vo.getAdapterGroupName() + ") : " + + ce.toString(), + ce); + } + logger.error("HttpClientAdapterServiceMultipart] Connection Exception (" + vo.getAdapterGroupName() + ") : " + + ce.toString(), ce); + throw ce; + } catch (Exception e) { + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), e.toString(), e); + } + logger.error("HttpClientAdapterServiceMultipart] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(), + e); + throw e; + } finally { + if (status != HttpStatus.SC_OK) { + String[] msgArgs = new String[1]; + msgArgs[0] = String.valueOf(status); + String resMsg = ExceptionUtil.make("RDCEAIAHA013", msgArgs); + if (logger.isDebug()) { + logger.debug(resMsg); + } + } + } + } + + private Properties assignRelayDataToInbound(Properties prop, Header[] responseHeaders) { + Properties headerProp = new Properties(); + if (responseHeaders == null || responseHeaders.length == 0) { + return headerProp; + } + + for (Header header : responseHeaders) { + headerProp.put(header.getName(), header.getValue()); + } + + return headerProp; + } + + /** + * 현재 1단계 레이아웃에만 적용되도록 구현됨. + * + * @param method + * @param responseMessage + * @param headerGroupName + * @param encode + * @param messageType + * @param relayHeaderKeys + * @return + * @throws Exception + */ + @SuppressWarnings("unchecked") + private String assignResponseHeaders(HttpUriRequestBase method, byte[] responseMessage, String headerGroupName, + String encode, String messageType, String relayHeaderKeys, int status, Properties responseHeaderProp) throws Exception { + if (!MessageType.JSON.equals(messageType) || StringUtils.isBlank(headerGroupName) + || StringUtils.isBlank(relayHeaderKeys)) { + return new String(responseMessage, encode); + } + + String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ","); + JSONObject headerJson = new JSONObject(); + for (String key : relayKeyArr) { + String value = responseHeaderProp.getProperty(key); + if (StringUtils.isNotBlank(value)) { + headerJson.put(key, value); + } + } + + headerJson.put(HTTP_STATUS, String.valueOf(status)); + + if (headerJson.size() <= 0) { + return new String(responseMessage, encode); + } + + JSONObject message = null; + if (responseMessage == null || responseMessage.length == 0) { + message = new JSONObject(); + } else { + message = parseJson(new String(responseMessage, encode)); + if (message == null) { + message = new JSONObject(); + message.put("Malformed_Response_Message", new String(responseMessage, encode)); + } + } + + if (StringUtils.isNotBlank(headerGroupName) && message != null) + message.put(headerGroupName, headerJson); + + logger.info("------------- message --------------- : {} ", message); + + return message.toJSONString(); + } + + // 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정 + private void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception { + method.setHeader("Authorization", + String.format("%s", authorization)); + } + + private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception { + method.setHeader("Authorization", + String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken())); + } + + private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues, + String encode) { + if (responseMessage == null) { + return false; + } + + if (StringUtils.isBlank(tokenErrorCodeKey)) { + return false; + } + + if (StringUtils.isBlank(tokenErrorCodeValues)) { + return false; + } + + try { + DocumentContext jsonContext = JsonPath.parse(new String(responseMessage, encode)); + String responseCode = jsonContext.read(tokenErrorCodeKey); + if (StringUtils.isBlank(responseCode)) { + return false; + } + + String[] arr = org.springframework.util.StringUtils.tokenizeToStringArray(tokenErrorCodeValues, ","); + return ArrayUtils.contains(arr, responseCode); + } catch (Exception e) { + logger.error("HttpClientAdapterServiceMultipart] checkTokenRetry error=" + e.getMessage()); + } + + return false; + } + + private void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader) { + if (httpHeader == null) { + return; + } + + if (httpHeader instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) httpHeader; + for (Object key : jsonObject.keySet()) { + Object obj = jsonObject.get(key); + if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) { + continue; + } + + method.setHeader((String) key, (String) obj); + + if (logger.isDebugEnabled()) { + logger.debug("Request Header :" + (String) key + "=[" + (String) obj + "]"); + } + + } + } + } + + @SuppressWarnings("deprecation") + private void assignPostBody(Object eaiBody, String contentType, String charset, HttpUriRequestBase method) throws Exception{ + if (eaiBody == null) { + return; + } + + if (StringUtils.contains(contentType, "application/x-www-form-urlencoded")) { + if (eaiBody instanceof JSONObject) { + List params = new ArrayList<>(); + JSONObject jsonObject = (JSONObject) eaiBody; + for (Object key : jsonObject.keySet()) { + params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key))); + } + + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, StandardCharsets.UTF_8); + method.setEntity(entity); + } + } else { + // multipart 처리 추가 : json => multipart 변환 + if (eaiBody instanceof JSONObject) { + JSONObject jasonObject = (JSONObject)eaiBody; + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + contentType = "multipart/form-data"; + for (Object keyObj : jasonObject.keySet()) { + String key = (String) keyObj; + Object value = jasonObject.get(key); + + if ("multipart_file".equals(key) && value instanceof JSONArray) { + JSONArray fileArray = (JSONArray) value; + for (Object fileItem : fileArray) { + JSONObject fileObj = (JSONObject) fileItem; + String paramName = (String) fileObj.get("target_param_name"); + ByteArrayBody fileBody = null; + + if (fileObj.containsKey("file_path")) { + // 파일 경로로부터 직접 읽기 + String filePath = (String) fileObj.get("file_path"); + if (logger.isDebug()) { + logger.debug("HttpClientAdapterServiceMultipart] (multipart_file) filePath = [" + filePath + "]"); + } + File file = new File(filePath); + byte[] fileBytes = readFileToByteArray(file); + fileBody = new ByteArrayBody(fileBytes, ContentType.APPLICATION_OCTET_STREAM, file.getName()); + } else if (fileObj.containsKey("file_name") && fileObj.containsKey("base64_file_data")) { + // base64 방식 + String fileName = (String) fileObj.get("file_name"); + String base64Data = (String) fileObj.get("base64_file_data"); + if (logger.isDebug()) { + logger.debug("HttpClientAdapterServiceMultipart] (multipart_file) file_name = [" + fileName + "]"); + } + byte[] fileBytes = Base64.decodeBase64(base64Data); + fileBody = new ByteArrayBody(fileBytes, ContentType.APPLICATION_OCTET_STREAM, fileName); + } + + if (fileBody != null) { + builder.addPart(paramName, fileBody); + } + } + } else { + builder.addTextBody(key, value.toString()); + } + } + HttpEntity multipart = builder.build(); + method.setEntity(multipart); + } else { + // Json Body 처리 + ContentType contentTypeObj = ContentType.create(contentType, charset); + + // 요청 본문을 StringEntity 객체로 생성 + StringEntity entity = new StringEntity(eaiBody.toString(), contentTypeObj); + + // 요청에 본문 추가 + method.setEntity(entity); + } + } + } + + private static byte[] readFileToByteArray(File file) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + byte[] bytes = new byte[(int) file.length()]; + int read = fis.read(bytes); + if (read != file.length()) { + throw new IOException("HttpClientAdapterServiceMultipart] (multipart_file) 파일을 완전히 읽지 못했습니다."); + } + return bytes; + } + } + + private HashMap getParameters(Object message) throws Exception { + + HashMap result = new HashMap(); + + if (message != null) { + if (message instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) message; + for (Object key : jsonObject.keySet()) { + Object obj = jsonObject.get(key); + if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) { + continue; + } + result.put((String) key, getStringValue(obj)); + } + } else { + String[] messages = ((String) message).split("&"); + String[] data = null; + + for (int i = 0; i < messages.length; i++) { + data = messages[i].split("=", 2); + if (data.length == 2) { + result.put(data[0], data[1]); + } + } + } + } + + return result; + } + + public String getStringValue(Object obj) { + if (obj == null) { + return null; + } + + if (obj instanceof String) { + return (String) obj; + } else if (obj instanceof Long) { + return Long.toString((Long) obj); + } else if (obj instanceof BigDecimal) { + return ((BigDecimal) obj).toPlainString(); + } else { + return (String) obj; + } + } + + public static Object parseJsonGeneric(String jsonData) { + if (StringUtils.isBlank(jsonData)) { + return null; + } + + try { + JSONParser parser = new JSONParser(); + Object parsedObj = parser.parse(jsonData); + return parsedObj; + } catch (ParseException e) { + e.printStackTrace(); + return null; + } + } + + @SuppressWarnings("deprecation") + private JSONObject parseJson(String message) throws Exception { + if (message == null) { + return null; + } + + return (JSONObject) JSONValue.parse(message); + } + + private Document convertXmlDocument(String message) throws Exception { + SAXReader builder = new SAXReader(); + Document document = builder.read(new StringReader(message)); + return document; + } + + @SuppressWarnings("deprecation") + private String changeUrl(String messageType, String url, String restOption, Object sendData) { + if ((MessageType.JSON.equals(messageType) || MessageType.XML.equals(messageType))) { + try { + if (StringUtils.isBlank(restOption)) { + return url; + } + List urlVariableList = new ArrayList(); + JSONObject restOptionObject = parseJson(restOption); + + if (restOptionObject == null) + throw new Exception("restOption is NULL"); + + String type = (String) restOptionObject.get("type"); + String requestExtraPath = (String) restOptionObject.get("extraPath"); + url = getUrl(url, requestExtraPath); + if (TYPE_VARIABLE_URL_REQUEST.equals(type)) { + Pattern p = Pattern.compile("\\{(.*?)\\}"); + Matcher m = p.matcher(requestExtraPath); + List uriVariables = new ArrayList<>(); + while(m.find()) { + String fieldName = m.group(1); + uriVariables.add(fieldName); + } + if (MessageType.JSON.equals(messageType)) { + JSONObject jsonObject; + if (sendData instanceof JSONObject) { + jsonObject = (JSONObject) sendData; + } else { + jsonObject = (JSONObject) JSONValue.parse((String) sendData); + } + for (Object tempObject : uriVariables) { + String urlVaribleId = (String) tempObject; + String uriVariable = (String) jsonObject.get(urlVaribleId); + urlVariableList.add(uriVariable); + jsonObject.remove(urlVaribleId); + } + } else if (MessageType.XML.equals(messageType)) { + Document doc; + if (sendData instanceof Document) { + doc = (Document) sendData; + } else { + doc = convertXmlDocument((String) sendData); + } + for (Object tempObject : uriVariables) { + String urlVaribleId = (String) tempObject; + Element element = (Element) doc.selectSingleNode("//" + urlVaribleId); + urlVariableList.add(element.getText()); + if (doc.getRootElement() == element) { + doc = null; + } else { + element.getParent().remove(element); + } + } + } + if (urlVariableList.size() > 0) { + UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build(); + Object[] urls = urlVariableList.toArray(); + url = uriComponents.expand(urls).toUriString(); + logger.debug("HttpClientAdapterServiceMultipart] after extand url=[" + url + "] "); + } + } + } catch (Exception e) { + logger.error("Failed to change url", e); + } + } + return url; + } + + private String getUrl(String baseUrl, String extraPath) { + if (StringUtils.isBlank(extraPath)) { + return baseUrl; + } + + if (StringUtils.contains(extraPath, "http://") || StringUtils.contains(extraPath, "https://")) { + return extraPath; + } + + String targetUrl = baseUrl; + if (!targetUrl.endsWith("/")) { + targetUrl += "/"; + } + if (extraPath.startsWith("/")) { + targetUrl += extraPath.substring(1); + } else { + targetUrl += extraPath; + } + + logger.debug("HttpClientAdapterServiceMultipart] after concatenate url=[" + targetUrl + "] "); + return targetUrl; + } + + private byte[] processMultipartResponse(CloseableHttpResponse response, String contentType, String saveFilePath) throws Exception { + JSONObject resultJson = new JSONObject(); + JSONArray multipartFiles = new JSONArray(); + HttpEntity entity = response.getEntity(); + + if (entity != null) { + byte[] entityBytes = EntityUtils.toByteArray(entity); + ByteArrayDataSource ds = new ByteArrayDataSource(new ByteArrayInputStream(entityBytes), contentType); + MimeMultipart multipart = new MimeMultipart(ds); + + for (int i = 0; i < multipart.getCount(); i++) { + BodyPart part = multipart.getBodyPart(i); + String name = part.getDisposition() == null ? "field" + i : part.getFileName(); + + if (part.getFileName() == null) { + // 일반 텍스트 필드 + InputStream is = part.getInputStream(); + StringWriter writer = new StringWriter(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) + writer.write(line); + } + resultJson.put(name, writer.toString().trim()); + } else { + // 파일 필드 + JSONObject fileObj = handleFilePart(part, name, saveFilePath); + multipartFiles.add(fileObj); + } + } + + resultJson.put("multipart_file", multipartFiles); + } + + return resultJson.toString().getBytes(); + } + + private JSONObject handleFilePart(BodyPart part, String paramName, String saveFilePath) throws Exception { + JSONObject fileObj = new JSONObject(); + fileObj.put("target_param_name", paramName); + + boolean saved = false; + File savedFile = null; + + if (StringUtils.isNotBlank(saveFilePath)) { + File dir = new File(saveFilePath); + if (!dir.exists()) dir.mkdirs(); + + savedFile = getUniqueFile(dir, part.getFileName()); + try (FileOutputStream fos = new FileOutputStream(savedFile); + InputStream is = part.getInputStream()) { + + byte[] buffer = new byte[4096]; + int len; + while ((len = is.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + saved = true; + fileObj.put("target_file_path", savedFile.getAbsolutePath()); + fileObj.put("target_file_name", savedFile.getName()); + } catch (IOException e) { + logger.error("HttpClientAdapterServiceMultipart] 파일 저장 실패: " + e.getMessage()); + } + } + + if (!saved) { + try (InputStream is = part.getInputStream(); + ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[4096]; + int len; + while ((len = is.read(buffer)) != -1) { + bos.write(buffer, 0, len); + } + + fileObj.put("base64_file_data", Base64.encodeBase64String(bos.toByteArray())); + } + } + + return fileObj; + } + + private static String extractParamName(String contentDisposition) { + // 예: form-data; name="add_file1"; filename="file.txt" + if (contentDisposition == null) return "unknown"; + for (String part : contentDisposition.split(";")) { + String trimmed = part.trim(); + if (trimmed.startsWith("name=")) { + return trimmed.substring(6, trimmed.length() - 1); // "name" 값만 추출 + } + } + return "unknown"; + } + + private static File getUniqueFile(File dir, String fileName) { + File file = new File(dir, fileName); + if (!file.exists()) return file; + + String name = fileName; + String ext = ""; + int dotIndex = fileName.lastIndexOf('.'); + if (dotIndex >= 0) { + name = fileName.substring(0, dotIndex); + ext = fileName.substring(dotIndex); + } + + int count = 1; + while (file.exists()) { + file = new File(dir, name + "(" + count + ")" + ext); + count++; + } + + return file; + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..153d5aa --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + +