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

com.googlecode.sarasvati.impl.BaseEngine Maven / Gradle / Ivy

The newest version!
/*
    This file is part of Sarasvati.

    Sarasvati 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 3 of the
    License, or (at your option) any later version.

    Sarasvati 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 Sarasvati.  If not, see .

    Copyright 2008-2009 Paul Lorenz
*/
package com.googlecode.sarasvati.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.googlecode.sarasvati.Arc;
import com.googlecode.sarasvati.ArcToken;
import com.googlecode.sarasvati.ArcTokenSetMember;
import com.googlecode.sarasvati.CustomNode;
import com.googlecode.sarasvati.Engine;
import com.googlecode.sarasvati.ExecutionType;
import com.googlecode.sarasvati.Graph;
import com.googlecode.sarasvati.GraphProcess;
import com.googlecode.sarasvati.GuardAction;
import com.googlecode.sarasvati.GuardResult;
import com.googlecode.sarasvati.JoinAction;
import com.googlecode.sarasvati.JoinResult;
import com.googlecode.sarasvati.Node;
import com.googlecode.sarasvati.NodeToken;
import com.googlecode.sarasvati.NodeTokenSetMember;
import com.googlecode.sarasvati.ProcessState;
import com.googlecode.sarasvati.SarasvatiException;
import com.googlecode.sarasvati.TokenSet;
import com.googlecode.sarasvati.TokenSetMember;
import com.googlecode.sarasvati.env.Env;
import com.googlecode.sarasvati.env.TokenSetMemberEnv;
import com.googlecode.sarasvati.event.ArcTokenEvent;
import com.googlecode.sarasvati.event.DefaultExecutionEventQueue;
import com.googlecode.sarasvati.event.EventActionType;
import com.googlecode.sarasvati.event.EventActions;
import com.googlecode.sarasvati.event.ExecutionEvent;
import com.googlecode.sarasvati.event.ExecutionEventType;
import com.googlecode.sarasvati.event.ExecutionListener;
import com.googlecode.sarasvati.event.GraphDefinedEventListenerInvoker;
import com.googlecode.sarasvati.event.NodeTokenEvent;
import com.googlecode.sarasvati.event.ProcessDefinedEventListenerInvoker;
import com.googlecode.sarasvati.event.ProcessEvent;
import com.googlecode.sarasvati.join.lang.JoinLangEnv;
import com.googlecode.sarasvati.join.lang.JoinLangEnvImpl;
import com.googlecode.sarasvati.rubric.RubricCompiler;
import com.googlecode.sarasvati.rubric.env.DefaultRubricEnv;
import com.googlecode.sarasvati.rubric.env.DefaultRubricFunctionRepository;
import com.googlecode.sarasvati.rubric.env.RubricEnv;
import com.googlecode.sarasvati.script.ScriptEnv;
import com.googlecode.sarasvati.visitor.BacktrackTokenVisitor;
import com.googlecode.sarasvati.visitor.TokenTraversals;

