/////////////////////////////////////////////////////////////////////////
//
// © University of Southampton IT Innovation Centre, 2004
//
// Copyright in this library belongs to the IT Innovation Centre of
// 2 Venture Road, Chilworth Science Park, Southampton, SO16 7NP, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//      Created by:             Stuart E. Middleton
//      Created date:           2004/04/30
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.applications.rapt;

import java.util.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import java.io.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JFileChooser;
import javax.swing.UIManager;
import javax.swing.filechooser.FileFilter;

import org.w3c.dom.Document;

import eu.gemss.GEMSS;
import eu.gemss.components.Configuration;
import eu.gemss.GridException;
import eu.gemss.components.ComponentManager;
import eu.gemss.components.transport.NetworkException;
import eu.gemss.signals.*;
import eu.gemss.signals.negotiation.*;
import eu.gemss.signals.security.*;
import eu.gemss.components.proxies.*;

import uk.ac.soton.itinnovation.gemss.*;
import uk.ac.soton.itinnovation.gemss.utils.signalhandlers.*;
import uk.ac.soton.itinnovation.gemss.utils.configuration.ConfigurationException;
import uk.ac.soton.itinnovation.gemss.applications.rapt.RAPTGridClientPanel;

/*
* RAPT Grid client. This client will take as input parameter a config filename. If the config file has
* a session entry it will load and execute the session, resuming where a previous run left off. If the
* session entry is not there it will run a RAPT job, using the input and output filenames.
* If the user saves and exits then a DOC error code of 2 is returned. If an error occurs and it cannot
* be recovered from then an error code of 1 is returned. If the job is successfully processes and the
* output zip file created then an error code of 0 is returned. It is expected that a DOS script file
* be written to respond appropriately to these error codes, and invoke the matlab output GUI etc as
* needed.
*/
public class RAPTGridClient {

	// constants
      private static final String mstaticServiceName = "serviceName";
	private static final String mstaticInputFilename = "inputFilename";
      private static final String mstaticRequestDescFilename = "requestDescFilename";
	private static final String mstaticOutputFilename = "outputFilename";
	private static final String mstaticProxyConfigFile = "ProxyConfigFile";
      private static final String mstaticQoSConfigFile = "QoSConfigFile";
	private static final String mstaticFastRetry = "FastRetry";
      private static final String mstaticStatusDelay = "StatusDelay";
	private static final String mstaticLogo = "logoFilename";
	private static final String mstaticSession = "sessionName";

	private static final String mstaticNegotiatorIDTag = "SampleClientNegotiatorID";
	private static final String mstaticTitle = "RAPT Grid Client v1.0";
	private static final String mstaticSessionSuffix = ".rapt_session";

	private static final String mstaticNegotiatorHeader = "Negotiator status:\n\n";
	private static final String mstaticJobStatusHeader = "RAPT job status:\n\n";

	// member variables
      private String mstrServiceName;
	private String mstrConfigFilename;
	private GEMSSNegotiator mnegotiator;
	private String mstrInputFilename;
      private String mstrRequestDescFile;
	private String mstrOutputFilename;
	private String mstrProxyConfigFile;
      private String mstrQoSConfigFile;
	private int mnStatusDelay;
	private String mstrLogoFilename;
	private String mstrSessionFilename;

	// exit error code
	private int mnExitErrorCode;

	// interface
	private RAPTGridClientPanel minterfaceObject;
	private JFrame mframe;

	/*
	* default constructor
	*/
	public RAPTGridClient() {
            mstrServiceName = null;
		mstrConfigFilename = null;
		mnegotiator = null;
		mstrInputFilename = null;
            mstrRequestDescFile = null;
		mstrOutputFilename = null;
		mstrProxyConfigFile = null;
            mstrQoSConfigFile = null;
		mnStatusDelay = 0;
		mstrLogoFilename = null;
		mstrSessionFilename = null;
		mnExitErrorCode = 2; // "saved" is default error code
	}

