
package com.eactive.eai.adapter.http.client;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.SSLContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.HttpResponseInterceptor;
import org.apache.hc.core5.ssl.SSLContextBuilder;

import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.Keys;
import com.eactive.eai.adapter.http.secure.HttpClient5SSLContextFactory;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.logger.HttpAdapterExtraHeaderVo;
import com.eactive.eai.common.message.EAIMessageKeys;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.HttpAdapterExtraLogUtil;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.httpouttlsinfo.HttpOutTlsInfoVO;
import com.eactive.eai.util.TestModeChecker;

public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdapterService, HttpClientAdapterServiceKey {
	protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
	protected HttpClient client;
	PoolingHttpClientConnectionManager connectionManager;
	
	private static boolean testMode = TestModeChecker.isTestMode();
	
	private static boolean httpHeaderLogMode = HttpAdapterExtraLogUtil.isHttpHeaderMode();
	
	public synchronized void close() {
		if(connectionManager != null) {
			connectionManager.close();
		}
	}
	
	protected HttpClientAdapterVO setting(Properties prop, Properties tempProp) {
		String url = null;
		String parameterName = null;
		String method = null;
		String contentType = null;
		String encode = null;
		String testCallYn = null;
		String adapterGroupName = null;
		String adapterName = null;
		String responseType = null;
		String urlEncodeYn = null;
		String adapterHeader = null;
		
		boolean useMtls = false;
		String keyStorePath = null;
		String keyStorePassword = null;
		String trustStorePath = null;
		String trustStorePassword = null;
		
		int connectionTimeout = 0;
		int timeout = 10000; // 기존값 -1 인데, 의미가 없음, -1로 설정될경우 client에서 에러남.
		int traceLevel = 0;
		long slowTranTime = 2000L;
		
		url = prop.getProperty(URL);

		String url2grp = tempProp.getProperty(URL);
		if (!StringUtils.isEmpty(url2grp)) {
			url = url2grp;
		}

		boolean isDrMode = Boolean.valueOf(PropManager.getInstance().getProperty(DR_PROP_GROUP, DR_PROP_KEY, "false"));

		if (isDrMode) {
			url = prop.getProperty(DR_URL);
		}

		method = prop.getProperty(METHOD);
		parameterName = prop.getProperty(PARAMETER_NAME, PARAMETER_MESSAGE);
		contentType = prop.getProperty(CONTENT_TYPE, "");
		responseType = prop.getProperty(RESPONSE_TYPE, "SYNC");
		urlEncodeYn = prop.getProperty(URL_ENCODE_YN, "N");
		String connectionTimeoutTemp = prop.getProperty(CONNECTION_TIMEOUT, "0");

		String httpVersion = prop.getProperty(HTTP_VERSION);
		adapterHeader = prop.getProperty(ADAPTER_HEADER, "");

		String traceLevelTemp = prop.getProperty(TRACE_LEVEL, "0");
		String timeoutTemp = "10000"; // 타임아웃 기준이 불명확해 운영 혼란 발생 → 인터페이스 TIMEOUT으로 단일화

		useMtls = StringUtils.equalsIgnoreCase(prop.getProperty(HttpClientAdapterServiceKey.USE_MTLS),"Y");
		keyStorePath = prop.getProperty(HttpClientAdapterServiceKey.KEYSTORE_PATH);
		keyStorePassword = prop.getProperty(HttpClientAdapterServiceKey.KEYSTORE_PWD);
		trustStorePath = prop.getProperty(HttpClientAdapterServiceKey.TRUSTSTORE_PATH);
		trustStorePassword = prop.getProperty(HttpClientAdapterServiceKey.TRUSTSTORE_PWD);
		
		encode = tempProp.getProperty(Keys.CHARSET_KEY_OUT, Charset.defaultCharset().name());
		testCallYn = tempProp.getProperty(TESTCALL_YN, "N");
		adapterGroupName = tempProp.getProperty(ADAPTER_GROUP_NAME);
		adapterName = tempProp.getProperty(ADAPTER_NAME);
		// 인터페이스 정보에 설정된 타임아웃
		String interfaceTimeout = tempProp.getProperty("INTERFACE_TIME_OUT");
		if (StringUtils.isNotBlank(interfaceTimeout) && NumberUtils.toInt(interfaceTimeout) > 0) {
			timeoutTemp = interfaceTimeout;
		}
		
		url = StringUtils.trim(url);
		parameterName = StringUtils.trim(parameterName);
		method = StringUtils.trim(method);
		encode = StringUtils.trim(encode);
		contentType = StringUtils.trim(contentType);
		
		try {
			connectionTimeout = Integer.parseInt(connectionTimeoutTemp);
		} catch (Exception e) {
			logger.debug("HttpClientAdapterSupport] connectionTimeoutTemp not int");
		}
		
		try {
			timeout = Integer.parseInt(timeoutTemp);
		} catch (NumberFormatException e) {
			logger.warn("HttpClientAdapterSupport] timeoutTemp not int", e);
		}

		try {
			traceLevel = Integer.parseInt(traceLevelTemp);
		} catch (Exception e) {
			logger.warn("HttpClientAdapterSupport] traceLevelTemp not int", e);
		}

		// client 중 BODY,POST,GET,NexCore 방식만 적용
		HashMap<String, String> adapterHeaders = new HashMap<>();
		try {
			Matcher matcher = Pattern
					.compile("\\{[ ]*\"[ ]*([a-zA-Z0-9_-]+)[ ]*\"[ ]*,[ ]*\"[ ]*([a-zA-Z0-9/;,\\*=_ -]+)[ ]*\"[ ]*\\}[ ]*")
					.matcher(adapterHeader.trim());
			while (matcher.find()) {
				String key = matcher.group(1).trim();
				String value = matcher.group(2).trim();
				adapterHeaders.put(key, value);
			}
		} catch (Exception e) {
			if(logger.isDebug()) logger.debug("HttpClientAdapterSupport] adapter header parsing error " + adapterHeader);
		}
		// GW 전달 로직 추가
		// tempProp로 넘겨서 하는 이유는 outboundProp가 clone 안하는 로직이라 outboundProp로 넘길시 문제의 소지가있음
		String transactionId = tempProp.getProperty(HttpClientAdapterServiceKey.TRANSACTION_ID);
		String instanceId = tempProp.getProperty(HttpClientAdapterServiceKey.INSTANCE_ID);
		if (StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(instanceId)) {
			adapterHeaders.put(HttpClientAdapterServiceKey.TRANSACTION_ID, transactionId);
			adapterHeaders.put(HttpClientAdapterServiceKey.INSTANCE_ID, instanceId);
		}
	      
		HttpClientAdapterVO vo = new HttpClientAdapterVO();
		vo.setUrl(url);
		vo.setMethod(method);
		vo.setParameterName(parameterName);
		vo.setEncode(encode);
		vo.setContentType(contentType);
		vo.setTestCallYn(testCallYn);
		vo.setResponseType(responseType);
		vo.setUrlEncodeYn(urlEncodeYn);
		vo.setAdapterGroupName(adapterGroupName);
		vo.setAdapterName(adapterName);
		
		vo.setMaxTotalConnections(getMaxTotalConnections());
		vo.setMaxHostConnections(getMaxConnectionsPerHost());
		vo.setConnectionTimeout(connectionTimeout);
		
		vo.setTimeout(timeout);
		vo.setTraceLevel(traceLevel);
		vo.setSlowTranTime(slowTranTime);
		vo.setAdapterHeaders(adapterHeaders);
		if (StringUtils.equalsAny(httpVersion, HTTP_VERSION_FULL_1_1, HTTP_VERSION_1_1)) {
			vo.setHttpVersion(HTTP_VERSION_1_1);
		} 
		else if (StringUtils.equals(httpVersion, HTTP_VERSION_2_0)) {
			vo.setHttpVersion(HTTP_VERSION_2_0);
		}
		else {
			vo.setHttpVersion(HTTP_VERSION_1_0);
		}
		vo.setUseMtls(useMtls);
		vo.setKeyStorePath(keyStorePath);
		vo.setKeyStorePassword(keyStorePassword);
		vo.setTrustStorePath(trustStorePath);
		vo.setTrustStorePassword(trustStorePassword);
		if (logger.isDebug()) {			
			logger.debug(vo.toString());
		}
		return vo;
	}

	protected HttpClient5AdapterServiceSupport() {
	}

	public synchronized void init(String adapterName, 
			boolean useMtls, HttpOutTlsInfoVO mtlsInfo,
			int maxTotalConnections, int maxHostConnections) {

		if(connectionManager != null) {
			if( (connectionManager.getMaxTotal() == maxTotalConnections)
				&& (connectionManager.getDefaultMaxPerRoute() == maxHostConnections) ) {
				if(logger.isInfo()) {
					logger.info("ConnectionManager REUSE :  AdapterName=" + adapterName
					+ " MaxTotalConnection (" + connectionManager.getMaxTotal()
					+ ") MaxHostConnections (" + connectionManager.getDefaultMaxPerRoute()
					+ ")"
					);
				}
				return;
			}
			else {
				if(logger.isWarn()) {
					logger.warn("ConnectionManager RELOAD :  AdapterName=" + adapterName
					+ " MaxTotalConnection (" + connectionManager.getMaxTotal()  + " -> " + maxTotalConnections
					+ ") MaxHostConnections (" + connectionManager.getDefaultMaxPerRoute() + " -> " + maxHostConnections
					+ ")"
					);
				}
				connectionManager.close();
				connectionManager = null;
				this.client = null;
			}
		}
		if(logger.isWarn()) {
			logger.warn("ConnectionManager INIT :  AdapterName=" + adapterName
			+ " MaxTotalConnection (" + maxTotalConnections
			+ ") MaxHostConnections (" + maxHostConnections
			+ ")"
			);
		}
		
		PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create();
		
		
		
		try {
			if(logger.isWarn()) {
				logger.warn("mTLS Config :  AdapterName=" + adapterName
				+ " useMtls (" + useMtls + ") mtlsInfo - " + mtlsInfo);
			}
			
			SSLContext sslContext = null;
			if (useMtls && mtlsInfo != null) {
				String storeType = mtlsInfo.getStoreType();
				String keyStoreInfo = mtlsInfo.getKeystoreInfo();
				String keyStorePassword = mtlsInfo.getKeystorePassword();
				String trustStoreInfo = mtlsInfo.getTruststoreInfo();
				String trustStorePassword = mtlsInfo.getTruststorePassword();
				
				String[] tlsVersions = null; 
//				{
//					"TLSv1.0"
//					, "TLSv1.1"
//					, "TLSv1.2"
//					, "TLSv1.3"
//				};
			String[] cipherSuites = null; 
//				{
//					"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 
//					"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 
//					"TLS_RSA_WITH_AES_256_CBC_SHA256", 
//					"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 
//					"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 
//					"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 
//					"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 
//					"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 
//					"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 
//					"TLS_RSA_WITH_AES_256_CBC_SHA", 
//					"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 
//					"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 
//					"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", 
//					"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", 
//					"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 
//					"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 
//					"TLS_RSA_WITH_AES_128_CBC_SHA256", 
//					"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 
//					"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 
//					"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 
//					"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 
//					"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 
//					"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 
//					"TLS_RSA_WITH_AES_128_CBC_SHA", 
//					"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 
//					"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 
//					"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", 
//					"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", 
//					"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 
//					"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 
//					"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 
//					"TLS_RSA_WITH_AES_256_GCM_SHA384", 
//					"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", 
//					"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", 
//					"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", 
//					"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", 
//					"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 
//					"TLS_RSA_WITH_AES_128_GCM_SHA256", 
//					"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", 
//					"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", 
//					"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", 
//					"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", 
//					"TLS_EMPTY_RENEGOTIATION_INFO_SCSV"
//			};
			
		        if( StringUtils.isAnyEmpty(keyStoreInfo, keyStorePassword) ) {
					throw new Exception("mTLS keyStore config error");
				}
				
		        boolean skipTrust = false;
		        // kbank에서는 trustStore를 사용하지 않음
		        if( StringUtils.isAnyEmpty(trustStoreInfo, trustStorePassword) ) {
//					throw new Exception("mTLS trustStore config error");
		        	if(logger.isWarn()) logger.warn("Skip trustStore validation adapterName : " + adapterName);
		        	skipTrust = true;
				}
				
		        sslContext = HttpClient5SSLContextFactory.createMTLSContextFromContent(
						storeType,
						keyStoreInfo, keyStorePassword, 
						trustStoreInfo, trustStorePassword, skipTrust
						,tlsVersions, cipherSuites);

		        // SSLConnectionSocketFactory 생성
//		        Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: Certificate for <localhost> doesn't match common name of the certificate subject: elink
//		    	at org.apache.hc.client5.http.ssl.DefaultHostnameVerifier.matchCN(DefaultHostnameVerifier.java:180)
//		    	at org.apache.hc.client5.http.ssl.DefaultHostnameVerifier.verify(DefaultHostnameVerifier.java:118)
//		    	at org.apache.hc.client5.http.ssl.TlsSessionValidator.verifySession(TlsSessionValidator.java:112)
//		    	=> NoopHostnameVerifier.INSTANCE
				
		        SSLConnectionSocketFactory sslSocketFactory = null;
		        if(testMode) {
		        	// Hostname verifier 비활성화 (테스트용)
					sslSocketFactory = new SSLConnectionSocketFactory(
					    sslContext
					    , NoopHostnameVerifier.INSTANCE 
					);
		        }
		        else {
		        	sslSocketFactory = new SSLConnectionSocketFactory(
						    sslContext						  
						);
		        }
		        cmBuilder.setSSLSocketFactory(sslSocketFactory);
			}
			else {
				sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustAllStrategy()).build();
				SSLConnectionSocketFactory sslSocketFactory = null;
		        if(testMode) {
		        	// Hostname verifier 비활성화 (테스트용)
					sslSocketFactory = new SSLConnectionSocketFactory(
					    sslContext
					    , NoopHostnameVerifier.INSTANCE 
					);
		        }
		        else {
		        	sslSocketFactory = new SSLConnectionSocketFactory(
						    sslContext						  
						);
		        }
		        cmBuilder.setSSLSocketFactory(sslSocketFactory);
			}
			
		} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException 
				| CertificateException | IOException | UnrecoverableKeyException e) {
			throw new RuntimeException(e);
		} 
		catch (Exception e) {
			throw new RuntimeException(e);
		} 
		this.connectionManager = cmBuilder.build();
		
		connectionManager.setMaxTotal(maxTotalConnections);
		connectionManager.setDefaultMaxPerRoute(maxHostConnections);
		
		
		HttpRequestInterceptor requestLogInterceptor = (request, entity, context) -> {
			if (!httpHeaderLogMode)  {
	    		logger.info("httpHeader logging off - use.http.header.log");
	    		return;
			}
		    	
            try {
				String uuid = (String) context.getAttribute(TransactionContextKeys.TRANSACTION_UUID);
				String contextAdapterGroupName = (String) context.getAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
				String contextAdapterName = (String) context.getAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME);
                String url = request.getUri().toString();

				Integer logProcessNo = (Integer) context.getAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO);
				if (logProcessNo == null || logProcessNo <= 0) {
					logProcessNo = 200;
				}

				Header[] headers = request.getHeaders();
				List<HttpAdapterExtraHeaderVo> headerVoList = null;
				if ( headers != null && headers.length != 0 ) {
		    		headerVoList = HttpAdapterExtraLogUtil.convertHeaderToListOfHttpAdapterExtraHeaderVo(headers);
		    	} else {
		    		headerVoList = new ArrayList<HttpAdapterExtraHeaderVo>(); 
		    	}
				
				String trimedPostBody = (String) context.getAttribute(HttpAdapterExtraLogUtil.BODY_FIELD_NAME);
				if(StringUtils.isNotEmpty(trimedPostBody)) {
					HttpAdapterExtraHeaderVo vo = new HttpAdapterExtraHeaderVo();
					vo.setName(HttpAdapterExtraLogUtil.BODY_FIELD_NAME);
					vo.setValue(trimedPostBody);
					headerVoList.add(vo);
				}
				
				HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, logProcessNo,
                		contextAdapterGroupName, contextAdapterName, headerVoList, url, request.getMethod(), 0);
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }
        };

		HttpResponseInterceptor responseLogInterceptor = (response, entity, context) -> {
			if (!httpHeaderLogMode)  {
	    		logger.info("httpHeader logging off - use.http.header.log");
	    		return;
			}
			
	        try{
				HttpRequest request = (HttpRequest)context.getAttribute("http.request");
				String url = request.getUri().toString();
				String uuid = (String) context.getAttribute(TransactionContextKeys.TRANSACTION_UUID);
				String contextAdapterGroupName = (String) context.getAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
				String contextAdapterName = (String) context.getAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME);
				Integer logProcessNo = (Integer) context.getAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO);
				if (logProcessNo == null || logProcessNo <= 0) {
					logProcessNo = 300;
				}else if(logProcessNo == 400){
					return;
				}else{
					logProcessNo += 100;
				}
				
				HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, logProcessNo,
						contextAdapterGroupName, contextAdapterName, response.getHeaders(), url, request.getMethod(), response.getCode());
			} catch (URISyntaxException e) {
				throw new RuntimeException(e);
			}
		};

		this.client = HttpClients.custom()
				.disableDefaultUserAgent()
				.disableContentCompression()
				.disableRedirectHandling()    // redirect 자동 재전송 비활성화
		        .disableAutomaticRetries()    // 429 포함 자동 재시도 비활성화
		        .addRequestInterceptorLast(requestLogInterceptor)
				.addResponseInterceptorFirst(responseLogInterceptor)
				.setConnectionManager(connectionManager)
				.evictExpiredConnections()
				.build();
	}

	public int getConnectionsInPool() {
		if (this.client == null || this.connectionManager == null)
			return 0;
		return this.connectionManager.getTotalStats().getAvailable() + this.connectionManager.getTotalStats().getLeased();
	}

	public int getMaxTotalConnections() {
		if (this.client == null || this.connectionManager == null)
			return 0;
		return this.connectionManager.getMaxTotal();
	}

	public int getMaxConnectionsPerHost() {
		if (this.client == null || this.connectionManager == null)
			return 0;
		return this.connectionManager.getDefaultMaxPerRoute();
	}

	public String getResponseCharSet(String httpResponseCharset, String adapterGroupName) {
		if (StringUtils.isNotBlank(httpResponseCharset)) {
			return httpResponseCharset;
		} else {
			AdapterManager m = AdapterManager.getInstance();
			AdapterGroupVO agvo = m.getAdapterGroupVO(adapterGroupName);
			return agvo.getMessageEncode();
		}
	}

	/**
	 * encoding에 대한 정의 필요 고려할 문제
	 * 1. 어댑터그룹이 UJN 타입일경우 return 은 UTF-8 로 getBytes 된
	 * 데이터여야됨.
	 * 2. http response에 받은 encoding 타입은 고려대상임. 결론 : response Encoding 과
	 * 어댑터의 MESSAGE_TYPE 다르면 변환, 나머지면 return 어댑터의 ENCODE은 요청전문 encoding에서만 사용
	 *
	 * 예) 어댑터의 ENCODE, 어댑터의 MESSAGE TYPE, RESPNSE ENCODING TYPE, 처리내용
	 * 예) ,ECU-KR ,JSN , EUC-KR , 응답받은 메시지를 return 예) ,ECU-KR , JSN , UTF-8 , 응답받은 메시지를 new
	 * String(xxx,"UTF-8") => getBytes("EUC-KR") 예) ,ECU-KR , UJN , EUC-KR , 응답받은
	 * 메시지를 new String(xxx,"EUC-KR") => getBytes("UTF-8") 예) ,ECU-KR , UJN , UTF-8 ,
	 * 응답받은 메시지를 return 예) ,UTF-8 , JSN , EUC-KR , 응답받은 메시지를 return
	 * 예) ,UTF-8 , JSN , UTF-8 , 응답받은 메시지를 new String(xxx,"UTF-8") => getBytes(EUC-KR") 예) ,UTF-8 ,
	 * UJN , EUC-KR , 응답받은 메시지를 new String(xxx,"EUC-KR") => getBytes("UTF-8") 예)
	 * ,UTF-8 , UJN , UTF-8 , 응답받은 메시지를 return
	 *
	 *
	 */
	public byte[] getEncodingConvert(String httpResponseCharset, String adapterGroupName, byte[] message) {
		byte[] responseMessage = null;
		if (StringUtils.isNotBlank(httpResponseCharset)) {
			AdapterManager m = AdapterManager.getInstance();
			AdapterGroupVO agvo = m.getAdapterGroupVO(adapterGroupName);
			String adapterTypeEncoding = StringUtils.defaultIfBlank(agvo.getMessageEncode(),
					Charset.defaultCharset().name());
			Charset responseCharset = null;
			Charset adapterCharset = null;
			try {
				responseCharset = Charset.forName(httpResponseCharset);
				adapterCharset = Charset.forName(adapterTypeEncoding);
				if (responseCharset.compareTo(adapterCharset) == 0) {
					responseMessage = message;
				} else {// http 응답 charset 과 어댑터의 charset 이 다를경우 charset 변환 필요
					String encodingString = new String(message, httpResponseCharset);
					responseMessage = encodingString.getBytes(adapterTypeEncoding);
				}
			} catch (UnsupportedCharsetException | UnsupportedEncodingException e) {
				responseMessage = message;
			}
		} else {
			responseMessage = message;
		}
		return responseMessage;
	}

	public void configureHttpClient(RequestConfig.Builder requestConfigBuilder, HttpClientAdapterVO vo, HttpUriRequestBase method) {

		if (vo.getConnectionTimeout() != 0) {
			requestConfigBuilder.setConnectTimeout(vo.getConnectionTimeout(), TimeUnit.MILLISECONDS);
		}

		if (vo.getTimeout() > -1) {
			requestConfigBuilder.setResponseTimeout(vo.getTimeout(), TimeUnit.MILLISECONDS);
		}
		
		org.apache.hc.core5.http.HttpVersion httpVersion ;
		if (HTTP_VERSION_1_0.equals(vo.getHttpVersion())) {
			httpVersion = org.apache.hc.core5.http.HttpVersion.HTTP_1_0;
		}
		else if (HTTP_VERSION_2_0.equals(vo.getHttpVersion())) {
			httpVersion = org.apache.hc.core5.http.HttpVersion.HTTP_2_0;
		}
		else {
			httpVersion = org.apache.hc.core5.http.HttpVersion.HTTP_1_1;
			method.addHeader("Connection", "close");
		}
		method.setVersion(httpVersion);

		RequestConfig requestConfig = requestConfigBuilder
				.setConnectTimeout(vo.getConnectionTimeout(), TimeUnit.MILLISECONDS)  // 연결 시간 초과를 5초로 설정
				.setResponseTimeout(vo.getTimeout(), TimeUnit.MILLISECONDS) // 응답 시간 초과를 5초로 설정
				.build();

		method.setConfig(requestConfig);
	}

	public abstract Object execute(Properties prop, Object message, Properties tempProp) throws Exception;
	
	protected boolean isTas(Properties tempProp) {
		String tranType = tempProp.getProperty("tranType");
		return EAIServerManager.getInstance().isTASEnabledEAIServer()
				&& StringUtils.equals(tranType, EAIMessageKeys.TRANTYPE_TAS);
	}
}