2  * ============LICENSE_START=======================================================
 
   4  * ================================================================================
 
   5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
 
   6  * ================================================================================
 
   7  * Modifications Copyright (c) 2019 Samsung
 
   8  * ================================================================================
 
   9  * Licensed under the Apache License, Version 2.0 (the "License");
 
  10  * you may not use this file except in compliance with the License.
 
  11  * You may obtain a copy of the License at
 
  13  *      http://www.apache.org/licenses/LICENSE-2.0
 
  15  * Unless required by applicable law or agreed to in writing, software
 
  16  * distributed under the License is distributed on an "AS IS" BASIS,
 
  17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  18  * See the License for the specific language governing permissions and
 
  19  * limitations under the License.
 
  20  * ============LICENSE_END=========================================================
 
  23 package org.onap.so.bpmn.core.plugins;
 
  25 import java.util.ArrayList;
 
  26 import java.util.List;
 
  27 import java.util.concurrent.atomic.AtomicInteger;
 
  28 import org.camunda.bpm.engine.delegate.BpmnError;
 
  29 import org.camunda.bpm.engine.delegate.DelegateExecution;
 
  30 import org.camunda.bpm.engine.delegate.ExecutionListener;
 
  31 import org.camunda.bpm.engine.delegate.JavaDelegate;
 
  32 import org.camunda.bpm.engine.impl.bpmn.behavior.ClassDelegateActivityBehavior;
 
  33 import org.camunda.bpm.engine.impl.bpmn.parser.AbstractBpmnParseListener;
 
  34 import org.camunda.bpm.engine.impl.bpmn.parser.BpmnParseListener;
 
  35 import org.camunda.bpm.engine.impl.cfg.AbstractProcessEnginePlugin;
 
  36 import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
 
  37 import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionEntity;
 
  38 import org.camunda.bpm.engine.impl.pvm.PvmTransition;
 
  39 import org.camunda.bpm.engine.impl.pvm.process.ActivityImpl;
 
  40 import org.camunda.bpm.engine.impl.pvm.process.TransitionImpl;
 
  41 import org.camunda.bpm.engine.impl.util.xml.Element;
 
  42 import org.onap.so.bpmn.core.WorkflowException;
 
  43 import org.springframework.stereotype.Component;
 
  44 import org.slf4j.Logger;
 
  45 import org.slf4j.LoggerFactory;
 
  48  * This plugin does the following:
 
  50  * <li>Adds logic at the start of every Call Activity to remove any existing WorkflowException object from the execution
 
  51  * (saving a copy of it in a different variable).</li>
 
  52  * <li>Adds logic at the end of every Call Activity to generate a MSOWorkflowException event if there is a
 
  53  * WorkflowException object in the execution.</li>
 
  57 public class WorkflowExceptionPlugin extends AbstractProcessEnginePlugin {
 
  58     private static final Logger logger = LoggerFactory.getLogger(WorkflowExceptionPlugin.class);
 
  60     private static final String WORKFLOW_EXCEPTION = "WorkflowException";
 
  63     public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
 
  64         List<BpmnParseListener> preParseListeners = processEngineConfiguration.getCustomPreBPMNParseListeners();
 
  66         if (preParseListeners == null) {
 
  67             preParseListeners = new ArrayList<>();
 
  68             processEngineConfiguration.setCustomPreBPMNParseListeners(preParseListeners);
 
  71         preParseListeners.add(new WorkflowExceptionParseListener());
 
  74     public static class WorkflowExceptionParseListener extends AbstractBpmnParseListener {
 
  76         public void parseProcess(Element processElement, ProcessDefinitionEntity processDefinition) {
 
  77             AtomicInteger triggerTaskIndex = new AtomicInteger(1);
 
  78             List<ActivityImpl> activities = new ArrayList<>(processDefinition.getActivities());
 
  79             recurse(activities, triggerTaskIndex);
 
  83          * Helper method that recurses (into subprocesses) over all the listed activities.
 
  85          * @param activities a list of workflow activities
 
  86          * @param triggerTaskIndex the index of the next trigger task (mutable)
 
  88         private void recurse(List<ActivityImpl> activities, AtomicInteger triggerTaskIndex) {
 
  89             for (ActivityImpl activity : activities) {
 
  90                 String type = (String) activity.getProperty("type");
 
  92                 if ("callActivity".equals(type)) {
 
  93                     // Add a WorkflowExceptionResetListener to clear the WorkflowException
 
  94                     // variable when each Call Activity starts.
 
  96                     activity.addListener(ExecutionListener.EVENTNAME_START, new WorkflowExceptionResetListener());
 
  98                     // Add a WorkflowExceptionTriggerTask after the call activity.
 
  99                     // It must be a task because a listener cannot be used to generate
 
 100                     // an event. Throwing BpmnError from an execution listener will
 
 101                     // cause the process to die.
 
 103                     List<PvmTransition> outTransitions = new ArrayList<>(activity.getOutgoingTransitions());
 
 105                     for (PvmTransition transition : outTransitions) {
 
 106                         String triggerTaskId = "WorkflowExceptionTriggerTask_" + triggerTaskIndex;
 
 108                         ActivityImpl triggerTask = activity.getFlowScope().createActivity(triggerTaskId);
 
 110                         ClassDelegateActivityBehavior behavior = new ClassDelegateActivityBehavior(
 
 111                                 WorkflowExceptionTriggerTask.class.getName(), new ArrayList<>(0));
 
 113                         triggerTask.setActivityBehavior(behavior);
 
 114                         triggerTask.setName("Workflow Exception Trigger Task " + triggerTaskIndex);
 
 115                         triggerTaskIndex.getAndIncrement();
 
 117                         TransitionImpl transitionImpl = (TransitionImpl) transition;
 
 118                         TransitionImpl triggerTaskOutTransition = triggerTask.createOutgoingTransition();
 
 119                         triggerTaskOutTransition.setDestination((ActivityImpl) transitionImpl.getDestination());
 
 120                         transitionImpl.setDestination(triggerTask);
 
 122                 } else if ("subProcess".equals(type)) {
 
 123                     recurse(new ArrayList<>(activity.getActivities()), triggerTaskIndex);
 
 130      * If there is a WorkflowException object in the execution, this method removes it (saving a copy of it in a
 
 131      * different variable).
 
 133     public static class WorkflowExceptionResetListener implements ExecutionListener {
 
 134         public void notify(DelegateExecution execution) throws Exception {
 
 135             Object workflowException = execution.getVariable(WORKFLOW_EXCEPTION);
 
 137             if (workflowException instanceof WorkflowException) {
 
 139                 String saveName = "SavedWorkflowException" + index;
 
 140                 while (execution.getVariable(saveName) != null) {
 
 141                     saveName = "SavedWorkflowException" + (++index);
 
 144                 logger.debug("WorkflowExceptionResetTask is moving WorkflowException to {}", saveName);
 
 146                 execution.setVariable(saveName, workflowException);
 
 147                 execution.setVariable(WORKFLOW_EXCEPTION, null);
 
 153      * Generates an MSOWorkflowException event if there is a WorkflowException object in the execution.
 
 155     public static class WorkflowExceptionTriggerTask implements JavaDelegate {
 
 156         public void execute(DelegateExecution execution) throws Exception {
 
 157             if (execution.getVariable(WORKFLOW_EXCEPTION) instanceof WorkflowException) {
 
 158                 logger.debug("WorkflowExceptionTriggerTask is generating a MSOWorkflowException event");
 
 159                 throw new BpmnError("MSOWorkflowException");