Activiti: обращение к веб-сервису из процесса
При имплементировании реальных сценариев зачастую процесс является частью программного комплекса. Представьте себе следующую ситуацию: где-то крутится SOAP сервис, возвращающий, как водится, при запросе некую информацию по определенному коду. И существует задача: как из процесса обратится к этому сервису, послать ему этот некий код и получить ответ?
Что у вас есть? Есть wsdl. Например, http://host:port/YourService?wsdl
Первым шагом мы получаем java-библиотеку для обращения к сервису. В терминале (или же в command line под Windows) запускаем:
wsimport -keep http://host:port/YourService?wsdl
Изучая код полученной библиотеки (а параметр keep нам создал и java-сорсы) мы видим, что обращение к сервису из java должно произойти следующим образом:
YourService ys = new YourService(new java.net.URL("http://host:port/YourService?wsdl")); YourServiceServer port = ys.getYourServicePort(); String response = port.requestData("Некий код");
Описываем свой класс WsDelegate, имплементирующий org.activiti.engine.delegate.JavaDelegate:
package ru.ossportal; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.JavaDelegate; import org.activiti.engine.delegate.Expression; import javax.xml.namespace.QName; public class WsDelegate implements org.activiti.engine.delegate.JavaDelegate { private Expression wsdl, idf; private Expression returnValue; public void execute(DelegateExecution execution) throws Exception { org.alfresco.repo.jscript.ScriptLogger logger = new org.alfresco.repo.jscript.ScriptLogger(); String wsdlString = (String)wsdl.getValue(execution); String id = (String)idf.getValue(execution); String returnVariableName = (String) returnValue.getValue(execution); String response = ""; try { YourService ys = new YourService(new java.net.URL(wsdlString)); YourServiceServer port = ys.getYourServicePort(); response = port.requestData(id); if (returnValue!=null) execution.setVariable(returnVariableName, response); } catch (Exception e) { if (logger.isLoggingEnabled()) logger.error("Failed to handle request: " + e.toString()); response = "Failed to handle request: " + e.toString(); if (returnValue!=null) execution.setVariable(returnVariableName, response); } } }
Компилируем и вместе с полученной библиотекой помещаем в ALFRESCO_HOME/tomcat/webapps/alfresco/WEB-INF/class
Можно создать jar файл и поместить в ALFRESCO_HOME/tomcat/webapps/alfresco/WEB-INF/lib
В процессе же вызов веб-сервиса описывается следующим образом
<serviceTask id="callWS" name="Call WS" activiti:class="ru.ossportal.WsDelegate" > <extensionElements> <!-- Указываем адресс wsdl --> <activiti:field name="wsdl" expression="http://host:port/YourService?wsdl" /> <!-- Передаем введеный ранее код --> <activiti:field name="idf" expression="${req_id}" /> <!-- Возвращаемое значение --> <activiti:field name="returnValue" expression="response" /> </extensionElements> </serviceTask>
Переменная ${response}, в которой хранится ответ сервиса, будет доступна процессу.
А теперь опишем следующий сценарий:
1. юзер инициирует процесс "Запрос данных" и вводит в форме процесса некий код;
2. Если юзер является администратором альфрески или же состоит в группе "Services_User" на столе у юзера появляется задача с ответом от сервиса. Если же юзер не состоит в вышеперечисленных группах, на столе появляется задача "Запрет в допуске"
Имплементируем вот такой маршрут:
Полный текст процесса:
<?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: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="DataRequest" name="Запрос данных"> <!-- Скрипт начального узла; работает на старте, прямо после того, как нажата кнопка Start Workflow --> <!-- Инициатор является владельцем дальнейших задач --> <!-- Проверяем принадлежность инициатора к группам в альфреске --> <!-- Формируем поле описания процесса --> <extensionElements> <activiti:executionListener event="start" class="org.alfresco.repo.workflow.activiti.listener.ScriptExecutionListener"> <activiti:field name="script"> <activiti:string> <![CDATA[ var assignee = initiator.properties.userName; execution.setVariable('assignee', assignee); var groupName = "GROUP_Services_User"; var admin = "GROUP_ALFRESCO_ADMINISTRATORS"; var validGroup = 0; var groups = people.getContainerGroups(person) for (var i=0; i<groups.length; i++ ) { if (groups[i].properties["cm:authorityName"]==groupName || groups[i].properties["cm:authorityName"]==admin) { validGroup = 1; break; } } execution.setVariable('validGroup', validGroup); var date = new Date(); var day = date.getDate(); var month = date.getMonth() + 1; var year = date.getFullYear(); var hours = date.getHours(); var min = date.getMinutes(); execution.setVariable('bpm_workflowDescription', "Запрос от "+year+"-"+month+"-"+day+" "+hours+":"+min); //UPDATED execution.setVariable('req_id', req_id); ]]> </activiti:string> </activiti:field> </activiti:executionListener> </extensionElements> <!-- Начальный узел процесса --> <startEvent id="start" activiti:formKey="req:start"/> <!-- Переход от старта к развилке, проверяющей валидность группы --> <sequenceFlow id='flow1' sourceRef='start' targetRef='isGroup' /> <!-- Развилка, проверяющая валидность группы --> <exclusiveGateway id="isGroup" name="Is valid group?" /> <!-- Группа валидна. Переходим к вызову сервиса --> <sequenceFlow id='flow2' sourceRef='isGroup' targetRef='callWS' > <conditionExpression xsi:type="tFormalExpression">${validGroup==1}</conditionExpression> </sequenceFlow> <!-- Группа не валидна. Переходим к задаче запрета допуска --> <sequenceFlow id='flow3' sourceRef='isGroup' targetRef='accessDenied' > <conditionExpression xsi:type="tFormalExpression">${validGroup==0}</conditionExpression> </sequenceFlow> <!-- Задача запрета допуска. Владелец задачи инициатор --> <userTask id="accessDenied" name="Запрет в допуске" activiti:formKey="req:accessDenied" activiti:assignee="${assignee}"> <extensionElements> <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener"> <!-- Скрипт при создании задачи --> <!-- Формируем заново поле описания процесса --> <activiti:field name="script"> <activiti:string> execution.setVariable('bpm_workflowDescription', "У Вас нет допуска на запрос."); </activiti:string> </activiti:field> </activiti:taskListener> </extensionElements> </userTask> <!-- Переход от задачи запрета допуска к окончанию процесса --> <sequenceFlow id='flow4' sourceRef='accessDenied' targetRef='end' /> <!-- Задача вызова сервиса. --> <serviceTask id="callWS" name="Call WS" activiti:class="ru.ossportal.WsDelegate" > <extensionElements> <activiti:field name="wsdl" expression="http://host:port/YourService?wsdl" /> <activiti:field name="idf" expression="${req_id}" /> <activiti:field name="returnValue" expression="response" /> </extensionElements> </serviceTask> <!-- Переход от вызова сервиса к показу результата --> <sequenceFlow id='flow5' sourceRef='callWS' targetRef='showResult' /> <!-- Показать результат. Владелец задачи инициатор --> <userTask id="showResult" name="Посмотреть результат запроса" activiti:formKey="req:showResult" activiti:assignee="${assignee}"> <!-- Скрипт при создании задачи --> <!-- Устанавливаем срок исполнения и приоритет, если те не указаны --> <!-- В соответствии со значением response формируем значение информирующего поля. --> <extensionElements> <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener"> <activiti:field name="script"> <activiti:string> <![CDATA[ if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate; if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; execution.setVariable('bpm_workflowDescription', bpm_workflowDescription); var r = response.search("Failed"); if (r==-1) { execution.setVariable('req_info', response); } else execution.setVariable('req_info',"Запрос не принят. Произошла следующая ошибка: "+response); ]]> </activiti:string> </activiti:field> </activiti:taskListener> </extensionElements> </userTask> <!-- Переход от вызова сервиса к окончанию процесса --> <sequenceFlow id='flow6' sourceRef='showResult' targetRef='end' /> <endEvent id="end" /> </process> </definitions>
Модель процесса
<?xml version="1.0" encoding="UTF-8"?> <model name="reg:workflowmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0"> <imports> <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" /> <import uri="http://www.alfresco.org/model/bpm/1.0" prefix="bpm" /> <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/> </imports> <namespaces> <namespace uri="http://www.reg.ru/model/workflow/1.0" prefix="reg" /> </namespaces> <types> <type name="reg:start"> <parent>bpm:startTask</parent> <mandatory-aspects> <aspect>reg:id</aspect> </mandatory-aspects> </type> <type name="reg:showResult"> <parent>bpm:workflowTask</parent> <overrides> <property name="bpm:packageItemActionGroup"> <default>read_package_item_actions</default> </property> </overrides> <mandatory-aspects> <aspect>reg:info</aspect> </mandatory-aspects> </type> <type name="reg:accessDenied"> <parent>bpm:workflowTask</parent> <overrides> <property name="bpm:packageItemActionGroup"> <default>read_package_item_actions</default> </property> </overrides> </type> </types> <aspects> <aspect name="reg:id"> <title>Код запроса</title> <properties> <property name="reg:id"> <type>d:text</type> <mandatory>false</mandatory> </property> </properties> </aspect> <aspect name="reg:info"> <title>Информирующее поле</title> <properties> <property name="reg:info"> <type>d:text</type> <mandatory>false</mandatory> </property> </properties> </aspect> </aspects> </model>
Важно знать, что в процессе должен быть хотя бы один userTask, чтобы процесс заработал.
Прикрепленные файлы | Размер |
---|---|
diagram2.png | 13.19 кб |
Комментарии
25/10/2012 - 15:56
Спасибо, хороший пример.
25/10/2012 - 16:05
Не за что. Я, правда, была неуверена, нужно ли писать, но написала, поскольку сама в свое время так споткнулась с вызовами SOAP сервисов, помните, наверно, мою тему на форуме. В документации активити есть пример использования wsdl, но как-то он не сработал у меня. А так вот - импортом классов из wsdl - сработало Подумалось, может, кому-то будет тоже полезно.
24/09/2014 - 13:54
Сделал почти все так же. Только сам процесс нарисовал в eclipse.
и в ява классе не обращался в WS а просто возвращаю строку Testik!!!!.
но процесс не запускается. :-((((
Возращается ошибка :
16:25:24,092 DEBUG [org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter] Task priority value (50) was invalid so it was set to the default value of 2. Task:Call Web Service
16:25:24,100 DEBUG [org.alfresco.repo.jscript.ScriptLogger] org.alfresco.service.cmr.workflow.WorkflowException: 08240046 Не удалось запустить бизнес-процесс activiti$DataRequest:6:7512.
6:25:24,102 DEBUG [org.alfresco.repo.jscript.ScriptLogger] Returning 500 status code
описание форм:
<!-- Настройки форм для маршрута datarequest -->
...
<config evaluator="string-compare" condition="activiti$DataRequest">
<forms>
<form>
<field-visibility>
<show id="bpm:workflowDescription" />
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="" appearance="title" label-id="workflow.set.general" />
<set id="assignee" appearance="title" label-id="workflow.set.assignee" />
<set id="items" appearance="title" label-id="workflow.set.items" />
<set id="other" appearance="title" label-id="workflow.set.other" />
<field id="bpm:workflowDescription" labelid="workflow.field.message" mandatory="false">
<control template="/org/alfresco/components/form/controls/textarea.ftl">
<control-param name="style">width: 95%</control-param>
</control>
</field>
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
<config evaluator="task-type" condition="mwf:DRStart">
<forms>
<form>
<field-visibility>
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="items" appearance="title" label-id="workflow.set.items" />
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
<config evaluator="task-type" condition="mwf:DRcallWS">
<forms>
<form>
<field-visibility>
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="items" appearance="title" label-id="workflow.set.items" />
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
<config evaluator="task-type" condition="mwf:DRaccessDenied">
<forms>
<form>
<field-visibility>
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="items" appearance="title" label-id="workflow.set.items" />
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
<config evaluator="task-type" condition="mwf:DRshowResult">
<forms>
<form>
<field-visibility>
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="items" appearance="title" label-id="workflow.set.items" />
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
По сообщению ошибки говориться что выставленныое значение в 50 признано не валидным и проставлено 2.
Что это может быть?
Помогите пожалуйста.
24/09/2014 - 20:36
К сожалению, я не могу сказать, где у Вас ошибка. То, что Вы показали - это вырисовка интерфейса share, а Вам надо смотреть в скрипт процесса, где именно вообще появляется это значение 50.
25/09/2014 - 09:06
java программа запускается, но прерывается в определенном месте. В тексте программы указал.
package ru.kristall.w1;
//import java.io.File;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.JavaDelegate;
import org.activiti.engine.delegate.Expression;
import javax.mail.*;
import javax.mail.internet.MimeMessage;
import ru.kristall.utils.*;
import javax.xml.namespace.QName;
public class WSSendEmail implements org.activiti.engine.delegate.JavaDelegate {
private Expression wsdl, idf;
private Expression returnValue;
// public void send2mail(String fName, String fuser, String tuser, String smtpHost, String username, String password,String thead, String ttext) throws Exception {
// try {
// int smtpPort = 25; //port of smtp mail server
// SmtpMessageSender messageSender = new SmtpMessageSender();
// Session session = messageSender.createSession(smtpHost, smtpPort, username,password);
// MimeMessage message = messageSender.createMimeMessage(session, thead, /*"from@mail.ru"*/ fuser,/*"to@mail.ru"*/ tuser, Message.RecipientType.TO);
// messageSender.addText(message,ttext,"utf-8","plain");
//// if (fName!=null) messageSender.addAttachment(message, new File(fName));
// messageSender.sendMimeMessage(message);
// }
// catch( Exception ee) {}
// }
public void execute(DelegateExecution execution) throws Exception {
org.alfresco.repo.jscript.ScriptLogger logger = new org.alfresco.repo.jscript.ScriptLogger();
logger.log("Execute my webScript !! 1");
// send2mail("", "billing@kristallcom.ru", "boltaev@kristallcom.ru","192.168.11.49", "billing", "BGbilling", "Test", "Text into body");
String wsdlString = (String)wsdl.getValue(execution);
logger.log("Execute my webScript !! 2");
Вот на этой строке прерывается программа.
String id = (String)idf.getValue(execution);
logger.log("Execute my webScript !! 3");
String returnVariableName = (String) returnValue.getValue(execution);
logger.log("Execute my webScript !! 4");
String response = "";
//
// try {
// YourService ys = new YourService(new java.net.URL(wsdlString));
// YourServiceServer port = ys.getYourServicePort();
// response = port.requestData(id);
logger.log("Execute my webScript !! 5");
response="Testik!!!";
logger.log("Execute my webScript !! 6");
if (returnValue!=null) execution.setVariable(returnVariableName, response);
logger.log("Execute my webScript !! 7");
//
// if (returnValue!=null) execution.setVariable(returnVariableName, response);
// } catch (Exception e) {
// if (logger.isLoggingEnabled()) logger.error("Failed to handle request: " + e.toString());
// response = "Failed to handle request: " + e.toString();
// if (returnValue!=null) execution.setVariable(returnVariableName, response);
// }
}
}
Видно что прерывание идет при отсутствии заначения в переменной. idf
Скорее всего я не правильно написал формы и пытаюсь передать значения. Подскажите пожалуйста где я ошибся.
Вот формы:
<!-- Настройки форм для маршрута datarequest -->
...
<config evaluator="string-compare" condition="activiti$DataRequest">
<forms>
<form>
<field-visibility>
<show id="bpm:workflowDescription" />
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="" appearance="title" label-id="workflow.set.general" />
<set id="assignee" appearance="title" label-id="workflow.set.assignee" />
<set id="items" appearance="title" label-id="workflow.set.items" />
<set id="other" appearance="title" label-id="workflow.set.other" />
<field id="bpm:workflowDescription" labelid="workflow.field.message" mandatory="false">
<control template="/org/alfresco/components/form/controls/textarea.ftl">
<control-param name="style">width: 95%</control-param>
</control>
</field>
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
<config evaluator="task-type" condition="mwf:DRStart">
<forms>
<form>
<field-visibility>
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="items" appearance="title" label-id="workflow.set.items" />
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
<config evaluator="task-type" condition="mwf:DRcallWS">
<forms>
<form>
<field-visibility>
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="items" appearance="title" label-id="workflow.set.items" />
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
<config evaluator="task-type" condition="mwf:DRaccessDenied">
<forms>
<form>
<field-visibility>
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="items" appearance="title" label-id="workflow.set.items" />
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
<config evaluator="task-type" condition="mwf:DRshowResult">
<forms>
<form>
<field-visibility>
<show id="reg:id" />
<show id="reg:info" />
</field-visibility>
<appearance>
<set id="items" appearance="title" label-id="workflow.set.items" />
<field id="reg:id" set="items" />
<field id="reg:info" set="items" />
</appearance>
</form>
</forms>
</config>
...
<!-- -->
Вот описание модели процесса:
<?xml version="1.0" encoding="UTF-8"?>
<model name="reg:workflowmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<imports>
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" />
<import uri="http://www.alfresco.org/model/bpm/1.0" prefix="bpm" />
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</imports>
<namespaces>
<namespace uri="http://www.reg.ru/model/workflow/1.0" prefix="reg" />
</namespaces>
<types>
<type name="reg:DRStart">
<parent>bpm:startTask</parent>
<mandatory-aspects>
<aspect>reg:id</aspect>
</mandatory-aspects>
</type>
<type name="reg:DRshowResult">
<parent>bpm:workflowTask</parent>
<overrides>
<property name="bpm:packageItemActionGroup">
<default>read_package_item_actions</default>
</property>
</overrides>
<mandatory-aspects>
<aspect>reg:info</aspect>
</mandatory-aspects>
</type>.
<type name="reg:DRaccessDenied">
<parent>bpm:workflowTask</parent>
<overrides>
<property name="bpm:packageItemActionGroup">
<default>read_package_item_actions</default>
</property>
</overrides>
</type>.
</types>
<aspects>
<aspect name="reg:id">
<title>Код запроса</title>
<properties>
<property name="reg:id">
<type>d:text</type>
<mandatory>false</mandatory>
</property>
</properties>
</aspect>
<aspect name="reg:info">
<title>Информирующее поле</title>
<properties>
<property name="reg:info">
<type>d:text</type>
<mandatory>false</mandatory>
</property>
</properties>
</aspect>
</aspects>
</model>
в описании процесса :
<serviceTask id="DRcallWS" name="Call Web Service" activiti:class="ru.kristall.w1.WSSendEmail" >
<extensionElements>
<activiti:field name="wsdl" expression="http://host:port/YourService?wsdl" />
Вот с этой передачей проблемы
<activiti:field name="idf" expression="${req_id}" />
<activiti:field name="returnValue" expression="response" />
</extensionElements>
</serviceTask>
25/09/2014 - 10:45
Сам код вроде правилен да и в процессе также нормально. Остается понять, не пустая ли сама переменная req_id. Вы ее описали в моделе, но существует ли он на момент передачи сервис таску?
Ясно, у меня в тексте нет нигде передачи этой переменной, поэтому Вы ошиблись. Смотрите, выражение ${req_id} означает всего лишь ссылку на переменную, которая у нас есть на старте, но нет на момент вызова сервиса. Т е до этого ее нужно определить.
Т е в данонм конкретном случае до сервис-таска в процессе у вас в стартовом ивенте должна быть такая строка:
Я сейчас поправлю текст. А то это будет вводить в заблуждение начинающих в Альфреско
25/09/2014 - 12:04
Все заработало, даже подложенные свои библиотеки подключились. Все заработало.
Очень полезная статья!
Для начинающих полностью описывает процесс построения рабочего процесса Так же и подключения java классов.
25/09/2014 - 12:18