miércoles, 22 de julio de 2009

Uso de un nodo de decisión

Un nodo de decisión se usa para modelar un punto en el flujo en que el propio proceso quien tomar una decisión sobre el camino a seguir. Los criterios de la decisión se pueden especificar de las siguientes formas:
  • Añadiendo condiciones a las transiciones o beanshell scripts que devuelven un boolean. Los nodos recorreran sus transiciones examinando las condiciones hasta encontrar la primera transición que cumpla las condiciones.
  • Mediante DecissionHandler que devuelve la transición a seguir.
En este post vamos a crear un proceso que contiene un nodo de decisión que utiliza un DecissionHandler para conocer la transición a seguir.

En la siguiente imagen se puede ver el proceso de ejemplo:

Este proceso empieza en un nodo de inicio del que se sale mediante la transicion comenzar. Esta transición tiene una acción crearvalor manejada por la clase ValorAction, que se encarga de introducir en el contexto de ejecución una variable valor:
public class ValorAction implements ActionHandler {
public String valor;

@Override
public void execute(ExecutionContext executionContext) throws Exception {
executionContext.getContextInstance().setVariable("valor", valor);
}
}

A continuación se llega a un nodo de decision manejado por una clase Decisor, que comprueba si en el contexto de ejecución existe una variable valor con un valor asignado. Si existe esta variable se abandona el nodo por la transición terminar1 y si no, por la transición terminar2:
public class Decisor implements DecisionHandler {

public Decisor(String info) {
super();
}

@Override
public String decide(ExecutionContext executionContext) throws Exception {

String valor =
(String)executionContext.getContextInstance().getVariable("valor");

if(valor != null) {
return "terminar1";
} else {
return "terminar2";
}
}
}

Por último, al llegar a cualquiera de los nodos finales se lanza una acción que muestra en que nodo está terminando la ejecución del flujo, manejada por la clase MostrarMensajeAction:
public class MostrarMensajeAction implements ActionHandler {

public MostrarMensajeAction(String info) {
super();
}

@Override
public void execute(ExecutionContext executionContext) throws Exception {

System.out.println(
"Se sale del flujo por el nodo: "
+ executionContext.getNode().getName());
}
}

Se puede ejecutar este proceso mediante una clase de test como esta:
public void testSimpleProcess() throws Exception {

// Extraer la definicion de proceso del archivo processdefinition.xml.
ProcessDefinition processDefinition =
ProcessDefinition.parseXmlResource("decision/processdefinition.xml");
assertNotNull("La definicion debe no ser nula", processDefinition);

// Create an instance of the process definition.
ProcessInstance instance = new ProcessInstance(processDefinition);
assertEquals(
"La instancia está en el estado inicial",
instance.getRootToken().getNode().getName(),
"inicio");
assertNull(
"La variable 'valor' no debe existir todavía",
instance.getContextInstance().getVariable("valor"));

// Mueve la instancia de proceso desde el estado inicial al primer estado.
instance.signal();
}
}

Si en la configuración se ha introducido un valor para la variable valor, por la consola se verá un mensaje que indica que estamos terminando en el nodo final1. En caso contrario veremos que se termina en el nodo final2.

Referencias:
jBPM Javadoc

martes, 7 de julio de 2009

Asignación de tareas en jBPM

En este artículo vamos a ver cómo crear tareas en jBPM y asignar que actor o actores deben realizarlas. Esto permite a los actores responsables de su realización recoger las tareas pendientes que existan en un determinado momento para completar su ejecución.

En primer lugar vamos a crear una definición de proceso llamada tarea formada por un nodo inicial, un nodo de tarea y un nodo final:

Como se dijo en un post anterior (Modelado de procesos en jBPM): “el nodo de tarea representa una o varias tareas que deben ser ejecutadas por una persona. Cuando la ejecución del proceso llega a un nodo de tarea, se crea una instancia de una tarea (task) en la lista de los miembros del workflow. El nodo se quedará en estado de espera hasta que el usuario informe de la realización de la tarea.

Para crear una tarea en un nodo de tarea, vamos a las propiedades de este nodo. En la tabla de tareas pulsamos el botón derecho del ratón y seleccionamos New Task. Le damos nombre a esta tarea, en este caso algoquehacer y descripción si queremos. En el apartado Assignment, vemos que hay varias formas de asignar un tarea:
  • Actor: Se le indica el actorId, que es un String que identifica el actor responsable de la tarea. Cuando se crea la tarea al entrar al nodo de tarea, esta tarea pasa inmediatamente a la lista de actividades pendientes de este actor.

  • Pooled Actor: Se le indica una secuencia de actorId separados por comas. Esta tarea al ser creada no es asignada a la lista de ningún actor. Un actor la debe recuperar de la lista de tareas pendientes para el pool de actores y asignarsela para que forme parte de su lista de tareas.

  • Swimlane: Un swimlane o carril sirve para definir una asignación que es la misma para varias tareas de un proceso.

  • Expression: Se le indica una expresión de asignación evaluada por el componente de identidad de jBPM. Permite realizar asignaciones en función del usuario, grupo o rol.

  • Handler: Se le indica una clase manejadora, que será la que realice la asignación de la tarea.

Para mantener el ejemplo sencillo vamos a realizar la asignación de la tarea a un actor, al que vamos a llamar user1:

Después de desplegar este proceso en el servidor, vamos a realizar una prueba en dos pasos. En el primer paso vamos a crear una instancia de este proceso tarea y a iniciar su ejecución. Esto es algo que ya hemos visto:
// Se carga la configuración de jBPM
JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();

// Se crea un contexto jBPM
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();

// Se obtiene un GraphSession que permite realizar operaciones adicionales
// sobre los grafos de procesos
GraphSession graphSession = jbpmContext.getGraphSession();
// Busca la última versión del proceso simple
ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("tarea");
// Se crea una nueva instancia del proceso simple
ProcessInstance processInstance = new ProcessInstance(processDefinition);

// Se pasa al nodo tarea
processInstance.signal();

// Se cierra el contexto jBPM
jbpmContext.close();

Se puede ver en la tabla JBPM_TASKINSTANCES una instancia de la tarea algoquehacer asignada al usuario user1 pendiente de ser ejecutada, es decir, no esta marcada como terminada. Vamos a recuperar los procesos pendientes para el usuario user1 gracias a la clase TaskManagementSession y a informar de la finalización de esta tarea:
// Se carga la configuración de jBPM
JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();

// Se crea un contexto jBPM
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();

TaskMgmtSession tms = jbpmContext.getTaskMgmtSession();
String actorId = "user1";
List instances = tms.findTaskInstances(actorId);
for (TaskInstance taskInstance : instances) {
System.out.println("Tarea pendiente de ejecutar por "
+ actorId + " : " + taskInstance.getName() + taskInstance.getId());

// Se informa de la finalización de la tarea
taskInstance.end();
}

// Se cierra el contexto jBPM
jbpmContext.close();

Se ha visto como asignar una tarea a un actor y de como recuperar las tareas pendientes de un actor en un proceso posterior para llevar a cabo su ejecución.

Referencias:
JBoss user guide
jBPM Javadoc

viernes, 3 de julio de 2009

Persistencia en MySQL de procesos jBPM

Hemos visto en un artículo anterior (Instalación jBPM en MySQL) cómo instalar jBPM en un entorno formado por un servidor de aplicaciones Jboss y una base de datos MySQL, y hemos desplegado una definición de un proceso simple que vamos a usar para nuestras pruebas.

También hemos visto como ir recorriendo el flujo de ejecución de una instancia de este proceso, aunque lo hemos hecho de forma local. El estado de esta ejecución se guarda en memoria y cuando esta termina no queda rastro de ella.

Sin embargo, lo normal es que un proceso no se ejecute de seguido, si no que haya que en ocasiones se mantendrá en espera de que ocurra algún evento o de que algún actor realice una tarea antes de reanudar la ejecución. Por ello, toda la información relativa a la ejecución del proceso debe persistir en una base de datos de la que poder recuperarla cuando sea preciso.

Para la persistencia en bases de datos, jBPM utiliza Hibernate. Por tanto, lo primero que hemos de hacer es realizar la configuración adecuada para nuestra base de datos en el archivo hibernate.config.xml. Dentro del proyecto que hemos creado, en la carpeta src/main/config se pueden encontrar varios ejemplos de configuración para las distintas bases de datos soportadas. En nuestro caso, sustituimos el contenido del archivo hibernate.config.xml por el de hibernate.config.mysql.xml. Vamos a hacer un cambio para que no se vuelva a crear el esquema de la base de datos cada vez que se cargue la configuración, que va a ser comentar la línea
<property name="hibernate.hbm2ddl.auto">create</property>

