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

org.ow2.bonita.pvm.internal.model.ExecutionImpl Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.ow2.bonita.pvm.internal.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import org.ow2.bonita.env.Environment;
import org.ow2.bonita.pvm.Execution;
import org.ow2.bonita.pvm.activity.ActivityExecution;
import org.ow2.bonita.pvm.client.ClientProcessDefinition;
import org.ow2.bonita.pvm.client.ClientProcessInstance;
import org.ow2.bonita.pvm.internal.job.JobImpl;
import org.ow2.bonita.pvm.internal.job.MessageImpl;
import org.ow2.bonita.pvm.internal.job.TimerImpl;
import org.ow2.bonita.pvm.internal.model.op.AtomicOperation;
import org.ow2.bonita.pvm.internal.model.op.ExecuteNode;
import org.ow2.bonita.pvm.internal.model.op.MoveToChildNode;
import org.ow2.bonita.pvm.internal.model.op.MoveToParentNode;
import org.ow2.bonita.pvm.internal.model.op.ProceedToDestination;
import org.ow2.bonita.pvm.internal.model.op.Signal;
import org.ow2.bonita.pvm.internal.model.op.TakeTransition;
import org.ow2.bonita.pvm.internal.type.Converter;
import org.ow2.bonita.pvm.internal.type.Type;
import org.ow2.bonita.pvm.internal.type.Variable;
import org.ow2.bonita.pvm.internal.type.VariableTypeResolver;
import org.ow2.bonita.pvm.internal.type.variable.NullVariable;
import org.ow2.bonita.pvm.internal.type.variable.UnpersistableVariable;
import org.ow2.bonita.pvm.internal.util.Clock;
import org.ow2.bonita.pvm.internal.util.EqualsUtil;
import org.ow2.bonita.pvm.internal.util.Priority;
import org.ow2.bonita.pvm.job.Job;
import org.ow2.bonita.pvm.job.Timer;
import org.ow2.bonita.pvm.listener.EventListener;
import org.ow2.bonita.pvm.listener.EventListenerExecution;
import org.ow2.bonita.pvm.model.Comment;
import org.ow2.bonita.pvm.model.Event;
import org.ow2.bonita.pvm.model.IdGenerator;
import org.ow2.bonita.pvm.model.Node;
import org.ow2.bonita.pvm.model.ObservableElement;
import org.ow2.bonita.pvm.model.OpenExecution;
import org.ow2.bonita.pvm.model.Transition;
import org.ow2.bonita.pvm.processlog.ProcessLog;
import org.ow2.bonita.pvm.session.LogSession;
import org.ow2.bonita.pvm.session.MessageSession;
import org.ow2.bonita.pvm.session.TimerSession;
import org.ow2.bonita.util.BonitaRuntimeException;
import org.ow2.bonita.util.ExceptionManager;
import org.ow2.bonita.util.Log;

/**
 * @author Tom Baeyens
 */
public class ExecutionImpl implements ClientProcessInstance, ActivityExecution,
    EventListenerExecution, Serializable {

  private static final long serialVersionUID = 1L;

  private static final Log log = Log.getLog(Execution.class.getName());

  // atomic operations
  public static final AtomicOperation EXECUTE_NODE = new ExecuteNode();
  public static final AtomicOperation PROCEED_TO_DESTINATION = new ProceedToDestination();
  public static final AtomicOperation TAKE_TRANSITION = new TakeTransition();
  public static final AtomicOperation PROPAGATE_TO_PARENT = new MoveToParentNode();

  protected long dbid;
  protected int dbversion;

  /**
   * an optional name for this execution. can be used to differentiate
   * concurrent paths of execution like e.g. the 'shipping' and 'billing' paths.
   */
  protected String name;

  /**
   * a key for this execution. typically this is an externally provided
   * reference that is unique within the scope of the process definition.
   */
  protected String key;

  /** a unique id for this execution. */
  protected String id;

  /** @see Execution */
  protected String state;

  protected PVMProcessDefinitionImpl processDefinition;

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

  /** current node */
  protected NodeImpl node;

  /** transition is not to be made persistable by default */
  protected TransitionImpl transition;

  /**
   * the node from which the transition was taken. This can be different from
   * the transition source in case a transition of an eclosing node was taken.
   * transitionOrigin is not to be made persistable by default
   */
  protected NodeImpl transitionOrigin;

  protected EventImpl event;
  protected ObservableElementImpl eventSource;

  /** are concurrent executions that related to this execution. */
  protected Collection executions;

  /**
   * the parent child relation of executions is convenient for some forms of
   * concurrency.
   */
  protected ExecutionImpl parent = null;
  protected ExecutionImpl processInstance;

  boolean hasVariables;
  protected Map variables;

  protected Set> jobs = new HashSet>();

  /** the super process link in case this is a sub process execution */
  protected ExecutionImpl superProcessExecution;

  /** the sub process link in case of sub process execution */
  protected ExecutionImpl subProcessInstance;

  /** the free text comments users make on this execution */
  protected Set comments;

  protected int priority = Priority.NORMAL;

  /**
   * maintains the index of the next log record. That way, the logs don't have
   * to be loaded to add one. Instead, for each log that is added to this
   * execution, the nextLogIndex is used and incremented.
   */
  protected int nextLogIndex;

  /**
   * caches the child executions by execution name. This member might be null
   * and is only created from the executions in case its needed. Note that not
   * all executions are forced to have a name and duplicates are allowed. In
   * case the {@link #executions} change, the executionsMap can be nulled or
   * also updated (but a check needs to be added whether it exists).
   */
  protected transient Map executionsMap = null;

  // transient members

  /** the queue of atomic operations to be performed for this execution. */
  protected Queue atomicOperations;

  public enum Propagation {
    UNSPECIFIED, WAIT, EXPLICIT
  }

  protected Propagation propagation = null;

  protected Node previousNode;
  protected Transition previousTransition;
  protected Exception exception;

  // It's important that this refers to a separate entity. This
  // execution must do nullpointercheck before accessing the
  // process modifications. That way, good performance is guaranteed
  // for the most common scenario: a persistent execution without
  // process modifications.
  protected ProcessModificationsImpl processModifications;

  // construction /////////////////////////////////////////////////////////////

  public void initializeProcessInstance(
      final PVMProcessDefinitionImpl processDefinition, final String key) {
    this.processDefinition = processDefinition;
    this.node = processDefinition.getInitial();
    this.processInstance = this;
    this.state = STATE_CREATED;
    this.key = key;

    final IdGenerator keyGenerator = Environment.getFromCurrent(IdGenerator.class,
        false);
    if (keyGenerator != null) {
      this.id = keyGenerator.createId(processDefinition, null, this);
    }
  }

  // execution method : start /////////////////////////////////////////////////

  public void begin() {
    if (this.state != STATE_CREATED) {
    	String message = ExceptionManager.getInstance().getFullMessage(
    			"bp_EI_1 ", toString(), this.state);
      throw new BonitaRuntimeException(message);
    }
    final ExecutionImpl scopedExecution = initializeScopes();
    this.state = STATE_ACTIVE;
    fire(Event.PROCESS_BEGIN, this.processDefinition);
    if (this.node != null) {
      scopedExecution.performAtomicOperation(EXECUTE_NODE);
    }
  }

  protected ExecutionImpl initializeScopes() {
    final LinkedList enteredNodes = new LinkedList();

    final NodeImpl initial = this.processDefinition.getInitial();
    ExecutionImpl scopedExecution = null;

    if (initial != null) {
      enteredNodes.add(initial);
      NodeImpl parentNode = initial.getParentNode();
      while (parentNode != null) {
        enteredNodes.addFirst(parentNode);
        parentNode = parentNode.getParentNode();
      }

      scopedExecution = this;

      initializeVariables(this.processDefinition, this);
      initializeTimers(this.processDefinition);

      for (final NodeImpl enteredNode : enteredNodes) {
        if (enteredNode.isLocalScope()) {
          scopedExecution.setNode(enteredNode);
          scopedExecution = scopedExecution.createScope(enteredNode);
        }
      }

      scopedExecution.setNode(initial);
    }
    return scopedExecution;
  }

  public ExecutionImpl createScope(final CompositeElementImpl scope) {
    final ExecutionImpl child = createExecution(scope.getName());

    // copy the current state from the child execution to the parent execution
    child.setNode(getNode());
    child.setTransition(getTransition());
    child.setPropagation(getPropagation());
    child.setTransitionOrigin(getTransitionOrigin());
    child.setPreviousTransition(getPreviousTransition());
    child.setPreviousNode(getPreviousNode());

    child.initializeVariables(scope, this);
    child.initializeTimers(scope);

    return child;
  }

  public ExecutionImpl destroyScope(final CompositeElementImpl scope) {
    destroyTimers(scope);
    destroyVariables(scope, this.parent);

    // copy the current state from the child execution to the parent execution
    getParent().setNode(getNode());
    getParent().setTransition(getTransition());
    getParent().setPropagation(getPropagation());
    getParent().setTransitionOrigin(getTransitionOrigin());
    getParent().setPreviousTransition(getPreviousTransition());
    getParent().setPreviousNode(getPreviousNode());

    end();
    this.parent.removeExecution(this);

    return this.parent;
  }

  // basic object methods /////////////////////////////////////////////////////

  @Override
  public String toString() {
    if (this.name != null) {
      return "execution[" + this.name + "]";
    }
    if (this.parent == null) {
      return "process-instance";
    }
    return "execution";
  }

  // execution method : end ///////////////////////////////////////////////////

  public void end() {
    end(Execution.STATE_ENDED);
  }

  public void end(final String state) {
    if (state == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_2");
      throw new BonitaRuntimeException(message);
    }
    if (state.equals(STATE_ACTIVE) || state.equals(STATE_CREATED)
        || state.equals(STATE_INACTIVE) || state.equals(STATE_SUSPENDED)
        || state.equals(STATE_ASYNC)) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_3", state);
      throw new BonitaRuntimeException(message);
    }

    if (log.isDebugEnabled()) {
      if (state == STATE_ENDED) {
        log.debug(toString() + " ends");
      } else {
        log.debug(toString() + " ends with state " + state);
      }
    }

    // end all child executions
    if (this.executions != null) {
      for (final ExecutionImpl child : this.executions) {
        child.end(state);
      }
    }

    lock(state);

    this.propagation = Propagation.EXPLICIT;
    if (this.parent == null) {
      fire(Event.PROCESS_END, this.processDefinition);
      if (this.superProcessExecution != null) {
        log.trace(toString() + " signals super process execution");
        this.superProcessExecution.signal();
      }
    }
  }

  public void cancel() {
    end(Execution.STATE_CANCELLED);
  }

  // execution method : suspend and resume ////////////////////////////////////

  /** @see Execution#suspend() */
  public void suspend() {
    if (isSuspended()) {
    	String message = ExceptionManager.getInstance().getFullMessage(
    			"bp_EI_4", toString());
      throw new BonitaRuntimeException(message);
    }
    lock(STATE_SUSPENDED);
  }

  /** @see Execution#resume() */
  public void resume() {
    if (!isSuspended()) {
    	String message = ExceptionManager.getInstance().getFullMessage(
    			"bp_EI_5", toString());
      throw new BonitaRuntimeException(message);
    }
    unlock();
  }

  // execution method : signal ////////////////////////////////////////////////

  @SuppressWarnings("unchecked")
  public void signal() {
    signal(null, (Map) null);
  }

  @SuppressWarnings("unchecked")
  public void signal(final String signal) {
    signal(signal, (Map) null);
  }

  public void signal(final Map parameters) {
    signal(null, parameters);
  }

  public void signal(final String signal, final Map parameters) {
    checkLock();
    if (this.node != null) {
      performAtomicOperation(new Signal(signal, parameters, this.node));
    } else if (this.transition != null) {
      performAtomicOperation(ExecutionImpl.PROCEED_TO_DESTINATION);
    } else {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_6");
      throw new BonitaRuntimeException(message);
    }
  }

  @SuppressWarnings("unchecked")
  public void signal(final Execution execution) {
    ((ExecutionImpl) execution).signal(null, (Map) null);
  }

  @SuppressWarnings("unchecked")
  public void signal(final String signalName, final Execution execution) {
    ((ExecutionImpl) execution).signal(signalName, (Map) null);
  }

  public void signal(final Map parameters, final Execution execution) {
    ((ExecutionImpl) execution).signal(null, parameters);
  }

  public void signal(final String signalName, final Map parameters,
      final Execution execution) {
    ((ExecutionImpl) execution).signal(signalName, parameters);
  }

  // execution method : take ////////////////////////////////////////////////

  /** @see Execution#takeDefaultTransition() */
  public void takeDefaultTransition() {
    final TransitionImpl defaultTransition = this.node.getDefaultTransition();
    if (defaultTransition == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_7", this.node);
      throw new BonitaRuntimeException(message);
    }
    take(defaultTransition);
  }

  /** @see Execution#take(String) */
  public void take(final String transitionName) {
    if (this.node == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_8", toString());
      throw new BonitaRuntimeException(message);
    }
    final TransitionImpl transition = findTransition(transitionName);
    if (transition == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_9", transitionName, this.node);
      throw new BonitaRuntimeException(message);
    }
    take(transition);
  }

  /** @see Execution#takeDefaultTransition() */
  public void take(final Transition transition) {
    checkLock();

    setPropagation(Propagation.EXPLICIT);
    setTransition((TransitionImpl) transition);
    // copy the current node as the transition origin. the origin can be
    // different from
    // the transition source in case a transition is taken from an enclosing
    // node
    setTransitionOrigin(getNode());
    setPreviousTransition(null);

    performAtomicOperation(TAKE_TRANSITION);
  }

  public void take(final Transition transition, final Execution execution) {
    ((ExecutionImpl) execution).take(transition);
  }

  // execution method : execute ///////////////////////////////////////////////

  /** @see Execution#execute(String) */
  public void execute(final String nodeName) {
    if (this.node == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_10");
      throw new BonitaRuntimeException(message);
    }
    final Node nestedNode = this.node.getNode(nodeName);
    if (nestedNode == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_11", nodeName, this.node);
      throw new BonitaRuntimeException(message);
    }
    execute(nestedNode);
  }

  /** @see Execution#execute(Node) */
  public void execute(final Node node) {
    if (node == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_12");
      throw new BonitaRuntimeException(message);
    }
    checkLock();

    this.propagation = Propagation.EXPLICIT;
    performAtomicOperation(new MoveToChildNode((NodeImpl) node));
  }

  // execution method : waitForSignal /////////////////////////////////////////

  public void waitForSignal() {
    this.propagation = Propagation.WAIT;
  }

  // execution method : proceed ///////////////////////////////////////////////

  public void proceed() {
    checkLock();

    // in graph based processDefinition languages we assume that a
    // default transition is available
    final TransitionImpl defaultTransition = findDefaultTransition();
    if (defaultTransition != null) {
      take(defaultTransition);

      // in block structured processDefinition languages we assume that
      // there is no default transition and that there is a
      // parent node of the current node
    } else {
      final NodeImpl parentNode = this.node.getParentNode();

      // if there is a parent node
      if (parentNode != null) {
        // propagate to the parent
        performAtomicOperation(PROPAGATE_TO_PARENT);

      } else {
        // When we don't know how to proceed, i don't know if it's best to
        // throw new BonitaRuntimeException("don't know how to proceed");
        // or to end the execution. Because of convenience for testing,
        // I opted to end the execution.
        end();
      }
    }
  }

  public void move(final Node destination, final Execution execution) {
    ((ExecutionImpl) execution).move(destination);
  }

  public void move(final Node destination) {
    checkLock();
    setNode((NodeImpl) destination);
  }

  // execution : internal methods /////////////////////////////////////////////

  public void moveTo(final NodeImpl destination) {
    // if the parent node needs to know the previous node
    if (destination.isPreviousNeeded()) {
      setPreviousNode(getNode());
      setPreviousTransition(getTransition());
    } else {
      this.previousNode = null;
      this.previousTransition = null;
    }

    // move the execution to the destination
    this.node = destination;
    this.transition = null;
    this.transitionOrigin = null;
  }

  public ExecutionImpl beginNode(final NodeImpl node) {
    ExecutionImpl propagatingExecution = this;
    if (node.isLocalScope()) {
      propagatingExecution = createScope(node);
    }
    fire(Event.NODE_BEGIN, node);
    return propagatingExecution;
  }

  public ExecutionImpl endNode(final NodeImpl node) {
    ExecutionImpl propagatingExecution = this;
    fire(Event.NODE_END, node);
    if (node.isLocalScope()) {
      propagatingExecution = destroyScope(node);
    }
    return propagatingExecution;
  }

  public synchronized void performAtomicOperation(final AtomicOperation operation) {
    if (operation.isAsync(this)) {
      sendContinuationMessage(operation);
    } else {
      performAtomicOperationSync(operation);
    }
  }

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

  protected void initializeVariables(final CompositeElementImpl scope,
      final ExecutionImpl outerExecution) {
    // loop over all variable definitions
    final List variableDefinitions = scope
        .getVariableDefinitions();
    if (!variableDefinitions.isEmpty()) {
      if (log.isTraceEnabled()) {
        log.trace("initializing variables in scope " + scope);
      }
      this.variables = new HashMap();
      for (final VariableDefinitionImpl variableDefinition : variableDefinitions) {
        final String key = variableDefinition.getKey();
        final Object value = variableDefinition.getSourceValue(outerExecution);
        final Type type = variableDefinition.getType();
        createVariable(key, value, type);
      }
    }
  }

  protected void destroyVariables(final CompositeElementImpl scope,
      final ExecutionImpl outerExecution) {
    // loop over all variable definitions
    final List variableDefinitions = scope
        .getVariableDefinitions();
    if (variableDefinitions != null) {
      if (log.isTraceEnabled()) {
        log.trace("destroying var scope " + scope);
      }

      for (final VariableDefinitionImpl variableDefinition : variableDefinitions) {
        final String destination = variableDefinition.getDestination();
        if (destination != null) {
          final String key = variableDefinition.getKey();
          final Object value = variableDefinition.getDestinationValue(this);
          outerExecution.setVariable(key, value);
        }
      }
    }
  }

  public void createVariable(final String key, final Object value) {
    createVariable(key, value, null, null);
  }

  public void createVariable(final String key, final Object value, final String typeName) {
    createVariable(key, value, typeName, null);
  }

  public void createVariable(final String key, final Object value, final Type type) {
    createVariable(key, value, null, type);
  }

  public void createVariable(final String key, final Object value, final String typeName,
      Type type) {
    if (isFinished()) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_13", key, this, this.state);
      throw new BonitaRuntimeException(message);
    }

    log.debug("create variable '" + key + "' in '" + this + "' with value '"
        + value + "'");

    if (type == null) {
      final Environment environment = Environment.getCurrent();
      if (environment != null) {
        final VariableTypeResolver variableTypeResolver = environment
            .get(VariableTypeResolver.class);
        if (variableTypeResolver != null) {
          if (typeName != null) {
            type = variableTypeResolver.findTypeByName(typeName);
          }
          if (type == null) {
            type = variableTypeResolver.findTypeByMatch(key, value);
          }
        }
      }
    }

    Variable variable = null;

    if (type != null) {
      final Class variableClass = type.getVariableClass();
      try {
        log.trace("creating new " + type + " variable " + key);
        variable = (Variable) variableClass.newInstance();
      } catch (final Exception e) {
      	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_14", variableClass.getName());
        throw new BonitaRuntimeException(message);
      }
      final Converter converter = type.getConverter();
      variable.setConverter(converter);

    } else {
      if (value == null) {
        log.trace("creating null variable for " + key);
        variable = new NullVariable();
      } else {
        log.trace("creating new unpersistable variable for " + key);
        variable = new UnpersistableVariable();
      }
    }

    variable.setKey(key);
    variable.setValue(value);

    if (this.variables == null) {
      this.variables = new HashMap();
    }
    this.variables.put(variable.getKey(), variable);
    this.hasVariables = true;

    // TODO add create-variable-log
  }

  public void setVariable(final String key, final Object value) {
    if (isFinished()) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_15", key, this, this.state);
      throw new BonitaRuntimeException(message);
    	/*throw new BonitaRuntimeException("can't update variable '" + key + "' on " + this
          + ": " + this.state);*/
    }
    Variable variable = getVariableObject(key);
    // if there is already a variable instance and it doesn't support the
    // current type...
    if ((variable != null) && (!variable.supports(value))) {
      // delete the old variable instance
      log.debug("variable type change. deleting '" + key + "' from '" + this
          + "'");
      removeVariable(key);
      variable = null;
    }

    if (variable != null) {
      log.debug("updating variable '" + key + "' in '" + this + "' to value '"
          + value + "'");
      variable.setValue(value);

    } else if (this.parent == null) {
      createVariable(key, value, null, null);

    } else {
      this.parent.setVariable(key, value);
    }
  }

  public void setVariables(final Map variables) {
    if (variables != null) {
      for (final String key : variables.keySet()) {
        final Object value = variables.get(key);
        setVariable(key, value);
      }
    }
  }

  public Object getVariable(final String key) {
    final Variable variable = getVariableObject(key);
    if (variable != null) {
      return variable.getValue();
    }

    if (this.parent != null) {
      return this.parent.getVariable(key);
    }

    return null;
  }

  public Variable getVariableObject(final String key) {
    return (this.hasVariables ? (Variable) this.variables.get(key) : null);
  }

  public boolean hasVariable(final String key) {
    return ((this.hasVariables && this.variables.containsKey(key)) || (this.parent != null && this.parent
        .hasVariable(key)));
  }

  public Set getVariableKeys() {
    Set variableKeys = null;
    if (this.parent != null) {
      variableKeys = this.parent.getVariableKeys();
    } else {
      variableKeys = new HashSet();
    }
    if (this.hasVariables) {
      variableKeys.addAll(this.variables.keySet());
    }
    return variableKeys;
  }

  public Map getVariablesMap() {
    return this.variables;
  }
  
  public Map getVariables() {
    Map values = null;
    if (this.parent != null) {
      values = this.parent.getVariables();
    } else {
      values = new HashMap();
    }
    if (this.hasVariables) {
      for (final Map.Entry entry : this.variables.entrySet()) {
        final String name = entry.getKey();
        final Variable variable = entry.getValue();
        final Object value = variable.getValue();
        values.put(name, value);
      }
    }
    return values;
  }

  public boolean hasVariables() {
    return (this.hasVariables || (this.parent != null && this.parent.hasVariables()));
  }

  public boolean removeVariable(final String key) {
    if (isFinished()) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_16", key, this, this.state);
      throw new BonitaRuntimeException(message);
    }

    Variable variable = null;
    if (this.hasVariables) {
      variable = this.variables.remove(key);
      if (this.variables.isEmpty()) {
        this.hasVariables = false;
      }
      if (variable != null) {
        return true;
      }
    }
    if (this.parent != null) {
      return this.parent.removeVariable(key);
    }
    // the actual value is not returned to prevent that an object
    // has to be fetched from the db for it to be deleted
    return false;
  }

  public void removeVariables() {
    if (this.hasVariables) {
      this.variables.clear();
    }
    this.hasVariables = false;
  }

  // timers ///////////////////////////////////////////////////////////////////

  protected void initializeTimers(final CompositeElementImpl scope) {
    // initialize the timers
    final Set timerDefinitions = scope.getTimerDefinitions();
    if (!timerDefinitions.isEmpty()) {
      this.jobs = new HashSet>();
      for (final TimerDefinitionImpl timerDefinition : timerDefinitions) {
        createTimer(timerDefinition.getEventName(), timerDefinition
            .getSignalName(), timerDefinition.getDueDateDescription(),
            timerDefinition.getDueDate(), timerDefinition.getRepeat(),
            timerDefinition.isExclusive(), timerDefinition.getRetries());
      }
    }
  }

  protected void destroyTimers(final CompositeElementImpl scope) {
    log.debug("destroying timers of " + toString());
    if (this.jobs != null && !this.jobs.isEmpty()) {
      // get the TimerSession from the environment
      final Environment environment = Environment.getCurrent();
      if (environment == null) {
      	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_17");
        throw new BonitaRuntimeException(message);
      }
      final TimerSession timerSession = environment.get(TimerSession.class);
      if (timerSession == null) {
      	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_18");
        throw new BonitaRuntimeException(message);
      }
      final Set jobsSnapshot = new HashSet(this.jobs);
      for (final Job job : jobsSnapshot) {
        if (job instanceof Timer) {
          timerSession.cancel((Timer) job);
          this.jobs.remove(job);
        }
      }
    }
  }

  public void createTimer(final String eventName, final String signalName,
      final String dueDateDescription) {
    createTimer(eventName, signalName, dueDateDescription, null, null, null,
        null);
  }

  public void createTimer(final String eventName, final String signalName,
      final String dueDateDescription, final String repeat) {
    createTimer(eventName, signalName, dueDateDescription, null, repeat, null,
        null);
  }

  public void createTimer(final String eventName, final String signalName,
      final String dueDateDescription, final Date dueDate, final String repeat,
      final Boolean isExclusive, final Integer retries) {
    if ((eventName == null) && (signalName == null)) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_19");
      throw new BonitaRuntimeException(message);
    }
    if (log.isDebugEnabled()) {
      log.debug("creating timer on " + this.toString());
    }

    // instantiate the timer
    final TimerImpl timerImpl = instantiateTimer();
    // create the bidirectional reference
    timerImpl.setExecution(this);
    this.jobs.add(timerImpl);
    // setInverseReference(timerImpl);

    // initialise all the timer properties
    timerImpl.setEventName(eventName);
    timerImpl.setSignalName(signalName);
    if (dueDate != null) {
      timerImpl.setDueDate(dueDate);
    } else {
      timerImpl.setDueDateDescription(dueDateDescription);
    }

    // the if is added to keep the original default
    if (isExclusive != null) {
      timerImpl.setExclusive(isExclusive);
    }

    // the if is added to keep the original default
    if (retries != null) {
      timerImpl.setRetries(retries);
    }

    // the if is added to keep the original default
    if (repeat != null) {
      timerImpl.setRepeat(repeat);
    }

    // get the TimerSession from the environment
    final Environment environment = Environment.getCurrent();
    if (environment == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_20");
      throw new BonitaRuntimeException(message);
    }
    final TimerSession timerSession = environment.get(TimerSession.class);
    if (timerSession == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_21");
      throw new BonitaRuntimeException(message);
    }

    // schedule the timer with the TimerSession
    timerSession.schedule(timerImpl);
  }

  @SuppressWarnings("unchecked")
  public Set getJobs() {
    return (Set) this.jobs;
  }

  protected TimerImpl instantiateTimer() {
    return new TimerImpl();
  }

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

  /** @see Execution#getState() */
  public String getState() {
    return this.state;
  }

  /** @see Execution#lock(String) */
  public void lock(final String state) {
    if (state == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_22");
      throw new BonitaRuntimeException(message);
    }
    checkLock();
    log.trace("locking " + this);
    this.state = state;
  }

  /** @see Execution#unlock() */
  public void unlock() {
    if (STATE_ACTIVE.equals(this.state)) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_23");
      throw new BonitaRuntimeException(message);
    }
    log.trace("unlocking " + this);
    this.state = STATE_ACTIVE;
  }

  /** @see Execution#isActive() */
  public boolean isActive() {
    return STATE_ACTIVE.equals(this.state);
  }

  /** @see Execution#isLocked() */
  public boolean isLocked() {
    return !isActive();
  }

  /** @see Execution#isSuspended() */
  public boolean isSuspended() {
    return STATE_SUSPENDED.equals(this.state);
  }

  /** @see Execution#isEnded() */
  public boolean isEnded() {
    return STATE_ENDED.equals(this.state);
  }

  /** @see Execution#isFinished() */
  public boolean isFinished() {
    return STATE_ENDED.equals(this.state) || STATE_CANCELLED.equals(this.state);
  }

  // state : internal methods /////////////////////////////////////////////////

  protected void checkLock() {
    if (!STATE_ACTIVE.equals(this.state)) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_24", toString(), this.state);
      throw new BonitaRuntimeException(message);
    }
  }

  // asynchronous continuations ////////////////////////////////////////////////

  public void sendContinuationMessage(final AtomicOperation operation) {
    final Environment environment = Environment.getCurrent();
    final MessageSession messageSession = environment.get(MessageSession.class);
    if (messageSession == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_25");
      throw new BonitaRuntimeException(message);
    }
    final MessageImpl asyncMessage = operation.createAsyncMessage(this);
    lock("async continuation message " + asyncMessage);
    messageSession.send(asyncMessage);
  }

  public void performAtomicOperationSync(final AtomicOperation operation) {
    if (this.atomicOperations == null) {

      // initialise the fifo queue of atomic operations
      this.atomicOperations = new LinkedList();
      this.atomicOperations.offer(operation);

      try {
        while (!this.atomicOperations.isEmpty()) {
          final AtomicOperation atomicOperation = this.atomicOperations.poll();
          atomicOperation.perform(this);
        }

      } catch (final RuntimeException e) {
        throw e;
      } finally {
        this.atomicOperations = null;
      }
    } else {
      this.atomicOperations.offer(operation);
    }
  }

  // events ///////////////////////////////////////////////////////////////////

  /** @see Execution#fire(String, ObservableElement) */
  public void fire(final String eventName, final ObservableElement eventSource) {
    fire(eventName, eventSource, (ObservableElementImpl) eventSource);
  }

  /**
   * fires the event on the given *processElement* and then propagates the event
   * up to the *processElement* parent chain.
   */
  void fire(final String eventName, final ObservableElement eventSource,
      final ObservableElementImpl observableElement) {
    if (observableElement != null) {
      final EventImpl event = observableElement.getEvent(eventName);
      if (event != null) {
        if (log.isTraceEnabled()) {
          if (observableElement == eventSource) {
            log.trace("firing " + event + " on " + eventSource);
          } else {
            log.trace("firing " + event + " on " + observableElement
                + ", propagated from source " + eventSource);
          }
        }
        fire(event, eventSource, observableElement);
      }
      propagateEvent(eventName, eventSource, observableElement);
    }
  }

  /**
   * this method enables specific process languages to overwrite the event
   * propagation behaviour
   */
  protected void propagateEvent(final String eventName,
      final ObservableElement eventSource, final ObservableElementImpl observableElement) {
    fire(eventName, eventSource, observableElement.getParent());
  }

  /** fires the given event without propagation */
  void fire(final EventImpl event, final ObservableElement eventSource,
      final ObservableElement observableElement) {
    try {
      this.event = event;
      this.eventSource = (ObservableElementImpl) eventSource;

      final List eventListenerReferences = event
          .getListenerReferences();

      if (eventListenerReferences != null) {
        for (final EventListenerReference eventListenerReference : eventListenerReferences) {

          if ((observableElement.equals(eventSource)) // this event is not
                                                      // propagated
              || (eventListenerReference.isPropagationEnabled()) // propagation
                                                                 // is allowed
          ) {
            final EventListener eventListener = eventListenerReference.get();

            log.trace("executing " + eventListener + " for " + event);
            try {
              // TODO can/should this invocation be unified with the exception
              // handler invocation of the event notification method?
              eventListener.notify(this);
            } catch (final Exception e) {
              log.trace("exception during action: " + e);
              handleException((ObservableElementImpl) observableElement, event,
                  eventListenerReference, e, "couldn't run action "
                      + eventListener);
            }
          }
        }
      }

    } finally {
      this.eventSource = null;
      this.event = null;
    }
  }

  public void handleException(ObservableElementImpl observableElement,
      final EventImpl event, final EventListenerReference eventListenerReference,
      Exception exception, final String rethrowMessage) {

    final List processElements = new ArrayList();
    if (eventListenerReference != null) {
      processElements.add(eventListenerReference);
    }
    if (event != null) {
      processElements.add(event);
    }
    while (observableElement != null) {
      processElements.add(observableElement);
      observableElement = observableElement.getParent();
    }

    for (final ProcessElementImpl processElement : processElements) {
      final List exceptionHandlers = processElement
          .getExceptionHandlers();
      if (exceptionHandlers != null) {
        for (final ExceptionHandlerImpl exceptionHandler : exceptionHandlers) {
          if (exceptionHandler.matches(exception)) {
            try {
              exceptionHandler.handle(this, exception);
              return;
            } catch (final Exception rethrowException) {
              if (!exceptionHandler.isRethrowMasked()) {
                exception = rethrowException;
              }
            }
            break;
          }
        }
      }
    }

    log.trace("rethrowing exception cause no exception handler for "
        + exception);
    ExceptionHandlerImpl.rethrow(exception, rethrowMessage + ": "
        + exception.getMessage());
  }

  /**
   * searches for an event up the process element parent hierarchy starting from
   * the given process element and returns an event or null if no such event
   * exists.
   */
  EventImpl findEvent(final String eventName, ObservableElementImpl observableElement) {
    EventImpl event = null;
    while ((event == null) && (observableElement != null)) {
      event = observableElement.getEvent(eventName);
      if (event == null) {
        observableElement = observableElement.getParent();
      }
    }
    return event;
  }

  // comments /////////////////////////////////////////////////////////////////

  public Comment createComment(final String message) {
    if (message == null) {
    	String msg = ExceptionManager.getInstance().getFullMessage("bp_EI_26");
      throw new BonitaRuntimeException(msg);
    }
    final CommentImpl comment = new CommentImpl(message);
    addComment(comment);
    return comment;
  }

  public void removeComment(final Comment comment) {
  	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_27");
    throw new UnsupportedOperationException(message);
  }

  public void addComment(final CommentImpl comment) {
    if (comment == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_28");
      throw new BonitaRuntimeException(message);
    }
    if (this.comments == null) {
      this.comments = new LinkedHashSet();
    }
    this.comments.add(comment);
  }

  // child executions /////////////////////////////////////////////////////////

  /** @see Execution#createProcessInstance() */
  public ExecutionImpl createExecution() {
    return createExecution((String) null);
  }

  public Execution createExecution(final Execution parent) {
    return ((ExecutionImpl) parent).createExecution();
  }

  public Execution createExecution(final String name, final Execution parent) {
    return ((ExecutionImpl) parent).createExecution(name);
  }

  /** @see Execution#createProcessInstance(String) */
  public ExecutionImpl createExecution(final String name) {
    // creating a child execution implies that this execution
    // is not a leave any more and therefore, it is inactivated
    if (isActive()) {
      lock(STATE_INACTIVE);
      this.propagation = Propagation.EXPLICIT;
    }

    // create child execution
    final ExecutionImpl childExecution = newChildExecution();
    childExecution.processDefinition = this.processDefinition;
    childExecution.processInstance = this.processInstance;
    childExecution.node = this.node;
    childExecution.state = STATE_ACTIVE;
    childExecution.name = name;
    log.debug("creating " + childExecution);
    // add it to this execution
    addExecution(childExecution);
    // invalidate the cached executionsMap
    this.executionsMap = null;

    final IdGenerator keyGenerator = Environment.getFromCurrent(IdGenerator.class,
        false);
    if (keyGenerator != null) {
      childExecution.id = keyGenerator.createId(this.processDefinition, this,
          childExecution);
    }

    return childExecution;
  }

  protected ExecutionImpl newChildExecution() {
    return new ExecutionImpl();
  }

  public void addExecution(final Execution execution) {
    final ExecutionImpl executionImpl = (ExecutionImpl) execution;
    executionImpl.parent = this;
    if (this.executions == null) {
      this.executions = new ArrayList();
    }
    this.executions.add(executionImpl);
  }

  /** @see Execution#getExecution(String) */
  public ExecutionImpl getExecution(final String name) {
    final Map executionsMap = getExecutionsMap();
    return (ExecutionImpl) (executionsMap != null ? executionsMap.get(name)
        : null);
  }

  public void removeExecution(final Execution child) {
    if (this.executions != null) {
      if (this.executions.remove(child)) {
        if (this.state.equals(STATE_INACTIVE) && (this.executions.isEmpty())) {
          if (log.isTraceEnabled()) {
            log.trace("last child execution was removed; unlocking");
          }
          this.state = STATE_ACTIVE;
        } else if (log.isTraceEnabled()) {
          log.trace("removed " + child + " from " + this);
        }
        // invalidate the executionsMap cache
        this.executionsMap = null;
      } else {
      	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_29", child, this);
        throw new BonitaRuntimeException(message);
      }
    }
  }

  public void removeExecution(final Execution child, final Execution parent) {
    ((ExecutionImpl) parent).removeExecution(child);
  }

  public Map getExecutionsMap() {
    if ((this.executionsMap == null) && (this.executions != null)) {
      // initialize executionsMap cache
      this.executionsMap = new HashMap();
      for (final ExecutionImpl execution : this.executions) {
        final String executionName = execution.getName();
        // the next test makes sure that the first execution wins
        // in case there are multiple executions with the same name
        if (!this.executionsMap.containsKey(executionName)) {
          this.executionsMap.put(executionName, execution);
        }
      }
    }
    return this.executionsMap;
  }

  public boolean hasExecution(final String name) {
    return ((getExecutionsMap() != null) && this.executionsMap.containsKey(name));
  }

  // sub process creation /////////////////////////////////////////////////////

  public ClientProcessInstance createSubProcessInstance(
      final ClientProcessDefinition processDefinition) {
    return createSubProcessInstance(processDefinition, null);
  }

  public ClientProcessInstance createSubProcessInstance(
      final ClientProcessDefinition processDefinition, final String key) {
    if (this.subProcessInstance != null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_30", toString(), this.subProcessInstance);
      throw new BonitaRuntimeException(message);
    }
    this.subProcessInstance = (ExecutionImpl) processDefinition
        .createProcessInstance(key);
    this.subProcessInstance.setSuperProcessExecution(this);
    return this.subProcessInstance;
  }

  public ClientProcessInstance beginSubProcessInstance(
      final ClientProcessDefinition processDefinition) {
    return beginSubProcessInstance(processDefinition, null);
  }

  public ClientProcessInstance beginSubProcessInstance(
      final ClientProcessDefinition processDefinition, final String key) {
    createSubProcessInstance(processDefinition, key);
    this.subProcessInstance.begin();
    return this.subProcessInstance;
  }

  // node name ////////////////////////////////////////////////////////////////

  public String getNodeName() {
    if (this.node == null) {
      return null;
    }
    return this.node.getName();
  }

  // //////////////////////////////////////////////////////////////////////////////

  public void addLog(final ProcessLog processLog) {
    final Environment environment = Environment.getCurrent();
    if (environment != null) {
      final LogSession logSession = environment.get(LogSession.class);
      if (logSession != null) {
        processLog.setExecution(this);
        processLog.setTime(Clock.getCurrentTime());
        logSession.add(processLog);
      }
    }
  }

  public int nextLogIndex() {
    return this.nextLogIndex++;
  }

  // overridable by process languages /////////////////////////////////////////

  /**
   * by default this will use {@link NodeImpl#findOutgoingTransition(String)} to
   * search for the outgoing transition, which includes a search over the parent
   * chain of the current node. This method allows process languages to
   * overwrite this default implementation of the transition lookup by
   * transitionName.
   */
  protected TransitionImpl findTransition(final String transitionName) {
    return this.node.findOutgoingTransition(transitionName);
  }

  protected TransitionImpl findDefaultTransition() {
    return this.node.findDefaultTransition();
  }

  // extensions ///////////////////////////////////////////////////////////////

  public  T getExtension(final Class extensionClass) {
    if (extensionClass == null) {
    	String message = ExceptionManager.getInstance().getFullMessage("bp_EI_31");
      throw new BonitaRuntimeException(message);
    }
    String message = ExceptionManager.getInstance().getFullMessage("bp_EI_32", extensionClass.getName());
    throw new BonitaRuntimeException(message);
  }

  // equals ///////////////////////////////////////////////////////////////////
  // hack to support comparing hibernate proxies against the real objects
  // since this always falls back to ==, we don't need to overwrite the hashcode
  @Override
  public boolean equals(final Object o) {
    return EqualsUtil.equals(this, o);
  }

  // getters and setters
  // /////////////////////////////////////////////////////////

  public List getComments() {
    if (this.comments == null) {
      return Collections.emptyList();
    }
    return new ArrayList(this.comments);
  }

  public Event getEvent() {
    return this.event;
  }

  public ObservableElement getEventSource() {
    return this.eventSource;
  }

  @SuppressWarnings("unchecked")
  public Collection getExecutions() {
    return (Collection) this.executions;
  }

  public String getName() {
    return this.name;
  }

  public ExecutionImpl getParent() {
    return this.parent;
  }

  public int getPriority() {
    return this.priority;
  }

  public PVMProcessDefinitionImpl getProcessDefinition() {
    return this.processDefinition;
  }

  public TransitionImpl getTransition() {
    return this.transition;
  }

  public void setEvent(final EventImpl event) {
    this.event = event;
  }

  public void setEventSource(final ObservableElementImpl eventSource) {
    this.eventSource = eventSource;
  }

  public void setPriority(final int priority) {
    this.priority = priority;
  }

  public void setTransition(final TransitionImpl transition) {
    this.transition = transition;
  }

  public Node getPreviousNode() {
    return this.previousNode;
  }

  public Transition getPreviousTransition() {
    return this.previousTransition;
  }

  public ExecutionImpl getProcessInstance() {
    return this.processInstance;
  }

  public void setProcessInstance(final ExecutionImpl processInstance) {
    this.processInstance = processInstance;
  }

  public void setComments(final Set comments) {
    this.comments = comments;
  }

  public NodeImpl getTransitionOrigin() {
    return this.transitionOrigin;
  }

  public void setTransitionOrigin(final NodeImpl transitionOrigin) {
    this.transitionOrigin = transitionOrigin;
  }

  public Exception getException() {
    return this.exception;
  }

  public void setException(final Exception exception) {
    this.exception = exception;
  }

  public ProcessModificationsImpl getProcessModifications() {
    return this.processModifications;
  }

  public void setProcessModifications(
      final ProcessModificationsImpl processModifications) {
    this.processModifications = processModifications;
  }

  public String getKey() {
    return this.key;
  }

  public Propagation getPropagation() {
    return this.propagation;
  }

  public void setPropagation(final Propagation propagation) {
    this.propagation = propagation;
  }

  public void setName(final String name) {
    this.name = name;
  }

  public void setState(final String state) {
    this.state = state;
  }

  public void setExecutions(final Collection executions) {
    this.executions = executions;
  }

  public void setParent(final ExecutionImpl parent) {
    this.parent = parent;
  }

  public void setPreviousNode(final Node previousNode) {
    this.previousNode = previousNode;
  }

  public void setPreviousTransition(final Transition previousTransition) {
    this.previousTransition = previousTransition;
  }

  public void setProcessDefinition(final PVMProcessDefinitionImpl processDefinition) {
    this.processDefinition = processDefinition;
  }

  public ExecutionImpl getSuperProcessExecution() {
    return this.superProcessExecution;
  }

  public void setSuperProcessExecution(final ExecutionImpl superProcessExecution) {
    this.superProcessExecution = superProcessExecution;
  }

  public ExecutionImpl getSubProcessInstance() {
    return this.subProcessInstance;
  }

  public void setSubProcessInstance(final ExecutionImpl subProcessExecution) {
    this.subProcessInstance = subProcessExecution;
  }

  public NodeImpl getNode() {
    return this.node;
  }

  public void setNode(final NodeImpl node) {
    this.node = node;
  }

  public long getDbid() {
    return this.dbid;
  }

  public void setKey(final String key) {
    this.key = key;
  }

  public String getId() {
    return this.id;
  }

  public void setId(final String id) {
    this.id = id;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy