10 Replies Latest reply on Oct 10, 2007 7:26 PM by Ronald van Kuijk

    dynamic commands

    Adrian Dimulescu Newbie

      Hello,

      Even if the predefined Commands that can be thrown at the CommandService are pretty rich, it would be nice to be able to throw any code you can think of, without the need to lookup the sources of the various Commands and try to figure out which combination would do the thing you want.

      It would make sense then to use dynamic languages like Groovy or Beanshell to specify the command. A simple BeanShell-only command :

      public class BshCommand implements Command {
      
       protected String code;
      
       public BshCommand(String code) {
       this.setCode(code);
       }
      
       public Object execute(JbpmContext jbpmContext) throws Exception {
       Interpreter interpreter = new Interpreter();
       interpreter.set("jbpmContext", jbpmContext);
       Object result = interpreter.eval(this.getCode());
       return result;
       }
      
       public String getCode() {
       return code;
       }
      
       public void setCode(String code) {
       this.code = code;
       }
      
      }
      


      which could be used like this :

      Command cmd = new BshCommand("return jbpmContext.getProcessInstance(5);");
      Object resp = service.execute(cmd);


      As bsh is already a dependency, I think the possibilies of querying a remote ejb would be greatly enhanced by just adding this command.

      Using BSF the BshCommand could actually be named ScriptCommand and might execute any other cool scripting languages...

      Best regards,
      Adrian.

        • 1. Re: dynamic commands
          Edward Staub Expert

          Adrian,

          Very cool! Groovy, even ;-)
          I've been noodling over how to make commands more extensible - this is definitely the ticket.

          -Ed Staub

          • 2. Re: dynamic commands
            Ronald van Kuijk Master

            Yes, it is more extensible, but personally I'd prefere that people who develop 'real' commands, 'donate' these to the project so everybody can benefit. With to much flexibility, that will probably done less :-(

            • 3. Re: dynamic commands
              Adrian Dimulescu Newbie

              I guess donations should be accepted for scripted commands too :)

              I think the big problem with the set of static commands is finding a case -- in production -- that you can't manage because the command does not exist.

              For instance, right now, if I'm not mistaken, there is no command that allows moving a token to an arbitrary node (without signalling).

              • 4. Re: dynamic commands
                Ronald van Kuijk Master

                I know and I do not totally disagree, but you can easilly add commands implemented in java as well.... the source IS open you know. And in our dev/test/acc/prod process we'd have to deploy all anyway, so adding a real command does not differ that much from adding a new dynamic command.

                • 5. Re: dynamic commands
                  Adrian Dimulescu Newbie

                  Here is my current implementation of BshCommand in case anybody needs it.
                  It supports passing optional parameters to scripts (in case you keep your script as a resource and want to pass it a parameter without having to String.replaceAll(...):

                  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 {
                  
                   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;
                  }
                  


                  some usage example. Suppose you have a script like this as a classpath resource (do not call it script.bsh 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");


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




                  • 6. Re: dynamic commands
                    Ronald van Kuijk Master

                    cool... can you make a wiki page for this?

                    • 8. Re: dynamic commands
                      Ronald van Kuijk Master

                      thanks... one more question....

                      can you put it in a page called JbpmBshCommand ?

                      • 9. Re: dynamic commands
                        Adrian Dimulescu Newbie

                        Mark Richards about the Command pattern -- which is heavily used by jBPM: http://www.nofluffjuststuff.com/media.jsp?mediaId=28

                        Noteworthy: he uses a DTO view of the Command for the client; the CommandImpl that contains the actual code only resides on the server ; he hates dynamic commands too :)

                        • 10. Re: dynamic commands
                          Ronald van Kuijk Master

                          Yeah... now I'm not alone anymore and even better, I'm in good company ;-)