Activiti — Business process engine

    Activiti framework (Java) — описание потока задач на XML (bpm) и управление этим процессом. Здесь опишу основные базовые понятия и как строить простые бизнес процессы.

    Основное понятие Activiti это процесс (process) и задача (task). Процесс это все задачи связанные между собой направленными потоками и ветвлениями.

    Затрону такие аспекты:

    • — Activiti в чистом виде
    • — Пользователи, Роли
    • — Подключение SpringBoot
    • — REST API
    • — Job и Delegate

    Движение по потокам идет шагами от задачи к задаче, каждый такой шаг приостанавливает выполнение процесса ожидая входных данных и выполнения задачи, все промежуточные действия сохраняются в базу данных.

    Где, что брать укажу ниже. Начнем с простого примера — процесс разработки программы, который состоит из написания кода и тестирования. Ниже диаграмма процесса.

    image

    Вот это все есть процесс, он имеет ИД, Имя и др характеристики.

    image

    В нем есть:

    Начало процесса, две задачи «Develop» и «Test», одно ветвление (gateway) и окончание процесса. В словах все происходит так:

    • загружаем описание bpm
    • стартуем процесс
    • после старта сразу попадаем в задачу Develop
    • после выполнения Develop она переходит в тестирование и по результату тестирования процесс завершается либо возвращается опять на разработку.

    Activiti состоит из некоторого набора сервисов

    Вот основные:

    • RepositoryService: управляет загрузкой описания процессов
    • RuntimeService: запускает процессы
    • TaskService: выполняет задачи
    • FormService: доступ к переменным задачи
    • HistoryService: доступ к истории выполнения процесса
    • IdentityService: Пользователи и Роли

    Activiti в чистом виде


    Но начинается все с конфигурации и файла — activiti.cfg.xml.

    Вот с этого

    ProcessEngineConfiguration cfg = ProcessEngineConfiguration
                    .createProcessEngineConfigurationFromResource("activiti.cfg.xml");
    

    Если не использовать свою конфигурацию, то Activiti сам развернет базу данных в памяти H2, меня это не устраивает, а вот мой любимый Oracle вполне, возможности подключения разных БД есть.

    Вот моя конфигурация

    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" />
    
            <!-- Database configurations -->
            <property name="databaseSchemaUpdate" value="false" />
    
            <property name="asyncExecutorActivate" value="false" />
    
            <!-- mail server configurations -->
            <property name="mailServerPort" value="5025" />
        </bean>
    </beans>
    


    Меняем значения в «property name=jdbc * » и подключаем другую БД

    Структура проекта



    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>
                <!-- Maven Assembly Plugin -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.4.1</version>
                    <configuration>
                        <!-- get all project dependencies -->
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                        <!-- MainClass in mainfest make a executable jar -->
                        <archive>
                            <manifest>
                                <mainClass>com.example.DemoActiviti</mainClass>
                            </manifest>
                        </archive>
                    </configuration>
                    <executions>
                        <execution>
                            <id>make-assembly</id>
                            <!-- bind to the packaging phase -->
                            <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>
    


    Наличие в POM плагина «maven-assembly-plugin», позволит собирать (package) запускаемый jar с зависимостями и запускать —
    java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar

    jdbc driver для Oracle установил в локальный 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
    


    Для этого процесса определим 4-ре действия: загрузка bpm, старт процесса, разработка и тестирование. Каждое действие будет иметь соответствующий параметр: deploy, start, develop, test.

    Скрипты для базы данных берем из
    activiti-get-started
    там в папке \activiti-6.0.0\activiti-6.0.0\database\create — скрипты для создания БД

    Пользователи, Роли


    Подготовим пользователей и роли:

    Identity
    
    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");
            }
        }
    }
    


    Создадим пользователей и группы, разработчик и тестировщик соответственно.

    В базе данных все таблицы разделены по соответствующим сервисам и имеют префиксы

    ACT_RE_*: repository.
    ACT_RU_*: runtime.
    ACT_ID_*: identity.
    ACT_HI_*:history
    и пр.

    После создания пользователей из можно посмотреть здесь

    image

    Наши задачи в описании назначим соответствующим группам (CandidateGroup), так например задачу Develop группе — programmers

    image

    И так первое что делаем размещаем в БД «MyProcess.bpmn», запускаем программу с командой deploy

    java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar deploy

    далее стартуем процесс start

    java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar start

    После delpoy и start процесса в базе появятся соответствующие записи.

    Репозиторий

    image

    Runtime, какая задача на исполнении

    image

    кому назначена

    image

    В коде это выглядит так (полный код будет ниже):

    deploy

    deployment = repositoryService.createDeployment()
                        .addClasspathResource("processes/MyProcess.bpmn").deploy()

    start

    ProcessInstance myProcess = runtimeService.startProcessInstanceByKey(DEV_PROCESS);

    develop

    После этого можно приступить к выполнения задачи на разработку

    java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar develop

                // Задачи для разработчика
                tasks = taskService.createTaskQuery().taskCandidateGroup("programmers").list();
    

    В задаче Develop определена одна переменная «issue»

    image

    После обработки переменных с помощью FormService, задача выполняется

    
                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>();
                    // переменные задачи
                    for (FormProperty formProperty : formData.getFormProperties()) {
                        System.out.println("Enter varName <" + formProperty.getName() +">:");
                        String value = scanner.nextLine();
                        variables.put(formProperty.getId(), value);
                    }
                    // выполняем задачу
                    taskService.complete(task.getId(), variables);
                    System.out.println("Task complete success:" + task.getTaskDefinitionKey());
                }
    

    image

    Для задачи Develop будет предложено ввести переменную.

    В историчной таблице можно увидеть переменные и значения задачи, процесса

    image

    Таким образом процесс после задачи Develop остановится на ней, состояние будет сохранено в базе.

    В общем цикл выглядит так:

    Запросить задачу на исполнителя

    tasks = taskService.createTaskQuery().taskCandidateGroup("...").list();

    Определение переменных

    Map<String, Object> variables = new HashMap<String, Object>();
    ...
    variables.put("var_1", value);
    

    Исполнение задачи

    taskService.complete(task.getId(), variables);

    Проверка окончания процесса

    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
    		.processInstanceId(processInstance.getId()).singleResult();
    if (processInstance != null && !processInstance.isEnded()) 
    

    После каждого выполнения задачи, процесс приостанавливается, до выполнения новой задачи.
    Так после выполнения Develop, перейдем к задаче Test, тут тоже будет предложено ввести переменную «devResult» — результат разработки (получилось не совсем корректно, еще до начала Test, вводим результат), а далее по результату будет будет ветвление или окончание (Ok) или опять на разработку (No), см. схему процесса.

    image

    В этом случае на разработку, и.т.д. Если теперь запросить задачи на разработчика, то они будут, а на тестирование — нет.

    Код программы
    
    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());
            }
    
            //
            RuntimeService runtimeService = processEngine.getRuntimeService();
    
            ProcessInstance processInstance;
            if ("start".equals(mode)){
                ProcessInstance myProcess = runtimeService.startProcessInstanceByKey(DEV_PROCESS);
                System.out.println("start process success:" + myProcess.getName() +", id="+ myProcess.getId());
                System.exit(0);
            }
    
            processInstance = runtimeService.createProcessInstanceQuery().deploymentId(deployment.getId()).singleResult();
    
            TaskService taskService = processEngine.getTaskService();
            FormService formService = processEngine.getFormService();
    
            List<Task> tasks = new ArrayList<>();
            if ("develop".equals(mode)) {
                System.out.println("develop mode");
                // получить задачи для разработчика
                tasks = taskService.createTaskQuery().taskCandidateGroup("programmers").list();
                if (tasks.isEmpty()) {
                    System.out.println("Задач на разработку нет");
                    System.exit(0);
                }
            }
    
            if ("test".equals(mode)) {
                System.out.println("test mode");
                // получить задачи для тестирования
                tasks = taskService.createTaskQuery().taskCandidateGroup("testers").list();
                if (tasks.isEmpty()) {
                    System.out.println("Задач на тестирование нет");
                    System.exit(0);
                }
            }
    
            Scanner scanner = new Scanner(System.in);
    
            if (processInstance != null && !processInstance.isEnded()) {
                System.out.println("tasks count: [" + tasks.size() + "]");
    
                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>();
                    // переменные задачи
                    for (FormProperty formProperty : formData.getFormProperties()) {
                        System.out.println("Enter varName <" + formProperty.getName() +">:");
                        String value = scanner.nextLine();
                        variables.put(formProperty.getId(), value);
                    }
                    // выполняем задачу
                    taskService.complete(task.getId(), variables);
                    System.out.println("Task complete success:" + task.getTaskDefinitionKey());
                }
                // Re-query the process instance, making sure the latest state is available
                //processInstance = runtimeService.createProcessInstanceQuery()
                //        .processInstanceId(processInstance.getId()).singleResult();
            }
        }
    
    }
    
    


    Подключение SpringBoot


    Модифицируем проект с использованием Spring

    Добавляем в POM зависимости

    POM с SpringBoot
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.3.1.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </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>
          ....
    


    DemoActiviti класс теперь стал таким
    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);
        }
    }
    


    Я использую смешанную модель — когда часть бинов описываются в xml конфигурации (@ImportResource(«classpath:activiti.cfg.xml»)), а другая определяется через аннотации.

    activiti.cfg.xml - spring
    <?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>
    


    Теперь за конфигурацию отвечает Spring, это видно

    bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"

    Добавим для SpringBoot стандартную обработку командной строки, в виде компоненты

    CommandLine
    
    @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();
            }
        }
    }
    


    Который обработает все те команды, я не буду их все реализовывать, там все просто, покажу две: test и develop. И добавим сервис для их обработки

    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) {
            ...... тут как и ранее, выше 
    
    }
    


    В компоненте CommandLine Autowir-им сервис DemoService, а в нем уже подготовленные Spring сервисы Activiti

    @Autowired
        private TaskService taskService;
    

    Собираем, запускаем как и ранее из командной строки.

    Если хотим использовать выполнение задач из Web, то подключаем REST API.

    REST API


    SpringBoot по умолчанию предоставит embedded Tomcat сервер, а далее дело техники.
    В POM, к тому что есть, добавляем spring web зависимость

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    

    CommandLine компонент удаляем, теперь все будет поступать через URL по HTTP. Добавляем 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;
        }
    }
    


    Те же команды выполняем, немного изменил ответы сервиса DemoService-а, который Autowire-тся в контроллере.

    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<>();
                // переменные задачи
                for (FormProperty formProperty : formData.getFormProperties()) {
                    variables.put(formProperty.getId(), param);
                }
                // выполняем задачу
                taskService.complete(task.getId(), variables);
            }
        }
    }
    


    тестируем с использованием curl, вот результат:

    image

    Порт для Tomcat я изменил на 8081 в application.properties
    server.port=8081

    Activiti Job


    В Activiti много конструкций, так например запуск задач по расписанию это «TimerStartEvent». Для того что бы Job начал исполняться в конфинге надо указать
    property name="asyncExecutorActivate" value="true" (см. activiti.cfg.xml), тогда java процесс останется запущенным и будет проверять расписание и запускать задачи.

    Вернусь к начальному проекту, где используется Activiti в чистом виде.

    В DemoActiviti классе оставлю поддержку только двух команд: deploy и start Сделаю новый процесс

    image

    После старта процесса он перейдет к таймеру который по расписанию будет запускать задачу «Develop». Расписание у таймера будет — запуск каждые 10 сек., cron выражение — «0/10 * * * * ?».

    image

    Сделаем deploy нового процесса как и ранее, затем стартуем процесс (start). Все — задача выполняется каждые 10 сек.

    В качестве задачи выбрана компонента Activiti — ServiceTask, у которого можно указать в качестве реализации Java class

    image

    класс 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);
        }
    }
    


    В таблице в базе (select * from ACT_RU_TIMER_JOB t) можно видеть

    image

    активность Job-а, в поле DUEDATE_ будет время следующего запуска.

    В истории исполнения будет фиксироваться переменная «issue» из Delegate

    select * from ACT_HI_VARINST t

    image

    код для 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());
            }
    
            //
            RuntimeService runtimeService = processEngine.getRuntimeService();
    
            ProcessInstance processInstance;
            if ("start".equals(mode)){
                ProcessInstance myProcess = runtimeService.startProcessInstanceByKey(DEV_PROCESS);
                System.out.println("start process success:" + myProcess.getName() +", id="+ myProcess.getId());
            }
        }
    }
    


    За бортом осталось еще многое: События, Listener, JPA и др., возможно к ним еще вернусь.

    Материалы
    Activiti
    Eclipse Designer

    devProcess 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>
    

    • +19
    • 5,2k
    • 6
    Поделиться публикацией

    Похожие публикации

    Комментарии 6

      +1
      Насколько мне известно, Activity сейчас не очень активно развивается. Несколько лет назад ряд разработчиков форкнуло его и запилили новый продукт, называется camunda, и по отзывам он намного более функциональный. Кому нужен BPM движок, рекомендую рассмотреть как альтернативу.
        0
        С Activiti столкнулся на работе, Camundu — посмотрю, спасибо
          0
          доп. инфо: разработчики Activiti года полтора-два назад не нашли понимания в целях и задачах с Alfresco, поэтому они сделали свой форк. Их работа называется Flowable. Поэтому можно считать, что Activiti активно развизвается. Только не так называется.
          А вообще Activiti создавался для перетягивания одеяла с jBPM.
            0
            Про форки сказали выше. Но активити тоже развивается, и в 7 версии будет основательно перетряхнутая архитектура
              0
              Спасибо, в любом случае движок интересный, он используется в крупных проектах, работа была не напрасной, )
            0

            Да читал конечно, в процессе изучения я обычно оформляют это как документ, конспект,…, вот и этот результат этого процесса

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое