package com.eactive.eai.adapter.wca;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;

import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterPropManager;
import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.adapter.wca.cfg.SNAAdapter;
import com.eactive.eai.adapter.wca.cfg.SNAAdapterManager;
import com.eactive.eai.adapter.wca.cfg.SessionGroupDataEx;
import com.eactive.eai.adapter.wca.data.AppCodeCmd;
import com.eactive.eai.adapter.wca.data.ManualBackupCmd;
import com.eactive.eai.adapter.wca.data.WCAAdapterInfoCmd;
import com.eactive.eai.adapter.wca.data.WCAHeader;
import com.eactive.eai.adapter.wca.data.WCAQueue;
import com.eactive.eai.adapter.wca.data.WCAQueueData;
import com.eactive.eai.adapter.wca.exception.WCAException;
import com.eactive.eai.adapter.wca.global.WCADefine;
import com.eactive.eai.adapter.wca.util.CodeCvrt;
import com.eactive.eai.adapter.wca.util.MyLog;
import com.eactive.eai.adapter.wca.util.MyUtil;
import com.eactive.eai.common.errorcode.ErrorCodeHandler;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.LogKeys;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.SMSLogUtil;

/** 
* 1. 기능 :  Softlink TCP 세션 연결 및 수신 데이타를 처리한다.
* 2. 처리 개요 : 
*       - 
* 3. 주의사항 
*
* @author  : 
* @version : v 1.0.0
* @see     : 
* @since   : 2005. 6. 17. 오후 10:01:13
*/  
public class WCASessionThread implements Runnable {
    /**
     * SNAAdapter critical한 정보 로깅을 위한 logger 
     */
    Logger adapter_logger = Logger.getLogger(WCADefine.ADAPTER_LOGGER_NAME);
    
    // 20090810 Start
    private String					sNodeName = "";				// Softlink Node Name
    // 20090810 End
    
    private Socket					sockSoftLink = null;		// SoftLink와 연결할 Socket    
    //private BufferedInputStream 	isSoftLink = null;			// Socket에 대한 InputStream
    private InputStream 			isSoftLink = null;			// Socket에 대한 InputStream
    private BufferedOutputStream 	osSoftLink = null;			// Socket에 대한 OutputStream
    private HashMap					hmapRcvData = null;			// 세션에서 수신된 데이터를 저장할 Queue에 대한 HashMap
    private MyLog					myLog = null;				// 메시지 로그 처리 오브젝트
    private MyLog					myTrc = null;				// 트레이스 처리 오브젝트
    private String					sIpAddr = null;				// SoftLink IP 주소
    private int 					nPortNo = 0;				// SoftLink Port 번호 
    private String 					sAdapterGroupName = null;   // SoftLink 세션에 대한 Adapter Group Name
    private String 					sAdapterName = null;		// SoftLink 세션에 대한 Adapter Name
    private String 					sGroupCode = null;			// SoftLink 세션에 대한 Group Code
    private int						nSessId = 0;				// SoftLiInk 세션 ID
    private WCAEventInterface		wcaEventHandler = null;		// SoftLiInk 세션에 대한 수신 데이터 처리 이벤트 핸들러
    
    private int		nRcvTimeout = 0;				// 데이터 수신 Timeout
    private int		nConnTimeout = 0;				// 세션 연결 Timeout
    private int		nSessStat = 0;              	// TCP 세션 상태 0 : 연결안됨, 1 : TCP 세션 연결, 2 : TCP 세션 사용중 (사용안함)
    
    private int		nDisconnectCnt = 0;				// 세션 해제 횟수
    private int		nContinuousDisconnectCnt = 0;	// 연속적으로 세션 연결 실패 횟수
    private int		nSndCnt = 0;					// 데이터 송신 횟수
    private int		nRcvCnt = 0;					// 데이터 수신 횟수
    
    private boolean bRunning 	= false;            // Thread 동작 여부
    private boolean bBackupSessionPool  = false;    // Backup 세션 여부
    
    // Softlink IP 주소가 여러개인 경우, 백업 모드 기능 자체를 사용할 수 없음 
    private boolean bBackupEnableFlag 	= true;		// Backup 세션 전환 사용 가능 여부
    
    private boolean bBeforeConn         = false;    // 이전 TCP 세션 연결 여부

    private int     tran_timeout        = WCADefine.DEFAULT_TRANSACTION_TIMEOUT ;  // 트랜잭션 타임아웃
    private String  tran_mode;                      // 송신(S),수신(R) 모드 
    
    long 			lReadStartTime = 0L, lReadEndTime = 0L, lNotifyEndTime = 0L;    
    
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd=HH:mm:ss.SSS");
    
    private SNAAdapter	snaAdapter = null;
        
    // 20090810    
    public boolean getBackupEnableFlag() {
    	return bBackupEnableFlag;
    }
    
    public void setBackupEnableFlag(boolean bBackupEnableFlag) {
    	this.bBackupEnableFlag = bBackupEnableFlag;
    	return;
    }
    // 20090810 End
    
    
    /** 
     * 1. 기능 : 노드 이름 설정 
     * 2. 처리 개요 : 
     * 3. 주의사항 
     * 
     * @param sNodName 노드이름
     **/  
     public void setNodeName(String s_node_name) {
         if (s_node_name == null) { s_node_name = ""; }
         this.sNodeName = s_node_name.trim();   
     }
     
     /** 
     * 1. 기능 :  노드 이름 반환
     * 2. 처리 개요 : 
     * 3. 주의사항 
     * 
     * @return    노드이름
     **/
     public String getNodeName() {
         return this.sNodeName; 
     }
     
   /** 
    * 1. 기능 : 세션 정보 반환
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @return 세션 정보
    **/   
    public String toString() {
        StringBuffer sb_name = new StringBuffer();
        
        sb_name.append(" " + sGroupCode + "-" + nSessId);
        
        if (bBackupSessionPool) {
            sb_name.append(":BACKUP ");
        }
        else { 
            sb_name.append(" ");
        }
        
        return sb_name.toString();
    }
    
    /** 
    * 1. 기능 :  INBOUND 핸들러 등록
    * 2. 처리 개요 : 
    * 3. 주의사항
    **/
    public void registerEventHandler(Object objHandler) {
        wcaEventHandler = (WCAEventInterface)objHandler;
        return;
    }
    
    /** 
    * 1. 기능 : 트랜잭션 타임아웃 설정
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @param    timeout    트랜잭션 타임아웃
    **/  
    public void setTranTimeout(int timeout) {
        this.tran_timeout = timeout;
    }
    
    /** 
    * 1. 기능 : 트랜잭션 타임아웃 반환
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @return    트랜잭션 타임아웃
    **/    
    public int getTranTimeout() {
        return this.tran_timeout;
    }
    
    /** 
    * 1. 기능 : 트랜잭션 송수신 모드 설정
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @param    sTranMode    트랜잭션 송수신 모드
    **/  
    public void setTranMode(String sTranMode) {
        if (sTranMode == null ) { sTranMode = ""; }
        this.tran_mode = sTranMode;   
    }
    
   /** 
    * 1. 기능 : 트랜잭션 송수신 모드 반환
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @return    트랜잭션 송수신 모드
    **/   
    public String getTranMode() {
        return this.tran_mode;   
    }
    
   /** 
    * 1. 기능 : Sotlink 세션 정보 설정
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @param    sIpAddr         Softlink Ip 주소  
    * @param    nPortNo         SoftLink Port 번호    
    * @param    nSessNum        WCA 세션 수
    **/   
    public void setSessionInfo(String sIpAddr, int nPortNo) {
        this.sIpAddr = sIpAddr;
        this.nPortNo = nPortNo;        
    }
    
    public void updateSessionInfoEx(SessionGroupDataEx sessgroup) {
    	// 로컬 세션인 경우
        if (!isBackupSessionPool()) {
        	setSessionInfo(sessgroup.getSoftLinkSevrName(),  sessgroup.getPort());
        }
        // 백업 세션인 경우
        else {
            setSessionInfo(sessgroup.getBackupNodeName(), sessgroup.getBackupPort());
        }
        
        setLogInfo(sessgroup.isLogWritable(), sessgroup.getLogLevel(), sessgroup.getLogSize());
        setTranTimeout(sessgroup.getTranTimeout());
        setBackupEnableFlag(sessgroup.getBackupEnableFlag());

        // APP_CODE 프라퍼티가 변경되었을 경우, Softlink에게 데이터 전송
        try {
        	sendWcaSessionAppCode(true);
        }
        catch (Exception e) {
        	adapter_logger.warn("WCASessionThread:updateSessionInfoEx> Exception Msg = " + e.getMessage());        	
        }
        
        myLog.logMsg(MyLog.INFO, toString() + " 세션 정보 갱신 " + sessgroup.toString());
    }
    
    // 2020.05.27 updateSessionInfoEx 함수를 참조하여 개발함
    public void updateWcaAppCodeEx() {
        // APP_CODE 프라퍼티가 변경되었을 경우, Softlink에게 데이터 전송
        try {
        	adapter_logger.warn("WCASessionThread:updateWcaAppCodeEx> Try to Send Wca AppCode");
        	sendWcaSessionAppCode(false);
        }
        catch (Exception e) {
        	adapter_logger.warn("WCASessionThread:updateWcaAppCodeEx> Exception Msg = " + e.getMessage());        	
        }
        
        myLog.logMsg(MyLog.INFO, toString() + " WCA APP CODE 정보 갱신");
    }    
    
    /** 
    * 1. 기능 : 로그 설정
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @param    bLogYN       로깅 여부
    * @param    nLogLevel    로깅 레벨
    * @param    nLogSize     로깅 파일 사이즈
    **/    
    public void setLogInfo(boolean bLogYN, int nLogLevel, int nLogSize) {
        if (myLog != null) {
    		myLog.setLogLevel(nLogLevel);
    		myLog.setTraceOnOff(bLogYN);
    		myLog.setMaxLogFileSize(nLogSize);
    	}
        
    	if (myTrc != null) {
    		myTrc.setLogLevel(nLogLevel);
    		myTrc.setTraceOnOff(bLogYN);
    		myTrc.setMaxLogFileSize(nLogSize);
    	}
    }
       
