public boolean isNew() {
   return currentState == NEW.getStateCode();
 }
/** @author Roman Smirnov */
public abstract class CmmnExecution extends CoreExecution implements CmmnCaseInstance {

  protected static final CmmnBehaviorLogger LOG = ProcessEngineLogger.CMNN_BEHAVIOR_LOGGER;

  private static final long serialVersionUID = 1L;

  protected transient CmmnCaseDefinition caseDefinition;

  // current position //////////////////////////////////////

  /** current activity */
  protected transient CmmnActivity activity;

  protected boolean required = false;

  protected int previousState;

  protected int currentState = NEW.getStateCode();

  protected Queue<VariableEvent> variableEventsQueue;

  protected transient TaskEntity task;

  /**
   * This property will be used if <code>this</code> {@link CmmnExecution} is in state {@link
   * CaseExecutionState#NEW} to note that an entry criterion is satisfied.
   */
  protected boolean entryCriterionSatisfied = false;

  public CmmnExecution() {}

  // plan items ///////////////////////////////////////////////////////////////

  public abstract List<? extends CmmnExecution> getCaseExecutions();

  protected abstract List<? extends CmmnExecution> getCaseExecutionsInternal();

  public CmmnExecution findCaseExecution(String activityId) {
    if ((getActivity() != null) && (getActivity().getId().equals(activityId))) {
      return this;
    }
    for (CmmnExecution nestedExecution : getCaseExecutions()) {
      CmmnExecution result = nestedExecution.findCaseExecution(activityId);
      if (result != null) {
        return result;
      }
    }
    return null;
  }

  // task /////////////////////////////////////////////////////////////////////

  public TaskEntity getTask() {
    return this.task;
  }

  public void setTask(Task task) {
    this.task = (TaskEntity) task;
  }

  public TaskEntity createTask(TaskDecorator taskDecorator) {
    TaskEntity task = TaskEntity.createAndInsert(this);

    setTask(task);

    taskDecorator.decorate(task, this);

    Context.getCommandContext().getHistoricTaskInstanceManager().createHistoricTask(task);

    // All properties set, now firing 'create' event
    task.fireEvent(TaskListener.EVENTNAME_CREATE);

    return task;
  }

  // super execution  ////////////////////////////////////////////////////////

  public abstract PvmExecutionImpl getSuperExecution();

  public abstract void setSuperExecution(PvmExecutionImpl superExecution);

  // sub process instance ////////////////////////////////////////////////////

  public abstract PvmExecutionImpl getSubProcessInstance();

  public abstract void setSubProcessInstance(PvmExecutionImpl subProcessInstance);

  public abstract PvmExecutionImpl createSubProcessInstance(PvmProcessDefinition processDefinition);

  public abstract PvmExecutionImpl createSubProcessInstance(
      PvmProcessDefinition processDefinition, String businessKey);

  public abstract PvmExecutionImpl createSubProcessInstance(
      PvmProcessDefinition processDefinition, String businessKey, String caseInstanceId);

  // sub-/super- case instance ////////////////////////////////////////////////////

  public abstract CmmnExecution getSubCaseInstance();

  public abstract void setSubCaseInstance(CmmnExecution subCaseInstance);

  public abstract CmmnExecution createSubCaseInstance(CmmnCaseDefinition caseDefinition);

  public abstract CmmnExecution createSubCaseInstance(
      CmmnCaseDefinition caseDefinition, String businessKey);

  public abstract CmmnExecution getSuperCaseExecution();

  public abstract void setSuperCaseExecution(CmmnExecution superCaseExecution);

  // sentry //////////////////////////////////////////////////////////////////

  // sentry: (1) create and initialize sentry parts

  protected abstract CmmnSentryPart newSentryPart();

  protected abstract void addSentryPart(CmmnSentryPart sentryPart);

  public void createSentryParts() {
    CmmnActivity activity = getActivity();
    ensureNotNull("Case execution '" + id + "': has no current activity", "activity", activity);

    List<CmmnSentryDeclaration> sentries = activity.getSentries();

    if (sentries != null && !sentries.isEmpty()) {

      for (CmmnSentryDeclaration sentryDeclaration : sentries) {

        CmmnIfPartDeclaration ifPartDeclaration = sentryDeclaration.getIfPart();
        if (ifPartDeclaration != null) {
          CmmnSentryPart ifPart = createIfPart(sentryDeclaration, ifPartDeclaration);
          addSentryPart(ifPart);
        }

        List<CmmnOnPartDeclaration> onPartDeclarations = sentryDeclaration.getOnParts();

        for (CmmnOnPartDeclaration onPartDeclaration : onPartDeclarations) {
          CmmnSentryPart onPart = createOnPart(sentryDeclaration, onPartDeclaration);
          addSentryPart(onPart);
        }

        List<CmmnVariableOnPartDeclaration> variableOnPartDeclarations =
            sentryDeclaration.getVariableOnParts();
        for (CmmnVariableOnPartDeclaration variableOnPartDeclaration : variableOnPartDeclarations) {
          CmmnSentryPart variableOnPart =
              createVariableOnPart(sentryDeclaration, variableOnPartDeclaration);
          addSentryPart(variableOnPart);
        }
      }
    }
  }

  protected CmmnSentryPart createOnPart(
      CmmnSentryDeclaration sentryDeclaration, CmmnOnPartDeclaration onPartDeclaration) {
    CmmnSentryPart sentryPart = createSentryPart(sentryDeclaration, PLAN_ITEM_ON_PART);

    // set the standard event
    String standardEvent = onPartDeclaration.getStandardEvent();
    sentryPart.setStandardEvent(standardEvent);

    // set source case execution
    CmmnActivity source = onPartDeclaration.getSource();
    ensureNotNull(
        "The source of sentry '" + sentryDeclaration.getId() + "' is null.", "source", source);

    String sourceActivityId = source.getId();
    sentryPart.setSource(sourceActivityId);

    // TODO: handle also sentryRef!!! (currently not implemented on purpose)

    return sentryPart;
  }

  protected CmmnSentryPart createIfPart(
      CmmnSentryDeclaration sentryDeclaration, CmmnIfPartDeclaration ifPartDeclaration) {
    return createSentryPart(sentryDeclaration, IF_PART);
  }

  protected CmmnSentryPart createVariableOnPart(
      CmmnSentryDeclaration sentryDeclaration,
      CmmnVariableOnPartDeclaration variableOnPartDeclaration) {
    CmmnSentryPart sentryPart = createSentryPart(sentryDeclaration, VARIABLE_ON_PART);

    // set the variable event
    String variableEvent = variableOnPartDeclaration.getVariableEvent();
    sentryPart.setVariableEvent(variableEvent);

    // set the variable name
    String variableName = variableOnPartDeclaration.getVariableName();
    sentryPart.setVariableName(variableName);

    return sentryPart;
  }

  protected CmmnSentryPart createSentryPart(CmmnSentryDeclaration sentryDeclaration, String type) {
    CmmnSentryPart newSentryPart = newSentryPart();

    // set the type
    newSentryPart.setType(type);

    // set the case instance and case execution
    newSentryPart.setCaseInstance(getCaseInstance());
    newSentryPart.setCaseExecution(this);

    // set sentry id
    String sentryId = sentryDeclaration.getId();
    newSentryPart.setSentryId(sentryId);

    return newSentryPart;
  }

  // sentry: (2) handle transitions

  public void handleChildTransition(CmmnExecution child, String transition) {
    // Step 1: collect all affected sentries
    List<String> affectedSentries = collectAffectedSentries(child, transition);

    // Step 2: fire force update on all case sentry part
    // contained by a affected sentry to provoke an
    // OptimisticLockingException
    forceUpdateOnSentries(affectedSentries);

    // Step 3: check each affected sentry whether it is satisfied.
    // the returned list contains all satisfied sentries
    List<String> satisfiedSentries = getSatisfiedSentries(affectedSentries);

    // Step 4: reset sentries -> satisfied == false
    resetSentries(satisfiedSentries);

    // Step 5: fire satisfied sentries
    fireSentries(satisfiedSentries);
  }

  public void fireIfOnlySentryParts() {
    // the following steps are a workaround, because setVariable()
    // does not check nor fire a sentry!!!
    Set<String> affectedSentries = new HashSet<String>();
    List<CmmnSentryPart> sentryParts = collectSentryParts(getSentries());
    for (CmmnSentryPart sentryPart : sentryParts) {
      if (isNotSatisfiedIfPartOnly(sentryPart)) {
        affectedSentries.add(sentryPart.getSentryId());
      }
    }

    // Step 7: check each not affected sentry whether it is satisfied
    List<String> satisfiedSentries = getSatisfiedSentries(new ArrayList<String>(affectedSentries));

    // Step 8: reset sentries -> satisfied == false
    resetSentries(satisfiedSentries);

    // Step 9: fire satisfied sentries
    fireSentries(satisfiedSentries);
  }

  public void handleVariableTransition(String variableName, String transition) {
    Map<String, List<CmmnSentryPart>> sentries = collectAllSentries();

    List<CmmnSentryPart> sentryParts = collectSentryParts(sentries);

    List<String> affectedSentries =
        collectAffectedSentriesWithVariableOnParts(variableName, transition, sentryParts);

    List<CmmnSentryPart> affectedSentryParts = getAffectedSentryParts(sentries, affectedSentries);
    forceUpdateOnCaseSentryParts(affectedSentryParts);

    List<String> allSentries = new ArrayList<String>(sentries.keySet());

    List<String> satisfiedSentries = getSatisfiedSentriesInExecutionTree(allSentries, sentries);

    List<CmmnSentryPart> satisfiedSentryParts = getAffectedSentryParts(sentries, satisfiedSentries);
    resetSentryParts(satisfiedSentryParts);

    fireSentries(satisfiedSentries);
  }

  protected List<String> collectAffectedSentries(CmmnExecution child, String transition) {
    List<? extends CmmnSentryPart> sentryParts = getCaseSentryParts();

    List<String> affectedSentries = new ArrayList<String>();

    for (CmmnSentryPart sentryPart : sentryParts) {

      // necessary for backward compatibility
      String sourceCaseExecutionId = sentryPart.getSourceCaseExecutionId();
      String sourceRef = sentryPart.getSource();
      if (child.getActivityId().equals(sourceRef) || child.getId().equals(sourceCaseExecutionId)) {

        String standardEvent = sentryPart.getStandardEvent();
        if (transition.equals(standardEvent)) {
          addIdIfNotSatisfied(affectedSentries, sentryPart);
        }
      }
    }

    return affectedSentries;
  }

