Framework Activiti (Java) - description du flux de tâches XML (bpm) et de sa gestion. Je décrirai ici les concepts de base et comment construire des processus métier simples.
Le concept principal d'Activiti est un processus et une tâche. Un processus est l'ensemble des tâches interconnectées par des flux et des branches dirigés.
Je vais aborder ces aspects:
- - Activiti dans sa forme la plus pure
- - Utilisateurs, rôles
- - Connecter SpringBoot
- - API REST
- - Emploi et délégué
Le mouvement des flux se fait par étapes d'une tâche à l'autre, chacune de ces étapes suspend le processus en attendant la saisie et la fin de la tâche, toutes les actions intermédiaires sont stockées dans la base de données.
Où, quoi prendre, je vais l'indiquer ci-dessous. Commençons par un exemple simple - le processus de développement de programme, qui consiste à écrire du code et à tester. Voici un diagramme de processus.

Tout cela est un processus, il a un ID, un nom et d'autres caractéristiques.

Il a:
Le début du processus, deux tâches «Développer» et «Tester», une branche (passerelle) et la fin du processus. En mots, tout se passe comme ceci:
- chargement de la description du bpm
- démarrer le processus
- après le début, nous tombons immédiatement dans la tâche de développement
- après l'exécution de Develop, il passe en test et selon le résultat du test, le processus se termine ou revient au développement.
Activiti se compose d'un ensemble de services
En voici les principaux:
- RepositoryService: contrôle le chargement des descriptions de processus
- RuntimeService: démarre les processus
- TaskService: effectue des tâches
- FormService: accès aux variables de tâches
- HistoryService: accès à l'historique des processus
- IdentityService: utilisateurs et rôles
Activiti dans sa forme la plus pure
Mais tout commence par la configuration et le fichier - activiti.cfg.xml.
De cela
ProcessEngineConfiguration cfg = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource("activiti.cfg.xml");
Si vous n'utilisez pas votre configuration, Activiti déploiera la base de données dans la mémoire H2 elle-même, cela ne me convient pas, mais mon cher Oracle est plein, il existe des options pour connecter différentes bases de données.
Voici ma configuration
activiti.cfg.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:xe" /> <property name="jdbcDriver" value="oracle.jdbc.driver.OracleDriver" /> <property name="jdbcUsername" value="BPM" /> <property name="jdbcPassword" value="1" /> <property name="databaseSchemaUpdate" value="false" /> <property name="asyncExecutorActivate" value="false" /> <property name="mailServerPort" value="5025" /> </bean> </beans>
Nous modifions les valeurs dans "nom de la propriété = jdbc *" et connectons une autre base de données
Structure du projet

