/*******************************************************************************
 *  Copyright 2017-2018 Centre for Development of Advanced Computing(C-DAC), Pune
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package cdac.medinfo.sdk.dcm30_04.services.cservices.tools.scp;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;


import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.enums.EnumCommandField;
import cdac.medinfo.sdk.dcm30_04.enums.EnumLoggingLevel;
import cdac.medinfo.sdk.dcm30_04.enums.EnumPDUType;

import cdac.medinfo.sdk.dcm30_04.services.association.AReleaseRQorRP;
import cdac.medinfo.sdk.dcm30_04.services.association.AgreedAssociation;
import cdac.medinfo.sdk.dcm30_04.services.association.AssociateRQ;
import cdac.medinfo.sdk.dcm30_04.services.association.AssociationNegotiator;
import cdac.medinfo.sdk.dcm30_04.services.association.DefaultAssociation;
import cdac.medinfo.sdk.dcm30_04.services.association.IDIMSEAssociation;
import cdac.medinfo.sdk.dcm30_04.services.association.RQUserInfo;
import cdac.medinfo.sdk.dcm30_04.services.association.SOPClassExtendedNegotiationSubItemFields;
import cdac.medinfo.sdk.dcm30_04.services.cecho.scp.CEchoSCP;
import cdac.medinfo.sdk.dcm30_04.services.cfind.scp.CFindSCP;
import cdac.medinfo.sdk.dcm30_04.services.cget.scp.CGetSCP;
import cdac.medinfo.sdk.dcm30_04.services.cmove.commons.CMoveRQ;
import cdac.medinfo.sdk.dcm30_04.services.cmove.scp.CMoveSCP;
import cdac.medinfo.sdk.dcm30_04.services.cstore.scp.CStoreSCP;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumDataSink;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumDataSource;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumLevelofDigitalSignatureSupport;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.EnumServiceType;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DIMSEResponse;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.DicomMessage;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.IDataSink;
import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.IDataSource;
import cdac.medinfo.sdk.dcm30_04.services.dsp.DSPVerifier;
import cdac.medinfo.sdk.dcm30_04.services.javadbhelper.DataSinkImpl;
import cdac.medinfo.sdk.dcm30_04.services.javadbhelper.DataSourceImpl;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.ClientSession;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.ProcessHandler;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.SCPSession;
import cdac.medinfo.sdk.dcm30_04.services.tls.commons.TLSServerConfig;
import cdac.medinfo.sdk.dcm30_04.services.tlsscp.commons.TLSSCPSession;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.IPairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.PairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;

/**
 * This class acts as main class for Utility tools of DCIOM.
 * One the desired parameters are provided SCP is started to process the requests.
 */
public class CService_SCP
{
	static int lengthOfOption = 18;
	static int lengthOfDetails = 79 - lengthOfOption;

	public static void main(String[] args)
	{
		EnumLoggingLevel enumLoggingLevel = EnumLoggingLevel.INFO;
		String strLoggingPath = null;
		
		String strAETitle = null;
		String strDestinationPath = null;
		int iPort = 0;
		String strDBConnectionString = null;
		CService_SCP objSTools = new CService_SCP();
		String strIP = null;
		ISingleCollection<String> objMoveInformation = new SingleCollection<String>();
		int iTimeout=0;
		String strextSOPClassUID = null; // for DSP Security profile support

		// attribute required for TLS support
		boolean isClientAuthNeeded = false;
		String strKeyStorePath = null;
		String strKeyStorePassword = null;
		ISingleCollection<String> strTrustedCertPathCollection = new SingleCollection<String>();



		/*
		 * If no parameters are provided then display the help.
		 */
		String strIniFile = null;
		String strKey = null;


		if(args == null || args.length == 0)
		{
			objSTools.displayHelp();
			System.exit(0);
		}

		/*
		 * This loop initializes the service parameters from the arguments provided.
		 */
		for(int i=0; i<args.length; i++)
		{
			//logging level
			if(args[i].equalsIgnoreCase("-l") && args.length > i+1)
			{
				String strLoggingLevel = args[i+1];
				if(strLoggingLevel.equalsIgnoreCase("i"))
				{
					enumLoggingLevel = EnumLoggingLevel.INFO;
				}
				else if(strLoggingLevel.equalsIgnoreCase("s"))
				{
					enumLoggingLevel = EnumLoggingLevel.SEVERE;
				}
				else
				{
					System.out.println("Not a Valid Command. The value of logging mode can be INFO(I) or SEVER(S)");
					objSTools.displayHelp();
					System.exit(0);
				}
				i++;
			}

			//logging path.
			else if(args[i].equalsIgnoreCase("-d") && args.length > i+1)
			{
				strLoggingPath = args[i+1];
				i++;
			}

			//Validation Mode
			else if(args[i].equalsIgnoreCase("-v") && args.length > i+1)
			{
				
				i++;
			}

			//calling AE Title
			else if(args[i].equalsIgnoreCase("-h") && args.length > i+1)
			{
				strAETitle = args[i+1] ;
				if(strAETitle == null)
				{
					System.out.println("Not a Valid Command. AE Title name is not defined.");
					objSTools.displayHelp();
					System.exit(0);
				}
				i++;
			}

			//Port
			else if(args[i].equalsIgnoreCase("-p") && args.length > i+1)
			{
				String strPort = args[i+1];
				iPort = Integer.parseInt(strPort);
				if(iPort == 0)
				{
					System.out.println("Not a valid command. Define proper port number");
					objSTools.displayHelp();
					System.exit(0);
				}
				i++;
			}

			//Destination Path
			else if(args[i].equalsIgnoreCase("-s") && args.length > i+1)
			{
				strDestinationPath = args[i+1] ;
				i++;
			}

			//DataBasePath
			else if(args[i].equalsIgnoreCase("-c") && args.length > i+1)
			{
				strDBConnectionString = args[i+1] ;
				i++;
			}


			//IP
			else if(args[i].equalsIgnoreCase("-i") && args.length > i+1)
			{
				strIP = args[i+1] ;
				i++;
			}

			//Move TO information
			else if(args[i].equalsIgnoreCase("-m") && args.length > i+1)
			{
				String strMoveInformation = args[i+1] ;
				if(strMoveInformation.indexOf(":") == -1)
				{
					System.out.println("Not a valid command");
					objSTools.displayHelp();
					System.exit(0);
				}
				else
				{
					objMoveInformation.add(strMoveInformation);
				}
				i++;
			}
			else if (args[i].trim().equalsIgnoreCase("-z") && args.length > i + 1)
			{
				String temp = args[i + 1].trim();
				String[] valueArray = temp.split("#");
				if (valueArray.length != 2)
				{
					System.out.println("Not a valid command");
					objSTools.displayHelp();
					System.exit(0);
				}
				strIniFile = valueArray[0];
				strKey = valueArray[1];
				i++;
			}

			//			Timeout
			else if(args[i].equalsIgnoreCase("-t") && args.length > i+1)
			{
				String strTimeout = args[i+1];
				iTimeout = Integer.parseInt(strTimeout);
				i++;
			}
			else if(args[i].equalsIgnoreCase("-e") && args.length > i+1)
			{
				strextSOPClassUID = args[i+1];
				i++;
			}
			//TLS
			else if(args[i].equalsIgnoreCase("-n") && args.length > i+1)
			{
				if(args[i+1].length() == 1)
				{
					if(args[i+1].charAt(0) == 't' || args[i+1].charAt(0) == 'T')
						isClientAuthNeeded = true;
					else if(args[i+1].charAt(0) == 'f' || args[i+1].charAt(0) == 'F')
						isClientAuthNeeded = false;
					else
					{
						System.out.println("Not a valid command, Define properly client authentication needed(T:true F:false)");
						objSTools.displayHelp();
						System.exit(0);
					}					  
				}
				else
				{
					System.out.println("Not a valid command, Define properly client authentication needed(T:true F:false)");
					objSTools.displayHelp();
					System.exit(0);
				}
				i++;
			}

			//KeyStorePath
			else if(args[i].equalsIgnoreCase("-k") && args.length > i+1)
			{
				strKeyStorePath = args[i+1] ;
				i++;
			}
			//KeyStore Password
			else if(args[i].equalsIgnoreCase("-q") && args.length > i+1)
			{
				strKeyStorePassword = args[i+1] ;
				i++;
			}
			//trusted certificate path
			else if(args[i].equalsIgnoreCase("-r") && args.length > i+1)
			{					
				strTrustedCertPathCollection.add(args[i+1]);
				i++;
			}

			else
			{
				System.out.println("Not a valid command");
				objSTools.displayHelp();
				System.exit(0);
			}
		}

		if(strAETitle == null || iPort == 0)
		{
			System.out.println("Not a valid command");
			objSTools.displayHelp();
			System.exit(0);
		}
		if(strDBConnectionString == null)
		{
			System.out.println("DataBase Path is not given. Query and retrieve requests will not be processed");
		}
		if(strKeyStorePath != null)
		{
			if(strTrustedCertPathCollection.size() == 0)
			{
				System.out.println("No trusted certificate provided.");
				objSTools.displayHelp();
				System.exit(0);
			}
		}
		if(strTrustedCertPathCollection.size()> 0)
		{
			if(strKeyStorePath == null)
			{
				System.out.println("Keystore path is not provided.");
				objSTools.displayHelp();
				System.exit(0);
			}
		}

		SCPHandler objSCPStater = new SCPHandler();
		objSCPStater.startSCP(strIP,enumLoggingLevel,strLoggingPath,strAETitle,iPort,strDestinationPath,strDBConnectionString,objMoveInformation, iTimeout,strextSOPClassUID
				,isClientAuthNeeded,strKeyStorePath,strKeyStorePassword,strTrustedCertPathCollection,strIniFile, strKey);
	}

