La idea de este post es mostrar que detalles hay que tener en cuenta a la hora de crear tareas dinámicas, algo muy útil cuando se presenta un caso donde no podemos saber de antemano cuantas tareas se deberán instanciar, o cuando esta cantidad de tareas nos la dicta algún variable del proceso. (También tenemos que optar por esta opción cuando necesitamos tener mas de una tarea de la cual tenemos una sola definición)

Task Process Example

Task Process Example

En este simple proceso, que carece de significado real (debido a que el modelado, raramente debería ser de la manera planteada), pero útil para demostrar la practica, vamos a ver como podemos crear en el nodo Tomar Curso una cantidad X de tareas “Tomar Curso” dependiendo de cuantos alumnos atiendan al dictado.

Para realizar esto tendríamos que a la hora de iniciar el proceso, en cualquier paso antes de llegar al nodo Tomar Curso asignar una nueva variable en el contexto que indique la cantidad de alumnos que van a asistir al curso. Esto lo hacemos de la siguiente manera.

executionContext.getContextInstance().setVariable(”cantStudent”, 4);

Donde la cantidad 4, podría ser calculada mediante la llamada a un servicio de reservas, o algo similar.

Luego a nivel jPDL vemos como seria la definición del Task-Node para que no nos cree la tarea definida dentro de el automáticamente cuando la ejecución del proceso llegue a este nodo:

<task-node name="Tomar Curso" create-tasks="false">
  <task name="Tomar Curso" swimlane="student"></task>
  <event type="node-enter">
    <action name="Create Dinamic Tasks" class="com.sample.action.CreateDinamicTasksActionHandler"></action>
  </event>
  <transition to="end"></transition>
</task-node>

Dos cosas importantes tenemos que notar en jPDL:

  • create-tasks=”false”: se encarga de que la tarea (task name=”Tomar Curso”) no sea instanciada cuando el flujo de ejecución llegue al task-node
  • La acción en el evento node-enter: esta acción sera la encargada de crear automáticamente las tareas especificadas dentro del nodo task-node.

Sin duda  el comportamiento  del proceso que definimos sera el siguiente:

  • El proceso comienza su ejecución mediante un signal sobre el nodo de arranque
  • La ejecución continua hasta el nodo Tomar Curso. Ya que la ejecución no se detiene en el nodo Dictar Curso, porque he decidido que el nodo Dictar Curso solo debe crear la tarea para el instructor y debe dar la posibilidad de que los alumnos puedan ver sus tareas creadas antes de que se termine de dictar el curso. Para esto utilice la siguiente propiedad del nodo task-node:
    <task-node name="Dictar Curso" signal="unsynchronized">
            <task name="Dictar Curso" swimlane="instructor"></task>
            <transition to="Tomar Curso"></transition>
     </task-node>
  • Cuando la ejecución llega al nodo Tomar Curso, este por definición no crea las tareas (TaskInstances) que tiene definidas dentro, sino que delega esta acción al evento que especifico en el evento node-enter. Esta clase delegada contiene lo siguiente:
    public void execute(ExecutionContext context) throws Exception {
            TaskMgmtInstance tmi=context.getTaskMgmtInstance();
            TaskNode tomarCurso=(TaskNode)context.getNode();
            Task tomarCursoTask=tomarCurso.getTask("Tomar Curso");
            int cantStudent = Integer.parseInt(context.getContextInstance().getVariable("cantStudent").toString());
            for(int i=0; i < cantStudent;i++){
                tmi.createTaskInstance(tomarCursoTask, context.getToken());
            }
        }

    Lo que podemos ver a simple vista es que obtenemos el nodo donde estamos parados, a este le pedimos las tareas (por nombre) que tiene y a esta tareas la instanciamos haciendo uso del método createTaskInstance de la clase TaskMgmtInstance. Como el nodo task-node que contiene a estas tareas no le he cambiado la opción por defecto para hacer signal, va a esperar que se completen las tareas instanciadas dinámicamente (en este caso 4 iguales).

  • Cuando las cuatro tareas se finalizan el proceso llega con su ejecución al nodo end.

Espero que haya servido de ayuda para introducir un poco la API de Task MGMT. Cualquier duda comenten!

