/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * Copyright (c) 2014, MPL CodeInside http://codeinside.ru */ package ru.codeinside.gses.activiti.behavior; import com.sun.xml.ws.client.ClientTransportException; import commons.Exceptions; import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.el.FixedValue; import org.activiti.engine.impl.identity.Authentication; import org.activiti.engine.impl.jobexecutor.TimerDeclarationImpl; import org.activiti.engine.impl.jobexecutor.TimerDeclarationType; import org.activiti.engine.impl.jobexecutor.TimerExecuteNestedActivityJobHandler; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.TimerEntity; import org.activiti.engine.impl.pvm.PvmTransition; import org.activiti.engine.impl.pvm.delegate.ActivityExecution; import org.activiti.engine.impl.variable.EntityManagerSession; import ru.codeinside.adm.AdminServiceProvider; import ru.codeinside.adm.database.Bid; import ru.codeinside.adm.database.ClientRequestEntity; import ru.codeinside.adm.database.Employee; import ru.codeinside.adm.database.ExternalGlue; import ru.codeinside.adm.database.InfoSystemService; import ru.codeinside.adm.database.SmevRequestType; import ru.codeinside.adm.database.SmevResponseType; import ru.codeinside.adm.database.SmevTask; import ru.codeinside.adm.database.SmevTaskStrategy; import ru.codeinside.gses.API; import ru.codeinside.gses.beans.Smev; import ru.codeinside.gses.service.Fn; import ru.codeinside.gses.webui.form.FormOvSignatureSeq; import ru.codeinside.gses.webui.form.ProtocolUtils; import ru.codeinside.gses.webui.form.TaskGoneException; import ru.codeinside.gses.webui.osgi.LogCustomizer; import ru.codeinside.gws.api.Client; import ru.codeinside.gws.api.ClientLog; import ru.codeinside.gws.api.ClientRequest; import ru.codeinside.gws.api.ClientResponse; import ru.codeinside.gws.api.InfoSystem; import ru.codeinside.gws.api.Packet; import ru.codeinside.gws.api.Revision; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.xml.soap.Name; import javax.xml.soap.SOAPFault; import javax.xml.ws.soap.SOAPFaultException; import java.net.URL; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.logging.Logger; import static com.google.common.collect.Collections2.filter; import static com.google.common.collect.Iterables.getOnlyElement; final public class SmevInteraction { final Logger logger = Logger.getLogger(getClass().getName()); /** * Аннотация тут лишь как подсказка для IDEA, в каком модуле искать сущности */ @PersistenceContext(unitName = "myPU") final EntityManager em; final ActivityExecution execution; final SmevTaskConfig config; SmevStage stage; SmevTask task; SmevRequestType lastRequestType; SmevResponseType lastResponseStatus; public SmevInteraction(ActivityExecution execution, SmevTaskConfig config) { this.execution = execution; this.config = config; em = Context.getCommandContext().getSession(EntityManagerSession.class).getEntityManager(); stage = SmevStage.ENTER; } public void robotAction() { List<SmevTask> tasks = findSmevTask(); if (tasks.isEmpty()) { task = new SmevTask(); task.setTaskId(execution.getCurrentActivityId()); task.setExecutionId(execution.getId()); task.setProcessInstanceId(execution.getProcessInstanceId()); task.setErrorMaxCount(config.retryCount.getValue(execution)); task.setErrorDelay(config.retryInterval.getValue(execution)); task.setPingMaxCount(config.pingCount.getValue(execution)); task.setPingDelay(config.pingInterval.getValue(execution)); task.setConsumer(config.consumer.getValue(execution)); task.setStrategy(config.strategy.getValue(execution)); task.setGroups(config.candidateGroup.getValue(execution)); task.setBid(AdminServiceProvider.get().getBidByProcessInstanceId(execution.getProcessInstanceId())); String login = Authentication.getAuthenticatedUserId(); if (login != null) { task.setEmployee(em.find(Employee.class, login)); } } else { task = tasks.get(0); lastResponseStatus = getResponseStatus(); lastRequestType = getRequestStatus(); } execute(); } void humanAction(boolean repeat) { List<SmevTask> tasks = findSmevTask(); if (tasks.isEmpty()) { throw new TaskGoneException(false); } task = tasks.get(0); lastResponseStatus = getResponseStatus(); lastRequestType = getRequestStatus(); if (repeat) { task.setNeedUserReaction(false); task.setPingCount(0); task.setErrorCount(0); execute(); } else { if (isFailure()) { leaveTo("error"); } else { leaveTo("reject"); } } } private List<SmevTask> findSmevTask() { List<SmevTask> tasks = em.createQuery("select t from SmevTask t where " + "t.taskId=:taskId and t.executionId=:executionId and t.processInstanceId=:processId", SmevTask.class) .setParameter("taskId", execution.getCurrentActivityId()) .setParameter("executionId", execution.getId()) .setParameter("processId", execution.getProcessInstanceId()) .getResultList(); if (tasks.size() > 1) { throw new IllegalStateException("Duplicate smevTask " + execution.getProcessInstanceId() + ":" + execution.getId() + ":" + execution.getCurrentActivityId() ); } return tasks; } private String createSoapFaultMessage(SOAPFaultException failure) { SOAPFault fault = failure.getFault(); StringBuilder message = new StringBuilder("Ошибка взаимодействия с поставщиком услуги"); Name code = fault.getFaultCodeAsName(); if (code != null) { message.append(" (").append(code.getLocalName()).append(')'); } message.append(":\n"); message.append(fault.getFaultString()); return message.toString(); } private SmevResponseType getResponseStatus() { return task.getResponseType(); } private SmevRequestType getRequestStatus() { return task.getRequestType(); } private boolean isSuccess() { SmevResponseType responseStatus = getResponseStatus(); return SmevResponseType.RESULT == responseStatus || SmevResponseType.STATE == responseStatus; } private boolean isReject() { SmevResponseType responseStatus = getResponseStatus(); return SmevResponseType.REJECT == responseStatus; } private boolean isFailure() { SmevResponseType responseStatus = getResponseStatus(); return SmevResponseType.INVALID == responseStatus || SmevResponseType.FAILURE == responseStatus; } private boolean isPool() { SmevResponseType responseStatus = getResponseStatus(); return SmevResponseType.ACCEPT == responseStatus || SmevResponseType.PROCESS == responseStatus; } // TODO: точка использования сервисов OSGI - сервисы могут быть НЕ доступны, и их нужно освобождать! private String processNextStage() { ClientRequest request; ClientResponse response; Smev smev; InfoSystemService service; Client client; ClientLog clientLog = null; String servicePort; Revision serviceRevision; ClientExchangeContext gwsContext; URL serviceWsdl; Bid bid; stage = SmevStage.REQUEST_PREPARE; { bid = task.getBid(); if (bid == null) { throw new IllegalStateException("Нет заявки для процесса {" + execution.getProcessInstanceId() + "}"); } ProcessEngineConfigurationImpl cfg = Context.getProcessEngineConfiguration(); smev = (Smev) cfg.getExpressionManager().createExpression("#{smev}").getValue(execution); service = smev.validateAndGetService(task.getConsumer()); ru.codeinside.adm.database.InfoSystem sender = service.getSource(); if (sender == null) { sender = smev.getDefaultSender(); } if (sender == null) { throw new IllegalStateException("Ошибка в конфигурации, не задана основная ифнормационнця система"); } client = smev.findByNameAndVersion(task.getConsumer(), service.getSversion()); // OSGI - ресурс! serviceWsdl = client.getWsdlUrl(); if (serviceWsdl == null) { throw new IllegalStateException("Ошибка в реализации потребителя, не задан WSDL"); } serviceRevision = client.getRevision(); if (serviceRevision == null) { throw new IllegalStateException("Ошибка в реализации потребителя, не задана ревизия СМЭВ"); } gwsContext = new ClientExchangeContext(execution, task.getConsumer()); gwsContext.setOriginRequestId(task.getOriginId()); gwsContext.setRequestId(task.getRequestId()); ru.codeinside.adm.database.InfoSystem origin = null; ExternalGlue glue = bid.getGlue(); if (glue != null) { origin = glue.getOrigin(); // первоисточник, если есть if (origin == null) { origin = glue.getSender(); // оправитель прямого запроса } } // TODO: не нужно - клиент эту переменную ЗАПИСЫВАЕТ а не читает gwsContext.setPool( task.getStrategy() == SmevTaskStrategy.PING && (lastRequestType == SmevRequestType.PING || lastRequestType == SmevRequestType.REQUEST) ); stage = SmevStage.REQUEST; servicePort = Fn.trimToNull(service.getAddress()); //serviceName нужен, чтобы в случае параллельного выполнения отличались имена переменных в разных потоках String serviceName = service.getSname(); Long requestId = (Long) gwsContext.getVariable(serviceName + FormOvSignatureSeq.REQUEST_ID); boolean isDataFlow = (requestId != null); if (isDataFlow && !ProtocolUtils.isPing(gwsContext)) { ClientRequestEntity entity = AdminServiceProvider.get().getClientRequestEntity(requestId); request = smev.createClientRequest(entity, gwsContext, execution.getId(), ""); } else { ProtocolUtils.writeInfoSystemsToContext(service, gwsContext); request = client.createClientRequest(gwsContext); if (request == null || request.packet == null) { throw new IllegalStateException("Ошибка в реализации потребителя, нет пакета данных"); } task.setRequestType(SmevRequestType.fromStatus(request.packet.status)); //TODO: использование logger вместо обработки IllegalStateException if (task.getStrategy() == SmevTaskStrategy.PING) { if (lastRequestType == null && task.getRequestType() != SmevRequestType.REQUEST || lastRequestType != null && (task.getRequestType() != SmevRequestType.PING && lastResponseStatus != null)) { logger.warning("Ошибка в реализации потребителя " + task.getConsumer() + ", ошибка в типе запроса " + task.getRequestType()); } } if (request.packet.status == Packet.Status.PING) { task.setPingCount(task.getPingCount() + 1); } if (servicePort != null) { request.portAddress = servicePort; } } fillRequestPacket(request, service, sender, origin); } stage = SmevStage.LOG; { boolean logEnabled = AdminServiceProvider.getBoolProperty(API.ENABLE_CLIENT_LOG) && service.isLogEnabled(); if (logEnabled || AdminServiceProvider.getBoolProperty(API.LOG_ERRORS)) { boolean logErrors = AdminServiceProvider.getBoolProperty(API.LOG_ERRORS); String logStatus = AdminServiceProvider.get().getSystemProperty(API.LOG_STATUS); Set<String> remote = smev.parseRemote(servicePort); clientLog = LogCustomizer.createClientLog(bid.getId(), task.getConsumer(), execution.getProcessInstanceId(), logEnabled, logErrors, logStatus, remote); } } stage = SmevStage.NETWORK; try { response = smev.createProtocol(serviceRevision).send(serviceWsdl, request, clientLog); // OSGI ресурс! } catch (RuntimeException e) { stage = SmevStage.NETWORK_ERROR; smev.storeUnavailable(service); RuntimeException e2 = smev.processFailure(client, gwsContext, clientLog, e); if (e2 == null) { throw e; } throw e2; } finally { if (clientLog != null) { clientLog.close(); } } stage = SmevStage.RESPONSE; if (response.verifyResult.error != null) { throw new IllegalStateException("Verification error: " + response.verifyResult.error); } if (task.getOriginId() == null) { task.setOriginId(response.packet.originRequestIdRef); } if (response.routerPacket != null && response.routerPacket.messageId != null) { task.setRequestId(response.routerPacket.messageId); } else { task.setRequestId(UUID.randomUUID().toString()); } client.processClientResponse(response, gwsContext); task.setResponseType(SmevResponseType.fromStatus(response.packet.status)); stage = SmevStage.LEAVE; return gwsContext.getSmevError(); } private void fillRequestPacket( ClientRequest request, InfoSystemService service, ru.codeinside.adm.database.InfoSystem sender, ru.codeinside.adm.database.InfoSystem origin) { ru.codeinside.adm.database.InfoSystem recipient = service.getInfoSystem(); if (request.packet.recipient == null) { request.packet.recipient = new InfoSystem(recipient.getCode(), recipient.getName()); } if (request.packet.sender == null) { request.packet.sender = new InfoSystem(sender.getCode(), sender.getName()); } if (origin != null && request.packet.originator == null) { request.packet.originator = new InfoSystem(origin.getCode(), origin.getName()); } if (request.packet.requestIdRef == null) { request.packet.requestIdRef = task.getRequestId(); } if (request.packet.originRequestIdRef == null) { request.packet.originRequestIdRef = task.getOriginId(); } if (AdminServiceProvider.getBoolProperty(API.PRODUCTION_MODE)) { request.packet.testMsg = null; } if (request.packet.date == null) { request.packet.date = new Date(); } } private void execute() { final boolean processRequired; boolean errorDetected = false; if (isSuccess() || isReject()) { logger.fine("success or reject"); processRequired = false; } else if (isPool()) { logger.fine("pooling"); processRequired = task.canProcess(); } else if (isFailure()) { logger.fine("failure"); processRequired = task.canProcess(); } else { logger.fine("internals"); processRequired = task.canProcess(); } String smevError = null; if (processRequired) { task.setRevision(task.getRevision() + 1); task.setRequestType(null); task.setResponseType(null); task.setFailure(null); // контекст блока не относится к пользователю! String userId = Authentication.getAuthenticatedUserId(); Authentication.setAuthenticatedUserId(null); try { smevError = processNextStage(); } catch (Exception e) { StringBuilder sb = new StringBuilder().append(stage).append(":\n"); if (e instanceof ClientTransportException) { sb.append(e.getMessage()); } else if (e instanceof IllegalStateException) { sb.append(e.getMessage()); } else if (e instanceof SOAPFaultException) { sb.append(createSoapFaultMessage((SOAPFaultException) e)); } else { sb.append(Exceptions.toString(e)); } errorDetected = true; task.registerFailure(sb.toString()); // сохранять предыдущий тип запроса при ошибке формирования текущего if (task.getRequestType() == null) { task.setRequestType(lastRequestType); } } finally { Authentication.setAuthenticatedUserId(userId); } } final boolean leave; final boolean needHuman; if (isSuccess() || isReject()) { if (SmevResponseType.REJECT == task.getResponseType()) { task.setFailure(Fn.trimToNull(smevError)); } leave = true; needHuman = false; } else if (isPool()) { leave = false; needHuman = task.needHumanReaction(); } else if (isFailure()) { if (!errorDetected) { task.registerFailure(task.getResponseType().name + getReason(smevError)); } leave = false; needHuman = task.needHumanReaction(); } else { if (!errorDetected) { task.registerFailure(stage + ": " + task.getResponseType() + getReason(smevError)); } leave = false; needHuman = task.needHumanReaction(); } task.setNeedUserReaction(needHuman); task.setLastChange(new Date()); em.persist(task); em.flush(); if (needHuman) { logger.info("Требуется решение человека для {" + execution.getProcessDefinitionId() + ":" + execution.getProcessInstanceId() + ":" + execution.getCurrentActivityId() + "}" ); } else if (leave) { if (isSuccess()) { leaveTo("result"); } else if (isReject()) { leaveTo("reject"); } else { leaveTo("error"); } } else { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.SECOND, isPool() ? task.getPingDelay() : task.getErrorDelay()); FixedValue nextRun = new FixedValue(calendar.getTime()); logger.fine("scheduleNextStage at " + nextRun.getExpressionText()); TimerDeclarationImpl timerDeclaration = new TimerDeclarationImpl( nextRun, TimerDeclarationType.DATE, TimerExecuteNestedActivityJobHandler.TYPE); timerDeclaration.setRetries(1); TimerEntity timer = timerDeclaration.prepareTimerEntity((ExecutionEntity) execution); timer.setJobHandlerConfiguration(execution.getCurrentActivityId()); timer.setExclusive(true); Context.getCommandContext().getJobManager().schedule(timer); } } /** * Обоснование ошибки от поставщика, предоставленное потребителем: */ private String getReason(String smevError) { smevError = Fn.trimToNull(smevError); return smevError == null ? "" : ("\nОбоснование: " + smevError); } void leaveTo(String prefix) { if (task.getFailure() != null || execution.hasVariable("smevError")) { // заменяем переменную! execution.setVariable("smevError", task.getFailure()); } PvmTransition active = getOnlyElement(filter(execution.getActivity().getOutgoingTransitions(), Transitions.withPrefix(prefix))); em.remove(task); em.flush(); execution.take(active); } }