POM <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>DemoActiviti</groupId> <artifactId>DemoActiviti</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.activiti</groupId> <version>6.0.0</version> <artifactId>activiti-spring-boot-starter-integration</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4.1</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>com.example.DemoActiviti</mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
La présence du plugin "maven-assembly-plugin" dans le POM vous permettra de construire (empaqueter) un jar qui sera lancé avec des dépendances et exécuté -
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar
pilote jdbc pour Oracle installé dans le référentiel maven local
mvn install:install-file -Dfile={Path/to/your/ojdbc6.jar} -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0 -Dpackaging=jar
log4j log4j.rootLogger=WARN, ACT log4j.appender.ACT=org.apache.log4j.ConsoleAppender log4j.appender.ACT.layout=org.apache.log4j.PatternLayout log4j.appender.ACT.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n
Pour ce processus, nous définissons 4 actions: chargement bpm, démarrage du processus, développement et test. Chaque action aura un paramètre correspondant: déployer, démarrer, développer, tester.
Nous prenons des scripts pour une base de données de
activiti-get-startedlà dans le dossier \ activiti-6.0.0 \ activiti-6.0.0 \ database \ create - scripts pour créer une base de données
Utilisateurs, rôles
Préparez les utilisateurs et les rôles:
Identité public class DemoActiviti { private static final String DEV_PROCESS = "devProcess"; public static void main(String[] args) { Locale.setDefault(Locale.ENGLISH); ProcessEngineConfiguration cfg = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource("activiti.cfg.xml"); ProcessEngine processEngine = cfg.buildProcessEngine(); createIdentity(processEngine, "programmer", "programmers"); createIdentity(processEngine, "tester", "testers"); } public static void createIdentity(ProcessEngine processEngine, String userName, String userGroup) { IdentityService identityService = processEngine.getIdentityService(); String userId = userName + "Id"; if (identityService.createUserQuery().userId(userId).count() == 0) { User user = identityService.newUser(userName); user.setId(userId); user.setEmail(userName + "@gmail.com"); identityService.saveUser(user); System.out.println("user created success fully"); } String groupId = userGroup + "Id"; if (identityService.createGroupQuery().groupId(groupId).count() == 0) { Group group = identityService.newGroup(userGroup); group.setName(userGroup); group.setId(groupId); identityService.saveGroup(group); System.out.println("group created success fully"); } if (identityService.createGroupQuery().groupId(groupId).list().size() > 0) { identityService.createMembership(userId, groupId); System.out.println("user to group success fully"); } } }
Créez respectivement des utilisateurs et des groupes, développeur et testeur.
Dans la base de données, toutes les tables sont divisées par les services correspondants et ont des préfixes
ACT_RE_ *: référentiel.
ACT_RU_ *: runtime.
ACT_ID_ *: identité.
ACT_HI _ *: historique
et ainsi de suite
Après avoir créé des utilisateurs à partir de, vous pouvez voir ici

Nous assignerons nos tâches dans la description aux groupes correspondants (CandidateGroup), par exemple, la tâche Développer au groupe - programmeurs

Et donc la première chose que nous faisons est de placer dans la base de données "MyProcess.bpmn", exécutez le programme avec la commande
deploy java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar deploy
ensuite, démarrez le processus de démarrage
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar start
Après le processus de lancement et de démarrage, les entrées correspondantes apparaîtront dans la base de données.
Dépôt

Exécuter quelle tâche est en cours d'exécution

à qui est attribué

Dans le code, cela ressemble à ceci (le code complet sera ci-dessous):
déployer deployment = repositoryService.createDeployment() .addClasspathResource("processes/MyProcess.bpmn").deploy()
commencer ProcessInstance myProcess = runtimeService.startProcessInstanceByKey(DEV_PROCESS);
développerAprès cela, vous pouvez commencer à terminer la tâche de développement.
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar develop
// tasks = taskService.createTaskQuery().taskCandidateGroup("programmers").list();
Dans la tâche de développement, une variable «problème» est définie

Après avoir traité les variables à l'aide de FormService, la tâche s'exécute
for (Task task : tasks) { System.out.println("Task:" + task.getTaskDefinitionKey() + ", id=" + task.getId()); FormData formData = formService.getTaskFormData(task.getId()); Map<String, Object> variables = new HashMap<String, Object>();

Pour la tâche de développement, vous serez invité à entrer une variable.
Dans le tableau historique, vous pouvez voir les variables et les valeurs de la tâche, du processus

Ainsi, le processus après que la tâche de développement s'arrête dessus, l'état sera enregistré dans la base de données.
En général, la boucle ressemble à ceci:
Demander une tâche pour un exécuteur testamentaire
tasks = taskService.createTaskQuery().taskCandidateGroup("...").list();
Définition des variables
Map<String, Object> variables = new HashMap<String, Object>(); ... variables.put("var_1", value);
Exécution de tâche
taskService.complete(task.getId(), variables);
Vérification de la fin du processus
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstance.getId()).singleResult(); if (processInstance != null && !processInstance.isEnded())
Après chaque exécution de tâche, le processus est suspendu jusqu'à ce qu'une nouvelle tâche soit terminée.
Donc, après avoir exécuté Develop, passons à la tâche Test, ici on nous demandera également d'entrer la variable "devResult" - le résultat de développement (cela n'a pas fonctionné tout à fait correctement, même avant le démarrage de Test, entrez le résultat), puis le résultat sera de ramification ou de fin (Ok) ou développement à nouveau (Non), voir diagramme de processus.