    /**
     * 1. 기능 : SoftLink와의 세션을 관리할 쓰레드 클래스 생성 함수
     * 2. 처리 개용 :
     * 		- 파라미터로 넘어온 정보를 내부 변수에 설정한다.
     * 		- 로그와 트레이스를 저장하기 위한 로그 처리 클래스의 오브젝트를 생성한다.
     * 3. 주의사항
     * 		- 트레이스 처리의 동기화를 위해서 myTrc 클래스에 대한 동기화를 수행한다.
     * 		- 송신 데이터 버퍼 형식 : 길이(4) + Data(n)
     * 
     * @param   sIpAddr                 Softlink Ip 주소 
     * @param   nPortNo                 SoftLink Port 번호 
     * @param   sAdapterGroupName       어댑터 그룹 이름
     * @param   sAdapterName            어댑터 이름
     * @param   sGroupCode              LU 그룹 이름
     * @param   nSessId                 Pool에 설정된 세션 수
     * @param   bBackupSessionPool      Backup Pool 여부 (true: 백업 세션)
     * @param   bLogYN                  로깅 여부
     * @param   nLogLevel               로그 레벨
     * @param   nLogSize                로그 파일 사이즈 
     */         
    public WCASessionThread(String sNodeName, String sIpAddr, int nPortNo, 
                            String sAdapterGroupName, String sAdapterName, String sGroupCode, int nSessId, 
                            boolean bBackupSessionPool,
                            boolean bLogYN, int nLogLevel, int nLogSize) {
        try {
            this.sNodeName			= sNodeName;
            this.sIpAddr            = sIpAddr;
            this.nPortNo            = nPortNo;
            this.sAdapterGroupName  = sAdapterGroupName;
            this.sAdapterName       = sAdapterName;
            this.sGroupCode         = sGroupCode;

            this.nSessId = nSessId;
            this.bBackupSessionPool = bBackupSessionPool;
            
            nSessStat = WCADefine.SESS_STAT_NOTCONN;
            
            hmapRcvData = new HashMap();            
            nRcvTimeout = WCADefine.DEFAULT_RECEIVE_TIMEOUT;
            nConnTimeout = WCADefine.DEFAULT_CONNECT_TIMEOUT;
            
            snaAdapter = SNAAdapterManager.getInstance().selectSnaAdapter(sAdapterGroupName);
            
            String sLogDirectory = PropManager.getInstance().getProperty(LogKeys.LOGGER_INFO, LogKeys.LOG_DIRECOTRY_PREFIX);
            if (sLogDirectory==null || sLogDirectory.equals("")) {
                sLogDirectory = WCADefine.SERVER_NAME + File.separatorChar + "wca";
            }
            else {
                sLogDirectory = sLogDirectory + File.separatorChar + WCADefine.SERVER_NAME + File.separatorChar + "wca";
            }
            
            //if (trc_logger.isDebug())  trc_logger.debug(">> sLogDirectory = " + sLogDirectory);
            // 로그와 트레이스 처리를 위한 오브젝트 생성
            String	sMyTrcPath = "";
            String	sMyLogPath = "";
            
            if (bBackupSessionPool) {               
                // 20090810 Start
                /*
                myTrc = new MyLog(bLogYN, nLogLevel, nLogSize,  sLogDirectory + File.separatorChar +  sGroupCode + "-" + nSessId + "_BACKUP.trc");
                myLog = new MyLog(bLogYN, nLogLevel, nLogSize, sLogDirectory + File.separatorChar +  sGroupCode + "-" + nSessId + "_BACKUP.log");

                sMyTrcPath = sLogDirectory + File.separatorChar + sGroupCode + "-" + nSessId + "_BACKUP.trc";
                sMyLogPath = sLogDirectory + File.separatorChar + sGroupCode + "-" + nSessId + "_BACKUP.log";
                */
                myTrc = new MyLog(bLogYN, nLogLevel, nLogSize,  sLogDirectory + File.separatorChar + sNodeName + "_" + sGroupCode + "-" + nSessId + "_BACKUP.trc");
                myLog = new MyLog(bLogYN, nLogLevel, nLogSize, sLogDirectory + File.separatorChar +  sNodeName + "_" + sGroupCode + "-" + nSessId + "_BACKUP.log");

                sMyTrcPath = sLogDirectory + File.separatorChar + sNodeName + "_" + sGroupCode + "-" + nSessId + "_BACKUP.trc";
                sMyLogPath = sLogDirectory + File.separatorChar + sNodeName + "_" + sGroupCode + "-" + nSessId + "_BACKUP.log";            	
                // 200908010 End
            }
            else {
            	// 20090810 Start
            	/*
                myTrc = new MyLog(bLogYN, nLogLevel, nLogSize, sLogDirectory + File.separatorChar + sGroupCode + "-" + nSessId + ".trc");   
                myLog = new MyLog(bLogYN, nLogLevel, nLogSize, sLogDirectory + File.separatorChar + sGroupCode + "-" + nSessId + ".log");
                
                sMyTrcPath = sLogDirectory + File.separatorChar + sGroupCode + "-" + nSessId + ".trc";
                sMyLogPath = sLogDirectory + File.separatorChar + sGroupCode + "-" + nSessId + ".log";
                */               
                myTrc = new MyLog(bLogYN, nLogLevel, nLogSize, sLogDirectory + File.separatorChar + sNodeName + "_" + sGroupCode + "-" + nSessId + ".trc");   
                myLog = new MyLog(bLogYN, nLogLevel, nLogSize, sLogDirectory + File.separatorChar + sNodeName + "_" + sGroupCode + "-" + nSessId + ".log");
                
                sMyTrcPath = sLogDirectory + File.separatorChar + sNodeName + "_" + sGroupCode + "-" + nSessId + ".trc";
                sMyLogPath = sLogDirectory + File.separatorChar + sNodeName + "_" + sGroupCode + "-" + nSessId + ".log";
                // 20090810 End
            }
            
            /* 20080523 Start */
            try {
	            if (adapter_logger.isWarn()) {
	            	adapter_logger.warn("adapter_logger trc_logger Path : " + Logger.LOGGER_ADAPTER);
	            	adapter_logger.warn("adapter_logger adapter_logger Path : " + WCADefine.ADAPTER_LOGGER_NAME);
	            	adapter_logger.warn("adapter_logger myTrc Path : " + sMyTrcPath);
	            	adapter_logger.warn("adapter_logger myLog Path : " + sMyLogPath);            	
	            }
	 
	        	myLog.logMsg(MyLog.WARN, "trc_logger Path : " + Logger.LOGGER_ADAPTER);
	        	myLog.logMsg(MyLog.WARN, "adapter_logger Path : " + WCADefine.ADAPTER_LOGGER_NAME);
	        	myLog.logMsg(MyLog.WARN, "myTrc Path : " + sMyTrcPath);
	        	myLog.logMsg(MyLog.WARN, "myLog Path : " + sMyLogPath);   
            }
            catch(Exception ignore) {
            	// System.out.println("WCASessionThread> Exception Msg = " + ignore.toString());
            }
            /* 20080523 End */
        }
        catch(Exception e) {
            adapter_logger.error("WCASessionThread] - [" + WCAException.E_ETC + "] " + toString() + e.toString());
        }
    }
    
    /** 
    * 1. 기능 : 백업 세션 여부 설정 (true: 백업 세션)
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @param    flag       백업 세션 여부
    **/ 
    public void setBackupSessionPool(boolean flag) {
        bBackupSessionPool = flag;
    }
    
    /** 
    * 1. 기능 : 백업 세션 여부 반환 (true: 백업 세션)
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @return    백업 세션 여부
    **/  
    public boolean isBackupSessionPool() {
        return bBackupSessionPool;
    }
    
    /** 
    * 1. 기능 : 데이터 송신별 수신 데이터를 대기할 Queue를 등록
    * 2. 처리 개요 : 
    *       - 송신 데이터의 트랜잭션 Key 필드를 기준으로 HashMap에 등록한다.
    * 3. 주의사항 
    *      - WLI의 송신 쓰레드는 데이터 송신 후, 등록한 Queue를 대기한다.
    * 
    * @param    objKey   HashMap에 저장될 unique한 key (UUID)
    * @param    objData  HashMap에 저장될 값 (송신에 대한 Queue 데이타)
    * @return   0
    **/
	public int registerRcvQueue(Object objKey, Object objData) {        
	    synchronized (hmapRcvData) {
	        hmapRcvData.put(objKey, objData);
	    }
		return 0;
	}
	
    /** 
    * 1. 기능 :  Queue에 등록된 송신에 대한 Queue 데이타 삭제
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @param    objKey   HashMap에 저장될 unique한 key (UUID)
    **/
    public int unregisterRcvQueue(Object objKey) {
	    synchronized (hmapRcvData) {
	        hmapRcvData.remove(objKey);
	    }
		return 0;
	}
    
    /**
     * 1. 기능 : SoftLink 세션을 연결한다.
     * 2. 처리 개요 : 
     * 		- SoftLink와 TCP 세션을 연결한다.
     * 		- 세션이 연결되면 소켓에 대한 In/Out 스트림을 생성한다.
     * 3. 주의사항
     * 
     * @param    nTimeout 연결 Timeout
     * @return   세션 연결 결과
     * @exception IOException
     */        
	public boolean connectSoftLink(int nTimeout) throws IOException {
	    SocketAddress	sockAddr = null;
        
	    try {
	        sockSoftLink = new Socket();            
            myLog.logMsg(MyLog.DEBUG, "connectSoftLink] New Socket is Created...");	        	        
		    sockAddr = new InetSocketAddress(sIpAddr, nPortNo);            
            myLog.logMsg(MyLog.INFO, "connectSoftLink] New Socket Address, Addr = " + sockAddr);           
		    
            myLog.logMsg(MyLog.DEBUG, "connectSoftLink] Try Connect to SoftLink..., nTimeout = " + nTimeout + "ms");
	        sockSoftLink.connect(sockAddr, nTimeout);
            
            myLog.logMsg(MyLog.INFO, "RECVEIVE Buffer Size = " + sockSoftLink.getReceiveBufferSize());
            //sockSoftLink.setReceiveBufferSize(2048);
            myLog.logMsg(MyLog.INFO, "connectSoftLink] Session is Connected...(Local Port = " + sockSoftLink.getLocalPort() + ")");
	                    
	        //isSoftLink = new BufferedInputStream(sockSoftLink.getInputStream());
            isSoftLink = sockSoftLink.getInputStream();
            osSoftLink = new BufferedOutputStream(sockSoftLink.getOutputStream());
            
            myLog.logMsg(MyLog.INFO, "connectSoftLink] Socket In/Out Stream is Opened...");
            
            // if true, pakcets are sent as quickly as possible regardless of their size
            sockSoftLink.setTcpNoDelay(true);
          
            nSessStat = WCADefine.SESS_STAT_READY;
            nContinuousDisconnectCnt = 0;
            return true;
	    }
	    catch (Exception e) {
	        myLog.logMsg(MyLog.ERROR, "connectSoftLink] " + e.toString());
	        
            // softlink가 내려가고 EAI가 내려갔을 경우,,, 세션이 백션으로 가야함...(2분)
            if (nContinuousDisconnectCnt % WCADefine.DEFAULT_BACKUP_CONVERSION_CHECK == 0) {
                checkBackupConversion();
            }
            
            nContinuousDisconnectCnt++;
	        nDisconnectCnt++;            
	        return false;
	    }
	}
	
   /** 
    * 1. 기능 :  Thread 종료
    * 2. 처리 개요 : 
    *          - running 상태 Flag를 false로 설정한다.
    * 3. 주의사항
    **/
    public void stopAdapter() {
        myLog.logMsg(MyLog.INFO, "stopAdapter] Thread is Stopping. " + toString() );
        bRunning = false;    
    }
    
   /** 
    * 1. 기능 :  어댑터 상태 설정
    * 2. 처리 개요 : 어댑터 그룹 이용가능한 세션이 없을 경우 어댑터 상태를 false로 한다.
    * 3. 주의사항
    **/
    private void setAdapterStatus(boolean flag) {
        int nAdapterConnected;
        
        /* 20080523 수정 시작 */
        /*
      	어댑터 상태를 사용안함으로 설정할때, EAI Framework쪽에서 어댑터 정보를 메모리에서 
      	삭제하기 때문에 getAdapterVO 함수 호출시 NULL 값이 리턴되어 synchronized(av) 라인 처리시
      	java.lang.NullPointerException 발생함
      	--> 전체 로직을 try, catch로 묶음
        */ 
        try {        
	        AdapterManager amanager = AdapterManager.getInstance();
	        AdapterVO av = amanager.getAdapterVO(this.sAdapterGroupName,this.sAdapterName);
	        
	        synchronized (av) {
	            // if (av.isStatus() == flag)
	            //{
	                try {
	                    nAdapterConnected = snaAdapter.getConnectedSessNum();
	                    if (av != null) {
		                    if (nAdapterConnected <= 0 ) {                  
		                        av.setStatus(false);
		                    }
		                    else {
		                        av.setStatus(true);
		                    }
	                    }
	                }
	                catch (Exception e) {
	                	adapter_logger.error("setAdapterStatus error", e);
	                }
	            //}            
	        }
        }
	    catch (Exception e) { 
	    	myLog.logMsg(MyLog.INFO, "setAdapterStatus] Exception Msg = " + e.toString());	                	
	    }    
	    /* 20080523 수정 끝 */
    }
    
