Version 4

    BshCommand is a dynamic Command that you can send to a jBPM CommandService. The most interesting usage is perhaps looking up the RemoteCommandService EJB and sending it dynamic commands, written in BeanShell.

     

    Usage

     

    Simplest usage :

     

    CommandService commandService = ...;
    
    commandService.execute(new BshCommand("return jbpmContext.getProcessInstance(5);"));
    

     

    You can notice that any script is given a variable called jbpmContext which contains the current JbpmContext. Like in any Command, you don't have to look it up. You can query jbpmContext in your script all you want, with no fear of hibernate LazyInitializationExceptions.

     

    You may want to pass the script a parameter so that you may be able to reuse the script code for different parameters :

     

    String script = "return jbpmContext.getProcessInstance(pid)";
    commandService.execute(new BshCommand(script, "pid", 5));
    

     

    If you want to pass several parameters, you have constructors that take a Map of parameters or two arrays of the same length of parameter names and parameter values :

     

    commandService.execute(new BshCommand(
         "jbpmContext.getProcessInstance(pid).getRootToken().signal(transition);", 
         new String[] {"pid", "transition"},
         new Object[] {new Long(5), "go-there"}
    );
    

     

    You may want to have your BSH script as a classpath resource (do not call it script.bsh though if you deploy under jboss because jboss will think it must deploy it as an ejb. Call it moveTokenToNodeAndSignal.bsh.command for example):

     

    // forcibly move a process instance's token to "System error" node and signal it to "restart".
    
    pi = jbpmContext.getProcessInstance(pid);
    tk = pi.getRootToken();
    
    pdef = pi.getProcessDefinition();
    node = pdef.getNode("System error");
    
    tk.addLog(new org.jbpm.logging.log.MessageLog("forcing restart of process that has no 'restart' leaving transition")); 
    tk.setNode(node);
    tk.signal("restart");     
    

     

    Send the command like this :

     

    String script = IOUtils.toString(this.getClass().getClassLoader().getResourceAsStream("moveTokenToNodeAndSignal.bsh.command"));
    commandService.execute(script, "pid", 4);
    

     

     

    The BshCommand code

     

    package org.jbpm.command;
    
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    
    import org.jbpm.JbpmContext;
    
    import bsh.Interpreter;
    
    /**
     * this Command executes a BeanShell script, optionally binding some variables. 
     * This way you can dynamically execute arbitrary code on your jBPM installation. 
     */
    public class BshCommand implements Command {
    
         public static final String JBPMCONTEXT_BINDING_VARIABLE = "jbpmContext";
         
         // the script code
         protected String code;
         
         // variables to bind
         protected Map scriptVariables;
         
         public BshCommand(String code) {
              this(code, new HashMap());
         }
         
         public BshCommand(String code, Map scriptVariables) {
              this.setCode(code);
              this.setScriptVariables(scriptVariables);
         }
         
         /**
          * as Java does not have inline constructors for Maps but it does have constructors for arrays, you may prefer to provide
          * arguments using this constructor. 
          * 
          * @see BshCommand(String code, Map scriptVariables)
          */
         public BshCommand(String code, String[] keys, Object[] values) {
              if (keys == null || values == null)
                   throw new NullPointerException("cannot accept null keys or values");
              if (keys.length != values.length)
                   throw new IllegalArgumentException("keys and values arrays must be of the same length");
              
              Map map = new HashMap();
              for (int i = 0; i < keys.length; i++)
                   map.put(keys+, values+);
              
              this.setCode(code);
              this.setScriptVariables(map);
         }
         
         /**
          * convenient alias for BshCommand(String code, String[] keys, Object[] values) if you only want to pass one argument to
          * your script
          */
         public BshCommand(String code, String key, Object value) {
              this(code, new String[] {key}, new Object[] {value});
         }
         
         
         public Object execute(JbpmContext jbpmContext) throws Exception {
              Interpreter interpreter = new Interpreter();
              
              interpreter.set(JBPMCONTEXT_BINDING_VARIABLE, jbpmContext);
              
              if (this.scriptVariables != null) {
                   for (Iterator i = this.scriptVariables.keySet().iterator(); i.hasNext();) {
                        String varName = (String) i.next();
                        if (varName.equals(JBPMCONTEXT_BINDING_VARIABLE))
                             continue;
                        Object o = this.scriptVariables.get(varName);
                        interpreter.set(varName, o);
                   }
              }
              Object result = interpreter.eval(this.getCode());
              return result;
         }
    
         // accessors 
         
         public String getCode() {
              return code;
         }
    
         public void setCode(String code) {
              this.code = code;
         }
    
         public Map getScriptVariables() {
              return scriptVariables;
         }
    
         public void setScriptVariables(Map scriptVariables) {
              this.scriptVariables = scriptVariables;
         }
         
         private static final long serialVersionUID = 1L;
    }