All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.camunda.demo.environment.simulation.TimeAwareDemoGenerator Maven / Gradle / Ivy

package com.camunda.demo.environment.simulation;

import java.io.ByteArrayInputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.apache.commons.math3.distribution.NormalDistribution;
import org.camunda.bpm.application.ProcessApplicationReference;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.camunda.bpm.engine.impl.util.ClockUtil;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.runtime.EventSubscription;
import org.camunda.bpm.engine.runtime.Job;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.BaseElement;
import org.camunda.bpm.model.bpmn.instance.BusinessRuleTask;
import org.camunda.bpm.model.bpmn.instance.ConditionExpression;
import org.camunda.bpm.model.bpmn.instance.ExclusiveGateway;
import org.camunda.bpm.model.bpmn.instance.ExtensionElements;
import org.camunda.bpm.model.bpmn.instance.InclusiveGateway;
import org.camunda.bpm.model.bpmn.instance.ScriptTask;
import org.camunda.bpm.model.bpmn.instance.SendTask;
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.bpmn.instance.ServiceTask;
import org.camunda.bpm.model.bpmn.instance.UserTask;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaExecutionListener;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperties;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperty;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaScript;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaTaskListener;
import org.camunda.bpm.model.xml.ModelInstance;
import org.camunda.bpm.model.xml.impl.util.IoUtil;
import org.camunda.bpm.model.xml.instance.ModelElementInstance;

/**
 * Classloading: Currently everything is done as JavaScript - so no classes are
 * necessary
 */
public class TimeAwareDemoGenerator {

  private static final Logger log = Logger.getLogger(TimeAwareDemoGenerator.class.getName());

  private String processDefinitionKey;
  private int numberOfDaysInPast;
  private StatisticalDistribution timeBetweenStartsBusinessDays;
  private String startTimeBusinessDay = "08:30";
  private String endTimeBusinessDay = "18:00";

  private StatisticalDistribution timeBetweenStartsWeekend;

  // private ;
  private ProcessDefinition processDefinition;
  private ProcessEngine engine;

  private Map distributions = new HashMap();
  private String originalBpmn;

  private ProcessApplicationReference processApplicationReference;

  public TimeAwareDemoGenerator(ProcessEngine engine, ProcessApplicationReference processApplicationReference) {
    this.engine = engine;
    this.processApplicationReference = processApplicationReference;
  }

  public TimeAwareDemoGenerator(ProcessEngine processEngine) {
    this.engine = processEngine;
  }

  public void generateData() {
    tweakProcessDefinition();
    synchronized (engine) {        
      ((ProcessEngineConfigurationImpl)engine.getProcessEngineConfiguration()).getJobExecutor().shutdown();
      try {
          startMultipleProcessInstances();
      } finally {
        restoreOriginalProcessDefinition();      
        ((ProcessEngineConfigurationImpl)engine.getProcessEngineConfiguration()).getJobExecutor().start();    
      }
    }
  }

  protected void restoreOriginalProcessDefinition() {
    log.info("restore original process definition");
    
    try {
      Deployment deployment = engine.getRepositoryService().createDeployment() //
          .addInputStream(processDefinitionKey + ".bpmn", new ByteArrayInputStream(originalBpmn.getBytes("UTF-8"))) //
          .deploy();
      if (processApplicationReference != null) {
        engine.getManagementService().registerProcessApplication(deployment.getId(), processApplicationReference);
      }
    } catch (Exception ex) {
      throw new RuntimeException("Could not deploy tweaked process definition",  ex);
    }
  }

