fév 07
Workflow et Gestion de flux
L’objectif est d’appréhender les concepts du workflow, pour ce faire nous allons implémenter un petit projet de test permettant de comprendre les principes.
Pour le projet nous nous appuieront sur les outils suivants: spring, hibernate,jdom et groovy et comme d’habitude le projet maven sera téléchargeable à la fin du tutoriel.
wikipedia : Un workflow est un flux d’informations au sein d’une organisation, comme par exemple la transmission automatique de documents entre des personnes.
On appelle « workflow » (traduisez littéralement « flux de travail ») la modélisation et la gestion informatique de l’ensemble des tâches à accomplir et des différents acteurs impliqués dans la réalisation d’un processus métier (aussi appelé processus opérationnel ou bien procédure d’entreprise). Le terme de « workflow » pourrait donc être traduit en français par « gestion électronique des processus métier ».
De façon plus pratique, le workflow décrit le circuit de validation, les tâches à accomplir entre les différents acteurs d’un processus, les délais, les modes de validation, et fournit à chacun des acteurs les informations nécessaires pour la réalisation de sa tâche.
Le moteur de workflow est le dispositif logiciel permettant d’exécuter une ou plusieurs définitions de workflow. Par abus de langage, on peut appeler ce dispositif logiciel tout simplement “workflow”.
Présentation du Projet
Pour notre projet de test nous allons aborder les concepts suivants : tache,fonction,transition,context d’execution. Je met de côté volontairement la partie gestion des droits et habilitations des utilisateurs pour éviter de complexifier le model.
exemple de flux

le workflow est composé de tache et de transition, un contexte d’exécution est transporté tout au long du processus, ce dernier peut être modifié par les taches, et les transitions pourront accéder à ce contexte, chaque transition est évalué et permet de définir le chemin a suivre dans le flux.
Chaque fois qu’un processus est lancer il est nécessaire de tracer son cycle de vie afin de pouvoir garder une vue du déroulement des opérations ainsi que se donner la possibilité de relancer un process, pour ce faire nous devons logguer chacune de ses instance ainsi que sauvegarder l’historique du chemins (les taches par lesquelles le processus est passé et leur états : READY – PROCESSING – DONE – ERROR - CANCELED) parcouru.
Nous devons aussi sauvegarder l’état du contexte d’exécution pour chaque tache, étant donné que le contexte peut contenir des objets complexes nous pouvons faire le choix de sérialisé tout les objet contenu dans la map du contexte d’exécution via l’api xstream afin de pouvoir garder une trace complète du contexte et réutiliser le context pour rejouer le processus en cas de besoins.
Liste des objets principaux que nous allons devoir gérer pour l’execution et le suivi des process
WorkflowManager mange les processus en cours d’exécution
Workflow manage l’exécution du flux , des tache et gere les transition
Task représente les tache et manage l’exécution des fonctions
Function représente chacune des opérations à effectuer au sein d’une tache
Les diagrammes de classes sont présentés plus bas dans cet article
Vue simplifié de la séquence d’exécution
Le model de données
le workflow sera décris dans un fichier XML, nos taches seront scripté avec groovy histoire de jouer un peu avec ce bel outil ainsi que les transitions et le tout sera stocké dans une table afin de pouvoir avoir un référentiel
Tout d’abord il vous faut créer la base mysql WORKFLOW, pour rappel le projet de test pointe sur une base local avec les login et password root/root
table WORKFLOW_DEFINITION
CREATE TABLE `workflow`.`WORKFLOW_DEFINITION` (
`WORKFLOW_DEFINITION_ID` bigint(20) NOT NULL auto_increment,
`NAME` varchar(255) default NULL,
`FLUX` text,
`TRANSITIONS` text,
PRIMARY KEY (`WORKFLOW_DEFINITION_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
Pour sauvegarder toutes les instances des flux ainsi que leur états (READY – PROCESSING – DONE – ERROR – CANCELED) nous utiliserons la table suivantes :
table WORKFLOW_INSTANCE
CREATE TABLE `workflow`.`WORKFLOW_INSTANCE` (
`WORKFLOW_INSTANCE_ID` bigint(20) NOT NULL auto_increment,
`DATES` datetime default NULL,
`ID_INSTANCE` bigint(20) default NULL,
PRIMARY KEY (`WORKFLOW_INSTANCE_ID`),
KEY `FKC73B35154C97409A` (`ID_INSTANCE`),
CONSTRAINT `FKC73B35154C97409A` FOREIGN KEY (`ID_INSTANCE`) REFERENCES `WORKFLOW_DEFINITION` (`WORKFLOW_DEFINITION_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=286 DEFAULT CHARSET=latin1
pour sauvegarder l’historique du chemins (les taches par lesquelles le processus est passé et leur états : READY – PROCESSING – DONE – ERROR - CANCELED) parcouru dans une table
table WORKFLOW_HISTORY_ACTIONS
CREATE TABLE `workflow`.`WORKFLOW_HISTORY_ACTIONS` (
`WORKFLOW_HISTORY_ACTIONS_ID` bigint(20) NOT NULL auto_increment,
`ID_TASK` bigint(20) default NULL,
`ID_USER` varchar(255) default NULL,
`DATES` datetime default NULL,
`STATES` varchar(255) default NULL,
`WORKFLOW_INSTANCE_ID` bigint(20) default NULL,
PRIMARY KEY (`WORKFLOW_HISTORY_ACTIONS_ID`),
KEY `FK36EAF79257EACA8` (`WORKFLOW_INSTANCE_ID`),
CONSTRAINT `FK36EAF79257EACA8` FOREIGN KEY (`WORKFLOW_INSTANCE_ID`) REFERENCES `WORKFLOW_INSTANCE` (`WORKFLOW_INSTANCE_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=190 DEFAULT CHARSET=latin1
pour sauvegarder l’état du contexte d’exécution pour chaque tache (les paramètre du contexte d’exécution qui doivent être loggué son définie dans la table WORKFLOW_HISTORY_ACTIONS_CONTEXT), étant donné que le contexte peut contenir des objet complexe nous avons décidé de sérialisé tout les objet contenu dans la map du contexte d’exécution via l’api xstream afin de pouvoir garder une trace complète du contexte.
table WORKFLOW_HISTORY_ACTIONS_CONTEXT
CREATE TABLE `workflow`.`WORKFLOW_HISTORY_CONTEXT` (
`WORKFLOW_HISTORY_CONTEXT_ID` bigint(20) NOT NULL auto_increment,
`KEY_LABEL` varchar(255) default NULL,
`KEY_TYPE` varchar(255) default NULL,
`KEY_VALUE` longtext,
`DATES` datetime default NULL,
`WORKFLOW_HISTORY_ACTIONS_ID` bigint(20) default NULL,
PRIMARY KEY (`WORKFLOW_HISTORY_CONTEXT_ID`),
KEY `FKB4E1E4448CB161C4` (`WORKFLOW_HISTORY_ACTIONS_ID`),
CONSTRAINT `FKB4E1E4448CB161C4` FOREIGN KEY (`WORKFLOW_HISTORY_ACTIONS_ID`) REFERENCES `WORKFLOW_HISTORY_ACTIONS` (`WORKFLOW_HISTORY_ACTIONS_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=latin1
Passons à la description de notre workflow, comme expliquer au début de l’article nous déclarons notre workflow dans un fichier xml, ce dernier sera stocker dans notre table WORKFLOW_DEFINITION. Exemple :
<!–
Ce fichier permet de définir un workflow
les taches sont codé avec groovy cela ammene une grande puissance et soupless
car nous pouvons codé de manière dynamique via le scripting et on peux
s’appuyer sur des services déja existant, que du bonheur koi
–>
<workflow>
<tasks>
<task id="1" name="Tache avec function groovy numero 1">
<functions>
<function type="groovy">
<script>
<![CDATA[
println "Function groovy commence"
println executionContext.testParam
executionContext.testParam = 9.5
println executionContext.testParam
println "Function groovy terminer"
]]>
</script>
</function>
</functions>
</task>
<task id="2" name="Tache avec function groovy numero 2">
<functions>
<function type="groovy">
<pathurls>
<url>
<!–[CDATA[
file:////home/ubuntu/Documents/repository/mysql/mysql-connector-java/5.1.5/mysql-connector-java-5.1.5.jar
]]–>
</url>
</pathurls>
<script>
<![CDATA[
println "voila une fonction interessante elle rajoute au path d\\\\’excution des jar et les consomme, si c pas magic tout ca!!!"
import groovy.sql.Sql
def driver="com.mysql.jdbc.Driver";
def sql = Sql.newInstance("jdbc:mysql://localhost:3306/workflow", "root","root", driver)
sql.execute("create table DEPARTEMENT (CODE VARCHAR(3), LIBELLE VARCHAR(100))")
executionContext.testParam=12
]]>
</script>
</function>
</functions>
</task>
<task id="3" name="tache numero 3">
<functions>
<function type="groovy">
<script>
<![CDATA[
println "fonction groovy ajoutant un element au context d\\\\’\\\\’excution"
executionContext.poi="test"
println executionContext.poi
]]>
</script>
</function>
</functions>
</task>
</tasks>
</workflow>
Notre descripteur de transition
<!–
exemple de fichier de transition
les transitions se code en groovy
chaque transition se doit de définir :
id ->
identifiant de la transition dans le systeme
from -> identifiant de la tache d’origine dans le systeme
to -> identifiant de la tache de destination dans le systeme
–>
<transitions>
<transition id="1" from="1" to="2"><rule><!–[CDATA[
testParam = executionContext.testParam
condition = false
if (testParam <= 10){
condition = true
}else{
condition = false
}
return condition
]]–></rule>
</transition>
<transition id="2" from="2" to="3"><rule><!–[CDATA[
testParam = executionContext.testParam
condition = false
if (testParam >
10){
condition = true
}else{
condition = false
}
return condition
]]–></rule>
</transition>
<transition id="3" from="1" to="3"><rule><!–[CDATA[
testParam = executionContext.testParam
condition = false
if (testParam >
20){
condition = true
}else{
condition = false
}
return condition
]]–></rule>
</transition></transitions>
lors de l’instanciation du workflow nous parsons le fichier XML afin de définir un objet « Workflow » composé des « Task » et « Transition », cela nous permettra de parcourir le processus et de l’executer.
Chacune des taches peuvent contenir plusieurs fonctions, une tache peut etre percues comme atomique dans le sens ou si une des fonctions la composant est en échec la tache doit être en echec.
En ce qui concerne le code du projet il n’est la que pour présenter les concepts et en aucun cas optimser pour une utilisation, il serait par example interressant de passer les workflows et les taches en Callable ainsi que de deleguer les traitemement aux états. Mais bon je vous laisse le plaisir d’implémenter les pattern state, delegate et les threads.
Modèle de classe des entités
Ces classes permettent de mapper la base de données
Modèle de classe WorkflowManager
Le workflowManager s’occupe de récupérer les définitions des process en base et lance les processus de construction des taches et transitions puis gère l’execution et le cycle de vie des workflows.
Modèle de classes Workflow
Représente la vue objet des workflows et observe les événements et états des taches pour en assurer l’audit
Modèle de classe Task
Modèle de classes Function
représente la vue objet des fonctions qui seront executé lors des process.
Je vais présenter les parties des sources qui sont intéressantes, pour le reste vous les trouverez dans le projet de test :
Le WorkflowManager, est notre point d’entré il s’occupe de récupérer la définition du workflow que nous voulons lancer, le construit et l’execute, pour ce faire il s’appuie sur TaskHelper et TransitionHelper qui s’occupe respectivement de parser les définitions XML et de les transformer en Task et Transition.
* WorkflowManager.java
*
* Created on 24 ao�t 2007, 10:31
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.karlverger.tutoriel.wf.businessmodel;
import com.karlverger.tutoriel.wf.businessmodel.exceptions.WorkflowException;
import com.karlverger.tutoriel.wf.businessmodel.tasks.TaskHelper;
import com.karlverger.tutoriel.wf.businessmodel.transitions.TransitionHelper;
import com.karlverger.tutoriel.wf.dao.WorkflowDefinitionDAO;
import com.karlverger.tutoriel.wf.entities.WorkflowDefinition;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
*
* @author kvr
*/
public class WorkflowManager implements IWorkflowManager,WorkflowObserver
{
private ExecutorService workflowExecutor;
private WorkflowDefinitionDAO workflowDefintionDAO;
private int poolSize;
private Map workflows = new HashMap();//maintient la liste des process en cours d’execution
public static ApplicationContext applicationContext;
static{
applicationContext=new ClassPathXmlApplicationContext("applicationContext-hibernate.xml");
}
public WorkflowManager(int poolSize)
{
this.poolSize=poolSize;
workflowExecutor = Executors.newFixedThreadPool(this.poolSize);
}
public WorkflowDefinitionDAO getWorkflowDefinitionDAO() {
return getWorkflowDefintionDAO();
}
public void setWorkflowDefinitionDAO(WorkflowDefinitionDAO workflowDefinitionDAO) {
this.setWorkflowDefintionDAO(workflowDefinitionDAO);
}
public void launch(String worlflowName,Map executionContext)
{
WorkflowDefinition workflowDefinition = getWorkflowDefintionDAO().getByName(worlflowName);
if(workflowDefinition!=null && workflowDefinition.getFlux()!=null && workflowDefinition.getTransition()!=null)
{
try {
Workflow workflow1 = new Workflow();
workflow1.setName(workflowDefinition.getName());
workflow1.setTasks(TaskHelper.getTasksFromXML(workflowDefinition.getFlux(), workflow1));
workflow1.setTransitions(TransitionHelper.getTransitionFromXML(workflowDefinition.getTransition()));
workflow1.setExecutionContext(executionContext);
workflow1.addObserver(this);
workflow1.execute();
workflows.put(workflow1, workflow1);
} catch (WorkflowException ex) {
Logger.getLogger(WorkflowManager.class.getName()).log(Level.SEVERE, null, ex);
}
}else{
System.err.println("Une erreur lors de la r�cup�ration de la d�finition du workflow est survenue");
}
}
public WorkflowDefinitionDAO getWorkflowDefintionDAO() {
return workflowDefintionDAO;
}
public void setWorkflowDefintionDAO(WorkflowDefinitionDAO workflowDefintionDAO) {
this.workflowDefintionDAO = workflowDefintionDAO;
}
public void handleNotification(Workflow workflow) {
if(workflow.getCurrentState() instanceof com.karlverger.tutoriel.wf.businessmodel.StateDone)
{
System.err.println("Workflow terminer : "+workflow.getName());
Workflow t = (Workflow)workflows.get(workflow);
workflows.remove(workflow);
t = null;
workflow = null;
}
}
}
TaskHelper
* TaskHelper.java
*
* Created on 27 ao�t 2007, 12:06
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.karlverger.tutoriel.wf.businessmodel.tasks;
import com.karlverger.tutoriel.wf.businessmodel.Workflow;
import com.karlverger.tutoriel.wf.businessmodel.functions.Function;
import com.karlverger.tutoriel.wf.businessmodel.functions.FunctionGroovy;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
/**
*
* @author kvr
*/
public class TaskHelper {
/** Creates a new instance of TaskHelper */
public TaskHelper() {
}
public static List getTasksFromXML(InputStream taskDefintion,Workflow workflow)
{
SAXBuilder sxb = new SAXBuilder();
Document pDoc=null;
try {
pDoc = sxb.build(taskDefintion);
} catch (IOException ex) {
ex.printStackTrace();
} catch (JDOMException ex) {
ex.printStackTrace();
}
return getTasksFromXML(pDoc,workflow);
}
public final static List getTasksFromXML(String taskDefintion,Workflow workflow)
{
SAXBuilder sxb = new SAXBuilder();
Document pDoc =null;
try
{
pDoc = sxb.build(new StringReader(taskDefintion));
}catch (JDOMException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
return getTasksFromXML(pDoc,workflow);
}
private static List getTasksFromXML(Document pDoc,Workflow workflow)
{
List tasks = new ArrayList();
Element workflowElement = pDoc.getRootElement();
Element elementTasks = workflowElement.getChild("tasks");
//Recuperation de tes taches du workflow
List listTasks = elementTasks.getChildren("task");
for (Iterator iter = listTasks.iterator(); iter.hasNext();)
{
//Creation des taches
Element elementTask = (Element) iter.next();
Task task = new Task();
task.setWorkflow(workflow);
//si l’identifiant de la tache est egal a 1 alors nous la definisson comme la tache initial du processus
if(elementTask.getAttributeValue("id").equalsIgnoreCase("1"))
{
task.setInitialTask(true);
}
task.setId(new Integer(elementTask.getAttributeValue("id")));
task.setName(elementTask.getAttributeValue("name"));
//Recuperation des functions de la tache
Element elementFunctions = elementTask.getChild("functions");
List listFunctions = elementFunctions.getChildren("function");
for (Iterator iterator = listFunctions.iterator(); iterator.hasNext();)
{
//Creation des functions
Element elementFunction = (Element) iterator.next();
if(elementFunction.getAttributeValue("type").equalsIgnoreCase("groovy"))
{
Function functionGroovy = new FunctionGroovy();
functionGroovy.setType("groovy");
functionGroovy.setScript(elementFunction.getChildText("script"));
/**
* traitement des path optionnel fournie par le scripteur
* dans le fichier de définition xml
*/
Element elementPathUrl = elementFunction.getChild("pathurls");
if(elementPathUrl!=null){
List<element> urls = elementPathUrl.getChildren("url");
for (Element elementUrl : urls) {
String url = elementUrl.getText();
if(url!=null)
functionGroovy.addPathUrl(url);
}
}
task.addFunction(functionGroovy);
}
}
tasks.add(task);
}
return tasks;
}
}
</element>
TransitionHelper
import com.karlverger.tutoriel.wf.util.XMLUtil;
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.sql.Clob;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* classe permettant de transformer un flux XML
* en une liste de <code>Transition</code>
* @author karl
*
*/
public class TransitionHelper {
/**
* Transforme un flux xml en Transition.
*
* @param ptransition flux XML
*
* @return Transition
*
* @throws Exception erreur
*/
public final static List getTransitionFromXML(InputStream ptransition) throws Exception {
if (ptransition != null) {
try {
DocumentBuilderFactory vDbf = DocumentBuilderFactory.newInstance();
vDbf.setNamespaceAware(true);
DocumentBuilder vDb;
vDb = vDbf.newDocumentBuilder();
Document vDoc = vDb.parse(ptransition);
return getTransitionFromXML(vDoc);
} catch (Exception vException) {
throw new Exception("Erreur de parsing du flux XML " + ptransition + " : " + vException.getMessage());
}
} else {
return new ArrayList();
}
}
/**
* Transforme un flux xml en Transition.
*
* @param ptransition flux XML
*
* @return Transition
*
* @throws Exception erreur
*/
public final static List getTransitionFromXML(Clob ptransition) throws Exception {
if (ptransition != null && ptransition.length() > 0) {
try {
DocumentBuilderFactory vDbf = DocumentBuilderFactory.newInstance();
vDbf.setNamespaceAware(true);
DocumentBuilder vDb;
vDb = vDbf.newDocumentBuilder();
Document vDoc = vDb.parse(ptransition.getAsciiStream());
return getTransitionFromXML(vDoc);
} catch (Exception vException) {
throw new Exception("Erreur de parsing du flux XML " + ptransition + " : " + vException.getMessage());
}
} else {
return new ArrayList();
}
}
/**
* Transforme un flux xml en Transition.
*
* @param pTransition flux XML
*
* @return Transition
*
* @throws Exception erreur
*/
public final static List getTransitionFromXML(String pTransition) {
List transitions = null;
if (pTransition != null && pTransition.length() > 0) {
try {
DocumentBuilderFactory vDbf = DocumentBuilderFactory.newInstance();
StringBufferInputStream vInputStream = new StringBufferInputStream(pTransition);
vDbf.setNamespaceAware(true);
DocumentBuilder vDb;
vDb = vDbf.newDocumentBuilder();
Document vDoc = vDb.parse(vInputStream);
transitions = getTransitionFromXML(vDoc);
return transitions;
} catch (Exception vException) {
System.err.println("Erreur de parsing du flux XML " + pTransition + " : " + vException.getMessage());
}
} else {
transitions = new ArrayList();
return transitions;
}
return transitions;
}
/**
* Transforme un flux xml en Transition.
*
* @param pDoc Document XML
*
* @return Transition
*
* @throws Exception erreur
*/
private final static List getTransitionFromXML(Document pDoc) {
final List vNewTransitions = new ArrayList();
// it�ration sur les transitions
Element vRoot = (Element) pDoc.getElementsByTagName("transitions").item(0);
List vTransitions = XMLUtil.getChildElements(vRoot, "transition");
for (Iterator vIterTransitions = vTransitions.iterator(); vIterTransitions.hasNext();) {
// transition courante
Element vTransition = (Element) vIterTransitions.next();
String vId = vTransition.getAttribute("id");
int vFrom = Integer.parseInt(vTransition.getAttribute("from"));
int vTo = Integer.parseInt(vTransition.getAttribute("to"));
Transition vNewTransition = new Transition();
vNewTransition.setId(vId);
vNewTransition.setFrom(vFrom);
vNewTransition.setTo(vTo);
// it�ration sur les r�gles
// => une seule normalement
List vRules = XMLUtil.getChildElements(vTransition, "rule");
if (vRules!=null && vRules.size() > 0) {
Element vRule = (Element) vRules.get(0);
Node vFirstChild = vRule.getFirstChild();
if (vFirstChild.getNodeType() == Node.CDATA_SECTION_NODE) {
vNewTransition.setRule(((org.apache.xerces.dom.CDATASectionImpl) vFirstChild).getData());
} else {
vNewTransition.setRule(vFirstChild.getNodeValue());
}
}
vNewTransitions.add(vNewTransition);
}
return vNewTransitions;
}
}
Workflow
* Workflow.java
*
* Created on 24 ao�t 2007, 10:36
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.karlverger.tutoriel.wf.businessmodel;
import com.karlverger.tutoriel.wf.businessmodel.exceptions.TaskException;
import com.karlverger.tutoriel.wf.businessmodel.exceptions.WorkflowException;
import com.karlverger.tutoriel.wf.businessmodel.tasks.Task;
import com.karlverger.tutoriel.wf.businessmodel.tasks.TaskObserver;
import com.karlverger.tutoriel.wf.businessmodel.transitions.Transition;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
/**
* cette représente un workflow, elle s’ocupe d’executer et de faire transiter les
* tache en fonction de leur état.
* @author karl
*/
public class Workflow implements TaskObserver, WorkflowObservable {
private Integer idFlux;
private Long idInstanceFlux;
private List tasks;
private List transitions;
private String name;
private Map executionContext;
private List<workflowobserver> observers;
private com.karlverger.tutoriel.wf.businessmodel.StateReady stateReady = new com.karlverger.tutoriel.wf.businessmodel.StateReady();
private com.karlverger.tutoriel.wf.businessmodel.StateProcessing stateProcessing = new com.karlverger.tutoriel.wf.businessmodel.StateProcessing();
private com.karlverger.tutoriel.wf.businessmodel.StateDone stateDone = new com.karlverger.tutoriel.wf.businessmodel.StateDone();
private com.karlverger.tutoriel.wf.businessmodel.StateError stateError = new com.karlverger.tutoriel.wf.businessmodel.StateError();
private com.karlverger.tutoriel.wf.businessmodel.State currentState = new com.karlverger.tutoriel.wf.businessmodel.StateReady();
private AuditTrail auditTrail = (AuditTrail) WorkflowManager.applicationContext.getBean("auditTrail");
public State getCurrentState() {
return currentState;
}
public void setCurrentState(State currentState) {
this.currentState = currentState;
notifyObservers();
}
public Map getExecutionContext() {
return executionContext;
}
public void setExecutionContext(Map executionContext) {
this.executionContext = executionContext;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getTasks() {
return tasks;
}
public void setTasks(List tasks) {
this.tasks = tasks;
}
public List getTransitions() {
return transitions;
}
public void setTransitions(List transitions) {
this.transitions = transitions;
}
public Integer getIdFlux() {
return idFlux;
}
public void setIdFlux(Integer idFlux) {
this.idFlux = idFlux;
}
public Long getIdInstanceFlux() {
return idInstanceFlux;
}
public void setIdInstanceFlux(Long idInstanceFlux) {
this.idInstanceFlux = idInstanceFlux;
}
public Workflow execute() throws WorkflowException {
this.setIdInstanceFlux( auditTrail.createInstance(this) );
//tache servant de prédicat pour la recherche de la tache initial
Task initialTaskPredicat = new Task();
initialTaskPredicat.setId(new Integer(1));
//recuperation de la tache initial et execution
tasks = this.getTasks();
int indexOfInitialTask = tasks.indexOf(initialTaskPredicat);
if (indexOfInitialTask != -1) {
Task taskInitial = (Task) tasks.get(indexOfInitialTask);
taskInitial.addObserver(this);
try {
setCurrentState(stateProcessing);
taskInitial.execute();
} catch (TaskException e) {
e.printStackTrace();
setCurrentState(stateError);
}
} else {
setCurrentState(stateError);
throw new WorkflowException("le workflow : " + this.getName() + " ne possede pas de tache initial");
}
return this;
}
public boolean isFinalTask(Task task){
boolean isFinal = false;
return isFinal;
}
public void handleNotification(Task task) {
/**
* s’occupe de tracer en base les étapes et le context d’execution
*/
auditTrail.createWorkflowHistoryAction(task);
/**
* s’occupe de prendre les décisions en fonction de l’état de la
* tache, par exemple elle s’occupe defaire transiter le workflow
* si la tache s’est déroulé correctement et est en état done
*/
if (task.getState() instanceof com.karlverger.tutoriel.wf.businessmodel.tasks.StateReady) {
System.err.println(task.getName() + " est Prète");
} else if (task.getState() instanceof com.karlverger.tutoriel.wf.businessmodel.tasks.StateProcessing) {
System.err.println(task.getName() + " est en cours de process");
} else if (task.getState() instanceof com.karlverger.tutoriel.wf.businessmodel.tasks.StateDone) {
System.err.println(task.getName() + " est en transite");
transite(task);
} else if (task.getState() instanceof com.karlverger.tutoriel.wf.businessmodel.tasks.StateError) {
System.err.println(task.getName() + " est en erreur");
}
}
/**
* cette methode permet d’evaluer les transitions
* atache a une tache, elle est appelé a chaque fois qu’une tache
* se termine en etat Done (StatDone)
*/
public void transite(Task task) {
System.err.println("On évalue les transitions pour la tache suivante : "+task.getName());
/**
* on itere sur les transition et lorsque l’on rencontre
* une transition correspondante a la tache courrante on l’evalue
*/
for (Iterator iter = this.getTransitions().iterator(); iter.hasNext();)
{
Transition transition = (Transition) iter.next();
if (transition.getFrom() == task.getId())
{
System.err.println("transition.getFrom() : " + transition.getFrom());
//ici nous devons evaluer la transition
GroovyShell shell = new GroovyShell();
Binding binding = new Binding();
binding.setVariable("executionContext", this.getExecutionContext());
try {
Script script = shell.parse(transition.getRule());
script.setBinding(binding);
Boolean retour = (Boolean) script.run();
if (retour.booleanValue())
{
Task taskPredicat = new Task();
taskPredicat.setId(new Integer(transition.getTo()));
//recuperation de la tache suivante et execution
int indexOfNextTask = this.getTasks().indexOf(taskPredicat);
if (indexOfNextTask != -1)
{
Task nextTask = (Task) this.getTasks().get(indexOfNextTask);
nextTask.addObserver(this);
System.err.println("EXECUTION DE LA TACHE : " + nextTask.getName());
try {
nextTask.execute();
} catch (TaskException e) {
setCurrentState(stateError);
e.printStackTrace();
}
}
break;
} else {
System.err.println("Aucune transition valide");
}
} catch (Exception e) {
setCurrentState(stateError);
e.printStackTrace();
}finally{
shell = null;
}
}
}
return;
}
public void addObserver(WorkflowObserver observer) {
if (observers == null) {
observers = new ArrayList<workflowobserver>();
}
observers.add(observer);
}
public void removeObserver(WorkflowObserver observer) {
if (observers != null && observers.size() > 0 && observers.contains(observer)) {
observers.remove(observer);
}
}
public void notifyObservers() {
if (observers != null && observers.size() > 0) {
for (Iterator iter = observers.iterator(); iter.hasNext();) {
WorkflowObserver observer = (WorkflowObserver) iter.next();
observer.handleNotification(this);
}
}
}
}
</workflowobserver></workflowobserver>
Task
import com.karlverger.tutoriel.wf.businessmodel.Workflow;
import com.karlverger.tutoriel.wf.businessmodel.exceptions.FunctionException;
import com.karlverger.tutoriel.wf.businessmodel.exceptions.TaskException;
import com.karlverger.tutoriel.wf.businessmodel.functions.Function;
import com.karlverger.tutoriel.wf.businessmodel.functions.FunctionObserver;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* représente une tache et ses états
* Dans le cadre d’un projet réel il serait interressant de déléguer
* les tratiement au états. ce qui n’est pas le cas ici.
* @author karl
*
*/
public class Task implements TaskObservable, FunctionObserver {
private List<taskobserver> observers;
private String name;
private Integer id;
private List functions;
private boolean initialTask;
private Workflow workflow;
private StateReady stateReady = new StateReady();
private StateProcessing stateProcessing = new StateProcessing();
private StateDone stateDone = new StateDone();
private StateError stateError = new StateError();
private State currentState = new StateReady();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public State getState() {
return this.currentState;
}
public void setState(State state) {
this.currentState = state;
//on notifie les observateur de la tache que celle ci vien de changer d’etat
notifyObservers();
}
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public void addFunction(Function function) {
if (functions == null) {
functions = new ArrayList();
}
function.setTask(this);
functions.add(function);
}
public void removeFunction(Function function) {
if (functions != null && functions.contains(function)) {
functions.remove(function);
}
}
public List getFunctions() {
return functions;
}
/**
* abonne un nouvel observateur pour ecouter la tache
*/
public void addObserver(TaskObserver observer) {
if (observers == null) {
observers = new ArrayList<taskobserver>();
}
observers.add(observer);
}
/**
* notifie les observateur de la tache
*/
public void notifyObservers() {
System.err.println("Nombre d’observateur sur la tache : "+observers.size());
if (observers != null && observers.size() > 0) {
for (Iterator iter = observers.iterator(); iter.hasNext();) {
TaskObserver observer = (TaskObserver) iter.next();
observer.handleNotification(this);
}
}
}
/**
* desabonne un observateur
*/
public void removeObserver(TaskObserver observer) {
if (observers != null && observers.size() > 0 && observers.contains(observer)) {
observers.remove(observer);
}
}
public boolean isInitialTask() {
return initialTask;
}
public void setInitialTask(boolean initialTask) {
this.initialTask = initialTask;
}
public void execute() throws TaskException {
System.err.println("état de la tache lors de sa demande d’execution : "+this.getState().getName());
this.setState(stateProcessing);
/**
* Recuperation et traitement des functions
* associees a� la tache et execution de ces functions
*/
for (Iterator iterator = this.getFunctions().iterator(); iterator.hasNext();) {
Function function = (Function) iterator.next();
/**
* execution de la fonction décrite
* dans la description XML du flux.
* la fonctoin peut acceder a la <code>Map</code> executionContext
*/
try {
function.addObserver(this);
function.execute();
} catch (FunctionException e) {
this.setState(stateError);
e.printStackTrace();
}
this.setState(stateDone);
}
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((id == null) ? 0 : id.hashCode());
return result;
}
/**
* Définit que Task est egal à une autre si elle
* possede le meme identifiant
* @Override
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Task other = (Task) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
public Workflow getWorkflow() {
return workflow;
}
public void setWorkflow(Workflow workflow) {
this.workflow = workflow;
}
public void handleNotification(Function function) {
System.err.println("Task, handleNotification de function : " + function.getScript());
}
}
</taskobserver></taskobserver>
FunctionGroovy
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.karlverger.tutoriel.wf.businessmodel.functions;
import com.karlverger.tutoriel.wf.businessmodel.exceptions.FunctionException;
import com.karlverger.tutoriel.wf.businessmodel.tasks.Task;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* cette Classe s’occuper d’executer une fonction scripter en groovy
* dans les taches du workflow
* @author karl
*/
public class FunctionGroovy implements Function {
private List<functionobserver> observers;
private String script;
private String type;
private Task task;
private List<string> pathUrls;
public Task getTask() {
return task;
}
public void setTask(Task task) {
this.task = task;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
/**
* execute le script groovy de cette fonction
*/
public void execute() throws FunctionException {
GroovyShell shell = new GroovyShell();
/**
* ajout des libraire nécéssaire à l’execution du script groovy
* ces dernieres sont rajoutées par le scripteur dans le fichier xml
*/
if(pathUrls!=null){
for (String url : pathUrls) {
try {
shell.getClassLoader().addURL(new URL(url));
} catch (MalformedURLException ex) {
Logger.getLogger(FunctionGroovy.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Script script = shell.parse(this.getScript());
Binding binding = new Binding();
binding.setVariable("executionContext", this.getTask().getWorkflow().getExecutionContext());
script.setBinding(binding);
Object retour = script.run();
shell = null;
script = null;
}
public void addObserver(FunctionObserver observer) {
if (observers == null) {
observers = new ArrayList<functionobserver>();
}
observers.add(observer);
}
public void notifyObservers() {
if (observers != null && observers.size() > 0) {
for (Iterator iter = observers.iterator(); iter.hasNext();) {
FunctionObserver observer = (FunctionObserver) iter.next();
observer.handleNotification(this);
}
}
}
public void removeObserver(FunctionObserver observer) {
if (observers != null && observers.size() > 0 && observers.contains(observer)) {
observers.remove(observer);
}
}
public String getScript() {
return this.script;
}
public void setScript(String arg) {
this.script=arg;
}
public void addPathUrl(String url) {
if(this.pathUrls==null)
this.pathUrls = new ArrayList<string>();
this.pathUrls.add(url);
}
public List<string> getPaths() {
return this.pathUrls;
}
}
</string></string></functionobserver></string></functionobserver>
Voila pour la présentation, vous pouvez télécharger le projet maven complet ici







février 12th, 2008 at 16:21
[…] et Gestion de flux This entry was written by admin. Bookmark the permalink. Follow any comments here with the RSS feed for this post.Content related […]
mai 8th, 2008 at 8:58
Bonjour, je trouve votre programme interessant, je souhaite avoir plus d’informations sur le code sources plus d’explications pour mieux comprendre votre project, merci inffiniment.
août 11th, 2008 at 2:22
hello…
exellent…
novembre 13th, 2008 at 20:42
Bravoo
en fait je suis sur un projet de réalisation de moteur workflow les informations m’intéressent bcp
et je souhaite avoir plus d’info si c’est possible
merci
novembre 13th, 2008 at 23:02
Salut Sbais, merci pour le commentaire, dis moi ce que tu cherches comme info voir si je peux t’aider
janvier 21st, 2009 at 15:56
Bonsoir karl
dsl pour le retard en fait je voudrait réaliser un outil BPM (workflow) pour mon projet de fin d’étude
j’essaye de trouver des informations sur comment je dois m’y prendre
et surtout les normes XPDL…
merci
février 24th, 2010 at 16:26
salut
merci pour le tuto c tres interessant
on m a confié de faire un workflow pour tracer le circuit du deroulement de la mise en place des actions proposées par la veille en vulnerabilité de securité
mais je ne sé par quoi commencer
merci pour ton aide