    /** 
    * 1. 기능 :  처리되지 않은 트랜잭션 출력 및 notify
    * 2. 처리 개요 : 
    * 3. 주의사항
    **/
    private void traceRemainTransaction() {        
        int 		iRemainWorkCnt = 0;
        String 		sKey = null;
        WCAQueue 	queueWSA = null;        
        WCAHeader 	header = null;
        String 		sMessage = "";
        String 		sCode = "";
        char 		cMode ;
        boolean 	bRecvSendOK = false;
        
        Iterator 	iter = null;
       
        HashMap 	copy = (HashMap)hmapRcvData.clone();
        iter = copy.keySet().iterator();
        
        myLog.logMsg(MyLog.ERROR, ">>>>>>>>> " + toString() +" Remain Work is started. (Number = "+copy.size() + ") <<<<<<<<<<");
        while (iter.hasNext()) {
            try {
                iRemainWorkCnt++;
                
                sKey = (String)iter.next();
                if (sKey == null) {
                    if (adapter_logger.isError()) { adapter_logger.error("traceRemainTransaction] " + toString() + "sKey is null"); }
                    myLog.logMsg(MyLog.ERROR, "traceRemainTransaction] sKey is null");
                    continue;
                }
                
                myLog.logMsg(MyLog.ERROR, "Remain Work " + String.valueOf(iRemainWorkCnt) + "] TRANSACTION KEY = " + sKey);
                
                // notify 
                synchronized (hmapRcvData) {
                    queueWSA = (WCAQueue)hmapRcvData.remove(sKey);
                }
                                
                synchronized (queueWSA) {
                    bRecvSendOK = queueWSA.getRecvSendOKFlag();
                    cMode = queueWSA.getMode();
                }
                
                if (bRecvSendOK) {
                    sCode = WCAException.E_SOFTLINK_RECV_SESSION_CLOSE;
                }
                else {
                    sCode = WCAException.E_SOFTLINK_SEND_SESSION_CLOSE;
                }
                    
                if (adapter_logger.isError()) { adapter_logger.error(WCAException.displayError(sCode) + toString() + " TRANSACTION KEY = " + sKey); }
                sMessage = sCode + ErrorCodeHandler.getMessage(sCode);	
                
                // 데이타 조립
                header = new WCAHeader();
                byte[]  pBuff = new byte[WCAHeader.WCA_HEADER_LEN + sMessage.getBytes().length];
                System.arraycopy(header.getHeader(), 0, pBuff, 0, WCAHeader.WCA_HEADER_LEN);
                System.arraycopy(sMessage.getBytes(), 0, pBuff, WCAHeader.WCA_HEADER_LEN, sMessage.getBytes().length);
                            
                // NonResponse 모드이고 sendok를 받았으면 INBOUND로 보낸다. 그외는 notify
                if (cMode == (WCAHeader.TRAN_MODE_NONRESPONSE).charAt(0) && bRecvSendOK){
                    myLog.logMsg(MyLog.DEBUG, "traceRemainTransaction] NonResponse, send OK is received ... inbound");
                    byte[] pRecvBuff = queueWSA.getDataFromQueue();
                    System.arraycopy(pRecvBuff, 0, pBuff, 0, WCAHeader.WCA_HEADER_LEN);
                    
                    if (wcaEventHandler != null) {
                        myLog.logMsg(MyLog.DEBUG, "traceRemainTransaction] Call Event Handler : handleTimeoutData()");
                        wcaEventHandler.handleNonRspData(sAdapterGroupName, sAdapterName, sGroupCode, pBuff.length, pBuff);
                    }
                    else {
                        myLog.logMsg(MyLog.ERROR, "traceRemainTransaction] Event Handler is Not Registered, So Discard Data...");
                    }
                }
                else {
                    synchronized (queueWSA) {
                        WCAQueueData qdataWSA = new WCAQueueData(pBuff.length, pBuff);
                        queueWSA.addDataToQueue(qdataWSA);
                        myLog.logMsg(MyLog.DEBUG, "traceRemainTransaction] (START) Notify WLI Thread...");
                        queueWSA.notifyAll();
                        myLog.logMsg(MyLog.DEBUG, "traceRemainTransaction] (END) Notify WLI Thread...");
                    }
                }
            }
            catch (Exception e) {
                if (adapter_logger.isError()) { adapter_logger.error(WCAException.displayError(WCAException.E_INFORM_ERROR, sKey)); }
            }
        }
    }
    
    /** 
    * 1. 기능 : 데이타 수신 대기 및 수신 데이타 처리
    * 2. 처리 개요 : 
    *       - 세션이 연결될때까지 계속 socket 연결 시도한다.
    *       - bRunning가 true 일 동안 계속 Socket InputStream을 read한다.
    *       - 수신 데이타를 DATA TYPE에 따라 처리한다.
    * 3. 주의사항
    **/
    public void run() {
        int		nRcvLen;
        byte[]	pLenBuff = null;
        int		nDataLen = 0;
        int		nCheckOldWCAQueueCnt = 0;
        
        int     nConnectedSessNum = 0;
        int     nConfiguredSessNum = 0;
        
        myLog.logMsg(MyLog.INFO, "Transaction Timeout = " + tran_timeout + ", Transaction Mode = " + tran_mode);
        myLog.logMsg(MyLog.INFO, "run] " + toString() + " Thread is Started. (Thread Group Name = " + Thread.currentThread().getThreadGroup().getName() + ")");
        //adapter_logger.debug(toString() + " Thread is Started. (Thread Group Name = " + Thread.currentThread().getThreadGroup().getName() + ")");
                    
        bRunning = true;           
 
        while(true) {
            try {                
                // Thread stop 시켰을 경우 종료한다.
                if (!bRunning) {
                    myLog.logMsg(MyLog.DEBUG, "run] " + toString() + " bRunning = false so stop");                 
                    break;
                }
                    
                // 1. Socket 연결 시도
                if (nSessStat == WCADefine.SESS_STAT_NOTCONN) {
                    if (bBackupSessionPool) {
                        nConfiguredSessNum = snaAdapter.getConfiguredBackupSessNum(sNodeName, sGroupCode);
                        nConnectedSessNum = snaAdapter.getConnectedBackupSessNum(sNodeName, sGroupCode);
                    }
                    else {
                        nConfiguredSessNum = snaAdapter.getConfiguredNormalSessNum(sNodeName, sGroupCode);
                        nConnectedSessNum = snaAdapter.getConnectedNormalSessNum(sNodeName, sGroupCode);
                    }
                    
                    if (nConfiguredSessNum < nConnectedSessNum) {
                        if (adapter_logger.isWarn()) { adapter_logger.warn("설정된 세션수보다 많은 세션수가  존재합니다.  Thread 종료합니다.["  + nConnectedSessNum + "/" + nConfiguredSessNum + "]"); }
                        myLog.logMsg(MyLog.WARN, "설정된 세션수보다 많은 세션수가  존재합니다.  Thread 종료합니다.["  + nConnectedSessNum + "/" + nConfiguredSessNum + "]");
                        closeSoftLink();
                        break;                        
                    }        
                    
                    myLog.logMsg(MyLog.DEBUG, "run] Status = SESS_STAT_NOTCONN, So Retry to Connect To SoftLink....");
                    if (connectSoftLink(nConnTimeout) == false) {
                        myLog.logMsg(MyLog.ERROR, "run] Can't Connect To SoftLink, Sleep " + WCADefine.DEFAULT_SESS_RECOVERY_GAP);
                        
                        if (bBeforeConn) {
                            adapter_logger.error(WCAException.displayError(WCAException.E_SOFTLINK_NOTCONN,toString()));
                            myLog.logMsg(MyLog.WARN, "run] Session is Closed ");
                            
                            bBeforeConn = false;

                            /* 20080523 SMS 로그 추가 시작 */
                            try {                      	
	                    		int nSessCnt = snaAdapter.getConnectedNormalSessNum(sNodeName, sGroupCode);
	                    		
	                            if (adapter_logger.isWarn()) { adapter_logger.warn("getConnectedNormalSessNum> " + sAdapterGroupName + ", " + sGroupCode + " = "+ nSessCnt); }
	                            myLog.logMsg(MyLog.WARN, "getConnectedNormalSessNum> " + sAdapterGroupName + ", " + sGroupCode + " = "+ nSessCnt);
	                            
	                        	if (!bBackupSessionPool) {                        	                       		
	                        		if (nSessCnt == 0) {
	                        			// String	strSmsMsg = sAdapterGroupName + " Session is Closed";
	                        			// String	strSmsMsg = sGroupCode + " Session is Closed";
	                        			String	strSmsMsg = "Session is Closed";
	                        			try {
	        	                            if (adapter_logger.isWarn()) { adapter_logger.warn("WCASessionThread:run> Log SMS Msg, Msg = " + sGroupCode + " " + strSmsMsg); }
	        	                            myLog.logMsg(MyLog.WARN, "WCASessionThread:run> Log SMS Msg, Msg = " + sGroupCode + " " + strSmsMsg);
	                        				SMSLogUtil.loggingSMS(sGroupCode, strSmsMsg);
	                        			}
	                        			catch (Exception ignore) {
	                        				myLog.logMsg(MyLog.DEBUG, "run] Exception, While Write SMS Log, Msg = " + ignore.toString());
	                        			}
	                        		}
	                        	}
                            }
                            catch (Exception ignore) {
                            	// System.out.println("WCASessionThread:run> Exception Msg = " + ignore.toString());
                            }
                            /* 20080523 SMS 로그 추가 끝 */
                        }
                        
                        Thread.sleep(WCADefine.DEFAULT_SESS_RECOVERY_GAP * 1000);
                        continue;
                    }
                }
                
                // 세션이 복귀되면 adapter 상태를 변경한다.
                if (!bBeforeConn) {
                    setAdapterStatus(false);
                    sendAdapterStatusData();
                    
                    // 20090810 Start
                    sendWcaSessionAppCode(true);
                    // 20090810 End 
                }
                
                bBeforeConn = true;
                
                // Socket 수신 타임 아웃 설정
                //sockSoftLink.setSoTimeout(WCADefine.DEFAULT_SOCK_RCV_TIMEOUT * 1000);
                                
                // 2. Socket 수신 데이타 대기 -- 길이 필드 수신  (4 바이트)                
                String dateString = formatter.format(ZonedDateTime.now());
                lReadStartTime = System.currentTimeMillis();
                
                pLenBuff = recvLenthField(WCADefine.LEGTH_FIELD_SIZE);
    			if (pLenBuff == null) {	// Connection is Close...
                    closeSoftLink();                    
    			    continue;
    			}
                                
    			// 3. 길이 필드 변환
    			// 20090302 Start
    			/*
    			nDataLen = Integer.parseInt(new String(pLenBuff));
    			*/
    			nDataLen = getInt(pLenBuff, 0);
    			// 20090302 End
    			myLog.logMsg(MyLog.INFO, "run]  Data Length Field = " + nDataLen);
    			if (nDataLen < 0) {
    			    myLog.logMsg(MyLog.ERROR, "run] Invalid Data Length Field... So Close Session");
                    closeSoftLink();    			    
    			    continue;    			    
    			}
    			
                // 4. 데이타 수신
    			byte[] pDataBuff = recvDataField(nRcvTimeout, nDataLen);
    			if (pDataBuff == null) {	// Connection is Close...
                    closeSoftLink();                    
    			    continue;
    			}
    			
                lReadEndTime = System.currentTimeMillis();
                if (adapter_logger.isDebug()) { adapter_logger.debug(dateString + " : WCA_RECEIVING_TIME = " + (lReadEndTime - lReadStartTime)); }                
                myLog.logMsg(MyLog.DEBUG, "WCA_RECEIVING_TIME = " + (lReadEndTime - lReadStartTime));
                
    			nRcvLen = pDataBuff.length;
    			myLog.logMsg(MyLog.INFO, "run] Receive Data Field Length = " + nRcvLen);
    			
                // 5. 수신 데이타 처리 
    			handleRcvData(nRcvLen, pDataBuff); 
            }
			catch (java.lang.NumberFormatException n) {   // 길이 데이타를 파싱못할경우...
				myLog.logMsg(MyLog.ERROR, "run] " + WCAException.displayError(WCAException.E_RECVERR_DATALEN, toString(), MyUtil.toHex(pLenBuff)));
                if (adapter_logger.isError()) { adapter_logger.error(WCAException.displayError(WCAException.E_RECVERR_DATALEN, toString(), MyUtil.toHex(pLenBuff))); }
                this.closeSoftLink();
			}
            catch (SocketTimeoutException ste) {
                myLog.logMsg(MyLog.DEBUG, "run] SocketTimeoutException ocurrs");
                nCheckOldWCAQueueCnt++;
                if (WCADefine.DEFAULT_SOCK_RCV_TIMEOUT * nCheckOldWCAQueueCnt > WCADefine.DEFAULT_TRANSACTION_DELETE_TIMEOUT) {
                    myLog.logMsg(MyLog.DEBUG, "run] There is No Data to Read, Running...");
                    nCheckOldWCAQueueCnt = 0;
                    myLog.logMsg(MyLog.DEBUG, "run] Check Old WCAQueue...");
                    /*
                    long	lCurrTime = System.currentTimeMillis();                    
                    // HashMap에 WCAQueue가 무한적 추가되는 것을 막기 위해서, 시간이 경과한 WCAQueue를 삭제함.                    
    		        synchronized(hmapRcvData) {                             
    		            Iterator iter = hmapRcvData.keySet().iterator();
    	                while(iter.hasNext()) {
    	                    String 		sKey = (String)iter.next();
                            if (sKey == null) { if (trc_logger.isDebug())  trc_logger.debug("run] sKey is null");}
                                
    	                    WCAQueue 	wcaQueue = (WCAQueue)hmapRcvData.get(sKey);
                            if (wcaQueue == null) { if (trc_logger.isDebug())  trc_logger.debug("run] wcaQueue is null");}

    	                    if (wcaQueue != null) {
    		                    long lTimeGap = lCurrTime - wcaQueue.lCreateTime;
    		                    
    		                    if (lTimeGap > (WCADefine.DEFAULT_TRANSACTION_DELETE_TIMEOUT * 1000)) {
    	        		            myLog.logMsg(MyLog.ERROR, "run] Delete TRANSACTION Queue, Key = " + sKey);
                                    if (adapter_logger.isError())  adapter_logger.error(toString() + " Delete TRANSACTION Queue, Key = " + sKey);
    	        		            hmapRcvData.remove(sKey);
    		                    }
            		        }
                        }
    		        }
                    */
                }
            }
            catch (java.net.SocketException socket) {
                myLog.logMsg(MyLog.ERROR, "run] Error = " + socket.toString());
                this.closeSoftLink();
            }
            catch (Exception e) {
                myLog.logMsg(MyLog.ERROR, "run] Error = " + e.toString());
                if (adapter_logger.isError()) { adapter_logger.error("[" + WCAException.E_ETC + "] " + toString() + " 프로그램 에러입니다.  ", e); }
                this.closeSoftLink();
            }            
        }
        
        // 어댑터 상태 체크
        setAdapterStatus(true);
        
        // 미처리 트랜잭션 출력
        traceRemainTransaction();
        
        // Thread 종료시 LOG 파일 close
        myLog.logMsg(MyLog.INFO, "run] " + toString() + " Thread is Stopped");
        
        //adapter_logger.debug(toString() + " Thread is Stopped");
        closeLogStream();
    }
   
    
    /** 
    * 1. 기능 :  수신 데이타 처리 
    * 2. 처리 개요 : 
    *          - 수신 데이타 트레이스 파일에 로깅
    *          - DATA TYPE에 따라 각 처리 함수로 분기한다.
    * 3. 주의사항
    **/
    public void handleRcvData(int nLen, byte[] pBuff) {
        String 		sDataType = null;
        
        boolean		isLogFirstData = false;
        String		strFirstData = "";
        
        String		luName = "";
        String		threadName = "";
        long	 	startTime = 0;
        
		WCAHeader wcaHeader = new WCAHeader(WCAHeader.WCA_HEADER_LEN, pBuff);
		
		nRcvCnt++;
        // 트레이스 로깅
        /*
		synchronized(myTrc) {
		    myTrc.logMsg("SoftLink -> WCA (LEN = " + nLen + ")");
		    myTrc.logMsg("WCA Header : " + wcaHeader.toString());		    
		    myTrc.logData(WCADefine.EBCDIC_CODE, nLen, pBuff);
		}
        */
        sDataType = wcaHeader.getDataType();
        
        myLog.logMsg(MyLog.INFO, "handleRcvData] Receive DATA_TYPE VALUE = " + sDataType );
        
        // 송신 완료 
        if (sDataType.equals(WCAHeader.DATA_TYPE_SEND_OK)) {
            myLog.logMsg(MyLog.DEBUG, "handleRcvData] Status Mode : Send OK ");
            handleSendOK(nLen, pBuff);
        }
        else if (sDataType.equals(WCAHeader.DATA_TYPE_TIMEOUT_ONLY) || sDataType.equals(WCAHeader.DATA_TYPE_TIMEOUT_LAST)) {
            myLog.logMsg(MyLog.DEBUG, "handleRcvData] Status Mode : Timeout ");
            handleTimeout(nLen, pBuff);
        }        
        else if (sDataType.equals(WCAHeader.DATA_TYPE_NORMAL_FIRST) || sDataType.equals(WCAHeader.DATA_TYPE_NORMAL_MIDDLE) ||                 
                 sDataType.equals(WCAHeader.DATA_TYPE_DFS_FIRST) || sDataType.equals(WCAHeader.DATA_TYPE_DFS_MIDDLE) ||                 
                 sDataType.equals(WCAHeader.DATA_TYPE_TIMEOUT_FIRST) || sDataType.equals(WCAHeader.DATA_TYPE_TIMEOUT_MIDDLE)) {
            myLog.logMsg(MyLog.DEBUG, "handleRcvData] Status Mode : Normal First, Normal Middle, Delay First, Delat Middel ");
            handleData(nLen, pBuff);
        }
        else if (sDataType.equals(WCAHeader.DATA_TYPE_NORMAL_ONLY) || sDataType.equals(WCAHeader.DATA_TYPE_NORMAL_LAST) ||
                sDataType.equals(WCAHeader.DATA_TYPE_DFS_ONLY) || sDataType.equals(WCAHeader.DATA_TYPE_DFS_LAST)) {
            /// DEBUG_LOG : 20200406
        	isLogFirstData = false;
        	
        	// 운영 환경이 기본 로그 레벨이 warn임, 20200406 가용성 테스트 이후에 isInfo으로 변경 해야 함. (KB 김양규 차장님 요청 사항)
        	if (adapter_logger.isInfo()) {
        	// if (adapter_logger.isError()) {
            	if (nLen > WCAHeader.WCA_HEADER_LEN + 60) {
            		isLogFirstData = true;
            		
    	        	byte[] bFirstData = new byte[60];
    	        	System.arraycopy(pBuff, WCAHeader.WCA_HEADER_LEN, bFirstData, 0, 60);

    	        	strFirstData = new String(CodeCvrt.cvrtEbc2Ksc(bFirstData));
    	        	luName =  wcaHeader.getLUName();
    	        	threadName = Thread.currentThread().getName();
    	        	
    	        	adapter_logger.error("handleRcvData> ST " + threadName + ", " + luName + ", " + strFirstData);
    	        	
            		startTime = System.currentTimeMillis();    	        	
            	}
            }
            
            myLog.logMsg(MyLog.DEBUG, "handleRcvData] Status Mode : NORML_ONLY, NORML_LAST, DFS");
            handleNormalDFS(nLen, pBuff);
            
    		if (isLogFirstData == true) {
   			 	long	 	endTime = System.currentTimeMillis();
   			 	long		elapseTime = endTime - startTime;
   			 
   			 	adapter_logger.error("handleRcvData> EN " + threadName + ", " + luName + ", " + strFirstData + ", " + elapseTime);
    		        
    			if (elapseTime > 5000) {
    				adapter_logger.error("handleRcvData> OVEROVERTIME " + elapseTime + ", " + threadName + ", " + luName + ", " + strFirstData);
    			}        
    			else if (elapseTime > 1000) {
    				adapter_logger.error("handleRcvData> OVERTIME " + elapseTime + ", " + threadName + ", " + luName + ", " + strFirstData);
    			}			
    		}            
        }
        else if (sDataType.equals(WCAHeader.DATA_TYPE_ERROR)) {
            myLog.logMsg(MyLog.DEBUG, "handleRcvData] Status Mode : Error");
            handleError(nLen, pBuff);
        }
        else if (sDataType.equals(WCAHeader.DATA_TYPE_ADAPTER_ERROR) || sDataType.equals(WCAHeader.DATA_TYPE_ADAPTER_OK)) {
            myLog.logMsg(MyLog.DEBUG, "handleRcvData] Status Mode : DATA_TYPE_ADAPTER_ERROR, DATA_TYPE_ADAPTER_OK");
            handleAutoBackup(nLen, pBuff);
        }
        else if (sDataType.equals(WCAHeader.DATA_TYPE_GET_ADAPTER_STATUS)) {
            myLog.logMsg(MyLog.DEBUG, "handleRcvData] Status Mode : DATA_TYPE_GET_ADAPTER_STATUS");
            handleAutoBackup(nLen, pBuff);
        }                
        else {          	    
		    myLog.logMsg(MyLog.ERROR, "handleRcvData] " + WCAException.displayError(WCAException.E_SOFTLINK_UNKNOWN_COMMAND, toString(), sDataType));
            if (adapter_logger.isError()) { adapter_logger.error(WCAException.displayError(WCAException.E_SOFTLINK_UNKNOWN_COMMAND, toString(), sDataType)); }
		}

        return;        
    }
    
    /** 
    * 1. 기능 :  송신 완료 (Send OK) 데이타 처리
    * 2. 처리 개요 : 
    *       - NONRESPONSE 모드 : 
    *            1. 송신 Queue에 있을 경우 : 데이타 수신했음을 알려준다.
    *            2. 송신 Queue에 없을 경우 : 타임아웃 데이타로 INBOUND handleTimeoutData 핸들러로 보낸다.
    *      - RESPONSE 모드 :  수신 데이타 대기한다.
    * 3. 주의사항
    **/
    public void handleSendOK(int nLen, byte[] pBuff) {
        myLog.logMsg(MyLog.DEBUG, "handleRcvData] Receive STATUS_SENDOK...");
        
        String      sKey    = null;
        
        WCAHeader wcaHeader = new WCAHeader(WCAHeader.WCA_HEADER_LEN, pBuff);
        
        char	cMode = wcaHeader.getMode();
        myLog.logMsg(MyLog.DEBUG, "handleSendOK] Tran Mode = " + cMode);
        
		sKey = getKey(wcaHeader);
        myLog.logMsg(MyLog.DEBUG, "handleSendOK] Key = " + sKey);
            
        /* NONRESPONSE 모드 */
        if (cMode == (WCAHeader.TRAN_MODE_NONRESPONSE).charAt(0)) {            
            myLog.logMsg(MyLog.DEBUG, "handleSendOK] Response Mode = MODE_NONRESPONSE");
            
            WCAQueue queueWSA = null;
            synchronized (hmapRcvData) {
                queueWSA = (WCAQueue)hmapRcvData.remove(sKey);
            }
            
            if (queueWSA != null) { 
                synchronized (queueWSA) {
                    queueWSA.setRecvSendOKFlag(true);
                    WCAQueueData qdataWSA = new WCAQueueData(nLen, pBuff);
                    myLog.logMsg(MyLog.DEBUG, "handleSendOK] Add Queue Data, Length = " + nLen);
                    queueWSA.addDataToQueue(qdataWSA);
                    myLog.logMsg(MyLog.DEBUG, "handleSendOK] Notify WLI Thread...");
                    queueWSA.notifyAll();
                    lNotifyEndTime = System.currentTimeMillis();
                    String dateString = formatter.format(ZonedDateTime.now());                 
                    if (adapter_logger.isDebug()) { adapter_logger.debug(dateString + " : WCA_PROCESSIING_TIME = " + (lNotifyEndTime - lReadEndTime)); }
                }
            }
            else {
                if (wcaEventHandler != null) {
                    myLog.logMsg(MyLog.DEBUG, "handleSendOK] Call Event Handler : handleTimeoutData()");
                    wcaEventHandler.handleTimeoutData(sAdapterGroupName, sAdapterName, sGroupCode, nLen, pBuff);
                }
                else {
                    myLog.logMsg(MyLog.ERROR, "handleSendOK] Event Handler is Not Registered, So Discard Data...");
                }
            }
        }
        else if (cMode == (WCAHeader.TRAN_MODE_RESPONSE).charAt(0) || cMode == (WCAHeader.TRAN_MODE_TERMINAL).charAt(0)) {
            myLog.logMsg(MyLog.DEBUG, "handleSendOK] Response Mode = MODE_RESPONSE");
            myLog.logMsg(MyLog.DEBUG, "handleSendOK] Wait Host Data...");
            WCAQueue queueWSA = null;
            synchronized (hmapRcvData){
                queueWSA = (WCAQueue)hmapRcvData.get(sKey);
                if (queueWSA != null)
                    queueWSA.setRecvSendOKFlag(true);
            }
        }
        else {
            myLog.logMsg(MyLog.ERROR, "handleSendOK] Receive Unknown Mode, Mode = " + cMode);
        }
        
        return;
    }
    