  protected void tweakProcessDefinition() {
    log.info("tweak process definition " + processDefinitionKey);

    processDefinition = engine.getRepositoryService().createProcessDefinitionQuery() //
        .processDefinitionKey(processDefinitionKey) //
        .latestVersion() //
        .singleResult();
    if (processDefinition==null) {
      throw new RuntimeException("Process with key '" + processDefinitionKey + "' not found.");
    }
    // store original process application reference
    if (processApplicationReference==null) {
      processApplicationReference = ((ProcessEngineConfigurationImpl)engine.getProcessEngineConfiguration()).getProcessApplicationManager().getProcessApplicationForDeployment(processDefinition.getDeploymentId());
    }
    
    BpmnModelInstance bpmn = engine.getRepositoryService().getBpmnModelInstance(processDefinition.getId());

    originalBpmn = IoUtil.convertXmlDocumentToString(bpmn.getDocument());
    // do not do a validation here as it caused quite strange trouble
    log.finer("-----\n" + originalBpmn + "\n------");

    Collection serviceTasks = bpmn.getModelElementsByType(bpmn.getModel().getType(ServiceTask.class));
    Collection sendTasks = bpmn.getModelElementsByType(bpmn.getModel().getType(SendTask.class));
    Collection businessRuleTasks = bpmn.getModelElementsByType(bpmn.getModel().getType(BusinessRuleTask.class));
    Collection scriptTasks = bpmn.getModelElementsByType(bpmn.getModel().getType(ScriptTask.class));
    Collection userTasks = bpmn.getModelElementsByType(bpmn.getModel().getType(UserTask.class));
    Collection executionListeners = bpmn.getModelElementsByType(bpmn.getModel().getType(CamundaExecutionListener.class));
    Collection taskListeners = bpmn.getModelElementsByType(bpmn.getModel().getType(CamundaTaskListener.class));
    Collection xorGateways = bpmn.getModelElementsByType(bpmn.getModel().getType(ExclusiveGateway.class));
    Collection orGateways = bpmn.getModelElementsByType(bpmn.getModel().getType(InclusiveGateway.class));

    Collection scripts = bpmn.getModelElementsByType(bpmn.getModel().getType(CamundaScript.class));

    for (ModelElementInstance modelElementInstance : serviceTasks) {
      ServiceTask serviceTask = ((ServiceTask) modelElementInstance);
      serviceTask.setCamundaClass(null);
      // TODO: Wait for https://app.camunda.com/jira/browse/CAM-4178 and set
      // to null!
      // serviceTask.setCamundaDelegateExpression(null);
      // Workaround:
      serviceTask.removeAttributeNs("http://activiti.org/bpmn", "delegateExpression");
      serviceTask.removeAttributeNs("http://camunda.org/schema/1.0/bpmn", "delegateExpression");
      
      serviceTask.setCamundaExpression("#{true}"); // Noop      
    }
    for (ModelElementInstance modelElementInstance : sendTasks) {
      SendTask serviceTask = ((SendTask) modelElementInstance);
      serviceTask.setCamundaClass(null);
      serviceTask.removeAttributeNs("http://activiti.org/bpmn", "delegateExpression");      
      serviceTask.removeAttributeNs("http://camunda.org/schema/1.0/bpmn", "delegateExpression");      
      serviceTask.setCamundaExpression("#{true}"); // Noop      
    }
    for (ModelElementInstance modelElementInstance : businessRuleTasks) {
      BusinessRuleTask businessRuleTask = (BusinessRuleTask) modelElementInstance;
      businessRuleTask.removeAttributeNs("http://activiti.org/bpmn", "decisionRef"); // DMN ref from 7.4 on
      businessRuleTask.removeAttributeNs("http://camunda.org/schema/1.0/bpmn", "decisionRef"); // DMN ref from 7.4 on
      businessRuleTask.setCamundaClass(null);
      businessRuleTask.removeAttributeNs("http://activiti.org/bpmn", "delegateExpression");      
      businessRuleTask.removeAttributeNs("http://camunda.org/schema/1.0/bpmn", "delegateExpression");
      
      businessRuleTask.setCamundaExpression("#{true}"); // Noop      
    }
    for (ModelElementInstance modelElementInstance : executionListeners) {
      CamundaExecutionListener executionListener = (CamundaExecutionListener) modelElementInstance;
//      executionListener.setCamundaClass(null);
      executionListener.removeAttributeNs("http://activiti.org/bpmn", "class");      
      executionListener.removeAttributeNs("http://camunda.org/schema/1.0/bpmn", "class");      

      executionListener.removeAttributeNs("http://activiti.org/bpmn", "delegateExpression");      
      executionListener.removeAttributeNs("http://camunda.org/schema/1.0/bpmn", "delegateExpression");      
      executionListener.setCamundaExpression("#{true}"); // Noop      
    }
    for (ModelElementInstance modelElementInstance : scripts) {
      CamundaScript script = (CamundaScript) modelElementInstance;
//      executionListener.setCamundaClass(null);
      script.setTextContent(""); // java.lang.System.out.println('x');
      script.setCamundaScriptFormat("javascript");
      script.removeAttributeNs("http://activiti.org/bpmn", "resource");
      script.removeAttributeNs("http://camunda.org/schema/1.0/bpmn", "resource");
    }

    for (ModelElementInstance modelElementInstance : userTasks) {
      UserTask userTask = ((UserTask) modelElementInstance);
      userTask.setCamundaAssignee(null);
      userTask.setCamundaCandidateGroups(null);
    }

    for (ModelElementInstance modelElementInstance : xorGateways) {
      ExclusiveGateway xorGateway = ((ExclusiveGateway) modelElementInstance);
      tweakGateway(xorGateway);
    }

    // Bpmn.validateModel(bpmn);
    String xmlString = Bpmn.convertToString(bpmn);
    try {
      engine.getRepositoryService().createDeployment() //
  //        .addString(processDefinitionKey + ".bpmn", xmlString) //
  //        .addModelInstance(processDefinitionKey + ".bpmn", bpmn) //
          .addInputStream(processDefinitionKey + ".bpmn", new ByteArrayInputStream(xmlString.getBytes("UTF-8")))
          .deploy();
    } catch (Exception ex) {
      throw new RuntimeException("Could not deploy tweaked process definition",  ex);
    }
  }

  protected void tweakGateway(ExclusiveGateway xorGateway) {
    ModelInstance bpmn = xorGateway.getModelInstance();

    double probabilitySum = 0;
    // Process Variable used to store sample from distribution to decide for
    // outgoing transition
    String var = "SIM_SAMPLE_VALUE_" + xorGateway.getId();

    Collection flows = xorGateway.getOutgoing();
    if (flows.size() > 1) { // if outgoing flows = 1 it is a joining gateway
      for (SequenceFlow sequenceFlow : flows) {
        double probability = Double.valueOf(readCamundaProperty(sequenceFlow, "probability"));

        ConditionExpression conditionExpression = bpmn.newInstance(ConditionExpression.class);
        conditionExpression.setTextContent("#{" + var + " >= " + probabilitySum + " && " + var + " < " + (probabilitySum + probability) + "}");
        sequenceFlow.setConditionExpression(conditionExpression);

        probabilitySum += probability;
      }

      // add execution listener to do decision based on random which corresponds
      // to configured probabilities
      // (because of expressions on outgoing sequence flows)
      CamundaExecutionListener executionListener = bpmn.newInstance(CamundaExecutionListener.class);
      executionListener.setCamundaEvent("start");
      CamundaScript script = bpmn.newInstance(CamundaScript.class);
      script.setTextContent(//
          "sample = com.camunda.demo.environment.simulation.StatisticsHelper.nextSample(" + probabilitySum + ");\n" + "execution.setVariable('" + var + "', sample);");
      script.setCamundaScriptFormat("Javascript");
      executionListener.setCamundaScript(script);

      if (xorGateway.getExtensionElements() == null) {
        ExtensionElements extensionElements = bpmn.newInstance(ExtensionElements.class);
        xorGateway.addChildElement(extensionElements);
      }
      xorGateway.getExtensionElements().addChildElement(executionListener);
    }
  }

  protected void copyTimeField(Calendar calFrom, Calendar calTo, int... calendarFieldConstant) {
    for (int i = 0; i < calendarFieldConstant.length; i++) {
      calTo.set(calendarFieldConstant[i], calFrom.get(calendarFieldConstant[i]));
    }
  }

  private boolean isInTimeFrame(Calendar cal, String startTime, String endTime) {
    try {
      // TODO: maybe cache?
      Date startDate = new SimpleDateFormat("HH:mm").parse(startTime);
      Date endDate = new SimpleDateFormat("HH:mm").parse(endTime);
      Calendar startCal = Calendar.getInstance();
      startCal.setTime(startDate);
      copyTimeField(cal, startCal, Calendar.YEAR, Calendar.DAY_OF_YEAR);

      Calendar endCal = Calendar.getInstance();
      endCal.setTime(endDate);
      copyTimeField(cal, endCal, Calendar.YEAR, Calendar.DAY_OF_YEAR);

      return (startCal.before(cal) && cal.before(endCal));
    } catch (ParseException ex) {
      throw new RuntimeException("Could not parse time format: '" + startTime + "' or '" + endTime + "'", ex);
    }
  }

