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.ValidationException; 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; /** * 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 RenderedCommand validate(URL url, Map parameters) { return 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. Throws JobValidationException if a ctrl is violated. If precond for a parameter is not met, the parameter is skipped, in the sense that if it has a format element, the code in the format element will not be generated. However, the value of the parameter may be referenced in another parameter's code, so all parameters that have preconds and are referenced elsewhere MUST supply a default value with vdef element. If a precond isn't met this method replaces the value of the parameter in the parameter map with the param's default value. */ private RenderedCommand render(URL url, Map parameters, boolean validateOnly) { try { log.debug("render command from " + parameters.size() + " visible parameters using config:" + url + ", validateOnly = " + validateOnly); /* 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(); //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(ValidationException jve) { throw jve; } catch (Exception err) { throw new RuntimeException(err); } finally { if (perlEval != null) { perlEval.cleanup(); } } } /* If a visible parameter isn't in the map, and the pise has a default value for that param, and the default doesn't violate a precond of a param already in the map, add it. The passed in parameters map is modified. REST API calls this before calling render or validate, gui doesn't so this is a reasonable place to also validate preconds of all params and throw an error if any are violated. We don't want to do that for the gui since when the user doesn't open the gui, all defaults are sent and they violate preconds - so we don't want to break that code, even though it's got it's problems. */ public void addDefaultParameters(URL url, Map parameters) { try { this.parameters = parameters; // Keep track of which parameters were passed in originally. We'll be adding to this.parameters below. // getParameters gives us the keys from this.parameters, but without the trailing "_". Set userParameters = getParameterSet(); piseMarshaller = initPiseMarshaller(url); perlEval = new PerlEval(); perlEval.initialize(); for (String param : piseMarshaller.getVisibleSet()) { // If we don't have one of the visible params in our map ... if (getParameterValue(param) == null) { // If there is a default value for the param ... String vdef = piseMarshaller.getVdef(param); if (vdef != null) { // Get rid of leading and trailing quotes if any. vdef = vdef.trim(); if (vdef.startsWith("\"")) { vdef = vdef.replaceFirst("\"", ""); } if (vdef.endsWith("\"")) { vdef = vdef.substring(0, vdef.length() - 1); } log.debug("TRY Add to map: " + param + "=" + vdef); setParameterValue(param, vdef); if (!processPrecond(param)) { log.debug("Precondition not satisfied. Removing " + param); removeParameter(param); } else { log.debug("Precondition is satisfied. Added " + param + "=" + vdef); } } } } log.debug("Finished adding defaults, now eval preconds of all parameters that user set"); // Check preconds of all the complete resulting set of params. List errors = new ArrayList(); for (String param : getParameterSet()) { if (userParameters.contains(param)) { if (!processPrecond(param)) { log.debug("Precondition of user entered parameter not satisfied."); errors.add(new FieldError(param, "Precondition not satisfied.")); } } } perlEval.terminate(); if (errors.size() > 0) { throw new ValidationException(errors); } } catch(ValidationException jve) { // Catch so that next catch block doesn't map ValidationException to a RuntimeException 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 removeParameter(String parameter) { String key = parameter + "_"; parameters.remove(key); } 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(); } */ public void validateRequiredParameters() { for (String param : piseMarshaller.getRequiredSet()) { if (getParameterValue(param) == null) { parameterErrors.add(new FieldError(param, param + " is required.")); log.debug(param + " is required."); } } } private List toUnixCmd(boolean validateOnly) throws IOException, InterruptedException, ExecutionException, Exception { List commandList = new ArrayList(); /* First evaluate preconds of all the parameters we received from caller. Removes paramName from the map if its precondition isn't satisfied. Doesn't record an error because of the way the portal2 works. If user doesn't open the tool gui, it sends default values for all parameters, including those that violate preconds, so we can't treat this as an error. */ log.debug("First we evaluate preconds of the parameters we received from the caller + added defaults"); for (String paramName : getParameterSet()) { if (! processPrecond(paramName)) { log.debug("Precondition not satisfied. Removing " + paramName); removeParameter(paramName); } } /* Evaluate controls of all params of type InFile, if their precond is met. Sometimes we use controls to say a InFile is required under certain conditions, but if the user doesn't supply a value for the InFile it won't be in the parameterSet so we won't check it's ctrl when we call processParameter on the caller supplied parameterSet. Puts errors in parameterErrors. */ log.debug("Evalute controls and preconds of InFiles"); for (String paramName : piseMarshaller.getInfileSet()) { if (processPrecond(paramName)) { processControls(paramName); } } log.debug("Validating required parameters"); validateRequiredParameters(); /* These are the parameters passed as Map argument for this class, which means parameter sent by our caller, either supplied via the GUI, the rest api, or added to the map by gui or rest code that adds all vdefs to the user entered values. */ log.debug("Evaluate visible parameters"); for (String paramName : getParameterSet()) { processParameter(paramName, false); } /* If we've found errors, bail out. Errors that occur after this are NOT expected, most likely a pisexml error. */ if (parameterErrors.size() > 0) { throw new ValidationException(parameterErrors); } // 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"); log.debug("Evaluate 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, true); // 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 controls = piseMarshaller.getCtrl(paramName); String vdef = piseMarshaller.getVdef(paramName); log.debug("EVALUATE Controls for InFile " + paramName); evaluateControls(controls, paramName, vdef); } /* */ // #### TODO private void processParameter(String paramName, boolean hidden) throws IOException, InterruptedException, ExecutionException, Exception { log.debug("START: processParameter: " + paramName + " (hidden: " + hidden + ")"); String parameterValue = hidden ? null : getParameterValue(paramName); String format = piseMarshaller.getFormat(paramName); String group = piseMarshaller.getGroup(paramName); String precond = piseMarshaller.getPrecond(paramName); String type = piseMarshaller.getType(paramName); String vdef = piseMarshaller.getVdef(paramName); String separator = piseMarshaller.getSeparator(paramName); String filenames = piseMarshaller.getFileNames(paramName); String paramfile = piseMarshaller.getParamFile(paramName); List controls = piseMarshaller.getCtrl(paramName); if (parameterValue == null || (parameterValue.length() < 100)) { log.debug(paramName +"=" + parameterValue + ", type=" + type); } else { log.debug(paramName +"=" + parameterValue.substring(0, Math.min(100, parameterValue.length())) + "...(truncated), type=" + type); } @SuppressWarnings("unused") String isCommand = piseMarshaller.getIsCommand(paramName); @SuppressWarnings("unused") String ishidden = piseMarshaller.getIsHidden(paramName); // With flist, user chooses a label that pise maps to a string of code. String flist = piseMarshaller.getflistValue(paramName, parameterValue); String perlFormat = null; String perlPrecond = null; if (type.equals("Results") && group == null) { group = "-99"; // this group is not going to be used at all } log.debug("Group: " + group); int groupValue=1; if (group !=null) { groupValue = Integer.parseInt(group); } /* here we assume that flist and format are mutually exclusive however the case where we have a format and a flist, the flist will override format I don't see the sense of having both in the same parameter */ if (flist != null) { format = flist; } /* The front end code is responsible for inserting the separator character, if any, and concatenating the list elements. However, if the pise doesn't specify a separator, the front end uses "@" as a separator because it must maintain the distinct elements in order to repopulate a form from them. THIS MEANS YOU CAN'T USE AN '@' in a LIST ELEMENT. */ if (type != null && type.equals("List")) { if (separator == null) { // Remove the dummy '@' that the front end inserted and concatenate the elements since there // is not separator specified in the pise xml. setParameterValue(paramName, SuperString.valueOf(parameterValue, '@').concatenate()); } else { ; } } /* We eval preconds for visible parameters before making calls to processParameter so we only need to eval preconds for hidden parameters now. */ if ((hidden == true) && (precond != null)) { log.debug("Evaluate Precondition for Hidden Parameter"); precond = restitutionPrecond(precond, paramName, vdef); perlPrecond = evaluatePerlStatement(precond); log.debug("Precond evaluation: " + perlPrecond + ", boolean value is " + Boolean.valueOf(perlPrecond)); if (!Boolean.valueOf(perlPrecond)) { //parameterErrors.add(new FieldError(paramName, "Precondition not satisfied.")); log.debug("Precondition not satisfied, not generating code for " + paramName); return; } } if (evaluateControls(controls, paramName, vdef) == false) { // If controls are violated quit processing this parameter log.debug("Return early because evaluateControls returned false"); return; } log.debug("format: " + (format == null ? "null" : format)); if (format != null) { format = restitutionFormat(format, paramName, vdef); perlFormat = evaluatePerlStatement(format); log.debug("Format evaluation: " + perlFormat); } if (perlFormat != null) { log.debug("paramfile: " + (paramfile == null ? "null" : paramfile)); if (paramfile != null) { String[] value; if (paramFiles.containsKey(paramfile) == false || paramFiles.get(paramfile) == null) { paramFiles.put(paramfile, new String[100]); } value = paramFiles.get(paramfile); if (value[groupValue] != null) { value[groupValue] += perlFormat; } else { value[groupValue] = perlFormat; } log.debug("parameter: " + paramName + " put in inputDataMap: '" + paramfile + "' -> '" + value[groupValue] + "'"); } else { if (groupValue >= 0) { if (unixCmdGroup[groupValue] != null) { unixCmdGroup[groupValue] += " " + perlFormat; } else { unixCmdGroup[groupValue] = perlFormat; } log.debug("unixCmdGroup[" + groupValue + "] = " + unixCmdGroup[groupValue]); } else { if (unixCmdGroupNegative[Math.abs(groupValue)] != null) { unixCmdGroupNegative[Math.abs(groupValue)] += " " + perlFormat; } else { unixCmdGroupNegative[Math.abs(groupValue)] = perlFormat; } log.debug("unixCmdGroupNegative[" + Math.abs(groupValue) + "] = " + unixCmdGroupNegative[Math.abs(groupValue)]); } } } if (filenames != null) { // filenames not null for infile and sequence means we have assigned standard names for these input files if (type.equals("InFile") || type.equals("Sequence")) { setInputFileName(paramName, filenames); log.debug("setInputFileName: '" + paramName + "' -> '" + filenames + "'"); } // Most output filenames are given in Results but can also be defined in Switch, List, OutFile, etc. else { setOutputFileName(paramName, filenames); log.debug("setOutputFileName: '" + paramName + "' -> '" + filenames + "'"); } } log.debug("END : processParameter: " + paramName + " (hidden: " + hidden + ")\n"); } /* TL: I'm adding this method to work around the problem where we want to know whether the user uploaded a file, or pasted in some text, by checking to see if $param_name is in the map. The problem with doing something like "($param_name)?...:..." is that the restitution replaces $param_name with the contents of the file or pasted in text, and if that contains a double quote or other character that has meaning to perl, the resulting perl expression may be garbage. Instead we can say "(defined $param_name) ? ...:..." and this method will replace the conditional with 1 or 0. - replaces "defined $value" with 1 (since in swami, $value is always defined) - replaces "defined $vdef" with 1 if there is a vdef attribute in the pise xml element, 0 otherwise - replaces "defined $param_name" with 1 if param_name is in the map of params we received from the gui, 0 otherwise. */ private String replaceDefined(String str, String parameterValue, String vdef) { StringBuffer buf = new StringBuffer(); // Find the text "defined", followed by whitespace, followed by "$", followed by one or more // "word" characters (i.e letter, number or underscore). Capture the "word" into group 1. Pattern p = Pattern.compile("defined\\s\\$(\\w+)"); Matcher matcher = p.matcher(str); while(matcher.find()) { /* replacement text depends on the word that follows the $, that is, group(1). but we'll be replacing the full string that was matched, in other words "defined $something" will be replace with "0" or "1" depending on what $something is. */ String var = matcher.group(1); if (var.equals("value")) { matcher.appendReplacement(buf, (parameterValue == null) ? "0" : "1"); } else if (var.equals("vdef")) { matcher.appendReplacement(buf, (vdef == null) ? "0" : "1"); } else { boolean defined = getParameterSet().contains(var); matcher.appendReplacement(buf, defined ? "1": "0"); } } matcher.appendTail(buf); return buf.toString(); } /* Returns a perl statement which will be passed to "perl -e". The stdout from running perl -e on the statement will produce the text "true" or "false". We pass the stdout to java's Boolean.valueOf() to get a boolean evaluation of the precond. */ private String restitutionPrecond(String precond, String paramName, String vdef) { /* find every substring in precond starting with $ if the substring is $value replace it with the value of the parameter identified in paramMap with key if the substring is $x replace it with the value of the parameter identified in paramMap with x */ log.debug("Original Expression: " + precond); String paramValue = getParameterValue(paramName); precond = replaceDefined(precond, paramValue, vdef); log.debug("Expression after replaceDefined: " + precond); Pattern p = Pattern.compile("\\$\\w*"); //Matcher m = p.matcher("Precond: " + precond); Matcher m = p.matcher("" + precond); StringBuffer sb = new StringBuffer(); while (m.find()) { if (m.group().contains("$value")) { m.appendReplacement(sb, "\"" + paramValue + "\""); //precond = precond.replace("$value", "\"" + paramValue + "\""); } else if (m.group().contains("$vdef")) { m.appendReplacement(sb, "\"" + vdef + "\""); //precond = precond.replace("$vdef", "\"" + vdef + "\""); } else // we should ignore restitution if the pattern match $ preceding bug restituted $ by "null" if (m.group().equalsIgnoreCase("$") == false) { String myKey = m.group().substring(1); /* In the special case where the code references $param_name and param_name isn't in our map, we return "print ''", which when run, outputs nothing, which evaluates to false. */ // Terri: I'm just trying out a change here. Up until now, if a parameter wasn't // in the map we received and it was referenced in a precond, we didn't use the vdef // value, we printed "" and bailed out, thus rendering the precondition or control false. I'm // trying out a change where we instead using the vdef value. I'm hoping this makes // it possible to avoid having to pass all the default values in the parameter map - that // seems silly, but who knows what this change may break. // -- Sometimes vdef is dead wrong value to be using. For example If switch param // isn't in map because it's precond wasn't met and vdef is 1, it's totally wrong to use 1. //### String theValue; if (getParameterSet().contains(myKey) == false) { // Any parameter that has a precondition, will be undefined if it's precond isn't met -> so // you must always write expressions to check if the param is defined before using it. //return "print \"\""; log.debug("INVALID PRECONDITION - uses value of undefined parameter " + myKey + ". Use 'defined'" ); String type = piseMarshaller.getType(myKey); if (type != null && type.equals("Switch")) { log.debug("Replacing undefined switch parameter with 0"); theValue="0"; } else if (type != null && type.equals("String")) { log.debug("Replacing undefined string parameter with ''"); theValue=""; } else { theValue = piseMarshaller.getVdef(myKey); if (theValue == null) // no vDef element in the pise xml { // If a precond or ctrl references a variable that isn't in the map and has no vdef // then we replace the whole expression with "" which evaluates to false. For a // precond that means it is violated, for a ctrl it means the error isn't triggered. return "print \"\""; } // sometimes users specify vdef empty string as "" instead of just leaving the vdef element empty. // Todo: fix this in the piseMarshaller? if (theValue.equals("\"\"")) { theValue = ""; } log.debug("Replacing undefined switch parameter with it's vdef = " + theValue); } } else { theValue = getParameterValue(myKey); } //precond = precond.replace(m.group(), "\"" + theValue + "\""); m.appendReplacement(sb, "\"" + theValue + "\""); } } m.appendTail(sb); precond = sb.toString(); //In perl ("false")? print"true" : print "false" returns true so we are replacing all true by 1 and all false by 0 precond = precond.replaceAll("false", "0"); precond = precond.replaceAll("true", "1"); // precond returns a True or False depends on the condition being verified precond = "(" + precond + ")? print \"true\" : print \"false\";"; log.debug("Parsed Precondition: " + precond); return precond; } private String restitutionFormat(String format, String paramName, String vdef) { log.debug("Original Format: " + format); String paramValue = getParameterValue(paramName); format = replaceDefined(format, paramValue, vdef); log.debug("Format after replaceDefined: " + format); Pattern p = Pattern.compile("\\$\\w*"); Matcher m = p.matcher(format); while (m.find()) { if (m.group().contains("$value")) { format = format.replace("$value", paramValue); } else if (m.group().contains("$vdef")) { format = format.replace("$vdef", vdef); } else { String myKey = m.group().substring(1); String myValue = null; if (getParameterSet().contains(myKey) == false) { /* TL: modified for raxmlhpc.xml "model" parameter. Need to get default value of parameters that haven't been set in the gui but are referenced in this parameter's code element. */ log.debug("INVALID FORMAT statement references undefined parameter " + myKey + ", will use it's vdef, or empty string."); myValue = piseMarshaller.getVdef(myKey); if (myValue == null) // no vDef element in the pise xml { myValue = ""; } // Terri - just added this when trying out the fix to precod restitution. Not sure if // it breaks anything. // sometimes users specify vdef empty string as "" instead of just leaving the vdef element empty. // Todo: fix this in the piseMarshaller? if (myValue.equals("\"\"")) { myValue = ""; } } else { myValue = getParameterValue(myKey); } format = format.replace(m.group(), myValue); } } /* A format can be a simple "..." or a condition (...)? "...":"..." If it's ? expression we stick a "print" in front of both components. But if it starts with "<<" we're going to assume the whole thing is a here document and not process the ? operator. This isn't exactly what a real perl interpreter would do but I think it's close enough for the pise and ngbw xml files we deal with these days. */ if (!format.trim().startsWith("<<") && format.contains("?")) { format = format.substring(0, format.indexOf("?") + 1) + " print " + format.substring(format.indexOf("?") + 1); format = format.substring(0, format.indexOf(":") + 1) + " print " + format.substring(format.indexOf(":") + 1); } else { format = "print " + format; } format = format.replaceAll("false", "0"); format = format.replaceAll("true", "1"); log.debug("Parsed Format: " + format); return format; } /* Controls are written to express an error condition when true. For example to require runtime to be <= 168, you write "$runtime > 168.0" Returns true (ie. all's well) if there are no controls or all controls evaluate to false. For each control that is true, sets an error message in ??? TODO */ private boolean evaluateControls(List controls, String paramName, String vdef) throws Exception { int errorCount = 0; String perl; String evaluatedPerl; if (controls == null) { return true; } for (PiseMarshaller.Control c : controls) { perl = restitutionPrecond(c.perl, paramName, vdef); evaluatedPerl= evaluatePerlStatement(perl); log.debug("ctrl: '" + perl + "' EVAL TO '" + evaluatedPerl + "'"); if (Boolean.valueOf(evaluatedPerl) == true) { parameterErrors.add(new FieldError(paramName, c.message)); errorCount += 1; } } return errorCount == 0; } } /* NEWLINE replacement: (search for "NEWLINE" to see what this comment is referring to). This code replaces the two character sequence of with a single newline character. Since evalutePerl() returns the stdtout trimmed of leading and trailing whitespace, if we want to have a parameter be followed by a newline, we need the result of the perl evaluation to end not with a newline character but with a backslash followed by an n, which we'll replace with a newline right here. note that when you print a java string, as the logging messages in this file do, if the string contains an actual newline, it will print on multiple lines. If you see "\n" in the value displayed, it means the string contains two characters: a backslash followed by an n. This is mostly what we see because if you put "\n" in an xml file element, the xml unmarshaller delivers this as a java string containing a backslash followed by an n. Both perl (if using double quoted strings) and java interpret "\\n" as the two character sequence . The first quote escapes the second, yielding a literal backslash and the n is unquoted. In a perl single quoted string, '\\n' is three characters: two backslashes followed by an n. If you have a format like '$value\\n', with single quotes, perl leaves the \\n alone and when we get here we replace the \n with a newline, so we're left with a single backslash followed by a newline ... probably not what you want. On the other hand, "$value\\n" works because when ask perl to print this it gives us the value followed by a backslash follwed by an n. */