blob: 9d8c5fccb87a4258ff20435bf14949f56ba423d5 [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.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingException;
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.InputMismatchException;
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.security.credentialmanager.CMException;
import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
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.purl.wf4ever.robundle.Bundle;
import uk.org.taverna.configuration.database.DatabaseConfiguration;
import uk.org.taverna.configuration.database.DatabaseManager;
import uk.org.taverna.databundle.DataBundles;
import uk.org.taverna.platform.execution.api.ExecutionEnvironment;
import uk.org.taverna.platform.execution.api.InvalidExecutionIdException;
import uk.org.taverna.platform.execution.api.InvalidWorkflowException;
import uk.org.taverna.platform.report.State;
import uk.org.taverna.platform.report.WorkflowReport;
import uk.org.taverna.platform.run.api.InvalidRunIdException;
import uk.org.taverna.platform.run.api.RunProfile;
import uk.org.taverna.platform.run.api.RunProfileException;
import uk.org.taverna.platform.run.api.RunService;
import uk.org.taverna.platform.run.api.RunStateException;
import uk.org.taverna.scufl2.api.common.NamedSet;
import uk.org.taverna.scufl2.api.container.WorkflowBundle;
import uk.org.taverna.scufl2.api.core.Workflow;
import uk.org.taverna.scufl2.api.io.ReaderException;
import uk.org.taverna.scufl2.api.io.WorkflowBundleIO;
import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
import uk.org.taverna.scufl2.validation.ValidationException;
import uk.org.taverna.scufl2.validation.correctness.CorrectnessValidator;
import uk.org.taverna.scufl2.validation.correctness.ReportCorrectnessValidationListener;
import uk.org.taverna.scufl2.validation.structural.ReportStructuralValidationListener;
import uk.org.taverna.scufl2.validation.structural.StructuralValidator;
/**
* A utility class that wraps the process of executing a workflow, allowing workflows to be easily
* executed independently of the GUI.
*
* @author Stuart Owen
* @author Alex Nenadic
*/
public class CommandLineTool {
private static boolean BOOTSTRAP_LOGGING = false;
private static Logger logger = Logger.getLogger(CommandLineTool.class);
private RunService runService;
private CredentialManager credentialManager;
private CommandLineOptions commandLineOptions;
private WorkflowBundle workflowBundle;
private WorkflowBundleIO workflowBundleIO;
private DatabaseConfiguration databaseConfiguration;
private DatabaseManager databaseManager;
public void run() {
try {
if (BOOTSTRAP_LOGGING)
initialiseLogging();
int result = setupAndExecute();
System.exit(result);
} catch (InvalidOptionException | IOException | ReadInputException
| InvalidRunIdException | RunStateException
| InvalidExecutionIdException | OpenDataflowException
| RunProfileException e) {
error(e.getMessage());
} catch (CMException e) {
error("There was an error initializing Taverna's SSLSocketFactory from Credential Manager. "
+ e.getMessage());
} catch (ReaderException e) {
error("There was an error reading the workflow: " + e.getMessage());
} catch (ValidationException e) {
error("There was an error validating the workflow: " + e.getMessage());
} catch (InvalidWorkflowException e) {
error("There was an error validating the workflow: " + e.getMessage());
} catch (DatabaseConfigurationException e) {
error("There was an error configuring the database: " + e.getMessage());
}
System.exit(1);
}
private void initialiseLogging() {
LogManager.resetConfiguration();
if (System.getProperty("log4j.configuration") == null) {
try {
PropertyConfigurator.configure(CommandLineTool.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 (commandLineOptions.hasLogFile()) {
RollingFileAppender appender;
try {
PatternLayout layout = new PatternLayout("%-5p %d{ISO8601} (%c:%L) - %m%n");
appender = new RollingFileAppender(layout, commandLineOptions.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 " + commandLineOptions.getLogFile());
}
}
}
public int setupAndExecute() throws InputMismatchException, InvalidOptionException,
CMException, OpenDataflowException, ReaderException, IOException, ValidationException,
ReadInputException, InvalidWorkflowException, RunProfileException,
InvalidRunIdException, RunStateException, InvalidExecutionIdException, DatabaseConfigurationException {
if (!commandLineOptions.askedForHelp()) {
setupDatabase(commandLineOptions);
if (commandLineOptions.getWorkflow() != null) {
/*
* Initialise Credential Manager and SSL stuff quite early as
* parsing and validating the workflow may require it
*/
String credentialManagerDirPath = commandLineOptions.getCredentialManagerDir();
/*
* If credentialManagerDirPath is null, the Credential Manager
* will be initialized 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.setConfigurationDirectoryPath(new File(
credentialManagerDirPath));
}
// Initialise the SSL stuff - set the SSLSocketFactory
// to use Taverna's Keystore and Truststore.
credentialManager.initializeSSL();
URL workflowURL = readWorkflowURL(commandLineOptions.getWorkflow());
workflowBundle = workflowBundleIO.readBundle(workflowURL, null);
logger.debug("Read the wf bundle");
validateWorkflowBundle(workflowBundle);
logger.debug("Validated the wf bundle");
Set<ExecutionEnvironment> executionEnvironments = runService
.getExecutionEnvironments();
ExecutionEnvironment executionEnvironment = null;
/*
* Find the right execution environment, e.g. local execution
* with the correct reference service based on command line
* options
*/
while (executionEnvironments.iterator().hasNext()) {
// TODO Choose the right one
// take the fist one for now
executionEnvironment = executionEnvironments.iterator().next();
break;
}
logger.debug("Got the execution environment");
InputsHandler inputsHandler = new InputsHandler();
Map<String, InputWorkflowPort> portMap = new HashMap<String, InputWorkflowPort>();
Workflow workflow = workflowBundle.getMainWorkflow();
for (InputWorkflowPort port : workflow.getInputPorts()) {
portMap.put(port.getName(), port);
}
inputsHandler.checkProvidedInputs(portMap, commandLineOptions);
logger.debug("Checked inputs");
Bundle inputs = inputsHandler.registerInputs(portMap, commandLineOptions, null);
logger.debug("Registered inputs");
RunProfile runProfile = new RunProfile(executionEnvironment, workflowBundle, inputs);
String runId = runService.createRun(runProfile);
runService.start(runId);
logger.debug("Started wf run");
WorkflowReport report = runService.getWorkflowReport(runId);
while (!workflowFinished(report)) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// Ignore
}
}
NamedSet<OutputWorkflowPort> workflowOutputPorts = workflow.getOutputPorts();
if (!workflowOutputPorts.isEmpty()) {
File outputDir = null;
if (commandLineOptions.saveResultsToDirectory()) {
outputDir = determineOutputDir(commandLineOptions, workflowBundle.getName());
outputDir.mkdirs();
}
Path outputs = DataBundles.getOutputs(runService.getDataBundle(runId));
if (outputDir != null) {
SaveResultsHandler saveResultsHandler = new SaveResultsHandler(outputDir);
for (OutputWorkflowPort outputWorkflowPort : workflowOutputPorts) {
String workflowOutputPortName = outputWorkflowPort.getName();
Path output = DataBundles.getPort(outputs, workflowOutputPortName);
if (!DataBundles.isMissing(output)) {
saveResultsHandler.saveResultsForPort(workflowOutputPortName, output);
}
}
}
}
if (report.getState().equals(State.FAILED)) {
System.out.println("Workflow failed - see report below.");
System.out.println(report);
} else if (report.getState().equals(State.COMPLETED)) {
System.out.println("Workflow completed.");
}
}
} else {
commandLineOptions.displayHelp();
}
// wait until user hits CTRL-C before exiting
if (commandLineOptions.getStartDatabaseOnly()) {
// FIXME: need to do this more gracefully.
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
return 0;
}
}
}
return 0;
}
private boolean workflowFinished(WorkflowReport report) {
State state = report.getState();
if (state == State.CANCELLED || state == State.COMPLETED || state == State.FAILED) {
return true;
}
return false;
}
protected void validateWorkflowBundle(WorkflowBundle workflowBundle) throws ValidationException {
CorrectnessValidator cv = new CorrectnessValidator();
ReportCorrectnessValidationListener rcvl = new ReportCorrectnessValidationListener();
cv.checkCorrectness(workflowBundle, true, rcvl);
if (rcvl.detectedProblems()) {
throw rcvl.getException();
}
StructuralValidator sv = new StructuralValidator();
ReportStructuralValidationListener rsvl = new ReportStructuralValidationListener();
sv.checkStructure(workflowBundle, rsvl);
if (rsvl.detectedProblems()) {
throw rcvl.getException();
}
}
private void setupDatabase(CommandLineOptions options)
throws DatabaseConfigurationException {
DatabaseConfigurationHandler dbHandler = new DatabaseConfigurationHandler(
options, databaseConfiguration, databaseManager);
dbHandler.configureDatabase();
try {
if (!options.isInMemory())
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 File determineOutputDir(CommandLineOptions options, String dataflowName) {
File result = new File(dataflowName + "_output");
int x = 1;
while (result.exists()) {
result = new File(dataflowName + "_output_" + x);
x++;
}
System.out.println("Outputs will be saved to the directory: "
+ result.getAbsolutePath());
return result;
}
protected void error(String msg) {
System.err.println(msg);
}
private URL readWorkflowURL(String workflowOption) throws OpenDataflowException {
try {
return new URL(new URL("file:"), workflowOption);
} catch (MalformedURLException e) {
throw new OpenDataflowException("The was an error processing the URL to the workflow: "
+ e.getMessage(), e);
}
}
public void setCommandLineOptions(CommandLineOptions commandLineOptions){
this.commandLineOptions = commandLineOptions;
}
public void setRunService(RunService runService) {
this.runService = runService;
}
public void setCredentialManager(CredentialManager credentialManager) {
this.credentialManager = credentialManager;
}
public void setWorkflowBundleIO(WorkflowBundleIO workflowBundleIO) {
this.workflowBundleIO = workflowBundleIO;
}
/**
* Sets the databaseConfiguration.
*
* @param databaseConfiguration the new value of databaseConfiguration
*/
public void setDatabaseConfiguration(DatabaseConfiguration databaseConfiguration) {
this.databaseConfiguration = databaseConfiguration;
}
/**
* Sets the databaseManager.
*
* @param databaseManager the new value of databaseManager
*/
public void setDatabaseManager(DatabaseManager databaseManager) {
this.databaseManager = databaseManager;
}
}