/**
 * Contains all the engine logic which is not backend specific.
 * 

* Instances of BaseEngine have local state which is not thread-safe. Create * a new Engine instance for each thread that needs one. * * @author Paul Lorenz */ public abstract class BaseEngine implements Engine { protected static final String DEFAULT_APPLICATION_CONTEXT = ""; protected static final Map globalQueueMap = new ConcurrentHashMap(); protected boolean arcExecutionStarted = false; protected final List asyncQueue = new LinkedList(); protected final DefaultExecutionEventQueue globalEventQueue; protected final String applicationContext; protected BaseEngine parentEngine; /** * Creates a new Engine with the given application context. * Each application context has its own set of global listeners. * * This allows different applications running the same JVM to * have different sets of listeners without having to add * them at the process level. * @param applicationContext The application context */ public BaseEngine (final String applicationContext) { this.applicationContext = applicationContext; this.globalEventQueue = getGlobalQueueForContext(applicationContext); } private DefaultExecutionEventQueue getGlobalQueueForContext (final String context) { DefaultExecutionEventQueue queue = globalQueueMap.get(context); if ( queue == null ) { queue = DefaultExecutionEventQueue.newCopyOnWriteListInstance(); contributeGlobalListeners( queue ); globalQueueMap.put(context, queue ); } return queue; } /** * Provides a subclass to override which execution event listeners are added to * new global queues. By default this adds the following listeners: *

    *
  • {@link TokenSetCompletionListener}
  • *
  • {@link GraphDefinedEventListenerInvoker}
  • *
  • {@link ProcessDefinedEventListenerInvoker}
  • *
* * @param queue The new global queue */ protected void contributeGlobalListeners (final DefaultExecutionEventQueue queue) { queue.addListener( new TokenSetCompletionListener(), ExecutionEventType.ARC_TOKEN_COMPLETED, ExecutionEventType.ARC_TOKEN_INCOMPLETE_JOIN, ExecutionEventType.NODE_TOKEN_COMPLETED ); queue.addListener( new GraphDefinedEventListenerInvoker() ); queue.addListener( new ProcessDefinedEventListenerInvoker() ); } @Override public GraphProcess startProcess (final String graphName) { return startProcess(graphName, null); } @Override public GraphProcess startProcess (final String graphName, final Env initialEnv) { if ( graphName == null ) { throw new SarasvatiException( "Can not start process for a null graph name" ); } final Graph graph = getRepository().getLatestGraph( graphName ); if ( graph == null ) { throw new SarasvatiException( "No graph found with name '" + graphName + "'" ); } return startProcess(graph, initialEnv); } @Override public GraphProcess startProcess (final Graph graph) { return startProcess(graph, null); } @Override public GraphProcess startProcess (final Graph graph, final Env env) { if ( graph == null ) { throw new SarasvatiException( "Can not start process for a null graph" ); } final GraphProcess process = getFactory().newProcess( graph ); if (env != null) { process.getEnv().importEnv(env); } ProcessEvent.fireCreatedEvent( this, process ); startProcess( process ); return process; } @Override public void startProcess (final GraphProcess process) { process.setState( ProcessState.Executing ); ProcessEvent.fireStartedEvent( this, process ); arcExecutionStarted = true; final List startNodes = process.getGraph().getStartNodes(); if ( startNodes.isEmpty() ) { throw new SarasvatiException( "Cannot start a workflow which has no start nodes! " + "Please check your process definition: '" + process.getGraph().getName() ); } try { for ( final Node startNode : startNodes ) { final NodeToken startToken = getFactory().newNodeToken( process, startNode, new ArrayList(0) ); NodeTokenEvent.fireCreatedEvent(this, startToken); process.addNodeToken( startToken ); executeNode( process, startToken ); } executeQueuedArcTokens( process ); } finally { arcExecutionStarted = false; drainAsyncQueue( process ); } if ( process.isExecuting() ) { checkForCompletion( process ); } } @Override public void cancelProcess (final GraphProcess process) { for (final GraphProcess subProcess : getRepository().getActiveNestedProcesses(process)) { cancelProcess(subProcess); } process.setState( ProcessState.PendingCancel ); final EventActions actions = ProcessEvent.firePendingCancelEvent( this, process ); if ( !actions.isEventTypeIncluded( EventActionType.DELAY_PROCESS_FINALIZE_CANCEL ) ) { finalizeCancel( process ); } } @Override public void finalizeComplete (final GraphProcess process) { process.setState( ProcessState.Completed ); ProcessEvent.fireCompletedEvent( this, process ); final NodeToken parentToken = process.getParentToken(); if ( parentToken != null ) { final Engine engine = getParentEngine() == null ? newEngine( false ) : getParentEngine(); engine.complete( parentToken, Arc.DEFAULT_ARC ); } } @Override public void finalizeCancel (final GraphProcess process) { process.setState( ProcessState.Canceled ); ProcessEvent.fireCanceledEvent( this, process ); } private void executeArc (final GraphProcess process, final ArcToken token) { if ( token.isPending() ) { token.markProcessed(); ArcTokenEvent.fireProcessedEvent( this, token ); process.addActiveArcToken( token ); final JoinAction joinAction = retryIncompleteArcToken(token); if ( JoinAction.Nothing == joinAction ) { ArcTokenEvent.fireIncompleteJoinEvent( this, token ); } } } @Override public JoinAction retryIncompleteArcToken (final ArcToken token) { if ( !token.isComplete() ) { final GraphProcess process = token.getProcess(); final Node targetNode = token.getArc().getEndNode(); final JoinResult result = targetNode.getJoinStrategy( token.getArc() ).performJoin( this, token ); if ( JoinAction.Complete == result.getJoinAction() ) { completeExecuteArc( process, targetNode, result.getArcTokensCompletingJoin(), result.getTerminatingTokenSets() ); } else if ( JoinAction.Merge == result.getJoinAction() ) { for ( final ArcToken arcToken : result.getArcTokensCompletingJoin() ) { result.getMergeTarget().getParentTokens().add( arcToken ); completeArcToken( process, arcToken, result.getMergeTarget() ); ArcTokenEvent.fireMergedEvent( this, arcToken ); } } return result.getJoinAction(); } return null; } private void completeExecuteArc (final GraphProcess process, final Node targetNode, final Collection tokens, final List terminatingTokenSets) { final NodeToken nodeToken = getFactory().newNodeToken( process, targetNode, new ArrayList( tokens ) ); process.addNodeToken( nodeToken ); // Add new node token to add the token sets which its generating arc tokens are members of final Set tokenSets = new HashSet(); for ( final ArcToken token : tokens ) { for ( final ArcTokenSetMember setMember : token.getTokenSetMemberships() ) { final TokenSet tokenSet = setMember.getTokenSet(); if ( !terminatingTokenSets.contains(tokenSet) && !tokenSets.contains( tokenSet ) ) { tokenSets.add( tokenSet ); final NodeTokenSetMember newSetMember = getFactory().newNodeTokenSetMember( tokenSet, nodeToken, setMember.getMemberIndex() ); tokenSet.getActiveNodeTokens( this ).add( nodeToken ); nodeToken.getTokenSetMemberships().add( newSetMember ); } } } NodeTokenEvent.fireCreatedEvent( this, nodeToken ); for ( final ArcToken token : tokens ) { completeArcToken( process, token, nodeToken ); } executeNode( process, nodeToken ); } private void completeArcToken (final GraphProcess process, final ArcToken token, final NodeToken child) { process.removeActiveArcToken( token ); if ( token.isPending() ) { token.markProcessed(); ArcTokenEvent.fireProcessedEvent( this, token ); } token.markComplete( child ); ArcTokenEvent.fireCompletedEvent( this, token ); } @Override public void reevaluateDelayedToken(final NodeToken token) { if (token.isComplete()) { throw new SarasvatiException("Cannot reevaluate a token which has already been completed."); } if (token.getGuardAction() != GuardAction.DelayUntil) { throw new SarasvatiException("Cannot reevaluate a token which has not been delayed."); } executeNode(token.getProcess(), token); } protected void executeNode (final GraphProcess process, final NodeToken token) { final GuardResult response = token.getNode().guard( this, token ); token.setGuardAction( response.getGuardAction() ); switch ( response.getGuardAction() ) { case AcceptToken : process.addActiveNodeToken( token ); final EventActions actions = NodeTokenEvent.fireAcceptedEvent( this, token ); if ( !actions.isEventTypeIncluded( EventActionType.DELAY_NODE_EXECUTION ) ) { token.getNode().execute( this, token ); NodeTokenEvent.fireExecutedEvent( this, token ); } break; case DiscardToken : token.markComplete(); process.removeActiveNodeToken( token ); // Although this token has never been explicitly added to the collection, // a poorly timed lazy load of HibGraphProcess.activeNodeTokens can cause // the token to be present therein NodeTokenEvent.fireDiscardedEvent( this, token ); final List exitArcs = Collections.emptyList(); NodeTokenEvent.fireCompletedEvent( this, token, exitArcs); break; case SkipNode : process.addActiveNodeToken( token ); NodeTokenEvent.fireSkippedEvent( this, token, response.getExitArcForSkip() ); complete( token, response.getExitArcForSkip() ); break; case DelayUntil : process.addActiveNodeToken( token ); token.markDelayed( response.getDelayTillTime() ); NodeTokenEvent.fireDelayedEvent( this, token ); getDelayedTokenScheduler().scheduleDelayedToken(token); break; } } @Override public void complete(final NodeToken token, final String arcName) { final GraphProcess process = token.getProcess(); completeNodeExecution( token, false, arcName ); if ( process.isExecuting() && !arcExecutionStarted ) { executeQueuedArcTokens( process ); } } @Override public void completeMany(final NodeToken token, final String...arcNames) { final GraphProcess process = token.getProcess(); completeNodeExecution(token, false, arcNames); if ( process.isExecuting() && !arcExecutionStarted ) { executeQueuedArcTokens( process ); } } @Override public void completeAsynchronous (final NodeToken token, final String arcName) { completeNodeExecution( token, true, arcName ); } @Override public void completeManyAsynchronous (final NodeToken token, final String...arcNames) { completeNodeExecution(token, true, arcNames); } @Override public void completeWithNewTokenSet (final NodeToken token, final String arcName, final String tokenSetName, final int numberOfTokens, final boolean asynchronous, final Env initialEnv, final Map> initialMemberEnv) { final GraphProcess process = token.getProcess(); if ( !process.isExecuting() || token.isComplete() ) { return; } final List outArcs = process.getGraph().getOutputArcs( token.getNode(), arcName ); completeNodeToken( process, token, outArcs, arcName ); if ( !outArcs.isEmpty() ) { int level = 0; for (final TokenSetMember tokenSetMember : token.getTokenSetMemberships()) { level = Math.max(level, tokenSetMember.getTokenSet().getLevel()); } final TokenSet tokenSet = getFactory().newTokenSet( process, tokenSetName, numberOfTokens - 1, level + 1 ); if ( initialEnv != null ) { tokenSet.getEnv().importEnv( initialEnv ); } if ( initialMemberEnv != null ) { final TokenSetMemberEnv memberEnv = tokenSet.getMemberEnv(); for ( final Map.Entry> entry : initialMemberEnv.entrySet() ) { memberEnv.setAttribute( entry.getKey(), entry.getValue() ); } } for ( int memberIndex = 0; memberIndex < numberOfTokens; memberIndex++ ) { for ( final Arc arc : outArcs ) { final ArcToken arcToken = generateArcToken( process, arc, token ); final ArcTokenSetMember setMember = getFactory().newArcTokenSetMember( tokenSet, arcToken, memberIndex ); tokenSet.getActiveArcTokens( this ).add( arcToken ); arcToken.getTokenSetMemberships().add( setMember ); finishNewArcTokenProcessing( process, arcToken, asynchronous ); } } } } protected void completeNodeExecution(final NodeToken token, final boolean asynchronous, final String...arcNames) { final GraphProcess process = token.getProcess(); if ( !process.isExecuting() || token.isComplete() ) { return; } final List exitArcs = process.getGraph().getOutputArcs( token.getNode(), arcNames); completeNodeToken(process, token, exitArcs, arcNames); for (final Arc arc : exitArcs) { final ArcToken arcToken = generateArcToken(process, arc, token); finishNewArcTokenProcessing(process, arcToken, asynchronous); } } private void completeNodeToken (final GraphProcess process, final NodeToken token, final List exitArcs, final String...arcNames) { process.removeActiveNodeToken(token); token.markComplete(); NodeTokenEvent.fireCompletedEvent(this, token, exitArcs, arcNames); } private ArcToken generateArcToken (final GraphProcess process, final Arc arc, final NodeToken token) { final Set nodeSetTokenMemberships = token.getTokenSetMemberships(); final ArcToken arcToken = getFactory().newArcToken( process, arc, ExecutionType.Forward, token, !nodeSetTokenMemberships.isEmpty() ); token.getChildTokens().add( arcToken ); for ( final NodeTokenSetMember setMember : nodeSetTokenMemberships ) { final TokenSet tokenSet = setMember.getTokenSet(); final ArcTokenSetMember newSetMember = getFactory().newArcTokenSetMember( tokenSet, arcToken, setMember.getMemberIndex() ); tokenSet.getActiveArcTokens( this ).add( arcToken ); arcToken.getTokenSetMemberships().add( newSetMember ); } return arcToken; } private void finishNewArcTokenProcessing (final GraphProcess process, final ArcToken arcToken, final boolean asynchronous) { ArcTokenEvent.fireCreatedEvent( this, arcToken ); if ( asynchronous && arcExecutionStarted ) { asyncQueue.add( arcToken ); } else { process.enqueueArcTokenForExecution( arcToken ); } } @Override public void executeQueuedArcTokens (final GraphProcess process) { arcExecutionStarted = true; try { while ( process.isExecuting() && !process.isArcTokenQueueEmpty() ) { executeArc( process, process.dequeueArcTokenForExecution() ); } checkForCompletion( process ); } finally { arcExecutionStarted = false; drainAsyncQueue( process ); } } private void drainAsyncQueue (final GraphProcess process) { while ( !asyncQueue.isEmpty() ) { process.enqueueArcTokenForExecution( asyncQueue.remove( 0 ) ); } } private void checkForCompletion (final GraphProcess process) { if ( process.isExecuting() && !process.hasActiveTokens() && process.isArcTokenQueueEmpty() && asyncQueue.isEmpty() ) { process.setState( ProcessState.PendingCompletion ); final EventActions actions = ProcessEvent.firePendingCompleteEvent( this, process ); if ( !actions.isEventTypeIncluded( EventActionType.DELAY_PROCESS_FINALIZE_COMPLETE ) ) { finalizeComplete( process ); } } } @Override public void setupScriptEnv (final ScriptEnv env, final NodeToken token) { env.addVariable( "engine", this ); env.addVariable( "token", token ); } @Override public void addNodeType (final String type, final Class nodeClass) { getFactory().addType(type, nodeClass); } @Override public void addGlobalCustomNodeType (final String type, final Class nodeClass) { getFactory().addGlobalCustomType( type, nodeClass ); } @Override public void backtrack (final NodeToken token) { if ( !token.getProcess().isExecuting() ) { throw new SarasvatiException( "Can only backtrack processes with a state of 'Executing'" ); } if ( !token.isComplete() ) { throw new SarasvatiException( "Cannot backtrack to a node token which isn't completed." ); } if ( token.getExecutionType().isBacktracked() ) { throw new SarasvatiException( "Cannot backtrack to a node token which has been backtracked." ); } NodeToken resultToken = null; final BacktrackTokenVisitor visitor = new BacktrackTokenVisitor( this, token ); if ( token.getChildTokens().isEmpty() ) { resultToken = visitor.backtrackDeadEnd( token ); } else { TokenTraversals.traverseChildrenInCreateOrder( token, visitor ); resultToken = visitor.backtrack(); } executeNode( resultToken.getProcess(), resultToken ); if ( !arcExecutionStarted ) { executeQueuedArcTokens( token.getProcess() ); } } @Override public RubricEnv newRubricEnv (final NodeToken token) { return new DefaultRubricEnv( this, token, DefaultRubricFunctionRepository.getGlobalInstance() ); } @Override public JoinLangEnv newJoinLangEnv (final ArcToken token) { return new JoinLangEnvImpl( this, token, newRubricEnv( token.getParentToken() ) ); } @Override public GuardResult evaluateGuard (final NodeToken token, final String guard) { if ( guard == null || guard.trim().length() == 0 ) { return AcceptTokenGuardResult.INSTANCE; } return (GuardResult) RubricCompiler.compile( guard ).eval( newRubricEnv( token ) ); } @Override public BaseEngine newEngine (final boolean forNested) { final BaseEngine engine = newEngine(); if ( forNested ) { engine.parentEngine = this; } return engine; } @Override public BaseEngine getParentEngine () { return parentEngine; } public String getApplicationContext() { return applicationContext; } /** * Creates a new engine base on the same parameters as this. For * example, if the engine is database backed, it should share * the same database engine. * * @return A new engine */ protected abstract BaseEngine newEngine (); // ========================================================================================== // Global Event Queue Methods // ========================================================================================== @Override public void addExecutionListener(final Class listenerClass, final ExecutionEventType... eventTypes) { globalEventQueue.addListener( this, listenerClass, eventTypes ); } @Override public void removeExecutionListener(final Class listenerClass, final ExecutionEventType... eventTypes) { globalEventQueue.removeListener( this, listenerClass, eventTypes ); } @Override public void addExecutionListener (final GraphProcess process, final Class listenerClass, final ExecutionEventType... eventTypes) { process.getEventQueue().addListener( this, listenerClass, eventTypes ); } @Override public void removeExecutionListener (final GraphProcess process, final Class listenerClass, final ExecutionEventType... eventTypes) { process.getEventQueue().removeListener( this, listenerClass, eventTypes ); } @Override public EventActions fireEvent (final ExecutionEvent event) { return globalEventQueue.fireEvent( event ); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy