#set( $symbol_pound = '#' )
#set( $symbol_dollar = '$' )
#set( $symbol_escape = '\' )
package ${package};
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Date;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import net.sourceforge.seqware.common.module.FileMetadata;
import net.sourceforge.seqware.common.module.ReturnValue;
import net.sourceforge.seqware.common.util.filetools.FileTools;
import net.sourceforge.seqware.pipeline.module.Module;
import net.sourceforge.seqware.pipeline.module.ModuleInterface;
import net.sourceforge.seqware.common.util.runtools.RunTools;
import org.openide.util.lookup.ServiceProvider;
/**
* This is a hello world module that should give you an idea of how
* to create a module of your own. It uses the same utilities and syntax
* as a real module.
*
* Please use JavaDoc to document each method (the user interface documents
* will be autogenerated using these comments). See
* http://en.wikipedia.org/wiki/Javadoc for more information.
*
* @author briandoconnor@gmail.com
*
*/
@ServiceProvider(service=ModuleInterface.class)
public class HelloWorldExample extends Module {
private OptionSet options = null;
private File tempDir = null;
/**
* getOptionParser is an internal method to parse command line args.
*
* @return OptionParser this is used to get command line options
*/
protected OptionParser getOptionParser() {
OptionParser parser = new OptionParser();
parser.accepts("greeting", "A greeting that will be echoed back in the output file, the number of times depends on the repeat param.").withRequiredArg().ofType(String.class).describedAs("required");
parser.accepts("repeat", "How many times to repeat greeting").withRequiredArg().ofType(Integer.class).describedAs("required");
parser.accepts("input-file", "This is the input file name to cat before addition to gretting").withRequiredArg().ofType(String.class).describedAs("optional");
parser.accepts("output-file", "This is the output file name").withRequiredArg().ofType(String.class).describedAs("required");
parser.accepts("echo-binary-path", "Option path to the echo binary").withRequiredArg().ofType(String.class).describedAs("optional");
parser.accepts("cat-binary-path", "Option path to the cat binary").withRequiredArg().ofType(String.class).describedAs("optional");
return (parser);
}
/**
* A method used to return the syntax for this module
* @return a string describing the syntax
*/
@Override
public String get_syntax() {
OptionParser parser = getOptionParser();
StringWriter output = new StringWriter();
try {
parser.printHelpOn(output);
return(output.toString());
} catch (IOException e) {
e.printStackTrace();
return(e.getMessage());
}
}
/**
* The init method is where you put any code needed to setup your module.
* Here I set some basic information in the ReturnValue object which will eventually
* populate the "processing" table in seqware_meta_db. I also create a temporary
* directory using the FileTools object.
*
* init is optional
*
* @return A ReturnValue object that contains information about the status of init
*/
@Override
public ReturnValue init() {
// setup the return value object, notice that we use
// ExitStatus, this is what SeqWare uses to track the status
ReturnValue ret = new ReturnValue();
ret.setExitStatus(ReturnValue.SUCCESS);
// fill in the algorithm field in the processing table
ret.setAlgorithm("hello-world-module");
// fill in the description field in the processing table
ret.setDescription("This demonstrates how to write a simple module");
// fill in the version field in the processing table
ret.setVersion("0.7.0");
try {
OptionParser parser = getOptionParser();
// The parameters object is actually an ArrayList of Strings created
// by splitting the command line options by space. JOpt expects a String[]
options = parser.parse(this.getParameters().toArray(new String[0]));
// create a temp directory in current working directory
tempDir = FileTools.createTempDirectory(new File("."));
// you can write to "stdout" or "stderr" which will be persisted back to the DB
ret.setStdout(ret.getStdout()+"Output: "+(String)options.valueOf("output-file")+"${symbol_escape}n");
} catch (OptionException e) {
e.printStackTrace();
ret.setStderr(e.getMessage());
ret.setExitStatus(ReturnValue.INVALIDPARAMETERS);
} catch (IOException e) {
e.printStackTrace();
ret.setStderr(e.getMessage());
ret.setExitStatus(ReturnValue.DIRECTORYNOTWRITABLE);
}
// now return the ReturnValue
return ret;
}
/**
* Verifies that the parameters make sense
*
* @return a ReturnValue object
*/
@Override
public ReturnValue do_verify_parameters() {
// most methods return a ReturnValue object
ReturnValue ret = new ReturnValue();
ret.setExitStatus(ReturnValue.SUCCESS);
// now look at the options and make sure they make sense
for (String option : new String[] {
"greeting", "repeat", "output-file"
}) {
if (!options.has(option)) {
ret.setExitStatus(ReturnValue.INVALIDPARAMETERS);
String stdErr = ret.getStderr();
ret.setStderr(stdErr+"Must include parameter: --"+option+"${symbol_escape}n");
}
}
// can pull back typed command line args
if ((Integer)options.valueOf("repeat") < 1) {
ret.setExitStatus(ReturnValue.INVALIDPARAMETERS);
String stdErr = ret.getStderr();
ret.setStderr(stdErr+"The parameter --repeat must be >= 1${symbol_escape}n");
}
return ret;
}
/**
* The do_verify_input method ensures that the input files exist. It
* may also do validation of the input files or anything that is needed
* to make sure the module has everything it needs to run. There is some
* overlap between this method and do_verify_parameters. This one is more
* focused on validating files, making sure web services are up, DBs can be
* connected to etc. While do_verify_parameters is primarily used to
* validate that the minimal parameters are passed in. The overlap between
* these two methods is at the discretion of the developer
*
* @return a ReturnValue object
*/
@Override
public ReturnValue do_verify_input() {
// not much to do, let's make sure the
// temp directory is writable
ReturnValue ret = new ReturnValue();
ret.setExitStatus(ReturnValue.SUCCESS);
// If input-file argument was specified, make sure it exits and is readable
if ( options.has("input-file") ) {
ReturnValue inputRet = FileTools.fileExistsAndReadable( new File( (String) options.valueOf("input-file") ));
if ( inputRet.getExitStatus() != ReturnValue.SUCCESS) {
ret.setExitStatus(ReturnValue.FILENOTREADABLE);
ret.setStderr("Can't read from input file " + (String) options.valueOf("input-file") + ": " + inputRet.getStderr() );
return ret;
}
}
// Notice the FileTools actually returns ReturnValue objects too!
if (FileTools.dirPathExistsAndWritable(tempDir).getExitStatus() != ReturnValue.SUCCESS) {
ret.setExitStatus(ReturnValue.DIRECTORYNOTWRITABLE);
ret.setStderr("Can't write to temp directory");
}
return ret;
}
/**
* This is really an optional method but a very good idea. You
* would test the programs your calling here by running them on
* a "known good" test dataset and then compare the new answer
* with the previous known good answer. Other forms of testing could be
* encapsulated here as well.
*
* @return a ReturnValue object
*/
@Override
public ReturnValue do_test() {
// notice the use of "NOTIMPLEMENTED", this signifies that we simply
// aren't doing this step. It's better than just saying SUCCESS
ReturnValue ret = new ReturnValue();
ret.setExitStatus(ReturnValue.NOTIMPLEMENTED);
// not much to do, just return
return(ret);
}
/**
* This is the core of a module. While some modules may be written in pure Java or use
* various third-party Java APIs, the vast majority of modules will use this method to
* make calls out to the shell (typically the BASH shell in Linux) and use that shell
* to execute various commands. In an ideal world this would never happen, we would all
* write out code with a language-agnostic, network-aware API (e.g. thrift, SOAP, etc).
* But until that day comes most programs in bioinformatics are command line tools
* (or websites). So the heart of the module is it acts as a way for us to treat the
* disparate tools as well-behaved modules that present a standard interface
* and report back their metadata in well-defined ways. That's, ultimately, what this
* object and, in particular this method, are all about.
*
* There are other alternatives out there, such as Galaxy, that may provide an XML
* syntax for accomplishing much of the same thing. For example, they make disparate tools
* appear to function the same because the inputs/outputs are all described using a standardized
* language. We chose Java because it was more expressive than XML as a module running
* descriptor. But clearly there are a lot of ways to solve this problem. The key concern,
* though, is that a module should present very clear inputs and outputs based,
* whenever possible, on standardized file types. This makes it easy to use modules in
* novel workflows, rearranging them as needed. Make every effort to make your modules
* self-contained and robust!
*
* @return a ReturnValue object
*/
@Override
public ReturnValue do_run() {
// prepare the return value
ReturnValue ret = new ReturnValue();
ret.setExitStatus(ReturnValue.SUCCESS);
// track the start time of do_run for timing purposes
ret.setRunStartTstmp(new Date());
// save StdErr and StdOut
StringBuffer stderr = new StringBuffer();
StringBuffer stdout = new StringBuffer();
int repeat = (Integer)options.valueOf("repeat");
String greeting = (String)options.valueOf("greeting");
String output = (String)options.valueOf("output-file");
String echoBinary = null ;
// If directory of output-file does not exist than make it
File parentDirectory = new File( (String) options.valueOf("output-file")).getParentFile();
if ( ! parentDirectory.exists() ) {
parentDirectory.mkdirs();
}
if ( options.has("echo-binary-path") ) {
echoBinary = (String)options.valueOf("echo-binary-path");
stderr.append("Using binary " + echoBinary + "${symbol_escape}n");
}
else {
echoBinary = "echo";
}
if ( options.has("input-file") ) {
String catBinary = null;
if ( options.has("cat-binary-path") ) {
catBinary = (String)options.valueOf("cat-binary-path");
}
else {
catBinary = "cat";
}
ReturnValue result = RunTools.runCommand(new String[] { "bash", "-c", catBinary + " " + (String) options.valueOf("input-file") + " >> "+output} );
stderr.append(result.getStderr());
stdout.append(result.getStdout());
}
for (int i=0; i<repeat; i++) {
// This is a generally safe way to call from the command line, with an explicit shell. Here it's
// bash. Normally, you can just call RunTools.runCommand with a string as an argument but
// the output redirect here requires this array style of calling.
ReturnValue result = RunTools.runCommand(new String[] { "bash", "-c", echoBinary + " '" + greeting+"' >> "+output} );
stderr.append(result.getStderr());
stdout.append(result.getStdout());
if (result.getProcessExitStatus() != ReturnValue.SUCCESS || result.getExitStatus() != ReturnValue.SUCCESS) {
ret.setExitStatus(result.getExitStatus());
ret.setProcessExitStatus(result.getProcessExitStatus());
ret.setStderr(stderr.toString());
ret.setStdout(stdout.toString());
return(ret);
}
}
// record the file output
FileMetadata fm = new FileMetadata();
fm.setMetaType("text/plain");
fm.setFilePath(output);
fm.setType("hello-world-text-output");
fm.setDescription("A text output for hello world.");
ret.getFiles().add(fm);
// note the time do_run finishes
ret.setRunStopTstmp(new Date());
return(ret);
}
/**
* A method to check to make sure the output was created correctly
*
* @return a ReturnValue object
*/
@Override
public ReturnValue do_verify_output() {
// this is easy, just make sure the file exists
return(FileTools.fileExistsAndReadable(new File((String)options.valueOf("output-file"))));
}
/**
* A cleanup method, make sure you cleanup files that are outside the current working directory
* since SeqWare won't clean those for you.
*
* clean_up is optional
* @return
*/
@Override
public ReturnValue clean_up() {
ReturnValue ret = new ReturnValue();
ret.setExitStatus(ReturnValue.SUCCESS);
if (!tempDir.delete()) {
ret.setExitStatus(ReturnValue.DIRECTORYNOTWRITABLE);
ret.setStderr("Can't delete folder: "+tempDir.getAbsolutePath());
}
return(ret);
}
}