  protected void startMultipleProcessInstances() {
    try {
      log.info("start multiple process instances for " + numberOfDaysInPast + " workingdays in the past");

      // for all desired days in past
      for (int i = numberOfDaysInPast; i >= 0; i--) {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DAY_OF_YEAR, -1 * i);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);

        int dayOfYear = cal.get(Calendar.DAY_OF_YEAR);

        // now lets start process instances on that day
        if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
          // weekend
        } else {
          while (cal.get(Calendar.DAY_OF_YEAR) == dayOfYear) {
            // business day (OK - simplified - do not take holidays into
            // account)
            double time = timeBetweenStartsBusinessDays.nextSample();
            cal.add(Calendar.SECOND, (int) Math.round(time));
            if (isInTimeFrame(cal, startTimeBusinessDay, endTimeBusinessDay)) {
              ClockUtil.setCurrentTime(cal.getTime());

              System.out.print(".");
              runSingleProcessInstance();

              // Housekeeping for safety (as the run might have changed the
              // clock)
              ClockUtil.setCurrentTime(cal.getTime());
            }
          }
        }
      }

    } finally {
      ClockUtil.reset();
    }
  }

  protected void runSingleProcessInstance() {
    ProcessInstance pi = engine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey);
    boolean piRunning = true;

    while (piRunning) {
      List tasks = engine.getTaskService().createTaskQuery().processInstanceId(pi.getId()).list();
//      List messages = engine.getRuntimeService().createEventSubscriptionQuery().processInstanceId(pi.getId()).eventType("message").list();
      List jobs = engine.getManagementService().createJobQuery().processInstanceId(pi.getId()).list();
      // TODO:
      // engine.getRuntimeService().createEventSubscriptionQuery().processInstanceId(pi.getId()).eventType("signal").list();

      handleTasks(pi, tasks);
//      handleMessages(pi, messages);
      handleJobs(jobs);

      // do queries again if we have changed anything in the process instance
      // for the moment we do not query processInstance.isEnded as we are not
      // sure
      // if we have yet tackled all situations (read: we are sure we haven't
      // yet).
      // This will at least not lead to endless loops
      piRunning = (tasks.size() > 0 
          //|| messages.size() > 0 
          || jobs.size() > 0);

      // TODO: Stop when we reach the NOW time (might leave open tasks - but
      // that is OK!)
    }

  }

  protected void handleTasks(ProcessInstance pi, List tasks) {
    for (org.camunda.bpm.engine.task.Task task : tasks) {
      String id = task.getTaskDefinitionKey();

      if (!distributions.containsKey(id)) {
        NormalDistribution distribution = createDistributionForElement(pi, id);
        distributions.put(id, distribution);
      }

      Calendar cal = Calendar.getInstance();
      cal.setTime(task.getCreateTime());
      double timeToWait = distributions.get(task.getTaskDefinitionKey()).sample();
      if (timeToWait <= 0) {
        timeToWait = 1;
      }
      cal.add(Calendar.SECOND, (int) Math.round(timeToWait));
      ClockUtil.setCurrentTime(cal.getTime());

      engine.getTaskService().complete(task.getId());
    }
  }

  protected void handleMessages(ProcessInstance pi, List messages) {
    for (EventSubscription eventSubscription : messages) {
      String id = eventSubscription.getActivityId();
      if (!distributions.containsKey(id)) {
        NormalDistribution distribution = createDistributionForElement(pi, id);
        distributions.put(id, distribution);
      }

      Calendar cal = Calendar.getInstance();
      cal.setTime(eventSubscription.getCreated());
      double timeToWait = distributions.get(id).sample();
      cal.add(Calendar.SECOND, (int) Math.round(timeToWait));
      ClockUtil.setCurrentTime(cal.getTime());

      engine.getRuntimeService().createMessageCorrelation(eventSubscription.getEventName()).processInstanceId(pi.getId()).correlate();
    }
  }

  protected void handleJobs(List jobs) {
    for (Job job : jobs) {
      if (engine.getManagementService().createJobQuery().jobId(job.getId()).count()==1) {          
        engine.getManagementService().executeJob(job.getId());
      }
      else {
        System.out.println("COULD NOT EXECUTE JOB " + job);
      }
    }
  }

  protected NormalDistribution createDistributionForElement(ProcessInstance pi, String id) {
    try {
      BaseElement taskElement = engine.getRepositoryService().getBpmnModelInstance(pi.getProcessDefinitionId()).getModelElementById(id);
      double durationMean = Double.parseDouble(readCamundaProperty(taskElement, "durationMean"));
      double durationStandardDeviation = Double.parseDouble(readCamundaProperty(taskElement, "durationSd"));
  
      NormalDistribution distribution = new NormalDistribution(durationMean, durationStandardDeviation);
      return distribution;
    }
    catch (Exception ex) {
      throw new RuntimeException("Could not read distribution for element '" + id + "' of process definition '" + pi.getProcessDefinitionId() + "'", ex);
    }
  }

  private String readCamundaProperty(BaseElement modelElementInstance, String propertyName) {
    Collection properties = modelElementInstance.getExtensionElements().getElementsQuery() //
        .filterByType(CamundaProperties.class) //
        .singleResult() //
        .getCamundaProperties();
    for (CamundaProperty property : properties) {
      // in 7.1 one has to use: property.getAttributeValue("name")
      if (propertyName.equals(property.getCamundaName())) {
        return property.getCamundaValue();
      }
    }
    return null;
  }

  public TimeAwareDemoGenerator timeBetweenStartsBusinessDays(double mean, double standardDeviation) {
    timeBetweenStartsBusinessDays = new StatisticalDistribution(mean, standardDeviation);
    return this;
  }

  public TimeAwareDemoGenerator processDefinitionKey(String processDefinitionKey) {
    this.processDefinitionKey = processDefinitionKey;
    return this;
  }

  public TimeAwareDemoGenerator numberOfDaysInPast(int numberOfDaysInPast) {
    this.numberOfDaysInPast = numberOfDaysInPast;
    return this;
  }

  public ProcessDefinition getProcessDefinition() {
    return processDefinition;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy