blob: f3274b5f269b291a3142bc88d4569a76e007abf3 [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.File;
import java.io.IOException;
import java.io.InputStream;
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.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.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);
return setupAndExecute(args,options);
} 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());
}
return 0;
}
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 {
if (!options.askedForHelp()) {
setupDatabase(options);
if (options.getWorkflow() != null) {
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 (!resultListener.isComplete()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
logger
.warn(
"Thread Interuption Exception whilst waiting for dataflow completion",
e);
}
}
}
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);
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);
}
}
private CommandLineResultListener addResultListener(
WorkflowInstanceFacade facade, InvocationContext context,
Dataflow dataflow, CommandLineOptions options) {
File outputDir = null;
File baclavaDoc = null;
if (options.saveResultsToDirectory()) {
outputDir = determineOutputDir(options, dataflow.getLocalName());
}
if (options.getOutputDocument() != null) {
baclavaDoc = new File(options.getOutputDocument());
}
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);
CommandLineResultListener listener = new CommandLineResultListener(
outputPortNamesAndDepth.size(), resultsHandler,
outputDir != null, baclavaDoc != null);
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);
}
}