  protected boolean isNotSatisfiedIfPartOnly(CmmnSentryPart sentryPart) {
    return IF_PART.equals(sentryPart.getType())
        && getSentries().get(sentryPart.getSentryId()).size() == 1
        && !sentryPart.isSatisfied();
  }

  protected void addIdIfNotSatisfied(List<String> affectedSentries, CmmnSentryPart sentryPart) {
    if (!sentryPart.isSatisfied()) {
      // if it is not already satisfied, then set the
      // current case sentry part to satisfied (=true).
      String sentryId = sentryPart.getSentryId();
      sentryPart.setSatisfied(true);

      // collect the id of affected sentry.
      if (!affectedSentries.contains(sentryId)) {
        affectedSentries.add(sentryId);
      }
    }
  }

  protected List<String> collectAffectedSentriesWithVariableOnParts(
      String variableName, String variableEvent, List<CmmnSentryPart> sentryParts) {

    List<String> affectedSentries = new ArrayList<String>();

    for (CmmnSentryPart sentryPart : sentryParts) {

      String sentryVariableName = sentryPart.getVariableName();
      String sentryVariableEvent = sentryPart.getVariableEvent();
      CmmnExecution execution = sentryPart.getCaseExecution();
      if (VARIABLE_ON_PART.equals(sentryPart.getType())
          && sentryVariableName.equals(variableName)
          && sentryVariableEvent.equals(variableEvent)
          && !hasVariableWithSameNameInParent(execution, sentryVariableName)) {

        addIdIfNotSatisfied(affectedSentries, sentryPart);
      }
    }

    return affectedSentries;
  }

  protected boolean hasVariableWithSameNameInParent(CmmnExecution execution, String variableName) {
    while (execution != null) {
      if (execution.getId().equals(getId())) {
        return false;
      }
      TypedValue variableTypedValue = execution.getVariableLocalTyped(variableName);
      if (variableTypedValue != null) {
        return true;
      }
      execution = execution.getParent();
    }
    return false;
  }

  protected Map<String, List<CmmnSentryPart>> collectAllSentries() {
    Map<String, List<CmmnSentryPart>> sentries = new HashMap<String, List<CmmnSentryPart>>();
    List<? extends CmmnExecution> caseExecutions = getCaseExecutions();
    for (CmmnExecution caseExecution : caseExecutions) {
      sentries.putAll(caseExecution.collectAllSentries());
    }
    sentries.putAll(getSentries());
    return sentries;
  }

  protected List<CmmnSentryPart> getAffectedSentryParts(
      Map<String, List<CmmnSentryPart>> allSentries, List<String> affectedSentries) {
    List<CmmnSentryPart> affectedSentryParts = new ArrayList<CmmnSentryPart>();
    for (String affectedSentryId : affectedSentries) {
      affectedSentryParts.addAll(allSentries.get(affectedSentryId));
    }
    return affectedSentryParts;
  }

  protected List<CmmnSentryPart> collectSentryParts(Map<String, List<CmmnSentryPart>> sentries) {
    List<CmmnSentryPart> sentryParts = new ArrayList<CmmnSentryPart>();
    for (String sentryId : sentries.keySet()) {
      sentryParts.addAll(sentries.get(sentryId));
    }
    return sentryParts;
  }

  protected void forceUpdateOnCaseSentryParts(List<CmmnSentryPart> sentryParts) {
    // set for each case sentry part forceUpdate flag to true to provoke
    // an OptimisticLockingException if different case sentry parts of the
    // same sentry has been satisfied concurrently.
    for (CmmnSentryPart sentryPart : sentryParts) {
      if (sentryPart instanceof CaseSentryPartEntity) {
        CaseSentryPartEntity sentryPartEntity = (CaseSentryPartEntity) sentryPart;
        sentryPartEntity.forceUpdate();
      }
    }
  }

  /** Checks for each given sentry id whether the corresponding sentry is satisfied. */
  protected List<String> getSatisfiedSentries(List<String> sentryIds) {
    List<String> result = new ArrayList<String>();

    if (sentryIds != null) {

      for (String sentryId : sentryIds) {

        if (isSentrySatisfied(sentryId)) {
          result.add(sentryId);
        }
      }
    }

    return result;
  }

  /**
   * Checks for each given sentry id in the execution tree whether the corresponding sentry is
   * satisfied.
   */
  protected List<String> getSatisfiedSentriesInExecutionTree(
      List<String> sentryIds, Map<String, List<CmmnSentryPart>> allSentries) {
    List<String> result = new ArrayList<String>();

    if (sentryIds != null) {

      for (String sentryId : sentryIds) {
        List<CmmnSentryPart> sentryParts = allSentries.get(sentryId);
        if (isSentryPartsSatisfied(sentryId, sentryParts)) {
          result.add(sentryId);
        }
      }
    }

    return result;
  }

  protected void forceUpdateOnSentries(List<String> sentryIds) {
    for (String sentryId : sentryIds) {
      List<? extends CmmnSentryPart> sentryParts = findSentry(sentryId);
      // set for each case sentry part forceUpdate flag to true to provoke
      // an OptimisticLockingException if different case sentry parts of the
      // same sentry has been satisfied concurrently.
      for (CmmnSentryPart sentryPart : sentryParts) {
        if (sentryPart instanceof CaseSentryPartEntity) {
          CaseSentryPartEntity sentryPartEntity = (CaseSentryPartEntity) sentryPart;
          sentryPartEntity.forceUpdate();
        }
      }
    }
  }

