/* * CommandRunner.java */ package org.ngbw.sdk; import java.io.IOException; import java.security.SecureRandom; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ngbw.sdk.core.configuration.ServiceFactory; import org.ngbw.sdk.core.shared.TaskRunStage; import org.ngbw.sdk.core.shared.UserRole; import org.ngbw.sdk.tool.RenderedCommand; import org.ngbw.sdk.tool.Tool; import org.ngbw.sdk.database.Folder; import org.ngbw.sdk.database.Task; import org.ngbw.sdk.database.TaskLogMessage; import org.ngbw.sdk.database.TaskOutputSourceDocument; import org.ngbw.sdk.database.User; /** CommandRunner provides a "backdoor" for clients that need to run a tool that's managed/deployed by the workbench, but don't want to use the workbench to display a gui and compose the command. @author: Terri Liebowitz */ class CommandRunner extends TaskRunner { private static final Log log = LogFactory.getLog(CommandRunner.class); private static final String username = "NGBW-RUN-COMMAND"; private static final String foldername = "NGBW-RUN-COMMAND"; private static final int MIN_PASSWORD_LENGTH = 10; /** * Constructor. * * @param serviceFactory */ CommandRunner(ServiceFactory serviceFactory) { super(serviceFactory); } /** Run already rendered commands synchronously. Does so by inserting a Task that isn't associated with any user. @param tool @param command Rendered command line to be executed. @param input Maps filenames (as they appear in command) to their contents as byte arrays. The data will be staged to the specified files before the command is executed. @param outputFiles Maps parameter names to the names of the expected output files. Filenames may include wildcards. The contents of the files will be harvested to create the map that is returned. @return Map of output parameter name -> (map of filename -> file contents) Each output parameter may map to multiple filenames if wildcards were used in outputFiles. * @throws Exception @exception Throws WorkbenchException which is a RuntimeException if any errors occur. In particular, if the command returns a non zero exit status. */ public Map> runCommand( String toolName, String[] command, Map input, Map outputFiles) throws Exception { User user = User.findUserByUsername(username); if (user == null) { user = new User(); user.setFirstName(username); user.setLastName(username); user.setUsername(username); user.setPassword(generatePassword()); user.setEmail(username); user.setRole(UserRole.STANDARD); user.save(); } long userId = user.getUserId(); // Set the userId in our TaskRunner base object. setUserId(userId); String taskLabel; TaskRunStage stage = TaskRunStage.READY; Folder folder = getRunCommandFolder(user); Task task = new Task(folder); task.setLabel(taskLabel = (toolName + "-" + System.currentTimeMillis())); task.setToolId(toolName); task.setStage(stage); task.save(); Tool tool = new Tool(task.getToolId(), m_serviceFactory.getToolRegistry(), userId); String jobHandle = getNewJobHandle(task.getToolId()); log.debug("Task has name = " + taskLabel + ", and handle = " + jobHandle); // builds a path that includes jobHandle. String workingDirectory = tool.getWorkingDirectory(jobHandle); RenderedCommand renderedCommand = null; Process taskRunner = null; try { // set jobhandle in task, set task state to SUBMITTED, save task in db task.setStage(TaskRunStage.SUBMIT); task.setJobHandle(jobHandle); task.save(); log.debug("Task name = " + taskLabel + ", handle = " + jobHandle + ", id = " + task.getTaskId() + " saved/updated in db."); // stage input data stage = TaskRunStage.INPUTSTAGING; tool.stageInput(workingDirectory, input); logProgress(task, stage, jobHandle + " Input files staged to " + workingDirectory); stage = TaskRunStage.COMMANDRENDERING; renderedCommand = new RenderedCommand(); renderedCommand.setCommand(command); renderedCommand.setOutputFileMap(outputFiles); logProgress(task, stage, jobHandle + " Command rendered." ); // throws Exception stage = TaskRunStage.PROCESSING; // Start the ProcessWorker (currently, this would be SSHProcessWorker). // BaseProcessWorker takes care of storing results in db and either removing // the working directory or moving it to the FAILED directory. taskRunner = tool.processTask(task, renderedCommand, jobHandle, null); logProgress(task, stage, jobHandle + " Command processed"); } catch (Throwable t) { log.error(jobHandle + ": ", t); tool.cleanWorkingDirectory(jobHandle, true); cleanup(task); throw new WorkbenchException(t); } Map> result = new HashMap>(); try { /* Wait for command to finish. If successful, TaskResult.get() returns the Task object. Throws an ExecutionException otherwise. */ TaskResult tr = new TaskResult(task, taskRunner); task = tr.get(); for (TaskLogMessage msg : task.logMessages()) { log.debug("Task Message for " + jobHandle + ": " + msg); } /* Find the task in the database and retrieve the output data that was stored along with it by the SSHProcessWorker/BaseProcessWorker. Returns map of parameter -> [filename -> SourceDocument] */ Map> output = task.output(); // Convert to a map that has document contents as a byte array instead of SourceDocument // object. for (String parameter : output.keySet()) { Map resultFileList = new HashMap(); List sourceDocList = output.get(parameter); for (TaskOutputSourceDocument file : sourceDocList) { resultFileList.put(file.getName(), file.getData()); } result.put(parameter, resultFileList); } } finally { cleanup(task); } return result; } /** * Generates a random password. * * @return the generated password */ private String generatePassword() { SecureRandom random = new SecureRandom(); char[] generated = new char[MIN_PASSWORD_LENGTH * 2]; byte[] candidates = new byte[64]; int nextByte = candidates.length; for (int i = 0 ; i < generated.length ; i += 1) { char nextChar; while (true) { if (nextByte >= candidates.length) { random.nextBytes(candidates); nextByte = 0; } nextChar = (char) candidates[nextByte++]; // these conditions define a sequence of printable, non-whitespace // characters. Categorization methods from the Character class don't // work here, possibly because we're using 1-byte values that might // have their high-order bit set, which would make such values invalid // as ASCII characters if (nextChar >= '!' && nextChar <= '~') break; } generated[i] = nextChar; } String password = new String(generated); return password.substring(random.nextInt(MIN_PASSWORD_LENGTH)); } /** * Returns the first folder with a label that equals the value of the foldername * class variable. The folder is created if it isn't found. * * @param the User that owns the Folder * @return a Folder with the requested label * @throws IOException * @throws SQLException */ private Folder getRunCommandFolder(User user) throws IOException, SQLException { List allFolders = user.findFolders(); for (Iterator folders = allFolders.iterator() ; folders.hasNext() ; ) { Folder possible = folders.next(); if (possible.getLabel().equals(foldername)) return possible; } Folder folder = new Folder(user); folder.setLabel(foldername); folder.save(); return folder; } /** * Performs cleanup prior to returning from the runCommand method. * * @param task a Task to delete * @throws IOException * @throws SQLException */ private void cleanup(Task task) throws IOException, SQLException { task.delete(); } }