	/*
	 * This function displays the help.
	 */
	private void displayHelp()
	{
		//Description of command
		System.out.println("\nDICOMSCP :: Process all C-Service request specified by DICOM.");

		//Signature
		System.out.println("\n" + "DICOMSCP [-i IP] -p Port [-d LogDir] " +
				"[-l LogMode] \n [-s StorageDir] [-c DBConString] -h AETitle [{-m AETilte@IP:Port}] [-t TimeOut]" +
		"[-n IsClientAuthReq] [-k Keystore] [-q keystorepass]  [-r TrustCert] \n [-e SOPClassUID] \n");

		//IP Address
		printToConsole("-i", "IP Address, default IP address is localhost.");

		//Port
		printToConsole("-p", "Port");

		//Log directory
		printToConsole("-d", "Log directory");
		printToConsole("", "User's temp directory is taken as default.");

		// Log Mode
		printToConsole("-l","Log mode");
		String [] arrLogmode = {"I INFO","S SEVER"};
		printToConsole("LogMode", arrLogmode);
		printToConsole("","Default Logging Mode is INFO.");

		//Storage directory
		printToConsole("-s","Storage directory of DICOM files.");
		printToConsole("", "User's temp directory is taken as default.");

		//Data base connection string
		printToConsole("-c","Data base connection string.");

		//Calling(host) AE Title
		printToConsole("-h","Calling(host) AE Title.");

		//Move destination title
		printToConsole("-m","Move destination title.");
		printToConsole("","For eg. - SDK@localhost:9002");

		//Session time out
		printToConsole("-t","Session time out in milliseconds");

		printToConsole("-e","SOP Class UID for which extended negotiation is supported");	

        printToConsole("-n", "is client authentication required.");
        String[] arrCertReq = { "T TRUE", "F FALSE" };
        printToConsole("CertReq", arrCertReq);
        printToConsole("", "Default is FALSE.");
        
        
		printToConsole("-k","path to Keystore for SCP(JKS)");

		printToConsole("-q","password to open Keystore");

		printToConsole("-r","path to trusted certificate (multiple trusted certificates can be added using -r option multiple times)");

        
        System.out.println("\nExample :\n\nThese examples are demonstration of simple C-Store Operation.");
        
		//Example
		System.out.println("\n1. DICOMSCP -p 9000 -c jdbc:derby://localhost:5050/C:/Database/SDKdb -h SDK" +
				"\nStarts server at localhost listening on port 9000, with SDK as AE Title and\n" +
				"jdbc:derby://localhost:5050/C:/Database/SDKdb data base connection string.");
		
		System.out.println("\n2. DICOMSCP -p 9000 -h SDK" +
				"\nStarts server at localhost listening on port 9000, with SDK as AE Title. \nThe server" +
				" process verification and storage request.");
		
		System.out.println("\n3. DICOMSCP -p 9000 -c jdbc:derby://localhost:5050/C:/Database/SDKdb -h SDK \n-m XYZ:9002" +
				"\nStarts server at localhost listening on port 9000, SDK as AE Title \nand " +
				"jdbc:derby://localhost:5050/C:/Database/SDKdb data base connection string.\nStorage server for move" +
				" operation started at localhost listening on port 9002");
		
		System.out.println("\nThis example is demonstration of C-Store operation with Digital Signature \nsupport.");
				
		System.out.println("\n4. DICOMSCP -p 9000 -h SDK -e 1.2.840.10008.5.1.4.1.1.12.1" +
				"\nStarts server at localhost listening on port 9000, with SDK as AE Title. \nThe server" +
				" process verification and storage request for Digitally Signed DataSetwith SOP class UID 1.2.840.10008.5.1.4.1.1.12.1");
		
		System.out.println("\nThis example is demonstration of C-Store operation with TLS support.");
		
		System.out.println("\n5. DICOMSCP -p 4546 -h SDK -n T -k C:\\Serverkeystore  private -r C:\\ClientCert.cer" +
				"\nStarts server at localhost listening on port 4546, with SDK as AE Title."+
				"The \nserver process verification and storage request over secure socket connection.");
        
	}

	/**
	 * Display Help of command on console in proper manner.
	 * @param strOption Option for command.
	 * @param strDetails Description of option.
	 */
	public static void printToConsole(String strOption, String strDetails)
	{
		String strBuffer = "";
		if (strOption != null)
			strBuffer = strBuffer + strOption ;
		while (lengthOfOption > strBuffer.length())
		{
			strBuffer = strBuffer + " ";
		}
		if (strDetails != null)
		{
			if (strDetails.length() < lengthOfDetails || strDetails.length() == lengthOfDetails)
			{
				strBuffer = strBuffer + strDetails;
				strDetails = null;
			}
			else
			{
				strBuffer = strBuffer + strDetails.substring(0, lengthOfDetails);
				strDetails = strDetails.substring(lengthOfDetails, (strDetails.length() ));
			}
		}

		System.out.println(strBuffer);
		if (strDetails == null)
			return;
		else
			printToConsole(null, strDetails);
	}

	/**
	 * Display Help of command on console in proper manner.
	 * @param strAttribute Attribute for option.
	 * @param strDesc Description of attributes.
	 */
	public static void printToConsole(String strAttribute, String[] strDesc)
	{
		if (strDesc.length > 1)
			printToConsole(strAttribute, strDesc[0]);
		for(int i = 1;i<strDesc.length;i++)
			printToConsole(null, strDesc[i]);
	}
}