Dans ce cas, le développement, etc. Si vous demandez maintenant des tâches pour le développeur, elles le seront, mais pour les tests - non.
Code de programme package com.example; import org.activiti.engine.*; import org.activiti.engine.form.FormData; import org.activiti.engine.form.FormProperty; import org.activiti.engine.repository.Deployment; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.apache.commons.lang3.StringUtils; import java.util.*; public class DemoActiviti { private static final String DEV_PROCESS = "devProcess"; public static void main(String[] args) { Locale.setDefault(Locale.ENGLISH); ProcessEngineConfiguration cfg = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource("activiti.cfg.xml"); ProcessEngine processEngine = cfg.buildProcessEngine(); RepositoryService repositoryService = processEngine.getRepositoryService(); String mode = StringUtils.EMPTY; if (args.length > 0) { mode = args[0]; } System.out.println("Processes mode: " + mode); Deployment deployment; if ("deploy".equals(mode)) { deployment = repositoryService.createDeployment() .addClasspathResource("processes/MyProcess.bpmn").deploy(); System.out.println("deploy process success"); System.exit(0); } else { List<Deployment> myProcesses = repositoryService.createDeploymentQuery() .processDefinitionKey(DEV_PROCESS).list(); deployment = myProcesses.get(myProcesses.size()-1); System.out.println("get process success:" + deployment.getId()); }
Connecter SpringBoot
Nous modifions le projet en utilisant Spring
Ajouter des dépendances à POM
POM avec SpringBoot <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.1.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>org.activiti</groupId> <version>6.0.0</version> <artifactId>activiti-spring-boot-starter-integration</artifactId> </dependency> ....
La classe DemoActiviti est devenue telle
DemoActiviti - SpringBootApplication @SpringBootApplication @ImportResource("classpath:activiti.cfg.xml") public class DemoActiviti { public static void main(String[] args) { Locale.setDefault(Locale.ENGLISH); SpringApplication.run(DemoActiviti.class, args); } }
J'utilise un modèle mixte - lorsqu'une partie des beans est décrite dans la configuration xml (@ImportResource ("classpath: activiti.cfg.xml")), et l'autre est définie par des annotations.
activiti.cfg.xml - printemps <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"> <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" /> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver" /> <property name="username" value="BPM" /> <property name="password" value="1" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> <property name="dataSource" ref="dataSource" /> <property name="transactionManager" ref="transactionManager" /> <property name="databaseSchemaUpdate" value="true" /> <property name="asyncExecutorActivate" value="false" /> </bean> </beans>
Maintenant, Spring est responsable de la configuration, on peut le voir
bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"
Ajouter un traitement de ligne de commande standard pour SpringBoot en tant que composant
Ligne de commande @Component public class CommandLine implements CommandLineRunner { @Autowired private DemoService demoService; public void run(String... args) { if ("test".equals(args[0])) { demoService.startTest(); } else if ("develop".equals(args[0])) { demoService.startDevelop(); } } }
Qui traitera toutes ces commandes, je ne les implémenterai pas toutes, tout y est simple, j'en montrerai deux: tester et développer. Et ajoutez un service pour les traiter
DemoService @Service public class DemoService { @Autowired private TaskService taskService; @Autowired private FormService formService; public void startTest() { List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("testers").list(); if (tasks.isEmpty()) { System.out.println(" "); return; } processTasks(tasks); } public void startDevelop() { List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("develop").list(); if (tasks.isEmpty()) { System.out.println(" "); return; } processTasks(tasks); } private void processTasks(List<Task> tasks) { Scanner scanner = new Scanner(System.in); for (Task task : tasks) { ...... , }
Dans le composant CommandLine Autowir, ils ont le service DemoService et les services Spring Activiti déjà préparés
@Autowired private TaskService taskService;
Nous collectons, exécutons comme auparavant à partir de la ligne de commande.
Si nous voulons utiliser l'exécution de tâches à partir du Web, nous connectons l'API REST.
API REST
SpringBoot fournira un serveur Tomcat intégré par défaut, puis une question technique.
Dans POM, à ce qu'il est, ajoutez une dépendance Web de printemps
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
Nous supprimons le composant CommandLine, maintenant tout passera par l'URL via HTTP. Ajouter RestController:
Restcontroller @RestController public class DemoRestController { @Autowired private DemoService demoService; @RequestMapping(value="/test", method= RequestMethod.GET, produces= {MediaType.APPLICATION_JSON_VALUE}) public List<String> startTest(@RequestParam String devResult) { List<String> strings = demoService.startTest(devResult); return strings; } @RequestMapping(value="/develop", method= RequestMethod.GET, produces= MediaType.APPLICATION_JSON_VALUE) public List<String> startDevelop(@RequestParam String issue) { List<String> strings = demoService.startDevelop(issue); return strings; } @RequestMapping(value="/start", method= RequestMethod.GET, produces= MediaType.APPLICATION_JSON_VALUE) public List<String> startProcess() { List<String> strings = demoService.startDevProcess(); return strings; } }
Nous exécutons les mêmes commandes, changeant légèrement les réponses du service DemoService, qu'Autowire est dans le contrôleur.
DemoService @Service public class DemoService { @Autowired private TaskService taskService; @Autowired private FormService formService; @Autowired private RuntimeService runtimeService; public List<String> startTest(String devResult) { List<String> results = new ArrayList<>(); List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("testers").list(); if (tasks.isEmpty()) { results.add("The tasks for testing are not"); return results; } Object issue = runtimeService.getVariables(tasks.get(0).getProcessInstanceId()).get("issue"); processTasks(tasks, devResult); results.add("Task N " + issue + " - tested, result=" + devResult); return results; } public List<String> startDevelop(String issue) { List<String> results = new ArrayList<>(); List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("programmers").list(); if (tasks.isEmpty()) { results.add("There are no development tasks"); return results; } processTasks(tasks, issue); Object mIssue = runtimeService.getVariables(tasks.get(0).getProcessInstanceId()).get("issue"); results.add("Task N " + mIssue + " - taken in the develop"); return results; } public List<String> startDevProcess() { List<String> results = new ArrayList<>(); ProcessInstance myProcess = runtimeService.startProcessInstanceByKey("devProcess"); results.add("The process is started #"+myProcess.getId()); return results; } private void processTasks(List<Task> tasks, String param) { for (Task task : tasks) { FormData formData = formService.getTaskFormData(task.getId()); Map<String, Object> variables = new HashMap<>();
test en utilisant curl, voici le résultat:

J'ai changé le port pour Tomcat en 8081 dans application.properties
server.port = 8081
Emploi Activiti
Activiti a de nombreuses conceptions, par exemple, le lancement de tâches planifiées est «TimerStartEvent». Pour que Job commence à s'exécuter en confinement, vous devez spécifier
property name="asyncExecutorActivate" value="true"
(voir activiti.cfg.xml), le processus java restera en cours d'exécution et vérifiera la planification et exécutera les tâches.
Je reviendrai sur le projet initial, où Activiti est utilisé dans sa forme la plus pure.
Dans la classe DemoActiviti je ne laisserai le support que pour deux commandes: déployer et démarrer je ferai un nouveau processus

Après le début du processus, il ira à la minuterie, qui selon le calendrier lancera la tâche «Développer». La minuterie aura un horaire - démarrer toutes les 10 secondes, expression cron - "0/10 * * * * *?".

Déployons le nouveau processus comme précédemment, puis démarrons le processus (démarrer). Tout - la tâche s'exécute toutes les 10 secondes.
En tant que tâche, le composant Activiti est sélectionné - ServiceTask, à partir duquel vous pouvez spécifier une implémentation de classe Java

classe DemoDelegate public class DemoDelegate implements JavaDelegate { @Override public void execute(DelegateExecution execution) { Date now = new Date(); execution.setVariable("issue", now.toString()); System.out.println("job start="+now); } }
Dans le tableau de la base de données (sélectionnez * dans ACT_RU_TIMER_JOB t), vous pouvez voir

Activité de travail, dans le champ DUEDATE_ il y aura une heure du prochain démarrage.
La variable "issue" du Délégué sera enregistrée dans l'historique d'exécution
select * from ACT_HI_VARINST t

code pour Job DemoActiviti c public class DemoActiviti { private static final String DEV_PROCESS = "devProcessJob"; public static void main(String[] args) { Locale.setDefault(Locale.ENGLISH); ProcessEngineConfiguration cfg = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource("activiti.cfg.xml"); ProcessEngine processEngine = cfg.buildProcessEngine(); RepositoryService repositoryService = processEngine.getRepositoryService(); String mode = StringUtils.EMPTY; if (args.length > 0) { mode = args[0]; } System.out.println("Processes mode: " + mode); Deployment deployment; if ("deploy".equals(mode)) { deployment = repositoryService.createDeployment() .addClasspathResource("processes/MyProcessJob.bpmn").deploy(); System.out.println("deploy process success"); System.exit(0); } else { List<Deployment> myProcesses = repositoryService.createDeploymentQuery() .processDefinitionKey(DEV_PROCESS).list(); deployment = myProcesses.get(myProcesses.size()-1); System.out.println("get process success:" + deployment.getId()); }
Il reste encore beaucoup de choses derrière: Events, Listener, JPA, etc., je vais peut-être y revenir.
Matériaux
ActivitiConcepteur EclipsedevProcess bpmn <?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test"> <process id="devProcess" name="Dev process" isExecutable="true"> <startEvent id="startevent1" name="Start" activiti:initiator="programmerId"></startEvent> <userTask id="develop" name="Develop" activiti:candidateGroups="programmers"> <extensionElements> <activiti:formProperty id="issue" name="issue" type="string" required="true"></activiti:formProperty> </extensionElements> </userTask> <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="develop"></sequenceFlow> <userTask id="test" name="Test" activiti:candidateGroups="testers"> <extensionElements> <activiti:formProperty id="devResult" name="devResult" type="string" default="No" required="true"></activiti:formProperty> </extensionElements> </userTask> <sequenceFlow id="flow2" sourceRef="develop" targetRef="test"></sequenceFlow> <exclusiveGateway id="gateway" name="Exclusive Gateway" default="flowNo"></exclusiveGateway> <sequenceFlow id="flow3" sourceRef="test" targetRef="gateway"></sequenceFlow> <sequenceFlow id="flowOk" name="Ok" sourceRef="gateway" targetRef="endevent1"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${devResult == "Ok"}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flowNo" name="No" sourceRef="gateway" targetRef="develop"></sequenceFlow> <endEvent id="endevent1" name="End"></endEvent> </process> </definitions>
devProcessJob bpmn <?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test"> <process id="devProcessJob" name="Dev process Job" isExecutable="true"> <startEvent id="startevent" name="Start" activiti:initiator="programmerId"></startEvent> <sequenceFlow id="flow1" sourceRef="startevent" targetRef="timerstartevent"></sequenceFlow> <endEvent id="endevent" name="End"></endEvent> <startEvent id="timerstartevent" name="Timer start"> <extensionElements> <activiti:formProperty id="issue" name="issue" type="string"></activiti:formProperty> </extensionElements> <timerEventDefinition> <timeCycle>0/10 * * * * ?</timeCycle> </timerEventDefinition> </startEvent> <sequenceFlow id="flow2" sourceRef="timerstartevent" targetRef="servicetask1"></sequenceFlow> <sequenceFlow id="flow3" sourceRef="servicetask1" targetRef="endevent"></sequenceFlow> <serviceTask id="servicetask1" name="Develop" activiti:class="com.example.DemoDelegate"></serviceTask> </process> </definitions>