/* * ProActive Parallel Suite(TM): * The Open Source library for parallel and distributed * Workflows & Scheduling, Orchestration, Cloud Automation * and Big Data Analysis on Enterprise Grids & Clouds. * * Copyright (c) 2007 - 2017 ActiveEon * Contact: contact@activeeon.com * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation: version 3 of * the License. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * If needed, contact us to obtain a release under GPL Version 2 or 3 * or a different license than the AGPL. */ package org.ow2.proactive.scheduler.common.task.flow; import java.io.Reader; import java.io.StringReader; import javax.script.Bindings; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import org.objectweb.proactive.annotation.PublicAPI; import org.ow2.proactive.scheduler.common.task.Task; import org.ow2.proactive.scripting.InvalidScriptException; import org.ow2.proactive.scripting.Script; import org.ow2.proactive.scripting.ScriptResult; import org.ow2.proactive.scripting.SimpleScript; import it.sauronsoftware.cron4j.InvalidPatternException; import it.sauronsoftware.cron4j.Predictor; /** * Dynamic evaluation of this script determines at runtime if a specific * Control Flow operation should be performed in a TaskFlow. * <p> * This class wraps information around a {@link org.ow2.proactive.scripting.Script} * to determine which {@link FlowAction} is attached to this script. * <p> * When using the action type {@link FlowActionType#REPLICATE}, the value of the * {@link FlowScript#replicateRunsVariable} determines the number of parallel runs. * * @author The ProActive Team * @since ProActive Scheduling 2.2 * @see FlowAction */ @PublicAPI @XmlAccessorType(XmlAccessType.FIELD) public class FlowScript extends Script<FlowAction> { /** String representation of a FlowActionType * see {@link FlowActionType#parse(String)} */ private String actionType = null; // implementation note: // target / targetElse / targetContinuation // would be much better represented with an InternalTask or a TaskId, but : // - InternalTask cannot be used because of project setup : // it is not exported on the worker on which this script executes; // it cannot be used on user side, and this class is PublicAPI // - TaskId cannot be used by the user prior to submission // A complete solution would involve exposing one class to the user, // copying the info onto another more complete InternalFlowScript, // and holding a TaskId, which would present some of the problems // of the current string-based implementation // // In the end, using strings is less safe, but faster and simpler ; // also, this is only internals never exposed to the user /** Name of the target task of this action if it requires one */ private String target = null; /** Name of the 'Else' target task if this action is an 'If' */ private String targetElse = null; /** Name of the 'Continuation' target task if this action is an 'If' */ private String targetContinuation = null; /** Name of the variable that will be set in the script's environment * to contain the result of the Task executed along this script */ public static final String resultVariable = "result"; /** Name of the boolean variable to set in the script to determine * if a LOOP action is enabled or if the execution should continue */ public static final String loopVariable = "loop"; /** Name of the Integer variable to set in the script to determine * the number of parallel runs of a REPLICATE action */ public static final String replicateRunsVariable = "runs"; /** Name of the variable to set in the script to determine * which one of the IF or ELSE branch is selected in an IF * control flow action */ public static final String branchSelectionVariable = "branch"; /** Value to set {@link #branchSelectionVariable} to * signify the IF branch should be selected */ public static final String ifBranchSelectedVariable = "if"; /** Value to set {@link #branchSelectionVariable} to * signify the ELSE branch should be selected */ public static final String elseBranchSelectedVariable = "else"; /** * Hibernate default constructor, * use {@link #createContinueFlowScript()}, * {@link #createLoopFlowScript(Script, String)} or * {@link #createReplicateFlowScript(Script)} to * create a FlowScript */ public FlowScript() { } @Override protected String getDefaultScriptName() { return "FlowScript"; } /** * Copy constructor * * @param fl Source script * @throws InvalidScriptException */ public FlowScript(FlowScript fl) throws InvalidScriptException { super(fl); if (fl.getActionType() != null) { this.actionType = new String(fl.getActionType()); } if (fl.getActionTarget() != null) { this.target = new String(fl.getActionTarget()); } if (fl.getActionTargetElse() != null) { this.targetElse = new String(fl.getActionTargetElse()); } if (fl.getActionContinuation() != null) { this.targetContinuation = new String(fl.getActionContinuation()); } } private FlowScript(Script<?> scr) throws InvalidScriptException { super(scr); } public static FlowScript createContinueFlowScript() throws InvalidScriptException { FlowScript fs = new FlowScript(new SimpleScript("", "javascript")); fs.setActionType(FlowActionType.CONTINUE); return fs; } /** * Creates a Control Flow Script configured to perform a LOOP control flow action * the code will be run using a javascript engine * * @param script code of the Javascript script * @param target target of the LOOP action * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createLoopFlowScript(String script, String target) throws InvalidScriptException { return createLoopFlowScript(script, "javascript", target); } /** * Creates a Control Flow Script configured to perform a LOOP control flow action * * @param script code of the script * @param engine engine running the script * @param target target of the LOOP action * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createLoopFlowScript(String script, String engine, String target) throws InvalidScriptException { Script<?> scr = new SimpleScript(script, engine); return createLoopFlowScript(scr, target); } /** * Creates a Control Flow Script configured to perform a LOOP control flow action * * @param script the script to execute * @param target target of the LOOP action * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createLoopFlowScript(Script<?> script, String target) throws InvalidScriptException { FlowScript flow = new FlowScript(script); flow.setActionType(FlowActionType.LOOP); flow.setActionTarget(target); return flow; } /** * Creates a Control Flow Script configured to perform an IF control flow action * the code will be run using a javascript engine * * @param script code of the Javascript script * @param targetIf IF branch * @param targetElse ELSE branch * @param targetCont CONTINUATION branch, can be null * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createIfFlowScript(String script, String targetIf, String targetElse, String targetCont) throws InvalidScriptException { return createIfFlowScript(script, "javascript", targetIf, targetElse, targetCont); } /** * Creates a Control Flow Script configured to perform an IF control flow action * * @param script code of the script * @param engine engine running the script * @param targetIf IF branch * @param targetElse ELSE branch * @param targetCont CONTINUATION branch, can be null * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createIfFlowScript(String script, String engine, String targetIf, String targetElse, String targetCont) throws InvalidScriptException { Script<?> scr = new SimpleScript(script, engine); return createIfFlowScript(scr, targetIf, targetElse, targetCont); } /** * Creates a Control Flow Script configured to perform an IF control flow action * * @param script the script to execute * @param targetIf IF branch * @param targetElse ELSE branch * @param targetCont CONTINUATION branch, can be null * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createIfFlowScript(Script<?> script, String targetIf, String targetElse, String targetCont) throws InvalidScriptException { FlowScript flow = new FlowScript(script); flow.setActionType(FlowActionType.IF); flow.setActionTarget(targetIf); flow.setActionTargetElse(targetElse); flow.setActionContinuation(targetCont); return flow; } /** * Creates a Control Flow Script configured to perform a REPLICATE control flow action * the code will be run using a javascript engine * * @param script code of the Javascript script * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createReplicateFlowScript(String script) throws InvalidScriptException { return createReplicateFlowScript(script, "javascript"); } /** * Creates a Control Flow Script configured to perform a Replicate control flow action * * @param script code of the script * @param engine engine running the script * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createReplicateFlowScript(String script, String engine) throws InvalidScriptException { Script<?> scr = new SimpleScript(script, engine); return createReplicateFlowScript(scr); } /** * Creates a Control Flow Script configured to perform a REPLICATE control flow action * * @param script the script to execute * @return a newly allocated and configured Control Flow Script * @throws InvalidScriptException */ public static FlowScript createReplicateFlowScript(Script<?> script) throws InvalidScriptException { FlowScript flow = new FlowScript(script); flow.setActionType(FlowActionType.REPLICATE); return flow; } /** * The Action Type does not have any effect on the execution of the script, * but will be used after the execution to determine what Control Flow Action * should be performed on the TaskFlow. * * @param actionType the String representation of the new ActionType of this script, * @see FlowActionType#parse(String) */ public void setActionType(String actionType) { this.actionType = actionType; } /** * The Action Type does not have any effect on the execution of the script, * but will be used after the execution to determine what Control Flow Action * should be performed on the TaskFlow. * * @param type the ActionType of this script, */ public void setActionType(FlowActionType type) { this.actionType = type.toString(); } /** * The Action Type does not have any effect on the execution of the script, * but will be used after the execution to determine what Control Flow Action * should be performed on the TaskFlow. * * @return the String representation of the ActionType of this script, * @see FlowActionType#parse(String) */ public String getActionType() { return this.actionType; } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#LOOP}, the target is the entry point of the next loop iteration. * If the Action type is {@link FlowActionType#IF}, the target is the branch executed when * the If condition succeeds. * <p> * This value has no effect on the execution of the script * * @param target the main target of the action of this script. */ public void setActionTarget(String target) { this.target = target; } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#LOOP}, the target is the entry point of the next loop iteration. * If the Action type is {@link FlowActionType#IF}, the target is the branch executed when * the If condition succeeds. * <p> * This value has no effect on the execution of the script * * @param target the main target of the action of this script. */ public void setActionTarget(Task target) { this.target = target.getName(); } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#LOOP}, the target is the entry point of the next loop iteration. * If the Action type is {@link FlowActionType#IF}, the target is the branch executed when * the If condition succeeds. * <p> * This value has no effect on the execution of the script * * @return the main target of the action of this script */ public String getActionTarget() { return this.target; } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#IF}, the targetElse is the branch executed when * the If condition fails. * <p> * This value has no effect on the execution of the script * * @param target the Else target of the action of this script */ public void setActionTargetElse(String target) { this.targetElse = target; } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#IF}, the targetElse is the branch executed when * the If condition fails. * <p> * This value has no effect on the execution of the script * * @param target the Else target of the action of this script */ public void setActionTargetElse(Task target) { this.targetElse = target.getName(); } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#IF}, the targetElse is the branch executed when * the If condition fails. * <p> * This value has no effect on the execution of the script * * @return the Else target of the action of this script */ public String getActionTargetElse() { return this.targetElse; } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#IF}, the targetContinuation is the Task on which both * if and else branches will join after either one has been executed. * <p> * This value has no effect on the execution of the script * * @param target the Continuation target of the action of this script */ public void setActionContinuation(String target) { this.targetContinuation = target; } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#IF}, the targetContinuation is the Task on which both * if and else branches will join after either one has been executed. * <p> * This value has no effect on the execution of the script * * @param target the Continuation target of the action of this script */ public void setActionContinuation(Task target) { this.targetContinuation = target.getName(); } /** * If the Action type (see {@link #getActionType()}) of this FlowScript * is {@link FlowActionType#IF}, the targetContinuation is the Task on which both * if and else branches will join after either one has been executed. * <p> * This value has no effect on the execution of the script * * @return the Continuation target of the action of this script */ public String getActionContinuation() { return this.targetContinuation; } @Override public String getId() { return this.id; } @Override protected Reader getReader() { return new StringReader(script); } @Override protected ScriptResult<FlowAction> getResult(Object evalResult, Bindings bindings) { try { FlowAction act = new FlowAction(); /* * no action defined */ if (this.actionType == null || this.actionType.equals(FlowActionType.CONTINUE.toString())) { act.setType(FlowActionType.CONTINUE); } /* * loop */ else if (this.actionType.equals(FlowActionType.LOOP.toString())) { if (this.target == null) { String msg = "LOOP control flow action requires a target"; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } else { if (bindings.containsKey(loopVariable)) { Boolean enabled; String loopValue = bindings.get(loopVariable).toString(); if ("true".equalsIgnoreCase(loopValue)) { enabled = Boolean.TRUE; } else if ("false".equalsIgnoreCase(loopValue)) { enabled = Boolean.FALSE; } else { try { (new Predictor(loopValue)).nextMatchingDate(); enabled = Boolean.TRUE; act.setCronExpr(loopValue); } catch (InvalidPatternException e) { enabled = Boolean.FALSE; } } if (enabled) { act.setType(FlowActionType.LOOP); act.setTarget(this.target); } else { act.setType(FlowActionType.CONTINUE); } } else { String msg = "Script environment for LOOP action needs to define variable " + loopVariable; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } } } /* * replicate */ else if (this.actionType.equals(FlowActionType.REPLICATE.toString())) { if (bindings.containsKey(replicateRunsVariable)) { act.setType(FlowActionType.REPLICATE); int args = 1; Object o = bindings.get(replicateRunsVariable); try { args = Integer.parseInt("" + o); } catch (NumberFormatException e) { try { args = (int) Math.floor(Double.parseDouble("" + o)); } catch (Exception e2) { String msg = "REPLICATE action: could not parse value for variable " + replicateRunsVariable; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg, e2)); } } if (args < 0) { String msg = "REPLICATE action: value of variable " + replicateRunsVariable + " cannot be negative"; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } act.setDupNumber(args); } else { String msg = "Script environment for REPLICATE action needs to define variable " + replicateRunsVariable; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } } /* * if */ else if (this.actionType.equals(FlowActionType.IF.toString())) { if (this.target == null) { String msg = "IF action requires a target "; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } else if (this.targetElse == null) { String msg = "IF action requires an ELSE target "; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } else { act.setType(FlowActionType.IF); if (bindings.containsKey(branchSelectionVariable)) { String val = new String((String) bindings.get(branchSelectionVariable)); if (val.toLowerCase().equals(ifBranchSelectedVariable)) { act.setTarget(this.target); act.setTargetElse(this.targetElse); } else if (val.toLowerCase().equals(elseBranchSelectedVariable)) { act.setTarget(this.targetElse); act.setTargetElse(this.target); } else { String msg = "IF action: value for " + branchSelectionVariable + " needs to be one of " + ifBranchSelectedVariable + " or " + elseBranchSelectedVariable; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } } else { String msg = "Environment for IF action needs to define variable " + branchSelectionVariable; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } if (this.targetContinuation != null) { act.setTargetContinuation(this.targetContinuation); } } } /* * unknown action */ else { String msg = actionType + " action type unknown"; logger.error(msg); return new ScriptResult<FlowAction>(new Exception(msg)); } return new ScriptResult<FlowAction>(act); } catch (Throwable th) { return new ScriptResult<FlowAction>(th); } } @Override protected void prepareSpecialBindings(Bindings bindings) { } }