  protected void resetSentries(List<String> sentries) {
    for (String sentry : sentries) {
      List<CmmnSentryPart> parts = getSentries().get(sentry);
      for (CmmnSentryPart part : parts) {
        part.setSatisfied(false);
      }
    }
  }

  protected void resetSentryParts(List<CmmnSentryPart> parts) {
    for (CmmnSentryPart part : parts) {
      part.setSatisfied(false);
    }
  }

  protected void fireSentries(List<String> satisfiedSentries) {
    if (satisfiedSentries != null && !satisfiedSentries.isEmpty()) {
      // if there are satisfied sentries, trigger the associated
      // case executions

      // 1. propagate to all child case executions ///////////////////////////////////////////

      // collect the execution tree.
      ArrayList<CmmnExecution> children = new ArrayList<CmmnExecution>();
      collectCaseExecutionsInExecutionTree(children);

      for (CmmnExecution currentChild : children) {

        // check and fire first exitCriteria
        currentChild.checkAndFireExitCriteria(satisfiedSentries);

        // then trigger entryCriteria
        currentChild.checkAndFireEntryCriteria(satisfiedSentries);
      }

      // 2. check exit criteria of the case instance //////////////////////////////////////////

      if (isCaseInstanceExecution() && isActive()) {
        checkAndFireExitCriteria(satisfiedSentries);
      }
    }
  }

  protected void collectCaseExecutionsInExecutionTree(List<CmmnExecution> children) {
    for (CmmnExecution child : getCaseExecutions()) {
      child.collectCaseExecutionsInExecutionTree(children);
    }
    children.addAll(getCaseExecutions());
  }

  protected void checkAndFireExitCriteria(List<String> satisfiedSentries) {
    if (isActive()) {
      CmmnActivity activity = getActivity();
      ensureNotNull(
          PvmException.class,
          "Case execution '" + getId() + "': has no current activity.",
          "activity",
          activity);

      // trigger first exitCriteria
      List<CmmnSentryDeclaration> exitCriteria = activity.getExitCriteria();
      for (CmmnSentryDeclaration sentryDeclaration : exitCriteria) {

        if (sentryDeclaration != null && satisfiedSentries.contains(sentryDeclaration.getId())) {
          fireExitCriteria();
          break;
        }
      }
    }
  }

  protected void checkAndFireEntryCriteria(List<String> satisfiedSentries) {
    if (isAvailable() || isNew()) {
      // do that only, when this child case execution
      // is available

      CmmnActivity activity = getActivity();
      ensureNotNull(
          PvmException.class,
          "Case execution '" + getId() + "': has no current activity.",
          "activity",
          activity);

      List<CmmnSentryDeclaration> criteria = activity.getEntryCriteria();
      for (CmmnSentryDeclaration sentryDeclaration : criteria) {
        if (sentryDeclaration != null && satisfiedSentries.contains(sentryDeclaration.getId())) {
          if (isAvailable()) {
            fireEntryCriteria();
          } else {
            entryCriterionSatisfied = true;
          }
          break;
        }
      }
    }
  }

  public void fireExitCriteria() {
    performOperation(CASE_EXECUTION_FIRE_EXIT_CRITERIA);
  }

  public void fireEntryCriteria() {
    performOperation(CASE_EXECUTION_FIRE_ENTRY_CRITERIA);
  }

  // sentry: (3) helper

  public abstract List<? extends CmmnSentryPart> getCaseSentryParts();

  protected abstract List<? extends CmmnSentryPart> findSentry(String sentryId);

  protected abstract Map<String, List<CmmnSentryPart>> getSentries();

  public boolean isSentrySatisfied(String sentryId) {
    List<? extends CmmnSentryPart> sentryParts = findSentry(sentryId);
    return isSentryPartsSatisfied(sentryId, sentryParts);
  }

  protected boolean isSentryPartsSatisfied(
      String sentryId, List<? extends CmmnSentryPart> sentryParts) {
    // if part will be evaluated in the end
    CmmnSentryPart ifPart = null;

    if (sentryParts != null && !sentryParts.isEmpty()) {
      for (CmmnSentryPart sentryPart : sentryParts) {

        if (PLAN_ITEM_ON_PART.equals(sentryPart.getType())) {

          if (!sentryPart.isSatisfied()) {
            return false;
          }

        } else if (VARIABLE_ON_PART.equals(sentryPart.getType())) {
          if (!sentryPart.isSatisfied()) {
            return false;
          }
        } else {
            /* IF_PART.equals(sentryPart.getType) == true */

          ifPart = sentryPart;

          // once the ifPart has been satisfied the whole sentry is satisfied
          if (ifPart.isSatisfied()) {
            return true;
          }
        }
      }
    }

    if (ifPart != null) {

      CmmnExecution execution = ifPart.getCaseExecution();
      ensureNotNull("Case execution of sentry '" + ifPart.getSentryId() + "': is null", execution);

      CmmnActivity activity = ifPart.getCaseExecution().getActivity();
      ensureNotNull("Case execution '" + id + "': has no current activity", "activity", activity);

      CmmnSentryDeclaration sentryDeclaration = activity.getSentry(sentryId);
      ensureNotNull(
          "Case execution '" + id + "': has no declaration for sentry '" + sentryId + "'",
          "sentryDeclaration",
          sentryDeclaration);

      CmmnIfPartDeclaration ifPartDeclaration = sentryDeclaration.getIfPart();
      ensureNotNull(
          "Sentry declaration '"
              + sentryId
              + "' has no definied ifPart, but there should be one defined for case execution '"
              + id
              + "'.",
          "ifPartDeclaration",
          ifPartDeclaration);

      Expression condition = ifPartDeclaration.getCondition();
      ensureNotNull(
          "A condition was expected for ifPart of Sentry declaration '"
              + sentryId
              + "' for case execution '"
              + id
              + "'.",
          "condition",
          condition);

      Object result = condition.getValue(this);
      ensureInstanceOf("condition expression returns non-Boolean", "result", result, Boolean.class);

      Boolean booleanResult = (Boolean) result;
      ifPart.setSatisfied(booleanResult);
      return booleanResult;
    }

    // if all onParts are satisfied and there is no
    // ifPart then the whole sentry is satisfied.
    return true;
  }