	/*
	* Check if a flag has been set in the proxy flag set
	* @param strFlag The flag to check
	* @return true if the flag is now set
	*/
	public boolean checkFlag( String strFlag )
	{
		boolean bOk;
		Vector vectorFlags;
		long ntimeStart, ntimeNow;
		int i;
		String strText;

		try {
			// check proxy is ok, if not flag cannot be set
			if( mnegotiator == null ) {
				if( minterfaceObject != null ) minterfaceObject.setNegotiatorStatusText( mstaticNegotiatorHeader + "unavailable at present" );
				return false;
			}

			// get the negotiator flags
			vectorFlags = mnegotiator.getNegotiatorFlags();

			// update the interface flag display with new copy
			if( minterfaceObject != null ) {
				strText = mstaticNegotiatorHeader;
				if( vectorFlags.size() == 0 ) {
					strText += "No state flags yet set";
				}
				else {
					for( i=0;i<vectorFlags.size();i++ ) {
						strText += "Flag ";
						strText += (String) vectorFlags.elementAt(i);
						strText += " set\n";
					}
				}
				minterfaceObject.setNegotiatorStatusText( strText );
			}

			// check for the flag
			if( vectorFlags.contains( strFlag ) ) return true;

			// nope
			return false;
		}
		catch( Exception ex ) {
			// report failure details
			System.out.println("Exception checking for flag "+strFlag+" = "+ex.getMessage());
			ex.printStackTrace( System.out );
			return false;
		}
	}

