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

com.effektif.workflow.impl.workflowinstance.ActivityInstanceImpl Maven / Gradle / Ivy

/*
 * Copyright 2014 Effektif GmbH.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.effektif.workflow.impl.workflowinstance;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.effektif.workflow.impl.job.Job;

import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.effektif.workflow.api.WorkflowEngine;
import com.effektif.workflow.api.model.RelativeTime;
import com.effektif.workflow.api.model.TypedValue;
import com.effektif.workflow.api.model.WorkflowInstanceId;
import com.effektif.workflow.api.workflowinstance.ActivityInstance;
import com.effektif.workflow.impl.conditions.ConditionImpl;
import com.effektif.workflow.impl.data.DataTypeImpl;
import com.effektif.workflow.impl.data.TypedValueImpl;
import com.effektif.workflow.impl.data.types.ListTypeImpl;
import com.effektif.workflow.impl.util.Lists;
import com.effektif.workflow.impl.util.Time;
import com.effektif.workflow.impl.workflow.ActivityImpl;
import com.effektif.workflow.impl.workflow.BindingImpl;
import com.effektif.workflow.impl.workflow.InputParameterImpl;
import com.effektif.workflow.impl.workflow.TransitionImpl;


/**
 * @author Tom Baeyens
 */
public class ActivityInstanceImpl extends ScopeInstanceImpl {
  
  public static final String STATE_STARTING = "starting"; 
  public static final String STATE_STARTING_MULTI_CONTAINER = "startingMultiParent"; 
  public static final String STATE_STARTING_MULTI_INSTANCE = "startingMultiInstance"; 
  public static final String STATE_PROPAGATE_TO_PARENT = "propagateToParent"; 
  public static final String STATE_JOINING = "joining"; 

  /** @see WorkflowInstanceImpl#isWorkAsync(ActivityInstanceImpl) */
  public static final Set START_WORKSTATES = new HashSet<>(Lists.of(
    STATE_STARTING,
    STATE_STARTING_MULTI_CONTAINER,
    STATE_STARTING_MULTI_INSTANCE));

  public static final Logger log = LoggerFactory.getLogger(WorkflowEngine.class);

  public String id;
  public ActivityImpl activity;
  public String workState;
  public WorkflowInstanceId calledWorkflowInstanceId;
  public List transitionsTaken;

  public ActivityInstanceImpl() {
  }

  public ActivityInstanceImpl(ScopeInstanceImpl parent, ActivityImpl activity, String id) {
    super(parent, activity);
    this.id = id;
    this.activity = activity;
  }

  public ActivityInstance toActivityInstance() {
    return toActivityInstance(false);
  }
  
  public ActivityInstance toActivityInstance(boolean includeWorkState) {
    ActivityInstance activityInstance = new ActivityInstance();
    activityInstance.setId(id);
    activityInstance.setActivityId(activity.id);
    activityInstance.setCalledWorkflowInstanceId(calledWorkflowInstanceId);
    toScopeInstance(activityInstance, includeWorkState);
    if (includeWorkState) {
      activityInstance.setPropertyOpt("workState", workState);
    }
    return activityInstance;
  }
  
  @Override
  protected String getActivityInstanceId() {
    return id;
  }
  
  public void execute() {
    if (workflow.workflowEngine.notifyActivityInstanceStarted(this)) {
      activity.activityType.execute(this);
    }
  }

  /** Default BPMN logic when an activity ends */
  @Override
  public void onwards() {
    if (log.isDebugEnabled()) {
      log.debug("Onwards "+this);
    }
    
    // First we end the activity instance.  
    // Subsequent invocations to end will be ignored.
    end();

    boolean isTransitionTaken = false;
    
    // Take each outgoing transition 'in parallel'
    // Note that process concurrency is not the same as java multithreaded computation
    if (activity.hasOutgoingTransitions()) {
      for (TransitionImpl transition: activity.outgoingTransitions) {
        // Only take a transition if there is no condition or if the condition resolves to true.
        ConditionImpl condition = transition.condition;
        if (condition!=null ? condition.eval(this) : true) {
          isTransitionTaken = true;
          takeTransition(transition);
        }
      }
    } 

    // if there were no outgoing transitions or none of them could be taken, 
    if (!isTransitionTaken) {
      // propagate the execution flow upwards to the parent
       propagateToParent();
    }
  }

  public void endAndPropagateToParent() {
    end();
    propagateToParent();
  }

  public void end() {
    if (end==null) {
      if (hasOpenActivityInstances()) {
        throw new RuntimeException("Can't end this activity instance. There are open activity instances: " +this);
      }
      setEnd(Time.now());
      workflow.workflowEngine.notifyActivityInstanceEnded(this);
      destroyScopeInstance();
      setWorkState(null);
    }
  }

  public void propagateToParent() {
    setWorkState(STATE_PROPAGATE_TO_PARENT);
    workflowInstance.addWork(this);
  }

  public void setWorkState(String workState) {
    // log.debug("Setting workstate of "+this+" from "+this.workState+" to "+workState);
    this.workState = workState;
    if (updates!=null) {
      getUpdates().isWorkStateChanged = true;
      if (parent!=null) {
        parent.propagateActivityInstanceChange();
      }
    }
  }

  public void setJoining() {
    setWorkState(STATE_JOINING);
  }
  
  public boolean isJoining() {
    return STATE_JOINING.equals(workState);
  }
  
  public void removeJoining(ActivityInstanceImpl activityInstance) {
    activityInstance.setWorkState(null);
  }

  /** Starts the to (destination) activity in the current (parent) scope.
   * This methods will also end the current activity instance.
   * This method can be called multiple times in one start() */
  public void takeTransition(TransitionImpl transition) {
    if (transition.id!=null || activity.activityType.saveTransitionsTaken()) {
      addTransitionTaken(transition.id);
    }
    ActivityInstanceImpl toActivityInstance = null;
    ActivityImpl to = transition.to;
    if (to!=null) {
      end();
      if (log.isDebugEnabled()) {
        log.debug("Taking transition to "+to);
      }
      toActivityInstance = parent.createActivityInstance(to);
    } else {
      log.debug("Dangling transition.  Propagating to parent.");
      end();
      propagateToParent();
    }
    workflow.workflowEngine.notifyTransitionTaken(this, transition, toActivityInstance);
  }
  
  protected void addTransitionTaken(String transitionId) {
    if (transitionsTaken==null) {
      transitionsTaken = new ArrayList<>();
    }
    transitionsTaken.add(transitionId);
    if (updates!=null) {
      getUpdates().isTransitionsTakenChanged = true;
      if (parent!=null) {
        parent.propagateActivityInstanceChange();
      }
    }
  }

  @Override
  public ActivityInstanceImpl findActivityInstance(String activityInstanceId) {
    if (activityInstanceId == null) {
      return null;
    }
    if (activityInstanceId.equals(this.id)) {
      return this;
    }
    return super.findActivityInstance(activityInstanceId);
  }

  public ActivityImpl getActivity() {
    return activity;
  }
  
  public void setActivity(ActivityImpl activityDefinition) {
    this.activity = activityDefinition;
  }
  
  public String toString() {
    String activityTypeName = activity.activityType.getActivityApiClass().getSimpleName();
    String activityId = activity.id; 
    String activityName = activity.activity.getName();
    return "("+activityTypeName+"|"+(activityName!=null?activityName+"|":"")+(activityId!=null?activityId+"|":"")+id+")";
  }
  
  public void setEnd(LocalDateTime end) {
    this.end = end;
    if (start!=null && end!=null) {
      this.duration = end.toDate().getTime()-start.toDate().getTime();
    }
    if (updates!=null) {
      updates.isEndChanged = true;
      if (parent!=null) {
        parent.propagateActivityInstanceChange();
      }
    }
  }

  @Override
  public ActivityInstanceImpl findActivityInstanceByActivityId(String activityDefinitionId) {
    if (activityDefinitionId==null) {
      return null;
    }
    if (activityDefinitionId.equals(activity.id)) {
      return this;
    }
    return super.findActivityInstanceByActivityId(activityDefinitionId);
  }
  
  @Override
  public boolean isWorkflowInstance() {
    return false;
  }

  public void setCalledWorkflowInstanceId(WorkflowInstanceId calledWorkflowInstanceId) {
    this.calledWorkflowInstanceId = calledWorkflowInstanceId;
  }
  
  public WorkflowInstanceId getCalledWorkflowInstanceId() {
    return calledWorkflowInstanceId;
  }

  @Override
  public ActivityInstanceUpdates getUpdates() {
    return (ActivityInstanceUpdates) updates;
  }

  public void trackUpdates(boolean isNew) {
    if (updates==null) {
      updates = new ActivityInstanceUpdates(isNew);
    } else {
      updates.reset(isNew);
    }
    super.trackUpdates(isNew);
  }

  public boolean hasActivityInstance(String activityInstanceId) {
    if (id!=null && id.equals(activityInstanceId)) {
      return true;
    }
    return super.hasActivityInstance(activityInstanceId);
  }

  public String getId() {
    return id;
  }

  // TODO add the expected type for conversion?
  public  T getInputValue(String key) {
    if (activity==null || activity.activityType==null || activity.activityType.getInputs()==null) {
      return null;
    }
    InputParameterImpl parameter = (InputParameterImpl) activity.activityType.getInputs().get(key);
    TypedValueImpl typedValue = getInputTypedValue(parameter);
    return (T) (typedValue!=null ? typedValue.value : null);
  }

  protected TypedValueImpl getInputTypedValue(InputParameterImpl parameter) {
    if (parameter!=null) {
      if (parameter.binding != null) {
        return getTypedValue(parameter.binding);
      }
      if (parameter.bindings != null) {
        DataTypeImpl listType = null;
        List values = new ArrayList<>();
        for (BindingImpl< ? > binding : parameter.bindings) {
          TypedValueImpl typedValue = getTypedValue(binding);
          if (typedValue!=null) {
            if (typedValue.getValue() instanceof Collection) {
              if (listType==null && typedValue.getType()!=null) {
                listType = typedValue.getType(); 
              }
              Collection value = (Collection) typedValue.value;
              if (value!=null) {
                values.addAll(value);
              }
            } else {
              if (listType==null && typedValue.getType()!=null) {
                listType = new ListTypeImpl(typedValue.getType());
              }
              values.add(typedValue.value);
            }
          }
        }
        return new TypedValueImpl(listType, values);
      }
    }
    return null;
  }

  public Map getInputValueImpls() {
    Map inputValues = new HashMap<>();
    if (activity!=null && activity.activityType!=null && activity.activityType.getInputs()!=null) {
      Map inputs = activity.activityType.getInputs();
      for (String inputKey: inputs.keySet()) {
        InputParameterImpl inputParameter = inputs.get(inputKey);
        TypedValueImpl inputValue = getInputTypedValue(inputParameter);
        if (inputValue!=null) {
          inputValues.put(inputKey, inputValue);
        }
      }
    }
    return inputValues;
  }

  public Map getInputValues() {
    Map inputValues = new HashMap<>();
    Map inputValueImpls = getInputValueImpls();
    if (inputValueImpls!=null) {
      for (String key: inputValueImpls.keySet()) {
        TypedValueImpl typedValueImpl = inputValueImpls.get(key);
        inputValues.put(key, typedValueImpl.toTypedValue());
      }
    }
    return inputValues;
  }
}