/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2004
* Copyright by ESO (in the framework of the ALMA collaboration),
* All rights reserved
*
* This library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
package alma.acs.genfw.runtime.sm;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import alma.acs.logging.AcsLogger;
/**
* Represents an Activity in state (do-activity).
* <p>
* Implementation note: uses a single instance of {@link ExecutorService}
* for cheaply reusing a thread in {@link #execute()} for all do activities.
*
* @author hsommer
* created Apr 29, 2004 3:55:28 PM
*/
public abstract class AcsDoActivity
{
private final AcsSimpleState m_nextState;
private final AcsSimpleState m_errorState;
private final String m_name;
private final AcsLogger logger;
/**
* not actually used as a queue, but simply to wrap and reuse a <code>Thread</code>,
* since only one do-action method is supposed to run at a time
* in a non-concurrent state machine.
* todo: or maybe it's a queue, if a composite (super) state also has a /do method ?
*/
private final ThreadPoolExecutor executor;
private volatile boolean m_completed;
/**
* @param name name for the activity
* @param nextState state to which the implicit "completion transition" will go.
* @param errorState error state to which we'll go in case of errors.
* @param logger
* Logger used by this class.
* @param threadFactory
* The thread factory used for asynchronous execution of the {@link #runActions()} method.
*/
public AcsDoActivity(String name, AcsSimpleState nextState, AcsSimpleState errorState, AcsLogger logger, ThreadPoolExecutor executor)
{
if (name == null) {
throw new IllegalArgumentException("Parameter 'name' must not be null.");
}
if (nextState == null) {
throw new IllegalArgumentException("Parameter 'nextState' must not be null.");
}
if (errorState == null) {
throw new IllegalArgumentException("Parameter 'errorState' must not be null.");
}
if (executor == null) {
throw new IllegalArgumentException("Parameter 'executor' must not be null.");
}
m_name = name;
m_nextState = nextState;
m_errorState = errorState;
this.logger = logger;
this.executor = executor;
m_completed = true;
}
/**
* Runs {@link #runActions()} in a separate thread and returns immediately.
* When the actions are completed, a transition to the next state is triggered,
* as specified in the constructor.
*/
public void execute()
{
m_completed = false;
Runnable doActionRunner = new Runnable() {
public void run()
{
try {
runActions();
m_completed = true;
// move on to next state
m_nextState.activate("/do-activities '" + m_name + " completed.");
}
catch (Throwable thr) {
String msg = "The asynchronous execution of actions for activity '" + m_name + "' has thrown an exception. Will go into error state.";
logger.log(Level.WARNING, msg, thr);
// Go into error state (@todo: decide if this is appropriate)
m_errorState.activate("/do-activities '" + m_name + "' failed.");
}
}
};
if (logger.isLoggable(Level.FINE)) {
if (executor.getActiveCount() > 0) {
logger.fine("Execution of activity '" + m_name + "' gets delayed because another activity is still being executed");
}
}
try {
executor.execute(doActionRunner);
}
catch (RejectedExecutionException ex) {
String msg = "/do-activities '" + m_name + "' failed";
logger.log(Level.WARNING, msg, ex);
m_errorState.activate(msg + ": " + ex.toString());
}
}
/**
* Must call the action methods associated with this activity.
*/
public abstract void runActions() throws AcsStateActionException;
/**
* Terminates the actions if they are still running.
*/
public void terminateActions()
{
if (!m_completed) {
logger.warning("/do-activities '" + m_name + "' terminated prematurely!");
executor.shutdownNow();
}
}
/**
* @param sourceStateName beginning of transition, or activity state
* @param targetStateName end of transition,
* or <code>null</code> if the action comes from the <code>do/</code> method of an activity state.
* @param actionName
*/
void logActionFailure(String sourceStateName, String targetStateName, String actionName, Throwable thr) {
String msg = "action '" + actionName + "' ";
if (targetStateName == null) {
msg += "associated with activity state '" + sourceStateName + "' ";
} else {
msg += "between states '" + sourceStateName + "' and '" + targetStateName + "' ";
}
msg += "has thrown an exception.";
logger.log(Level.SEVERE, msg, thr);
}
}