/*******************************************************************************
 *  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.basicdicommediasecurityprofile;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Locale;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


import cdac.medinfo.sdk.dcm30_04.commoninterface.IDataSet;
import cdac.medinfo.sdk.dcm30_04.commoninterface.IItem;
import cdac.medinfo.sdk.dcm30_04.dicomparserserializers.DicomParser;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomFileStream;
import cdac.medinfo.sdk.dcm30_04.dicomstreams.DicomStream;
import cdac.medinfo.sdk.dcm30_04.enums.EnumDirectoryRecordType;
import cdac.medinfo.sdk.dcm30_04.exceptions.ConfidentialityAttributeException;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidDICOMFileException;
import cdac.medinfo.sdk.dcm30_04.services.dicomnetcommons.DataTypeConvertor;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomLogger;
import cdac.medinfo.sdk.dcm30_04.utils.EnumFeatureList;
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.ResourceBundleWrapper;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;

/**
 * The Basic DICOM Media Security Profile allows encapsulation of a DICOM File into a Secure DICOM File 
 * such that the following aspects of security are addressed:
 * 1. confidentiality,
 * 2. integrity,
 * 3. data origin authentication.
 * BasicDICOMMediaSecurityProfile performs encryption/decryption,digital signing and digesting of a DICOM file 
 * or a DICOMDIR File-Set as per required to provide support for all three security aspects.
 */
public class BasicDICOMMediaSecurityProfile
{
	

	private final String SIGN_ALGORITHM = "SHA1withRSA";
	private final String DIGEST_ALGORITHM = "SHA1";
	private final int iBlockSize = 1024;


	/**
	 * This method encrypts the DICOMDIR file set and generates secure dicom file-set.
	 * If Source and destination path are same then Update operation will be performed and existing file will be modified.
	 * Encryption will be performed on each file of file-set but DICOMDIR file will not be encrypted.
	 * @param strSourceDICOMDIRPath path to the directory which contains DICOMDIR file and other images in DICOMDIR format
	 * 								name of DICOMDIR file should not be included
	 * @param strSecureDICOMDIRPath path to the directory where encrypted files will be stored in DICOMDIR structure.
	 * @param enumProfileAlgorithm - Encryption Algorithm that is to be used.
	 * @param objKey - Key that is to be used for the encryption of Data ELements.
	 * @param objIV - value of initializing vector
	 * @throws IOException Signals if IO Operation Fails.
	 * @throws NoSuchAlgorithmException Signal is Encryption of the Data Set Fails.
	 * @throws NoSuchPaddingException Signal is Encryption of the Data Set Fails.
	 * @throws InvalidKeyException Signal is Encryption of the Data Set Fails.
	 * @throws IllegalBlockSizeException Signal is Encryption of the Data Set Fails.
	 * @throws BadPaddingException Signal is Encryption of the Data Set Fails.
	 * @throws InvalidAlgorithmParameterException signal if algorithm parameter are not valid
	 */
	@SuppressWarnings("unchecked")
	public void encryptDICOMFileSet(String strSourceDICOMDIRPath,String strSecureDICOMDIRPath,EnumProfileAlgorithm enumProfileAlgorithm, byte[] objKey, byte[] objIV)throws IOException,NoSuchAlgorithmException, NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException,InvalidAlgorithmParameterException
	{
		File objFile = null;
		String strDIRPath = strSourceDICOMDIRPath +File.separator+"DICOMDIR";

		//Parsing DICOMDIR file to retrieve
		DicomParser objDicomParser = new DicomParser();
		IDataSet objIDataSet = objDicomParser.parseAsPart10File(strDIRPath,true);

		String strDICOMFilePath = null;
		String strSecureDICOMFilePath = null;

		/*
		 * Retrieving image information from DICOMDIR file 
		 * (0x0004,0x1220)Directory Record Sequence contains information about all stored Images.
		 * In this sequence attribute(0x0004,0x1430) Directory Record Sequence specifies path information of an image.
		 */
		if(objIDataSet.isPresent(0x0004,0x1220))
		{
			ISingleCollection<IItem> objItemList = (ISingleCollection<IItem>)objIDataSet.getValue(0x0004,0x1220);

			for(IItem objItem : objItemList)
			{
				if(objItem.contains(0x0004,0x1430))
				{
					String strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

					if(EnumDirectoryRecordType.IMAGE.getValue().equals(strDirectoryRecordType))
					{
						if(objItem.contains(0x0004,0x1500))
						{
							/*
							 * Retrieving image file path information. 
							 * Truncating filename from path and concatenating this path with base directory.
							 * if Source and destination directory are not same then directory structure for encrypted 
							 * file need to be created.
							 */

							Object objImageFileVaue = objItem.getValue(0x0004,0x1500);
							String strImageFilePath = null;
							if (objImageFileVaue instanceof String[]) 
								strImageFilePath = DataTypeConvertor.arrayToString((String[])objImageFileVaue);
							else
								strImageFilePath = objImageFileVaue.toString();
							String strImageDirPath = strImageFilePath.substring(0,strImageFilePath.lastIndexOf(File.separator));
							objFile = new File(strSecureDICOMDIRPath+File.separator+strImageDirPath);
							if(!objFile.exists())
							{
								objFile.mkdirs();
								objFile = null;
							}

							strDICOMFilePath = strSourceDICOMDIRPath+File.separator+strImageFilePath;
							strSecureDICOMFilePath = strSecureDICOMDIRPath+File.separator+strImageFilePath;
							//calling encryption for each file.
							encryptDICOMFile(strDICOMFilePath,strSecureDICOMFilePath,enumProfileAlgorithm,objKey,objIV);
						}
					}
				}
			}

			/*
			 * If source and destination file are not same then DICOMDIR file need to be copied
			 * in destination folder also.
			 */
			if(!strSourceDICOMDIRPath.equalsIgnoreCase(strSecureDICOMDIRPath))
			{
				strSecureDICOMDIRPath = strSecureDICOMDIRPath +File.separator+ "DICOMDIR";
				copyFile(strDIRPath, strSecureDICOMDIRPath);
			}
		}
	}