También hay que añadir la librería del conector de MySQL que contiene el driver JDBC a la base de datos.

Una vez hecho esto, veremos cómo se maneja la persistencia en jBPM. Lo primero es cargar la configuración de jBPM, que se guarda en el archivo jbpm.cfg.xml, de la siguiente manera:
// Se carga la configuración de jBPM
JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();

Para poder realizar operaciones de persistencia se debe crear un contexto jBPM:
 // Se crea un contexto jBPM
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();

Una vez creado este contexto, se pueden realizar operaciones de persistencia. Cuando se hayan finalizado estas operaciones, se debe cerrar el contexto:
// Se cierra el contexto jBPM
jbpmContext.close();

Como ejemplo vamos a crear una instancia del proceso simple:
// Se obtiene un GraphSession que permite realizar operaciones adicionales
// sobre los grafos de procesos
GraphSession graphSession = jbpmContext.getGraphSession();
// Busca la última versión del proceso simple
ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("simple");
// Se crea una nueva instancia del proceso simple
ProcessInstance processInstance = new ProcessInstance(processDefinition);

// Se buscan las instancias del proceso simple
List processInstances =
graphSession.findProcessInstances(processDefinition.getId());
for (Object object : processInstances) {
processInstance = (ProcessInstance)object;

System.out.println("Se ha creado la instancia: " + processInstance.getId()
+ " del proceso: " + processDefinition.getName());
}

Ahora podemos revisar la base de datos y comprobar la creación de esta instancia en la tabla JBPM_PROCESSINSTANCE. También podemos ver en la tabla JBPM_TOKEN el token de ejecucion de esta instancia. En el campo NODE de esta tabla se indica en que nodo del flujo se encuentra la ejecución de este proceso.

De esta manera, se ha creado una instancia del proceso simple que está preparada para su ejecución. Para ello, un usuario debería recuperar esta instancia del contexto de la siguiente forma:
// Se carga la configuración de jBPM
JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();

// Se crea un contexto jBPM
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();

// Se obtiene un GraphSession que permite realizar operaciones adicionales
// sobre los grafos de procesos
GraphSession graphSession = jbpmContext.getGraphSession();
// Busca la última versión del proceso simple
ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("simple");

// Se buscan las instancias del proceso simple
List processInstances =
graphSession.findProcessInstances(processDefinition.getId());
for (Object object : processInstances) {
ProcessInstance processInstance = (ProcessInstance)object;

// Se comprueba si esta instancia ha concluido
if(!processInstance.hasEnded()) {
System.out.println("Se va a enviar una señal a la instancia: "
+ processInstance.getId());
// Se arranca la ejecución de esta instancia
processInstance.signal();
System.out.println("Se ha enviado una señal a la instancia: "
+ processInstance.getId());
} else {
System.out.println("Ya ha terminado la instancia: "
+ processInstance.getId());
}
}

jbpmContext.close();

Con esto hemos visto como usar la persistencia de jBPM para crear una instancia de un proceso y posteriormente recuperar esta instancia y continuar con su ejecución.

Referencias:
jBPM user guide
jBPM Javadoc

jueves, 2 de julio de 2009

Ejemplo de un proceso sencillo en jBPM

En el artículo de instalación de Jboss Tools (Instalación de JBoss Tools) se creó un proyecto Simple que definía en proceso simple en jBPM. Tambien en un artículo anterior (Modelado de processos en jBPM) hemos visto los elementos que permiten definir un proceso. Teniendo estos dos artículos en cuenta vamos a echar un vistazo al proceso simple.

En primer lugar vamos a ir a la definición de dicho proceso, que se encuentra en el archivo processdefinition.xml del paquete simple del directorio src/main/jpdl. Si hacemos doble clic sobre este archivo se vemos este proceso modelado de forma gráfica en el jBPM Graphical Process Designer:



El proceso simple está compuesto de tres nodos y dos transiciones:
  • Nodo start: Nodo inicial.
  • Nodo first: Nodo intermedio de estado.
  • Nodo end: Nodo final del proceso.
  • Transición to_state: Transición que lleva del nodo start al nodo first.
  • Transición to_end: Transición que va del nodo first al nodo end.
Las dos transiciones tienen asociadas una acción definida a través del manejador MessageActionHandler:
package com.sample.action;

import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;

public class MessageActionHandler implements ActionHandler {

private static final long serialVersionUID = 1L;

/**
* The message member gets its value from the configuration in the
* processdefinition. The value is injected directly by the engine.
*/
String message;

/**
* A message process variable is assigned the value of the message
* member. The process variable is created if it doesn't exist yet.
*/
public void execute(ExecutionContext context) throws Exception {
context.getContextInstance().setVariable("message", message);
}
}

Esta accion guarda en el contexto de la instancia de proceso la variable 'message', cuyo valor está definido dentro de las propiedades de la acción.

Vamos a probar la ejecución de este proceso mediante la clase de test SimpleProcessTest:
package com.sample;

import junit.framework.TestCase;

import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;

public class SimpleProcessTest extends TestCase {

/**
* Método de test que pasa por todas las tareas del proceso 'simple'.
*/
public void testSimpleProcess() throws Exception {
...
}
}

Lo primero que hace este método es leer la definición del proceso del archivo simple/processdefinition.xml:
// Extraer la definicion de proceso del archivo processdefinition.xml.
ProcessDefinition processDefinition = ProcessDefinition.parseXmlResource("simple/processdefinition.xml");
assertNotNull("La definicion debe no ser nula", processDefinition);

A continuación crea un nueva instancia del proceso simple:
// Create an instance of the process definition.
ProcessInstance instance = new ProcessInstance(processDefinition);
assertEquals(
"La instancia está en el estado inicial",
instance.getRootToken().getNode().getName(),
"start");
assertNull(
"La variable 'message' no debe existir todavía",
instance.getContextInstance().getVariable("message"));

Después manda una señal a la instancia creada para que pase al primer estado:
// Mueve la instancia de proceso desde el estado inicial al primer estado.
// Se debe ejecutar la acción configurada y debe aparecer el mensaje
// apropiado en la variable de proceso message.
instance.signal();
assertEquals(
"La instancia está en el primer estado",
instance.getRootToken().getNode().getName(),
"first");
assertEquals(
"La variable 'message' contiene el mensaje",
instance.getContextInstance().getVariable("message"),
"¡Va al primer estado!");

Por último, se envía otra señal a la instancia para que vaya al estado final y termine la ejecución del proceso:
// Mueve la instancia de proceso al estado final. Se debe ejecutar de
// nuevo la acción configurada. El mensaje contiene un nuevo valor.
instance.signal();
assertEquals(
"La instancia está en el estado final",
instance.getRootToken().getNode().getName(),
"end");
assertTrue("La instancia ha terminado", instance.hasEnded());
assertEquals(
"La variable 'message' ha cambiado",
instance.getContextInstance().getVariable("message"),
"About to finish!");

Hemos visto como crear instancias de un proceso a partir de un archivo de definición de proceso, que puede ser creado de forma gráfica. También hemos visto como se realiza la ejecución del proceso.

No se ha tratado la persistencia en la base de datos de la información del proceso. En el próximo artículo veremos como persistir la información de ejecución de un proceso en la base de datos.

Modelado de procesos en jBPM

Antes de seguir adelante voy a hablar de los elementos que definen un proceso en jBPM.

Un proceso está definidido a partir de sus nodos, transiciones y acciones:
  • Nodos: Definen los pasos por los que que se tienen que dar durante la ejecución de un proceso, y pueden ser de los siguientes tipos:

    • Nodo de tarea (task-node): Representa una o varias tareas que deben ser ejecutadas por una persona. Cuando la ejecución del proceso llega a un nodo de tarea, se crea una instancia de una tarea (task) en la lista de los miembros del workflow. El nodo se quedará en estado de espera hasta que el usuario informe de la realización de la tarea.
    • Nodo de estado (state): Es un estado de espera que no requiere de la ejecución de ninguna tarea. Permanece a la espera de una señal que lance la continuación del proceso.
    • Nodo de decisión (decision): Una decisión se puede modelar de dos formas distintas, en función de si es el proceso o una entidad externa quien debe tomar la decisión.
      • Si es el propio proceso quien debe tomar la decisión se debe uasr un nodo de decisión. Los criterios de la decisión se pueden especificar añadiendo condiciones a las transiciones, mediante beanshell scripts que devuelven un boolean. Los nodos recorreran sus transiciones examinando las condiciones hasta encontrar la primera transición que cumpla las condiciones. Tambien se pueden expresar condiciones en un DecissionHandler que devuelve la transición a seguir.
      • Si es una entidad externa quien debe tomar la decisión se deberían usar varias transiciones que salgan de un estado de espera. A este estado de espera se le indicará cual es la transición a seguir cuando se relance la ejecución del proceso.
    • Nodo fork y join: Un nodo fork divide la ejecución de un proceso en múltiples ejecuciones concurrentes. Los nodos join recogen todas estas ejecuciones concurrentes antes de continuar con la ejecución del proceso.
    • Nodo node: Permite definir el comportamiento de nuestros propios nodos. Contienen un subelemento Action que se ejecuta cuando el proceso llega al nodo.

  • Transiciones: Van de un nodo a otro e indican el camino a seguir desde un nodo.

  • Acciones: Son código que se va a ajecutar cuando ocurren determinados eventos en la ejecución de un proceso. Principalmente cuando se entra o se sale de un nodo y cuando se toma una transición. Estas acciones no tienen influencia sobre el flujo de control del proceso.

Con estos elementos, veremos en el siguiente post un ejemplo de modelado de un sencillo proceso.

Referencias:
jBPM user guide

Instalación de Jboss Tools

Para crear workflows con los que hacer pruebas es útil contar con una herramienta de modelado gráfico que simplifique las cosas.

Vamos a instalar Jboss Tools, que es un conjunto de herramientas para Eclipse. Entre estas herramientas, Jboss Tools dispone dispone de plugins que proporcionan soporte en Eclipse para Hibernate, JBoss AS, Drools, jBPM, JSF, (X)HTML, Seam, Smooks, JBoss ESB o JBoss Portal, entre otros.

Se puede obtener Jboss Tools de la siguiente dirección:

http://www.jboss.org/tools/download.html

Se puede realizar la descarga de forma directa o seguir las instrucciones para realizar la actualización desde Eclipse.

Una vez instalado Jboss Tools, vamos a crear un proyecto jBPM de ejemplo en Eclipse.

Vamos a File → New Project, desplegamos Jboss jBPM y seleccionamos Process Project. Se introduce el nombre del proyecto, que vamos a llamar Simple. A continuación seleccionamos la ruta el entorno de ejecución de Jboss jBPM, que en nuestro caso se encuentra en C:\java\jbpm. En el siguiente paso nos aseguramos de que está habilitado el check para que cree la definición de proceso de prueba. Por último, pulsamos Finish.

Con esto se debería haber creado un proyecto de ejemplo que tiene una deficinición de un proceso simple en el archivo processdefinition.xml del paquete simple de la carpeta src/main/jpdl. Si el paquete simple estuviese vacío, se pueden recuperar los archivos que se encuentran en la carpeta C:\java\jbpm\examples\src\test\resources\simple y dejarlos en el paquete simple.

Si aparece un error en el proyecto que hace referencia a que falta la librería C:\java\jbpm\lib\activation.jar, debemos arreglarlo yendo al archivo C:\java\jbpm\src\resources\gpd\version.info.xml y eliminar la línea que hace referencia a esta librería. Reiniciamos el Eclipse y el error debería haber desaparecido.

Si se hace doble clic sobre el archivo processdefinition.xml se abrirá el jBPM Graphical Process Designer con la descripción gráfica del proceso de ejemplo:



El siguiente paso a dar es desplegar esta deficinión de proceso en el servidor jBPM para poder trabajar con ella. Para ello vamos a la pestaña deployment y revisamos los datos en el apartado Deployment Server Settings para que coincidan con nuestro servidor de aplicaciones, que deberá estar arrancado. Pulsando en Deploy Process Archive se realizará el despliegue, podemos comprobarlo verificando que esta definición se ha guardado en la tabla JBPM_PROCESSDEFINITION de nuestra base de datos.

Ya tenemos el entorno de trabajo configurado y una definición de proceso desplegada. En próximos posts veremos como trabajar con el API de jBPM sobre este proceso y cómo definir nuevos procesos de forma gráfica.