Activiti framework (Java): descripción del flujo de tareas XML (bpm) y su gestión. Aquí describiré los conceptos básicos básicos y cómo construir procesos comerciales simples.
El concepto principal de Activiti es un proceso y una tarea. Un proceso es todas las tareas interconectadas por flujos y ramas dirigidas.
Tocaré tales aspectos:
- - Activiti en estado puro
- - Usuarios, roles
- - Conecte SpringBoot
- - API REST
- - Trabajo y delegado
El movimiento de los flujos va en pasos de una tarea a otra, cada uno de estos pasos detiene el proceso mientras espera la entrada y la finalización de la tarea, todas las acciones intermedias se almacenan en la base de datos.
Dónde, qué llevar, lo indicaré a continuación. Comencemos con un ejemplo simple: el proceso de desarrollo del programa, que consiste en escribir código y probarlo. A continuación se muestra un diagrama de proceso.

Todo esto es un proceso, tiene una identificación, nombre y otras características.

Tiene:
El comienzo del proceso, dos tareas "Desarrollar" y "Probar", una rama (puerta de enlace) y el final del proceso. En palabras, todo sucede así:
- Descripción de bpm de carga
- comenzar el proceso
- después del comienzo, inmediatamente caemos en la tarea Desarrollar
- después de la ejecución de Develop, pasa a las pruebas y, según el resultado de las pruebas, el proceso finaliza o vuelve nuevamente al desarrollo.
Activiti consiste en un conjunto de servicios
Aquí están los principales:
- RepositoryService: controla la carga de descripciones de procesos
- RuntimeService: inicia procesos
- TaskService: realiza tareas
- FormService: acceso a variables de tarea
- HistoryService: acceso al historial del proceso
- IdentityService: usuarios y roles
Activiti en estado puro
Pero todo comienza con la configuración y el archivo: activiti.cfg.xml.
De esto
ProcessEngineConfiguration cfg = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource("activiti.cfg.xml");
Si no usa su configuración, Activiti desplegará la base de datos en la memoria H2, esto no me conviene, pero mi querido Oracle está lleno, hay opciones para conectar diferentes bases de datos.
Aquí está mi configuración
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>
Cambiamos los valores en "property name = jdbc *" y conectamos otra base de datos
Estructura del proyecto

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 presencia del complemento "maven-assembly-plugin" en el POM le permitirá construir (empaquetar) un jar que se inicia con dependencias y ejecutar:
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar
Controlador jdbc para Oracle instalado en el repositorio local de Maven
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
Para este proceso, definimos 4 acciones: carga de bpm, inicio del proceso, desarrollo y pruebas. Cada acción tendrá un parámetro correspondiente: desplegar, iniciar, desarrollar, probar.
Tomamos scripts para una base de datos de
activiti-get-beginallí en la carpeta \ activiti-6.0.0 \ activiti-6.0.0 \ database \ create - scripts para crear una base de datos
Usuarios, roles
Prepare usuarios y roles:
Identidad 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"); } } }
Crear usuarios y grupos, desarrollador y probador, respectivamente.
En la base de datos, todas las tablas están divididas por los servicios correspondientes y tienen prefijos.
ACT_RE_ *: repositorio.
ACT_RU_ *: tiempo de ejecución.
ACT_ID_ *: identidad.
ACT_HI _ *: historial
y así sucesivamente
Después de crear usuarios desde, puedes ver aquí

Asignaremos nuestras tareas en la descripción a los grupos correspondientes (CandidateGroup), por ejemplo, la tarea Desarrollar al grupo - programadores

Entonces, lo primero que hacemos es colocar en la base de datos "MyProcess.bpmn", ejecutar el programa con el comando de
despliegue java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar deploy
a continuación, inicie el proceso de inicio
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar start
Después del proceso de inicio e inicio, las entradas correspondientes aparecerán en la base de datos.
Repositorio

Tiempo de ejecución qué tarea está en ejecución

a quien se le asigna

En el código, se ve así (el código completo estará debajo):
desplegar deployment = repositoryService.createDeployment() .addClasspathResource("processes/MyProcess.bpmn").deploy()
empezar ProcessInstance myProcess = runtimeService.startProcessInstanceByKey(DEV_PROCESS);
desarrollarDespués de eso, puede comenzar a completar la tarea de desarrollo.
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar develop
// tasks = taskService.createTaskQuery().taskCandidateGroup("programmers").list();
En la tarea Desarrollar, se define una variable de "problema"

Después de procesar las variables usando FormService, la tarea se ejecuta
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>();

Para la tarea Desarrollar, se le pedirá que ingrese una variable.
En la tabla histórica puedes ver las variables y valores de la tarea, proceso

Por lo tanto, el proceso después de que la tarea Desarrollar se detenga, el estado se guardará en la base de datos.
En general, el bucle se ve así:
Solicitar una tarea para un ejecutor
tasks = taskService.createTaskQuery().taskCandidateGroup("...").list();
Definición de variables
Map<String, Object> variables = new HashMap<String, Object>(); ... variables.put("var_1", value);
Ejecución de tareas
taskService.complete(task.getId(), variables);
Verificando el final del proceso
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstance.getId()).singleResult(); if (processInstance != null && !processInstance.isEnded())
Después de la ejecución de cada tarea, el proceso se suspende hasta que se complete una nueva tarea.
Entonces, después de ejecutar Develop, pasemos a la tarea Prueba, aquí también se nos pedirá que ingresemos la variable "devResult": el resultado del desarrollo (no funcionó del todo bien, incluso antes de que comience la Prueba, ingrese el resultado), y luego el resultado se ramificará o terminará (Ok) o desarrollo nuevamente (No), ver diagrama de proceso.

En este caso, el desarrollo, etc. Si ahora solicita tareas para el desarrollador, serán, pero para pruebas, no.
Código del programa 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()); }
Connect SpringBoot
Modificamos el proyecto usando Spring
Agregar dependencias a POM
POM con 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 clase DemoActiviti ahora se ha convertido en tal
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); } }
Utilizo un modelo mixto, cuando parte de los beans se describen en la configuración xml (@ImportResource ("classpath: activiti.cfg.xml")), y el otro se define a través de anotaciones.
activiti.cfg.xml - primavera <?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>
Ahora Spring es responsable de la configuración, se puede ver
bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"
Agregue el procesamiento de línea de comando estándar para SpringBoot como componente
Línea de comandos @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(); } } }
Que procesará todos esos comandos, no los implementaré todos, todo es simple allí, mostraré dos: probar y desarrollar. Y agregue un servicio para procesarlos
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) { ...... , }
En el componente CommandLine Autowir, tienen el servicio DemoService, y en él los servicios Spring Activiti ya preparados.
@Autowired private TaskService taskService;
Recopilamos, corremos como antes desde la línea de comandos.
Si queremos usar la ejecución de tareas desde la Web, entonces conectamos la API REST.
API REST
SpringBoot proporcionará un servidor Tomcat incrustado de manera predeterminada, y luego una cuestión técnica.
En POM, a lo que es, agregue dependencia web de Spring
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
Eliminamos el componente CommandLine, ahora todo vendrá a través de la URL a través de HTTP. Añadir 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; } }
Ejecutamos los mismos comandos, cambiamos ligeramente las respuestas del servicio DemoService, que Autowire está en el controlador.
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<>();
prueba usando curl, aquí está el resultado:

Cambié el puerto de Tomcat a 8081 en application.properties
server.port = 8081
Trabajo Activiti
Activiti tiene muchos diseños, por ejemplo, el lanzamiento de tareas programadas es "TimerStartEvent". Para que Job comience a ejecutarse en confinamiento, debe especificar
property name="asyncExecutorActivate" value="true"
(vea activiti.cfg.xml), entonces el proceso de Java seguirá ejecutándose y verificará la programación y ejecutará las tareas.
Volveré al proyecto inicial, donde Activiti se usa en su forma más pura.
En la clase DemoActiviti dejaré soporte solo para dos comandos: desplegar e iniciar Haré un nuevo proceso

Después del inicio del proceso, irá al temporizador, que de acuerdo con el cronograma iniciará la tarea "Desarrollar". El temporizador tendrá una programación: comenzará cada 10 segundos, expresión cron: "0/10 * * * * *?".

Implementemos el nuevo proceso como antes, luego comencemos el proceso (inicio). Todo: la tarea se ejecuta cada 10 segundos.
Como tarea, se selecciona el componente Activiti: ServiceTask, desde el cual puede especificar como implementación de clase Java

clase 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); } }
En la tabla de la base de datos (seleccione * de ACT_RU_TIMER_JOB t) puede ver

Actividad laboral, en el campo DUEDATE_ habrá un momento del próximo inicio.
La variable "problema" del delegado se registrará en el historial de ejecución
select * from ACT_HI_VARINST t

código para DemoActiviti c Job 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()); }
Todavía queda mucho: Eventos, Listener, JPA, etc., tal vez volveré a ellos.
Materiales
ActivitiDiseñador de 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>