alma.acs.nc.sm.generic.AcsScxmlActionDispatcher.java Source code

Java tutorial

Introduction

Here is the source code for alma.acs.nc.sm.generic.AcsScxmlActionDispatcher.java

Source

/*******************************************************************************
 * ALMA - Atacama Large Millimeter Array
 * Copyright (c) ESO - European Southern Observatory, 2011
 * (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.nc.sm.generic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.scxml.ErrorReporter;
import org.apache.commons.scxml.EventDispatcher;
import org.apache.commons.scxml.SCInstance;
import org.apache.commons.scxml.SCXMLExpressionException;
import org.apache.commons.scxml.TriggerEvent;
import org.apache.commons.scxml.model.CustomAction;
import org.apache.commons.scxml.model.ModelException;
import org.apache.commons.scxml.semantics.ErrorConstants;

import alma.ACSErrTypeCommon.wrappers.AcsJStateMachineActionEx;

/**
 * The user-supplied dispatcher that will be invoked by an 
 * instance of {@link AcsScxmlDispatchingAction},
 * thus bringing action handlers under user control 
 * instead of using directly the handler instantiated by the SCXML framework.
 * <p>
 * Handlers must be registered using {@link #registerActionHandler(Enum, AcsScxmlActionExecutor)}.
 * 
 * @author hsommer
 * @param <A> The SM action enumeration. 
 */
public class AcsScxmlActionDispatcher<A extends Enum<A>> {
    public static final String CUSTOM_ACTIONS_DOMAIN_NAME = "http://my.custom-actions.domain/CUSTOM";

    protected final Logger logger;
    private final Class<A> actionType;
    private final Map<A, AcsScxmlActionExecutor<A>> actionMap;

    /**
     * Optionally set by method {@link #setActionExceptionHandler(ActionExceptionHandler)}.
     */
    private volatile ActionExceptionHandler actionExceptionHandler;

    public AcsScxmlActionDispatcher(Logger logger, Class<A> actionType) {
        this.logger = logger;
        this.actionType = actionType;
        actionMap = new EnumMap<A, AcsScxmlActionExecutor<A>>(actionType);
    }

    /**
     * Generic action handler, dispatches to the right handler based on action type.
     * <p>
     * TODO: org.apache.commons.scxml.env.SimpleErrorReporter#onError prints the class name of the "Object errCtx".
     *       Currently we'd get NPE or useless info.
     */
    public void execute(A action, final EventDispatcher evtDispatcher, final ErrorReporter errRep,
            final SCInstance scInstance, final Collection<TriggerEvent> derivedEvents)
            throws ModelException, SCXMLExpressionException {
        AcsScxmlActionExecutor<A> handler = getActionHandler(action);
        if (handler != null) {
            boolean handledAction = false;
            try {
                handledAction = handler.execute(action, evtDispatcher, errRep, scInstance, derivedEvents);
            } catch (AcsJStateMachineActionEx ex) {
                handledAction = true;

                if (actionExceptionHandler != null) { // handler is optional
                    // ex will be thrown at the user
                    actionExceptionHandler.setActionException(ex);
                } else {
                    logger.log(Level.WARNING,
                            "Handler " + handler.getClass() + " failed to execute action " + action.name(), ex);
                }

                // TODO: define and fire a standardized internal error event so that the state machine
                // can reflect the problem and can be maneuvered out of the error again by the user 
            }
            if (!handledAction) {
                errRep.onError(ErrorConstants.UNKNOWN_ACTION,
                        "Handler " + handler.getClass() + " handler unexpectedly did not handle action ", action);
            }
        } else {
            errRep.onError(ErrorConstants.UNKNOWN_ACTION, "No handler registered for action " + action.name(),
                    null);
        }
    }

    /**
     * An alternative to {@link #execute(Enum, EventDispatcher, ErrorReporter, SCInstance, Collection)}
     * where the handler can be accessed and stored directly.
     * Using this approach may bring a tiny performance advantage, but probably should be removed again..
     */
    public AcsScxmlActionExecutor<A> getActionHandler(A action) {
        return actionMap.get(action);
    }

    /**
     * Registers an action handler for the given SM action.
     * Note that the same handler object can be registered for one or many actions,
     * so that we are flexible in how we implement the handlers.
     * @param action
     * @param handler
     */
    public void registerActionHandler(A action, AcsScxmlActionExecutor<A> handler) {
        actionMap.put(action, handler);
    }

    /**
     * Checks whether all action enums have an action handler registered.
     * <p>
     * This method can be used to validate early at runtime that no action goes
     * without handler, which might happen after a change in the SM model.
     * <p>
     * Note that this kind of completeness check could also be done at build time
     * using an action enum with visitor pattern, see  
     * http://raginggoblin.wordpress.com/2010/06/10/switch-over-all-values-of-a-java-enum/.
     * However, this would require us to dispatch all actions through a single interface
     * that has a typed method per action and whose implementation could then of course 
     * dispatch further to various objects.
     * For the time being we stay with the looser coupling of registering various action handler
     * objects and validating them for completeness early at runtime (not waiting until
     * some rare action finally occurs).
     * 
     * @return true if for all SM actions we have a registered handler.
     */
    public boolean isActionMappingComplete() {
        for (A action : actionType.getEnumConstants()) {
            if (!actionMap.containsKey(action)) {
                logger.warning("No handler has been registered for SM action " + action.name() + ".");
                return false;
            }
        }
        return true;
    }

    /**
     * Used by an {@link AcsScxmlDispatchingAction} object to convert its action name to the corresponding enum value.
     * (This type information cannot be passed directly because Apache SCXML creates the action with an empty constructor.)
     */
    public Class<A> getActionType() {
        return actionType;
    }

    /**
     * Wraps the registered action handlers in a form suitable for the SCXML framework.
     * The framework will instantiate DispatchingScxmlAction for each action,
     * which will then call back to {@link #execute(Enum, EventDispatcher, ErrorReporter, SCInstance, Collection)}. 
     * @return
     */
    protected List<CustomAction> getScxmlActionMap() {
        List<CustomAction> ret = new ArrayList<CustomAction>();

        for (A action : actionMap.keySet()) {
            ret.add(new CustomAction(CUSTOM_ACTIONS_DOMAIN_NAME, action.name(), AcsScxmlDispatchingAction.class));
        }
        return ret;
    }

    public static interface ActionExceptionHandler {
        /**
         * Callback method, which only gets called if an action terminates with an exception.
         * @param ex
         */
        public void setActionException(AcsJStateMachineActionEx ex);
    }

    public void setActionExceptionHandler(ActionExceptionHandler handler) {
        actionExceptionHandler = handler;
    }

}