    /** 
    * 1. 기능 :  Timeout  데이타 처리
    * 2. 처리 개요 : 
    *       - TIMOUT 첫번째 데이타일 경우 (DATA_TYPE_TIMEOUT_ONLY) : 
    *         수신데이타를 INBOUND 핸들러로 보낸다.  
    *       - TIMOUT 데이타가 하나이상의 패킷일 경우 : 
    *         Queue에 저장된 키값을 구한다. (key값이 없을 경우 에러로그만 남긴다.)
    *             1. 송신 Queue에 있을 경우 : 수신된 모든 데이타를 INBOUND 핸들러로 보낸다.
    *             2. 송신 Queue에 없을 경우 : 현재 받은 데이타만 INBOUND 핸들러로 보낸다.
    * 3. 주의사항
    **/
    public void handleTimeout(int nLen, byte[] pBuff) {
        String  sKey        = null;
        String  sDataType   = null;
        byte[]  pRecvBuff   = null;
        char    cMode;
              
        WCAHeader wcaHeader = new WCAHeader(WCAHeader.WCA_HEADER_LEN, pBuff);
        
        sDataType = wcaHeader.getDataType();
        myLog.logMsg(MyLog.DEBUG, "handleTimeout] Data Type = " + sDataType);     
        cMode = wcaHeader.getMode();        
        myLog.logMsg(MyLog.DEBUG, "handleTimeout] Tran Mode = " + cMode);
        
        if (sDataType.equals(WCAHeader.DATA_TYPE_TIMEOUT_ONLY)){
            pRecvBuff = pBuff;
        }
        else {   
            sKey = getKey(wcaHeader);          
            if (sKey == null) {
                myLog.logMsg(MyLog.ERROR, "handleTimeout] Key is NULL" );
                if (adapter_logger.isError()) { adapter_logger.error("handleTimeout] - " + WCAException.displayError(WCAException.E_RECVDATA_ERROR, toString(), "Key is NULL")); }
                return;
            }
            
            WCAQueue queueWSA = null;
            synchronized (hmapRcvData) {
                queueWSA = (WCAQueue)hmapRcvData.remove(sKey);
            }
            
            if (queueWSA != null) {
                synchronized (queueWSA) {
                    WCAQueueData qdataWSA = new WCAQueueData(nLen, pBuff);
                    myLog.logMsg(MyLog.DEBUG,"handleTimeout] Add Queue Data, Length = " + nLen);
                    queueWSA.addDataToQueue(qdataWSA);
                }
                pRecvBuff = queueWSA.getDataFromQueue();                
            }
            else {
                myLog.logMsg(MyLog.ERROR, "handleTimeout] - [" + WCAException.E_SOFTLINK_RECV_DATA_NOTFOUND + "] Queue Data doesn't existed....");                
                if (adapter_logger.isError()) { adapter_logger.error("handleTimeout] - [" + WCAException.E_SOFTLINK_RECV_DATA_NOTFOUND + "] " + toString() + " Queue Data doesn't existed...."); }
                return;
            }
        }         
        
        if (wcaEventHandler != null) {
            myLog.logMsg(MyLog.DEBUG, "handleTimeout] Call Event Handler : handleTimeoutData()");
            wcaEventHandler.handleTimeoutData(sAdapterGroupName, sAdapterName, sGroupCode,  pRecvBuff.length, pRecvBuff);
        }
        else {
            myLog.logMsg(MyLog.ERROR, "handleTimeout] Event Handler is Not Registered, So Discard Data...");
        }
    }
    
    /** 
    * 1. 기능 :  수신 데이타를 Queue에 저장한다. 
    * 2. 처리 개요 : 
    *       - DATA TYPE이 FIRST, MIDDEL인 경우 Queue에 저장될 key 값을 구하여 Queue 값을 구한다.
    *       - Queue에 수신데이타 저장 값이 있을 경우 Queue 데이타를 추가하며,
    *                                   없을 경우 Queue를 생성하여 Queue 데이타를 추가한다.
    *       - Respnse 모드이고 timeout 데이타인 경우 setResTimeoutFlag(true)을 설정해준다.
    *         (차후 INBOUND 핸들러로 보내기 위함)
    * 3. 주의사항
    *     
    **/
    public void handleData(int nLen, byte[] pBuff) {
        String  sKey = null;
        
        WCAHeader wcaHeader = new WCAHeader(WCAHeader.WCA_HEADER_LEN, pBuff);
        
        sKey = getKey(wcaHeader);          
        if (sKey == null) {
            myLog.logMsg(MyLog.ERROR, "handleData] - "+ WCAException.displayError(WCAException.E_RECVDATA_ERROR, toString(), "Key is NULL"));
            if (adapter_logger.isError()) { adapter_logger.error("handleData] - "+ WCAException.displayError(WCAException.E_RECVDATA_ERROR, toString(), "Key is NULL")); }        
            return;
        }
        
        WCAQueue queueWSA = null;
        synchronized (hmapRcvData) {
            queueWSA = (WCAQueue)hmapRcvData.get(sKey);
        }
        
        // Status가 DATA_TYPE_TIMEOUT_FIRST인 경우 LUNAME으로 Hashmap에 저장한다.
        String 	sDataType = wcaHeader.getDataType();        
        char	cMode = wcaHeader.getMode();        
        myLog.logMsg(MyLog.DEBUG, "handleData] Data Type = " + sDataType + ", Mode = " + cMode);
        
        if (queueWSA == null) {
            queueWSA = new WCAQueue();
            registerRcvQueue(sKey, queueWSA);
                            
            // DFS가 아니고 response 모드인 경우 -> 타임아웃데이타            
            if ((String.valueOf(cMode).equals(WCAHeader.TRAN_MODE_RESPONSE) || String.valueOf(cMode).equals(WCAHeader.TRAN_MODE_TERMINAL)) && 
            		!sDataType.substring(0,1).equals(WCAHeader.DATA_TYPE_DFS_PREFIX)) {
                queueWSA.setResTimeoutFlag(true);
            }
        }
                 
        if (queueWSA != null) {
            synchronized (queueWSA) {
                WCAQueueData qdataWSA = new WCAQueueData(nLen, pBuff);
                myLog.logMsg(MyLog.DEBUG,"handleData] Add Queue Data, Length = " + nLen);
                queueWSA.addDataToQueue(qdataWSA);
            }
        }
        return;
    }
    
   /** 
    * 1. 기능 :  Normal Data 및 DFS 데이타를 처리한다.
    * 2. 처리 개요 : 
    *       - NONRESPONSE 모드 : 
    *            1. Queue에 있을 경우 : 수신된 모든 데이타를 INBOUND 핸들러로 보낸다.
    *            2. Queue에 없을 경우 : 수신된 현재 데이타를 INBOUND 핸들러로 보낸다.
    *      - RESPONSE 모드 :  
    *            1. Queue에 있을 경우 : RESPONSE 모드이고 TIMEOUT 데이타이면 INBOUND 핸들러로 보낸다.
    *                                 그외의 경우 데이타 수신했음을 알려준다. (notify)
    *            2. Queue에 없을 경우 : timeout 데이타로 INBOUND 핸들러로 보낸다.
    * 3. 주의사항
    *     
    **/
    public void handleNormalDFS(int nLen, byte[] pBuff) {  
        String  sKey = null;
        char	cMode;
        byte[] 	pRecvBuff = null;
        
        WCAHeader wcaHeader = new WCAHeader(WCAHeader.WCA_HEADER_LEN, pBuff);        
        
        sKey = getKey(wcaHeader);
        if (sKey == null) {
            myLog.logMsg(MyLog.ERROR, "handleNormalDFS] - " + WCAException.displayError(WCAException.E_RECVDATA_ERROR, toString(), "Key is NULL"));
            if (adapter_logger.isError()) { adapter_logger.error("handleNormalDFS] - " + WCAException.displayError(WCAException.E_RECVDATA_ERROR, toString(), "Key is NULL")); }
            return;
        }
        
        cMode = wcaHeader.getMode();        
        myLog.logMsg(MyLog.DEBUG, "handleNormalDFS] Tran Mode = " + cMode);
        
        if (cMode == (WCAHeader.TRAN_MODE_NONRESPONSE).charAt(0)) {
            WCAQueue queueWSA = null;
            synchronized(hmapRcvData) {
                queueWSA = (WCAQueue)hmapRcvData.remove(sKey);
            }
            
            if (queueWSA != null) {
                synchronized (queueWSA) {
                    WCAQueueData qdataWSA = new WCAQueueData(nLen, pBuff);
                    myLog.logMsg(MyLog.DEBUG,"handleNormalDFS] Add Queue Data, Length = " + nLen);
                    queueWSA.addDataToQueue(qdataWSA);				    
                }
                pRecvBuff = queueWSA.getDataFromQueue();
            }
            else {
                pRecvBuff = pBuff;
                myLog.logMsg(MyLog.DEBUG, "handleNormalDFS] - [" + WCAException.E_SOFTLINK_RECV_DATA_NOTFOUND +"] Queue Data doesn't existed....");
            }
        
            if (wcaEventHandler != null) {
                myLog.logMsg(MyLog.DEBUG, "handleNormalDFS] Call Event Handler : handleNonRspData()");                
                wcaEventHandler.handleNonRspData(sAdapterGroupName, sAdapterName, sGroupCode, pRecvBuff.length, pRecvBuff);
            }
            else {
                myLog.logMsg(MyLog.ERROR, "handleNormalDFS] Event Handler is Not Registered, So Discard Data...");
            }
        }
        else if (cMode == (WCAHeader.TRAN_MODE_RESPONSE).charAt(0) || cMode == (WCAHeader.TRAN_MODE_TERMINAL).charAt(0)) {            
            WCAQueue queueWSA = null;
            synchronized (hmapRcvData) {
                queueWSA = (WCAQueue)hmapRcvData.remove(sKey);
            }            
            
            if (queueWSA != null) {                
                synchronized (queueWSA) {
                    WCAQueueData qdataWSA = new WCAQueueData(nLen, pBuff);
                    myLog.logMsg(MyLog.DEBUG,"handleNormalDFS] Add Queue Data, Length = " + nLen);
                    queueWSA.addDataToQueue(qdataWSA);	
                    
                    // timeout데이타일 경우 
                    if (queueWSA.getResTimeoutFlag()) {
                        pRecvBuff = queueWSA.getDataFromQueue();
                        if (wcaEventHandler != null) {
                            myLog.logMsg(MyLog.DEBUG, "handleNormalDFS] Call Event Handler : handleTimeoutData()");
                            wcaEventHandler.handleTimeoutData(sAdapterGroupName, sAdapterName, sGroupCode, pRecvBuff.length, pRecvBuff);
                        }
                        else {
                            myLog.logMsg(MyLog.ERROR, "handleNormalDFS] Event Handler is Not Registered, So Discard Data...");
                        }
                    }
                    else {
                        myLog.logMsg(MyLog.DEBUG, "handleNormalDFS] Notify WLI Thread...");
                        
                        // 20200724 Start
                        if (adapter_logger.isInfo()) { adapter_logger.debug("Before Notify WLI Thread, " + sAdapterGroupName + ", " + sAdapterName + ", " + sGroupCode); }
                        // 20200724 End
                        
                        queueWSA.notifyAll();
                        
                        // 20200724 Start
                        if (adapter_logger.isInfo()) { adapter_logger.debug("After Notify WLI Thread, " + sAdapterGroupName + ", " + sAdapterName + ", " + sGroupCode); }
                        // 20200724 End
                        
                        lNotifyEndTime = System.currentTimeMillis();
                        String dateString = formatter.format(ZonedDateTime.now());
                        
                        // 20200724 Start
                        // 로그 레벨 변경
                        // if (adapter_logger.isDebug()) { adapter_logger.debug(dateString + " : WCA_PROCESSIING_TIME = " + (lNotifyEndTime - lReadEndTime)); }
                        if (adapter_logger.isInfo()) { adapter_logger.info(dateString + " : WCA_PROCESSIING_TIME = " + (lNotifyEndTime - lReadEndTime)); }
                        // 20200724 End 
                    }
                }
            }
            else {
                if (wcaEventHandler != null) {
                    myLog.logMsg(MyLog.DEBUG, "handleNormalDFS] Call Event Handler : handleTimeoutData()");                    
                    wcaEventHandler.handleTimeoutData(sAdapterGroupName, sAdapterName, sGroupCode, nLen, pBuff);
                }
                else {
                    myLog.logMsg(MyLog.ERROR, "handleNormalDFS] Event Handler is Not Registered, So Discard Data...");
                }
            }
        }
        else {
            myLog.logMsg(MyLog.ERROR, "handleNormalDFS] Receive Unknown Mode, Mode = " + cMode);
            adapter_logger.error("handleNormalDFS] - " + WCAException.displayError(WCAException.E_SOFTLINK_UNKNOWN_COMMAND, toString(), "Unknown Mode =  " + cMode) );
        }
    }
    
    /** 
    * 1. 기능 :  ERROR Data 및 DFS 데이타를 처리한다.
    * 2. 처리 개요 : 
    *       - Queue에 있을 경우 : 
    *            1. "LU사용중"이라는 에러코드인 경우 setResendFlag(true)로 설정한다.
    *               (차후 다른 LU 그룹으로 Resend 하기 위함)
    *            2. 그외의 경우 :  그외의 경우 데이타 수신했음을 알려준다. (notify)
    *       - Queue에 없을 경우 : 수신된 데이타를 INBOUND 핸들러로 보낸다.
    * 3. 주의사항
    *     
    **/
    public void handleError(int nLen, byte[] pBuff) {       
        String  sKey        = null;
        byte[]  bErrCode    = new byte[WCAException.SOFTLINK_ERROR_SIZE];
        byte[]  bErrMsg     = null;
        
        WCAHeader wcaHeader = new WCAHeader(WCAHeader.WCA_HEADER_LEN, pBuff);        
        
        if (nLen >= (WCAHeader.WCA_HEADER_LEN + WCAException.SOFTLINK_ERROR_SIZE)) {
            System.arraycopy(pBuff, WCAHeader.WCA_HEADER_LEN, bErrCode, 0, WCAException.SOFTLINK_ERROR_SIZE);
            String sErrCode = new String(bErrCode);
            myLog.logMsg(MyLog.DEBUG, "handleError] ErrCode = " + sErrCode);
        }
        
        if (nLen > (WCAHeader.WCA_HEADER_LEN + WCAException.SOFTLINK_ERROR_SIZE)) {
            bErrMsg = new byte[nLen - WCAHeader.WCA_HEADER_LEN - WCAException.SOFTLINK_ERROR_SIZE];
            System.arraycopy(pBuff, WCAHeader.WCA_HEADER_LEN + WCAException.SOFTLINK_ERROR_SIZE, bErrMsg, 0, nLen - WCAHeader.WCA_HEADER_LEN - WCAException.SOFTLINK_ERROR_SIZE);
            String sErrMsg = new String(bErrMsg);
            myLog.logMsg(MyLog.DEBUG, "handleError] ErrMsg = " + sErrMsg);
        }

		sKey = getKey(wcaHeader);
        myLog.logMsg(MyLog.DEBUG, "handleError] Key = " + sKey);		        
        
        if (bErrCode != null && bErrMsg != null) {
            String sRecvErrCode =  new String(bErrCode);
            if (adapter_logger.isError()) { adapter_logger.error("[" + sRecvErrCode+"] " + toString() + " " + sKey + " : " + new String(bErrMsg)); }
        }
                
        WCAQueue queueWSA = null;
        synchronized (hmapRcvData) {
            queueWSA = (WCAQueue)hmapRcvData.remove(sKey);
        }
        
        if (queueWSA != null) {
			// 해당 LU 세션이 사용중, 다운일 경우 다른 LU 세션으로 보낸다.		
			if (new String(bErrCode).equals(WCAException.E_HOST_LU_DOWN) || new String(bErrCode).equals(WCAException.E_HOST_LU_IN_USED)
					|| new String(bErrCode).equals(WCAException.E_HOST_LU_NON_EXIST)) {
				queueWSA.setResendFlag(true);
			}

            synchronized (queueWSA) {	        
                WCAQueueData qdataWSA = new WCAQueueData(nLen, pBuff);
                myLog.logMsg(MyLog.DEBUG,"handleError] Add Queue Data, Length = " + nLen);		            
                queueWSA.addDataToQueue(qdataWSA);	
                myLog.logMsg(MyLog.DEBUG, "handleError] Notify WLI Thread...");
                queueWSA.notifyAll();
                lNotifyEndTime = System.currentTimeMillis();
                String dateString = formatter.format(ZonedDateTime.now());                 
                if (adapter_logger.isDebug())  adapter_logger.debug(dateString + " : WCA_PROCESSIING_TIME = " + (lNotifyEndTime - lReadEndTime));
            }
        }
        else {      
            /*      
            if (wcaEventHandler != null) {
                myLog.logMsg(MyLog.DEBUG, "handleError] Call Event Handler : handleTimeoutData()");
                wcaEventHandler.handleTimeoutData(sAdapterGroupName, sAdapterName, nLen, pBuff);
            }
            else {
                myLog.logMsg(MyLog.ERROR, "handleError] Event Handler is Not Registered, So Discard Data...");
            }
            */
            myLog.logMsg(MyLog.DEBUG, "handleError] Doesnt' Exist Data in Queue!!");
        }       
    }
    
   /** 
    * 1. 기능 :  수동 백업 및 복구 명령을 처리한다.
    * 2. 처리 개요 : 
    *       - DATA_TYPE_ADAPTER_ERROR 인 경우 : 해당 어댑터 그룹, LU 그룹을 백업모드로 전환한다.
    *       - DATA_TYPE_ADAPTER_OK 인 경우    : 해당 어댑터 그룹, LU 그룹을 백업모드에서 복귀한다.
    * 3. 주의사항
    *     
    **/
    public void handleAutoBackup(int nLen, byte[] pBuff) {
        ManualBackupCmd cmd = new ManualBackupCmd(ManualBackupCmd.BACKUP_CMD_LEN, pBuff);
        
        String 	sBackupAdapterGrpName = null;
        String 	sBackupLUGrpName = null;
        
        try {
            String sDataType = cmd.getDataType();
            sBackupAdapterGrpName = cmd.getAdapterGrpName();
            sBackupLUGrpName = cmd.getLUGrpName();
            
            // 20090810 Start
            if (bBackupEnableFlag == false) {
                myLog.logMsg(MyLog.NOTICE, "handleAutoBackup] Backup Enable Flag is False, So Ignore 자동 백업 명령 ("  + 
                		sBackupAdapterGrpName + "/" + sNodeName + "/" + sBackupLUGrpName + ")");
                return;
            }
            // 20090810 End
            
            // 수동 백업 전환
            if (sDataType.equals(ManualBackupCmd.DATA_TYPE_ADAPTER_ERROR)) {
                myLog.logMsg(MyLog.NOTICE, "handleAutoBackup] 자동 백업 명령 수신 ("  + sBackupAdapterGrpName + "/" + sNodeName + "/" + sBackupLUGrpName + ")");
                adapter_logger.warn("handleAutoBackup] - " + toString() + " 자동 백업 명령 수신  "  + sBackupAdapterGrpName + "/" + sBackupLUGrpName);
                
                if (bBackupEnableFlag == true) {                
                	if (isBackupSessionPool()) { return; }
                	snaAdapter.setAutoBackupMode(sNodeName, sBackupLUGrpName, true);
                }
                else {
                    myLog.logMsg(MyLog.NOTICE, "handleAutoBackup] bBackupEnableFlag is False, So Ignore Data");
                    adapter_logger.warn("handleAutoBackup] bBackupEnableFlag is False, So Ignore Data");                	
                }
                // 20090810 End
            }
            else { // 수동 복구 전환
                myLog.logMsg(MyLog.NOTICE, "handleAutoBackup] 자동 복구 명령 수신 (" + sBackupAdapterGrpName + "/" + sBackupLUGrpName + ")");
                adapter_logger.warn("handleAutoBackup] - " + toString() + " 자동 복구 명령 수신  "  + sBackupAdapterGrpName + "/" + sBackupLUGrpName);
                
                if (bBackupEnableFlag == true) {  
                	if (isBackupSessionPool()) { return; }
                	snaAdapter.setAutoBackupMode(sNodeName, sBackupLUGrpName, false);
                }
                else {
                    myLog.logMsg(MyLog.NOTICE, "handleAutoBackup] bBackupEnableFlag is False, So Ignore Data");
                    adapter_logger.warn("handleAutoBackup] bBackupEnableFlag is False, So Ignore Data");                	
                }
                // 20090810 End
            }
        }
        catch (WCAException e) {
            // 해당 세션을 찾지 못하였을 경우
            if (e.getErrorReturnCode().equals(WCAException.E_SOFTLINK_NOT_FOUND_SESSION)) {
                adapter_logger.error(WCAException.displayError(WCAException.E_SOFTLINK_NOT_FOUND_SESSION, "백업 전환/복구 세션 시  발생[" + sBackupAdapterGrpName + "/" + sBackupLUGrpName + "]"));
            }
        }
    }
    
    
    /** 
    * 1. 기능 :  Header 데이타에서 Queue에 저장된 Key 값 추출
    * 2. 처리 개요 : 
    *       - Transaction Key가 없는경우 LU 이름으로 한다.
    * 3. 주의사항
    *     
    **/
    private String getKey(WCAHeader wcaHeader) {
        String sKey = wcaHeader.getKey();
                    
        if (sKey == null || (sKey != null && sKey.equals(new String(WCAHeader.NULL_TRANKEY)))) {
            myLog.logMsg(MyLog.DEBUG, "getKey] Key is null or empty, So LuName is Key " );
            sKey = wcaHeader.getLUName().trim();
        }
        myLog.logMsg(MyLog.DEBUG, "getKey] Key = (" + sKey + ")");
        
        if (sKey == null || (sKey != null && sKey.equals(new String(WCAHeader.NULL_TSNAME)))) {
            myLog.logMsg(MyLog.DEBUG, "getKey] Key, LuName is null or empty" );    
            return null;
        }
        
        return sKey;
    }
    
    /**
     * 1. 기능 : SoftLink 세션을 통해서 데이터를 수신한다.
     * 2. 처리 개요 : 
     * 		- SoftLink 세션을 통해서 파라미터인 nDataLen 값 만큼 데이터를 수신한다.
     * 		- 정해진 데이터를 nTimeout시간안에 수신하지 못하면 장애처리 한다.
     * 3. 주의사항
     * 		- 이 함수를 호출하기 전에 먼저 데이터 길이 필드를 수신한 해야 한다.
     * 
     * @param   nTimeout 데이터 수신 Timeout
     * @param   nDataLen 수신할 데이터 길이   
     * @return  pBuff 수신된 데이터가 저장된 버퍼
     */
    public byte[] recvDataField(int nTimeout, int nDataLen) {
        byte[] 	pBuff = null;
        int		nRcvLen = 0;
        int		nRemainLen = 0;
        int		nBuffPos = 0;
        long 	lStartTime = 0;
        long 	lCurrTime = 0;
        
        try {
            pBuff = new byte[nDataLen];  
            nRemainLen = nDataLen;
            
            //sockSoftLink.setSoTimeout(1000);
            sockSoftLink.setSoTimeout(0);
            
            lStartTime = new Date().getTime();
            
            myLog.logMsg(MyLog.DEBUG, "[Before Receiving Data] ");            
            while (nRemainLen > 0) {
                nRcvLen = isSoftLink.read(pBuff, nBuffPos, nRemainLen);
                if (nRcvLen < 0) {
                    myLog.logMsg(MyLog.ERROR, "recvDataField] " + WCAException.displayError(WCAException.E_SOFTLINK_NOTCONN, toString()) + " ( nRcvLen = " + nRcvLen + ")");
                    if (adapter_logger.isError()) { adapter_logger.error(WCAException.displayError(WCAException.E_SOFTLINK_NOTCONN, toString())); }
                    return null;
                }
                else {
                    nRemainLen -= nRcvLen;
                    nBuffPos += nRcvLen;
                }
                
                lCurrTime = new Date().getTime();
                if (lCurrTime - lStartTime > (nTimeout * 1000)) {
                    sockSoftLink.setSoTimeout(0);
                    if (adapter_logger.isError()) { adapter_logger.error("recvDataField] - " + toString() + " Recv Timeout, StartTime = " + lStartTime + ", CurrTime = " + lCurrTime); }
                    myLog.logMsg(MyLog.ERROR, "recvDataField] Recv Timeout, StartTime = " + lStartTime + ", CurrTime = " + lCurrTime);
                    return null;
                }
            }
            myLog.logMsg(MyLog.DEBUG, "[After Receiving Data] ");
            sockSoftLink.setSoTimeout(0);
            
            return pBuff;
        }
        catch(Exception e) {
            myLog.logMsg(MyLog.ERROR, "recvDataField] Error :" + e.toString());
            if (adapter_logger.isError()) { adapter_logger.error("recvDataField] " + WCAException.E_ETC + "] " +  toString() + " Error :" + e.toString()); }
            
            try {
                sockSoftLink.setSoTimeout(0);
            }
            catch(SocketException se) {
                myLog.logMsg(MyLog.ERROR, "recvDataField] Error :" + e.toString());
                if (adapter_logger.isError()) { adapter_logger.error("recvDataField] - [" + WCAException.E_ETC + "] " + toString() +  " Error :" + e.toString()); }
            }
            
            return null;
        }
    }    
    
    public byte[] recvLenthField(int nDataLen) throws Exception {
        byte[] 	pBuff = null;
        int		nRcvLen = 0;
        int		nRemainLen = 0;
        int		nBuffPos = 0;
        
        try {
            pBuff = new byte[nDataLen];  
            nRemainLen = nDataLen;
            
            //sockSoftLink.setSoTimeout(1000 * WCADefine.DEFAULT_SOCK_RCV_TIMEOUT);
            sockSoftLink.setSoTimeout(0);
            
            myLog.logMsg(MyLog.DEBUG, "[Before Receiving Data Length] ");            
            while (nRemainLen > 0) {
                nRcvLen = isSoftLink.read(pBuff, nBuffPos, nRemainLen);
                if (nRcvLen < 0) {                     
                    myLog.logMsg(MyLog.ERROR, "recvLenthField] " + WCAException.displayError(WCAException.E_SOFTLINK_NOTCONN, toString()) + " ( nRcvLen = " + nRcvLen + ")");
                    if (adapter_logger.isError()) { adapter_logger.error(WCAException.displayError(WCAException.E_SOFTLINK_NOTCONN, toString())); }
                    return null;
                }
                else {
                    nRemainLen -= nRcvLen;
                    nBuffPos += nRcvLen;
                }                
            }
            
            myLog.logMsg(MyLog.DEBUG, "[After Receiving Data Length] ");
            sockSoftLink.setSoTimeout(0);
            
            return pBuff;
        }
        catch (Exception e) {
            throw e;
        }
    }    

    /**
     * 1. 기능 : SoftLink TCP 세션 상태 설정
     * 2. 처리 개요 : 
     * 3. 주의사항
     * 
     * @param  세션 상태 
     *         0 : 연결안됨, 1 : TCP 세션 연결, 2 : TCP 세션 사용중 (사용안함)
     */
    public void setSessionStat(int nSessStat) {
        this.nSessStat = nSessStat;
    }
    
    /**
     * 1. 기능 : SoftLink TCP 세션 이용 가능 상태 반환
     * 2. 처리 개요 : 
     *      - SESS_STAT_READY 일 경우  true 반환
     * 3. 주의사항
     * 
     * @param  세션 상태 
     *         0 : 연결안됨 (SESS_STAT_NOTCONN), 
     *         1 : TCP 세션 연결 (SESS_STAT_READY), 
     *         2 : TCP 세션 사용중 (SESS_STAT_INUSE - 사용안함)
     */
    public boolean isAvailable() {
        boolean 	bAvailable = false;        
        
        if (nSessStat == WCADefine.SESS_STAT_READY) {
            bAvailable = true;
        }
        else { 
            bAvailable = false;
        }
                       
        return bAvailable;
    }
    
    /** 
    * 1. 기능 :  Softlink와 연결된 세션수를 반환
    * 2. 처리 개요 : 
    * 3. 주의사항 
    * 
    * @return    Softlink와 연결된 세션수
    **/
    public int getConnectedSessNum() {
        int		nSessNum = 0;
        
        if (nSessStat > 0) {
            nSessNum++;
        }
        
        return nSessNum;
    }
    
    /**
     * 1. 기능 : 호스트로 송신한 데이터를 SoftLink 세션을 통해서 호트로 송신한다.
     * 2. 처리 개요 : 
     * 		- 트레이스 파일에 해당 데이터를 트레이스한다.
     * 		- 데이터의 내용을 호스트로 송신하기 위해 SoftLink 세션으로 송신한다.
     * 3. 주의사항
     * 		- 트레이스 처리의 동기화를 위해서 myTrc 클래스에 대한 동기화를 수행한다.
     * 		- 송신 데이터 버퍼 형식 : 길이(4) + Data(n)
     * 
     * @param   nLen 송신할 데이터의 길이
     * @param   pBuff 송신할 데이터가 저장된 버퍼
     * @return  nLen 송신된 데이터 길이
     * @exception WCAException
     */    
    public int sendData(int nLen, byte[] pBuff) throws WCAException {
        byte[] 		pLenBuff = null;
        long 		lStartTime = 0L, lEndTime = 0L;
        WCAHeader 	wcaHeader = null;

        try {
            // 길이에 대한 길이 필드 생성
        	// 20090302 Start
			// pLenBuff = MyUtil.getLenBuff(nLen, WCADefine.LEGTH_FIELD_SIZE);
			pLenBuff = setInt(nLen);
			// 20090302 End
			
            wcaHeader = new WCAHeader(WCAHeader.WCA_HEADER_LEN, pBuff);
            
			// 데이터 트레이스            
            /*
			synchronized(myTrc) {
			    myTrc.logMsg("WCA -> SoftLink (LEN = " + nLen + ")");
			    myTrc.logMsg("WCA Header : " + wcaHeader.toString());
			    myTrc.logData(WCADefine.EBCDIC_CODE, nLen, pBuff);
			} 
            */     
            
			// 길이 필드 및 데이터 송신
            synchronized (osSoftLink) {
                String dateString = formatter.format(ZonedDateTime.now()); 
                
                lStartTime = System.currentTimeMillis();                
                osSoftLink.write(pLenBuff, 0, WCADefine.LEGTH_FIELD_SIZE);                
                osSoftLink.write(pBuff, 0, nLen);                
                osSoftLink.flush();                
                lEndTime = System.currentTimeMillis();
                
                if (adapter_logger.isDebug()) { adapter_logger.debug(dateString + " : WCA_SENDING_TIME = " + (lEndTime - lStartTime) + ", KEY = "+wcaHeader.getKey()); }
            }			
			/*
			synchronized(myTrc) {
				myTrc.logMsg("[After Sending] WCA_SENDING_TIME = " + (lEndTime - lStartTime) + "("+wcaHeader.getKey() + ")");
			    myTrc.logMsg("[After Sending] WCA -> SoftLink : " + wcaHeader.toString());
			}
            */
			nSndCnt++;            
			return nLen;
        }
        catch (IOException ioe) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, ioe);                     
            throw new WCAException(WCAException.E_SOFTLINK_SENDERR, ioe);
        }
        catch (Exception e) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, e);                     
            throw new WCAException(WCAException.E_SOFTLINK_SENDERR, e);
        }
    }
    
    // 20090810 Start
    /**
     * 1. 기능 : WCA 어댑터 APP_CODE 명령 전송
     * 2. 처리 개요 : 
     * 3. 주의사항
     * 
     * @exception WCAException
     */    
    public void sendWcaSessionAppCode(boolean bSendAdapterInfo) throws WCAException
    {
        int 			nLen = 0;
        byte[] 			pBuff = null;
        byte[] 			pLenBuff = null;
        String 			sAppCode = "";
//        String			sSendWcaAdapterInfo = "0";
        StringBuffer	sbAppCode = new StringBuffer();

        // 20190828 Start
        try {        
        	/*
        	sSendWcaAdapterInfo = AdapterPropManager.getInstance().getProperty(sAdapterName, "SEND_WCA_ADAPTER_INFO");
        	if ("1".compareToIgnoreCase(sSendWcaAdapterInfo) == 0) {
        		this.sendWcaAdapterInfo();
        	}
        	*/
        	
        	if (bSendAdapterInfo == true) {
        		this.sendWcaAdapterInfo();
        	}
        }
        catch (Exception e) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, e);                     
        }
        // 20190828 End        
        
        try {
        	// 어댑터 프라퍼티에 등록할 경우
        	sAppCode = AdapterPropManager.getInstance().getProperty(sAdapterName, "APP_CODE");
        	
        	// For Test 
        	// sDataCode = "ABC,DEF,XYZ";
        	
        	// APP_CODE 값이 NULL 이거나 길이가 0이라도 Softlink APP 코드 정보를 클리어 하기 위해 보내야 함.
        	if (sAppCode == null) {
        		myLog.logMsg(MyLog.WARN, "sendWcaSessionAppCode> Data Code is null, So Send Dummy App Code to Softlink");
        		sAppCode = "";
        		sbAppCode.append(sAppCode);
        	}
        	else {
	        	sAppCode = sAppCode.trim();
	        	if (sAppCode.length() < 3) {
	        		myLog.logMsg(MyLog.WARN, "sendWcaSessionAppCode> Data Code[" + sAppCode + "] is Invalid, So Send Dummy App Code to Softlink");
	        		sAppCode = "";
	        		sbAppCode.append(sAppCode);
	        	}        		
	        	else {
	            	// 불필요한 공백 삭제
	                ArrayList alDataCode = MyUtil.stringTokenizer(sAppCode, ",");
	                if (alDataCode != null) {	
		                for (int i = 0; i < alDataCode.size(); i++) {
		                	String sTempDataCode = (String)alDataCode.get(i);
		                	sbAppCode.append(sTempDataCode.trim());
		                	
		                	// 마지막 항목이 아니면 "," 추가
		                	if (i != (alDataCode.size() - 1)) {
		                		sbAppCode.append(",");
		                	}
		                }	        		
	                }
	        	}
        	}
            
            AppCodeCmd cmd = new AppCodeCmd(sbAppCode.toString());
            
            pBuff = cmd.getData();
            nLen = pBuff.length;
            
            // 길이에 대한 길이 필드 생성
            // 20090302 Start
			// pLenBuff = MyUtil.getLenBuff(nLen, WCADefine.LEGTH_FIELD_SIZE);
			pLenBuff = setInt(nLen);
			// 20090302 End
			
			myLog.logMsg(MyLog.WARN, "sendWcaSessionDataCode> App Code = " + sbAppCode.toString());
			myLog.logData(WCADefine.ASCII_CODE, nLen, pBuff);
            
			// 길이 필드 및 데이터 송신
            synchronized (osSoftLink) {
                osSoftLink.write(pLenBuff, 0, WCADefine.LEGTH_FIELD_SIZE);
                osSoftLink.write(pBuff, 0, nLen);
                osSoftLink.flush();
            }
        }
        catch (IOException ioe) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, ioe);
            throw new WCAException(WCAException.E_SOFTLINK_SENDERR, ioe);
        }
        catch (Exception e) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, e);                     
            throw new WCAException(WCAException.E_SOFTLINK_SENDERR, e);
        }
    }
    // 20090810 End
    
    // 20190828 Start
    /**
     * 1. 기능 : 어댑터 정보 명령 전송
     * 2. 처리 개요 : 
     * 3. 주의사항
     * 
     * @exception WCAException
     */    
    public void sendWcaAdapterInfo() throws WCAException {
        int 			nLen = 0;
        byte[] 			pBuff = null;
        byte[] 			pLenBuff = null;
        String 			sAdptInfo = "";
        
        try {
        	sAdptInfo = this.sAdapterName;

        	WCAAdapterInfoCmd cmd = new WCAAdapterInfoCmd(sAdptInfo);
            
            pBuff = cmd.getData();
            nLen = pBuff.length;
            
            // 길이에 대한 길이 필드 생성
            // 20090302 Start
			// pLenBuff = MyUtil.getLenBuff(nLen, WCADefine.LEGTH_FIELD_SIZE);
			pLenBuff = setInt(nLen);
			// 20090302 End
			
			myLog.logMsg(MyLog.WARN, "sendWcaAdapterInfo> Adapter Info = " + sAdptInfo);
			myLog.logData(WCADefine.ASCII_CODE, nLen, pBuff);
            
			// 길이 필드 및 데이터 송신
            synchronized (osSoftLink) {
                osSoftLink.write(pLenBuff, 0, WCADefine.LEGTH_FIELD_SIZE);
                osSoftLink.write(pBuff, 0, nLen);
                osSoftLink.flush();
            }
        }
        catch (IOException ioe) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, ioe);
            throw new WCAException(WCAException.E_SOFTLINK_SENDERR, ioe);
        }
        catch (Exception e) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, e);                     
            throw new WCAException(WCAException.E_SOFTLINK_SENDERR, e);
        }
    }
    // 20190828 End
    
    /**
     * 1. 기능 : 어댑터 상태 명령 전송
     * 2. 처리 개요 : 
     * 3. 주의사항
     * 
     * @exception WCAException
     */    
    public void sendAdapterStatusData() throws WCAException {
        int nLen = 0;
        byte[] pBuff = null;
        byte[] pLenBuff = null;
        
        try {
            // 첫번째 세션만 수행
            if (nSessId != 0) { return; }
            if (this.isBackupSessionPool()) { return; }
            
            ManualBackupCmd cmd = new ManualBackupCmd();
            cmd.setAdapterGrpName(this.sAdapterGroupName);
            cmd.setLUGrpName(this.sGroupCode);
            cmd.setDataType(WCAHeader.DATA_TYPE_GET_ADAPTER_STATUS);
            
            pBuff = cmd.getData();
            nLen = pBuff.length;
            
            // 길이에 대한 길이 필드 생성
            // 20090302 Start
			// pLenBuff = MyUtil.getLenBuff(nLen, WCADefine.LEGTH_FIELD_SIZE);
			pLenBuff = setInt(nLen);
			// 20090302 End
			
			// 데이터 트레이스
            /*			
            WCAHeader wcaHeader = new WCAHeader(WCAHeader.WCA_HEADER_LEN, pBuff);
			synchronized(myTrc) {
			    myTrc.logMsg("WCA -> SoftLink (LEN = " + nLen + ")");
			    myTrc.logMsg("WCA Header : " + wcaHeader.toString());
			    myTrc.logData(WCADefine.EBCDIC_CODE, nLen, pBuff);
			}
            */
            
			// 길이 필드 및 데이터 송신
            synchronized (osSoftLink) {
                osSoftLink.write(pLenBuff, 0, WCADefine.LEGTH_FIELD_SIZE);
                osSoftLink.write(pBuff, 0, nLen);
                osSoftLink.flush();
            }
        }
        catch (IOException ioe) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, ioe);
            throw new WCAException(WCAException.E_SOFTLINK_SENDERR, ioe);
        }
        catch (Exception e) {
        	adapter_logger.error(WCAException.E_SOFTLINK_SENDERR, e);                     
            throw new WCAException(WCAException.E_SOFTLINK_SENDERR, e);
        }
    }
    
    /** 
    * 1. 기능 :  Softlink TCP 세션 close
    * 2. 처리 개요 : 
    * 3. 주의사항
    **/
    public void closeSoftLink() {
        try {
        	try {
        		if (nSessStat != WCADefine.SESS_STAT_NOTCONN) {
        			// 20200825 Start
        			if (isSoftLink != null) {
        				try {
        					isSoftLink.close();
        					isSoftLink = null;
        				}
        				catch (Exception ignore1) {
        					adapter_logger.error("closeSoftLink] - [" + WCAException.E_ETC +"] " + toString() + " isSoftLink.close, Error = " + ignore1.toString());
        				}
        			}
        			
        			if (osSoftLink != null) {
        				try {
        					osSoftLink.close();
        					osSoftLink = null;
        				}
        				catch (Exception ignore2) {
        					adapter_logger.error("closeSoftLink] - [" + WCAException.E_ETC +"] " + toString() + " osSoftLink.close Error = " + ignore2.toString());        					
        				}
        			}        			
        			// 20200825 End
        			
        			sockSoftLink.close();
        		}
        		else { 
        			return;
        		}
        	}
            catch (IOException ioe) {
                myLog.logMsg(MyLog.ERROR, "closeSoftLink] - [" + WCAException.E_ETC + "] Error = " + ioe.toString());
                adapter_logger.error("closeSoftLink] - [" + WCAException.E_ETC +"] " + toString() + " Error = " + ioe.toString());            
            }
            
            // 어댑터 상태 체크                    
            myLog.logMsg(MyLog.DEBUG, "setAdapterStatus] setting");
            setAdapterStatus(true);
            myLog.logMsg(MyLog.DEBUG, "setAdapterStatus] ending");
            
            // 미처리 트랜잭션 출력
            traceRemainTransaction();
            
            // 백업 전환
            checkBackupConversion();
        }
        finally {
            myLog.logMsg(MyLog.INFO, "socketClose] SessStat = SESS_STAT_NOTCONN");
            nSessStat = WCADefine.SESS_STAT_NOTCONN;
        }
    }
    
   /** 
    * 1. 기능 :  자동 백업 전환
    * 2. 처리 개요 : 
    * 3. 주의사항
    **/
    private void checkBackupConversion() {
        try {
            if (bRunning && !isBackupSessionPool()) {
            	// 20091005 Start
            	// 백업 Enable Flag가 false 이면 백업 전환 처리를 하지 않는다.
                if (bBackupEnableFlag == false) {
                	myLog.logMsg(MyLog.WARN, "checkBackupConversion] - bBackupEnableFlag is " + bBackupEnableFlag);
                	return;
                }
                // 20091005 End
                
                // 20091005 Start
                /*
                // 백업 모드 이면 return
                if (snaAdapter.getBackupMode(sNodeName, sGroupCode)) return;
                */
                // 20091005 End
                    
                myLog.logMsg(MyLog.DEBUG, "setWCAAutoBackup] starting ");
                int nConnectedSessNum = snaAdapter.getConnectedNormalSessNum(sNodeName, sGroupCode);
                myLog.logMsg(MyLog.DEBUG, "setWCAAutoBackup] nConnectedSessNum = " + nConnectedSessNum);
                if (nConnectedSessNum == 0) {
                    snaAdapter.setWCAAutoBackup(sNodeName, sGroupCode);
                    setAdapterStatus(true);
                }
                
                myLog.logMsg(MyLog.DEBUG, "setWCAAutoBackup] ending ");
            }
        } 
        catch (WCAException e) {
            myLog.logMsg(MyLog.ERROR, "closeSoftLink] - [" + e.getErrorReturnCode() + "] Error = " + e.toString());
            adapter_logger.error("closeSoftLink] - [" + e.getErrorReturnCode() +"] " + toString() + " Error = " + e.toString());                        
        }
    }
    
    /** 
    * 1. 기능 :  Softlink input/output stream close
    * 2. 처리 개요 : 
    * 3. 주의사항
    **/
    public void closeLogStream() {
    	// 20200825 Start
        // myLog.closeLogStream();
        // myTrc.closeLogStream();
        
        if (myLog != null) { myLog.closeLogStream(); }
        if (myTrc != null) { myTrc.closeLogStream(); }
    	// 20200825 End
    }
    
	int setInt(byte[] buf, int val, int idx) {
		buf[idx++] = (byte)((val & 0xff000000) / 0x1000000);		
		buf[idx++] = (byte)((val & 0x00ff0000) / 0x10000);
		buf[idx++] = (byte)((val & 0x0000ff00) / 0x100);
		buf[idx++] = (byte)(val & 0x000000ff);

		return 4;
	}
	
	byte[] setInt(int val) {
		byte[] 	buf = new byte[4];
		int		idx = 0;
		
		buf[idx++] = (byte)((val & 0xff000000) / 0x1000000);		
		buf[idx++] = (byte)((val & 0x00ff0000) / 0x10000);
		buf[idx++] = (byte)((val & 0x0000ff00) / 0x100);
		buf[idx++] = (byte)(val & 0x000000ff);

		return buf;
	}	

	int setInt2(byte[] buf, int val, int idx) {
		buf[idx++] = (byte)(val & 0x000000ff);
		buf[idx++] = (byte)((val & 0x0000ff00) / 0x100);
		buf[idx++] = (byte)((val & 0x00ff0000) / 0x10000);		
		buf[idx++] = (byte)((val & 0xff000000) / 0x1000000);
		
		return 4;
	}
	
	static int getInt(byte[] buf, int idx) {
		int b1 = 0, b2 = 0, b3 = 0, b4 = 0;

		b1 = (buf [idx] >= 0) ? buf[idx] : 0x100 + buf[idx];
		b2 = (buf [idx + 1] >= 0) ? buf[idx + 1] : 0x100 + buf[idx + 1];
		b3 = (buf [idx + 2] >= 0) ? buf[idx + 2] : 0x100 + buf[idx + 2];
		b4 = (buf [idx + 3] >= 0) ? buf[idx + 3] : 0x100 + buf[idx + 3];

		return b1 * 0x1000000 + b2 * 0x10000 + b3 * 0x100 + b4;
	}  
}