  protected boolean containsIfPartAndExecutionActive(
      String sentryId, Map<String, List<CmmnSentryPart>> sentries) {
    List<? extends CmmnSentryPart> sentryParts = sentries.get(sentryId);

    for (CmmnSentryPart part : sentryParts) {
      CmmnExecution caseExecution = part.getCaseExecution();
      if (IF_PART.equals(part.getType()) && caseExecution != null && caseExecution.isActive()) {
        return true;
      }
    }

    return false;
  }

  public boolean isEntryCriterionSatisfied() {
    return entryCriterionSatisfied;
  }

  // business key ////////////////////////////////////////////////////////////

  public String getCaseBusinessKey() {
    return getCaseInstance().getBusinessKey();
  }

  // case definition ///////////////////////////////////////////////////////

  public CmmnCaseDefinition getCaseDefinition() {
    return caseDefinition;
  }

  public void setCaseDefinition(CmmnCaseDefinition caseDefinition) {
    this.caseDefinition = caseDefinition;
  }

  // case instance /////////////////////////////////////////////////////////

  /** ensures initialization and returns the process instance. */
  public abstract CmmnExecution getCaseInstance();

  public abstract void setCaseInstance(CmmnExecution caseInstance);

  public boolean isCaseInstanceExecution() {
    return getParent() == null;
  }

  // case instance id /////////////////////////////////////////////////////////

  /** ensures initialization and returns the process instance. */
  public String getCaseInstanceId() {
    return getCaseInstance().getId();
  }

  // parent ///////////////////////////////////////////////////////////////////

  /** ensures initialization and returns the parent */
  public abstract CmmnExecution getParent();

  public abstract void setParent(CmmnExecution parent);

  // activity /////////////////////////////////////////////////////////////////

  /** ensures initialization and returns the activity */
  public CmmnActivity getActivity() {
    return activity;
  }

  public void setActivity(CmmnActivity activity) {
    this.activity = activity;
  }

  // variables ////////////////////////////////////////////

  @Override
  public String getVariableScopeKey() {
    return "caseExecution";
  }

  public AbstractVariableScope getParentVariableScope() {
    return getParent();
  }

  // delete/remove /////////////////////////////////////////////////////

  public void deleteCascade() {
    performOperation(CASE_EXECUTION_DELETE_CASCADE);
  }

  public void remove() {
    CmmnExecution parent = getParent();
    if (parent != null) {
      parent.getCaseExecutionsInternal().remove(this);
    }
  }

  // required //////////////////////////////////////////////////

  public boolean isRequired() {
    return required;
  }

  public void setRequired(boolean required) {
    this.required = required;
  }

  // state /////////////////////////////////////////////////////

  public CaseExecutionState getCurrentState() {
    return CaseExecutionState.CASE_EXECUTION_STATES.get(getState());
  }

  public void setCurrentState(CaseExecutionState currentState) {
    if (!isSuspending() && !isTerminating()) {
      // do not reset the previous state, if this case execution
      // is currently terminating or suspending. otherwise the
      // "real" previous state is lost.
      previousState = this.currentState;
    }
    this.currentState = currentState.getStateCode();
  }

  public int getState() {
    return currentState;
  }

  public void setState(int state) {
    this.currentState = state;
  }

  public boolean isNew() {
    return currentState == NEW.getStateCode();
  }

  public boolean isAvailable() {
    return currentState == AVAILABLE.getStateCode();
  }

  public boolean isEnabled() {
    return currentState == ENABLED.getStateCode();
  }

  public boolean isDisabled() {
    return currentState == DISABLED.getStateCode();
  }

  public boolean isActive() {
    return currentState == ACTIVE.getStateCode();
  }

  public boolean isCompleted() {
    return currentState == COMPLETED.getStateCode();
  }

  public boolean isSuspended() {
    return currentState == SUSPENDED.getStateCode();
  }

  public boolean isSuspending() {
    return currentState == SUSPENDING_ON_SUSPENSION.getStateCode()
        || currentState == SUSPENDING_ON_PARENT_SUSPENSION.getStateCode();
  }

  public boolean isTerminated() {
    return currentState == TERMINATED.getStateCode();
  }

  public boolean isTerminating() {
    return currentState == TERMINATING_ON_TERMINATION.getStateCode()
        || currentState == TERMINATING_ON_PARENT_TERMINATION.getStateCode()
        || currentState == TERMINATING_ON_EXIT.getStateCode();
  }

  public boolean isFailed() {
    return currentState == FAILED.getStateCode();
  }

  public boolean isClosed() {
    return currentState == CLOSED.getStateCode();
  }

  // previous state /////////////////////////////////////////////