class SCPHandler 
{
	public void startSCP(String strIP ,EnumLoggingLevel enumLoggingLevel, String strLoggingPath, String strAETitle, int iPort, String strDestinationPath, String strDBConnectionString, ISingleCollection<String> objMoveInformation,int iTimeout,String strextSOPClassUID
			,boolean isClientAuthNeeded,String  strKeyStorePath,String strKeyStorePassword,ISingleCollection<String> strTrustedCertPathCollection,String strIniFile, String strKey)
	{
		try
		{
			DicomConfig objDicomConfig = DicomConfig.createInstance();

			//enable logging,specify the path of the directory where log files are created.
			//User can specify any directory in place of 'c:\DCM_Logs'.
			////If no directory is defined by user to create logs then it is created in User Temp DIR.
			objDicomConfig.enableLogging(enumLoggingLevel, strLoggingPath);
			/*
			 * Creates CStoreSCPHadler.
			 */
			DIMSERQHandler scp = new DIMSERQHandler();
			scp.setStoragePath(strDestinationPath);

			if(strDBConnectionString != null)
			{
				String ConnectionString = strDBConnectionString;
				String driverString = "org.apache.derby.jdbc.ClientDriver";
				IDataSink objIDataSink = new DataSinkImpl(ConnectionString,driverString);
				scp.addDataSink(EnumDataSink.CSERVICES, objIDataSink);
				IDataSource objIDataSource = new DataSourceImpl(ConnectionString,driverString);
				scp.addDataSource(EnumDataSource.CSERVICES, objIDataSource);
			}


			if(objMoveInformation.size()!=0)
			{
				for(int i=0; i<objMoveInformation.size();i++)
				{
					String str = objMoveInformation.get(i);
					if(str.indexOf('@') == -1)
					{
						scp.addSupportedAETitles(str.substring(0,str.indexOf(":")), InetAddress.getLocalHost().getHostAddress(), Integer.parseInt(str.substring(str.indexOf(":")+1,str.length())));
					}
					else
						scp.addSupportedAETitles(str.substring(0,str.indexOf("@")), str.substring(str.indexOf("@")+1, str.indexOf(":")), Integer.parseInt(str.substring(str.indexOf(":")+1,str.length())));
				}
			}

			/*
			 * Created SCP session and set Default association and Process Handler on it.
			 */
			if(strTrustedCertPathCollection.size() >0 && strKeyStorePath != null)
			{
				TLSSCPSession session = new TLSSCPSession();
				if(iTimeout > 0)
					session.setTimeOut(iTimeout);

				/*
				 * Initializes the service types and sets it on SCP Session
				 * EnumService Type configures SCP Session to load corresponding association context and start
				 * corresponding DIMSE Services.
				 */
				EnumServiceType []arrenumServiceType = new EnumServiceType[5];
				arrenumServiceType[0] = EnumServiceType.CSTORE_DEFAULT;
				arrenumServiceType[1] = EnumServiceType.CFIND_DEFAULT;
				arrenumServiceType[2] = EnumServiceType.CECHO;
				arrenumServiceType[3] = EnumServiceType.CMOVE;
				arrenumServiceType[4] = EnumServiceType.CGET;
				session.selectServices(arrenumServiceType);

				if(null != strextSOPClassUID)
				{
					DefaultAssociation objDefaultAssociation = new DefaultAssociation("SDK");
					objDefaultAssociation.loadAssociation(arrenumServiceType);

					// Add Digital Signature information with Association
					RQUserInfo objRQUserInfo = objDefaultAssociation.getRQUserInfo();
					SOPClassExtendedNegotiationSubItemFields objSubITemField = new SOPClassExtendedNegotiationSubItemFields();
					objSubITemField.setSOPClassExtNegotiationSOPclassuid(strextSOPClassUID);			
					objSubITemField.setLevelofDigitalSignatureSupport(EnumLevelofDigitalSignatureSupport.LEVEL2);			

					objRQUserInfo.addSOPClassExtendedNegotiationSubItemField(objSubITemField);

					session.setDefaultAssociation(objDefaultAssociation);			
				}

				//Set Process Handler as the SCP TestCode
				session.setIProcessHandler(scp);


				//Set TLS Configuration
				KeyManagerFactory   mgrFact     =   KeyManagerFactory.getInstance("SunX509");
				KeyStore            serverStore =   KeyStore.getInstance("JKS");		
				serverStore.load(new FileInputStream(strKeyStorePath), strKeyStorePassword.toCharArray());
				mgrFact.init(serverStore,strKeyStorePassword.toCharArray());

				//	set up a trustmanager so Server(SCP) can recognize the client(SCU)
				TrustManagerFactory trustFact   =   TrustManagerFactory.getInstance("SunX509");
				KeyStore            trustStore  =   KeyStore.getInstance("JKS");	

				trustStore.load(null, null);

				// Add multiple certificates in trustStore
				for (String strCertPath : strTrustedCertPathCollection)
				{
					CertificateFactory cf = CertificateFactory.getInstance("X.509");
					FileInputStream fis = new FileInputStream(strCertPath);
					BufferedInputStream bis = new BufferedInputStream(fis);
					X509Certificate objX509Certificate = (X509Certificate)cf.generateCertificate(bis);
					bis.close();

					// Add client certificate to trust store.
					trustStore.setCertificateEntry(strCertPath, objX509Certificate);
				}				
				trustFact.init(trustStore);

				// Initialize TLSServerConfig
				TLSServerConfig objServerConfig = new TLSServerConfig();
				objServerConfig.setNeedClientAuth(isClientAuthNeeded);						
				objServerConfig.setKeyManagers(mgrFact.getKeyManagers());
				objServerConfig.setTrustManagers(trustFact.getTrustManagers());
				objServerConfig.setSecureRandom(SecureRandom.getInstance("SHA1PRNG"));

				System.out.println("Starting TLS SCP");

				if(strIP == null)
					//Start the Session
					session.start(strAETitle,iPort,objServerConfig);
				else
					session.start(strAETitle, strIP, iPort,objServerConfig);
			}
			else
			{

				SCPSession session = new SCPSession();
				if(iTimeout > 0)
					session.setTimeOut(iTimeout);

				/*
				 * Initializes the service types and sets it on SCP Session
				 * EnumService Type configures SCP Session to load corresponding association context and start
				 * corresponding DIMSE Services.
				 */
				EnumServiceType []arrenumServiceType = new EnumServiceType[5];
				arrenumServiceType[0] = EnumServiceType.CSTORE_DEFAULT;
				arrenumServiceType[1] = EnumServiceType.CFIND_DEFAULT;
				arrenumServiceType[2] = EnumServiceType.CECHO;
				arrenumServiceType[3] = EnumServiceType.CMOVE;
				arrenumServiceType[4] = EnumServiceType.CGET;
				session.selectServices(arrenumServiceType);

				if(null != strextSOPClassUID)
				{
					DefaultAssociation objDefaultAssociation = new DefaultAssociation("SDK");
					objDefaultAssociation.loadAssociation(arrenumServiceType);

					// Add Digital Signature information with Association
					RQUserInfo objRQUserInfo = objDefaultAssociation.getRQUserInfo();
					SOPClassExtendedNegotiationSubItemFields objSubITemField = new SOPClassExtendedNegotiationSubItemFields();
					objSubITemField.setSOPClassExtNegotiationSOPclassuid(strextSOPClassUID);			
					objSubITemField.setLevelofDigitalSignatureSupport(EnumLevelofDigitalSignatureSupport.LEVEL2);			

					objRQUserInfo.addSOPClassExtendedNegotiationSubItemField(objSubITemField);

					session.setDefaultAssociation(objDefaultAssociation);			
				}

				//Set Process Handler as the SCP TestCode
				session.setIProcessHandler(scp);

				System.out.println("Starting SCP");

				if(strIP == null)
					//Start the Session
					session.start(strAETitle,iPort);
				else
					session.start(strAETitle, strIP, iPort);
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

class DIMSERQHandler extends ProcessHandler
{
	// create ExecutorService to manage threads                        
	ExecutorService threadExecutor = Executors.newCachedThreadPool();

	/**
	 * This method notifies when a new client connection is requested. A decision is to be made whether to
	 * accept or reject the connection.  
	 * @param objClientSession  object of client session.
	 */
	public void onConnect(ClientSession objClientSession)
	{
		objClientSession.acceptConnection(true);
	}


	/**
	 * When an Association Request, Associate Release Request or Abort is received from one of the 
	 * SCUs, this method creates new instance of AssociateRQHandler and executes it in separate thread.
	 * Once the current received request is processed and a suitable response is sent to the SCU, the
	 * thread executed for AssociateRQHandler ends.
	 */
	public void onAssociateReceive(ClientSession objClientSession) 
	{
		AssociateRQHandler objAssociateRQHandler = new AssociateRQHandler(objClientSession);
		threadExecutor.execute(objAssociateRQHandler);
	}

	/**
	 * When a Service Request is received from one of the SCUs, this method creates new instance of
	 * ServiceRQHandler and executes it in separate thread there by relieving the DIMSERQHandler to receive another service request.
	 * Once the current received request is processed and a suitable response is sent to the SCU, the thread executed for ServiceRQHandler ends.
	 */
	public void onServiceRequest(ClientSession objClientSession)
	{
		ServiceRQHandler objServiceRQHandler = new ServiceRQHandler(objClientSession, getDataSink(EnumDataSink.CSERVICES),getDataSource(EnumDataSource.CSERVICES),getStoragePath(),getSupportedAETitles());
		threadExecutor.execute(objServiceRQHandler);
	}
}

/**
 * This class processes Association Request, Associate Release Request or Abort which is received from one of the 
 * SCUs. 
 * If Association Request is received,
 * 		Create Association Negotiator which negotiates the request with the existing default association and
 * 		creates an AgreedAssociation which contains the negotiated association values. AssociationNegotiator
 * 		returns the Association Response as AssociateAC when request is accepted or returns AssociateRJ is 
 * 		none of the values in Association Request are acceptable.
 * 	If Associate Release Request is received,
 * 		Create a Associate Release Response and send the response to the SCU using the respective ClientSession.
 *  If Abort is received,
 *  	An Abort response must be generated and sent using ClientSession
 *    
 * @param objClientSession instance of Client Session.
 */

class AssociateRQHandler implements Runnable
{


	ClientSession objClientSession = null;
	public AssociateRQHandler(ClientSession objClientSession)
	{
		this.objClientSession = objClientSession;
	}

	public void run()
	{
		//Get Association RQ
		IDIMSEAssociation objIDIMSEAssociation = objClientSession.getDIMSEAssociation();
		EnumPDUType enumPDUType = objIDIMSEAssociation.getPDUType();
		IDIMSEAssociation objAssociationRSP = null;

		//For Association Request negotiate with Default Association and generate Response
		switch(enumPDUType)
		{
		case ASSOCIATE_RQ :
		{
			AssociateRQ objAssociateRQ = (AssociateRQ)objIDIMSEAssociation;

			//get the default association from Client Session.
			DefaultAssociation objDefaultAssociation = objClientSession.getDefaultAssociation();

			//Negotiate and get Response
			AssociationNegotiator objAssNegotiator = new AssociationNegotiator();
			objAssociationRSP = objAssNegotiator.negotiate(objDefaultAssociation, objAssociateRQ);

			//set agreed association in client Session.
			objClientSession.setAgreedAssociation(objAssNegotiator.getAgreedAssociation());

			//Send Response
			objClientSession.sendAssociationMessage(objAssociationRSP);
			break;
		}
		case RELEASE_RQ :
		{

			//Create Release Response.
			objAssociationRSP = new AReleaseRQorRP(EnumPDUType.RELEASE_RP);

			//Send Release Response.
			objClientSession.sendAssociationMessage(objAssociationRSP);

			//Close Client Session
			objClientSession.close();
			break;
		}
		case ABORT :
		{
			System.out.println("Abort has arrived Stop Porcessing -- Association is no longer available");
			break;
		}
		}
	}
}

/**
 * This class processed any service related request received from a SCU.
 * For Example,
 * If C-Store-RQ is received,
 * 		Create CStoreSCP using the ClientSession.
 * 		Process the request using the CStoreSCP which actually serializes the DataSet on a Stream. 
 * 		Stores Data Set in the DataBase and returns
 * 		a CStoreRSP containing the response.
 * 		Send this response using the ClientSession.
 * @see objClientSession Object of Client Session.
 */
class ServiceRQHandler implements Runnable
{
	ClientSession objClientSession = null;
	IDataSink objIDataSink = null;
	IDataSource objIDataSource = null;
	String strStoragePath = null;
	IPairedCollection<String, String> objSupportedAETitle = null;
	public ServiceRQHandler(ClientSession objClientSession, IDataSink objIDataSink, IDataSource objIDataSource, String strStoragePath, IPairedCollection<String, String> strSupportedAETitle)
	{
		this.objClientSession = objClientSession;
		this.objIDataSink = objIDataSink;
		this.objIDataSource = objIDataSource;
		this.strStoragePath = strStoragePath;
		this.objSupportedAETitle = strSupportedAETitle;
	}


	public void run()
	{
		DicomMessage objDicomMessage = objClientSession.getDicomMessage();
		EnumCommandField enumCommandField = objDicomMessage.getCommandType();

		switch(enumCommandField)
		{

		case C_ECHO_RQ:
		{
			System.out.println("C-Echo Request Received");
			/*
			 * Create CECHO_SCP get RQ from ClientSession call processRequest and pass C_ECHO_RQ on CECHO_SCP
			 * receive response from CECHO_SCP sendResponse using ClientSession
			 */
			DIMSEResponse objDResponse = null;
			CEchoSCP objCEchoSCP = CEchoSCP.createInstance(objClientSession); 
			try
			{
				/*
				 * Process CEcho Request.
				 */
				objDResponse = objCEchoSCP.processRequest(objClientSession.getDicomMessage());

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);

				System.out.println("C-Echo Response Sent");
			}
			catch(IOException ioe)
			{
				ioe.printStackTrace();
			}
			break;
		}

		case C_STORE_RQ:
		{

			System.out.println("C-Store Request Received");

			try
			{
				// Verify the Digital Signature in case of extended negotiation support.
				IDataSet objreceivedDataSet = objClientSession.getDicomMessage().getDataSet();
				AgreedAssociation objAgreedAssociation = objClientSession.getAgreedAssociation();	

				// Retrieve Agreed Extended Negotiation Sub Item Fields
				SOPClassExtendedNegotiationSubItemFields objExtField =	objAgreedAssociation.getSOPClassExtendedNegotiationSubItemFields(objreceivedDataSet.getSOPClassUID());

				if(objExtField != null)
				{

					if(objExtField.getLevelofDigitalSignatureSupport() == EnumLevelofDigitalSignatureSupport.LEVEL2)
					{ 

						DSPVerifier objVerifier = new DSPVerifier();

						// Verify All the Digital Signatures present in DataSet
						boolean verified = objVerifier.VerifyDigitalSignatures(objreceivedDataSet);

						if(verified)
							System.out.println("Digital Signature of received DataSet is Verified.");
						else
							System.out.println("Digital Signature verification failed.");
					}

				}
			}
			catch(Exception dse)
			{
				System.out.println("Exception while verifying digital signature of received dataset : "+ dse.getMessage());
			}

			/*
			 * Create CSTORE_SCP get RQ from ClientSession call processRequest and pass C_STORE_RQ on CSTORE_SCP
			 * receive response from CSTORE_SCP sendResponse using ClientSession
			 */
			CStoreSCP objCStoreSCP = CStoreSCP.createInstance(objClientSession); 
			try
			{
				/*
				 * Process CStore Request. The DataSet is serialized at the Storage Path provided on C-Store SCP.
				 * If No Storage Path is provide on C-Store SCP then File is serialized at "%temp%/CDAC-SDK/DCM-J/DicomDump"
				 * in User Temp Folder.
				 */
				objCStoreSCP.setStoragePath(strStoragePath);

				//Set the DataSink for C-Store Service to C-Store SCP for processing of the request.
				IPairedCollection<EnumDataSink,IDataSink> objPairedCollection = new PairedCollection<EnumDataSink, IDataSink>();
				objPairedCollection.put(EnumDataSink.CSERVICES, objIDataSink);
				objCStoreSCP.setDataSinkList(objPairedCollection);

				//Provide the request to C-Store SCP for processing.  processRequest function will process the
				//request and returns the response
				DIMSEResponse objDResponse = objCStoreSCP.processRequest(objClientSession.getDicomMessage());

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);

				System.out.println("C-Store Response Sent");

			}
			catch(Exception ioe)
			{
				ioe.printStackTrace();
				objClientSession.close();
			}
			break;
		}

		case C_FIND_RQ:
		{
			System.out.println("C-Find Request Received");
			/*
			 * Create CFIND_SCP get RQ from ClientSession call processRequest and pass C_FIND_RQ on CFIND_SCP
			 * receive response from CFIND_SCP sendResponse using ClientSession
			 */
			DIMSEResponse objDResponse = null;
			CFindSCP objCFindSCP = CFindSCP.createInstance(objClientSession); 
			try
			{

				/*
				 * Process CFind Request using the CFindSCP which actually searches the key attribute 
				 * in the data set and sends a CFindRSP pending response containing the dataset and returns final success response.
				 */
				/*
				 * Set Data Source on C-Find SCP.
				 */
				IPairedCollection<EnumDataSource,IDataSource> objPairedCollection = new PairedCollection<EnumDataSource, IDataSource>();
				objPairedCollection.put(EnumDataSource.CSERVICES, objIDataSource);
				objCFindSCP.setDataSourceList(objPairedCollection);
				objDResponse = objCFindSCP.processRequest(objClientSession.getDicomMessage());

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);
			}
			catch(Exception ioe)
			{
				ioe.printStackTrace();
				objClientSession.close();
			}
			break;
		}
		case C_MOVE_RQ:
		{
			System.out.println("C-Move Request Received");

			DIMSEResponse objDResponse = null;
			CMoveSCP objCMoveSCP = CMoveSCP.createInstance(objClientSession); 

			try
			{
				if(objSupportedAETitle != null)
				{
					IPairedCollection<String, String> objPairedCollection = objSupportedAETitle;
					String strSupportedAETitle = objPairedCollection.get(new CMoveRQ(objClientSession.getDicomMessage()).getMoveDestination());
					if(strSupportedAETitle != null)
					{
						String[] strArray = strSupportedAETitle.split(":");
						String strIP = strArray[0];
						int iPort = Integer.parseInt(strArray[1]);

						/*
						 * Set C-Store SCP on C-Move SCP.
						 */
						objCMoveSCP.addAETitleInfo(new CMoveRQ(objClientSession.getDicomMessage()).getMoveDestination(), strIP, iPort);
					}
				}

				/*
				 * Set DataSource onC-Move SCP.
				 * Process CMove Request using the CMoveSCP which searches for the key attribute
				 * then act as C-Store SCU , establishes association with C-Store Server , performs 
				 * C-Store and then release the association.
				 * For each C-Store sends the pending response to C-move SCU
				 * After all C-Store request send final response to the C-move SCU.
				 */
				objCMoveSCP.setDataSource(objIDataSource);
				objDResponse = objCMoveSCP.processRequest(objClientSession.getDicomMessage());
				System.out.println("C-Move RSP sent");

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);
				break;
			}
			catch(Exception ioe)
			{
				ioe.printStackTrace();
				objClientSession.close();
			}
			break;
		}
		case C_GET_RQ:
		{
			System.out.println("C-Get request received");

			/*
			 * Create CGET_SCP and get RQ from ClientSession 
			 * Call processRequest and pass CGET_SCP on CGET_SCP
			 * Receive response from CGET_SCP sendResponse using ClientSession
			 */
			DIMSEResponse objDResponse = null;
			CGetSCP objCGetSCP = CGetSCP.createInstance(objClientSession); 

			try
			{

				/*
				 * Set Data Source in C-Get SCP.
				 */
				objCGetSCP.setDataSource(objIDataSource);

				/*
				 * Provide the request to C-Get SCP for processing.  processRequest function will process the request and returns the response
				 */
				objDResponse = objCGetSCP.processRequest(objClientSession.getDicomMessage());

				/*
				 * Send the service message through Client Session.
				 */
				if(objDResponse != null)
					objClientSession.sendServiceMessage(objDResponse);

				System.out.println("C-Get response sent");

			}catch(Exception ioe)
			{
				ioe.printStackTrace();
				objClientSession.close();
			}
		}
		}
	}
}