	/**
	 * This method encrypts the DICOM file and dumped it on destination path provided as secure dicom file path.
	 * @param strDICOMFilePath value of DICOM file path which need to be converted in secure DICOM file.
	 * @param strSecureDICOMFilePath value of secure DICOM file path.
	 * @param enumProfileAlgorithm - Encryption Algorithm that is to be used.
	 * @param objKey - Key that is to be used for the encryption of Data ELements.
	 * @param objIV - value of initializing vector
	 * @throws IOException Signals if IO Operation Fails.
	 * @throws NoSuchAlgorithmException Signal is Encryption of the Data Set Fails.
	 * @throws NoSuchPaddingException Signal is Encryption of the Data Set Fails.
	 * @throws InvalidKeyException Signal is Encryption of the Data Set Fails.
	 * @throws IllegalBlockSizeException Signal is Encryption of the Data Set Fails.
	 * @throws BadPaddingException Signal is Encryption of the Data Set Fails.
	 * @throws InvalidAlgorithmParameterException signal if algorithm param are not valid
	 */
	public void encryptDICOMFile(String strDICOMFilePath,String strSecureDICOMFilePath,EnumProfileAlgorithm enumProfileAlgorithm, byte[] objKey, byte[] objIV)throws IOException,NoSuchAlgorithmException, NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException,InvalidAlgorithmParameterException
	{
		/*
		 * If source and destination path are same then encrypted file will be created in the same directory 
		 * with a random generated name.After completion of encryption process original file will be deleted 
		 * and new file will be renamed as original file.
		 */
		boolean isUpdate = false;
		if(strDICOMFilePath.equalsIgnoreCase(strSecureDICOMFilePath))
		{
			isUpdate = true;
			strSecureDICOMFilePath = getTempFilePath(strDICOMFilePath) + File.separator +StandardMethods.generateUUID();
		}

		DicomFileStream objDicomFileStream = new DicomFileStream();

		//Initializing input stream to read DICOM file
		FileInputStream objOriginalFileStream = new FileInputStream(strDICOMFilePath);
		objDicomFileStream.setInputStream(objOriginalFileStream);

		try
		{
			DicomParser objDicomParser = new DicomParser();
			objDicomParser.parseAsPart10File(objDicomFileStream, true);
		}
		catch(Exception ex)
		{			
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions","DicomFileEncryptionFailed",new Locale(DicomConfig.createInstance().getLocale()))+"("+strDICOMFilePath+"): "+ ex.getMessage());
			}			
			throw new InvalidDICOMFileException(ex.getMessage(),ex);
		}

		objOriginalFileStream = new FileInputStream(strDICOMFilePath);
		objDicomFileStream.setInputStream(objOriginalFileStream);

		//Initializing output stream to write secure DICOM file
		FileOutputStream objSecureFileStream = new FileOutputStream(strSecureDICOMFilePath);
		objDicomFileStream.setOutputStream(objSecureFileStream);

		//calling encryption
		encryptDICOMFile(objDicomFileStream,objDicomFileStream, enumProfileAlgorithm, objKey, objIV);

		objDicomFileStream.close();

		/*
		 * This block of code will be executed if source and destination file are same.
		 * It deletes the original file and rename new generated file.
		 */
		File objSourceFile = new File(strDICOMFilePath);
		if(isUpdate)
		{
			if(objSourceFile.delete())
			{
				objSourceFile = null;
				updateFile(strDICOMFilePath, strSecureDICOMFilePath);
			}
		}
	}



	/**
	 * This method encrypts the data provided on stream and serializes it on destination stream.
	 * @param objSourceDicomStream stream containing data which needs to be encrypted
	 * @param objDestStream stream on which encrypted data will be serialized
	 * @param enumProfileAlgorithm  Encryption Algorithm that is to be used.
	 * @param objKey  Key that is to be used for the encryption of Data ELements.
	 * @param objIV  value of initializing vector
	 * @throws IOException Signals if IO Operation Fails.
	 * @throws NoSuchAlgorithmException Signal is Encryption of the Data Set Fails.
	 * @throws NoSuchPaddingException Signal is Encryption of the Data Set Fails.
	 * @throws InvalidKeyException Signal is Encryption of the Data Set Fails.
	 * @throws IllegalBlockSizeException Signal is Encryption of the Data Set Fails.
	 * @throws BadPaddingException Signal is Encryption of the Data Set Fails.
	 * @throws InvalidAlgorithmParameterException signal if algorithm parameter are not valid
	 */
	private void encryptDICOMFile(DicomStream objSourceDicomStream,DicomStream objDestStream,EnumProfileAlgorithm enumProfileAlgorithm, byte[] objKey, byte[] objIV)throws NoSuchAlgorithmException,NoSuchPaddingException,InvalidKeyException,IOException,InvalidAlgorithmParameterException,BadPaddingException,IllegalBlockSizeException
	{
			//If the Encryption Algorithm or the Key is not specified then exception should be thrown as the data set cannot be encrypted then.
			if(enumProfileAlgorithm == null || objKey == null)
			{
				String strValue = null;
				if(enumProfileAlgorithm == null)
				{
					strValue = ResourceBundleWrapper.getValue("exceptions","BasicDICOMMediaSecurityProfile.NoAlgorithmSpecified",new Locale(DicomConfig.createInstance().getLocale()));
				}
				else
				{
					strValue = ResourceBundleWrapper.getValue("exceptions","BasicDICOMMediaSecurityProfile.KeyNotSpecified",new Locale(DicomConfig.createInstance().getLocale()));
				}

				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(strValue);

				}
				throw new ConfidentialityAttributeException(strValue);
			}

			SecretKeySpec objSecretKeySpec = null;
			IvParameterSpec  objIVSpec = null;

			//Initializing vector specification
			if(objIV != null && objIV.length !=0)
				objIVSpec = new IvParameterSpec(objIV);

			Cipher rsaCipher = null;
			/*
			 * Initializes the Cipher bases on the Profile Algorithm specified
			 */
			if(enumProfileAlgorithm == EnumProfileAlgorithm.DES)
			{
				objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.DES.getValue());
				rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.DES.getValue());
			}
			else if(enumProfileAlgorithm == EnumProfileAlgorithm.AES)
			{
				objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.AES.getValue());
				rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.AES.getValue());
			}
			else if(enumProfileAlgorithm == EnumProfileAlgorithm.TRIPPLE_DES)
			{
				objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.TRIPPLE_DES.getValue());
				rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.TRIPPLE_DES.getValue());
			}

			//Initializing cipher with key and initialization vector 
			rsaCipher.init(Cipher.ENCRYPT_MODE, objSecretKeySpec, objIVSpec);

			byte[] objDataBlock = null;
			byte[] encryptedBlock = null;
			while(objSourceDicomStream.available() != 0)
			{
				/*
				 * This block of code reads data block and generates encrypted block for that data.
				 * It parallely writes the encrypted block to destination stream. 
				 */
				if(objSourceDicomStream.available() < iBlockSize)
					objDataBlock = new byte[objSourceDicomStream.available()];
				else
					objDataBlock = new byte[iBlockSize];

				objSourceDicomStream.readBuffer(objDataBlock);
				encryptedBlock = rsaCipher.update(objDataBlock);
				objDestStream.writeBuffer(encryptedBlock,0,encryptedBlock.length);
			}

			//Finalizing encryption process
			encryptedBlock = rsaCipher.doFinal();
			objDestStream.writeBuffer(encryptedBlock,0,encryptedBlock.length);
		}

	/**
	 * This method decrypts the DICOMDIR file set and generates original DICOM file-set.
	 * If Source and destination path are same then Update operation will be performed and existing file will be modified.
	 * Decryption will be performed on each file of file-set but DICOMDIR file will not be modified.
	 * @param strSecureDICOMDIRPath path to the directory which contains DICOMDIR file and other encrypted images which need to be decrypted.
	 * 								name of DICOMDIR file should not be included
	 * @param strSourceDICOMDIRPath path to the directory where decrypted files will be stored.
	 * @param enumProfileAlgorithm  Algorithm that is to be used.
	 * @param objKey  Key that is to be used for the decrption of Data ELements.
	 * @param objIV  value of initializing vector
	 * @throws IOException Signals if IO Operation Fails.
	 * @throws NoSuchAlgorithmException Signal is decrption of the Data Set Fails.
	 * @throws NoSuchPaddingException Signal is decrption of the Data Set Fails.
	 * @throws InvalidKeyException Signal is decrption of the Data Set Fails.
	 * @throws IllegalBlockSizeException Signal is decrption of the Data Set Fails.
	 * @throws BadPaddingException Signal is decrption of the Data Set Fails.
	 * @throws InvalidAlgorithmParameterException signal if algorithm parameter are not valid
	 */
	@SuppressWarnings("unchecked")
	public void decryptDICOMFileSet(String strSecureDICOMDIRPath,String strSourceDICOMDIRPath,EnumProfileAlgorithm enumProfileAlgorithm, byte[] objKey, byte[] objIV)throws IOException,NoSuchAlgorithmException, NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException,InvalidAlgorithmParameterException
	{
		File objFile = null;
		String strDIRPath = strSecureDICOMDIRPath +File.separator+ "DICOMDIR";

		//Parsing DICOMDIR to retrieve image information
		DicomParser objDicomParser = new DicomParser();
		IDataSet objIDataSet = objDicomParser.parseAsPart10File(strDIRPath,true);

		String strDICOMFilePath = null;
		String strSecureDICOMFilePath = null;

		/*
		 * Retrieving image information from DICOMDIR file 
		 * (0x0004,0x1220)Directory Record Sequence contains information about all stored Images.
		 * In this sequence attribute(0x0004,0x1430) Directory Record Sequence specifies path information of an image.
		 */
		if(objIDataSet.isPresent(0x0004,0x1220))
		{
			ISingleCollection<IItem> objItemList = (ISingleCollection<IItem>)objIDataSet.getValue(0x0004,0x1220);

			for(IItem objItem : objItemList)
			{
				if(objItem.contains(0x0004,0x1430))
				{
					String strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

					if(EnumDirectoryRecordType.IMAGE.getValue().equals(strDirectoryRecordType))
					{
						if(objItem.contains(0x0004,0x1500))
						{
							Object objImageFileVaue = objItem.getValue(0x0004,0x1500);
							String strImageFilePath = null;
							if (objImageFileVaue instanceof String[]) 
								strImageFilePath = DataTypeConvertor.arrayToString((String[])objImageFileVaue);
							else
								strImageFilePath = objImageFileVaue.toString();

							/*
							 * Retrieving image file path information. 
							 * Truncating filename from path and concatenating this path with base directory.
							 * if Source and destination directory are not same then directory structure for encrypted 
							 * file need to be created.
							 */
							String strImageDirPath = strImageFilePath.substring(0,strImageFilePath.lastIndexOf(File.separator));
							objFile = new File(strSourceDICOMDIRPath+File.separator+strImageDirPath);
							if(!objFile.exists())
							{
								objFile.mkdirs();
								objFile = null;
							}

							strDICOMFilePath = strSourceDICOMDIRPath+File.separator+strImageFilePath;
							strSecureDICOMFilePath = strSecureDICOMDIRPath+File.separator+strImageFilePath;
							//calling decryption for each file.
							decryptSecureDICOMFile(strSecureDICOMFilePath,strDICOMFilePath,enumProfileAlgorithm,objKey,objIV);
						}
					}
				}
			}

			/*
			 * If source and destination file are not same then DICOMDIR file need to be copied
			 * in destination folder also.
			 */
			if(!strSecureDICOMDIRPath.equalsIgnoreCase(strSourceDICOMDIRPath))
			{
				strSourceDICOMDIRPath = strSourceDICOMDIRPath +File.separator+ "DICOMDIR";
				copyFile(strDIRPath, strSourceDICOMDIRPath);
			}
		}
	}



	/**
	 * This method decrypts the secure DICOM file according to the profile algorithm , key and initializing vector provided to it.
	 * decrypted secure DICOM file will serialized on file path provided to it
	 * It serializes the decrypted data on destination stream
	 * @param strSecureDICOMFilePath path to secure DICOM file which need to be decrypted in original DICOM file.
	 * @param strDICOMFilePath path where decrypted original DICOM file will be serialized
	 * @param enumProfileAlgorithm  Algorithm that is to be used.
	 * @param objKey  Key that is to be used for the decrption of Data ELements.
	 * @param objIV  value of initializing vector
	 * @throws IOException Signals if IO Operation Fails.
	 * @throws NoSuchAlgorithmException Signal is decrption of the Data Set Fails.
	 * @throws NoSuchPaddingException Signal is decrption of the Data Set Fails.
	 * @throws InvalidKeyException Signal is decrption of the Data Set Fails.
	 * @throws IllegalBlockSizeException Signal is decrption of the Data Set Fails.
	 * @throws BadPaddingException Signal is decrption of the Data Set Fails.
	 * @throws InvalidAlgorithmParameterException signal if algorithm param are not valid
	 */
	public void decryptSecureDICOMFile(String strSecureDICOMFilePath,String strDICOMFilePath,EnumProfileAlgorithm enumProfileAlgorithm, byte[] objKey, byte[] objIV)throws IOException,NoSuchAlgorithmException, NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException,InvalidAlgorithmParameterException
	{
		/*
		 * If source and destination path are same then decrypted file will be created in the same directory 
		 * with a random generated name.After completion of decryption process original file will be deleted 
		 * and new file will be renamed as original file.
		 */
		boolean isUpdate = false;
		if(strSecureDICOMFilePath.equalsIgnoreCase(strDICOMFilePath))
		{
			isUpdate = true;
			strDICOMFilePath = getTempFilePath(strSecureDICOMFilePath) + File.separator+StandardMethods.generateUUID();
		}

		DicomFileStream objDicomFileStream = new DicomFileStream();

		//Initializes Input stream to read the data
		FileInputStream objSecureFileStream = new FileInputStream(strSecureDICOMFilePath);
		objDicomFileStream.setInputStream(objSecureFileStream);

		//Initializes output stream to write the decrypted block
		FileOutputStream objOriginalFileStream = new FileOutputStream(strDICOMFilePath);
		objDicomFileStream.setOutputStream(objOriginalFileStream);

		//calling decryption process
		decryptSecureDICOMFile(objDicomFileStream,objDicomFileStream, enumProfileAlgorithm, objKey, objIV,strDICOMFilePath);

		objDicomFileStream.close();

		/*
		 * This block of code will be executed if source and destination file are same.
		 * It deletes the original file and rename new generated file.
		 */
		File objSourceFile = new File(strSecureDICOMFilePath);
		if(isUpdate)
		{
			if(objSourceFile.delete())
			{
				objSourceFile = null;
				updateFile(strSecureDICOMFilePath, strDICOMFilePath);
			}
		}
	}

	/**
	 * This method decrypts the data provided on stream according to the profile algorithm , key and initializing vector provided to it.
	 * It serializes the decrypted data on destination stream
	 * @param objSourceDicomStream stream containing data which needs to be decrypted
	 * @param objDestStream stream on which decrypted data will be serialized
	 * @param enumProfileAlgorithm - Decryption Algorithm that is to be used.
	 * @param objKey  Key that is to be used for the decryption of Data ELements.
	 * @param objIV  value of initializing vector
	 * @throws IOException Signals if IO Operation Fails.
	 * @throws NoSuchAlgorithmException Signal is decryption of the Data Set Fails.
	 * @throws NoSuchPaddingException Signal is decryption of the Data Set Fails.
	 * @throws InvalidKeyException Signal is decryption of the Data Set Fails.
	 * @throws IllegalBlockSizeException Signal is decryption of the Data Set Fails.
	 * @throws BadPaddingException Signal is decryption of the Data Set Fails.
	 * @throws InvalidAlgorithmParameterException signal if algorithm param are not valid
	 */
	private void decryptSecureDICOMFile(DicomStream objSourceDicomStream,DicomStream objDestStream, EnumProfileAlgorithm enumProfileAlgorithm, byte[] objKey, byte[] objIV,String strDecryptedDCMFilePath) throws  NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException,InvalidAlgorithmParameterException
	{
		// if the profile Algorithm or the Key is not specified then exception should be thrown as the data set cannot be encrypted then.
		if(enumProfileAlgorithm == null || objKey == null)
		{
			String strValue = null;
			if(enumProfileAlgorithm == null)
			{
				strValue = ResourceBundleWrapper.getValue("exceptions","BasicDICOMMediaSecurityProfile.NoAlgorithmSpecified",new Locale(DicomConfig.createInstance().getLocale()));
			}
			else
			{
				strValue = ResourceBundleWrapper.getValue("exceptions","BasicDICOMMediaSecurityProfile.KeyNotSpecified",new Locale(DicomConfig.createInstance().getLocale()));
			}

			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(strValue);

			}
			throw new ConfidentialityAttributeException(strValue);
		}

		//Initializing key and vector
		SecretKeySpec objSecretKeySpec = null;
		IvParameterSpec  objIVSpec = null;

		if(objIV != null && objIV.length !=0)
			objIVSpec = new IvParameterSpec(objIV); 

		Cipher rsaCipher = null;
		/*
		 * Initializes the Cipher bases on the Profile Algorithm specified
		 */
		if(enumProfileAlgorithm == EnumProfileAlgorithm.DES)
		{
			objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.DES.getValue());
			rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.DES.getValue());
		}
		else if(enumProfileAlgorithm == EnumProfileAlgorithm.AES)
		{
			objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.AES.getValue());
			rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.AES.getValue());
		}
		else if(enumProfileAlgorithm == EnumProfileAlgorithm.TRIPPLE_DES)
		{
			objSecretKeySpec = new SecretKeySpec(objKey, EnumProfileAlgorithm.TRIPPLE_DES.getValue());
			rsaCipher = Cipher.getInstance(EnumProfileAlgorithm.TRIPPLE_DES.getValue());
		}

		//Initializing cipher using key and initializing vector.
		rsaCipher.init(Cipher.DECRYPT_MODE, objSecretKeySpec, objIVSpec);

		byte[] objDataBlock = null;
		byte[] decryptedBlock = null;
		while(objSourceDicomStream.available() != 0)
		{
			/*
			 * This block of code reads data block and generates decrypted block for that data.
			 * It parallely writes the decrypted block to destination stream. 
			 */
			if(objSourceDicomStream.available() < iBlockSize)
				objDataBlock = new byte[objSourceDicomStream.available()];
			else
				objDataBlock = new byte[iBlockSize];

			objSourceDicomStream.readBuffer(objDataBlock);
			decryptedBlock = rsaCipher.update(objDataBlock);
			objDestStream.writeBuffer(decryptedBlock,0,decryptedBlock.length);
		}

		//finalizing decryption process
		decryptedBlock = rsaCipher.doFinal();
		objDestStream.writeBuffer(decryptedBlock,0,decryptedBlock.length);
		
		
		// Check that Decrypted file is valid DICOM file or not
		
		DicomFileStream objDicomFileStream = new DicomFileStream();

		//Initializing input stream to read DICOM file
		FileInputStream objOriginalFileStream = new FileInputStream(strDecryptedDCMFilePath);
		objDicomFileStream.setInputStream(objOriginalFileStream);	

		try
		{
			DicomParser objDicomParser = new DicomParser();
			objDicomParser.parseAsPart10File(objDicomFileStream, true);
		}
		catch(Exception ex)
		{			
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions","DicomFileDecryptionFailed",new Locale(DicomConfig.createInstance().getLocale()))+ ex.getMessage());
			}			
			throw new InvalidDICOMFileException(ex.getMessage(),ex);
		}
	}

	/***
	 *  Retrieving image file path information. 
	 * Truncating filename from path and concatenating this path with base directory.
	 * if Source and destination directory are not same then signature will be generated 
	 * in separate directory with the suffix specified
	 * @param strSourceDICOMDIRPath - Source directory
	 * @param strImageFilePath
	 * @param strSignatureDIR - Destination directory
	 * @param strSignSuffix - suffix specified
	 * @return
	 */
	private String getFilePath(String strSourceDICOMDIRPath,String strImageFilePath,String strSignatureDIR,String strSignSuffix)
	{
		String strSignatureFilePath = null;
		String strDICOMFilePath = strSourceDICOMDIRPath+File.separator+strImageFilePath;
		if(strSourceDICOMDIRPath.equalsIgnoreCase(strSignatureDIR))
			strSignatureFilePath = strDICOMFilePath.substring(0, strDICOMFilePath.lastIndexOf('.')) + strSignSuffix;
		else
		{
			String imageFileName = strImageFilePath.substring(strImageFilePath.lastIndexOf(File.separator),strImageFilePath.lastIndexOf('.'));
			strSignatureFilePath = strSignatureDIR + File.separator + imageFileName + strSignSuffix;
		}
		return strSignatureFilePath;
	}

	/**
	 * This method generates digital signature for each file in DICOMDIR file set.
	 * If Source and destination path are same then signature will be generated parallel to the images.
	 * Name of signatures will be concatenation of file name and a suffix specified.
	 * If suffix is not specified then '_Sig' will be taken as default. 
	 * @param strSourceDICOMDIRPath path to the directory which contains DICOMDIR file and other images for which signature need to be generated.
	 * 								name of DICOMDIR file should not be included
	 * @param strSignatureDIR path to the directory where new generated signatures will be stored.
	 * @param objPrivateKey instance of private key.
	 * @param strSignSuffix suffix value which will be concatenated with image file name to generate name of signature file.
	 * @throws InvalidKeyException if key provided to it is invalid.
	 * @throws FileNotFoundException if specified file is not found.
	 * @throws IOException if input/output operation fails.
	 * @throws SignatureException if signature generation fails.
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid.
	 */
	@SuppressWarnings("unchecked")
	public void generateSignForFileSet(String strSourceDICOMDIRPath,String strSignatureDIR,PrivateKey objPrivateKey,String strSignSuffix)throws InvalidKeyException,FileNotFoundException,IOException,SignatureException,NoSuchAlgorithmException
	{
		if(strSignSuffix == null || "".equals(strSignSuffix))
		{
			strSignSuffix = "_Sig";
		}
		if(strSignSuffix.startsWith("."))
		{
			DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "InvalidSuffix", new Locale(DicomConfig.createInstance().getLocale())));
			strSignSuffix = "_Sig";
		}

		String strDIRPath = strSourceDICOMDIRPath + File.separator+"DICOMDIR";

		//Parsing DICOMDIR file to retrieve image information
		DicomParser objDicomParser = new DicomParser();
		IDataSet objIDataSet = objDicomParser.parseAsPart10File(strDIRPath,true);

		String strDICOMFilePath = null;
		String strSignatureFilePath = null;

		/*
		 * Retrieving image information from DICOMDIR file 
		 * (0x0004,0x1220)Directory Record Sequence contains information about all stored Images.
		 * In this sequence attribute(0x0004,0x1430) Directory Record Sequence specifies path information of an image.
		 */
		if(objIDataSet.isPresent(0x0004,0x1220))
		{
			ISingleCollection<IItem> objItemList = (ISingleCollection<IItem>)objIDataSet.getValue(0x0004,0x1220);

			for(IItem objItem : objItemList)
			{
				if(objItem.contains(0x0004,0x1430))
				{
					String strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

					if(EnumDirectoryRecordType.IMAGE.getValue().equals(strDirectoryRecordType))
					{
						if(objItem.contains(0x0004,0x1500))
						{
							Object objImageFileVaue = objItem.getValue(0x0004,0x1500);
							String strImageFilePath = null;
							if (objImageFileVaue instanceof String[]) 
								strImageFilePath = DataTypeConvertor.arrayToString((String[])objImageFileVaue);
							else
								strImageFilePath = objImageFileVaue.toString();

							/*
							 * Retrieving image file path information. 
							 * Truncating filename from path and concatenating this path with base directory.
							 * if Source and destination directory are not same then signature will be generated 
							 * in separate directory with the suffix specified
							 */
							strDICOMFilePath = strSourceDICOMDIRPath+File.separator+strImageFilePath;
							strSignatureFilePath = getFilePath(strSourceDICOMDIRPath,strImageFilePath,strSignatureDIR,strSignSuffix);
							//generating signature for each file.
							generateSignature(strDICOMFilePath, strSignatureFilePath, objPrivateKey);
						}
					}
				}
			}
		}
	}


	/**
	 * Generates the Digital Signature for provided secure file and dump it on given location.
	 * In generation of Digital Signature it uses private key provided to it and SHA-1 as the digest algorithm.
	 * @param strSecureDICOMFile path to secure DICOM file.
	 * @param strDigitalSignaturePath path where generated signature will be dumped. 
	 * @param objPrivateKey instance of private key.
	 * @throws InvalidKeyException if key provided to it is invalid.
	 * @throws FileNotFoundException if specified file is not found.
	 * @throws IOException if input/output operation fails.
	 * @throws SignatureException if signature generation fails.
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid.
	 */
	public void generateSignature(String strSecureDICOMFile,String strDigitalSignaturePath,PrivateKey objPrivateKey)throws InvalidKeyException,FileNotFoundException,IOException,SignatureException,NoSuchAlgorithmException
	{
		DicomFileStream objDicomFileStream = new DicomFileStream();
		
		//Initializing the input stream to read the data.
		FileInputStream objSecureFileStream = new FileInputStream(strSecureDICOMFile);
		objDicomFileStream.setInputStream(objSecureFileStream);

		try
		{
			DicomParser objDicomParser = new DicomParser();
			objDicomParser.parseAsPart10File(objDicomFileStream, true);
		}
		catch(Exception ex)
		{			
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions","SignatureGenerationFailed",new Locale(DicomConfig.createInstance().getLocale()))+"("+strSecureDICOMFile+"): "+ ex.getMessage());
			}			
			throw new InvalidDICOMFileException(ex.getMessage(),ex);
		}
		
		objSecureFileStream = new FileInputStream(strSecureDICOMFile);
		objDicomFileStream.setInputStream(objSecureFileStream);
		
		//Initialing output stream to write the generated signature
		FileOutputStream objFileOutputStream = new FileOutputStream(strDigitalSignaturePath);
		objDicomFileStream.setOutputStream(objFileOutputStream);

		//Generating signature
		generateSignature(objDicomFileStream,objDicomFileStream, objPrivateKey);

		objDicomFileStream.close();
	}

	/**
	 * Generates the Digital Signature for source data available on stream
	 * Generated signature will be serialized on destination stream
	 * In generation of Digital Signature it uses private key provided to it and SHA-1 as the digest algorithm.
	 * @param objSourceDicomStream stream containing source data
	 * @param objDestStream stream on which signature will be serialized 
	 * @param objPrivateKey instance of private key.
	 * @throws InvalidKeyException if key provided to it is invalid.
	 * @throws IOException if input/output operation fails.
	 * @throws SignatureException if signature generation fails.
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid.
	 */
	private void generateSignature(DicomStream objSourceDicomStream,DicomStream objDestStream,PrivateKey objPrivateKey)throws InvalidKeyException,IOException,SignatureException,NoSuchAlgorithmException
	{
		// if the Key is not specified then exception should be thrown as the signature can not be generated then.
		if(objPrivateKey == null)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions","BasicDICOMMediaSecurityProfile.KeyNotSpecified",new Locale(DicomConfig.createInstance().getLocale())));

			}
			throw new ConfidentialityAttributeException(ResourceBundleWrapper.getValue("exceptions","BasicDICOMMediaSecurityProfile.KeyNotSpecified",new Locale(DicomConfig.createInstance().getLocale())));
		}

		//Creates instance of signature for specified algorithm
		Signature objSignature = Signature.getInstance(SIGN_ALGORITHM);

		//Initializes the signature with private key
		objSignature.initSign(objPrivateKey);

		byte []objDataBlock = null;
		while(objSourceDicomStream.available() != 0)
		{
			if(objSourceDicomStream.available() < iBlockSize)
				objDataBlock = new byte[objSourceDicomStream.available()];
			else
				objDataBlock = new byte[iBlockSize];

			objSourceDicomStream.readBuffer(objDataBlock);

			//Populating data in instance of signature to generate digital signature
			objSignature.update(objDataBlock);
		}

		//Generating array of bytes containing value of signature
		byte[] objSignatureData = objSignature.sign();

		//Writing signature on destination stream
		objDestStream.writeBuffer(objSignatureData, 0, objSignatureData.length);
	}

	/**
	 * This method verifies digital signature for each file in DICOMDIR file set.
	 * If Source and destination path are same then signature will be seen in the same directory , parralal to the image.
	 * Name of signatures will be concatenation of file name and a suffix specified.
	 * If suffix is not specified then '_Sig' will be taken as default. 
	 * @param strSourceDICOMDIRPath path to the directory which contains DICOMDIR file and other images for which signature need to be verified.
	 * 								name of DICOMDIR file should not be included
	 * @param strSignatureDIR path to the directory where original signatures are stored.
	 * @param objPublicKey instance of public key.
	 * @param strSignSuffix suffix value which will be concatenated with image file name to generate name of signature file.
	 * @throws InvalidKeyException if key provided to it is invalid.
	 * @throws FileNotFoundException if specified file is not found.
	 * @throws IOException if input/output operation fails.
	 * @throws SignatureException if signature generation fails.
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid.
	 * @return IPairedCollection<String,Boolean> - map which contains file name and verification status.
	 */
	@SuppressWarnings("unchecked")
	public IPairedCollection<String,Boolean> verifySignForFileSet(String strSourceDICOMDIRPath,String strSignatureDIR,PublicKey objPublicKey,String strSignSuffix)throws NoSuchAlgorithmException,InvalidKeyException,FileNotFoundException,IOException,SignatureException
	{
		if(strSignSuffix == null || "".equals(strSignSuffix))
		{
			strSignSuffix = "_Sig";
		}
		if(strSignSuffix.startsWith("."))
		{
			DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "InvalidSuffix", new Locale(DicomConfig.createInstance().getLocale())));
			strSignSuffix = "_Sig";
		}

		IPairedCollection<String,Boolean> objStatusList = new PairedCollection<String, Boolean>();
		String strDIRPath = strSourceDICOMDIRPath +File.separator + "DICOMDIR";

		//Parsing DICOMDIR file to retrieve image information
		DicomParser objDicomParser = new DicomParser();
		IDataSet objIDataSet = objDicomParser.parseAsPart10File(strDIRPath,true);

		String strDICOMFilePath = null;
		String strSignatureFilePath = null;

		/*
		 * Retrieving image information from DICOMDIR file 
		 * (0x0004,0x1220)Directory Record Sequence contains information about all stored Images.
		 * In this sequence attribute(0x0004,0x1430) Directory Record Sequence specifies path information of an image.
		 */
		if(objIDataSet.isPresent(0x0004,0x1220))
		{
			ISingleCollection<IItem> objItemList = (ISingleCollection<IItem>)objIDataSet.getValue(0x0004,0x1220);

			for(IItem objItem : objItemList)
			{
				if(objItem.contains(0x0004,0x1430))
				{
					String strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

					if(EnumDirectoryRecordType.IMAGE.getValue().equals(strDirectoryRecordType))
					{
						if(objItem.contains(0x0004,0x1500))
						{
							/*
							 * Retrieving image file path information. 
							 * Truncating filename from path and concatenating this path with base directory.
							 * if Source and destination directory are not same then signature will be generated 
							 * in separate directory with the suffix specified
							 */
							Object objImageFileVaue = objItem.getValue(0x0004,0x1500);
							String strImageFilePath = null;
							if (objImageFileVaue instanceof String[]) 
								strImageFilePath = DataTypeConvertor.arrayToString((String[])objImageFileVaue);
							else
								strImageFilePath = objImageFileVaue.toString();
							strDICOMFilePath = strSourceDICOMDIRPath+File.separator+strImageFilePath;
							strSignatureFilePath = getFilePath(strSourceDICOMDIRPath,strImageFilePath,strSignatureDIR,strSignSuffix);

							//verifying signature for each file.
							boolean bStatus = verifySignature(strDICOMFilePath, strSignatureFilePath, objPublicKey);
							objStatusList.put(strDICOMFilePath,bStatus);
						}
					}
				}
			}
		}

		return objStatusList;
	}

	/**
	 * Verifies the DigitalSignature specified according to the provided secure DICOM file.
	 * In verification of Digital Signature it uses public key provided to it and SHA-1 as the digest algorithm.
	 * @param strSecureDICOMFile path to secure DICOM file.
	 * @param strDigitalSignaturePath path where generated signature will be dumped. 
	 * @param objPublicKey instance of public key.
	 * @throws InvalidKeyException if key provided to it is invalid.
	 * @throws FileNotFoundException if specified file is not found.
	 * @throws IOException if input/output operation fails.
	 * @throws SignatureException if signature generation fails.
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid.
	 * @return boolean - shows status as true or false
	 */
	public boolean verifySignature(String strSecureDICOMFile,String strDigitalSignaturePath,PublicKey objPublicKey)throws NoSuchAlgorithmException,InvalidKeyException,FileNotFoundException,IOException,SignatureException
	{
		//Initializing input stream to read the source data from file.
		FileInputStream objFileInputStream1 = new FileInputStream(strSecureDICOMFile);
		DicomFileStream objSecureFileStream = new DicomFileStream();
		objSecureFileStream.setInputStream(objFileInputStream1);

		//Initializing input stream to read the existing signature data from file.
		FileInputStream objFileInputStream2 = new FileInputStream(strDigitalSignaturePath);
		DicomFileStream objDigitalSignStream = new DicomFileStream();
		objDigitalSignStream.setInputStream(objFileInputStream2);

		boolean isVerified = verifySignature(objSecureFileStream, objDigitalSignStream, objPublicKey);

		objSecureFileStream.close();
		objDigitalSignStream.close();

		return isVerified;
	}

	/**
	 * Verifies the DigitalSignature provided on stream for source data available on source data stream
	 * In verification of Digital Signature it uses public key provided to it and SHA-1 as the digest algorithm.
	 * @param objSourceDataStream stream containing source data
	 * @param objExistingSignStream stream which contains digital signature 
	 * @param objPublicKey instance of public key.
	 * @throws InvalidKeyException if key provided to it is invalid.
	 * @throws IOException if input/output operation fails.
	 * @throws SignatureException if signature generation fails.
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid.
	 * @return boolean - shows status as true or false
	 */
	public boolean verifySignature(DicomStream objSourceDataStream,DicomStream objExistingSignStream,PublicKey objPublicKey)throws NoSuchAlgorithmException,InvalidKeyException,IOException,SignatureException
	{
		// if the Key is not specified then exception should be thrown as the signature can not be verified then.
		if(objPublicKey == null)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions","BasicDICOMMediaSecurityProfile.KeyNotSpecified",new Locale(DicomConfig.createInstance().getLocale())));

			}
			throw new ConfidentialityAttributeException(ResourceBundleWrapper.getValue("exceptions","BasicDICOMMediaSecurityProfile.KeyNotSpecified",new Locale(DicomConfig.createInstance().getLocale())));
		}

		//Creates instance of signature for specified algorithm
		Signature objSignature = Signature.getInstance(SIGN_ALGORITHM);

		//Initializes the signature with public key
		objSignature.initVerify(objPublicKey);

		byte []objDataBlock = null;
		while(objSourceDataStream.available() != 0)
		{
			if(objSourceDataStream.available() < iBlockSize)
				objDataBlock = new byte[objSourceDataStream.available()];
			else
				objDataBlock = new byte[iBlockSize];

			objSourceDataStream.readBuffer(objDataBlock);

			//Populating instance of signature in order to generate the digital signature
			objSignature.update(objDataBlock);
		}

		byte[] objExistingSignBlock = new byte[objExistingSignStream.available()];
		objExistingSignStream.readBuffer(objExistingSignBlock);

		//Performing verification
		boolean isVerified = objSignature.verify(objExistingSignBlock);

		return isVerified;
	}

	/**
	 * This method generates digest for each file in DICOMDIR file set.
	 * If Source and destination path are same then digest will be generated in the same directory , parralal to the image.
	 * Name of digest will be concatenation of file name and a suffix specified.
	 * If suffix is not specified then '_Digest' will be taken as default. 
	 * @param strSourceDICOMDIRPath path to the directory which contains DICOMDIR file and other images for which digest need to be generated.
	 * 								name of DICOMDIR file should not be included
	 * @param strDigestDIR path to the directory where new generated digest files will be stored.
	 * @param strDigestSuffix suffix value which will be concatenated with image file name to generate name of digest file.
	 * @throws FileNotFoundException if specified file path is invalid
	 * @throws IOException if input/output operation fails
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid
	 * @return IPairedCollection<String,Boolean> - map which contains file name and verification status.
	 */
	@SuppressWarnings("unchecked")
	public void generateDigestForFileSet(String strSourceDICOMDIRPath,String strDigestDIR,String strDigestSuffix)throws NoSuchAlgorithmException,FileNotFoundException,IOException
	{
		if(strDigestSuffix == null || "".equals(strDigestSuffix))
		{
			strDigestSuffix = "_Digest";
		}
		if(strDigestSuffix.startsWith("."))
		{
			DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "InvalidSuffix", new Locale(DicomConfig.createInstance().getLocale())));
			strDigestSuffix = "_Digest";
		}

		String strDIRPath = strSourceDICOMDIRPath + "//DICOMDIR";

		//Parsing DICOMDIR file to retrieve image information
		DicomParser objDicomParser = new DicomParser();
		IDataSet objIDataSet = objDicomParser.parseAsPart10File(strDIRPath,true);

		String strDICOMFilePath = null;
		String strDigestFilePath = null;

		/*
		 * Retrieving image information from DICOMDIR file 
		 * (0x0004,0x1220)Directory Record Sequence contains information about all stored Images.
		 * In this sequence attribute(0x0004,0x1430) Directory Record Sequence specifies path information of an image.
		 */
		if(objIDataSet.isPresent(0x0004,0x1220))
		{
			ISingleCollection<IItem> objItemList = (ISingleCollection<IItem>)objIDataSet.getValue(0x0004,0x1220);

			for(IItem objItem : objItemList)
			{
				if(objItem.contains(0x0004,0x1430))
				{
					String strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

					if(EnumDirectoryRecordType.IMAGE.getValue().equals(strDirectoryRecordType))
					{
						if(objItem.contains(0x0004,0x1500))
						{
							/*
							 * Retrieving image file path information. 
							 * Truncating filename from path and concatenating this path with base directory.
							 * if Source and destination directory are not same then signature will be generated 
							 * in separate directory with the suffix specified
							 */
							Object objImageFileVaue = objItem.getValue(0x0004,0x1500);
							String strImageFilePath = null;
							if (objImageFileVaue instanceof String[]) 
								strImageFilePath = DataTypeConvertor.arrayToString((String[])objImageFileVaue);
							else
								strImageFilePath = objImageFileVaue.toString();
							strDICOMFilePath = strSourceDICOMDIRPath+File.separator+strImageFilePath;

							strDigestFilePath = getFilePath(strSourceDICOMDIRPath,strImageFilePath,strDigestDIR,strDigestSuffix);
							//generating digest for each file.
							generateDigest(strDICOMFilePath, strDigestFilePath);
						}
					}
				}
			}
		}
	}


	/**
	 * Generates digest(hash code) for file provided and accordingly dump it on given path.
	 * In digest generation it uses SHA1 algorithm.
	 * @param strSecureDICOMFile path to secure dicom file for which digest(hash code) need to be generated.
	 * @param strDigestPath path where generated digest(hash code) will be dumped.
	 * @throws FileNotFoundException if specified file path is invalid
	 * @throws IOException if input/output operation fails
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid
	 */
	public void generateDigest(String strSecureDICOMFile,String strDigestPath)throws NoSuchAlgorithmException,FileNotFoundException,IOException
	{
		DicomFileStream objDicomFileStream = new DicomFileStream();

		//Initializing the input stream to read the data.
		FileInputStream objFileInputStream = new FileInputStream(strSecureDICOMFile);
		objDicomFileStream.setInputStream(objFileInputStream);

		try
		{
			DicomParser objDicomParser = new DicomParser();
			objDicomParser.parseAsPart10File(objDicomFileStream, true);
		}
		catch(Exception ex)
		{			
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("exceptions","DigestGenerationFailed",new Locale(DicomConfig.createInstance().getLocale()))+"("+strSecureDICOMFile+"): "+ ex.getMessage());
			}			
			throw new InvalidDICOMFileException(ex.getMessage(),ex);
		}
		
		objFileInputStream = new FileInputStream(strSecureDICOMFile);
		objDicomFileStream.setInputStream(objFileInputStream);
		
		//Initializing the output stream to write the digest data.
		FileOutputStream objFileOutputStream = new FileOutputStream(strDigestPath);
		objDicomFileStream.setOutputStream(objFileOutputStream);

		//Generating Digest
		generateDigest(objDicomFileStream,objDicomFileStream);

		objDicomFileStream.close();
	}

	/**
	 * Generates digest(hash code) for data available on source stream.
	 * Generated digest will be serialized on destination stream provided to it
	 * In digest generation it uses SHA1 algorithm.
	 * @param objSourceDataStream stream containing source data
	 * @param objDestDataStream stream on which generated digest will be serialized
	 * @throws IOException if input/output operation fails
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid
	 */
	public void generateDigest(DicomStream objSourceDataStream,DicomStream objDestDataStream)throws NoSuchAlgorithmException,IOException
	{
		//Initializing Message Digest according to the algorithm specified.
		MessageDigest objMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);

		byte []objDataBlock = null;
		while(objSourceDataStream.available() != 0)
		{
			if(objSourceDataStream.available() < iBlockSize)
				objDataBlock = new byte[objSourceDataStream.available()];
			else
				objDataBlock = new byte[iBlockSize];

			objSourceDataStream.readBuffer(objDataBlock);

			//Populating source data in instance of digest 
			objMessageDigest.update(objDataBlock);
		}

		//Generating Digest
		byte[] objDigestBlock = objMessageDigest.digest();

		//Writing digest data on destination stream
		objDestDataStream.writeBuffer(objDigestBlock, 0, objDigestBlock.length);
	}

	/**
	 * This method verifies digest for each file in DICOMDIR file set.
	 * If Source and destination path are same then digest will be seen in the same directory , parallel to the image.
	 * Name of digest will be concatenation of file name and a suffix specified.
	 * If suffix is not specified then '_Digest' will be taken as default. 
	 * @param strSourceDICOMDIRPath path to the directory which contains DICOMDIR file and other images for which digest need to be verified.
	 * 								name of DICOMDIR file should not be included
	 * @param strDigestDIR path to the directory where original digest files are stored.
	 * @param strDigestSuffix suffix value which will be concatenated with image file name to generate name of digest file.
	 * @throws FileNotFoundException if specified file path is invalid
	 * @throws IOException if input/output operation fails
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid
	 */
	@SuppressWarnings("unchecked")
	public IPairedCollection<String,Boolean> verifyDigestForFileSet(String strSourceDICOMDIRPath,String strDigestDIR,String strDigestSuffix)throws NoSuchAlgorithmException,FileNotFoundException,IOException
	{
		if(strDigestSuffix == null || "".equals(strDigestSuffix))
		{
			strDigestSuffix = "_Digest";
		}
		if(strDigestSuffix.startsWith("."))
		{
			DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "InvalidSuffix", new Locale(DicomConfig.createInstance().getLocale())));
			strDigestSuffix = "_Digest";
		}

		IPairedCollection<String,Boolean> objStatusList = new PairedCollection<String, Boolean>();
		String strDIRPath = strSourceDICOMDIRPath +File.separator+ "DICOMDIR";

		//Parsing DICOMDIR file to retrieve image information
		DicomParser objDicomParser = new DicomParser();
		IDataSet objIDataSet = objDicomParser.parseAsPart10File(strDIRPath,true);

		String strDICOMFilePath = null;
		String strDigestFilePath = null;

		/*
		 * Retrieving image information from DICOMDIR file 
		 * (0x0004,0x1220)Directory Record Sequence contains information about all stored Images.
		 * In this sequence attribute(0x0004,0x1430) Directory Record Sequence specifies path information of an image.
		 */
		if(objIDataSet.isPresent(0x0004,0x1220))
		{
			ISingleCollection<IItem> objItemList = (ISingleCollection<IItem>)objIDataSet.getValue(0x0004,0x1220);

			for(IItem objItem : objItemList)
			{
				if(objItem.contains(0x0004,0x1430))
				{
					String strDirectoryRecordType = objItem.getValue(0x0004,0x1430).toString();

					if(EnumDirectoryRecordType.IMAGE.getValue().equals(strDirectoryRecordType))
					{
						if(objItem.contains(0x0004,0x1500))
						{
							/*
							 * Retrieving image file path information. 
							 * Truncating filename from path and concatenating this path with base directory.
							 * if Source and destination directory are not same then signature will be generated 
							 * in separate directory with the suffix specified
							 */
							Object objImageFileVaue = objItem.getValue(0x0004,0x1500);
							String strImageFilePath = null;
							if (objImageFileVaue instanceof String[]) 
								strImageFilePath = DataTypeConvertor.arrayToString((String[])objImageFileVaue);
							else
								strImageFilePath = objImageFileVaue.toString();


							strDICOMFilePath = strSourceDICOMDIRPath+File.separator+strImageFilePath;

							strDigestFilePath = getFilePath(strSourceDICOMDIRPath,strImageFilePath,strDigestDIR,strDigestSuffix);

							//verifying digest for each file.
							boolean bStatus = verifyDigest(strDICOMFilePath, strDigestFilePath);
							objStatusList.put(strDICOMFilePath,bStatus);
						}
					}
				}
			}
		}

		return objStatusList;
	}

	/**
	 * Verifies digest(hash code) provided on given location according to the secure dicom file specified
	 * In digest generation it uses SHA1 algorithm.
	 * @param strSecureDICOMFile path to secure dicom file for which digest(hash code) need to be generated.
	 * @param strDigestPath path where generated digest(hash code) will be dumped.
	 * @throws FileNotFoundException if specified file path is invalid
	 * @throws IOException if input/output operation fails
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid
	 * @return boolean - shows status as true or false
	 */
	public boolean verifyDigest(String strSecureDICOMFile,String strDigestPath)throws NoSuchAlgorithmException,FileNotFoundException,IOException
	{
		//Initializing the input stream to read the source data.
		FileInputStream objFileInputStream1 = new FileInputStream(strSecureDICOMFile);
		DicomFileStream objSecureFileStream = new DicomFileStream();
		objSecureFileStream.setInputStream(objFileInputStream1);

		//Initializing the input stream to read the existing digest data.
		FileInputStream objFileInputStream2 = new FileInputStream(strDigestPath);
		DicomFileStream objDigestFileStream = new DicomFileStream();
		objDigestFileStream.setInputStream(objFileInputStream2);

		boolean isVerfied = verifyDigest(objSecureFileStream, objDigestFileStream);

		objSecureFileStream.close();
		objDigestFileStream.close();

		return isVerfied;
	}

	/**
	 * Verifies digest(hash code) provided on given stream for source data available on source data stream.
	 * In digest generation it uses SHA1 algorithm.
	 * @param objSourceDataStream stream containing source data
	 * @param objDigestDataStream stream containing digest data
	 * @throws IOException if input/output operation fails
	 * @throws NoSuchAlgorithmException if algorithm specified is invalid
	 * @return boolean - shows status as true or false
	 */
	public boolean verifyDigest(DicomStream objSourceDataStream,DicomStream objDigestDataStream)throws NoSuchAlgorithmException,IOException
	{
		//Initializing Message Digest according to the algorithm specified.
		MessageDigest objMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);

		byte []objDataBlock = null;
		while(objSourceDataStream.available() != 0)
		{
			if(objSourceDataStream.available() < iBlockSize)
				objDataBlock = new byte[objSourceDataStream.available()];
			else
				objDataBlock = new byte[iBlockSize];

			objSourceDataStream.readBuffer(objDataBlock);

			//Populating source data in instance of MessageDigest
			objMessageDigest.update(objDataBlock);
		}

		//Generating Digest for secure dicom file
		byte[] objGeneratedDigest = objMessageDigest.digest();

		//Reading digest data from file
		byte[] objExistingDigestBlock = new byte[objDigestDataStream.available()];
		objDigestDataStream.readBuffer(objExistingDigestBlock);

		//Verifying digests
		boolean isVerified = MessageDigest.isEqual(objGeneratedDigest,objExistingDigestBlock);

		return isVerified;
	}

	/*
	 * Retrieves Parent Directory Name for a provided file name.
	 */
	private String getTempFilePath(String strSource)
	{
		File objFile = new File(strSource);
		String strParent = objFile.getParent();
		objFile = null;
		return strParent;
	}

	/*
	 * Rename one file name with other file name.
	 * @param strTo new name after renaming operation.
	 * @param strFrom old name before renaming operation
	 */
	private boolean updateFile(String strTo,String strFrom)
	{
		File objFile1 = new File(strTo);
		File objFile2 = new File(strFrom);
		boolean status = objFile2.renameTo(objFile1);
		objFile1 = null;
		objFile2 = null;
		return status;
	}

	/*
	 * Copy one file to another location
	 * @param strSource source path from where file need to be copied
	 * @param strDestination destination path where file need to be copied 
	 */
	private boolean copyFile(String strSource,String strDestination)throws IOException
	{
		FileInputStream objFileInputStream = new FileInputStream(strSource);
		FileOutputStream objFileOutputStream = new FileOutputStream(strDestination);
		byte[] arrBlock = new byte[1024];
		while(objFileInputStream.available() != 0 && objFileInputStream.available() >= 1024)
		{
			objFileInputStream.read(arrBlock);
			objFileOutputStream.write(arrBlock);
		}

		if(objFileInputStream.available() != 0)
		{
			arrBlock = new byte[objFileInputStream.available()];
			objFileInputStream.read(arrBlock);
			objFileOutputStream.write(arrBlock);
		}

		objFileInputStream.close();
		objFileOutputStream.close();

		return true;
	}
}