39 comentarios para “Jugando con jBPM #11 – Crear Task Dinámicas!”

  1. Rodrigo escribió

    Hola, al parsear la definicion del workflow

    ProcessDefinition processDefinition = ProcessDefinition.parseXmlResource(”multiple/processdefinition.xml”);

    Me da el siguiente error:
    10:33:40,343 [main] WARN JpdlXmlReader :
    process xml warning: task references unknown swimlane ‘instructor’:

    ¿Hay que crear las swimlanes antes del proceso que las usa o algo parecido?

  2. salaboy escribió

    Exacto, debes definirlas antes a nivel de , En GPD (Graphic Process Designer / Eclipse) si haces click sobre el fondo (es decir, la parte blanca del dibujo) vas a ver que ahi podes definir las swimlanes.
    Saludos.

  3. cmc escribió

    Hola, quería saber si es posible indicarle a que actor asignarle la tarea al crearla dinámicamente.

    Un saludo.

  4. salaboy escribió

    sobre el taskInstance tenes los metodos para asginar un actor o pooledActors.
    Saludos

  5. Fer escribió

    Excelente articulo!!!

    Si en vez de 4 “tomar cursos” fueran 4 “aprobar curso” donde la idea es que tengo 4 alumnos inscriptos y un rol APROBADOR que analiza si acepta a ese alumno o no, y si NO lo aprueba se cancela el curso.
    Como sería la instanciacion de esa tarea? ya que tendria que tener una decision en el medio (si la respuesta es Aprobar OK, pasa a la proxima tarea, y si la respuesta es Aprobar NO, termina el task node)

  6. salaboy escribió

    En realidad deberíamos modelarlo de la siguiente manera:

    Primero una tarea o un nodo que tome un parametro que indique cuantas inscripciones a un curso se abren.
    Luego basándonos en esta cantidad de inscripciones disponibles deberías tener un task-node que cree dinámicamente las tareas de inscripciones para que cada alumno se inscriba. Aca podrías utilizar alguna política para que si algún alumno no se inscribe y el curso tiene que empezar no se espere a que todos los alumnos se inscriban.
    Una vez que los alumnos se inscriben, por cada alumno inscripto deberías crear una tarea de aprobación asignada al aprobado, que va revisando el perfil de cada alumno y le da el ok o no a cada uno para realizar el curso.

    Luego con la cantidad de alumnos ok, yo crearía una tarea que represente al curso, esta quizas no la necesitas.

    Espero haber sido claro con mi comentario, sino espero tu nuevo comentario y podemos ampliar el ejemplo propuesto arriba.
    Saludos!

  7. Fer escribió

    Hola, Muchas gracias por la rápida respuesta!.
    Me surgieron nuevas dudas!!.
    1) Es posible definir Procesos (workflows), tareas, etc, DINAMICAMENTE, sin usar los XML? (por ejemplo escribiendo en la base)
    Como se haría esto en el caso de que se pueda? (una idea)

    2)respecto del ejemplo anterior. Lo que necesitaba hacer es un proceso donde se crean dinamicamente 4 task “Aprobar Alumno”, pero en caso de que el usuario NO aprueba a un alumno, Se termina el proceso. Si se aprueba, se pasa a la siguiente task “Aprobar alumno”. O sea que no solo deberia crear dinamicamente los 4 task “Aprobar Alumno” sino que despues de crear cada uno, deberia engancharle una “Decision”. Eso es lo que me esta mareando un poco, y nose bien como hacerlo.

    Muchas gracias de nuevo por tu ayuda!!!!!

  8. salaboy escribió

    con respecto a tu pregunta 2) lo que realmente necesitarías es un nodo Fork dinámico, donde se definen ramas enteras de ejecución. en estas ramas vas a poder definir varios nodos, que pueden ser distintos en cada rama o iguales en tu caso. Veo que en tu caso cuando dices terminar el proceso, es que se termina solamente para el alumno y no para el proceso. Con este enfoque podrías modelarlo bien. Revisa un poco el nodo Fork para ver como se comporta.
    Con respecto a tu pregunta 1) no deberías estar queriendo hacer esto.. si es tan dinámico no tiene sentido que modeles un proceso. Tienes que pensarlo por ese lado.

    Saludos!

  9. Fer escribió

    Muchas gracias de Nuevo!!!
    Respecto a 1), leí en un foro que se puede de deployar un XML programaticamente (en runtime), con ProcessArchiveDeployer, pero no encontre NADA al respecto en la documentacion.
    Hay algo de cierto en eso? Se puede?
    Muchas gracias de nuevo!, y mil disculpas por tantas preguntas. Es que estoy evaluando si usamos JBPM o no.

  10. salaboy escribió

    Si totalmente puedes hacer deploy de xml en runtime cuando quieras, y si lo vas a hacer programaticamente seguramente te convenga usar JbpmContext.deployProcessDefinition(String), eso siempre lo puedes hacer.
    Cualquier pregunta que tengas no dudes en postearla. COmo te decía antes por ahí tienes que aprender un poco sobre modelado antes de decidir como vas a modelar tus procesos y si tienen carácter dinámico o no.

    Saludos!

  11. Fer escribió

    Tenes toda la razon!!
    Excelente!,es lo que estaba buscando. Lo bueno es que estas dudas, tambien le van a servir a otro.

    Mi duda ahora es, si deployo en runtime un proceso que tiene un Node. Como hago en runtime para definir el metodo execute de dicho nodo? (veo dificil que se pueda hacer)

  12. salaboy escribió

    No no es difícil, lo que puedes hacer es ya tener deployado las clases que van a tener las acciones delegadas.. es decir, pones en el classloader de tu jboss un jar que tenga muchos action handlers implementados y luego en el node solo aclaras la clase que ya esta deployada. Si en realidad necesitas hacer esto dinámicamente tienes dos opciones mas:
    1) recurrir al PAR deployer, a mi gusto no es la mas linda
    2) leer un poco sobre runtime actions

    Saludos!

  13. Fer escribió

    Ahora si!!!
    MIL GRACIAS! en serio!
    Excelente Blog!

  14. salaboy escribió

    De nada, cuando quieras espero tus comentarios.
    Recuerda siempre que sus comentarios me inspiran para escribir nuevos artículos!
    Saludos!

  15. Fer escribió

    Hola!, quería saber si conoces, donde puedo encontrar un ejemplo de uso de Forms con tasks, o explicar un poco para que sirve. No encontre nada en la web.
    Si vi que existe un libro sobre JBPM, si algun bondadoso estaría dispuesto a compartirlo, mi email es tangomannn@yahoo.com.ar
    Es una lastima que algo tan poderoso como JBPM tenga una documentacion tan pobre.
    Muchas gracias!!
    Saludos, desde Buenos Aires

  16. salaboy escribió

    Gracias por tu comentario. Te comento que a lo largo de mis experiencias con jBPM encontré que la mejor documentación del framework es el código. Y muchos en el comunidad no estan acostumbrados o no tienen el tiempo necesario como para interiorisarse con el mismo. Actualmente hay un solo libro publicado sobre jBPM pero esta muy orientado a analistas de negocios, por lo tanto solo comenta temas básicos de desarrollo.
    Con respecto a tu problemática sobre los task y forms, creo que el tema en si merece un post. Por lo tanto voy a tratar de escribir uno en la brevedad.
    Por el momento puedo decirte que deberías buscar y leer en la documentación oficial cuando habla sobre task controllers y algunos ejemplos sobre esto.
    Saludos, yo también me encuentro en capital federal – Buenos Aires!

  17. Fer escribió

    Muchas gracias!
    si, ya me largue a probar con el codigo y a jugar un poco, dado que hay poca doc.

    Muchas gracias de nuevo!

  18. salaboy escribió

    Cualquier duda, espero tu comentario.
    Saludos!

  19. Fer escribió

    Hola Sala, te queria hacer una consulta.
    Al querer arrancar una instancia de una tarea(TaskInstance.start()) estoy recibiendo una exception. La verdad me tiene trabadisimo hace dos dias. Te pego el codigo, por si se te ocurre algo.
    Muchisimas gracias

    Exception: org.hibernate.HibernateException: null index column for collection: org.jbpm.graph.exe.ProcessInstance.instances
    La Taskinstance esta ya asignada al usuario

    Codigo:
    protected void executeFirsTask(){
    JbpmContext ctx = JbpmConfiguration.getInstance().createJbpmContext();
    try{
    List tasks = ctx.getTaskMgmtSession().findTaskInstances(getUsername());

    if (tasks!=null && tasks.size() >0){
    TaskInstance ti =tasks.remove(0);
    ti.start(); //HERE Throws the ExceptioN!
    ti.end();
    ctx.save(ti);
    }
    }finally{
    ctx.close();
    }
    }

  20. salaboy escribió

    Puedes debuggear y decirme si la TaskInstance tiene date de start?
    Tambien serviría un poco mas de la exception.
    Fijate que solamente estas tratando de comenzar la primer tarea de la lista… estas seguro que es la que quieres empezar?

  21. Fer escribió

    Hola
    Hasta recien el date de start estaba en null. Ahora actualice el JAR a 3.3.0 y arranca la task, y le pone fecha a start, PERO ahora la exception salta en ti.end() (La misma exception: org.hibernate.HibernateException: null index column for collection: org.jbpm.graph.exe.ProcessInstance.instances
    )

    Si es la primera tarea la que quiero empezar (la debuguie para chequear que sea).

    Me parece que lo que indica la exception es que:
    TaskInstance.ProcessInstance.instances esta en null. Pero por lo que vi, siempre esta en null.

    Te juro que hace 2 dias que estoy trabado con esto…
    Cualquier ayuda es bienvenida
    muchas gracias

  22. salaboy escribió

    Me llama la atención. Vos estabas generando tareas dinámicamente no?
    Yo conociendo tan poco de como estas manejando las cosas en tu aplicación, se me complica para aconsejarte.
    Pero una pregunta que se me ocurre es:
    como se creo esa taskInstance que estas tratando de arrancar y terminar?
    Eso me puede dar una pista sobre tu problema.
    Saludos

  23. Fer escribió

    Aparentemente las crea y las persiste bien a las tareas.

    Pongo el breve codigo.

    public class TaskCreation implements ActionHandler {
    public static String AUTH_NUMBER = “AUTH_NUMBER”;
    public static String USERS = “USERS”;

    public void execute(ExecutionContext executionContext) throws Exception {

    JbpmContext jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();
    try{
    TaskMgmtInstance tmi = executionContext.getTaskMgmtInstance();
    TaskNode nodeApproval = (TaskNode)executionContext.getNode();
    Task task = (Task)((nodeApproval.getTasks().iterator().hasNext())?nodeApproval.getTasks().iterator().next():null);

    Integer tasksNumber = (Integer)executionContext.getVariable(nodeApproval.getName()+AUTH_NUMBER);
    String user = (String)executionContext.getVariable(nodeApproval.getName()+USERS);
    for (int i=0; i<tasksNumber; i++){
    TaskInstance newTaskInstance= tmi.createTaskInstance(task, executionContext.getToken());
    newTaskInstance.setActorId(user);

    jbpmContext.save(newTaskInstance);
    }

    jbpmContext.getSession().save(executionContext.getTaskMgmtInstance());
    }finally{
    jbpmContext.close();
    }

    }
    }

    Aclaracion:
    La linea:
    jbpmContext.getSession().save(executionContext.getTaskMgmtInstance());
    la tuve que poner para que NO me salte la siguiente exception.

    ERROR DbPersistenceService : hibernate flush failed
    org.hibernate.TransientObjectException: object references an unsaved transient instance – save the transient instance before flushing: org.jbpm.taskmgmt.exe.TaskInstance

    Estara por aca el problema?

  24. salaboy escribió

    Si debe ser por eso el problema..
    Si estas en un actionHandler se supone que el proceso ya conoce el contexto.
    Por lo tanto si vos especificas el contexto con las lineas:
    JbpmContext jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();

    estas mezclando los conceptos.
    Tenes que sacar todo lo que tenga que ver con el contexto y dejarlo como lo puse yo en el ejemplo..
    Proba asi…
    Saludos

  25. Fer escribió

    Sala!, muchisimasss graciasss por orientarme!!!
    Lo que hice ahora y anda, es crear UNA SOLA VEZ el contexto, y despues reutilizarlo con
    JbpmContext jbpmContext = JbpmConfiguration.getInstance().getCurrentJbpmContext();
    Ahora esto trae un problema!!,y es que JBPM No persiste hasta que haces context.close(). Pero se soluciona haciendo explicitamente:
    jbpmContext.getSession().flush();

    Muchas Gracias !! en serio!!
    Saludos, Fer

  26. salaboy escribió

    para que quede claro, al contexto solo lo tenes que crear desde donde estas ejecutando el proceso (cliente) y no dentro de los action handlers.
    Si todavía estas haciendo eso dentro del action handler debes sacarlo, sino te vas a encontrar con problemas cuando no deberías.

    Saludos

  27. Fer escribió

    Si, es medio peligroso. Habria que encapsularlo en un Singleton donde si ya esta creado te de la instancia actual.

    Muchas gracias!!, ya de a poco despues de debuguear tanto el framework le voy agarrando la mano.
    Pero eso de tener que hacer jbpmContext.getSession().flush(); o persistir a mano algunos objetos, me parece que son algunos bugcitos que se les pasaron.

  28. salaboy escribió

    No no, no deberia estar encapsulado, debido a que ya lo tiene. Debes sacar eso. Si estas usando el método flush de la session de hibernate ya es porque estas haciendo algo mal.
    Prueba sacando todo rastro del contexto en ese ActionHandler y vas a ver que todo va a funcionar correctamente y te va a quedar el codigo mucho mas limpio y como debe ser.
    Saludos

  29. Fer escribió

    Juro que probe todas las combinaciones que se te pueden ocurrir y si no hago un save, y luego un flush() desde la session de hibernate, no me me persiste las TaskInstances que creo dinamicamente.
    Segun la documentación:
    Uno tiene que encargarse de hacer los save() de los process instances, y el flush se hace en el close() del JbpmCOntext, y para hacer un save() necesitas si o si utilizar el context, porque es un metodo del JbpmContext.
    A menos que hagas jbpmContext.loadProcessInstanceForUpdate() y ahi en el close se hace automaticamente el save().

    Abrazo!

  30. salaboy escribió

    En la documentacion hace referencia a los clientes que estan utilizando a jBPM y no a los action handlers que ya por defecto tiene el contexto y saben como deben persistirse..
    Tiene que dejar el codigo limpio como esta en el ejemplo:
    public void execute(ExecutionContext context) throws Exception {
    TaskMgmtInstance tmi=context.getTaskMgmtInstance();
    TaskNode tomarCurso=(TaskNode)context.getNode();
    Task tomarCursoTask=tomarCurso.getTask(”Tomar Curso”);
    int cantStudent = Integer.parseInt(context.getContextInstance().getVariable(”cantStudent”).toString());
    for(int i=0; i < cantStudent;i++){
    tmi.createTaskInstance(tomarCursoTask, context.getToken());
    }
    }

    En este código en ningún lugar se hace referencia al contexto ya que eso lo define el cliente y el proceso lo único que hace es ejecutarse y utilizar los servicios que el contexto le provee.
    Esta suele ser una de las grandes fallas conceptuales a la hora de usar jBPM.
    Entiendes mi punto de vista?
    Mas alla de que lo hayas hecho funcionar no es la manera correcta de usarlo, entiendes porque?
    Saludos.
    PD: esta bueno que tengamos estas discusiones un poco mas teóricas.

  31. Fer escribió

    Me gusto esta última aclaración. Me aclaró mucho mas el panorama.
    Voy a tratar hacerlo funcionar como decis, dejando las persistencias del lado del cliente.
    Te cuento en breve.
    Muchas gracias por el tiempo Sala!

  32. Fer escribió

    Bueno, encontre el “error”.
    Era que estaba haciendo
    new ProcessDefinition() en vez de ProcessDefinition pd = ProcessDefinition.createNewProcessDefinition();

    Aparentemente al crear un process definition programaticamente si no usas esa linea, la persistencia funciona MUYYY MALLL. Tenia que hacer chanchadas por todos lados.
    La verdad que muy mal que no expliquen eso en la documentacion. Ya deje un post en el foro de jboss.
    Un abrazo y MUCHISIMAS GRACIAS Sala.

  33. salaboy escribió

    Si vi tu post en el foro.
    En realidad la mejor documentación esta en el mismo código de jBPM.
    Tienes que aprender a buscar en el código ya que es una de las ventajas de que el proyecto sea open source.
    Si revisas la implementación de la clase ProcessDefinition vas a encontrar que no hay ningun problema con hacer
    ProcessDefinition processDefinition = new ProcessDefinition();
    Salvo que después tenes que agregarle información a mano como hace el método createNewProcessDefinition().
    Te dejo como consejo entonces que te amigues con el código y con el debug de tu IDE.
    Cualquier consulta, no dudes en dejar tu comentario.
    Saludos

  34. Fer escribió

    je, no es que le tenga miedo a debuguear codigo ajeno y que no lo haya hecho, es que la ventaja de usar un framework ajeno es esa, la de leer la interfaz, saber lo que hace la clase y olvidarte de la implementación. Si tuviera que ponerme a debuguear, hibernate, spring, struts y las decenas de frameworks y herramientas que se utilizan en un proyecto, se hace un poco larga la cosa. No pido mucho!! un Javadoc nada mas!
    saludos.

  35. salaboy escribió

    Tienes razón en que no hay mucha documentación y la documentación que hay no es muy completa.
    Por eso mi recomendación para este framework en particular, es programar con los fuentes al lado, al menos para empezar a comprender el funcionamiento interno de la herramienta. Una vez que encuentres los comportamientos básicos ya no te harían falta, es como parte del aprendizaje.
    Tienes que tener en cuenta también la madurez del framework para compararlo con otros como hibernate y struts que probablemente tienen mas de 20 libros escritos sobre ellos y muchas implementaciones que han ayudado a que esas herramientas se mejoren con el tiempo, asi como la documentación sobre ellos.

    Si tienes algún problema preguntame, yo no tengo problema en tratar de buscar la solución, asi no te pasas una semana luchando con un tema en particular.

    Saludos

  36. Fer escribió

    Muchas gracias!!, voy a seguir tu consejo de bucear un poco por dentro de JBPM

    saludos!

  37. Luis Alfredo Narvaez escribió

    Salaboy, como andas?

    Una pregunta.. sabes como crear tasks adjuntando archivos?

  38. Luis Alfredo Narvaez escribió

    Salaboy! Me surgio otra pregunta con respecto a las tareas.

    ¿Yo puedo definir formularios de tarea que funcionen en otro contexto diferente al de la web-console de jBPM? (por ejemplo, en un contexto de una aplicacion nuestra) o es mejor cambiar el codigo de la web-console para adaptarlo a nuestras necesidades?

    Disculpa que pregunte tanto, pero no encuentro mas informacion al respecto, anduve revisando el codigo fuente, pero no encontré muchas cosas.

  39. salaboy escribió

    No hay problemas con tus preguntas, gracias por tus comentarios.
    Con respecto a definir formularios que funcionen en otro contexto, por supuesto que puedes. La web-console es solo una aplicacion para ver como funciona jBPM de una manera rapida.
    Por eso mismo te recomiendo que no trates de adaptarla a tus necesidades. Creando aplicaciones con JBoss Seam o cualquier otro framework web seria lo recomendable, asi tenes todo el control del desarrollo. Si revisas un poco los foros de jBPM mucha gente se queja de esto y hasta los mismos desarroladores dicen que no todos entienden que mente retorcida ideo esa consola.

    Con respecto a los archivos, es todo un tema, ya que dependiendo de que tipo de archivos y como quieras manejarlos es la solucion que vas a implementar.
    Para dejarte un ejemplo te recomiendo que veas como esta implementado Alfresco, que mas que manejar archivos, maneja documentos en un repositorio de contenidos. Si no quieres algo tan sofisticado, guardando el nombre y la ruta al archivo en una variable del proceso y subir el archivo a un servidor con alguna rutina, podria ser una solucion valida.

    Espero haber sido de ayuda, sino vuelve a preguntar.
    Saludos!

Escribe un comentario