	/*
	* Create the interface frame and other bits
	*/
	public void PopupInterface()
	{
		try {
			// make the interface object
			minterfaceObject = new RAPTGridClientPanel( mstrLogoFilename,this );
			minterfaceObject.setOpaque(true); //content panes must be opaque

			// Make sure we have nice window decorations
			UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName() );
			JFrame.setDefaultLookAndFeelDecorated( true );

			// Create and set up the frame
			mframe = new JFrame( mstaticTitle + " : <new job>" );
			mframe.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
			mframe.addWindowListener( new FrameWindowListener() );

			// Create and set up the content pane
			mframe.getContentPane().setLayout( new GridLayout(1,1) );
			mframe.getContentPane().add( minterfaceObject );
			mframe.setResizable( true );

			// Display the window
			mframe.pack();
			mframe.setVisible( true );
		}
		catch( Exception ex ) {
			System.out.println("Exception creating interface "+ex.getMessage());
			ex.printStackTrace( System.out );
		}
	}

	/*
	* Save the proxy state callback function and exit if required. This can be called after each state
	* change to implement checkpointing of the client progress (in case of a power cut etc). The config file
	* will have the automatically generated session file name addeded to it once this session name has been
	* allocated (no check is made to see if it's already there so only use this method with the RAPT
	* controlling batch file that creates the config file in the first place).
	* @param bExit If true then the client will exit immediately with the save and exit error code (2)
	*/
	public void SaveCallbackHandler( boolean bExit )
	{
		ComponentManager compMan;
		PrintWriter writer;

		try {
			// get the comp man
			compMan = GEMSS.getInstance();
			if( compMan == null ) throw new Exception("null component manager");

			// make up a new session filename (if not already set)
			if( mstrSessionFilename == null ) {
				// make a new and unique session name based on the current time
				mstrSessionFilename = new String("RAPT-Grid-Session-"+System.currentTimeMillis());

				// append the session name to the config file for future reference so if this app
				// is re-started it will know what the session is called.
				writer = new PrintWriter( new FileOutputStream( mstrConfigFilename,true ) );
				writer.println();
				writer.println("#Session name entry automatically added by the RAPT grid client [do not edit]");
				writer.println( mstaticSession+"="+mstrSessionFilename );
				writer.close();
			}

			// save session (overwriting any existing session one)
			if( !compMan.manageInstance( mstaticNegotiatorIDTag,mnegotiator ) ) {
				System.out.println("Session ID already managed, ignoring");
			}
			try {
				System.out.println("Saving session to " +mstrSessionFilename);
				compMan.saveSession( mstrSessionFilename );
			}
			catch( IOException ie ) {
				System.out.println("Session failed to save to "+mstrSessionFilename+" : "+ie.getMessage());
				ie.printStackTrace( System.out );
				JOptionPane.showMessageDialog( minterfaceObject,"Session state failed to save to "+mstrSessionFilename+". Please check the 'gemss.config' sessions property is correctly set-up, or check logs for more details about this error");
				return;
			}

			// update the frame title
			if( mframe != null ) mframe.setTitle( mstaticTitle+" : "+mstrSessionFilename );

			// session saved ok, report to user
			System.out.println("Session saved ok to "+mstrSessionFilename);

			// force exit and throw the exit code for save and exit
			if( bExit ) System.exit(2);
		}
		catch( Exception ex ) {
			JOptionPane.showMessageDialog( minterfaceObject,"Session failed to save ["+mstrSessionFilename+"]");
			System.out.println("Exception saving "+ex.getMessage());
			ex.printStackTrace( System.out );
		}
	}

	/*
	* Load a session from the session file
	* @param strFilename filename of the session file to be loaded.
	* @return false if returned on error
	*/
	public boolean LoadSession( String strFilename )
	{
		ComponentManager compMan;
		JFileChooser fileDialogue;
		eu.gemss.components.Configuration config;

		try {
			// get the comp man
			compMan = GEMSS.getInstance();
			if( compMan == null ) throw new Exception("null component manager");

			// check filename is valid
			if( strFilename == null || strFilename.equals("") ) throw new Exception("Load session aborted - invalid filename");

			// load the session
			compMan.loadSession( strFilename );

			// get the previosuly saved instance of the negotiator (if any - it could be null)
			mnegotiator = (GEMSSNegotiator) compMan.getManagedInstance( mstaticNegotiatorIDTag );

			// update the frame title
			if( mframe != null ) mframe.setTitle( mstaticTitle + " : "+strFilename );

			// session loaded ok, report to user
			System.out.println("Grid session loaded ok from "+strFilename);
			JOptionPane.showMessageDialog( minterfaceObject,"Grid session loaded ok from "+strFilename);

			// set flags in interface
			checkFlag(""); // this call will set the interface flags, we ignore the results here

			// all ok
			return true;
		}
		catch( Exception ex ) {
			JOptionPane.showMessageDialog( minterfaceObject,"Grid session failed to load ["+strFilename+"]");
			System.out.println("Exception loading Grid session "+ex.getMessage());
			ex.printStackTrace( System.out );
			return false;
		}
	}

	/*
	* Run method to start the sample client. The config file holds most of the
	* parameters such as the service endpoint information and input / output
	* filenames.
	* @param strConfigFilename configuration filename
	* @return immediate error code 0 = success, 1 = fail, 2 = save and exit. If the popup window has
	* been successfully created then this method will return 0 and let the window listener invoke
	* the System.exit call with the appropriate error code
	*/
	public int run( String strConfigFilename )
	{
		uk.ac.soton.itinnovation.gemss.utils.configuration.Configuration config;
		eu.gemss.components.ComponentManager componentManager;
		SignalHandler handler;
		GEMSSNegotiatorRequest negotiatorRequest;
		GridException gridException;
		String strStatus;
		Vector vectorFlags;
		boolean bOk;
		int i;

		try{
			// This RAPT client uses a configuration file for obtaining certain values needed to run
			mstrConfigFilename = strConfigFilename;
			config = new uk.ac.soton.itinnovation.gemss.utils.configuration.Configuration( mstrConfigFilename );

			// get the config parameters
                  mstrServiceName = config.getConfigurationValue( mstaticServiceName );
			mstrProxyConfigFile = config.getConfigurationValue( mstaticProxyConfigFile );
                  mstrQoSConfigFile = config.getConfigurationValue( mstaticQoSConfigFile );
			mstrInputFilename = config.getConfigurationValue( mstaticInputFilename );
                  mstrRequestDescFile = config.getConfigurationValue( mstrRequestDescFile );
			mstrOutputFilename = config.getConfigurationValue( mstaticOutputFilename );
			mstrLogoFilename = config.getConfigurationValue( mstaticLogo );
                  mnStatusDelay = Integer.valueOf( config.getConfigurationValue( mstaticStatusDelay ) ).intValue();
			// optional session filename to be loaded
			try { mstrSessionFilename = config.getConfigurationValue( mstaticSession ); }
			catch( ConfigurationException ce ) { mstrSessionFilename = null; } // ignore if missing

			// print parameters so user knows what is going on
                  System.out.println("Service name = "+mstrServiceName);
			System.out.println("Proxy config filename = "+mstrProxyConfigFile);
                  System.out.println("QoS config filename = "+mstrQoSConfigFile);
			System.out.println("Input archive file = "+mstrInputFilename);
			System.out.println("Request desc file = "+mstrRequestDescFile);
			System.out.println("Output archive file  = "+mstrOutputFilename);
			System.out.println("Status poll delay = "+mnStatusDelay+" ms");
			System.out.println("Logo filename = "+mstrLogoFilename);
			if( mstrSessionFilename != null ) System.out.println("Session = "+mstrSessionFilename);

			// popup the interface
			PopupInterface();
		}
		catch( Exception ex ) {
			// pre-interface exceptions - immediate exit is required since the window listener
			// on the JFrame will not have been created.
			System.out.println("Unrecognised failure reason: ");
			ex.printStackTrace();
			mnExitErrorCode = 1;
			return mnExitErrorCode; // end
		}

		// interface is now enabled, so we will ALWAYS return a 0 error code and let the window
		// listsner on the frame do the System.exit with the right error code. This lets the
		// interface hang around long enough for the user to read the information we make
		// available
		try {
			// set the config text and default text box messages (ignore logo name)
			minterfaceObject.setConfigurationText( "Configuration parameters:\n\n"+
				"Input archive file = "+mstrInputFilename+"\n"+
	                  "Output archive file  = "+mstrOutputFilename+ "\n"+
			      "Proxy config file = "+mstrProxyConfigFile+ "\n" );
			minterfaceObject.setJobStatusText( mstaticJobStatusHeader+"Unavailable at present" );
			minterfaceObject.setNegotiatorStatusText( mstaticNegotiatorHeader+"Unavailable at present" );

			// load session if needed
			// after ths call the negotiator may or may not be set, and could have a number
			// of flags set already. As such before each workflow action the flags will
			// be checked to see if this action has been performed already.
			if( mstrSessionFilename != null ) {
				if( !LoadSession( mstrSessionFilename ) ) throw new Exception("Failed to load session "+mstrSessionFilename);
			}

			// make a negotiator if we need a new one
			if( mnegotiator == null ) {
				// get the framework's instance of the GEMSS component manager.
				System.out.println("Starting the component manager");
				componentManager = GEMSS.getInstance();
				if( componentManager == null ) {
					minterfaceObject.setNegotiatorStatusText( mstaticNegotiatorHeader+"Negotiator stopped due to an infrastructure error [component manager].\nCheck logs for more information." );
					minterfaceObject.haltInterface( true );
					throw new Exception("Component manager object null");
				}

				// At this point it is a good idea to register any signal handlers you have implemented for dealing with
				// signals. Initially the only signal that is necessary to handle is 'eu.gemss.signals.security.CredentialRequest'.
				// This signal is used to obtain credential information. A sample signal handler will be registered here.
				System.out.println("Registering signal handlers");
				handler = new uk.ac.soton.itinnovation.gemss.utils.signalhandlers.CredentialRequestGUIHandler();
				componentManager.registerSignalHandler( handler,eu.gemss.signals.security.CredentialRequest.SIGNAL_TYPE );

				// ask the component manager for a GEMSS negotiator instance. Each GEMSSNegotiator instance
				// will be bound to a single application service, and be used to run a single job.
				// You can create many negotiators to run many jobs simultaniously.
				// The GEMSSNegotiatorRequest class holds the init data about the target service. Once service discovery has
				// been included into the framework, the negotiator will not need the specific WSDL URI etc and will be
				// able to find this out for itself.
				System.out.println("Asking for a GEMSSNegotiator to run the job");
				negotiatorRequest = new GEMSSNegotiatorRequest( mstrRequestDescFile, mstrServiceName, mstrQoSConfigFile, mstrProxyConfigFile, "eu.gemss.components.proxies.GEMSSNegotiator", null, null );
				mnegotiator = (eu.gemss.components.proxies.GEMSSNegotiator) componentManager.getInstance( negotiatorRequest );
				if( mnegotiator == null ) {
					minterfaceObject.setNegotiatorStatusText( mstaticNegotiatorHeader+"Negotiator stopped due to an infrastructure error [Proxy].\nCheck logs for more information." );
					minterfaceObject.haltInterface( true );
					throw new Exception("GEMSS Negotiator object null");
				}
			} // end of negotiator null check

			// save state
			SaveCallbackHandler( false );

			// wait for the negotiator to finish setting up the job. It will perform any discovery and
			// negotiation steps, and call getCId to get a job conversation ID. Once it has finished the
			// negotiator will be bound to a specific service and ready for use.
			// If the WSDL URI is provided explicitly then this negotiation step is more trivial, and the
			// discovery step bypassed so as to use the URI provided.
			System.out.println("Waiting for negotiation to finish");
			bOk = false;
			while( !bOk ) {
				// check the negotiator flags
				if( checkFlag( GEMSSNegotiator.mstaticNegotiatorHaltedFlag ) ) {
					// an error has occured during negotiation
					gridException = mnegotiator.getNegotiatorException();
					if( gridException != null ) throw gridException;
					throw new Exception( "Negotiator halted with a null exception" );
				}
				else if( checkFlag( GEMSSNegotiationProxy.mstaticNegCompleteFlag ) &&
					checkFlag( GEMSSNegotiationProxy.mstaticJobPrepFlag ) ) {
					// both the negotiation AND job preperation are complete, so now is the time to move on
					// and do the job handling thing.
					bOk = true;
				}
				else {
					// wait a short time (1 second) for the thread to do something more
					try { Thread.sleep( 1000 ); } catch( InterruptedException ie ) { ; }
				}
			} // loop on negotiation

			// update interface
			minterfaceObject.setWorkflowNodeValue( 0,3 ); // job prep finished
			minterfaceObject.setWorkflowNodeValue( 1,1 ); // upload started

			// save state
			SaveCallbackHandler( false );

			// upload the input archive file to the service
			if( !checkFlag( GEMSSJobHandlingProxy.mstaticUploadCompleteFlag ) ) {
				System.out.println("Input data uploading");
				bOk = false;
				while( !bOk ) {
					// try to upload data
					try {
						mnegotiator.uploadData( mstrInputFilename );
						bOk = true;
					}
					catch( GridException gridex ) {
						// abort if anything other than a network problem
						// otherwise keep trying again and again until timeout
						if( !(gridex instanceof NetworkException) ) throw gridex;
					}
				} // loop until successful
			} // end upload flag check

			// update interface
			minterfaceObject.setWorkflowNodeValue( 1,3 ); // upload finished
			minterfaceObject.setWorkflowNodeValue( 2,1 ); // start started

			// save state
			SaveCallbackHandler( false );

			// start the job
			if( !checkFlag( GEMSSJobHandlingProxy.mstaticJobStartedFlag ) ) {
				System.out.println("Starting job");
				bOk = false;
				while( !bOk ) {
					// try to start data
					try {
						mnegotiator.start();
						bOk = true;
					}
					catch( GridException gridex ) {
						// abort if anything other than a network problem
						// otherwise keep trying again and again until timeout
						if( !(gridex instanceof NetworkException) ) throw gridex;
					}
				} // loop until successful
			} // end started flag check

			// update interface
			minterfaceObject.setWorkflowNodeValue( 2,3 ); // start finished
			minterfaceObject.setWorkflowNodeValue( 3,1 ); // running started

			// save state
			SaveCallbackHandler( false );

			// set the negotiator handle into the interface (for kill + download intermediate files)
			// do now since the job has just started and so both kill and download intermediate
			// files become possible
			minterfaceObject.setNegotiator( mnegotiator );

			// do we need to check if job has finished and download it?
			if( !checkFlag( GEMSSJobHandlingProxy.mstaticFinalDownloadCompleteFlag ) ) {
				// wait for the job to finish, calling getStatus regularly to get an idea on the
				// progress so far
				bOk = false;
				do {
					System.out.println("Checking status");

					// get status
					try {
						strStatus = mnegotiator.getStatus();
					}
					catch( GridException gridex ) {
						// abort if anything other than a network problem
						// otherwise keep trying again and again until timeout
						if( !(gridex instanceof NetworkException) ) throw gridex;
						strStatus = "unavailable due to network connection problems";
					}

					// get the proxy flags
					vectorFlags = mnegotiator.getNegotiatorFlags();

					// has the job finished, failed or been killed?
					if( vectorFlags.contains( GEMSSJobHandlingProxy.mstaticJobFinishedFlag ) ) {
						bOk = true;
						// update status text
						minterfaceObject.setJobStatusText( mstaticJobStatusHeader+"Job has finished" );
						minterfaceObject.jobFinished();
					}
					else if( vectorFlags.contains( GEMSSJobHandlingProxy.mstaticJobFailedFlag ) ) {
						// update status text
						minterfaceObject.setJobStatusText( mstaticJobStatusHeader+"Job has failed. Ask your service provider for more details or change the input data and try again." );
						minterfaceObject.haltInterface( true );

						// terminate with an error code
						mnExitErrorCode = 1;
						return 0; // do not exit immediately
					}
					else if( vectorFlags.contains( GEMSSJobHandlingProxy.mstaticJobKilledFlag ) ) {
						// update status text
						minterfaceObject.setJobStatusText( mstaticJobStatusHeader+"Job has been killed." );
						minterfaceObject.haltInterface( true );

						// terminate with an error code
						mnExitErrorCode = 1;
						return 0; // do not exit immediately
					}
					else {
						// update interface with the job status report
						minterfaceObject.setJobStatusText( mstaticJobStatusHeader + strStatus );

						// The job is on-going, so report the status
						System.out.println( mstaticNegotiatorHeader + strStatus );

                                    // wait for the status delay time to avoid polling continously for long jobs
                                    try { Thread.sleep( mnStatusDelay ); } catch( InterruptedException ie ) { ; }
					}
				} while( !bOk );

				// update interface
				minterfaceObject.setWorkflowNodeValue( 3,3 ); // running finished
				minterfaceObject.setWorkflowNodeValue( 4,1 ); // download started

				// save state
				SaveCallbackHandler( false );

				// download the result data. The empty string argument means that all the result
				// data will be downloaded and the final download flag set. You can specify filenames
				// to download as intermediate downloads if you like, and this can be useful to see how
				// a job is going or to look at partial / low-res results quickly.
				System.out.println("Downloading data");
				bOk = false;
				while( !bOk ) {
					// try to download data
					try {
						mnegotiator.downloadData( "",mstrOutputFilename );
						bOk = true;
					}
					catch( GridException gridex ) {
						// abort if anything other than a network problem
						// otherwise keep trying again and again until timeout
						if( !(gridex instanceof NetworkException) ) throw gridex;
					}
				} // loop until successful
			} // end download flag check

			// update interface
			minterfaceObject.setWorkflowNodeValue( 4,3 ); // download finished
			minterfaceObject.setWorkflowNodeValue( 5,1 ); // acknowledge started

			// save state
			SaveCallbackHandler( false );

			// acknowledge the results
			if( !checkFlag( GEMSSJobHandlingProxy.mstaticAcknowledgementSentFlag ) ) {
				System.out.println("Acknowledging results");
				bOk = false;
				while( !bOk ) {
					// try to acknowledge results
					try {
						mnegotiator.acknowledgeResults();
						bOk = true;
					}
					catch( GridException gridex ) {
						// abort if anything other than a network problem
						// otherwise keep trying again and again until timeout
						if( !(gridex instanceof NetworkException) ) throw gridex;
					}
				} // loop until successful
			} // end acknowledgeResults check

			// update interface
			minterfaceObject.setWorkflowNodeValue( 5,3 ); // acknowledege finished
			minterfaceObject.setWorkflowNodeValue( 6,3 ); // finished finished

			// thats it - job run ok and redy to end.
			minterfaceObject.haltInterface( false );
			minterfaceObject.setNegotiatorStatusText( mstaticNegotiatorHeader+"Negotiator has finished after a successful run.\nThe result data is now available at "+mstrOutputFilename+".\nClose the RAPT Grid Client to automatically invoke the RAPT output visualization tool and inspect your job's result data." );

			System.out.println("Job processing complete, data saved to "+mstrOutputFilename);
			mnExitErrorCode = 0;

			// delete the session file completely since it's not needed anymore
			componentManager = GEMSS.getInstance();
			if( componentManager == null ) throw new Exception("Component manager object null (2)");
			if( !componentManager.deleteSession( mstrSessionFilename ) ) throw new Exception("Failed to delete session file");

			// all done
			return 0; // we do not want immediate exit
		}
		catch( GridException gridex ) {
			// update interface
			if( minterfaceObject != null ) {
				minterfaceObject.setNegotiatorStatusText( mstaticNegotiatorHeader+"Negotiator stopped due to an Grid error.\n"+gridex.getUserMessage()+"\nCheck logs for more information." );
				minterfaceObject.haltInterface( true );
			}
			System.out.println("GridException: "+gridex.getUserMessage());
			mnExitErrorCode = 1;
			return 0; // we do not want immediate exit
		}
		catch( Exception ex ) {
			// update interface
			if( minterfaceObject != null ) {
				minterfaceObject.setNegotiatorStatusText( mstaticNegotiatorHeader+"Negotiator stopped due to an infrastructure error [fatal exception].\nCheck logs for more information." );
				minterfaceObject.haltInterface( true );
			}
			System.out.println("Failure reason: ");
			ex.printStackTrace();
			mnExitErrorCode = 1;
			return 0; // we do not want immediate exit
		}
	} // end of run

	/*
	* set the shutdown hook
	*/
	public void setShutdownHook() {
		//add shutdown hook
		ShutdownThread shutdownHook = new ShutdownThread();
		Runtime.getRuntime().addShutdownHook(shutdownHook);
	} // end of setShutdownHook

	/*
	* shutdown class to respond to a kill request
	*/
	class ShutdownThread extends Thread {
		/*
		* constructor
		*/
		public ShutdownThread() {
			super();
		}

		/*
		* shutdown the component manager
		*/
		public void run() {
			Date date;
			try{
				System.out.println("[Shutdown thread] Shutting down");
				if( mnegotiator != null ) GEMSS.getInstance().terminate( mnegotiator );
				System.out.println("[Shutdown thread] Shutdown complete");
				// note end time
				date = new Date( System.currentTimeMillis() );
				System.out.println( "Shutdown time = "+date.toString() );
			}
			catch(Exception ex) {
				System.out.println("[Shutdown thread] Shutdown incomplete : "+ex.getMessage());
				ex.printStackTrace();
			}
		}
	} // end of ShutdownThread

	/*
	* Main method to start the sample client application. Call this app with no
	* args to get the argument list help text.
	* An exit code of 0 = success, 1 = error, 2 = save and exit
	* @param args argument string
	*/
	public static void main(String[] args) {
		RAPTGridClient gemssClient;
		Date date;
		int nErrorCode;

		try{
			//read in the configuration file name
			if( args.length!=2 || !args[0].equals("-configFile") ) {
				System.out.println("Command line incorrect, format should be: 'java uk.ac.soton.itinnovation.gemss.infrastructure.SampleClientPhaseTwo -configFile <config-file-path>'");
				System.exit(1);
			}

			// create a client instance
			gemssClient = new RAPTGridClient();
			if( gemssClient == null ) {
				System.out.println("Unrecognised failure, could not create client");
				System.exit(1);
			}
			else {
				//set shutdown hook
				gemssClient.setShutdownHook();
			}

			// note start time
			date = new Date( System.currentTimeMillis() );
			System.out.println( "Start time = "+date.toString() );

			// run the sample client
			nErrorCode = gemssClient.run( args[1] );

			// note end time
			date = new Date( System.currentTimeMillis() );
			System.out.println( "End time = "+date.toString() );

			// throw error code if there is one for immediate termination. If the popup window
			// has been enabled the run method will return 0 and let the window listener actually
			// throw the exception once the user has read the cause via the GUI and clicked on
			// the close button.
			if( nErrorCode != 0 ) System.exit( nErrorCode );
		}
		catch( Throwable ex ) {
			System.out.println("Unrecognised failure, reason:");
			ex.printStackTrace();
			System.exit(1);
		}
	} // end of main

	/* file filter class for session files */
	class SessionFileFilter extends FileFilter {
		/* constructor */
		public SessionFileFilter() { super(); }
		/* accept all .sobj files and directories
		* @param file file to decied upon
		* @return true if the file should be displayed (i.e. not filtered)
		*/
		public boolean accept( File file )
		{
			if( file.isDirectory() ) return true;
			if( file.getName().endsWith(".sobj") ) return true;
			return false;
		}
		/* return the description of this filter
		* @return "session file"
		*/
		public String getDescription() { return "GEMSS Session File"; }
	} // end of sessionFileFilter class

	/* window listsner to allow window close to exit with an error code */
	class FrameWindowListener extends WindowAdapter {
		public void windowClosing(WindowEvent e) { System.exit( mnExitErrorCode ); }
	}
} // end of SampleClientPhaseTwo