  public CaseExecutionState getPreviousState() {
    return CaseExecutionState.CASE_EXECUTION_STATES.get(getPrevious());
  }

  public int getPrevious() {
    return previousState;
  }

  public void setPrevious(int previous) {
    this.previousState = previous;
  }

  // state transition ///////////////////////////////////////////

  public void create() {
    create(null);
  }

  public void create(Map<String, Object> variables) {
    if (variables != null) {
      setVariables(variables);
    }

    performOperation(CASE_INSTANCE_CREATE);
  }

  public List<CmmnExecution> createChildExecutions(List<CmmnActivity> activities) {
    List<CmmnExecution> children = new ArrayList<CmmnExecution>();

    // first create new child case executions
    for (CmmnActivity currentActivity : activities) {
      CmmnExecution child = createCaseExecution(currentActivity);
      children.add(child);
    }

    return children;
  }

  public void triggerChildExecutionsLifecycle(List<CmmnExecution> children) {
    // then notify create listener for each created
    // child case execution
    for (CmmnExecution child : children) {

      if (isActive()) {
        if (child.isNew()) {
          child.performOperation(CASE_EXECUTION_CREATE);
        }
      } else {
        // if this case execution is not active anymore,
        // then stop notifying create listener and executing
        // of each child case execution
        break;
      }
    }
  }

  protected abstract CmmnExecution createCaseExecution(CmmnActivity activity);

  protected abstract CmmnExecution newCaseExecution();

  public void enable() {
    performOperation(CASE_EXECUTION_ENABLE);
  }

  public void disable() {
    performOperation(CASE_EXECUTION_DISABLE);
  }

  public void reenable() {
    performOperation(CASE_EXECUTION_RE_ENABLE);
  }

  public void manualStart() {
    performOperation(CASE_EXECUTION_MANUAL_START);
  }

  public void start() {
    performOperation(CASE_EXECUTION_START);
  }

  public void complete() {
    performOperation(CASE_EXECUTION_COMPLETE);
  }

  public void manualComplete() {
    performOperation(CASE_EXECUTION_MANUAL_COMPLETE);
  }

  public void occur() {
    performOperation(CASE_EXECUTION_OCCUR);
  }

  public void terminate() {
    performOperation(CASE_EXECUTION_TERMINATING_ON_TERMINATION);
  }

  public void performTerminate() {
    performOperation(CASE_EXECUTION_TERMINATE);
  }

  public void parentTerminate() {
    performOperation(CASE_EXECUTION_TERMINATING_ON_PARENT_TERMINATION);
  }

  public void performParentTerminate() {
    performOperation(CASE_EXECUTION_PARENT_TERMINATE);
  }

  public void exit() {
    performOperation(CASE_EXECUTION_TERMINATING_ON_EXIT);
  }

  public void performExit() {
    performOperation(CASE_EXECUTION_EXIT);
  }

  public void suspend() {
    performOperation(CASE_EXECUTION_SUSPENDING_ON_SUSPENSION);
  }

  public void performSuspension() {
    performOperation(CASE_EXECUTION_SUSPEND);
  }

  public void parentSuspend() {
    performOperation(CASE_EXECUTION_SUSPENDING_ON_PARENT_SUSPENSION);
  }

  public void performParentSuspension() {
    performOperation(CASE_EXECUTION_PARENT_SUSPEND);
  }

  public void resume() {
    performOperation(CASE_EXECUTION_RESUME);
  }

  public void parentResume() {
    performOperation(CASE_EXECUTION_PARENT_RESUME);
  }

  public void reactivate() {
    performOperation(CASE_EXECUTION_RE_ACTIVATE);
  }

  public void close() {
    performOperation(CASE_INSTANCE_CLOSE);
  }

  // variable listeners
  public void dispatchEvent(VariableEvent variableEvent) {
    boolean invokeCustomListeners =
        Context.getProcessEngineConfiguration().isInvokeCustomVariableListeners();

    Map<String, List<VariableListener<?>>> listeners =
        getActivity().getVariableListeners(variableEvent.getEventName(), invokeCustomListeners);

    // only attempt to invoke listeners if there are any (as this involves resolving the upwards
    // execution hierarchy)
    if (!listeners.isEmpty()) {
      getCaseInstance().queueVariableEvent(variableEvent, invokeCustomListeners);
    }
  }

  protected void queueVariableEvent(VariableEvent variableEvent, boolean includeCustomerListeners) {

    Queue<VariableEvent> variableEventsQueue = getVariableEventQueue();

    variableEventsQueue.add(variableEvent);

    // if this is the first event added, trigger listener invocation
    if (variableEventsQueue.size() == 1) {
      invokeVariableListeners(includeCustomerListeners);
    }
  }

