fév 07

Workflow et Gestion de flux

Tag: Analyse, Architecture, Développement, Idées, Springkarl verger @ 21:54

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

flux.jpg

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

sequenceworkflowmanager.jpeg

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

entities.jpg

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.

workflowmanager.jpg

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

workflow.jpg

Modèle de classe Task

task.jpg

Modèle de classes Function

représente la vue objet des fonctions qui seront executé lors des process.

function.jpg

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 &amp;&amp; workflowDefinition.getFlux()!=null &amp;&amp; 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

package com.karlverger.tutoriel.wf.businessmodel.transitions;

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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; observers.size() > 0 &amp;&amp; observers.contains(observer)) {
            observers.remove(observer);
        }
    }

    public void notifyObservers() {
        if (observers != null &amp;&amp; observers.size() > 0) {
            for (Iterator iter = observers.iterator(); iter.hasNext();) {
                WorkflowObserver observer = (WorkflowObserver) iter.next();
                observer.handleNotification(this);
            }
        }
    }
}
</workflowobserver></workflowobserver>

Task

package com.karlverger.tutoriel.wf.businessmodel.tasks;

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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; observers.size() > 0 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; observers.size() > 0 &amp;&amp; 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

7 Responses to “Workflow et Gestion de flux”

  1. Workflow et Gestion de flux « Java Net says:

    […] 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 […]

  2. nadjette yahiaoui says:

    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.

  3. feedk.blog3k.net says:

    hello…

    exellent…

  4. Sbais says:

    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

  5. karl verger says:

    Salut Sbais, merci pour le commentaire, dis moi ce que tu cherches comme info voir si je peux t’aider

  6. sbais says:

    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

  7. Moncef says:

    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

Leave a Reply