Merging in TAVUTILS-25

git-svn-id: https://taverna.googlecode.com/svn/taverna/engine/net.sf.taverna.t2.taverna-commandline/branches/taverna-commandline-1.3@13568 bf327186-88b3-11dd-a302-d386e5130c1c
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineInvocationContext.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineInvocationContext.java
new file mode 100644
index 0000000..5c60ddf
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineInvocationContext.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.provenance.reporter.ProvenanceReporter;
+import net.sf.taverna.t2.reference.ReferenceService;
+/**
+ * An InvocationContext used by the command line tool.
+ * 
+ * @author Stuart Owen
+ *
+ */
+public class CommandLineInvocationContext implements InvocationContext {
+
+	private final ReferenceService referenceService;
+
+	private final ProvenanceReporter provenanceReporter;
+
+	private List<Object> entities = Collections
+			.synchronizedList(new ArrayList<Object>());
+
+	public CommandLineInvocationContext(ReferenceService referenceService,
+			ProvenanceReporter provenanceReporter) {
+		this.referenceService = referenceService;
+		this.provenanceReporter = provenanceReporter;
+	}
+
+	public void addEntity(Object entity) {
+		entities.add(entity);
+	}
+
+	public <T extends Object> List<T> getEntities(Class<T> entityType) {
+		List<T> entitiesOfType = new ArrayList<T>();
+		synchronized (entities) {
+			for (Object entity : entities) {
+				if (entityType.isInstance(entity)) {
+					entitiesOfType.add(entityType.cast(entity));
+				}
+			}
+		}
+		return entitiesOfType;
+	}
+
+	public ProvenanceReporter getProvenanceReporter() {
+		return provenanceReporter;
+	}
+
+	public ReferenceService getReferenceService() {
+		return referenceService;
+	}
+
+	public void removeEntity(Object entity) {
+		entities.remove(entity);
+	}
+}
\ No newline at end of file
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineLauncher.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineLauncher.java
new file mode 100644
index 0000000..e8fc4ba
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineLauncher.java
@@ -0,0 +1,586 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline;
+
+import java.io.BufferedReader;
+import java.io.Console;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.naming.NamingException;
+
+import net.sf.taverna.platform.spring.RavenAwareClassPathXmlApplicationContext;
+import net.sf.taverna.raven.launcher.Launchable;
+import net.sf.taverna.t2.commandline.data.DatabaseConfigurationHandler;
+import net.sf.taverna.t2.commandline.data.InputsHandler;
+import net.sf.taverna.t2.commandline.data.SaveResultsHandler;
+import net.sf.taverna.t2.commandline.exceptions.DatabaseConfigurationException;
+import net.sf.taverna.t2.commandline.exceptions.InvalidOptionException;
+import net.sf.taverna.t2.commandline.exceptions.OpenDataflowException;
+import net.sf.taverna.t2.commandline.exceptions.ReadInputException;
+import net.sf.taverna.t2.commandline.options.CommandLineOptions;
+import net.sf.taverna.t2.facade.WorkflowInstanceFacade;
+import net.sf.taverna.t2.facade.WorkflowInstanceFacade.State;
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.invocation.TokenOrderException;
+import net.sf.taverna.t2.invocation.WorkflowDataToken;
+import net.sf.taverna.t2.provenance.ProvenanceConnectorFactory;
+import net.sf.taverna.t2.provenance.ProvenanceConnectorFactoryRegistry;
+import net.sf.taverna.t2.provenance.connector.ProvenanceConnector;
+import net.sf.taverna.t2.reference.ReferenceService;
+import net.sf.taverna.t2.security.credentialmanager.CMException;
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.workbench.reference.config.DataManagementConfiguration;
+import net.sf.taverna.t2.workflowmodel.Dataflow;
+import net.sf.taverna.t2.workflowmodel.DataflowInputPort;
+import net.sf.taverna.t2.workflowmodel.DataflowOutputPort;
+import net.sf.taverna.t2.workflowmodel.DataflowValidationReport;
+import net.sf.taverna.t2.workflowmodel.EditException;
+import net.sf.taverna.t2.workflowmodel.Edits;
+import net.sf.taverna.t2.workflowmodel.EditsRegistry;
+import net.sf.taverna.t2.workflowmodel.InvalidDataflowException;
+import net.sf.taverna.t2.workflowmodel.serialization.DeserializationException;
+import net.sf.taverna.t2.workflowmodel.serialization.xml.XMLDeserializer;
+import net.sf.taverna.t2.workflowmodel.serialization.xml.XMLDeserializerRegistry;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.RollingFileAppender;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * A utility class that wraps the process of executing a workflow, allowing
+ * workflows to be easily executed independently of the GUI.
+ *
+ * @author Stuart Owen
+ */
+
+public class CommandLineLauncher implements Launchable {
+
+	private static Logger logger = Logger.getLogger(CommandLineLauncher.class);
+	
+	/**
+	 * Main method, purely for development and debugging purposes. Full
+	 * execution of workflows will not work through this method.
+	 *
+	 * @param args
+	 * @throws Exception
+	 */
+	public static void main(String[] args) {
+		new CommandLineLauncher().launch(args);
+	}
+
+	public int launch(String[] args) {
+
+		try {
+			CommandLineOptions options = new CommandLineOptions(args);
+			initialiseLogging(options);
+			int result = setupAndExecute(args,options);
+			System.exit(result);
+			return result;
+		} catch (EditException e) {
+			error("There was an error opening the workflow: " + e.getMessage());
+		} catch (DeserializationException e) {
+			error("There was an error opening the workflow: " + e.getMessage());
+		} catch (InvalidDataflowException e) {
+			error("There was an error validating the workflow: "
+					+ e.getMessage());
+		} catch (TokenOrderException e) {
+			error("There was an error starting the workflow execution: "
+					+ e.getMessage());
+		} catch (InvalidOptionException e) {
+			error(e.getMessage());
+		} catch (ReadInputException e) {
+			error(e.getMessage());
+		} catch (OpenDataflowException e) {
+			error(e.getMessage());
+		} catch (DatabaseConfigurationException e) {
+			error(e.getMessage());
+		}
+		catch (CMException e) {
+			error("There was an error instantiating Credential Manager: " + e.getMessage());
+		}
+		// Should be unreachable
+		System.exit(-1);
+		return -1;
+	}
+
+	private void initialiseLogging(CommandLineOptions options) {
+		LogManager.resetConfiguration();
+
+		if (System.getProperty("log4j.configuration") == null) {
+			try {
+				PropertyConfigurator.configure(CommandLineLauncher.class
+						.getClassLoader().getResource("cl-log4j.properties")
+						.toURI().toURL());
+			} catch (MalformedURLException e) {
+				logger
+						.error(
+								"There was a serious error reading the default logging configuration",
+								e);
+			} catch (URISyntaxException e) {
+				logger
+						.error(
+								"There was a serious error reading the default logging configuration",
+								e);
+			}
+
+		} else {
+			PropertyConfigurator.configure(System
+					.getProperty("log4j.configuration"));
+		}
+
+		if (options.hasLogFile()) {
+			RollingFileAppender appender;
+			try {
+
+				PatternLayout layout = new PatternLayout(
+						"%-5p %d{ISO8601} (%c:%L) - %m%n");
+				appender = new RollingFileAppender(layout, options.getLogFile());
+				appender.setMaxFileSize("1MB");
+				appender.setEncoding("UTF-8");
+				appender.setMaxBackupIndex(4);
+				// Let root logger decide level
+				appender.setThreshold(Level.ALL);
+				LogManager.getRootLogger().addAppender(appender);
+			} catch (IOException e) {
+				System.err.println("Could not log to " + options.getLogFile());
+			}
+		}
+	}
+
+	public int setupAndExecute(String[] args,CommandLineOptions options) throws InvalidOptionException,
+			EditException, DeserializationException, InvalidDataflowException,
+			TokenOrderException, ReadInputException, OpenDataflowException,
+			DatabaseConfigurationException, CMException {
+
+
+		if (!options.askedForHelp()) {
+			setupDatabase(options);
+
+			if (options.getWorkflow() != null) {
+							
+				// Initialise Credential Manager and SSL stuff quite early as parsing and
+				// validating the workflow might require its services
+				String credentialManagerDirPath = options.getCredentialManagerDir();
+				String credentialManagerPassword = null;									
+				if (options.hasOption(CommandLineOptions.CREDENTIAL_MANAGER_PASSWORD_OPTION)){ // if this parameter was used when launching the command line tool
+					// Try to read the password from stdin (terminal or pipe)
+					credentialManagerPassword = getCredentialManagerPasswordFromStdin();
+				}
+				else{ 	
+					// Try to read the password from a special file located in 
+					// Credential Manager directory (if the dir was not null)
+					credentialManagerPassword = getCredentialManagerPasswordFromFile(options.getCredentialManagerDir());
+				}
+				if (credentialManagerPassword != null){
+					// Initialise Credential Manager (Taverna's Keystore and Truststore) and 
+					// SSL stuff - set the SSLSocketFactory to use Taverna's Keystore and Truststore.
+					
+					// If credentialManagerDirPath is null - initialize from the default location in <TAVERNA_HOME>/security somewhere 
+					// inside user's home directory. This should not be used when running command 
+					// line tool on a server and the Credential Manager dir path should always be 
+					// passed in as we do not want to store the security files in user's home directory 
+					// on the server (we do not even know which user the command line tool will be running as).
+
+					//if (credentialManagerDirPath != null){
+						CredentialManager.initialiseSSL(credentialManagerDirPath, credentialManagerPassword); // this can now handle situations when credentialManagerDirPath is null
+					//}
+					//else{
+						// Initialize from the default location in <TAVERNA_HOME>/security somewhere 
+						// inside user's home directory. This should not be used when running command 
+						// line tool on a server and the Credential Manager dir path should always be 
+						// passed in as we do not want to store the security files in user's home directory 
+						// on the server (we do not even know which user the command line tool will be running as).
+					//	CredentialManager.initialiseSSL(credentialManagerPassword);
+					//}
+				}
+				else{
+					logger.warn("No master password provided for Credential Manager.");
+				}
+
+				URL workflowURL = readWorkflowURL(options.getWorkflow());
+
+				Dataflow dataflow = openDataflow(workflowURL);
+				validateDataflow(dataflow);
+
+				InvocationContext context = createInvocationContext();
+
+				WorkflowInstanceFacade facade = compileFacade(dataflow, context);
+				InputsHandler inputsHandler = new InputsHandler();
+				Map<String, DataflowInputPort> portMap = new HashMap<String, DataflowInputPort>();
+
+				for (DataflowInputPort port : dataflow.getInputPorts()) {
+					portMap.put(port.getName(), port);
+				}
+				inputsHandler.checkProvidedInputs(portMap, options);
+				Map<String, WorkflowDataToken> inputs = inputsHandler
+						.registerInputs(portMap, options, context);
+
+				CommandLineResultListener resultListener = addResultListener(
+						facade, context, dataflow, options);
+
+				executeWorkflow(facade, inputs, resultListener);
+			}
+		} else {
+			options.displayHelp();
+		}
+
+		// wait until user hits CTRL-C before exiting
+		if (options.getStartDatabaseOnly()) {
+			// FIXME: need to do this more gracefully.
+			while (true) {
+				try {
+					Thread.sleep(500);
+				} catch (InterruptedException e) {
+					return 0;
+				}
+			}
+		}
+
+		return 0;
+	}
+
+	protected void validateDataflow(Dataflow dataflow)
+			throws InvalidDataflowException {
+		// FIXME: this needs expanding upon to give more details info back to
+		// the user
+		// FIXME: added a getMessage to InvalidDataflowException may be good
+		// place to do this.
+		DataflowValidationReport report = dataflow.checkValidity();
+		if (!report.isValid()) {
+			throw new InvalidDataflowException(dataflow, report);
+		}
+	}
+
+	protected void executeWorkflow(WorkflowInstanceFacade facade,
+			Map<String, WorkflowDataToken> inputs,
+			CommandLineResultListener resultListener)
+			throws TokenOrderException {
+		facade.fire();
+		for (String inputName : inputs.keySet()) {
+			WorkflowDataToken token = inputs.get(inputName);
+			facade.pushData(token, inputName);
+		}
+		while (facade.getState().compareTo(State.completed) < 0) {			
+			try {
+				Thread.sleep(100);
+			} catch (InterruptedException e) {
+				logger
+						.warn(
+								"Thread Interuption Exception whilst waiting for dataflow completion",
+								e);
+			}
+		}
+		resultListener.saveProvenance();
+		resultListener.saveOutputDocument();
+	}
+
+	private void setupDatabase(CommandLineOptions options)
+			throws DatabaseConfigurationException {
+		DatabaseConfigurationHandler dbHandler = new DatabaseConfigurationHandler(
+				options);
+		dbHandler.configureDatabase();
+		if (!options.isInMemory()) {
+			try {
+				dbHandler.testDatabaseConnection();
+			} catch (NamingException e) {
+				throw new DatabaseConfigurationException(
+						"There was an error trying to setup the database datasource: "
+								+ e.getMessage(), e);
+			} catch (SQLException e) {
+				if (options.isClientServer()) {
+					throw new DatabaseConfigurationException(
+							"There was an error whilst making a test database connection. If running with -clientserver you should check that a server is running (check -startdb or -dbproperties)",
+							e);
+				}
+				if (options.isEmbedded()) {
+					throw new DatabaseConfigurationException(
+							"There was an error whilst making a test database connection. If running with -embedded you should make sure that another process isn't using the database, or a server running through -startdb",
+							e);
+				}
+			}
+		}
+
+	}
+
+	private InvocationContext createInvocationContext() {
+		ReferenceService referenceService = createReferenceServiceBean();
+		ProvenanceConnector connector = null;
+		DataManagementConfiguration dbConfig = DataManagementConfiguration
+				.getInstance();
+		if (dbConfig.isProvenanceEnabled()) {
+			String connectorType = dbConfig.getConnectorType();
+
+			for (ProvenanceConnectorFactory factory : ProvenanceConnectorFactoryRegistry
+					.getInstance().getInstances()) {
+				if (connectorType.equalsIgnoreCase(factory.getConnectorType())) {
+					connector = factory.getProvenanceConnector();
+				}
+
+			}
+			if (connector != null) {
+				connector.init();
+			} else {
+				error("Unable to initialise the provenance - the ProvenanceConnector cannot be found.");
+			}
+		}
+		InvocationContext context = new CommandLineInvocationContext(
+				referenceService, connector);
+		if (connector != null){
+			connector.setInvocationContext(context);
+		}
+		return context;
+	}
+
+	private File determineOutputDir(CommandLineOptions options,
+			String dataflowName) {
+		File result = null;
+		if (options.getOutputDirectory() != null) {
+			result = new File(options.getOutputDirectory());
+			if (result.exists()) {
+				error("The specified output directory '"
+						+ options.getOutputDirectory() + "' already exists");
+			}
+		} else if (options.getOutputDocument() == null) {
+			result = new File(dataflowName + "_output");
+			int x = 1;
+			while (result.exists()) {
+				result = new File(dataflowName + "_output_" + x);
+				x++;
+			}
+		}
+		if (result != null) {
+			System.out.println("Outputs will be saved to the directory: "
+					+ result.getAbsolutePath());
+		}
+		return result;
+	}
+
+	protected void error(String msg) {
+		System.err.println(msg);
+		System.exit(-1);
+	}
+
+	private URL readWorkflowURL(String workflowOption)
+			throws OpenDataflowException {
+		URL url;
+		try {
+			url = new URL("file:");
+			return new URL(url, workflowOption);
+		} catch (MalformedURLException e) {
+			throw new OpenDataflowException(
+					"The was an error processing the URL to the workflow: "
+							+ e.getMessage(), e);
+		}
+	}
+	
+	/**
+	 * 
+	 * @param cmDir
+	 * @return Password for Credential Manager.
+	 * @throws CMException
+	 */
+	private String getCredentialManagerPasswordFromFile(String cmDir) throws CMException{
+
+		if (cmDir == null){
+			return null;
+		}
+		File passwordFile = new File(cmDir, "password.txt");
+		String password = null;
+		BufferedReader buffReader = null;
+		try {
+			buffReader = new BufferedReader(new FileReader(passwordFile));
+			password = buffReader.readLine();
+		} catch (IOException ioe) {
+			// For some reason the error of the exception thrown 
+			// does not get printed from the Launcher so print it here as
+			// well as it gives more clue as to what is going wrong.
+			logger.error("There was an error reading the Credential Manager password from "
+					+ passwordFile.toString() + ": " + ioe.getMessage(), ioe); 
+			throw new CMException(
+					"There was an error reading the Credential Manager password from "
+							+ passwordFile.toString() + ": " + ioe.getMessage(), ioe);
+		} finally {
+			try {
+				buffReader.close();
+			} catch (Exception ioe1) {
+				// Ignore
+			}
+		}
+		return password;
+	}
+
+	private String getCredentialManagerPasswordFromStdin() throws CMException{
+		
+		String password = null;
+        
+		Console console = System.console();		
+
+		if (console == null) { // password is being piped in, not entered in the terminal by user
+			BufferedReader buffReader = null;
+    		try {
+    			buffReader = new BufferedReader(new InputStreamReader(System.in));
+    			password = buffReader.readLine();
+    		} 
+    		catch (IOException ex) {
+    			// For some reason the error of the exception thrown 
+    			// does not get printed from the Launcher so print it here as
+    			// well as it gives more clue as to what is going wrong.
+    			logger.error("An error occured while trying to read Credential Manager's password the user piped in: "
+						+ ex.getMessage(), ex); 
+    			throw new CMException(
+						"An error occured while trying to read Credential Manager's password the user piped in: "
+								+ ex.getMessage(), ex);
+				} 
+    		finally {
+    			try {
+    				buffReader.close();
+    			} catch (Exception ioe1) {
+    				// Ignore
+    			}
+    		}	  
+		}
+		else{ // read the password from the terminal as entered by the user
+			try {
+				// Block until user enters password
+				char passwordArray[] = console
+						.readPassword("Password for Credential Manager: ");
+				if (passwordArray != null) { // user did not abort input
+					password = new String(passwordArray);
+				} // else password will be null
+
+			} catch (Exception ex) {
+    			// For some reason the error of the exception thrown 
+    			// does not get printed from the Launcher so print it here as
+    			// well as it gives more clue as to what is going wrong.
+				logger.error("An error occured while trying to read Credential Manager's password from the terminal: "
+								+ ex.getMessage(), ex);
+				throw new CMException(
+						"An error occured while trying to read Credential Manager's password from the terminal: "
+								+ ex.getMessage(), ex);
+			}
+		}
+        return password;
+	}
+	
+	private CommandLineResultListener addResultListener(
+			WorkflowInstanceFacade facade, InvocationContext context,
+			Dataflow dataflow, CommandLineOptions options) {
+		File outputDir = null;
+		File baclavaDoc = null;
+		File janus = null;
+		File janusDir = null;
+		File opm = null;
+		
+		if (options.saveResultsToDirectory()) {
+			outputDir = determineOutputDir(options, dataflow.getLocalName());
+			janusDir = outputDir;
+		}
+		if (options.getOutputDocument() != null) {
+			baclavaDoc = new File(options.getOutputDocument());
+		}
+		if (options.isJanus()) {
+			if (options.getJanus() == null) {
+				if (janusDir == null) {
+					janusDir = determineOutputDir(options, dataflow.getLocalName());
+				}
+				janus = new File(janusDir, "provenance-janus.rdf");
+			} else {
+				janus = new File(options.getJanus());
+			}
+		}
+		if (options.isOPM()) {
+			if (options.getOPM() == null) {
+				if (janusDir == null) {
+					janusDir = determineOutputDir(options, dataflow.getLocalName());
+				}
+				opm = new File(janusDir, "provenance-opm.rdf");
+			} else {
+				opm = new File(options.getOPM());
+			}
+		}
+		
+		Map<String, Integer> outputPortNamesAndDepth = new HashMap<String, Integer>();
+		for (DataflowOutputPort port : dataflow.getOutputPorts()) {
+			outputPortNamesAndDepth.put(port.getName(), port.getDepth());
+		}
+		SaveResultsHandler resultsHandler = new SaveResultsHandler(
+				outputPortNamesAndDepth, outputDir, baclavaDoc, janus, opm);
+		CommandLineResultListener listener = new CommandLineResultListener(
+				outputPortNamesAndDepth.size(), resultsHandler,
+				outputDir != null, baclavaDoc != null, opm != null, janus != null, facade.getWorkflowRunId());
+		facade.addResultListener(listener);
+		return listener;
+
+	}
+
+	protected ReferenceService createReferenceServiceBean() {
+		ApplicationContext appContext = new RavenAwareClassPathXmlApplicationContext(
+				DataManagementConfiguration.getInstance().getDatabaseContext());
+		return (ReferenceService) appContext
+				.getBean("t2reference.service.referenceService");
+	}
+
+	protected WorkflowInstanceFacade compileFacade(Dataflow dataflow,
+			InvocationContext context) throws InvalidDataflowException {
+		Edits edits = EditsRegistry.getEdits();
+		return edits.createWorkflowInstanceFacade(dataflow, context, "");
+	}
+
+	protected Dataflow openDataflow(URL workflowURL)
+			throws DeserializationException, EditException,
+			OpenDataflowException {
+		XMLDeserializer deserializer = XMLDeserializerRegistry.getInstance()
+				.getDeserializer();
+		SAXBuilder builder = new SAXBuilder();
+		Element el;
+		try {
+			InputStream stream = workflowURL.openStream();
+			el = builder.build(stream).detachRootElement();
+		} catch (JDOMException e) {
+			throw new OpenDataflowException(
+					"There was a problem processing the workflow XML: "
+							+ e.getMessage(), e);
+		} catch (IOException e) {
+			throw new OpenDataflowException(
+					"There was a problem reading the workflow file: "
+							+ e.getMessage(), e);
+		}
+		return deserializer.deserializeDataflow(el);
+	}
+
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineResultListener.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineResultListener.java
new file mode 100644
index 0000000..aadae0f
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/CommandLineResultListener.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.commandline.data.SaveResultsHandler;
+import net.sf.taverna.t2.facade.ResultListener;
+import net.sf.taverna.t2.invocation.WorkflowDataToken;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A ResultListener that is using for collecting and storing results when running
+ * workflows from the commandline.
+ * 
+ * @author Stuart Owen 
+ */
+public class CommandLineResultListener implements ResultListener {
+	
+	private static final Logger logger = Logger.getLogger(CommandLineResultListener.class);
+	
+	private Map<String, WorkflowDataToken> outputMap = new HashMap<String, WorkflowDataToken>();	
+	private Map<String,WorkflowDataToken> finalTokens = new HashMap<String, WorkflowDataToken>();	
+	private final SaveResultsHandler saveResultsHandler;
+	private final int numberOfOutputs;
+	private final boolean saveIndividualResults;
+	private final boolean saveOutputDocument;
+
+	private boolean saveOpm;
+
+	private boolean saveJanus;
+
+	private final String workflowRunId;
+
+	public CommandLineResultListener(int numberOfOutputs,SaveResultsHandler saveResultsHandler,boolean saveIndividualResults,boolean saveOutputDocument, boolean saveOpm, boolean saveJanus, String workflowRunId) {		
+		this.numberOfOutputs = numberOfOutputs;
+		this.saveResultsHandler = saveResultsHandler;
+		this.saveIndividualResults = saveIndividualResults;
+		this.saveOutputDocument = saveOutputDocument;	
+		this.saveOpm = saveOpm;
+		this.saveJanus = saveJanus;
+		this.workflowRunId = workflowRunId;
+	}
+
+	public Map<String, WorkflowDataToken> getOutputMap() {
+		return outputMap;
+	}	
+
+	public void resultTokenProduced(WorkflowDataToken token, String portName) {		
+		if (saveIndividualResults) {
+			saveResultsHandler.tokenReceived(token, portName);
+		}
+		
+		if (token.isFinal()) {
+			finalTokens.put(portName, token);			
+		}
+	}
+	
+	public void saveOutputDocument() {
+		if (saveOutputDocument) {
+			try {
+				saveResultsHandler.saveOutputDocument(finalTokens);
+			} catch (Exception e) {
+				logger.error("An error occurred saving the final results to -outputdoc",e);
+			}
+		}
+	}
+
+	public void saveProvenance() {
+		if (saveOpm) {
+			saveResultsHandler.saveOpm(workflowRunId);
+		}
+		if (saveJanus) {
+			saveResultsHandler.saveJanus(workflowRunId);
+		}			
+	}
+
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/DatabaseConfigurationHandler.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/DatabaseConfigurationHandler.java
new file mode 100644
index 0000000..7769a3c
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/DatabaseConfigurationHandler.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.data;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import javax.naming.NamingException;
+
+import net.sf.taverna.t2.commandline.exceptions.DatabaseConfigurationException;
+import net.sf.taverna.t2.commandline.options.CommandLineOptions;
+import net.sf.taverna.t2.workbench.reference.config.DataManagementConfiguration;
+import net.sf.taverna.t2.workbench.reference.config.DataManagementHelper;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Handles the initialisation and configuration of the data source according to 
+ * the command line arguments, or a properties file. 
+ * This also handles starting a network based instance of a Derby server, if requested. 
+ * 
+ * @author Stuart Owen
+ *
+ */
+public class DatabaseConfigurationHandler {
+
+	private final CommandLineOptions options;
+	private DataManagementConfiguration dbConfig;
+	private static Logger logger = Logger.getLogger(DatabaseConfigurationHandler.class);
+
+	public DatabaseConfigurationHandler(CommandLineOptions options) {
+		this.options = options;
+		dbConfig = DataManagementConfiguration.getInstance();
+		dbConfig.disableAutoSave();
+	}
+
+	public void configureDatabase() throws DatabaseConfigurationException {
+		overrideDefaults();
+		useOptions();
+		if (dbConfig.getStartInternalDerbyServer()) {
+			DataManagementHelper.startDerbyNetworkServer();
+			System.out.println("Started Derby Server on Port: "
+					+ dbConfig.getCurrentPort());
+		}
+		DataManagementHelper.setupDataSource();				
+	}
+
+	public DataManagementConfiguration getDBConfig() {
+		return dbConfig;
+	}
+	
+	private void importConfigurationFromStream(InputStream inStr)
+			throws IOException {
+		Properties p = new Properties();
+		p.load(inStr);		
+		for (Object key : p.keySet()) {
+			dbConfig.setProperty((String)key, p.getProperty((String)key).trim());
+		}
+	}
+
+	protected void overrideDefaults() throws DatabaseConfigurationException {
+		
+		InputStream inStr = DatabaseConfigurationHandler.class.getClassLoader().getResourceAsStream("database-defaults.properties");
+		try {
+			importConfigurationFromStream(inStr);
+		} catch (IOException e) {
+			throw new DatabaseConfigurationException("There was an error reading the default database configuration settings: "+e.getMessage(),e);
+		}
+	}
+
+	protected void readConfigirationFromFile(String filename) throws IOException {
+		FileInputStream fileInputStream = new FileInputStream(filename);
+		importConfigurationFromStream(fileInputStream);
+		fileInputStream.close();
+	}
+
+	public void testDatabaseConnection()
+			throws DatabaseConfigurationException, NamingException, SQLException {
+		//try and get a connection
+		Connection con = null;
+		try {
+			con = DataManagementHelper.openConnection();
+		} finally {
+			if (con!=null)
+				try {
+					con.close();
+				} catch (SQLException e) {
+					logger.warn("There was an SQL error whilst closing the test connection: "+e.getMessage(),e);
+				}
+		}
+	}
+	
+	public void useOptions() throws DatabaseConfigurationException {
+		
+		if (options.hasOption("port")) {			
+			dbConfig.setPort(options.getDatabasePort());		
+		}
+		
+		if (options.hasOption("startdb")) {
+			dbConfig.setStartInternalDerbyServer(true);
+		}
+		
+		if (options.hasOption("inmemory")) {			
+			dbConfig.setInMemory(true);		
+		}
+		
+		if (options.hasOption("embedded")) {
+			dbConfig.setInMemory(false);
+			dbConfig.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
+		}
+		
+		if (options.isProvenanceEnabled()) {
+			dbConfig.setProvenanceEnabled(true);
+		}
+		
+		if (options.hasOption("clientserver")) {
+			dbConfig.setInMemory(false);
+			dbConfig.setDriverClassName("org.apache.derby.jdbc.ClientDriver");
+			dbConfig.setJDBCUri("jdbc:derby://localhost:" + dbConfig.getPort() + "/t2-database;create=true;upgrade=true");			
+		}		
+		
+		if (options.hasOption("dbproperties")) {
+			try {
+				readConfigirationFromFile(options.getDatabaseProperties());
+			} catch (IOException e) {
+				throw new DatabaseConfigurationException("There was an error reading the database configuration options at "+options.getDatabaseProperties()+" : "+e.getMessage(),e);
+			}
+		}
+	}
+
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/ErrorDocumentHandler.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/ErrorDocumentHandler.java
new file mode 100644
index 0000000..50b909c
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/ErrorDocumentHandler.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.data;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.reference.ErrorDocument;
+import net.sf.taverna.t2.reference.ErrorDocumentService;
+import net.sf.taverna.t2.reference.IdentifiedList;
+import net.sf.taverna.t2.reference.ListService;
+import net.sf.taverna.t2.reference.StackTraceElementBean;
+import net.sf.taverna.t2.reference.T2Reference;
+import net.sf.taverna.t2.reference.T2ReferenceType;
+
+/**
+ * Handles Error documents and transforming them into String representations 
+ * that can be stored as a file, or within a Baclava document.
+ * 
+ * @author Stuart Owen
+ *
+ */
+public class ErrorDocumentHandler {
+
+	/**
+	 * Creates a string representation of the ErrorDocument.
+	 */
+	public static String buildErrorDocumentString(ErrorDocument errDocument,
+			InvocationContext context) {
+
+		String errDocumentString = "";
+
+		String exceptionMessage = errDocument.getExceptionMessage();
+		if (exceptionMessage != null && !exceptionMessage.equals("")) {
+			DefaultMutableTreeNode exceptionMessageNode = new DefaultMutableTreeNode(
+					exceptionMessage);
+			errDocumentString += exceptionMessageNode + "\n";
+			List<StackTraceElementBean> stackTrace = errDocument
+					.getStackTraceStrings();
+			if (stackTrace.size() > 0) {
+				for (StackTraceElementBean stackTraceElement : stackTrace) {
+					errDocumentString += getStackTraceElementString(stackTraceElement)
+							+ "\n";
+				}
+			}
+		}
+
+		Set<T2Reference> errorReferences = errDocument.getErrorReferences();
+		if (!errorReferences.isEmpty()) {
+			errDocumentString += "Set of ErrorDocumentS to follow." + "\n";
+		}
+		int errorCounter = 1;
+		int listCounter = 0;
+		for (T2Reference reference : errorReferences) {
+			if (reference.getReferenceType().equals(
+					T2ReferenceType.ErrorDocument)) {
+				ErrorDocumentService errorDocumentService = context
+						.getReferenceService().getErrorDocumentService();
+				ErrorDocument causeErrorDocument = errorDocumentService
+						.getError(reference);
+				if (listCounter == 0) {
+					errDocumentString += "ErrorDocument " + (errorCounter++)
+							+ "\n";
+				} else {
+					errDocumentString += "ErrorDocument " + listCounter + "."
+							+ (errorCounter++) + "\n";
+				}
+				errDocumentString += buildErrorDocumentString(
+						causeErrorDocument, context)
+						+ "\n";
+			} else if (reference.getReferenceType().equals(
+					T2ReferenceType.IdentifiedList)) {
+				List<ErrorDocument> errorDocuments = getErrorDocuments(
+						reference, context);
+				errDocumentString += "ErrorDocument list " + (++listCounter)
+						+ "\n";
+				for (ErrorDocument causeErrorDocument : errorDocuments) {
+					errDocumentString += buildErrorDocumentString(
+							causeErrorDocument, context)
+							+ "\n";
+				}
+			}
+		}
+
+		return errDocumentString;
+	}
+
+	public static void buildErrorDocumentTree(DefaultMutableTreeNode node,
+			ErrorDocument errorDocument, InvocationContext context) {
+		DefaultMutableTreeNode child = new DefaultMutableTreeNode(errorDocument);
+		String exceptionMessage = errorDocument.getExceptionMessage();
+		if (exceptionMessage != null && !exceptionMessage.equals("")) {
+			DefaultMutableTreeNode exceptionMessageNode = new DefaultMutableTreeNode(
+					exceptionMessage);
+			child.add(exceptionMessageNode);
+			List<StackTraceElementBean> stackTrace = errorDocument
+					.getStackTraceStrings();
+			if (stackTrace.size() > 0) {
+				for (StackTraceElementBean stackTraceElement : stackTrace) {
+					exceptionMessageNode.add(new DefaultMutableTreeNode(
+							getStackTraceElementString(stackTraceElement)));
+				}
+			}
+
+		}
+		node.add(child);
+
+		Set<T2Reference> errorReferences = errorDocument.getErrorReferences();
+		for (T2Reference reference : errorReferences) {
+			if (reference.getReferenceType().equals(
+					T2ReferenceType.ErrorDocument)) {
+				ErrorDocumentService errorDocumentService = context
+						.getReferenceService().getErrorDocumentService();
+				ErrorDocument causeErrorDocument = errorDocumentService
+						.getError(reference);
+				if (errorReferences.size() == 1) {
+					buildErrorDocumentTree(node, causeErrorDocument, context);
+				} else {
+					buildErrorDocumentTree(child, causeErrorDocument, context);
+				}
+			} else if (reference.getReferenceType().equals(
+					T2ReferenceType.IdentifiedList)) {
+				List<ErrorDocument> errorDocuments = getErrorDocuments(
+						reference, context);
+				if (errorDocuments.size() == 1) {
+					buildErrorDocumentTree(node, errorDocuments.get(0), context);
+				} else {
+					for (ErrorDocument errorDocument2 : errorDocuments) {
+						buildErrorDocumentTree(child, errorDocument2, context);
+					}
+				}
+			}
+		}
+	}
+
+	private static String getStackTraceElementString(
+			StackTraceElementBean stackTraceElement) {
+		StringBuilder sb = new StringBuilder();
+		sb.append(stackTraceElement.getClassName());
+		sb.append('.');
+		sb.append(stackTraceElement.getMethodName());
+		if (stackTraceElement.getFileName() == null) {
+			sb.append("(unknown file)");
+		} else {
+			sb.append('(');
+			sb.append(stackTraceElement.getFileName());
+			sb.append(':');
+			sb.append(stackTraceElement.getLineNumber());
+			sb.append(')');
+		}
+		return sb.toString();
+	}
+
+	public static List<ErrorDocument> getErrorDocuments(T2Reference reference,
+			InvocationContext context) {
+		List<ErrorDocument> errorDocuments = new ArrayList<ErrorDocument>();
+		if (reference.getReferenceType().equals(T2ReferenceType.ErrorDocument)) {
+			ErrorDocumentService errorDocumentService = context
+					.getReferenceService().getErrorDocumentService();
+			errorDocuments.add(errorDocumentService.getError(reference));
+		} else if (reference.getReferenceType().equals(
+				T2ReferenceType.IdentifiedList)) {
+			ListService listService = context.getReferenceService()
+					.getListService();
+			IdentifiedList<T2Reference> list = listService.getList(reference);
+			for (T2Reference listReference : list) {
+				errorDocuments
+						.addAll(getErrorDocuments(listReference, context));
+			}
+		}
+		return errorDocuments;
+	}
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/InputsHandler.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/InputsHandler.java
new file mode 100644
index 0000000..0b41bea
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/InputsHandler.java
@@ -0,0 +1,280 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.data;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.commandline.exceptions.InputMismatchException;
+import net.sf.taverna.t2.commandline.exceptions.InvalidOptionException;
+import net.sf.taverna.t2.commandline.exceptions.ReadInputException;
+import net.sf.taverna.t2.commandline.options.CommandLineOptions;
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.invocation.WorkflowDataToken;
+import net.sf.taverna.t2.lang.baclava.BaclavaDocumentHandler;
+import net.sf.taverna.t2.reference.T2Reference;
+import net.sf.taverna.t2.workflowmodel.DataflowInputPort;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+import org.embl.ebi.escience.baclava.DataThing;
+import org.jdom.JDOMException;
+
+/**
+ * 
+ * Handles the reading, or processing, or input values according to arguments provided to the commandline.
+ * The may be either as direct values, from a file, or from a Baclava document.
+ * 
+ * Also handles registering the input values with the Reference Service, ready to initiate
+ * the workflow run.
+ * 
+ * @author Stuart Owen
+ *
+ */
+public class InputsHandler {
+	
+	private static Logger logger = Logger.getLogger(InputsHandler.class); 
+
+	
+	public void checkProvidedInputs(Map<String, DataflowInputPort> portMap, CommandLineOptions options) throws InputMismatchException  {
+		//we dont check for the document 
+		if (options.getInputDocument()==null) {
+			Set<String> providedInputNames = new HashSet<String>();
+			for (int i=0;i<options.getInputFiles().length;i+=2) {
+				providedInputNames.add(options.getInputFiles()[i]);								
+			}
+			
+			for (int i=0;i<options.getInputValues().length;i+=2) {
+				providedInputNames.add(options.getInputValues()[i]);								
+			}
+			
+			if (portMap.size()*2 != (options.getInputFiles().length + options.getInputValues().length)) {
+				throw new InputMismatchException("The number of inputs provided does not match the number of input ports.",portMap.keySet(),providedInputNames);
+			}
+			
+			for (String portName : portMap.keySet()) {
+				if (!providedInputNames.contains(portName)) {
+					throw new InputMismatchException("The provided inputs does not contain an input for the port '"+portName+"'",portMap.keySet(),providedInputNames);
+				}
+			}
+		}
+	}
+
+
+	public Map<String, WorkflowDataToken> registerInputs(Map<String, DataflowInputPort> portMap, CommandLineOptions options,
+			InvocationContext context) throws InvalidOptionException, ReadInputException  {
+		Map<String,WorkflowDataToken> inputs = new HashMap<String, WorkflowDataToken>();
+		URL url;
+		try {
+			url = new URL("file:");
+		} catch (MalformedURLException e1) {
+			//Should never happen, but just incase:
+			throw new ReadInputException("The was an internal error setting up the URL to open the inputs. You should contact Taverna support.",e1);
+		}
+				
+		if (options.hasInputFiles()) {
+			regesterInputsFromFiles(portMap, options, context, inputs, url);
+		}
+		
+		if (options.hasInputValues()) {
+			registerInputsFromValues(portMap, options, context, inputs);
+			
+		}
+		
+		if (options.getInputDocument()!=null) {
+			registerInputsFromBaclava(options, context, inputs, url);
+		}
+		
+		return inputs;
+	}
+
+
+	private void registerInputsFromBaclava(CommandLineOptions options,
+			InvocationContext context, Map<String, WorkflowDataToken> inputs,
+			URL url) throws ReadInputException {
+		String inputDocPath = options.getInputDocument();
+		
+		URL inputDocURL;
+		try {
+			inputDocURL = new URL(url, inputDocPath);
+		} catch (MalformedURLException e1) {
+			throw new ReadInputException(
+					"The a error reading the input document from : "
+							+ inputDocPath + ", " + e1.getMessage(), e1);
+		}
+		Map<String, DataThing> things;
+		try {
+			things = new BaclavaDocumentHandler().readData(inputDocURL.openStream());
+		} catch (IOException e) {
+			throw new ReadInputException(
+					"There was an error reading the input document file: "
+							+ e.getMessage(), e);
+		} catch (JDOMException e) {
+			throw new ReadInputException(
+					"There was a error processing the input document XML: "
+							+ e.getMessage(), e);
+		}
+		for (String inputName : things.keySet()) {
+			DataThing thing = things.get(inputName);
+			Object object = thing.getDataObject();
+			T2Reference entityId=context.getReferenceService().register(object,getObjectDepth(object), true, context);
+			WorkflowDataToken token = new WorkflowDataToken("",new int[]{}, entityId, context);
+			inputs.put(inputName, token);
+		}
+	}
+
+
+	private void registerInputsFromValues(
+			Map<String, DataflowInputPort> portMap, CommandLineOptions options,
+			InvocationContext context, Map<String, WorkflowDataToken> inputs)
+			throws InvalidOptionException {
+		String[] inputParams = options.getInputValues();
+		for (int i = 0; i < inputParams.length; i = i + 2) {
+			String inputName = inputParams[i];
+			try {					
+				String inputValue = inputParams[i + 1];
+				DataflowInputPort port = portMap.get(inputName);
+				
+				if (port==null) {
+					throw new InvalidOptionException("Cannot find an input port named '"+inputName+"'");
+				}
+									
+				T2Reference entityId=null;
+				if (options.hasDelimiterFor(inputName)) {
+					String delimiter=options.inputDelimiter(inputName);
+					Object value=checkForDepthMismatch(1, port.getDepth(), inputName, inputValue.split(delimiter));
+					entityId=context.getReferenceService().register(value, port.getDepth(), true, context);						
+				}
+				else
+				{
+					Object value=checkForDepthMismatch(0, port.getDepth(), inputName, inputValue);
+					entityId=context.getReferenceService().register(value, port.getDepth(), true, context);
+				}
+									
+				WorkflowDataToken token = new WorkflowDataToken("",new int[]{}, entityId, context);								
+				inputs.put(inputName, token);					
+				
+			} catch (IndexOutOfBoundsException e) {
+				throw new InvalidOptionException("Missing input value for input "+ inputName);					
+			} 
+		}
+	}
+
+
+	private void regesterInputsFromFiles(
+			Map<String, DataflowInputPort> portMap, CommandLineOptions options,
+			InvocationContext context, Map<String, WorkflowDataToken> inputs,
+			URL url) throws InvalidOptionException {
+		String[] inputParams = options.getInputFiles();
+		for (int i = 0; i < inputParams.length; i = i + 2) {
+			String inputName = inputParams[i];
+			try {					
+				URL inputURL = new URL(url, inputParams[i + 1]);
+				DataflowInputPort port = portMap.get(inputName);
+				
+				if (port==null) {
+					throw new InvalidOptionException("Cannot find an input port named '"+inputName+"'");
+				}
+				
+				T2Reference entityId=null;
+				
+				if (options.hasDelimiterFor(inputName)) {
+					String delimiter=options.inputDelimiter(inputName);
+					Object value = IOUtils.toString(inputURL.openStream()).split(delimiter);
+					
+					value=checkForDepthMismatch(1, port.getDepth(), inputName, value);						
+					entityId=context.getReferenceService().register(value, port.getDepth(), true, context);						
+				}
+				else
+				{
+					Object value = IOUtils.toByteArray(inputURL.openStream());
+					value=checkForDepthMismatch(0, port.getDepth(), inputName, value);
+					entityId=context.getReferenceService().register(value, port.getDepth(), true, context);
+				}
+														
+				WorkflowDataToken token = new WorkflowDataToken("",new int[]{}, entityId, context);
+				inputs.put(inputName, token);
+				
+			} catch (IndexOutOfBoundsException e) {
+				throw new InvalidOptionException("Missing input filename for input "+ inputName);					
+			} catch (IOException e) {
+				throw new InvalidOptionException("Could not read input " + inputName + ": " + e.getMessage());				
+			}
+		}
+	}
+	
+	private Object checkForDepthMismatch(int inputDepth,int portDepth,String inputName,Object inputValue) throws InvalidOptionException {
+		if (inputDepth!=portDepth) {
+			if (inputDepth<portDepth) {
+				logger.warn("Wrapping input for '" + inputName + "' from a depth of "+inputDepth+" to the required depth of "+portDepth);
+				while (inputDepth<portDepth) {
+					List<Object> l=new ArrayList<Object>();
+					l.add(inputValue);
+					inputValue=l;
+					inputDepth++;
+				}
+			}
+			else {
+				String msg="There is a mismatch between depth of the list for the input port '"+inputName+"' and the data presented. The input port requires a "+depthToString(portDepth)+" and the data presented is a "+depthToString(inputDepth);				
+				throw new InvalidOptionException(msg);
+			}
+		}
+		
+		return inputValue;
+	}
+	
+	private String depthToString(int depth) {
+		switch (depth) {
+		case 0:
+			return "single item";			
+		case 1:
+			return "list";			
+		case 2:
+			return "list of lists";			
+		default:
+			return "list of depth "+depth;
+		}
+	}
+
+
+	@SuppressWarnings("unchecked")
+	private int getObjectDepth(Object o) {
+		int result = 0;
+		if (o instanceof Iterable) {
+			result++;
+			Iterator i = ((Iterable) o).iterator();
+			
+			if (i.hasNext()) {
+				Object child = i.next();
+				result = result + getObjectDepth(child);
+			}
+		}
+		return result;
+	}
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/MimeTypeHandler.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/MimeTypeHandler.java
new file mode 100644
index 0000000..2e026d6
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/MimeTypeHandler.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.data;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.reference.ExternalReferenceSPI;
+import net.sf.taverna.t2.reference.ReferenceSet;
+import net.sf.taverna.t2.reference.ReferencedDataNature;
+import net.sf.taverna.t2.reference.T2Reference;
+import net.sf.taverna.t2.reference.T2ReferenceType;
+
+import org.apache.log4j.Logger;
+
+import eu.medsea.mimeutil.MimeType;
+import eu.medsea.mimeutil.MimeUtil2;
+
+/**
+ * Handles identifying mime-types for a given data stream, or T2Reference
+ * 
+ * @author Stuart Owen 
+ */
+public class MimeTypeHandler {
+	
+	private static Logger logger = Logger.getLogger(MimeTypeHandler.class);
+	
+	@SuppressWarnings("unchecked")
+	public static List<MimeType> getMimeTypes(InputStream inputStream,InvocationContext context) throws IOException {
+		List<MimeType> mimeList = new ArrayList<MimeType>();
+		MimeUtil2 mimeUtil = new MimeUtil2();
+		mimeUtil
+				.registerMimeDetector("eu.medsea.mimeutil.detector.ExtensionMimeDetector");
+		mimeUtil
+				.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");
+		mimeUtil
+				.registerMimeDetector("eu.medsea.mimeutil.detector.WindowsRegistryMimeDetector");
+		mimeUtil
+				.registerMimeDetector("eu.medsea.mimeutil.detector.ExtraMimeTypes");
+		
+		try {
+			byte[] bytes = new byte[2048];
+			inputStream.read(bytes);
+			Collection mimeTypes2 = mimeUtil.getMimeTypes(bytes);
+			mimeList.addAll(mimeTypes2);
+		} finally {
+			try {
+				inputStream.close();
+			} catch (IOException e) {
+				logger.error(
+						"Failed to close stream after determining mimetype", e);
+			}
+		}
+		return mimeList;
+	}
+	
+	public static List<String> determineMimeTypes(T2Reference reference,
+			InvocationContext context) throws IOException {
+		List<String> mimeTypeList = new ArrayList<String>();
+
+		if (reference.getReferenceType() == T2ReferenceType.ErrorDocument) {
+			mimeTypeList.add("text/plain");
+		} else {
+			ReferenceSet referenceSet = (ReferenceSet) context
+			.getReferenceService().resolveIdentifier(reference,
+					null, context);
+			if (!referenceSet.getExternalReferences().isEmpty()) {
+				
+				ExternalReferenceSPI externalReference = referenceSet
+						.getExternalReferences().iterator().next();
+
+				List<MimeType> mimeTypes = getMimeTypes(
+						externalReference.openStream(context), context);
+
+				for (MimeType type : mimeTypes) {
+					if (!type.toString().equals("text/plain")
+							&& !type.toString().equals(
+									"application/octet-stream")) {
+						mimeTypeList.add(type.toString());
+					}
+				}
+				if (externalReference.getDataNature() == ReferencedDataNature.TEXT) {
+					mimeTypeList.add("text/plain");
+				} else {
+					mimeTypeList.add("application/octet-stream");
+				}
+			}
+
+		}
+
+		return mimeTypeList;
+	}
+		
+	public static List<MimeType> getMimeTypes(
+			ExternalReferenceSPI externalReference, InvocationContext context) throws IOException {
+		
+		InputStream inputStream = externalReference.openStream(context);
+		return getMimeTypes(inputStream, context);
+	}
+
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/SaveResultsHandler.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/SaveResultsHandler.java
new file mode 100644
index 0000000..b2f8ea4
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/data/SaveResultsHandler.java
@@ -0,0 +1,330 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.data;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.taverna.t2.commandline.CommandLineResultListener;
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.invocation.WorkflowDataToken;
+import net.sf.taverna.t2.lang.baclava.BaclavaDocumentHandler;
+import net.sf.taverna.t2.provenance.api.ProvenanceAccess;
+import net.sf.taverna.t2.provenance.client.ProvenanceExporter;
+import net.sf.taverna.t2.reference.ErrorDocument;
+import net.sf.taverna.t2.reference.ExternalReferenceSPI;
+import net.sf.taverna.t2.reference.Identified;
+import net.sf.taverna.t2.reference.IdentifiedList;
+import net.sf.taverna.t2.reference.ReferenceService;
+import net.sf.taverna.t2.reference.ReferenceSet;
+import net.sf.taverna.t2.reference.T2Reference;
+import net.sf.taverna.t2.reference.T2ReferenceType;
+import net.sf.taverna.t2.workbench.reference.config.DataManagementConfiguration;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * Handles all recording of results as they are received by the {@link CommandLineResultListener}
+ * or when the workflow enactment has completed.
+ * This includes saving as a Baclava Document, or storing individual results.
+ * 
+ * @author Stuart Owen
+ * 
+ * @see BaclavaHandler
+ * @see CommandLineResultListener
+ *
+ */
+public class SaveResultsHandler {
+
+	private final Map<String, Integer> portsAndDepth;
+	private HashMap<String, Integer> depthSeen;
+	private final File rootDirectory;
+	private static Logger logger = Logger
+			.getLogger(CommandLineResultListener.class);
+	private final File outputDocumentFile;
+	private final File janus;
+	private final File opm;
+	private ProvenanceExporter provExport;	
+
+	public SaveResultsHandler(Map<String, Integer> portsAndDepth,
+			File rootDirectory, File outputDocumentFile, File janus, File opm) {
+
+		this.portsAndDepth = portsAndDepth;
+		this.rootDirectory = rootDirectory;
+		this.outputDocumentFile = outputDocumentFile;
+		this.janus = janus;
+		this.opm = opm;
+
+		depthSeen = new HashMap<String, Integer>();
+		for (String portName : portsAndDepth.keySet()) {
+			depthSeen.put(portName, -1);
+		}
+	}
+
+	public void tokenReceived(WorkflowDataToken token, String portName) {
+		if (rootDirectory != null) { //only save individual results if a directory is specified
+			if (portsAndDepth.containsKey(portName)) {
+				int[] index = token.getIndex();
+				if (depthSeen.get(portName) == -1)
+					depthSeen.put(portName, index.length);
+				if (index.length >= depthSeen.get(portName)) {
+					storeToken(token, portName);
+				}
+			} else {
+				logger
+						.error("Result recieved for unexpected Port: "
+								+ portName);
+			}
+		}
+	}
+	
+	public void saveOutputDocument(Map<String,WorkflowDataToken> allResults) throws Exception {
+		if (outputDocumentFile!=null) {			
+			BaclavaDocumentHandler handler = new BaclavaDocumentHandler();
+			InvocationContext context = null;
+			Map<String,T2Reference> references = new HashMap<String, T2Reference>();			
+			//fetch the references from the tokens, and pick up the context on the way
+			for (String portname : allResults.keySet()) {
+				WorkflowDataToken token = allResults.get(portname);
+				if (context==null) {
+					context=token.getContext();
+				}
+				references.put(portname, token.getData());
+			} 
+			handler.setChosenReferences(references);
+			
+			handler.setInvocationContext(context);
+			ReferenceService referenceService=null;
+			if (context!=null) referenceService = context.getReferenceService();
+			handler.setReferenceService(referenceService);
+			
+			handler.saveData(outputDocumentFile);
+		}
+	}
+
+	protected void storeToken(WorkflowDataToken token, String portName) {
+
+		if (token.getData().getReferenceType() == T2ReferenceType.IdentifiedList) {
+			saveList(token, portName);
+		} else {
+			File dataDirectory = rootDirectory;
+			File dataFile = null;
+
+			if (token.getIndex().length > 0) {
+				dataDirectory = new File(rootDirectory, portName);
+				for (int i = 0; i < token.getIndex().length - 1; i++) {
+					dataDirectory = new File(dataDirectory, String
+							.valueOf(token.getIndex()[i] + 1));
+				}
+				dataFile = new File(dataDirectory, String.valueOf(token
+						.getIndex()[token.getIndex().length - 1] + 1));
+			} else {
+				dataFile = new File(dataDirectory, portName);
+			}
+			
+			if (!dataDirectory.exists()) {
+				dataDirectory.mkdirs();
+			}
+
+			if (dataFile.exists()) {
+				System.err.println("There is already data saved to: "
+						+ dataFile.getAbsolutePath());
+				System.exit(-1);
+			}
+			
+			saveIndividualDataFile(token.getData(), dataFile, token
+					.getContext());
+		}
+	}
+
+	private void saveList(WorkflowDataToken token, String portName) {
+		File dataDirectory = null;
+		int[] index = token.getIndex();
+
+		if (index.length > 0) {
+			dataDirectory = new File(rootDirectory, portName);
+			for (int i = 0; i < index.length - 1; i++) {
+				dataDirectory = new File(dataDirectory, String.valueOf(token
+						.getIndex()[i] + 1));
+			}
+			dataDirectory = new File(dataDirectory, String.valueOf(token
+					.getIndex()[index.length - 1]+ 1));
+		} else {
+			dataDirectory = new File(rootDirectory, portName);
+		}
+		
+		T2Reference reference = token.getData();
+		IdentifiedList<T2Reference> list = token.getContext()
+				.getReferenceService().getListService().getList(reference);
+		saveListItems(token.getContext(), dataDirectory,  list);
+	}
+
+	private void saveListItems(InvocationContext context, File dataDirectory,IdentifiedList<T2Reference> list) {
+		int c = 0;
+		if (!dataDirectory.exists()) {
+			dataDirectory.mkdirs();
+		}
+		for (T2Reference id : list) {			
+			File dataFile = new File(dataDirectory, String.valueOf(c+1));
+			if (id.getReferenceType() ==  T2ReferenceType.IdentifiedList) {
+				IdentifiedList<T2Reference> innerList = context
+				.getReferenceService().getListService().getList(id);				
+				saveListItems(context, dataFile, innerList);
+			}
+			else {				
+				saveIndividualDataFile(id, dataFile, context);				
+			}
+			c++;
+		}
+	}
+
+	protected void saveIndividualDataFile(T2Reference reference, File dataFile,
+			InvocationContext context) {
+
+		if (dataFile.exists()) {
+			System.err.println("There is already data saved to: "
+					+ dataFile.getAbsolutePath());
+			System.exit(-1);
+		}
+
+		Object data = null;
+		InputStream stream = null;
+		try {
+		if (reference.containsErrors()) {
+			ErrorDocument errorDoc = context.getReferenceService()
+			.getErrorDocumentService().getError(reference);
+			data = ErrorDocumentHandler.buildErrorDocumentString(errorDoc, context);
+			dataFile = new File(dataFile.getAbsolutePath()+".error");
+		} else {
+			// FIXME: this really should be done using a stream rather
+			// than an instance of the object in memory						
+			Identified identified = context.getReferenceService().resolveIdentifier(reference, null, context);
+			ReferenceSet referenceSet = (ReferenceSet) identified;
+				ExternalReferenceSPI externalReference = referenceSet.getExternalReferences().iterator().next();				
+				stream = externalReference.openStream(context);
+				data = stream;		
+		}
+
+		FileOutputStream fos = null;
+		try {
+			fos = new FileOutputStream(dataFile);
+			if (data instanceof InputStream) {
+				IOUtils.copyLarge(stream, fos);
+			}
+			else {				
+				PrintWriter out = new PrintWriter(new OutputStreamWriter(fos));
+				out.print(data.toString());
+				out.flush();
+				out.close();
+			}
+		} catch (FileNotFoundException e) {
+			logger.error("Unable to find the file: '"
+					+ dataFile.getAbsolutePath() + "' for writing results", e);
+		} catch (IOException e) {
+			logger.error("IO Error writing resuts to: '"
+					+ dataFile.getAbsolutePath(), e);
+		} finally {
+			if (fos != null) {
+				try {
+					fos.close();
+				} catch (IOException e) {
+					logger.error("Cannot close file output stream", e);
+				}
+			}
+		}
+	} finally {
+		if (stream != null) {
+			try {
+				stream.close();
+			} catch (IOException e) {
+				logger.error("Cannot close stream from reference", e);
+			}
+		}
+	}
+	}
+
+	public void saveOpm(String workflowRunId) {
+		if (opm.getParentFile() != null) {
+			opm.getParentFile().mkdirs();
+		}
+		BufferedOutputStream outStream;
+		try {
+			outStream = new BufferedOutputStream(new FileOutputStream(opm));
+		} catch (FileNotFoundException e1) {
+			logger.error("Can't find directory for writing OPM to " + opm, e1);
+			return;
+		}
+		try {
+			getProvenanceExporter().exportAsOPMRDF(workflowRunId, outStream);
+		} catch (Exception e) {
+			logger.error("Can't write OPM to " + opm, e);
+		} finally {
+			try {
+				outStream.close();
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	protected synchronized ProvenanceExporter getProvenanceExporter() {
+		if (provExport == null) {
+			DataManagementConfiguration dbConfig = DataManagementConfiguration.getInstance();
+			String connectorType = dbConfig.getConnectorType();
+			ProvenanceAccess provAccess = new ProvenanceAccess(connectorType);
+			provExport = new ProvenanceExporter(provAccess);
+		}
+		return provExport;
+	}
+
+	public void saveJanus(String workflowRunId) {
+		if (janus.getParentFile() != null) {
+			janus.getParentFile().mkdirs();
+		}
+		BufferedOutputStream outStream;
+		try {
+			outStream = new BufferedOutputStream(new FileOutputStream(janus));
+		} catch (FileNotFoundException e1) {
+			logger.error("Can't find directory for writing Janus to " + janus, e1);
+			return;
+		}
+		try {
+			getProvenanceExporter().exportAsJanusRDF(workflowRunId, outStream);
+		} catch (Exception e) {
+			logger.error("Can't write Janus to " + janus, e);
+		} finally {
+			try {
+				outStream.close();
+			} catch (IOException e) {
+			}
+		}
+	}
+	
+	
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/DatabaseConfigurationException.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/DatabaseConfigurationException.java
new file mode 100644
index 0000000..616fb70
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/DatabaseConfigurationException.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.exceptions;
+
+public class DatabaseConfigurationException extends Exception {
+	
+	private static final long serialVersionUID = -4128248547532355697L;
+
+	public DatabaseConfigurationException() {
+
+	}
+
+	public DatabaseConfigurationException(String message) {
+		super(message);
+	}
+
+	public DatabaseConfigurationException(Throwable cause) {
+		super(cause);
+	}
+
+	public DatabaseConfigurationException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/InputMismatchException.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/InputMismatchException.java
new file mode 100644
index 0000000..eb4a289
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/InputMismatchException.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.exceptions;
+
+import java.util.Set;
+
+public class InputMismatchException extends InvalidOptionException {	
+	
+	private static final long serialVersionUID = -5368068332397293706L;
+	private final Set<String> expectedInputNames;
+	private final Set<String> providedInputNames;
+	
+	public InputMismatchException(String msg, Set<String> expectedInputNames, Set<String> providedInputNames) {
+		super(msg);
+		this.expectedInputNames = expectedInputNames;
+		this.providedInputNames = providedInputNames;				
+	}
+
+	public String getMessage() {
+		String result = super.getMessage();
+		result += "\n" + expectedInputNames.size() + " inputs were expected";
+		if (expectedInputNames.size()>0) result += " which are:\n";
+		for (String name : expectedInputNames) {
+			result += "'"+name+"' ";			
+		}
+		
+		result += "\n" + providedInputNames.size() + " inputs were provided";
+		if (providedInputNames.size()>0) result += " which are:\n";
+		for (String name : providedInputNames) {
+			result += "'"+name+"' ";			
+		}
+		return result;
+	}
+	
+
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/InvalidOptionException.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/InvalidOptionException.java
new file mode 100644
index 0000000..c07a19f
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/InvalidOptionException.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.exceptions;
+
+public class InvalidOptionException extends Exception {
+
+	private static final long serialVersionUID = 2467409785164223258L;
+
+	public InvalidOptionException(String message) {
+		super(message);
+	}
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/OpenDataflowException.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/OpenDataflowException.java
new file mode 100644
index 0000000..664ab71
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/OpenDataflowException.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.exceptions;
+
+public class OpenDataflowException extends Exception {
+
+	
+	private static final long serialVersionUID = 4778578311101082197L;
+
+	public OpenDataflowException() {
+		
+	}
+
+	public OpenDataflowException(String message) {
+		super(message);	
+	}
+
+	public OpenDataflowException(Throwable cause) {
+		super(cause);
+	}
+
+	public OpenDataflowException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/ReadInputException.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/ReadInputException.java
new file mode 100644
index 0000000..226d6ea
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/exceptions/ReadInputException.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.exceptions;
+
+public class ReadInputException extends Exception {
+		
+	private static final long serialVersionUID = -3494432791254643055L;
+
+	public ReadInputException(String msg) {
+		super(msg); 
+	}
+	
+	public ReadInputException(String msg, Throwable e) {
+		super(msg,e); 
+	}
+	
+}
diff --git a/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/options/CommandLineOptions.java b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/options/CommandLineOptions.java
new file mode 100644
index 0000000..ccbd861
--- /dev/null
+++ b/taverna-commandline-common/src/main/java/net/sf/taverna/t2/commandline/options/CommandLineOptions.java
@@ -0,0 +1,454 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.commandline.options;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import net.sf.taverna.t2.commandline.exceptions.InvalidOptionException;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * Handles the processing of command line arguments for enacting a workflow.
+ * This class encapsulates all command line options, and exposes them through higher-level
+ * accessors. Upon creation it checks the validity of the command line options and raises an
+ * {@link InvalidOptionException} if they are invalid.
+ * 
+ * @author Stuart Owen
+ *
+ */
+public class CommandLineOptions {
+
+	private static final Logger logger = Logger
+			.getLogger(CommandLineOptions.class);
+	private Options options;
+	private CommandLine commandLine;
+	
+	private static final String JANUS = "janus";
+	private static final String OPM = "opm";
+	public static final String CREDENTIAL_MANAGER_DIR_OPTION = "cmdir";
+	public static final String CREDENTIAL_MANAGER_PASSWORD_OPTION = "cmpassword";
+
+	public CommandLineOptions(String[] args) throws InvalidOptionException {
+		this.options = intitialiseOptions();
+		this.commandLine = processArgs(args);		
+		checkForInvalid();
+	}
+
+	public boolean askedForHelp() {
+		return hasOption("help") || (getArgs().length==0 && getOptions().length==0);
+	}
+	
+	public boolean isProvenanceEnabled() {
+		return hasOption("provenance") || hasOption(OPM) || hasOption(JANUS);
+	}
+	
+	protected void checkForInvalid() throws InvalidOptionException {
+		if (askedForHelp()) return;
+		if (isProvenanceEnabled()
+				&& !(hasOption("embedded") || hasOption("clientserver") || hasOption("dbproperties")))
+			throw new InvalidOptionException(
+					"You should be running with a database to use provenance");
+		if (isProvenanceEnabled() && hasOption("inmemory"))
+			throw new InvalidOptionException(
+					"You should be running with a database to use provenance");
+		if ((hasOption("inputfile") || hasOption("inputvalue"))
+				&& hasOption("inputdoc"))
+			throw new InvalidOptionException(
+					"You can't provide both -input and -inputdoc arguments");
+		
+		if (hasOption("inputdelimiter") && hasOption("inputdoc"))
+			throw new InvalidOptionException("You cannot combine the -inputdelimiter and -inputdoc arguments");
+
+		if (getArgs().length == 0
+				&& !(hasOption("help") || hasOption("startdb")))
+			throw new InvalidOptionException("You must specify a workflow");
+		
+		if (hasOption("inmemory") && hasOption("embedded"))
+			throw new InvalidOptionException(
+					"The options -embedded, -clientserver and -inmemory cannot be used together");
+		if (hasOption("inmemory") && hasOption("clientserver"))
+			throw new InvalidOptionException(
+					"The options -embedded, -clientserver and -inmemory cannot be used together");
+		if (hasOption("embedded") && hasOption("clientserver"))
+			throw new InvalidOptionException(
+					"The options -embedded, -clientserver and -inmemory cannot be used together");
+	}
+
+	public void displayHelp() {
+		boolean full = false;
+		if (hasOption("help")) full=true;
+		displayHelp(full);
+	}
+
+	public void displayHelp(boolean showFullText) {
+
+		HelpFormatter formatter = new HelpFormatter();
+		try {
+			formatter
+					.printHelp("executeworkflow [options] [workflow]", options);
+			if (showFullText) {
+				InputStream helpStream = CommandLineOptions.class
+						.getClassLoader().getResourceAsStream("help.txt");
+				String helpText = IOUtils.toString(helpStream);
+				System.out.println(helpText);
+			}
+
+		} catch (IOException e) {
+			logger.error("Error reading the help document", e);
+			System.exit(-1);
+		}
+	}
+
+	public String[] getArgs() {
+		return commandLine.getArgs();
+	}		
+	
+	/**
+	 * 
+	 * @return the port that the database should run on
+	 */
+	public String getDatabasePort() {
+		return getOptionValue("port");
+	}
+
+	/**
+	 * 
+	 * @return a path to a properties file that contains database configuration
+	 *         settings
+	 */
+	public String getDatabaseProperties() {
+		return getOptionValue("dbproperties");
+	}
+
+	/**
+	 * 
+	 * @return the path to the input document
+	 */
+	public String getInputDocument() {
+		return getOptionValue("inputdoc");
+	}
+
+	/**
+	 * Returns an array that alternates between a portname and path to a file
+	 * containing the input values. Therefore the array will always contain an
+	 * even number of elements
+	 * 
+	 * @return an array of portname and path to files containing individual
+	 *         inputs.
+	 */
+	public String[] getInputFiles() {
+		if (hasInputFiles()) {
+			return getOptionValues("inputfile");
+		} else {
+			return new String[] {};
+		}
+	}
+
+	public String[] getInputValues() {
+		if (hasInputValues()) {
+			return getOptionValues("inputvalue");
+		} else {
+			return new String[] {};
+		}
+	}
+
+	public String getLogFile() {
+		return getOptionValue("logfile");
+	}
+
+	public Option [] getOptions() {
+		return commandLine.getOptions();
+	}
+
+	private String getOptionValue(String opt) {
+		return commandLine.getOptionValue(opt);
+	}
+
+	private String[] getOptionValues(String arg0) {
+		return commandLine.getOptionValues(arg0);
+	}
+
+	/**
+	 * 
+	 * @return the directory to write the results to
+	 */
+	public String getOutputDirectory() {
+		return getOptionValue("outputdir");
+	}
+
+	/**
+	 * 
+	 * @return the path to the output document
+	 */
+	public String getOutputDocument() {
+		return getOptionValue("outputdoc");
+	}
+
+	public boolean getStartDatabase() {
+		return hasOption("startdb");
+	}
+	
+	/**
+	 * @return the directory with Credential Manager's files
+	 */
+	public String getCredentialManagerDir() {
+		return getOptionValue(CREDENTIAL_MANAGER_DIR_OPTION);
+	}
+
+	public boolean getStartDatabaseOnly() throws InvalidOptionException {
+		return (getStartDatabase() && (getWorkflow() == null));
+	}
+
+	public String getWorkflow() throws InvalidOptionException {
+		if (getArgs().length == 0) {
+			return null;
+		} else if (getArgs().length != 1) {
+			throw new InvalidOptionException(
+					"You should only specify one workflow file");
+		} else {
+			return getArgs()[0];
+		}
+	}
+
+	public boolean hasDelimiterFor(String inputName) {
+		boolean result = false;
+		if (hasOption("inputdelimiter")) {
+			String [] values = getOptionValues("inputdelimiter");
+			for (int i=0;i<values.length;i+=2) {
+				if (values[i].equals(inputName))
+				{
+					result=true;
+					break;
+				}
+			}
+		}
+		return result;
+	}
+
+	public boolean hasInputFiles() {
+		return hasOption("inputfile");
+	}
+
+	public boolean hasInputValues() {
+		return hasOption("inputvalue");
+	}
+
+	public boolean hasLogFile() {
+		return hasOption("logfile");
+	}
+
+	public boolean hasOption(String option) {
+		return commandLine.hasOption(option);
+	}
+
+	public String inputDelimiter(String inputName) {
+		String result = null;
+		if (hasOption("inputdelimiter")) {
+			String [] values = getOptionValues("inputdelimiter");
+			for (int i=0;i<values.length;i+=2) {
+				if (values[i].equals(inputName))
+				{
+					result=values[i+1];
+					break;
+				}
+			}
+		}
+		return result;
+	}
+
+	@SuppressWarnings("static-access")
+	private Options intitialiseOptions() {
+		Option helpOption = new Option("help", "Display comprehensive help information.");
+
+		Option outputOption = OptionBuilder
+				.withArgName("directory")
+				.hasArg()
+				.withDescription(
+						"Save outputs as files in directory, default "
+								+ "is to make a new directory workflowName_output.")
+				.create("outputdir");
+
+		Option outputdocOption = OptionBuilder.withArgName("document").hasArg()
+				.withDescription("Save outputs to a new Baclava document.")
+				.create("outputdoc");
+
+		Option logFileOption = OptionBuilder
+				.withArgName("filename")
+				.hasArg()
+				.withDescription(
+						"The logfile to which more verbose logging will be written to.")
+				.create("logfile");
+
+		Option inputdocOption = OptionBuilder.withArgName("document").hasArg()
+				.withDescription("Load inputs from a Baclava document.").create(
+						"inputdoc");
+
+		Option inputFileOption = OptionBuilder
+				.withArgName("inputname filename").hasArgs(2)
+				.withValueSeparator(' ').withDescription(
+						"Load the named input from file or URL.").create(
+						"inputfile");
+
+		Option inputValueOption = OptionBuilder.withArgName("inputname value")
+				.hasArgs(2).withValueSeparator(' ').withDescription(
+						"Directly use the value for the named input.").create(
+						"inputvalue");
+
+		Option inputDelimiterOption = OptionBuilder
+				.withArgName("inputname delimiter")
+				.hasArgs(2)
+				.withValueSeparator(' ')
+				.withDescription(
+						"Cause an inputvalue or inputfile to be split into a list according to the delimiter. The associated workflow input must be expected to receive a list.")
+				.create("inputdelimiter");
+
+		Option dbProperties = OptionBuilder.withArgName("filename").hasArg()
+				.withDescription(
+						"Load a properties file to configure the database.")
+				.create("dbproperties");
+
+		Option port = OptionBuilder
+				.withArgName("portnumber")
+				.hasArg()
+				.withDescription(
+						"The port that the database is running on. If set requested to start its own internal server, this is the start port that will be used.")
+				.create("port");
+
+		Option embedded = new Option("embedded",
+				"Connect to an embedded Derby database. This can prevent mulitple invocations.");
+		Option clientserver = new Option("clientserver",
+				"Connect as a client to a derby server instance.");
+		Option inMemOption = new Option(
+				"inmemory",
+				"Run the workflow with data stored in-memory rather than in a database. This can give performance inprovements, at the cost of overall memory usage.");
+		Option startDB = new Option("startdb",
+				"Automatically start an internal Derby database server.");
+		Option provenance = new Option("provenance",
+				"Generate provenance information and store it in the database.");
+		
+		
+		Option opm = OptionBuilder
+				.withArgName("file")
+				.hasOptionalArg()
+				.withDescription(
+						"Save Open Provenance Model (OPM) RDF/XML trace of execution to FILE or 'provenance-opm.rdf'.")
+				.create(OPM);
+
+		Option janus = OptionBuilder
+				.withArgName("file")
+				.hasOptionalArg()
+				.withDescription(
+						"Save Janus RDF/XML trace of execution to FILE or 'provenance-janus.rdf'.")
+				.create(JANUS);		
+		
+		Option credentialManagerDirectory = OptionBuilder.withArgName("directory path").
+		hasArg().withDescription(
+				"Absolute path to a directory where Credential Manager's files (keystore and truststore) are located. ")
+		.create(CREDENTIAL_MANAGER_DIR_OPTION);
+		Option credentialManagerPassword = new Option(CREDENTIAL_MANAGER_PASSWORD_OPTION, "Indicate that the master password for Credential Manager will be provided on standard input."); // optional password option, to be read from standard input
+		
+		Options options = new Options();
+		options.addOption(helpOption);
+		options.addOption(inputFileOption);
+		options.addOption(inputValueOption);
+		options.addOption(inputDelimiterOption);
+		options.addOption(inputdocOption);
+		options.addOption(outputOption);
+		options.addOption(outputdocOption);
+		options.addOption(inMemOption);
+		options.addOption(embedded);
+		options.addOption(clientserver);
+		options.addOption(dbProperties);
+		options.addOption(port);
+		options.addOption(startDB);
+		options.addOption(provenance);
+		options.addOption(opm);
+		options.addOption(janus);
+		options.addOption(logFileOption);
+		options.addOption(credentialManagerDirectory);
+		options.addOption(credentialManagerPassword);
+
+		return options;
+	}
+
+	public boolean isClientServer() {
+		return hasOption("clientserver");
+	}
+
+	public boolean isEmbedded() {
+		return hasOption("embedded");
+	}
+
+	public boolean isInMemory() {
+		return hasOption("inmemory");
+	}
+
+	private CommandLine processArgs(String[] args) {
+		CommandLineParser parser = new GnuParser();
+		CommandLine line = null;
+		try {
+			// parse the command line arguments
+			line = parser.parse(options, args);
+		} catch (ParseException exp) {
+			// oops, something went wrong
+			System.err.println("Parsing failed.  Reason: " + exp.getMessage());
+			System.exit(1);
+		}
+		return line;
+	}
+
+	/**
+	 * Save the results to a directory if -output has been explicitly defined,
+	 * and/or if -outputdoc hasn't been defined
+	 * 
+	 * @return boolean
+	 */
+	public boolean saveResultsToDirectory() {
+		return (options.hasOption("outputdir") || !options
+				.hasOption("outputdoc"));
+	}
+
+	public String getJanus() {
+		return getOptionValue(JANUS);
+	}
+	public String getOPM() {
+		return getOptionValue(OPM);
+	}
+	
+	public boolean isOPM() {
+		return hasOption(OPM);
+	}
+	public boolean isJanus() {
+		return hasOption(JANUS);
+	}
+
+}
diff --git a/taverna-commandline-common/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable b/taverna-commandline-common/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable
new file mode 100644
index 0000000..1314920
--- /dev/null
+++ b/taverna-commandline-common/src/main/resources/META-INF/services/net.sf.taverna.raven.launcher.Launchable
@@ -0,0 +1 @@
+net.sf.taverna.t2.commandline.CommandLineLauncher
\ No newline at end of file
diff --git a/taverna-commandline-common/src/main/resources/cl-log4j.properties b/taverna-commandline-common/src/main/resources/cl-log4j.properties
new file mode 100644
index 0000000..8c17461
--- /dev/null
+++ b/taverna-commandline-common/src/main/resources/cl-log4j.properties
@@ -0,0 +1,27 @@
+# By default, WARN (our external libraries)
+log4j.rootLogger=INFO, CONSOLE
+
+# Log INFO from anything Taverna
+log4j.logger.net.sf.taverna.t2=INFO
+
+
+
+# Restrict logging from classes that like to complain a lot
+log4j.logger.net.sf.taverna.raven=WARN
+log4j.logger.net.sf.taverna.t2.workbench.configuration.ConfigurationManager=WARN
+log4j.logger.net.sf.taverna.t2.workbench.views.graph.GraphViewComponent=WARN
+log4j.logger.net.sf.taverna.t2.workbench.ui.impl.WorkbenchPerspectives=WARN
+log4j.logger.net.sf.taverna.t2.workbench.ui.impl.DataflowEditsListener=WARN
+log4j.logger.net.sf.taverna.t2.workbench.ui.views.contextualviews.annotated.AnnotatedContextualView=WARN
+log4j.logger.net.sf.taverna.t2.workbench.ui.workflowview.WorkflowView=WARN
+log4j.logger.net.sf.taverna.t2.ui.menu.impl.MenuManagerImpl=ERROR
+
+log4j.logger.org.apache.commons.httpclient=ERROR
+
+
+# Default output to console is restrictive
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%-5p %d{ISO8601} (%c:%L) - %m%n
+# Output WARN and above to console - 
+log4j.appender.CONSOLE.Threshold=ERROR
\ No newline at end of file
diff --git a/taverna-commandline-common/src/main/resources/database-defaults.properties b/taverna-commandline-common/src/main/resources/database-defaults.properties
new file mode 100644
index 0000000..1080856
--- /dev/null
+++ b/taverna-commandline-common/src/main/resources/database-defaults.properties
@@ -0,0 +1,11 @@
+in_memory = true
+provenance = false
+connector = derby
+port = 1527 
+dialect = org.hibernate.dialect.DerbyDialect
+start_derby = false
+pool_max_active = 50
+pool_min_idle = 50
+pool_max_idle = 10
+driver = org.apache.derby.jdbc.EmbeddedDriver
+jdbcuri = jdbc:derby:t2-database;create=true;upgrade=true
\ No newline at end of file
diff --git a/taverna-commandline-common/src/main/resources/help.txt b/taverna-commandline-common/src/main/resources/help.txt
new file mode 100644
index 0000000..e2103a1
--- /dev/null
+++ b/taverna-commandline-common/src/main/resources/help.txt
@@ -0,0 +1,87 @@
+By default, the workflow is executed using the -inmemory option, and the
+results are written out to a directory named after the workflow name.
+
+If this directory already exists then a new directory is created, and
+appended with _<n>, where n is incremented to the next available index.
+
+Results are written out to files named after the output port for that result.
+If a result is composed of lists, then a directory is created for the output
+port and individual list items are named after the list element index (with 1
+being the first index). The the output is the result of an error, the filename
+is appended with '.error'.
+
+You can provide your own output directory with the -outputdir option. There
+will be an error if the directory already exists.
+
+You can also record your results to a Baclava document using -outputdoc
+option. The document will be overwritten if it already exists.
+
+Inputs can be provided in three ways. Both -inputfile and -inputvalue options
+can be used together; -inputdoc option must be used on its own. -inputfile and
+-inputvalue options both take two additional arguments, the name of the port
+for the input, and either a file containing the input data, or the input value
+itself respectively.
+
+If one of more of your workflow inputs is a list, you can create a list
+input by using the -inputdelimiter option, which may be used with either
+-inputfile or -inputvalue. This option takes two parameters - an input name
+and the delimiter by which to split the input into a list.
+
+The delimiter may be a simple character, such as a comma or a new-line
+character, or a regular expression. The input string, or file, will then be
+converted into a list being split by the delimiter specified. Make sure to 
+put the delimiter character in quotes as it may be interpreted by the shell 
+as a special character, e.g. ;.
+
+If a list of greater depth (i.e. a list or lists or deeper) is required then
+you will need to use the -inputdoc option.  However, if you provide an input
+of lower depth to that required, then it will automatically be wrapped in one
+or more lists up to the required depth. Providing an input of greater depth
+than that required will result in an error.
+
+If a workflow has a high memory requirement, then it may be better to run it
+using a database to store data rather than storing it in memory, which is the
+default option. There are three options for using a database:
+
+-embedded option, runs with an embedded database. This is slightly faster than
+the -clientserver option (below), but has the limitation that only one
+executeworkflow script may be executed simultaneously.
+
+-clientserver option allows the workflow to be executed backed by the database
+running as a server. By default a database is not started for you, but may be
+started using -startdb option.
+
+-startdb option starts a database. It may be used without providing a workflow
+to allow a database to be started separately, allowing multiple simultaneous
+executeworkflow script runs.
+
+More advanced database configurations can be specified using -dbproperties
+option, allowing you to take full control over the database used. This takes a
+second argument, the filename of the propeties file, for which the following
+example contains the default settings used:
+
+in_memory = true
+provenance = false
+connector = derby
+port = 1527
+dialect = org.hibernate.dialect.DerbyDialect
+start_derby = false
+driver = org.apache.derby.jdbc.EmbeddedDriver
+jdbcuri = jdbc:derby:t2-database;create=true;upgrade=true
+
+Note that when using -dbproperties together with other options, the other
+options take precedence.
+
+-cmdir option lets you specify an absolute path to a directory where 
+Credential Manager's files (keystore and truststore - containing user's 
+credentials and trusted certificates for accessing secure services) are stored.
+If not specified and the workflow requires access to these files, Taverna will 
+try to find them in the default location in <TAVERNA_HOME>/security somewhere 
+inside user's home directory (depending on the platform).
+
+-cmpassword option can be used to tell Taverna to expect the password for the 
+Credential Manager on standard input. If the password is not piped in, Taverna 
+will prompt you for it in the terminal and block until it is entered. Do not 
+enter your password in the command line! If -cmpassword option is not specified 
+and -cmdir option is used, Taverna will try to find the password in a special 
+file password.txt in the directory specified with -cmdir option.
diff --git a/taverna-commandline-common/src/test/java/net/sf/taverna/t2/commandline/TestCommandLineOptionsHandler.java b/taverna-commandline-common/src/test/java/net/sf/taverna/t2/commandline/TestCommandLineOptionsHandler.java
new file mode 100644
index 0000000..dddeba6
--- /dev/null
+++ b/taverna-commandline-common/src/test/java/net/sf/taverna/t2/commandline/TestCommandLineOptionsHandler.java
@@ -0,0 +1,266 @@
+package net.sf.taverna.t2.commandline;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import net.sf.taverna.t2.commandline.exceptions.InvalidOptionException;
+import net.sf.taverna.t2.commandline.options.CommandLineOptions;
+
+import org.junit.Test;
+
+public class TestCommandLineOptionsHandler {
+
+	@Test
+	public void testWorkflowName() throws Exception {
+		CommandLineOptions handler = new CommandLineOptions(
+				new String[] { "myworkflow.t2flow" });
+		assertEquals("myworkflow.t2flow", handler.getWorkflow());
+	}	
+	
+	@Test
+	public void shouldShowHelp() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(
+				new String[] { "-help" });
+		assertTrue(options.askedForHelp());
+		options = new CommandLineOptions(
+				new String[] {});
+		assertTrue(options.askedForHelp());
+		options = new CommandLineOptions(new String[] { "myworkflow.t2flow" });
+		assertFalse(options.askedForHelp());
+	}
+
+	@Test
+	public void getWorkflow() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(
+				new String[] { "-help" });
+		assertNull(options.getWorkflow());
+		options = new CommandLineOptions(new String[] { "myworkflow.t2flow" });
+		assertEquals("myworkflow.t2flow", options.getWorkflow());
+	}
+
+	@Test(expected = InvalidOptionException.class)
+	public void cannotProvideInputFileAndInputDoc() throws Exception {
+		new CommandLineOptions(new String[] { "-inputfile", "fred", "fred.txt",
+				"-inputdoc", "myworkflow.t2flow" });
+	}
+	
+	@Test(expected = InvalidOptionException.class)
+	public void cannotProvideInputValueAndInputDoc() throws Exception {
+		new CommandLineOptions(new String[] { "-inputvalue", "fred", "fred.txt",
+				"-inputdoc", "myworkflow.t2flow" });
+	}
+	
+	@Test
+	public void canProvideInputValueAndFileTogether() throws Exception {
+		//should not be an error
+		new CommandLineOptions(new String[] { "-inputvalue", "fred", "abc",
+				"-inputfile","fred2","fred2.txt","myworkflow.t2flow" });
+	}
+
+	@Test
+	public void getInputs() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-inputfile", "fred", "fred.txt", "myworkflow.t2flow" });
+		assertEquals(2, options.getInputFiles().length);
+		assertEquals("fred", options.getInputFiles()[0]);
+		assertEquals("fred.txt", options.getInputFiles()[1]);
+
+		options = new CommandLineOptions(new String[] { "-inputfile", "fred",
+				"fred.txt", "-inputfile", "fred2", "fred2.txt",
+				"myworkflow.t2flow" });
+		assertEquals(4, options.getInputFiles().length);
+		assertEquals("fred", options.getInputFiles()[0]);
+		assertEquals("fred.txt", options.getInputFiles()[1]);
+		assertEquals("fred2", options.getInputFiles()[2]);
+		assertEquals("fred2.txt", options.getInputFiles()[3]);
+
+		options = new CommandLineOptions(new String[] { "myworkflow.t2flow" });
+		assertNotNull(options.getInputFiles());
+		assertEquals(0, options.getInputFiles().length);
+
+	}
+
+	@Test
+	public void hasInputValue() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-inputvalue", "fred", "abc", "myworkflow.t2flow" });
+		assertTrue(options.hasInputValues());
+
+		options = new CommandLineOptions(new String[] { "myworkflow.t2flow" });
+		assertFalse(options.hasInputValues());
+	}
+	
+	@Test
+	public void getInputValues() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-inputvalue", "fred", "abc", "myworkflow.t2flow" });
+		assertEquals(2, options.getInputValues().length);
+
+		options = new CommandLineOptions(new String[] { "myworkflow.t2flow" });
+		assertNotNull(options.getInputValues());
+		assertEquals(0,options.getInputValues().length);
+	}
+
+	@Test
+	public void hasInputs() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-inputfile", "fred", "fred.txt", "myworkflow.t2flow" });
+		assertTrue(options.hasInputFiles());
+
+		options = new CommandLineOptions(new String[] { "myworkflow.t2flow" });
+		assertFalse(options.hasInputFiles());
+	}	
+
+	@Test
+	public void noWorkflowNameButStartDB() throws Exception {
+		// should not throw an error
+		CommandLineOptions options = new CommandLineOptions(
+				new String[] { "-startdb" });
+		assertTrue(options.getStartDatabase());
+		assertTrue(options.getStartDatabaseOnly());
+	}
+
+	@Test
+	public void workflowNameAndStartDB() throws Exception {
+		// should not throw an error
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-startdb", "myworkflow.t2flow" });
+		assertTrue(options.getStartDatabase());
+		assertFalse(options.getStartDatabaseOnly());
+	}
+
+	@Test(expected = InvalidOptionException.class)
+	public void provenanceButNoDatabase() throws Exception {
+		new CommandLineOptions(new String[] { "-provenance",
+				"myworkflow.t2flow" });
+	}
+
+	@Test(expected = InvalidOptionException.class)
+	public void provenanceButNoDatabase2() throws Exception {
+		new CommandLineOptions(new String[] { "-provenance", "-inmemory",
+				"myworkflow.t2flow" });
+	}
+
+	@Test
+	public void provenanceDatabase() throws Exception {
+		// should be no errors
+		new CommandLineOptions(new String[] { "-provenance", "-embedded",
+				"myworkflow.t2flow" });
+		new CommandLineOptions(new String[] { "-provenance", "-clientserver",
+				"myworkflow.t2flow" });
+		new CommandLineOptions(new String[] { "-provenance", "-dbproperties",
+				"dbproperties.properties", "myworkflow.t2flow" });
+	}
+	
+	@Test
+	public void testHasInputDelimiter() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-inputvalue","in1","1,2,3","-inputdelimiter","in1",",","-inputdelimiter","in2",",","myworkflow.t2flow" });
+		assertTrue(options.hasDelimiterFor("in1"));
+		assertTrue(options.hasDelimiterFor("in2"));
+		assertFalse(options.hasDelimiterFor("in3"));
+	}
+	
+	@Test(expected = InvalidOptionException.class)
+	public void testInputDelimiterInvalidWithInputDoc() throws Exception {
+		new CommandLineOptions(new String[] {
+				"-inputdoc","doc.xml","-inputdelimiter","in1",",","myworkflow.t2flow" });
+	}
+	
+	
+	@Test
+	public void testInputDelimiter() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-inputvalue","in1","1,2,3","-inputdelimiter","in1",",","-inputdelimiter","in2","!","myworkflow.t2flow" });
+		assertEquals(",",options.inputDelimiter("in1"));
+		assertEquals("!",options.inputDelimiter("in2"));
+		assertNull(options.inputDelimiter("in3"));
+	}
+
+	@Test
+	public void testInMemory() throws Exception {
+		CommandLineOptions handler = new CommandLineOptions(new String[] {
+				"-inmemory", "myworkflow.t2flow" });
+		assertTrue(handler.hasOption("inmemory"));
+	}
+
+	@Test
+	public void testEmbedded() throws Exception {
+		CommandLineOptions handler = new CommandLineOptions(new String[] {
+				"-embedded", "myworkflow.t2flow" });
+		assertTrue(handler.hasOption("embedded"));
+	}
+
+	@Test
+	public void testClientServer() throws Exception {
+		CommandLineOptions handler = new CommandLineOptions(new String[] {
+				"-clientserver", "myworkflow.t2flow" });
+		assertTrue(handler.hasOption("clientserver"));
+	}
+
+	@Test(expected = InvalidOptionException.class)
+	public void testInvalidEmbeddedAndClientServer() throws Exception {
+		new CommandLineOptions(new String[] { "-clientserver", "-embedded",
+				"myworkflow.t2flow" });
+	}
+
+	@Test(expected = InvalidOptionException.class)
+	public void testInvalidEmbeddedAndMemory() throws Exception {
+		new CommandLineOptions(new String[] { "-embedded", "-inmemory",
+				"myworkflow.t2flow" });
+	}
+
+	@Test(expected = InvalidOptionException.class)
+	public void testInvalidClientServerAndInMemory() throws Exception {
+		new CommandLineOptions(new String[] { "-clientserver", "-inmemory",
+				"myworkflow.t2flow" });
+	}
+
+	@Test
+	public void isInMemory() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-inmemory", "myworkflow.t2flow" });
+
+		assertTrue(options.isInMemory());
+		assertFalse(options.isClientServer());
+		assertFalse(options.isEmbedded());
+	}
+
+	@Test
+	public void isClientServer() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-clientserver", "myworkflow.t2flow" });
+
+		assertTrue(options.isClientServer());
+		assertFalse(options.isInMemory());
+		assertFalse(options.isEmbedded());
+	}
+	
+	@Test
+	public void hasLogFile() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-logfile","/tmp/logging", "myworkflow.t2flow" });
+
+		assertTrue(options.hasLogFile());
+		assertEquals("/tmp/logging", options.getLogFile());
+	}
+	
+	@Test(expected = InvalidOptionException.class)
+	public void hasLogFileNotValidWithoutWorkflow() throws Exception{
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-logfile","/tmp/logging"});
+	}
+
+	@Test
+	public void isEmbedded() throws Exception {
+		CommandLineOptions options = new CommandLineOptions(new String[] {
+				"-embedded", "myworkflow.t2flow" });
+
+		assertTrue(options.isEmbedded());
+		assertFalse(options.isInMemory());
+		assertFalse(options.isClientServer());
+	}
+
+}
diff --git a/taverna-commandline-common/src/test/java/net/sf/taverna/t2/commandline/TestDatabaseConfigurationHandler.java b/taverna-commandline-common/src/test/java/net/sf/taverna/t2/commandline/TestDatabaseConfigurationHandler.java
new file mode 100644
index 0000000..555a1a6
--- /dev/null
+++ b/taverna-commandline-common/src/test/java/net/sf/taverna/t2/commandline/TestDatabaseConfigurationHandler.java
@@ -0,0 +1,20 @@
+package net.sf.taverna.t2.commandline;
+
+import static org.junit.Assert.assertEquals;
+import net.sf.taverna.t2.commandline.data.DatabaseConfigurationHandler;
+import net.sf.taverna.t2.commandline.options.CommandLineOptions;
+import net.sf.taverna.t2.workbench.reference.config.DataManagementConfiguration;
+
+import org.junit.Test;
+
+public class TestDatabaseConfigurationHandler {
+
+	@Test
+	public void testDefaults() throws Exception {
+		CommandLineOptions opts = new CommandLineOptions(new String[]{"myworkflow.t2flow"});
+		DatabaseConfigurationHandler handler = new DatabaseConfigurationHandler(opts);
+		handler.configureDatabase();
+		assertEquals("org.apache.derby.jdbc.EmbeddedDriver", DataManagementConfiguration.getInstance().getDriverClassName());
+		assertEquals(false, DataManagementConfiguration.getInstance().getStartInternalDerbyServer());
+	}
+}