  protected void invokeVariableListeners(boolean includeCustomerListeners) {
    Queue<VariableEvent> variableEventsQueue = getVariableEventQueue();

    while (!variableEventsQueue.isEmpty()) {
      // do not remove the event yet, as otherwise new events will immediately be dispatched
      VariableEvent nextEvent = variableEventsQueue.peek();

      CmmnExecution sourceExecution = (CmmnExecution) nextEvent.getSourceScope();

      DelegateCaseVariableInstanceImpl delegateVariable =
          DelegateCaseVariableInstanceImpl.fromVariableInstance(nextEvent.getVariableInstance());
      delegateVariable.setEventName(nextEvent.getEventName());
      delegateVariable.setSourceExecution(sourceExecution);

      Map<String, List<VariableListener<?>>> listenersByActivity =
          sourceExecution
              .getActivity()
              .getVariableListeners(delegateVariable.getEventName(), includeCustomerListeners);

      CmmnExecution currentExecution = sourceExecution;
      while (currentExecution != null) {

        if (currentExecution.getActivityId() != null) {
          List<VariableListener<?>> listeners =
              listenersByActivity.get(currentExecution.getActivityId());

          if (listeners != null) {
            delegateVariable.setScopeExecution(currentExecution);

            for (VariableListener<?> listener : listeners) {
              try {
                CaseVariableListener caseVariableListener = (CaseVariableListener) listener;
                CaseVariableListenerInvocation invocation =
                    new CaseVariableListenerInvocation(
                        caseVariableListener, delegateVariable, currentExecution);
                Context.getProcessEngineConfiguration()
                    .getDelegateInterceptor()
                    .handleInvocation(invocation);
              } catch (Exception e) {
                throw LOG.invokeVariableListenerException(e);
              }
            }
          }
        }

        currentExecution = currentExecution.getParent();
      }

      // finally remove the event from the queue
      variableEventsQueue.remove();
    }
  }

  protected Queue<VariableEvent> getVariableEventQueue() {
    if (variableEventsQueue == null) {
      variableEventsQueue = new LinkedList<VariableEvent>();
    }

    return variableEventsQueue;
  }

  // toString() //////////////////////////////////////  ///////////

  public String toString() {
    if (isCaseInstanceExecution()) {
      return "CaseInstance[" + getToStringIdentity() + "]";
    } else {
      return "CmmnExecution[" + getToStringIdentity() + "]";
    }
  }

  protected String getToStringIdentity() {
    return id;
  }
}
/** @author Roman Smirnov */
public abstract class CmmnExecution extends CoreExecution implements CmmnCaseInstance {

  private static final long serialVersionUID = 1L;

  protected transient CmmnCaseDefinition caseDefinition;

  // current position //////////////////////////////////////

  /** current activity */
  protected transient CmmnActivity activity;

  /** the activity which is to be started next */
  protected transient CmmnActivity nextActivity;

  protected boolean required;

  protected int previousState;

  protected int currentState = NEW.getStateCode();

  public CmmnExecution() {}

  // plan items ///////////////////////////////////////////////////////////////

  public abstract List<? extends CmmnExecution> getCaseExecutions();

  protected abstract List<? extends CmmnExecution> getCaseExecutionsInternal();

  public CmmnExecution findCaseExecution(String activityId) {
    if ((getActivity() != null) && (getActivity().getId().equals(activityId))) {
      return this;
    }
    for (CmmnExecution nestedExecution : getCaseExecutions()) {
      CmmnExecution result = nestedExecution.findCaseExecution(activityId);
      if (result != null) {
        return result;
      }
    }
    return null;
  }

  // sub process instance ////////////////////////////////////////////////////

  public abstract PvmExecutionImpl getSubProcessInstance();

  public abstract void setSubProcessInstance(PvmExecutionImpl subProcessInstance);

  public abstract PvmExecutionImpl createSubProcessInstance(PvmProcessDefinition processDefinition);

  public abstract PvmExecutionImpl createSubProcessInstance(
      PvmProcessDefinition processDefinition, String businessKey);

  public abstract PvmExecutionImpl createSubProcessInstance(
      PvmProcessDefinition processDefinition, String businessKey, String caseInstanceId);

  // sub-/super- case instance ////////////////////////////////////////////////////

  public abstract CmmnExecution getSubCaseInstance();

  public abstract void setSubCaseInstance(CmmnExecution subCaseInstance);

  public abstract CmmnExecution createSubCaseInstance(CmmnCaseDefinition caseDefinition);

  public abstract CmmnExecution createSubCaseInstance(
      CmmnCaseDefinition caseDefinition, String businessKey);

  public abstract CmmnExecution getSuperCaseExecution();

  public abstract void setSuperCaseExecution(CmmnExecution superCaseExecution);

  // business key ////////////////////////////////////////////////////////////

  public String getCaseBusinessKey() {
    return getCaseInstance().getBusinessKey();
  }

  // case definition ///////////////////////////////////////////////////////

  public CmmnCaseDefinition getCaseDefinition() {
    return caseDefinition;
  }

  public void setCaseDefinition(CmmnCaseDefinition caseDefinition) {
    this.caseDefinition = caseDefinition;
  }

  // case instance /////////////////////////////////////////////////////////

  /** ensures initialization and returns the process instance. */
  public abstract CmmnExecution getCaseInstance();

  public abstract void setCaseInstance(CmmnExecution caseInstance);

  public boolean isCaseInstanceExecution() {
    return getParent() == null;
  }

  // case instance id /////////////////////////////////////////////////////////

  /** ensures initialization and returns the process instance. */
  public String getCaseInstanceId() {
    return getCaseInstance().getId();
  }

  // parent ///////////////////////////////////////////////////////////////////

  /** ensures initialization and returns the parent */
  public abstract CmmnExecution getParent();

  public abstract void setParent(CmmnExecution parent);

  // activity /////////////////////////////////////////////////////////////////

  /** ensures initialization and returns the activity */
  public CmmnActivity getActivity() {
    return activity;
  }

  public void setActivity(CmmnActivity activity) {
    this.activity = activity;
  }

