You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@struts.apache.org by mr...@apache.org on 2005/08/26 07:46:58 UTC

svn commit: r240168 [4/30] - in /struts/sandbox/trunk/ti: ./ core/src/java/org/apache/ti/ core/src/java/org/apache/ti/config/ core/src/java/org/apache/ti/config/mapper/ core/src/java/org/apache/ti/core/ core/src/java/org/apache/ti/core/factory/ core/sr...

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowController.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowController.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowController.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowController.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,876 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * 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.
+ *
+ * $Header:$
+ */
+package org.apache.ti.pageflow;
+
+import com.opensymphony.xwork.Action;
+import org.apache.ti.core.ActionMessage;
+import org.apache.ti.core.urls.MutableURI;
+import org.apache.ti.pageflow.handler.ExceptionsHandler;
+import org.apache.ti.pageflow.handler.Handlers;
+import org.apache.ti.pageflow.handler.LoginHandler;
+import org.apache.ti.pageflow.internal.AdapterManager;
+import org.apache.ti.pageflow.internal.InternalConstants;
+import org.apache.ti.pageflow.internal.InternalExpressionUtils;
+import org.apache.ti.pageflow.internal.InternalUtils;
+import org.apache.ti.pageflow.xwork.PageFlowAction;
+import org.apache.ti.pageflow.xwork.PageFlowActionContext;
+import org.apache.ti.pageflow.xwork.PageFlowResult;
+import org.apache.ti.util.internal.InternalStringBuilder;
+import org.apache.ti.util.internal.ServletUtils;
+import org.apache.ti.util.internal.cache.ClassLevelCache;
+import org.apache.ti.util.logging.Logger;
+
+import javax.security.auth.login.LoginException;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ * Base class for user-written flow controllers - {@link PageFlowController}s and {@link SharedFlowController}s.
+ */
+public abstract class FlowController extends PageFlowManagedObject
+        implements PageFlowConstants, ActionResolver {
+
+    private static final Logger _log = Logger.getInstance(FlowController.class);
+
+    private static final String ONCREATE_EXCEPTION_FORWARD = InternalConstants.ATTR_PREFIX + "onCreateException";
+    private static final String CACHEID_ACTION_METHODS = InternalConstants.ATTR_PREFIX + "actionMethods";
+    private static final int DEFAULT_MAX_CONCURRENT_REQUEST_COUNT = 4;
+    private static final int EXCEEDED_MAX_CONCURRENT_REQUESTS_ERRORCODE = 503;
+    private static final Forward NULL_ACTION_FORWARD = new Forward();
+
+
+    /**
+     * Cached reference to the associated Struts ModuleConfig.
+     */
+    private transient ModuleConfig _moduleConfig = null;
+
+    /**
+     * @see #incrementRequestCount
+     */
+    private transient int _requestCount = 0;
+
+    /**
+     * Default constructor.
+     */
+    protected FlowController() {
+    }
+
+    /**
+     * Reinitialize the object for a new request.  Used by the framework; normally should not be called directly.
+     */
+    public void reinitialize() {
+        //
+        // Cache the associated ModuleConfig.  This is used throughout the code, in places where the request
+        // isn't available to do a lazy initialization.
+        //
+        super.reinitialize();
+        initModuleConfig();
+    }
+
+    /**
+     * Send a Page Flow error to the browser.
+     *
+     * @param errText the error message to display.
+     */
+    protected void sendError(String errText)
+            throws IOException {
+        InternalUtils.sendError("PageFlow_Custom_Error", null, new Object[]{getDisplayName(), errText});
+    }
+
+    /**
+     * Handle the given exception - invoke user code if appropriate and return a destination URI.
+     *
+     * @param ex the Exception to handle.
+     * @throws PageFlowException if another Exception is thrown during handling of <code>ex</code>.
+     */
+    public synchronized Forward handleException(Throwable ex)
+            throws PageFlowException {
+        ExceptionsHandler eh = Handlers.get().getExceptionsHandler();
+        
+        // First, put the exception into the request (or other applicable context).
+        Throwable unwrapped = eh.unwrapException(ex);
+        eh.exposeException(unwrapped);
+        eh.handleException(unwrapped);
+        return new Forward(Action.NONE);
+    }
+
+    /**
+     * Get the name of the current action being executed.  This call is only valid
+     * during {@link FlowController#execute} (where any user action method is invoked), and during the lifecycle
+     * methods {@link FlowController#beforeAction} and {@link FlowController#afterAction}.
+     *
+     * @return the name of the current action being executed.
+     * @throws IllegalStateException if this method is invoked outside of action method
+     *                               execution (i.e., outside of the call to {@link FlowController#execute}, and outside of
+     *                               {@link FlowController#onCreate}, {@link FlowController#beforeAction}, {@link FlowController#afterAction}.
+     */
+
+    protected static String getCurrentActionName() {
+        return getContext().getName();
+    }
+
+    /**
+     * Perform decision logic to determine the next URI to be displayed.
+     *
+     * @return a Struts forward object that specifies the next URI to be displayed.
+     * @throws PageFlowException if an Exception was thrown during user action-handling code.
+     */
+    public Forward execute()
+            throws PageFlowException {
+        //
+        // Don't actually run the action (and perform the associated synchronization) if there are too many
+        // concurrent requests to this instance.
+        //
+        if (incrementRequestCount()) {
+            try {
+                synchronized (this) {
+                    return internalExecute();
+                }
+            } finally {
+                decrementRequestCount();
+            }
+        } else {
+            return null;    // error was written to the response by incrementRequestCount()
+        }
+    }
+
+    /**
+     * An internal method for executing an action; should not be invoked directly.
+     */
+    protected Forward internalExecute()
+            throws PageFlowException {
+        ContainerAdapter sca = AdapterManager.getContainerAdapter();
+        PageFlowEventReporter eventReporter = sca.getEventReporter();
+        eventReporter.actionRaised(this);
+        long startTime = System.currentTimeMillis();
+        
+        //
+        // If we handled an exception in onCreate, just forward to the result of that.
+        //
+        Forward onCreateFwd = (Forward) getContext().getRequestScope().get(ONCREATE_EXCEPTION_FORWARD);
+
+        if (onCreateFwd != null) {
+            return onCreateFwd == NULL_ACTION_FORWARD ? null : onCreateFwd;
+        }
+
+
+        PageFlowUtils.setActionPath();
+        
+        // Store information on this action for use with navigateTo=ti.NavigateTo.previousAction.
+        savePreviousActionInfo();
+        
+        
+        //
+        // First change the actionPath (path) so that it lines up with our naming convention
+        // for action methods.
+        //
+        boolean gotPastBeforeAction = false;
+
+        try {
+            //
+            // beforeAction callback
+            //
+            beforeAction();
+            gotPastBeforeAction = true;
+
+            PageFlowActionContext actionContext = PageFlowActionContext.get();
+            PageFlowAction pfAction = actionContext.getAction();
+            String actionName = actionContext.getName();
+            
+            //
+            // Check whether isLoginRequired=true for this action.
+            //
+            LoginHandler loginHandler = Handlers.get().getLoginHandler();
+
+            if (pfAction.isLoginRequired() && loginHandler.getUserPrincipal() == null) {
+                NotLoggedInException ex = createNotLoggedInException(actionName);
+                return handleException(ex);
+            }
+            
+            //
+            // Now delegate to the appropriate action method, or if it's a simple action, handle it that way.
+            //
+            Forward retVal;
+            if (pfAction.isSimpleAction()) {
+                retVal = handleSimpleAction(pfAction);
+            } else {
+                retVal = getActionMethodForward(actionName);
+            }
+
+            long timeTaken = System.currentTimeMillis() - startTime;
+            eventReporter.actionSuccess(this, retVal, timeTaken);
+            return retVal;
+        } catch (Exception e) {
+            //
+            // Even though we handle any Throwable thrown by the user's action method, we don't need
+            // to catch Throwable here, because anything thrown by the action method will be wrapped
+            // in an InvocationTargetException.  Any Error (or other Throwable) that appears here
+            // should not be handled by handleException() -- it's probably a framework problem and
+            // should bubble out to the container.
+            //
+            return handleException(e);
+        } finally {
+            Forward overrideReturn = null;
+
+            if (gotPastBeforeAction) {
+                //
+                // afterAction callback
+                //
+                try {
+                    afterAction();
+                } catch (Throwable th) {
+                    overrideReturn = handleException(th);
+                }
+            }
+
+            if (overrideReturn != null) return overrideReturn;
+        }
+    }
+
+    NotLoggedInException createNotLoggedInException(String actionName) {
+        if (ServletUtils.isSessionExpired(getContext().getWebContext())) {
+            return new LoginExpiredException(this);
+        } else {
+            return new NotLoggedInException(this);
+        }
+    }
+
+    public void login(String username, String password) throws LoginException {
+        Handlers.get().getLoginHandler().login(username, password);
+    }
+
+    public void logout(boolean invalidateSessions) {
+        Handlers.get().getLoginHandler().logout(invalidateSessions);
+    }
+
+    /**
+     * Initialize after object creation.  This is a framework-invoked method; it should not normally be called directly.
+     */
+    public synchronized void create() {
+        try {
+            super.create();
+        } catch (Throwable th) {
+            try {
+                _log.info("Handling exception in onCreate(), FlowController " + this, th);
+                Forward fwd = handleException(th);
+                if (fwd == null) fwd = NULL_ACTION_FORWARD;
+                getContext().getRequestScope().put(ONCREATE_EXCEPTION_FORWARD, fwd);
+            } catch (Exception e) {
+                _log.error("Exception thrown while handling exception in onCreate(): " + e.getMessage(), th);
+            }
+        }
+
+        PageFlowEventReporter er = AdapterManager.getContainerAdapter().getEventReporter();
+        er.flowControllerCreated(this);
+    }
+
+    /**
+     * Internal destroy method that is invoked when this object is being removed from the session.  This is a
+     * framework-invoked method; it should not normally be called directly.
+     */
+    void destroy() {
+        super.destroy();
+        PageFlowEventReporter er = AdapterManager.getContainerAdapter().getEventReporter();
+        er.flowControllerDestroyed(this, Handlers.get().getStorageHandler().getStorageLocation());
+    }
+
+    /**
+     * Get the namespace for this controller.
+     *
+     * @return a String that is the namespace for this controller.
+     */
+    public abstract String getNamespace();
+
+    /**
+     * Callback that occurs before any user action method is invoked.
+     */
+    protected synchronized void beforeAction()
+            throws Exception {
+    }
+
+    /**
+     * Callback that occurs after any user action method is invoked.
+     */
+    protected synchronized void afterAction()
+            throws Exception {
+    }
+
+    /**
+     * Callback that is invoked when this controller instance is created.
+     */
+    protected void onCreate()
+            throws Exception {
+    }
+
+    /**
+     * Callback that is invoked when this controller instance is "destroyed", i.e., removed from storage.
+     * <br>
+     * Note that this method is <strong>not synchronized</strong>.  It is dangerous to synchronize your override of
+     * this method because it may be invoked during a callback from the container.  Depending on the container,
+     * synchronization here can cause deadlocks.
+     */
+    protected void onDestroy() {
+    }
+
+    /**
+     * Get an action handler method of the given name/signature.
+     *
+     * @param methodName the name of the action handler method to query.
+     * @param argType    the type of the argument to the action handler method; if <code>null</code>,
+     *                   the method takes no arguments.
+     * @return the desired Method, or <code>null</code> if it doesn't exist.
+     */
+    protected Method getActionMethod(String methodName, Class argType) {
+        String cacheKey = argType != null ? methodName + '/' + argType.getName() : methodName;
+        Class thisClass = getClass();
+        ClassLevelCache cache = ClassLevelCache.getCache(thisClass);
+        Method actionMethod = (Method) cache.get(CACHEID_ACTION_METHODS, cacheKey);
+
+        if (actionMethod != null) {
+            return actionMethod;
+        } else {
+            //
+            // We didn't find it in the cache.  Look for it reflectively.
+            //
+            if (argType == null) {
+                //
+                // No form -- look for a method with no arguments.
+                //
+                actionMethod = InternalUtils.lookupMethod(thisClass, methodName, null);
+            } else {
+                //
+                // Has a form.  Look for a method with a single argument -- either the given type
+                // or any superclass.
+                //
+                while (argType != null) {
+                    actionMethod = InternalUtils.lookupMethod(thisClass, methodName, new Class[]{argType});
+
+                    if (actionMethod != null) {
+                        break;
+                    }
+
+                    argType = argType.getSuperclass();
+                }
+            }
+
+            if (actionMethod != null) {
+                Class returnType = actionMethod.getReturnType();
+                if (returnType.equals(Forward.class) || returnType.equals(String.class)) {
+                    if (!Modifier.isPublic(actionMethod.getModifiers())) actionMethod.setAccessible(true);
+                    cache.put(CACHEID_ACTION_METHODS, cacheKey, actionMethod);
+                    return actionMethod;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private Class getFormClass(Object form)
+            throws ClassNotFoundException {
+        String formClassName = getAction().getFormBeanType();
+        if (formClassName != null) return InternalUtils.getReloadableClass(formClassName);
+        return form != null ? form.getClass() : null;
+    }
+
+    /**
+     * Get the forward returned by the action handler method that corresponds to the
+     * given action name and form-bean, or send an error to the browser if there is no
+     * matching method.
+     *
+     * @param actionName the name of the Struts action to handle.
+     * @return the forward returned by the action handler method, or <code>null</code> if
+     *         there was no matching method (in which case an error was written to the
+     *         browser.
+     * @throws Exception if an Exception was raised in user code.
+     */
+    Forward getActionMethodForward(String actionName)
+            throws Exception {
+        //
+        // Find the method.
+        //
+        Object formBean = getContext().getFormBean();
+        Class formClass = getFormClass(formBean);
+        Method actionMethod = getActionMethod(actionName, formClass);
+
+        //
+        // Invoke the method.
+        //
+        if (actionMethod != null) {
+            return invokeActionMethod(actionMethod, formBean);
+        }
+
+        if (_log.isWarnEnabled()) {
+            InternalStringBuilder msg = new InternalStringBuilder("Could not find matching action method for action=");
+            msg.append(actionName).append(", form=");
+            msg.append(formBean != null ? formBean.getClass().getName() : "[none]");
+            _log.warn(msg.toString());
+        }
+
+        FlowControllerException ex = new NoMatchingActionMethodException(formBean, this);
+        InternalUtils.throwPageFlowException(ex);
+        return null;
+    }
+
+    /**
+     * Invoke the given action handler method, passing it an argument if appropriate.
+     *
+     * @param method the action handler method to invoke.
+     * @param arg    the form-bean to pass; may be <code>null</code>.
+     * @return the forward returned by the action handler method.
+     * @throws Exception if an Exception was raised in user code.
+     */
+    Forward invokeActionMethod(Method method, Object arg)
+            throws Exception {
+        Class[] paramTypes = method.getParameterTypes();
+
+        try {
+            if (paramTypes.length > 0 && paramTypes[0].isInstance(arg)) {
+                if (_log.isDebugEnabled()) {
+                    _log.debug("Invoking action method " + method.getName() + '(' + paramTypes[0].getName() + ')');
+                }
+
+                return invokeForwardMethod(method, new Object[]{arg});
+            } else if (paramTypes.length == 0) {
+                if (_log.isDebugEnabled()) {
+                    _log.debug("Invoking action method " + method.getName() + "()");
+                }
+
+                return invokeForwardMethod(method, null);
+            }
+        } finally {
+            if (!getAction().isReadonly()) {
+                ensureFailover();
+            }
+        }
+
+        if (_log.isWarnEnabled()) {
+            _log.warn("Could not find action method " + method.getName() + " with appropriate signature.");
+        }
+
+        return null;
+    }
+
+    private Forward invokeForwardMethod(Method method, Object[] args)
+            throws IllegalAccessException, InvocationTargetException {
+        Object result = method.invoke(this, args);
+        if (result instanceof String) {
+            result = new Forward((String) result);
+        }
+        return (Forward) result;
+    }
+
+    private void initModuleConfig() {
+        if (_moduleConfig == null) {
+            _moduleConfig = Handlers.get().getModuleRegistrationHandler().getModuleConfig(getNamespace());
+            assert _moduleConfig != null : getNamespace() + "; " + getClass().getName();
+        }
+    }
+
+    /**
+     * Gets the Struts module configuration associated with this controller.
+     *
+     * @return the Struts ModuleConfig for this controller.
+     */
+    public ModuleConfig getModuleConfig() {
+        initModuleConfig();
+        return _moduleConfig;
+    }
+
+    /**
+     * Call an action and return the result URI.
+     * 
+     * @param actionName the name of the action to run.
+     * @param form the form bean instance to pass to the action, or <code>null</code> if none should be passed.
+     * @return the result webapp-relative URI, as a String.
+     * @throws ActionNotFoundException when the given action does not exist in this FlowController.
+     * @throws Exception if the action method throws an Exception.
+     */ 
+    /* TODO: re-enable this method
+    public String resolveAction( String actionName, Object form )
+        throws Exception
+    {
+        ActionConfig mapping = ( ActionConfig ) getModuleConfig().findActionConfig( '/' + actionName );
+        
+        if ( mapping == null )
+        {
+            InternalUtils.throwPageFlowException( new ActionNotFoundException( actionName, this, form ) );
+        }
+        
+        forward fwd = getActionMethodForward( actionName, form );
+        
+        if ( fwd instanceof forward )
+        {
+            ( ( forward ) fwd ).initialize();
+        }
+        
+        String path = fwd.getPath();
+        if ( path.startsWith("/") || FileUtils.isAbsoluteURI( path ) )
+        {
+            return path;
+        }
+        else
+        {
+            return '/' + getNamespace() + '/' + path;
+        }
+    }
+    */
+
+    /**
+     * Get a list of the names of actions handled by methods in this PageFlowController.
+     *
+     * @return a String array containing the names of actions handled by methods in this PageFlowController.
+     */
+    public String[] getActions() {
+        Map actionConfigs = getModuleConfig().getActionConfigs();
+        return (String[]) actionConfigs.keySet().toArray(new String[actionConfigs.size()]);
+    }
+
+    /**
+     * Tell whether a given String is the name of an action handled by a method in this PageFlowController.
+     *
+     * @param name the action-name to query.
+     * @return <code>true</code> if <code>name</code> is the name of an action handled by a method in this
+     *         PageFlowController.
+     */
+    public boolean isAction(String name) {
+        return getModuleConfig().findActionConfig(name) != null;
+    }
+
+    /**
+     * Called on this object for non-lookup (refresh) requests.  This is a framework-invoked method that should not
+     * normally be called directly.
+     */
+    public final synchronized void refresh() {
+        onRefresh();
+    }
+
+    /**
+     * Callback that is invoked when this controller is involved in a refresh request, as can happen in a portal
+     * environment on a request where no action is run in the current page flow, but a previously-displayed page in the
+     * page flow is re-rendered.
+     */
+    protected void onRefresh() {
+    }
+
+    /**
+     * Remove this instance from the user session.
+     */
+    protected void remove() {
+        removeFromSession();
+    }
+
+    /**
+     * Used by derived classes to store information on the most recent action executed.
+     */
+    void savePreviousActionInfo() {
+    }
+
+    /**
+     * Store information about recent pages displayed.  This is a framework-invoked method that should not normally be
+     * called directly.
+     */
+    public void savePreviousPageInfo(PageFlowResult result, Forward fwd, Object form) {
+    }
+
+    /**
+     * When this FlowController does not use a {@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}
+     * annotation with a
+     * <code>navigateTo=</code>{@link org.apache.ti.pageflow.annotations.ti.NavigateTo#previousAction ti.NavigateTo.previousAction}
+     * attribute, the following methods always return <code>null</code> by default.
+     * <ul>
+     * <li>getPreviousActionInfo</li>
+     * <li>getPreviousActionURI</li>
+     * <li>getPreviousForm</li>
+     * </ul>
+     * Override <code>alwaysTrackPreviousAction</code> (which always returns <code>false</code>) to enable these methods
+     * in all cases.
+     *
+     * @return <code>true</code> if the previous action should always be tracked, regardless of whether
+     *         <code>return-to="previousAction"</code> is used.
+     * @see PageFlowController#getPreviousActionInfo
+     * @see PageFlowController#getPreviousActionURI
+     * @see PageFlowController#getPreviousFormBean
+     */
+    protected boolean alwaysTrackPreviousAction() {
+        return false;
+    }
+
+    /**
+     * When this FlowController does not use a {@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}
+     * annotation with either a
+     * <code>navigateTo</code>={@link org.apache.ti.pageflow.annotations.ti.NavigateTo#currentPage ti.NavigateTo.currentPage}
+     * attribute or a
+     * <code>navigateTo</code>={@link org.apache.ti.pageflow.annotations.ti.NavigateTo#previousPage ti.NavigateTo.previousPage}
+     * attribute, the following methods always return <code>null</code> by default.
+     * <ul>
+     * <li>getCurrentPageInfo</li>
+     * <li>getPreviousPageInfo</li>
+     * <li>getCurrentForwardPath</li>
+     * <li>getPreviousForwardPath</li>
+     * </ul>
+     * Override <code>alwaysTrackPreviousPage</code> (which always returns <code>false</code>) to enable these methods
+     * in all cases.
+     *
+     * @return <code>true</code> if the previous page should always be tracked, regardless
+     *         of whether <code>return-to="currentPage"</code> or <code>return-to="previousPage"</code>
+     *         is used.
+     * @see PageFlowController#getCurrentPageInfo
+     * @see PageFlowController#getPreviousPageInfo
+     * @see PageFlowController#getCurrentForwardPath
+     * @see PageFlowController#getPreviousForwardPath
+     */
+    protected boolean alwaysTrackPreviousPage() {
+        return false;
+    }
+
+    /**
+     * Increment the count of concurrent requests to this FlowController.  Note that this method
+     * is not synchronized -- it is used to decide whether to synchronize on this instance,
+     * or to bail out with an error message about too many concurrent requests.  This is a framework-invoked
+     * method that should not normally be called directly.
+     */
+    public boolean incrementRequestCount()
+            throws PageFlowException {
+        //
+        // Now, if the current count of concurrent requests to this instance is greater than the max,
+        // send an error on the response.
+        //
+        if (_requestCount >= DEFAULT_MAX_CONCURRENT_REQUEST_COUNT) {
+            if (_log.isDebugEnabled()) {
+                _log.debug("Too many requests to FlowController " + getDisplayName() + " ("
+                        + (_requestCount + 1) + '>' + DEFAULT_MAX_CONCURRENT_REQUEST_COUNT
+                        + "); returning error code " + EXCEEDED_MAX_CONCURRENT_REQUESTS_ERRORCODE);
+            }
+
+            // TODO: re-add (need an abstraction for this)
+            //response.sendError( EXCEEDED_MAX_CONCURRENT_REQUESTS_ERRORCODE );
+            return false;
+        }
+        
+        //
+        // We're ok -- increment the count and continue.
+        //
+        ++_requestCount;
+        return true;
+    }
+
+    /**
+     * Decrement the count of concurrent requests to this FlowController.  Note that this method is not synchronized --
+     * it is used in conjunction with {@link #incrementRequestCount} to decide whether to synchronize on this instance,
+     * or to bail out with an error message about too many concurrent requests.  This is a framework-invoked
+     * method that should not normally be called directly.
+     */
+    public void decrementRequestCount() {
+        assert _requestCount > 0 : getContext().getRequestPath();
+        --_requestCount;
+    }
+
+    /**
+     * Invoke the given exception handler method.  This is a framework-invoked method that should not normally be called
+     * directly
+     *
+     * @param method   the action handler method to invoke.
+     * @param ex       the Throwable that is to be handled.
+     * @param message  the String message that is to be passed to the handler method.
+     * @param formBean the form bean that is associated with the action being processed; may be <code>null</code>.
+     * @param readonly if <code>true</code>, session failover will not be triggered after invoking the method.
+     * @return the forward returned by the exception handler method.
+     */
+    public synchronized Forward invokeExceptionHandler(Method method, Throwable ex, String message,
+                                                       Object formBean, boolean readonly)
+            throws PageFlowException {
+        try {
+            if (_log.isDebugEnabled()) {
+                _log.debug("Invoking exception handler method " + method.getName() + '('
+                        + method.getParameterTypes()[0].getName() + ", ...)");
+            }
+
+            try {
+                Forward retVal = null;
+
+                try {
+                    Object[] args = new Object[]{ex, message};
+                    retVal = invokeForwardMethod(method, args);
+                } finally {
+                    if (!readonly) {
+                        ensureFailover();
+                    }
+                }
+
+                return retVal;
+            } catch (InvocationTargetException e) {
+                Throwable target = e.getTargetException();
+
+                if (target instanceof Exception) {
+                    throw (Exception) target;
+                } else {
+                    throw e;
+                }
+            }
+        } catch (Throwable e) {
+            _log.error("Exception while handling exception " + ex.getClass().getName()
+                    + ".  The original exception will be thrown.", e);
+
+            ExceptionsHandler eh = Handlers.get().getExceptionsHandler();
+            Throwable unwrapped = eh.unwrapException(e);
+
+            if (!eh.eatUnhandledException(unwrapped)) {
+                if (ex instanceof PageFlowException) throw (PageFlowException) ex;
+                if (ex instanceof Error) throw (Error) ex;
+                throw new PageFlowException(ex);
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * Add a property-related message that will be shown with the Errors and Error tags.
+     *
+     * @param propertyName the name of the property with which to associate this error.
+     * @param messageKey   the message-resources key for the message.
+     * @param messageArgs  zero or more arguments to the message.
+     */
+    protected static void addActionError(String propertyName, String messageKey, Object[] messageArgs) {
+        InternalUtils.addActionError(propertyName, new ActionMessage(messageKey, messageArgs));
+    }
+
+    /**
+     * Add a property-related message as an expression that will be evaluated and shown with the Errors and Error tags.
+     *
+     * @param propertyName the name of the property with which to associate this error.
+     * @param expression   the expression that will be evaluated to generate the error message.
+     * @param messageArgs  zero or more arguments to the message; may be expressions.
+     */
+    protected static void addActionErrorExpression(String propertyName, String expression, Object[] messageArgs) {
+        PageFlowUtils.addActionErrorExpression(propertyName, expression, messageArgs);
+    }
+
+    private static Forward handleSimpleAction(PageFlowAction action) {
+        Map/*< String, String >*/ conditionalForwards = action.getConditionalForwardsMap();
+
+        if (!conditionalForwards.isEmpty()) {
+            for (Iterator/*< Map.Entry< String, String > >*/ i = conditionalForwards.entrySet().iterator(); i.hasNext();) {
+                Map.Entry/*< String, String >*/ entry = (Map.Entry) i.next();
+                String expression = (String) entry.getKey();
+                String forwardName = (String) entry.getValue();
+
+                try {
+                    if (InternalExpressionUtils.evaluateCondition(expression)) {
+                        if (_log.isTraceEnabled()) {
+                            PageFlowActionContext actionContext = PageFlowActionContext.get();
+                            _log.trace("Expression '" + expression + "' evaluated to true on simple action "
+                                    + actionContext.getName() + "; using forward "
+                                    + forwardName + '.');
+                        }
+
+                        return new Forward(forwardName);
+                    }
+                } catch (Exception e)  // ELException
+                {
+                    if (_log.isErrorEnabled()) {
+                        _log.error("Exception occurred evaluating navigation expression '" + expression
+                                + "'.  Cause: " + e.getCause(), e);
+                    }
+                }
+            }
+        }
+
+
+        String defaultForwardName = action.getDefaultForward();
+        assert defaultForwardName != null : "defaultForwardName is null on simple action "
+                + PageFlowActionContext.get().getName();
+
+        if (_log.isTraceEnabled()) {
+            PageFlowActionContext actionContext = PageFlowActionContext.get();
+            _log.trace("No expression evaluated to true on simple action " + actionContext.getName()
+                    + "; using forward " + defaultForwardName + '.');
+        }
+
+        return new Forward(defaultForwardName);
+    }
+
+
+    /**
+     * Get the flow-scoped form bean member associated with the given ActionConfig.  This is a framework-invoked
+     * method that should not normally be called directly.
+     */
+    public Object getFormBean(PageFlowAction action) {
+        String formMember = action.getFormMember();
+
+        try {
+            if (formMember != null) {
+                Field field = getClass().getDeclaredField(formMember);
+                field.setAccessible(true);
+                return field.get(this);
+            }
+        } catch (Exception e) {
+            _log.error("Could not use member field " + formMember + " as the form bean.", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Create a raw action URI, which can be modified before being sent through the registered URL rewriting chain
+     * using {@link org.apache.ti.core.urls.URLRewriterService#rewriteURL}.
+     *
+     * @param actionName the action name to convert into a MutableURI; may be qualified with a path from the webapp
+     *                   root, in which case the parent directory from the current request is <i>not</i> used.
+     * @return a MutableURI for the given action, suitable for URL rewriting.
+     * @throws URISyntaxException    if there is a problem converting the action URI (derived
+     *                               from processing the given action name) into a MutableURI.
+     * @throws IllegalStateException if this method is invoked outside of action method
+     *                               execution (i.e., outside of the call to {@link FlowController#execute},
+     *                               and outside of {@link FlowController#onCreate},
+     *                               {@link FlowController#beforeAction}, {@link FlowController#afterAction}.
+     */
+    public MutableURI getActionURI(String actionName)
+            throws URISyntaxException {
+        return PageFlowUtils.getActionURI(actionName);
+    }
+
+    /**
+     * Create a fully-rewritten URI given an action and parameters.
+     *
+     * @param actionName the action name to convert into a fully-rewritten URI; may be qualified with a path from the
+     *                   webapp root, in which case the parent directory from the current request is <i>not</i> used.
+     * @param parameters the additional parameters to include in the URI query.
+     * @param asValidXml flag indicating that the query of the uri should be written
+     *                   using the &quot;&amp;amp;&quot; entity, rather than the character, '&amp;'
+     * @return a fully-rewritten URI for the given action.
+     * @throws URISyntaxException    if there is a problem converting the action url (derived
+     *                               from processing the given action name) into a URI.
+     * @throws IllegalStateException if this method is invoked outside of action method
+     *                               execution (i.e., outside of the call to {@link FlowController#execute},
+     *                               and outside of {@link FlowController#onCreate},
+     *                               {@link FlowController#beforeAction}, {@link FlowController#afterAction}.
+     */
+    public String getRewrittenActionURI(String actionName, Map parameters, boolean asValidXml)
+            throws URISyntaxException {
+        return PageFlowUtils.getRewrittenActionURI(actionName, parameters, null, asValidXml);
+    }
+}

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowControllerException.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowControllerException.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowControllerException.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowControllerException.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * 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.
+ *
+ * $Header:$
+ */
+package org.apache.ti.pageflow;
+
+import org.apache.ti.pageflow.xwork.PageFlowActionContext;
+
+
+/**
+ * Base class for PageFlow-related Exceptions.
+ */
+public abstract class FlowControllerException
+        extends PageFlowManagedObjectException {
+
+    private String _actionName;
+
+
+    protected FlowControllerException(FlowController fc) {
+        super(fc);
+        _actionName = PageFlowActionContext.get().getName();
+    }
+
+    protected FlowControllerException(FlowController fc, Throwable cause) {
+        super(fc, cause);
+        _actionName = PageFlowActionContext.get().getName();
+    }
+
+    /**
+     * Get the related FlowController.
+     *
+     * @return the {@link FlowController} associated with this exception.
+     */
+    public FlowController getFlowController() {
+        return (FlowController) getManagedObject();
+    }
+
+    /**
+     * Get the name of the related FlowController.
+     *
+     * @return the class name of the {@link FlowController} associated with this exception.
+     */
+    public String getFlowControllerURI() {
+        FlowController flowController = getFlowController();
+        return flowController != null ? flowController.getDisplayName() : null;
+    }
+
+    /**
+     * Get the name of the action associated with this exception.
+     *
+     * @return a String that is the name of the action associated with this exception.
+     */
+    public String getActionName() {
+        return _actionName;
+    }
+
+    public void setActionName(String actionName) {
+        _actionName = actionName;
+    }
+
+    /**
+     * Tell whether the root cause may be session expiration in cases where the requested session ID is different than
+     * the actual session ID; if <code>true</code>, then a {@link SessionExpiredException} will be thrown instead of
+     * this one in these situations.
+     */
+    public abstract boolean causeMayBeSessionExpiration();
+}

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowControllerFactory.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowControllerFactory.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowControllerFactory.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FlowControllerFactory.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * 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.
+ *
+ * $Header:$
+ */
+package org.apache.ti.pageflow;
+
+import org.apache.ti.core.factory.Factory;
+import org.apache.ti.core.factory.FactoryUtils;
+import org.apache.ti.pageflow.handler.Handlers;
+import org.apache.ti.pageflow.handler.ModuleRegistrationHandler;
+import org.apache.ti.pageflow.internal.InternalConstants;
+import org.apache.ti.pageflow.internal.InternalUtils;
+import org.apache.ti.pageflow.xwork.PageFlowActionContext;
+import org.apache.ti.schema.config.DefaultSharedFlowRefs;
+import org.apache.ti.schema.config.PageflowConfig;
+import org.apache.ti.schema.config.PageflowFactories;
+import org.apache.ti.schema.config.PageflowFactory;
+import org.apache.ti.schema.config.SharedFlowRef;
+import org.apache.ti.util.config.ConfigUtil;
+import org.apache.ti.util.logging.Logger;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+/**
+ * Factory for creating {@link FlowController}s - user {@link PageFlowController}s and {@link SharedFlowController}s.
+ */
+public class FlowControllerFactory
+        extends Factory {
+
+    private static final Logger _log = Logger.getInstance(FlowControllerFactory.class);
+
+    private static final String CONTEXT_ATTR = InternalConstants.ATTR_PREFIX + "fcFactory";
+
+    protected FlowControllerFactory() {
+    }
+
+    protected void onCreate() {
+    }
+
+    public static void init(Map appScope) {
+        PageflowFactories factoriesBean = ConfigUtil.getConfig().getPageflowFactories();
+        FlowControllerFactory factory = null;
+
+        if (factoriesBean != null) {
+            PageflowFactory fcFactoryBean = factoriesBean.getFlowcontrollerFactory();
+            factory = (FlowControllerFactory) FactoryUtils.getFactory(fcFactoryBean, FlowControllerFactory.class);
+        }
+
+        if (factory == null) factory = new FlowControllerFactory();
+        factory.reinit();
+
+        appScope.put(CONTEXT_ATTR, factory);
+    }
+
+    /**
+     * Called to reinitialize this instance, most importantly after it has been serialized/deserialized.
+     */
+    protected void reinit() {
+        super.reinit();
+    }
+
+    /**
+     * Get a FlowControllerFactory.
+     *
+     * @return a FlowControllerFactory for the given application.  It may or may not be a cached instance.
+     */
+    public static FlowControllerFactory get() {
+        Map appScope = PageFlowActionContext.get().getApplication();
+        FlowControllerFactory factory = (FlowControllerFactory) appScope.get(CONTEXT_ATTR);
+        assert factory != null
+                : FlowControllerFactory.class.getName() + " was not found in application attribute " + CONTEXT_ATTR;
+        factory.reinit();
+        return factory;
+    }
+
+    /**
+     * Get the page flow instance that should be associated with the given request.  If it doesn't exist, create it.
+     * If one is created, the page flow stack (for nesting) will be cleared or pushed, and the new instance will be
+     * stored as the current page flow.
+     *
+     * @return the {@link PageFlowController} for the request, or <code>null</code> if none was found.
+     */
+    public PageFlowController getPageFlowForRequest()
+            throws InstantiationException, IllegalAccessException {
+        PageFlowController cur = PageFlowUtils.getCurrentPageFlow();
+        
+        //
+        // Reinitialize transient data that may have been lost on session failover.
+        //
+        if (cur != null) cur.reinitialize();
+        
+        //
+        // If there's no current PageFlow, or if the current PageFlowController has a namespace that
+        // is incompatible with the current request URI, then create the appropriate PageFlowController.
+        //
+        PageFlowActionContext actionContext = PageFlowActionContext.get();
+        String namespace = actionContext.getNamespace();
+
+        if (namespace != null && (cur == null || !cur.getNamespace().equals(namespace))) {
+            try {
+                String className = InternalUtils.getFlowControllerClassName(namespace);
+                return className != null ? createPageFlow(className) : null;
+            } catch (ClassNotFoundException e) {
+                if (_log.isInfoEnabled()) _log.info("No page flow exists for namespace " + namespace);
+                return null;
+            }
+        }
+
+        return cur;
+    }
+
+    /**
+     * Create a page flow of the given type.  The page flow stack (for nesting) will be cleared or pushed, and the new
+     * instance will be stored as the current page flow.
+     *
+     * @param pageFlowClassName the type name of the desired page flow.
+     * @return the newly-created {@link PageFlowController}, or <code>null</code> if none was found.
+     */
+    public PageFlowController createPageFlow(String pageFlowClassName)
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+        Class pageFlowClass = getFlowControllerClass(pageFlowClassName);
+        return createPageFlow(pageFlowClass);
+    }
+
+    /**
+     * Create a {@link PageFlowController} of the given type.  The PageFlowController stack (for
+     * nesting) will be cleared or pushed, and the new instance will be stored as the current
+     * PageFlowController.
+     *
+     * @param pageFlowClass the type of the desired PageFlowController.
+     * @return the newly-created PageFlowController, or <code>null</code> if none was found.
+     */
+    public PageFlowController createPageFlow(Class pageFlowClass)
+            throws InstantiationException, IllegalAccessException {
+        if (!PageFlowController.class.isAssignableFrom(pageFlowClass)) return null;
+        
+        //
+        // First check if this is a request for a "long lived" page flow.  If so, try
+        // PageFlowUtils.getCurrentPageFlow again, with the longLived flag.
+        //
+        PageFlowController retVal = null;
+        String namespace = InternalUtils.inferNamespaceFromClassName(pageFlowClass.getName());
+        ModuleRegistrationHandler mrh = Handlers.get().getModuleRegistrationHandler();
+        ModuleConfig mc = mrh.getModuleConfig(namespace);
+
+        if (mc == null) {
+            _log.error("Struts module " + namespace + " not found for " + pageFlowClass.getName()
+                    + "; cannot create page flow.");
+            return null;
+        }
+
+        if (mc.isLongLivedFlow()) {
+            retVal = PageFlowUtils.getLongLivedPageFlow(namespace);
+
+            if (_log.isDebugEnabled()) {
+                if (retVal != null) {
+                    _log.debug("Using long lived PageFlowController of type " + pageFlowClass.getName());
+                }
+            }
+        }
+        
+        //
+        // First, see if this is a nested page flow that's already on the stack.  Unless "renesting" is explicitly
+        // enabled, we don't want to allow another instance of this page flow to be nested.  This is a common
+        // browser back-button problem:
+        //    1) request nested page flow A
+        //    2) request nested page flow B
+        //    3) press back button, and execute an action on A.
+        //
+        // This logic does not deal with immediate self-nesting (A->A), which is taken care of in
+        // PageFlowController.forwardTo().  Nested page flows can only self-nest by forwarding to the .jpf URI, not
+        // indirectly by executing actions on themselves (think about it -- that would be a disaster).
+        //
+        boolean createdNew = false;
+        boolean isNestable = mc.isNestedFlow();
+        PageFlowStack pfStack = PageFlowStack.get(false);
+
+        if (isNestable && pfStack != null) {
+            PageflowConfig options = ConfigUtil.getConfig().getPageflowConfig();
+
+            if (options == null || !options.getEnableSelfNesting()) {
+                int lastIndexOfJpfClass = pfStack.lastIndexOf(pageFlowClass);
+
+                if (lastIndexOfJpfClass != -1) {
+                    retVal = pfStack.popUntil(lastIndexOfJpfClass);
+                    retVal.persistInSession();
+                    return retVal;
+                }
+            }
+        }
+        
+        //
+        // OK, if it's not an existing long lived page flow, and if this wasn't a nested page flow already on the
+        // stack, then create a new instance.
+        //
+        if (retVal == null) {
+            if (_log.isDebugEnabled()) {
+                _log.debug("Creating PageFlowController of type " + pageFlowClass.getName());
+            }
+
+            retVal = (PageFlowController) getFlowControllerInstance(pageFlowClass);
+            createdNew = true;
+        }
+        
+        //
+        // Store the previous PageFlowController on the nesting stack (if this one is nestable),
+        // or destroy the nesting stack.
+        //
+        if (isNestable) {
+            //
+            // Call create() on the newly-created page flow.
+            //
+            if (createdNew) retVal.create();
+            PageFlowController current = PageFlowUtils.getCurrentPageFlow();
+
+            if (current != null) {
+                if (_log.isDebugEnabled()) {
+                    _log.debug("Pushing PageFlowController " + current + " onto the nesting stack");
+                }
+
+                if (pfStack == null) pfStack = PageFlowStack.get(true);
+                pfStack.push(current);
+            }
+
+            retVal.reinitialize();
+            retVal.persistInSession();
+        } else {
+            //
+            // Going to a non-nested pageflow.  Blow away the pageflow stack.
+            //
+            if (pfStack != null) {
+                if (_log.isDebugEnabled()) {
+                    _log.debug("Destroying the PageFlowController stack.");
+                }
+                
+                //
+                // Start popping page flows until 1) there are none left on the stack, or 2) we find
+                // one of the type we're returning.  If (2), we'll use that one (this means that executing
+                // an action on a nesting page flow while in a nested one will not destroy the nesting
+                // page flow only to create a new instance of it).
+                //
+                PageFlowController onStackAlready = pfStack.popUntil(retVal.getClass());
+
+                if (onStackAlready != null) {
+                    if (_log.isDebugEnabled()) {
+                        _log.debug("Found a page flow of type " + retVal.getClass() + " in the stack; "
+                                + "using that instance and stopping destruction of the nesting stack.");
+                    }
+
+                    retVal = onStackAlready;
+                    retVal.persistInSession();
+                } else {
+                    //
+                    // We're actually using the newly-created page flow, so call create() on it.
+                    // Note that we make the call to persistInSession *before* create, so the previous flow's
+                    // onDestroy() gets called before the new one's onCreate().
+                    //
+                    retVal.reinitialize();
+                    retVal.persistInSession();
+                    retVal.create();
+                }
+            } else {
+                //
+                // We're actually using the newly-created page flow, so call create() on it (*after* persisting
+                // in the session so the previous page flow's onDestroy() gets called before the new one's
+                // onCreate()).
+                //
+                retVal.reinitialize();
+                retVal.persistInSession();
+                if (createdNew) retVal.create();
+            }
+        }
+
+        return retVal;
+    }
+
+    /**
+     * Create a {@link SharedFlowController} of the given type.
+     *
+     * @param sharedFlowClassName the type name of the desired SharedFlowController.
+     * @return the newly-created SharedFlowController, or <code>null</code> if none was found.
+     */
+    public SharedFlowController createSharedFlow(String sharedFlowClassName)
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+        Class sharedFlowClass = getFlowControllerClass(sharedFlowClassName);
+        return createSharedFlow(sharedFlowClass);
+    }
+
+    /**
+     * Create a {@link SharedFlowController} of the given type.
+     *
+     * @param sharedFlowClass the type of the desired SharedFlowController.
+     * @return the newly-created SharedFlowController, or <code>null</code> if none was found.
+     */
+    public SharedFlowController createSharedFlow(Class sharedFlowClass)
+            throws InstantiationException, IllegalAccessException {
+        assert SharedFlowController.class.isAssignableFrom(sharedFlowClass) : sharedFlowClass.getName();
+
+        if (_log.isDebugEnabled()) {
+            _log.debug("Creating SharedFlowController of type " + sharedFlowClass.getName());
+        }
+
+        SharedFlowController retVal = (SharedFlowController) getFlowControllerInstance(sharedFlowClass);
+        retVal.create();
+
+        if (_log.isDebugEnabled()) {
+            _log.debug("Storing " + retVal + " in the session...");
+        }
+
+        retVal.persistInSession();
+        return retVal;
+    }
+
+    /**
+     * Get the map of shared flows for the given request.  The map is derived from the shared flows
+     * that are declared (through the <code>sharedFlowRefs</code> attribute of
+     * {@link org.apache.ti.pageflow.annotations.ti.controller &#64;ti.controller}) in the page flow for the request.
+     *
+     * @return a Map of shared-flow-name (String) to {@link SharedFlowController}.
+     * @throws ClassNotFoundException if a declared shared flow class could not be found.
+     * @throws InstantiationException if a declared shared flow class could not be instantiated.
+     * @throws IllegalAccessException if a declared shared flow class was not accessible.
+     */
+    public Map/*< String, SharedFlowController >*/ getSharedFlowsForRequest()
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+        PageFlowActionContext actionContext = PageFlowActionContext.get();
+        String namespace = actionContext.getNamespace();
+        LinkedHashMap/*< String, SharedFlowController >*/ sharedFlows = getDefaultSharedFlows();
+        if (namespace == null) return null;
+        ModuleRegistrationHandler mrh = Handlers.get().getModuleRegistrationHandler();
+        ModuleConfig mc = mrh.getModuleConfig(namespace);
+
+        if (mc != null) {
+            Map/*< String, String >*/ sharedFlowTypes = mc.getSharedFlowTypes();
+
+            if (sharedFlowTypes != null && sharedFlowTypes.size() > 0) {
+                if (sharedFlows == null) sharedFlows = new LinkedHashMap/*< String, SharedFlowController >*/();
+
+                for (Iterator/*<Map.Entry>*/ i = sharedFlowTypes.entrySet().iterator(); i.hasNext();) {
+                    Map.Entry entry = (Map.Entry) i.next();
+                    String name = (String) entry.getKey();
+                    String type = (String) entry.getValue();
+                    addSharedFlow(name, type, sharedFlows);
+                }
+
+                return sharedFlows;
+            }
+        }
+
+        return sharedFlows;
+    }
+
+    LinkedHashMap/*< String, SharedFlowController >*/ getDefaultSharedFlows()
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+        DefaultSharedFlowRefs defaultRefs = ConfigUtil.getConfig().getDefaultSharedFlowRefs();
+
+        if (defaultRefs != null) {
+            SharedFlowRef[] refs = defaultRefs.getSharedFlowRefArray();
+
+            if (refs.length > 0) {
+                LinkedHashMap/*< String, SharedFlowController >*/ sharedFlows = new LinkedHashMap();
+
+                for (int i = 0; i < refs.length; i++) {
+                    SharedFlowRef ref = refs[i];
+                    if (_log.isInfoEnabled()) {
+                        _log.info("Shared flow of type " + ref.getType() + " is a default shared flow reference "
+                                + "with name " + ref.getName());
+                    }
+                    addSharedFlow(ref.getName(), ref.getType(), sharedFlows);
+                }
+
+                return sharedFlows;
+            }
+        }
+
+        return null;
+    }
+
+    private void addSharedFlow(String name, String type, LinkedHashMap sharedFlows)
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+        SharedFlowController sf = PageFlowUtils.getSharedFlow(type);
+        
+        //
+        // Reinitialize transient data that may have been lost on session failover.
+        //
+        if (sf != null) {
+            sf.reinitialize();
+        } else {
+            sf = createSharedFlow(type);
+        }
+
+        sharedFlows.put(name, sf);
+    }
+
+    /**
+     * Get a FlowController class.  By default, this loads the class using the thread context class loader.
+     *
+     * @param className the name of the {@link FlowController} class to load.
+     * @return the loaded {@link FlowController} class.
+     * @throws ClassNotFoundException if the requested class could not be found.
+     */
+    public Class getFlowControllerClass(String className)
+            throws ClassNotFoundException {
+        return Handlers.get().getReloadableClassHandler().loadClass(className);
+    }
+
+    /**
+     * Get a FlowController instance, given a FlowController class.
+     *
+     * @param flowControllerClass the Class, which must be assignable to {@link FlowController}.
+     * @return a new FlowController instance.
+     */
+    public FlowController getFlowControllerInstance(Class flowControllerClass)
+            throws InstantiationException, IllegalAccessException {
+        assert FlowController.class.isAssignableFrom(flowControllerClass)
+                : "Class " + flowControllerClass.getName() + " does not extend " + FlowController.class.getName();
+        return (FlowController) flowControllerClass.newInstance();
+    }
+}

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FormData.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FormData.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FormData.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/FormData.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * 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.
+ *
+ * $Header:$
+ */
+package org.apache.ti.pageflow;
+
+
+
+
+/**
+ * Base class for form beans associated with action methods in {@link PageFlowController}s.  Note that Page Flow actions
+ * may take form beans of any type.
+ */
+public class FormData {
+
+    /* TODO: re-add the functionality in this class
+    
+    private static final Logger _log = Logger.getInstance( FormData.class );
+    
+    //
+    // This is used to allow us to run against Validator 1.0 or 1.1.  The reflective Method is only used when running
+    // against Validator 1.0 (legacy).
+    //
+    private static Method _legacyInitValidatorMethod = null;
+    
+    static
+    {
+        try
+        {
+            _legacyInitValidatorMethod = 
+                    Resources.class.getMethod( "initValidator",
+                                               new Class[]{
+                                                   String.class,
+                                                   Object.class,
+                                                   ServletContext.class,
+                                                   HttpServletRequest.class,
+                                                   ActionErrors.class,
+                                                   int.class
+                                               } );
+        }
+        catch ( NoSuchMethodException e )
+        {
+            // ignore -- we're in Validator 1.1 or later.
+        }
+    }
+    
+    public FormData()
+    {
+        super();
+    }
+
+    public ActionErrors validate( ActionMapping mapping, HttpServletRequest request )
+    {
+        return validateBean( this, mapping.getAttribute(), mapping, request );
+    }
+
+    //MessageResources that conglomerates a primary and backup MessageResources.
+    private static class MergedMessageResources
+            extends MessageResources
+    {
+        private MessageResources _primary;
+        private MessageResources _backup;
+
+        public MergedMessageResources( MessageResources primary, MessageResources backup )
+        {
+            super( primary.getFactory(), primary.getConfig(), primary.getReturnNull() );
+            _primary = primary;
+            _backup = backup;
+        }
+
+        public String getMessage( Locale locale, String key )
+        {
+            String message = _primary.getMessage( locale, key );
+            if ( message == null ) message = _backup.getMessage( locale, key );
+            return message;
+        }
+    }
+    
+    /**
+     * Run all validation (declarative validation from annotations and the result of {@link Validatable#validate}) on
+     * a given bean.
+     * 
+     * @param bean the bean to validate.
+     * @param beanName the name of the bean, to be passed to Validator to look up declarative validation rules.
+     * @param mapping the current ActionMapping.
+     * @param request the current HttpServletRequest.
+     * @return an ActionErrors object containing errors that occurred during bean validation.
+     * 
+    protected ActionErrors validateBean( Object bean, String beanName, ActionMapping mapping, HttpServletRequest request )
+    {
+        MessageResources messageResources = ( MessageResources ) request.getAttribute( Globals.MESSAGES_KEY );
+        ExpressionAwareMessageResources.update( messageResources, bean );
+        
+        //
+        // See if this action uses a form that defines its own message resources.  If so, use those, or combine them
+        // with the message resources from the current module.
+        //
+        if ( mapping instanceof PageFlowAction )
+        {
+            PageFlowAction pfam = ( PageFlowAction ) mapping;
+            String bundle = pfam.getFormBeanMessageResourcesKey();
+            
+            if ( bundle != null )
+            {
+                MessageResources formBeanResources = ( MessageResources ) request.getAttribute( bundle );
+                ExpressionAwareMessageResources.update( formBeanResources, bean );
+                
+                if ( formBeanResources != null )
+                {
+                    if ( messageResources != null )
+                    {
+                        formBeanResources = new MergedMessageResources( messageResources, formBeanResources );
+                    }
+                    
+                    request.setAttribute( Globals.MESSAGES_KEY, formBeanResources );
+                    messageResources = formBeanResources;
+                }
+            }
+        }
+        
+        //
+        // If there's no MessageResources for this request, create one that can evaluate expressions.
+        //
+        if ( messageResources == null )
+        {
+            messageResources = new ExpressionAwareMessageResources( bean, request, getServlet().getServletContext() );
+            request.setAttribute( Globals.MESSAGES_KEY, messageResources );
+        }
+        
+        
+        ServletContext servletContext = getServlet().getServletContext();
+        ActionErrors errors = new ActionErrors();
+
+        //
+        // If the ValidatorPlugIn was initialized for this module, run it.
+        //
+        if ( Resources.getValidatorResources( servletContext, request ) != null )
+        {
+            try
+            {
+                //
+                // Run validations associated with the bean.
+                //
+                Validator beanV = initValidator( beanName, bean, servletContext, request, errors, page );
+                validatorResults = beanV.validate();
+                
+                //
+                // Run validations associated with the action.
+                //
+                Validator actionV = initValidator( mapping.getPath(), bean, servletContext, request, errors, page );
+                validatorResults.merge( actionV.validate() );
+            }
+            catch ( ValidatorException e )
+            {
+                _log.error( e.getMessage(), e );
+            }
+        }
+
+        //
+        // If this bean implements our Validatable interface, run its validate method.
+        //
+        if ( bean instanceof Validatable )
+        {
+            ( ( Validatable ) bean ).validate( mapping, request, errors );
+        }
+        
+        return errors;
+    }
+    
+    private static Validator initValidator( String beanName, Object bean, ServletContext context, 
+                                            HttpServletRequest request, ActionErrors errors, int page )
+    {
+        if ( _legacyInitValidatorMethod != null )
+        {
+            try
+            {
+                Object[] args = new Object[]{ beanName, bean, context, request, errors, new Integer( page ) };
+                Validator validator = ( Validator ) _legacyInitValidatorMethod.invoke( Resources.class, args );
+                
+                //
+                // The NetUI validator rules work on both 1.1 and 1.2.  They take ActionMessages instead of ActionErrors.
+                //
+                validator.addResource( "org.apache.struts.action.ActionMessages", errors );
+                return validator;
+            }
+            catch ( IllegalAccessException e )
+            {
+                assert false : e.getMessage();
+                throw new RuntimeException( e );
+            }
+            catch ( InvocationTargetException e )
+            {
+                assert false : e.getMessage();
+                throw new RuntimeException( e );
+            }
+        }
+        else
+        {
+            return Resources.initValidator( beanName, bean, context, request, errors, page );
+        }
+    }
+    */
+}

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/Forward.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/Forward.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/Forward.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/Forward.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * 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.
+ *
+ * $Header:$
+ */
+package org.apache.ti.pageflow;
+
+import org.apache.ti.pageflow.handler.Handlers;
+import org.apache.ti.pageflow.handler.ReloadableClassHandler;
+import org.apache.ti.pageflow.xwork.PageFlowActionContext;
+import org.apache.ti.util.internal.InternalStringBuilder;
+import org.apache.ti.util.logging.Logger;
+
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * An object of this type is returned from an action methods in a {@link PageFlowController} to
+ * determine the next URI to be displayed.  It is constructed on the name of a forward defined
+ * by the @{@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward} annotation, and resolves to the URI
+ * specified in that forward.
+ */
+public class Forward implements Serializable {
+
+    public static final Forward SUCCESS = new Forward("success");
+
+    private static final long serialVersionUID = 1;
+    private static final Object[] EMPTY_ACTION_FORM_ARRAY = new Object[0];
+
+    private static final Logger _log = Logger.getInstance(Forward.class);
+
+
+    private String _name;
+    private String _path;
+
+    private List _outputFormBeans = null;
+    private boolean _init = false;
+    private InternalStringBuilder _queryString = null;
+    private boolean _explicitPath = false;
+    private String _outputFormBeanType = null;
+    private Map _actionOutputs = null;
+
+    /**
+     * Construct based on an initializer forward.  This is a framework-invoked constructor that should not normally
+     * be called directly.
+     */
+    protected Forward(Forward init) {
+        _outputFormBeans = init._outputFormBeans;
+        _init = init._init;
+        _queryString = init._queryString;
+        _explicitPath = init._explicitPath;
+        _outputFormBeanType = init._outputFormBeanType;
+        _actionOutputs = init._actionOutputs;
+    }
+
+    /**
+     * Construct based on a given request.  This is a framework-invoked constructor that should not normally
+     * be called directly.
+     */
+    protected Forward() {
+        setPath(PageFlowActionContext.get().getRequestPath());
+        _explicitPath = true;
+    }
+
+    /**
+     * Constructor which accepts the name of a forward defined by the
+     * {@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}
+     * annotation.
+     *
+     * @param forwardName the name of the forward
+     *                    ({@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}) to resolve.
+     */
+    public Forward(String forwardName) {
+        setName(forwardName);
+    }
+
+    /**
+     * Constructor which accepts the name of a forward defined by the
+     * {@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}
+     * annotation.  The value returned from {@link #getPath} is resolved from this forward.
+     * Also accepts a form bean to make available in the request (or user session, as appropriate).
+     *
+     * @param forwardName    the name of the forward
+     *                       ({@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}) to resolve.
+     * @param outputFormBean a form bean instance to make available in the request (or user session, as appropriate).
+     *                       See {@link #addOutputForm} for details about how this manifests itself.
+     */
+    public Forward(String forwardName, Object outputFormBean) {
+        this(forwardName);
+
+        if (outputFormBean != null) {
+            addOutputForm(outputFormBean);
+        }
+    }
+
+    /**
+     * Constructor which accepts the name of a forward defined by the
+     * {@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}
+     * annotation.  The value returned from {@link #getPath}
+     * is resolved from this forward.  Also accepts a named action output
+     * to make available in the request, through {@link PageFlowUtils#getActionOutput}..
+     *
+     * @param forwardName       the name of the forward
+     *                          ({@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}) to resolve.
+     * @param actionOutputName  the name of a action output to make available in the request.
+     * @param actionOutputValue the action output object to make available in the request.
+     */
+    public Forward(String forwardName, String actionOutputName, Object actionOutputValue) {
+        this(forwardName);
+        addActionOutput(actionOutputName, actionOutputValue);
+    }
+
+    /**
+     * Constructs a forward that returns the given URI for {@link #getPath}.
+     *
+     * @param uri the URI to return for {@link #getPath}.
+     */
+    public Forward(String forwardName, URI uri) {
+        this(forwardName);
+        setPath(uri.toString());
+        _explicitPath = true;
+    }
+
+    /**
+     * Constructs a forward that returns the given URL for {@link #getPath}.  Because the URL path
+     * is absolute by nature, this forward will cause a browser redirect.
+     *
+     * @param url the URL to return for {@link #getPath}.
+     */
+    public Forward(String forwardName, URL url) {
+        this(forwardName);
+        setPath(url.toString());
+        _explicitPath = true;
+    }
+
+    public String getName() {
+        return _name;
+    }
+
+    public void setName(String name) {
+        _name = name;
+    }
+
+    /**
+     * Add a form bean that will be made available in the request (or user session, as
+     * appropriate) if this forward is returned by an action method in a {@link PageFlowController}.
+     * Specifically, each form bean is stored as a request attribute with a name determined by
+     * {@link PageFlowUtils#getFormBeanName}.
+     *
+     * @param formBean the form bean instance to add.
+     */
+    public final void addOutputForm(Object formBean) {
+        if (formBean == null) throw new IllegalArgumentException("The output form bean may not be null.");
+        if (_outputFormBeans == null) _outputFormBeans = new ArrayList();
+        _outputFormBeans.add(formBean);
+    }
+
+    /**
+     * Get all form beans attached to this forward through {@link #addOutputForm} or {@link #Forward(String, Object)}.
+     *
+     * @return an array of form bean instances that are attached to this forward.
+     */
+    public final Object[] getOutputForms() {
+        if (_outputFormBeans == null) return EMPTY_ACTION_FORM_ARRAY;
+        return _outputFormBeans.toArray(EMPTY_ACTION_FORM_ARRAY);
+    }
+
+    /**
+     * Get all form beans attached to this forward through {@link #addOutputForm} or {@link #Forward(String, Object)}.
+     *
+     * @return an List of form bean instances that are attached to this forward.  <strong>May be <code>null</code></strong>.
+     */
+    public final List getOutputFormBeans() {
+        return _outputFormBeans;
+    }
+
+    /**
+     * Get the first output form bean that was added to this forward.
+     */
+    public Object getFirstOutputForm() {
+        if (_outputFormBeans == null || _outputFormBeans.size() == 0) {
+            if (_outputFormBeanType != null) {
+                try {
+                    if (_log.isDebugEnabled()) {
+                        _log.debug("Creating form bean of type " + _outputFormBeanType);
+                    }
+
+                    ReloadableClassHandler rch = Handlers.get().getReloadableClassHandler();
+                    Object formBean = rch.newInstance(_outputFormBeanType);
+                    addOutputForm(formBean);
+                    return formBean;
+                } catch (Exception e) {
+                    _log.error("Could not create form bean instance of " + _outputFormBeanType, e);
+                }
+            }
+
+            return null;
+        } else {
+            return _outputFormBeans.get(0);
+        }
+    }
+
+    /**
+     * Set the path to be returned by {@link #getPath}.  This overrides any path or forward name
+     * set in a constructor.
+     *
+     * @param contextRelativePath the path to be returned by {@link #getPath}.
+     */
+    public void setPath(String contextRelativePath) {
+        _path = contextRelativePath;
+    }
+
+    public boolean isExplicitPath() {
+        return _explicitPath;
+    }
+
+    /**
+     * Get the path associated with this object.  Resolve it from the name of a forward
+     * ({@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward}) if necessary.
+     *
+     * @return a String that is the URI path.
+     * @see #Forward(String)
+     * @see #Forward(String, Object)
+     * @see #Forward(String, URI)
+     * @see #Forward(String, URL)
+     * @see #setPath
+     */
+    public String getPath() {
+        return _path;
+    }
+
+    /**
+     * Set the query string that will be appended to the URI returned by {@link #getPath}.
+     *
+     * @param queryString the query string that will be appended to the URI.  If this string does not
+     *                    start with <code>'?'</code>, then this character will be prepended; if the string is
+     *                    <code>null</code>, the query string will be removed.
+     */
+    public void setQueryString(String queryString) {
+        if (queryString == null || queryString.length() == 0) {
+            _queryString = null;
+        } else if (queryString.charAt(0) == '?') {
+            _queryString = new InternalStringBuilder(queryString);
+        } else {
+            _queryString = new InternalStringBuilder("?").append(queryString);
+        }
+    }
+
+    /**
+     * Get the query string that will be appended to the URI returned by {@link #getPath}.
+     *
+     * @return the query string that will be appended to the URI, or <code>null</code> if there
+     *         is no query string.
+     */
+    public String getQueryString() {
+        return _queryString != null ? _queryString.toString() : null;
+    }
+
+    /**
+     * Add a query parameter to the URI returned by {@link #getPath}.
+     *
+     * @param paramName the name of the query parameter.
+     * @param value     the value of the query parameter, or <code>null</code> if there is no value.
+     */
+    public void addQueryParam(String paramName, String value) {
+        if (_queryString == null) {
+            _queryString = new InternalStringBuilder("?");
+        } else {
+            _queryString.append('&');
+        }
+
+        _queryString.append(paramName);
+
+        if (value != null) {
+            _queryString.append('=').append(value);
+        }
+    }
+
+    /**
+     * Add a query parameter with no value to the URI returned by {@link #getPath}.
+     *
+     * @param paramName the name of the query parameter.
+     */
+    public final void addQueryParam(String paramName) {
+        addQueryParam(paramName, null);
+    }
+
+    /**
+     * Adds an action output that will be made available in the request, through {@link PageFlowUtils#getActionOutput}.
+     *
+     * @param paramName the name of the action output.
+     * @param value     the action output value.
+     */
+    public void addActionOutput(String paramName, Object value) {
+        if (paramName == null || paramName.length() == 0) {
+            throw new IllegalArgumentException("An action output name may not be null or empty.");
+        }
+
+        if (_actionOutputs == null) {
+            _actionOutputs = new HashMap();
+        }
+
+        _actionOutputs.put(paramName, value);
+    }
+
+    /**
+     * Get all action outputs that have been set on this forward.
+     *
+     * @return a Map of name/value pairs representing all action outputs.
+     * @see #addActionOutput
+     */
+    public Map getActionOutputs() {
+        return _actionOutputs;
+    }
+}

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/IllegalActionOutputException.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/IllegalActionOutputException.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/IllegalActionOutputException.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/IllegalActionOutputException.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * 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.
+ *
+ * $Header:$
+ */
+package org.apache.ti.pageflow;
+
+
+/**
+ * Exception that occurs when an action output has been added to a {@link Forward} that resolves to a
+ * {@link org.apache.ti.pageflow.annotations.ti.forward &#64;ti.forward} annotation marked with
+ * <code>{@link org.apache.ti.pageflow.annotations.ti.forward#redirect redirect}=true</code>.
+ * action outputs may not be used on redirect forwards.
+ */
+public class IllegalActionOutputException extends FlowControllerException {
+
+    private String _forwardName;
+    private String _actionOutputName;
+
+
+    /**
+     * Constructor.
+     *
+     * @param forwardName      the name of the relevant {@link Forward}.
+     * @param flowController   the current {@link FlowController} instance.
+     * @param actionOutputName the name of the relevant action output.
+     */
+    public IllegalActionOutputException(String forwardName, FlowController flowController,
+                                        String actionOutputName) {
+        super(flowController);
+        _forwardName = forwardName;
+        _actionOutputName = actionOutputName;
+    }
+
+    /**
+     * Get the name of the relevant {@link Forward}.
+     *
+     * @return a String that is the name of the relevant {@link Forward}.
+     */
+    public String getForwardName() {
+        return _forwardName;
+    }
+
+    /**
+     * Set the name of the relevant {@link Forward}.
+     *
+     * @param forwardName a String that is the name of the relevant {@link Forward}.
+     */
+    public void setForwardName(String forwardName) {
+        _forwardName = forwardName;
+    }
+
+    /**
+     * Get the name of the relevant action output.
+     *
+     * @return a String that is the name of the relevant action output.
+     */
+    public String getActionOutputName() {
+        return _actionOutputName;
+    }
+
+    /**
+     * Set the name of the relevant action output.
+     *
+     * @param actionOutputName a String that is the name of the relevant action output.
+     */
+    public void setActionOutputName(String actionOutputName) {
+        _actionOutputName = actionOutputName;
+    }
+
+    protected Object[] getMessageArgs() {
+        return new Object[]{_forwardName, getActionName(), getFlowControllerURI(), _actionOutputName};
+    }
+
+    public String[] getMessageParts() {
+        return new String[]
+        {
+            "The forward \"", "\" on action ", " in page flow ", " has at least one action output (\"",
+            "\"), but is set to redirect=\"true\".  action outputs may not be used on redirect forwards."
+        };
+    }
+
+    /**
+     * Tell whether the root cause may be session expiration in cases where the requested session ID is different than
+     * the actual session ID.  In this case, the answer is <code>false</code>.
+     */
+    public boolean causeMayBeSessionExpiration() {
+        return false;
+    }
+}

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/IllegalOutputFormException.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/IllegalOutputFormException.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/IllegalOutputFormException.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/IllegalOutputFormException.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * 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.
+ *
+ * $Header:$
+ */
+package org.apache.ti.pageflow;
+
+
+/**
+ * Base type for errors related to output forms on Forwards.
+ *
+ * @see Forward#addOutputForm
+ */
+public abstract class IllegalOutputFormException extends FlowControllerException {
+
+    private String _forwardName;
+    private String _outputFormType;
+
+
+    /**
+     * @param forwardName    the name of the relevant {@link Forward}.
+     * @param flowController the current {@link FlowController} instance.
+     * @param outputFormType the type name of the relevant output form.
+     */
+    public IllegalOutputFormException(String forwardName, FlowController flowController,
+                                      String outputFormType) {
+        super(flowController);
+        _forwardName = forwardName;
+        _outputFormType = outputFormType;
+    }
+
+    /**
+     * Get the name of the relevant {@link Forward}.
+     *
+     * @return a String that is the name of the relevant {@link Forward}.
+     */
+    public String getForwardName() {
+        return _forwardName;
+    }
+
+    /**
+     * Set the name of the relevant {@link Forward}.
+     *
+     * @param forwardName a String that is the name of the relevant {@link Forward}.
+     */
+    public void setForwardName(String forwardName) {
+        _forwardName = forwardName;
+    }
+
+    /**
+     * Get the type name of the relevant output form.
+     *
+     * @return a String that is the type name of the relevant output form.
+     */
+    public String getOutputFormType() {
+        return _outputFormType;
+    }
+
+    /**
+     * Set the type name of the relevant output form.
+     *
+     * @param outputFormType a String that is the type name of the relevant output form.
+     */
+    public void setOutputFormType(String outputFormType) {
+        _outputFormType = outputFormType;
+    }
+
+    /**
+     * Tell whether the root cause may be session expiration in cases where the requested session ID is different than
+     * the actual session ID.  In this case, the answer is <code>false</code>.
+     */
+    public boolean causeMayBeSessionExpiration() {
+        return false;
+    }
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@struts.apache.org
For additional commands, e-mail: dev-help@struts.apache.org