package org.ngbw.pise.commandrenderer; import java.io.IOException; import java.io.StringBufferInputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ngbw.sdk.api.tool.CommandRenderer; import org.ngbw.sdk.common.util.StringUtils; import org.ngbw.sdk.common.util.SuperString; import org.ngbw.sdk.tool.RenderedCommand; import org.ngbw.pise.commandrenderer.PiseMarshaller; import org.ngbw.sdk.api.tool.FieldError; import org.ngbw.sdk.api.tool.JobValidationException; /** * This Class implements CommandRenderer this implementation takes care of * PiseXML and Perl contents *
* @author Rami *
* @author R. Hannes Niedner * */ public class PiseCommandRenderer implements CommandRenderer { private static Log log = LogFactory.getLog(PiseCommandRenderer.class .getName()); private final Map cfgMap = new HashMap(); private PiseMarshaller piseMarshaller; //parameter name -> parameter value private Map parameters; // private variable for the toUnixCmd() method private String[] unixCmdGroup = null; private String[] unixCmdGroupNegative = null; private Map paramFiles = null; private RenderedCommand renderedCommand= null; private final static String SCHEDULER_CONF = "scheduler.conf"; private PerlEval perlEval = null; List parameterErrors = new ArrayList(); public PiseCommandRenderer() { super(); init(); } private void init() { parameters = null; piseMarshaller = null; unixCmdGroup = new String[100]; unixCmdGroupNegative = new String[100]; paramFiles = new HashMap(); renderedCommand = new RenderedCommand(); } // TODO: public void validate(URL url, Map parameters) { render(url, parameters, true); } public RenderedCommand render(URL url, Map parameters) { return render(url, parameters, false); } /** @param url Url of the pise xml file @param parameters A map of parameter name to value, where parameter name is the name element of a parameter element in the pise xml file and where value may be something like "1" or "y", or the contents of a source document, depending on the type of parameter. Caller must verify that entries in parameters map meet certain criteria BEFORE calling this method: - keys must correspond to parameters in the pise xml file - values of the correct type as specified by the pise file (for example, integer, string, double) - values are in min/max range specified by the pise xml This method does evaluates perl precond and ctrls elements for the parameters and throws JobValidationException if they are violated. If precond for a parameter is not met, the parameter must not be in the parameters map. */ private RenderedCommand render(URL url, Map parameters, boolean validateOnly) { try { log.debug("render command from " + parameters.size() + " parameters using config:" + url); log.debug("validateOnly is " + validateOnly); init(); /* Keep a pointer to the passed in parameters map. This method will modify the map - upon return, caller will see a modified map. For example, this method adds dummy parameters that correspond to the parameter files that are generated by the pisexml. */ this.parameters = parameters; piseMarshaller = initPiseMarshaller(url); perlEval = new PerlEval(); perlEval.initialize(); List commandList = toUnixCmd(validateOnly); perlEval.terminate(); if (validateOnly) { return null; } //log.debug("toUnixCmd returns a list with " + commandList.size() + " elements"); String[] commandArray = new String[commandList.size()]; renderedCommand.setCommand(commandList.toArray(commandArray)); if (log.isDebugEnabled()) { log.debug("Returned command: " + StringUtils.join(renderedCommand.getCommand(), " ")); Map input = renderedCommand.getInputFileMap(); Map output = renderedCommand.getOutputFileMap(); for(String parameter : input.keySet()) log.debug("Input: " + parameter + ": " + input.get(parameter)); for(String parameter : output.keySet()) log.debug("Output: " + parameter + ": " + output.get(parameter)); } setSchedulerProperties(); return renderedCommand; } catch(JobValidationException jve) { throw jve; } catch (Exception err) { throw new RuntimeException(err); } finally { if (perlEval != null) { perlEval.cleanup(); } } } /** If the xml specified a param file with a specific name (given by SCHEDULER_CONF) we expect that file to contain properties for scheduling the job. We load the contents of the file into the renderedCommand.schedulerProperties. */ private void setSchedulerProperties() { Map inputData = new TreeMap(); Map inputFileNames = renderedCommand.getInputFileMap(); String p = null; for (Iterator> names = inputFileNames.entrySet().iterator() ; names.hasNext() ; ) { Map.Entry entry = names.next(); String parameter = entry.getKey(); String fileName = entry.getValue(); if (fileName.equals(SCHEDULER_CONF)) { log.debug("Found " + SCHEDULER_CONF + " in renderedCommand.inputFileMap, for parameter: " + parameter); p = parameter; break; } } String value; if (p == null || (value = parameters.get(p)) == null) { log.debug("Parameter " + p + " value is null."); return; } log.debug("Parameter " + p + " value is:" + new String(value)); try { renderedCommand.getSchedulerProperties().load(new StringBufferInputStream(value)); } catch (Throwable t) { log.warn("Error loading scheduler properties", t); } } //initialize the JAXB Marshaller private PiseMarshaller initPiseMarshaller(URL url) { if (url == null) throw new NullPointerException("Tool config file URL is null!"); if (cfgMap.containsKey(url) == false) try { PiseMarshaller pm = new PiseMarshaller(url.openStream()); cfgMap.put(url, pm); } catch (IOException e) { throw new RuntimeException("Cannot initialize PiseMarshaller.", e); } return cfgMap.get(url); } //all parameter names in the submitted maps have an underscore appended //thus we declare our own private getters and setters for the value private Set getParameterSet() { Set keySet = new HashSet(); for (String key : parameters.keySet()) { //the regex secefies the only a trailing _ //and the replaceFirst will only take this one String paramName = key.replaceFirst("_$", ""); keySet.add(paramName); } return keySet; } private String getParameterKey(String parameter) { return parameter + "_"; } private String getParameterValue(String parameter) { String key = getParameterKey(parameter); if (parameters.containsKey(key) || parameters.get(key) != null) return new String(parameters.get(key)); else return null; // that is expected since parameters with // null values are notin the parameter map } private void setParameterValue(String parameter, String value) { String key = parameter + "_"; parameters.put(key, value); } private void setInputFileName(String parameter, String fileName) { String key = parameter + "_"; renderedCommand.getInputFileMap().put(key, fileName); } private void setOutputFileName(String parameter, String fileName) { renderedCommand.getOutputFileMap().put(parameter, fileName); } private String evaluatePerlStatement(String perlStatement) throws Exception { return perlEval.evaluateStatement(perlStatement); } /* private String evaluatePerlStatement(String perlStatement) throws IOException, InterruptedException, ExecutionException { log.debug("PerlStatement: " + perlStatement); // after some test, we had to create a table containing the different element of // the statement, otherwise it seems not working with one String String[] command = new String[3]; command[0] = "perl"; command[1] = "-e"; command[2] = perlStatement; Process worker = Runtime.getRuntime().exec(command); final Future stdOut = InputStreamCollector.readInputStream(worker.getInputStream()); final Future stdErr = InputStreamCollector.readInputStream(worker.getErrorStream()); final int exitCode = worker.waitFor(); if (exitCode != 0) throw new RuntimeException("Exit value was not 0 but " + exitCode + " STDERR: \n" + stdErr.get()); return stdOut.get().trim(); } */ /* */ private List toUnixCmd(boolean validateOnly) throws IOException, InterruptedException, ExecutionException, Exception { List commandList = new ArrayList(); /* A command is constructed from multiple type of parameters: These are the parameters passed as Map argument for this class, which means parameter selected through GUI */ log.debug("toUnixCmd: processing " + getParameterSet().size() + " GUI parameters"); FieldError error; for (String paramName : getParameterSet()) { processParameter(paramName, false); } if (parameterErrors.size() > 0) { throw new JobValidationException(parameterErrors); } if (validateOnly) { return null; } // These are the parameters hidden from the GUI, but necessary for generating the command line correctly log.debug("toUnixCmd: processing " + piseMarshaller.getHiddenSet().size() + " HIDDEN parameters"); for (String paramName : piseMarshaller.getHiddenSet()) processParameter(paramName, true); /* These are the parameters that generate an outfile, they are hidden from the GUI since the GUI dosen't give the user the possibility to specify the names of the output files. */ log.debug("toUnixCmd: processing " + piseMarshaller.getOutfileSet().size() + " OUTFILESET parameters"); for (String paramName : piseMarshaller.getOutfileSet()) processParameter(paramName, false); // Not sure of the difference bewteen Pise ResultFile and piseOutfile, but here we handle ResultFiles. log.debug("toUnixCmd: processing " + piseMarshaller.getResultSet().size() + " RESULTSET parameters"); for (String paramName : piseMarshaller.getResultSet()) processParameter(paramName, true); /* All kinds of parameters, visible, hidden, etc can specify text to be placed in a parameter file instead of on the commandline. The calls to processParameter above put entries in paramFiles each time they find a parameter that puts text in a parameter file. The map is keyed by parameter file name. Here we append all the lines that go into each parameter file into a single string and we add the param file name/ param file contents to our list of input files. */ for(String key : paramFiles.keySet()) { // Append the lines that go in the parameter file. String[] values = paramFiles.get(key); StringBuilder valueSb = new StringBuilder(); for(String value : values) { if (value != null) { valueSb.append(value); } } String value = valueSb.toString(); /* This adds an entry in the parameter map, with key = parameter file name, value = contents. See NEWLINE comment at end of file. */ setParameterValue(key, value.replace("\\n", "\n")); /* This adds an entry in the input file map with key = parameter file name, value = parameter file name. */ setInputFileName(key, key); } // 3- Creating the command line by ordering them with respect to their group if (unixCmdGroup[0] != null) { commandList.add(unixCmdGroup[0]); log.debug("for unixCmdGroup[0] commandList.add: " + unixCmdGroup[0]); } for (int j = 1; j < unixCmdGroup.length; j++) { // Arranging positve groups if (unixCmdGroup[j] != null) { commandList.add(unixCmdGroup[j]); log.debug("for unixCmdGroup[" + j + "] commandList.add: " + unixCmdGroup[j]); } } // Arranging the negative groups for (int k=1 ; k