  // next activity /////////////////////////////////////////////////////////////

  public CmmnActivity getNextActivity() {
    return nextActivity;
  }

  public void setNextActivity(CmmnActivity nextActivity) {
    this.nextActivity = nextActivity;
  }

  // variables ////////////////////////////////////////////

  public CorePersistentVariableScope getParentVariableScope() {
    return getParent();
  }

  // delete/remove /////////////////////////////////////////////////////

  public void deleteCascade() {
    performOperation(CASE_EXECUTION_DELETE_CASCADE);
  }

  public void remove() {
    CmmnExecution parent = getParent();
    if (parent != null) {
      parent.getCaseExecutionsInternal().remove(this);
    }
  }

  // required //////////////////////////////////////////////////

  public boolean isRequired() {
    return required;
  }

  public void setRequired(boolean required) {
    this.required = required;
  }

  // state /////////////////////////////////////////////////////

  public CaseExecutionState getCurrentState() {
    return CaseExecutionState.CASE_EXECUTION_STATES.get(getState());
  }

  public void setCurrentState(CaseExecutionState currentState) {
    previousState = this.currentState;
    this.currentState = currentState.getStateCode();
  }

  public int getState() {
    return currentState;
  }

  public void setState(int state) {
    this.currentState = state;
  }

  public boolean isNew() {
    return currentState == NEW.getStateCode();
  }

  public boolean isAvailable() {
    return currentState == AVAILABLE.getStateCode();
  }

  public boolean isEnabled() {
    return currentState == ENABLED.getStateCode();
  }

  public boolean isDisabled() {
    return currentState == DISABLED.getStateCode();
  }

  public boolean isActive() {
    return currentState == ACTIVE.getStateCode();
  }

  public boolean isCompleted() {
    return currentState == COMPLETED.getStateCode();
  }

  public boolean isSuspended() {
    return currentState == SUSPENDED.getStateCode();
  }

  public boolean isTerminated() {
    return currentState == TERMINATED.getStateCode();
  }

  public boolean isFailed() {
    return currentState == FAILED.getStateCode();
  }

  public boolean isClosed() {
    return currentState == CLOSED.getStateCode();
  }

  // previous state /////////////////////////////////////////////

  public CaseExecutionState getPreviousState() {
    return CaseExecutionState.CASE_EXECUTION_STATES.get(getPrevious());
  }

  public int getPrevious() {
    return previousState;
  }

  public void setPrevious(int previous) {
    this.previousState = previous;
  }

  // state transition ///////////////////////////////////////////

  public void create() {
    create(null);
  }

  public void create(Map<String, Object> variables) {
    if (variables != null) {
      setVariables(variables);
    }

    performOperation(CASE_INSTANCE_CREATE);
  }

  public void createChildExecutions(List<CmmnActivity> activities) {
    List<CmmnExecution> children = new ArrayList<CmmnExecution>();

    // first create new child case executions
    for (CmmnActivity currentActivity : activities) {
      CmmnExecution child = createCaseExecution(currentActivity);
      children.add(child);
    }

    // then notify create listener for each created
    // child case execution
    for (CmmnExecution child : children) {

      if (isActive()) {
        child.performOperation(CASE_EXECUTION_CREATE);
      } else {
        // if this case execution is not active anymore,
        // then stop notifying create listener and executing
        // of each child case execution
        break;
      }
    }
  }

  protected abstract CmmnExecution createCaseExecution(CmmnActivity activity);

  protected abstract CmmnExecution newCaseExecution();

  public void enable() {
    performOperation(CASE_EXECUTION_ENABLE);
  }

  public void disable() {
    performOperation(CASE_EXECUTION_DISABLE);
  }

  public void reenable() {
    performOperation(CASE_EXECUTION_RE_ENABLE);
  }

  public void manualStart() {
    performOperation(CASE_EXECUTION_MANUAL_START);
  }

  public void start() {
    performOperation(CASE_EXECUTION_START);
  }

  public void complete() {
    performOperation(CASE_EXECUTION_COMPLETE);
  }

  public void manualComplete() {
    performOperation(CASE_EXECUTION_MANUAL_COMPLETE);
  }

  public void occur() {
    performOperation(CASE_EXECUTION_OCCUR);
  }

  public void terminate() {
    performOperation(CASE_EXECUTION_TERMINATE);
  }

  public void parentTerminate() {
    performOperation(CASE_EXECUTION_PARENT_TERMINATE);
  }

  public void exit() {
    performOperation(CASE_EXECUTION_EXIT);
  }

  public void suspend() {
    performOperation(CASE_EXECUTION_SUSPEND);
  }

  public void parentSuspend() {
    performOperation(CASE_EXECUTION_PARENT_SUSPEND);
  }

  public void resume() {
    performOperation(CASE_EXECUTION_RESUME);
  }

  public void parentResume() {
    performOperation(CASE_EXECUTION_PARENT_RESUME);
  }

  public void reactivate() {
    performOperation(CASE_EXECUTION_RE_ACTIVATE);
  }

  public void close() {
    performOperation(CASE_INSTANCE_CLOSE);
  }

  // toString() /////////////////////////////////////////////////

  public String toString() {
    if (isCaseInstanceExecution()) {
      return "CaseInstance[" + getToStringIdentity() + "]";
    } else {
      return "CmmnExecution[" + getToStringIdentity() + "]";
    }
  }

  protected String getToStringIdentity() {
    return id;
  }
}