blob: 814ce438e127e4406c6bb24002870189a062fcd3 [file] [log] [blame]
/*******************************************************************************
* 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.Authenticator;
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.security.credentialmanager.CredentialManagerAuthenticator;
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()) {
Authenticator.setDefault(new CredentialManagerAuthenticator());
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);
}
}