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 [7/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/PageFlowRequestProcessor.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowRequestProcessor.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowRequestProcessor.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowRequestProcessor.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,898 @@
+/*
+ * 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.internal.InternalConstants;
+import org.apache.ti.util.logging.Logger;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * The Page Flow extension of the Struts RequestProcessor, which contains callbacks that are invoked
+ * during processing of a request to the Struts action servlet.  This class is registered as the
+ * <strong>controller</strong> for all Struts modules derived from page flows.
+ */
+public class PageFlowRequestProcessor
+        implements Serializable, InternalConstants, PageFlowConstants {
+
+    private static int requestNumber = 0;
+
+    private static final Logger _log = Logger.getInstance(PageFlowRequestProcessor.class);
+
+    private static final String ACTION_OVERRIDE_PARAM_PREFIX = "actionOverride:";
+    private static final int ACTION_OVERRIDE_PARAM_PREFIX_LEN = ACTION_OVERRIDE_PARAM_PREFIX.length();
+    private static final String SCHEME_UNSECURE = "http";
+    private static final String SCHEME_SECURE = "https";
+    private static final String REDIRECT_REQUEST_ATTRS_PREFIX = InternalConstants.ATTR_PREFIX + "requestAttrs:";
+    private static final String REDIRECT_REQUEST_ATTRS_PARAM = "forceRedirect";
+
+
+    private Map/*< String, Class >*/ _formBeanClasses = new HashMap/*< String, Class >*/();
+    // private Map/*< String, List< PageFlowAction > >*/ _overloadedActions = new HashMap/*< String, List< PageFlowAction > >*/();
+    private ContainerAdapter _containerAdapter;
+    private Handlers _handlers;
+    private FlowControllerFactory _flowControllerFactory;
+    //private InternalConcurrentHashMap/*< String, Class >*/ _pageServletClasses = new InternalConcurrentHashMap/*< String, Class >*/();
+    //private PageFlowPageFilter _pageServletFilter;
+    
+    /*
+    protected void processPopulate( HttpServletRequest request, HttpServletResponse response, Object form,
+                                    PageFlowAction mapping )
+        throws PageFlowException
+    {
+        //
+        // If a previous action forwarded us a form, use that -- don't populate it from request parameters.
+        //
+        Object previousForm = InternalUtils.getForwardedFormBean( request, true );
+
+        if ( previousForm != null )
+        {
+            return;
+        }
+
+        if ( _log.isDebugEnabled() )
+        {
+            _log.debug( "Populating bean properties from this request" );
+        }
+
+        // struts does this
+        if ( form != null )
+        {
+            form.setServlet( servlet );
+            form.reset( mapping, request );
+        }
+
+        if ( mapping.getMultipartClass() != null )
+        {
+            request.setAttribute( Globals.MULTIPART_KEY, mapping.getMultipartClass() );
+        }
+
+        PageFlowActionContext requestWrapper = getContext();
+        boolean alreadyCalledInRequest = requestWrapper.isProcessPopulateAlreadyCalled();
+        if ( ! alreadyCalledInRequest ) requestWrapper.setProcessPopulateAlreadyCalled( true );
+        
+        //
+        // If this is a forwarded request and the form-bean is null, don't call to ProcessPopulate.
+        // We don't want to expose errors due to parameters from the original request, which won't
+        // apply to a forwarded action that doesn't take a form.
+        //
+        if ( !alreadyCalledInRequest || form != null )
+        {
+            //
+            // If this request was forwarded by a button-override of the main form action, then ensure that there are
+            // no databinding errors when the override action does not use a form bean.
+            //
+            if ( form == null && requestWrapper.isForwardedByButton() ) form = NULL_ACTION_FORM;
+            
+            ProcessPopulate.populate( request, response, form, alreadyCalledInRequest );
+        }
+    }
+    */
+
+    /**
+     * The requested action can be overridden by a request parameter.  In this case, we parse the action from
+     * the request parameter and forward to a URI constructed from it.
+     * 
+     * @return <code>true</code> if the action was overridden by a request parameter, in which case the request
+     *         was forwarded.
+     * @throws IOException
+     * @throws PageFlowException    
+     */ 
+    /* TODO: re-add this -- it should be in Chain
+    protected boolean processActionOverride()
+        throws IOException, PageFlowException
+    {
+        // Only make this check if this is an initial (non-forwarded) request.
+        //
+        // TODO: performance?
+        //
+        PageFlowActionContext wrapper = getContext();
+        if ( ! wrapper.isForwardedByButton() && ! wrapper.isForwardedRequest() )
+        {
+            //
+            // First, since we need access to request parameters here, process a multipart request
+            // if that's what we have.  This puts the parameters (each in a MIME part) behind an
+            // interface that makes them look like normal request parameters.
+            //
+            // TODO: re-add multipart support
+            // HttpServletRequest multipartAwareRequest = processMultipart( request );
+            
+            
+            for ( Iterator i = getContext().getWebContext().getRequestScope().keySet().iterator(); i.hasNext(); )
+            {
+                String paramName = ( String ) i.next();
+
+                if ( paramName.startsWith( ACTION_OVERRIDE_PARAM_PREFIX ) )
+                {
+                    String actionPath = paramName.substring( ACTION_OVERRIDE_PARAM_PREFIX_LEN );
+
+                    String qualifiedAction = InternalUtils.qualifyAction( actionPath );
+                    actionPath = InternalUtils.createActionPath(qualifiedAction );
+
+                    if ( _log.isDebugEnabled() )
+                    {
+                        _log.debug( "A request parameter overrode the action.  Forwarding to: " + actionPath );
+                    }
+
+                    wrapper.setForwardedByButton( true );
+                    doForward( actionPath );
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
+    
+    private void processSecurity()
+    {
+        PageFlowActionContext context = PageFlowActionContext.getContext();        
+        Map requestScope = context.getWebContext().getRequestScope();
+        String uri = context.getRequestPath();
+        
+        //
+        // Allow the container to do a security check on forwarded requests, if that feature is enabled.
+        //
+        PageflowConfig pageflowConfig = ConfigUtil.getConfig().getPageflowConfig();
+        if ( pageflowConfig != null && pageflowConfig.getEnsureSecureForwards() && getContext().isForwardedRequest() )
+        {
+            //
+            // In some situations (namely, in scoped requests under portal), the initial
+            // security check may not have been done for the request URI.  In this case, a redirect
+            // to https may happen during checkSecurity().
+            //
+            if ( _containerAdapter.doSecurityRedirect( uri) )
+            {
+                if ( _log.isDebugEnabled() )
+                {
+                    _log.debug( "checkSecurity() caused a redirect.  Ending processing for this request "
+                            + '(' + uri + ')' );
+                }
+                
+                return;
+            }
+        }
+        
+        //
+        // If we've come in on a forced redirect due to security constraints, look for request attrs
+        // that we put into the session.
+        //
+        String hash = ( String ) requestScope.get( REDIRECT_REQUEST_ATTRS_PARAM );
+        if ( hash != null )
+        {
+            Map sessionScope = getContext().getSession();
+            
+            if ( sessionScope != null )
+            {
+                String carryoverAttrName = makeRedirectedRequestAttrsKey( uri, hash );
+                Map attrs = ( Map ) sessionScope.get( carryoverAttrName );
+                sessionScope.remove( carryoverAttrName );
+                
+                if ( attrs != null )
+                {
+                    for ( Iterator i = attrs.entrySet().iterator(); i.hasNext(); )
+                    {
+                        Map.Entry entry = ( Map.Entry ) i.next();
+                        
+                        String attrName = ( String ) entry.getKey();
+                        if ( requestScope.get( attrName ) == null )
+                        {
+                            requestScope.put( attrName, entry.getValue() );
+                        }
+                    }
+                }
+            }
+        }
+    }
+    */
+    
+    /**
+     * Process any direct request for a page flow by forwarding to its "begin" action.
+     * 
+     * @param request the current HttpServletRequest
+     * @param response the current HttpServletResponse
+     * @param uri the decoded request URI
+     * @return <code>true</code> if the request was for a page flow, in which case it was forwarded.
+     * @throws IOException
+     * @throws PageFlowException
+     */ 
+    /* TODO: re-add
+    protected boolean processPageFlowRequest( HttpServletRequest request, HttpServletResponse response, String uri )
+        throws IOException, PageFlowException
+    {
+        //
+        // forward requests for *.jpf to the "begin" action within the appropriate Struts module.
+        //
+        if ( FileUtils.osSensitiveEndsWith( uri, PageFlowConstants.PAGEFLOW_EXTENSION ) )
+        {
+            //
+            // Make sure the current module config matches the request URI.  If not, this could be an
+            // EAR where the jpf-struts-config.xml wasn't included because of a compilation error.
+            //
+            String modulePath = PageFlowUtils.getModulePath( request );
+            if ( ! moduleConfig.getPrefix().equals( modulePath ) )
+            {
+                if ( _log.isErrorEnabled() )
+                {
+                    InternalStringBuilder msg = new InternalStringBuilder( "No module configuration registered for " );
+                    msg.append( uri ).append( " (namespace " ).append( namespace ).append( ")." );
+                    _log.error( msg.toString() );
+                }
+
+                if ( modulePath.length() == 0 ) modulePath = "/";
+                InternalUtils.sendDevTimeError( "PageFlow_NoModuleConf", null,
+                                                HttpServletResponse.SC_INTERNAL_SERVER_ERROR, request, response,
+                                                getServletContext(), new Object[]{ uri, modulePath } );
+                return true;
+            }
+
+            //
+            // Make sure that the requested pageflow matches the pageflow for the directory.
+            //
+            PageFlowAction beginMapping = getBeginMapping();
+            if ( beginMapping != null )
+            {
+                String desiredType = beginMapping.getParameter();
+                desiredType = desiredType.substring( desiredType.lastIndexOf( '.' ) + 1 ) + JPF_EXTENSION;
+                String requestedType = InternalUtils.getRequestPath( request );
+                requestedType = requestedType.substring( requestedType.lastIndexOf( '/' ) + 1 );
+
+                if ( ! requestedType.equals( desiredType ) )
+                {
+                    if ( _log.isDebugEnabled() )
+                    {
+                        _log.debug( "Wrong .jpf requested for this directory: got " + requestedType
+                                   + ", expected " + desiredType );
+                    }
+
+                    if ( _log.isErrorEnabled() )
+                    {
+                        InternalStringBuilder msg = new InternalStringBuilder( "Wrong .jpf requested for this directory: got " );
+                        msg.append( requestedType ).append( ", expected " ).append( desiredType ).append( '.' );
+                        _log.error( msg.toString() );
+                    }
+
+                    InternalUtils.sendDevTimeError( "PageFlow_WrongPath", null,
+                                                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR, request, response,
+                                                    getServletContext(), new Object[]{ requestedType, desiredType } );
+
+                    return true;
+                }
+            }
+
+            uri = PageFlowUtils.getBeginActionURI( uri );
+
+            if ( _log.isDebugEnabled() )
+            {
+                _log.debug( "Got request for " + request.getRequestURI() + ", forwarding to " + uri );
+            }
+
+            doForward( uri, request, response );
+            return true;
+        } 
+        
+        return false;
+    }
+    
+    protected PageFlowAction getBeginMapping()
+    {
+    return ( PageFlowAction ) moduleConfig.findActionConfig( BEGIN_ACTION_PATH );
+    }
+    
+    */
+    
+    /**
+     * A MultipartRequestWrapper that we cache in the outer request once we've handled the multipart request once.
+     * It extends the base Struts MultipartRequestWrapper by being aware of ScopedRequests; for ScopedRequests, it
+     * filters the parameter names accordingly.
+     */ 
+    /* TODO: re-add
+    private static class RehydratedMultipartRequestWrapper extends MultipartRequestWrapper
+    {
+        public RehydratedMultipartRequestWrapper( HttpServletRequest req )
+        {
+            super( req );
+             
+            MultipartRequestHandler handler = MultipartRequestUtils.getCachedMultipartHandler( req );
+             
+            if ( handler != null )
+            {
+                ScopedRequest scopedRequest = ScopedUtils.unwrapRequest( req );
+                Map textElements = handler.getTextElements();
+                parameters = scopedRequest != null ? scopedRequest.filterParameterMap( textElements ) : textElements;
+            }
+        }
+    }
+    */
+    
+    /**
+     * If this is a multipart request, wrap it with a special wrapper.  Otherwise, return the request unchanged.
+     */
+    /* TODO: re-add multipart support
+    protected HttpServletRequest processMultipart( HttpServletRequest request )
+    {
+        if ( ! "POST".equalsIgnoreCase( request.getMethod() ) ) return request;
+
+        String contentType = request.getContentType();
+        if ( contentType != null && contentType.startsWith( "multipart/form-data" ) )
+        {
+            PageFlowActionContext pageFlowRequestWrapper = getContext();
+            
+            //
+            // We may have already gotten a multipart wrapper during process().  If so, use that.
+            //
+            MultipartRequestWrapper cachedWrapper = pageFlowRequestWrapper.getMultipartRequestWrapper();
+            
+            if ( cachedWrapper != null && cachedWrapper.getRequest() == request ) return cachedWrapper;
+            
+            try
+            {
+                //
+                // First, pre-handle the multipart request.  This parses the stream and caches a single
+                // MultipartRequestHandler in the outer request, so we can create new wrappers around it at will.
+                //
+                MultipartRequestUtils.preHandleMultipartRequest( request );
+            }
+            catch ( PageFlowException e )
+            {
+                _log.error( "Could not parse multipart request.", e.getRootCause() );
+                return request;
+            }
+            
+            MultipartRequestWrapper ret = new RehydratedMultipartRequestWrapper( request );
+            pageFlowRequestWrapper.setMultipartRequestWrapper( ret );
+            return ret;
+        }
+        else
+        {
+            return request;
+        }
+
+    }
+    */
+
+    /*
+    private boolean isCorrectFormType( Class formBeanClass, PageFlowAction mapping )
+    {
+        assert mapping.getName() != null : "cannot pass an PageFlowAction that has no form bean";
+        Class cachedFormBeanClass = ( Class ) _formBeanClasses.get( mapping.getName() );
+        return isCorrectFormType( formBeanClass, cachedFormBeanClass, mapping );
+    }
+    
+    private boolean isCorrectFormType( Class formBeanClass, Class actionMappingFormBeanClass, PageFlowAction mapping )
+    {
+        if ( actionMappingFormBeanClass != null )
+        {
+            return actionMappingFormBeanClass .isAssignableFrom( formBeanClass );
+        }
+        else
+        {
+            //
+            // The form bean class couldn't be loaded at init time -- just check against the class name.
+            //
+            FormBeanConfig mappingFormBean = moduleConfig.findFormBeanConfig( mapping.getName() );
+            String formClassName = formBeanClass.getName();
+            
+            if ( mappingFormBean != null && mappingFormBean.getType().equals( formClassName ) ) return true;
+            
+            if ( mapping instanceof PageFlowAction )
+            {
+                String desiredType = ( ( PageFlowAction ) mapping ).getFormBeanClass();
+                if ( formClassName.equals( desiredType ) ) return true;
+            }
+        }
+        
+        return false;
+    }
+    */
+
+    /* TODO: re-add tokens, or something like it.
+    private PageFlowAction checkTransaction( HttpServletRequest request, HttpServletResponse response,
+                                            PageFlowAction mapping, String actionPath )
+        throws IOException
+    {
+        if ( mapping instanceof PageFlowAction && ( ( PageFlowAction ) mapping ).isPreventDoubleSubmit() )
+        {
+            if ( ! TokenProcessor.getInstance().isTokenValid( request, true ) )
+            {
+                FlowController currentFC = getContext().getCurrentFlowController();
+                String actionName = InternalUtils.getActionName( mapping );
+                DoubleSubmitException ex = new DoubleSubmitException( actionName, currentFC );
+                
+                if ( currentFC != null )
+                {
+                    try
+                    {
+                        forward fwd = currentFC.handleException( ex, mapping, null, request, response );
+                        return new ExceptionHandledPageFlowAction( actionPath, fwd );
+                    }
+                    catch ( PageFlowException servletException)
+                    {
+                        _log.error( "Exception occurred while handling " + ex.getClass().getName(), servletException );
+                    }
+                }
+                
+                ex.sendResponseErrorCode( response );
+                return null;
+            }
+        }
+        
+        return mapping;
+    }
+    */
+    public void init()
+            throws PageFlowException {
+        //
+        // Cache a list of overloaded actions for each overloaded action path (actions are overloaded by form bean type).
+        //
+        // TODO: re-add overloaded action support
+//        cacheOverloadedPageFlowActions();
+        
+        //
+        // Cache the form bean Classes by form bean name.
+        //
+        // TODO: re-add class caching?
+//        cacheFormClasses();
+    }
+    
+    /* TODO: re-add overloaded action support
+    private void cacheOverloadedPageFlowActions()
+    {
+        ActionConfig[] actionConfigs = moduleConfig.findActionConfigs();
+        
+        for ( int i = 0; i < actionConfigs.length; i++ )
+        {
+            ActionConfig actionConfig = actionConfigs[i];
+            
+            if ( actionConfig instanceof PageFlowAction )
+            {
+                PageFlowAction mapping = ( PageFlowAction ) actionConfig;
+                String unqualifiedActionPath = ( ( PageFlowAction ) actionConfig ).getUnqualifiedActionPath();
+                
+                if ( unqualifiedActionPath != null )
+                {
+                    List overloaded = ( List ) _overloadedActions.get( unqualifiedActionPath );
+                    
+                    if ( overloaded == null )
+                    {
+                        overloaded = new ArrayList();
+                        _overloadedActions.put( unqualifiedActionPath, overloaded );
+                    }
+                    
+                    overloaded.add( mapping );
+                }
+            }
+        }
+    }
+    */
+    
+    /*
+    private void cacheFormClasses()
+    {
+        FormBeanConfig[] formBeans = moduleConfig.findFormBeanConfigs();
+        ReloadableClassHandler rch = _handlers.getReloadableClassHandler();
+        
+        for ( int i = 0; i < formBeans.length; i++ )
+        {
+            FormBeanConfig formBeanConfig = formBeans[i];
+            String formType = InternalUtils.getFormBeanType( formBeanConfig );
+            
+            try
+            {
+                Class formBeanClass = rch.loadClass( formType );
+                _formBeanClasses.put( formBeanConfig.getName(), formBeanClass );
+            }
+            catch ( ClassNotFoundException e )
+            {
+                _log.error( "Could not load class " + formType + " referenced from form bean config "
+                            + formBeanConfig.getName() + " in Struts module " + moduleConfig );
+            }
+        }
+    }
+    */
+
+    /**
+     * Read component instance mapping configuration file.
+     * This is where we read files properties.
+     */
+    
+    /* TODO: re-add Tiles support
+    protected void initDefinitionsMapping() throws PageFlowException
+    {
+        definitionsFactory = null;
+        TilesUtilImpl tilesUtil = TilesUtil.getTilesUtil();
+
+        if ( tilesUtil instanceof TilesUtilStrutsImpl )
+        {
+            // Retrieve and set factory for this modules
+            definitionsFactory =
+                    ( ( TilesUtilStrutsImpl ) tilesUtil ).getDefinitionsFactory( getServletContext(), moduleConfig );
+
+            if ( definitionsFactory == null && log.isDebugEnabled() )
+            {
+                log.debug( "Definition Factory not found for module: '"
+                           + moduleConfig.getPrefix() );
+            }
+        }
+    }
+    */
+
+    /* TODO: re-add this.  It's code to customize XWork -- to handle shared flow and form-bean-specific actions.
+    public PageFlowAction processMapping( HttpServletRequest request, HttpServletResponse response, String path )
+        throws IOException
+    {
+        FlowController fc = getContext().getFlowController();
+        Object forwardedForm = InternalUtils.getForwardedFormBean( false );
+        
+        //
+        // First, see if this is a request for a shared flow action.  The shared flow's name (as declared by the
+        // current page flow) will precede the dot.
+        //
+        if ( fc != null && ! processSharedFlowMapping( request, response, path, fc ) ) return null;
+        
+        //
+        // Look for a form-specific action path.  This is used when there are two actions with the same
+        // name, but different forms (in nesting).
+        //
+        Class forwardedFormClass = null;
+        
+        if ( forwardedForm != null )
+        {
+            forwardedFormClass = forwardedForm.getClass();
+            List possibleMatches = ( List ) _overloadedActions.get( path );
+            PageFlowAction bestMatch = null;
+            
+            //
+            // Troll through the overloaded actions for the given path.  Look for the one whose form bean class is
+            // exactly the class of the forwarded form; failing that, look for one that's assignable from the class
+            // of the forwarded form.
+            //
+            for ( int i = 0; possibleMatches != null && i < possibleMatches.size(); ++i )
+            {
+                PageFlowAction possibleMatch = ( PageFlowAction ) possibleMatches.get( i );
+                Class cachedFormBeanClass = ( Class ) _formBeanClasses.get( possibleMatch.getName() );
+                
+                if ( forwardedFormClass.equals( cachedFormBeanClass ) )
+                {
+                    bestMatch = possibleMatch;
+                    break;
+                }
+                if ( bestMatch == null && isCorrectFormType( forwardedFormClass, possibleMatch ) )
+                {
+                    bestMatch = possibleMatch;
+                }
+            }
+            
+            if ( bestMatch != null )
+            {
+                request.setAttribute( Globals.MAPPING_KEY, bestMatch );
+                
+                if ( _log.isDebugEnabled() )
+                {
+                    _log.debug( "Found form-specific action mapping " + bestMatch.getPath() + " for " + path
+                                + ", form " + forwardedFormClass.getName() );
+                }
+                
+                return checkTransaction(bestMatch);
+            }
+        }
+        
+        //
+        // Look for a directly-defined mapping for this path.
+        //
+        PageFlowAction mapping = ( PageFlowAction ) moduleConfig.findActionConfig( path );
+        
+        if ( mapping != null )
+        {
+            boolean wrongForm = false;
+            
+            //
+            // We're going to bail out if there is a forwarded form and this mapping requires a different form type.
+            //
+            if ( forwardedForm != null )
+            {
+                boolean mappingHasNoFormBean = mapping.getName() == null;
+                wrongForm = mappingHasNoFormBean || ! isCorrectFormType( forwardedFormClass, mapping );
+            }
+            
+            if ( ! wrongForm )
+            {
+                request.setAttribute( Globals.MAPPING_KEY, mapping );
+                return checkTransaction( request, response, mapping, path );
+            }
+        }
+
+        //
+        // getContext().getOriginalServletPath returns the request URI we had before trying to forward to an action
+        // in a shared flow.
+        //
+        String errorServletPath = getContext().getOriginalServletPath();
+        
+        //
+        // If the error action path has a slash in it, then it's not local to the current page flow.  Replace
+        // it with the original servlet path.
+        //
+        if ( errorServletPath != null && path.indexOf( '/' ) > 0 ) path = errorServletPath;
+        return processUnresolvedAction( path, request, response, forwardedForm );
+    }
+    
+    protected boolean processSharedFlowMapping( HttpServletRequest request, HttpServletResponse response,
+                                                String actionPath, FlowController currentFlowController )
+            throws IOException
+    {
+        if ( currentFlowController.isPageFlow() )
+        {
+            int dot = actionPath.indexOf( '.' );
+            
+            if ( dot != -1 )
+            {
+                Map/*< String, SharedFlowController >* sharedFlows = PageFlowUtils.getSharedFlows( request );
+                if ( sharedFlows == null ) return true;
+                if ( dot == actionPath.length() - 1 ) return true;     // empty action name
+                assert actionPath.length() > 0 && actionPath.charAt( 0 ) == '/' : actionPath;
+                String sharedFlowName = actionPath.substring( 1, dot );
+                SharedFlowController sf = ( SharedFlowController ) sharedFlows.get( sharedFlowName );
+                
+                if ( sf != null )
+                {
+                    if ( _log.isDebugEnabled() )
+                    {
+                        _log.debug( "Forwarding to shared flow " + sf.getDisplayName() + " to handle action \""
+                                    + actionPath + "\"." );
+                    }
+                    
+                    //
+                    // Save the original request URI, so if the action fails on the shared flow, too, then we can
+                    // give an error message that includes *this* URI, not the shared flow URI.
+                    //
+                    getContext().setOriginalServletPath( InternalUtils.getRequestPath( request ) );
+                    
+                    //
+                    // Construct a URI that is [shared flow namespace] + [base action path] + [action-extension (.do)]
+                    //
+                    int lastSlash = actionPath.lastIndexOf( '/' );
+                    assert lastSlash != -1 : actionPath;
+                    InternalStringBuilder uri = new InternalStringBuilder( sf.getModulePath() );
+                    uri.append( '/' );
+                    uri.append( actionPath.substring( dot + 1 ) );
+                    uri.append( ACTION_EXTENSION );
+                    
+                    try
+                    {
+                        doForward( uri.toString(), request, response );
+                        return false;
+                    }
+                    catch ( PageFlowException e )
+                    {
+                        _log.error( "Could not forward to shared flow URI " + uri, e );
+                    }
+                }
+            }
+        }
+        
+        return true;
+    }
+    
+    protected PageFlowAction processUnresolvedAction( String actionPath, HttpServletRequest request,
+                                                     HttpServletResponse response, Object returningForm )
+        throws IOException
+    {
+                if ( _log.isInfoEnabled() )
+        {
+            InternalStringBuilder msg = new InternalStringBuilder( "action \"" ).append( actionPath );
+            _log.info( msg.append( "\" was also unhandled by Global.app." ).toString() );
+        }
+        
+        //
+        // If there's a PageFlowController for this request, try and let it handle an
+        // action-not-found exception.  Otherwise, let Struts print out its "invalid path"
+        // message.
+        //
+        FlowController fc = PageFlowUtils.getCurrentPageFlow( request );
+        
+        try
+        {
+            if ( fc != null )
+            {
+                Exception ex = new ActionNotFoundException( actionPath, fc, returningForm );
+                InternalUtils.setCurrentModule( fc.getModuleConfig(), request );
+                forward result = fc.handleException( ex, null, null, request, response );
+                return new ExceptionHandledPageFlowAction( actionPath, result );
+            }
+        }
+        catch ( PageFlowException e )
+        {
+            // ignore this -- just let Struts do its thing.
+            
+            if ( _log.isDebugEnabled() )
+            {
+                _log.debug( e );
+            }
+        }
+                    
+        if ( _log.isDebugEnabled() )
+        {
+            _log.debug( "Couldn't handle an ActionNotFoundException -- delegating to Struts" ); 
+        }
+        
+        return super.processMapping( request, response, actionPath );
+    }
+    */
+    
+    /*
+    protected boolean processRoles( HttpServletRequest request, HttpServletResponse response, PageFlowAction mapping )
+        throws IOException, PageFlowException 
+    {
+        //
+        // If there are no required roles for this action, just return.
+        //
+        String roles[] = mapping.getRoleNames();
+        if ( roles == null || roles.length < 1 )
+        {
+            return true;
+        }
+
+        // Check the current user against the list of required roles
+        FlowController fc = getContext().getCurrentFlowController();
+        FlowControllerHandlerContext context = new FlowControllerHandlerContext( request, response, fc );
+        
+        for ( int i = 0; i < roles.length; i++ )
+        {
+            if ( _handlers.getLoginHandler().isUserInRole( context, roles[i] ) )
+            {
+                if ( _log.isDebugEnabled() )
+                {
+                    _log.debug( " User " + request.getRemoteUser() + " has role '" + roles[i] + "', granting access" );
+                }
+                
+                return true;
+            }
+        }
+
+        // The current user is not authorized for this action
+        if ( _log.isDebugEnabled() )
+        {
+            _log.debug( " User '" + request.getRemoteUser() + "' does not have any required role, denying access" );
+        }
+                
+        //
+        // Here, Struts sends an HTTP error.  We try to let the current page flow handle a relevant exception.
+        //
+        LoginHandler loginHandler = _handlers.getLoginHandler();
+        String actionName = InternalUtils.getActionName( mapping );
+        FlowController currentFC = getContext().getCurrentFlowController();
+        FlowControllerException ex;
+        
+        if ( loginHandler.getUserPrincipal( context ) == null )
+        {
+            ex = currentFC.createNotLoggedInException( actionName, request );
+        }
+        else
+        {
+            ex = new UnfulfilledRolesException( mapping.getRoleNames(), mapping.getRoles(), actionName, currentFC );
+        }
+        
+        if ( currentFC != null )
+        {
+            forward fwd = currentFC.handleException( ex, mapping, null, request, response );
+            processForwardConfig( request, response, fwd );
+        }
+        else
+        {
+            ( ( ResponseErrorCodeSender ) ex ).sendResponseErrorCode( response );
+        }
+        
+        return false;
+    }
+    
+    private static String addScopeParams( String url, HttpServletRequest request )
+    {
+        //
+        // If the current request is scoped, add the right request parameter to the URL.
+        //
+        String scopeID = request.getParameter( ScopedUtils.SCOPE_ID_PARAM );
+        if ( scopeID != null )
+        {
+            return InternalUtils.addParam( url, ScopedUtils.SCOPE_ID_PARAM, scopeID );
+        }
+        else
+        {
+            return url;
+        }
+    }
+    
+    */
+
+
+    /**
+     * Set the no-cache headers.  This overrides the base Struts behavior to prevent caching even for the pages.
+     */
+    /*
+    protected void processNoCache( HttpServletRequest request, HttpServletResponse response )
+    {
+        //
+        // Set the no-cache headers if:
+        //    1) the module is configured for it, or
+        //    2) netui-config.xml has an "always" value for <pageflow-config><prevent-cache>, or
+        //    3) netui-config.xml has an "inDevMode" value for <pageflow-config><prevent-cache>, and we're not in
+        //       production mode.
+        //
+        boolean noCache = moduleConfig.getControllerConfig().getNocache();
+        
+        if ( ! noCache )
+        {
+            PageflowConfig pfConfig = ConfigUtil.getConfig().getPageflowConfig();
+            
+            if ( pfConfig != null )
+            {
+                PageflowConfig.PreventCache.Enum preventCache = pfConfig.getPreventCache();
+                
+                if ( preventCache != null )
+                {
+                    switch ( preventCache.intValue() )
+                    {
+                        case PageflowConfig.PreventCache.INT_ALWAYS:
+                            noCache = true;
+                            break;
+                        case PageflowConfig.PreventCache.INT_IN_DEV_MODE:
+                            noCache = ! _containerAdapter.isInProductionMode();
+                            break;
+                    }
+                }
+            }
+        }
+        
+        if ( noCache )
+        {
+            //
+            // The call to PageFlowPageFilter.preventCache() will cause caching to be prevented
+            // even when we end up forwarding to a pagee.  Normally, no-cache headers are lost
+            // when a server forward occurs.
+            //
+            ServletUtils.preventCache( response );
+            PageFlowPageFilter.preventCache( request );
+        }
+    }
+    */
+
+}

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowStack.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowStack.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowStack.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowStack.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,301 @@
+/*
+ * 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.StorageHandler;
+import org.apache.ti.pageflow.interceptor.action.ActionInterceptor;
+import org.apache.ti.pageflow.interceptor.action.ActionInterceptorContext;
+import org.apache.ti.pageflow.interceptor.action.InterceptorForward;
+import org.apache.ti.pageflow.internal.InternalConstants;
+import org.apache.ti.pageflow.internal.InternalUtils;
+import org.apache.ti.util.logging.Logger;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import java.io.Serializable;
+import java.util.Stack;
+
+
+/**
+ * Stack for keeping track of a series of nested page flows.  When a nested page flow is entered,
+ * the previous page flow is pushed onto this stack, which is kept in the user session.
+ */
+public class PageFlowStack
+        implements HttpSessionBindingListener, Serializable {
+
+    private static final Logger _log = Logger.getInstance(PageFlowStack.class);
+    private static final String JPF_STACK_ATTR = InternalConstants.ATTR_PREFIX + "nestingStack";
+
+    private Stack _stack = new Stack();
+
+
+    /**
+     * Wrapper that contains a pushed page flow and information related to it.
+     */
+    public static class PushedPageFlow implements Serializable {
+
+        private PageFlowController _pageFlow;
+        private ActionInterceptor _interceptor;
+        private InterceptorForward _interceptedForward;
+        private String _interceptedActionName;
+
+        public PushedPageFlow(PageFlowController pageFlow) {
+            _pageFlow = pageFlow;
+        }
+
+        public PushedPageFlow(PageFlowController pageFlow, ActionInterceptor interceptor,
+                              InterceptorForward interceptedFwd, String interceptedActionName) {
+            this(pageFlow);
+            _interceptor = interceptor;
+            _interceptedForward = interceptedFwd;
+            _interceptedActionName = interceptedActionName;
+        }
+
+        public PageFlowController getPageFlow() {
+            return _pageFlow;
+        }
+
+        public ActionInterceptor getInterceptor() {
+            return _interceptor;
+        }
+
+        public InterceptorForward getInterceptedForward() {
+            return _interceptedForward;
+        }
+
+        public String getInterceptedActionName() {
+            return _interceptedActionName;
+        }
+    }
+
+    /**
+     * Get the stack of nested page flows for the current user session.  Create and store an empty
+     * stack if none exists.
+     *
+     * @return the stack of nested page flows {@link PushedPageFlow}s) for the current user session.
+     */
+    public static PageFlowStack get() {
+        return get(true);
+    }
+
+    /**
+     * Get the stack of nested page flows for the current user session.  Create and store an empty
+     * stack if none exists.
+     *
+     * @return a {@link PageFlowStack} of nested page flows ({@link PageFlowController}s) for the current user session.
+     */
+    public static PageFlowStack get(boolean createIfNotExist) {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        String attrName = InternalUtils.getScopedAttrName(JPF_STACK_ATTR);
+        PageFlowStack jpfStack = (PageFlowStack) sh.getAttribute(attrName);
+
+        if (jpfStack == null && createIfNotExist) {
+            jpfStack = new PageFlowStack();
+            jpfStack.save();
+        }
+
+        return jpfStack;
+    }
+
+    /**
+     * Destroy the stack of {@link PageFlowController}s that have invoked nested page flows.
+     */
+    public void destroy() {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        String attrName = InternalUtils.getScopedAttrName(JPF_STACK_ATTR);
+
+        sh.removeAttribute(attrName);
+    }
+
+    /**
+     * Pop page flows from the nesting stack until one of the given type is found.
+     *
+     * @return the last popped page flow if one of the given type was found, or <code>null</code>
+     *         if none was found.
+     */
+    PageFlowController popUntil(Class stopAt) {
+        while (!isEmpty()) {
+            PageFlowController popped = pop().getPageFlow();
+
+            if (popped.getClass().equals(stopAt)) {
+                //
+                // If we've popped everything from the stack, remove the stack attribute from the session.
+                //
+                if (isEmpty()) destroy();
+                return popped;
+            } else {
+                //
+                // We're discarding the popped page flow.  Invoke its destroy() callback, unless it's longLived.
+                //
+                if (!popped.isLongLivedFlow()) popped.destroy();
+            }
+        }
+
+        destroy();   // we're empty -- remove the attribute from the session.
+        return null;
+    }
+
+    /**
+     * Pop page flows from the nesting stack until the given index.
+     *
+     * @return the last popped page flow of the given type.
+     */
+    PageFlowController popUntil(int index) {
+        for (int i = _stack.size() - 1; i > index; --i) {
+            pop();
+        }
+
+        return pop().getPageFlow();
+    }
+
+    int lastIndexOf(Class target) {
+        for (int i = _stack.size() - 1; i >= 0; --i) {
+            if (((PushedPageFlow) _stack.elementAt(i)).getPageFlow().getClass().equals(target)) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    void ensureFailover() {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        String attrName = InternalUtils.getScopedAttrName(JPF_STACK_ATTR);
+
+        sh.ensureFailover(attrName, this);
+    }
+
+    void save() {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        String attrName = InternalUtils.getScopedAttrName(JPF_STACK_ATTR);
+
+        sh.setAttribute(attrName, this);
+    }
+
+    private PageFlowStack() {
+    }
+
+    /**
+     * Push a page flow onto the stack of nested page flows in the session.
+     *
+     * @param pageFlow the page flow to push.
+     */
+    public void push(PageFlowController pageFlow) {
+        ActionInterceptorContext interceptorContext = ActionInterceptorContext.getActiveContext(true);
+
+        if (interceptorContext != null) {
+            ActionInterceptor interceptor = interceptorContext.getOverridingActionInterceptor();
+            InterceptorForward originalForward = interceptorContext.getOriginalForward();
+            String actionName = interceptorContext.getActionName();
+            _stack.push(new PushedPageFlow(pageFlow, interceptor, originalForward, actionName));
+        } else {
+            _stack.push(new PushedPageFlow(pageFlow));
+        }
+        
+        // Tell the page flow that it is on the nesting stack.
+        pageFlow.setIsOnNestingStack(true);
+        
+        // To ensure that this attribute is replicated for session failover.
+        ensureFailover();
+    }
+
+    /**
+     * Pop the most recently-pushed page flow from the stack of nested page flows in the session.
+     *
+     * @return a {@link PushedPageFlow} that represents the popped page flow.
+     */
+    public PushedPageFlow pop() {
+        PushedPageFlow ppf = (PushedPageFlow) _stack.pop();
+        PageFlowController pfc = ppf.getPageFlow();
+        pfc.setIsOnNestingStack(false);
+        
+        // Reinitialize the page flow, in case it's lost its transient state.
+        pfc.reinitialize();
+        ensureFailover();   // to ensure that this attribute is replicated for session failover
+
+        return ppf;
+    }
+
+    /**
+     * Get the most recently-pushed page flow from the stack of nested page flows in the session.
+     *
+     * @return a {@link PushedPageFlow} that represents the page flow at the top of the stack.
+     */
+    public PushedPageFlow peek() {
+        return (PushedPageFlow) _stack.peek();
+    }
+
+    /**
+     * Tell whether the stack of nested page flows is empty.
+     *
+     * @return <code>true</code> if there are no nested page flows on the stack.
+     */
+    public boolean isEmpty() {
+        return _stack.isEmpty();
+    }
+
+    /**
+     * Get the size of the stack of nested page flows.
+     *
+     * @return the number of page flows that are currently (hidden away) on the stack.
+     */
+    public int size() {
+        return _stack.size();
+    }
+
+    /**
+     * @exclude
+     */
+    public void valueBound(HttpSessionBindingEvent event) {
+    }
+
+    /**
+     * @exclude
+     */
+    public void valueUnbound(HttpSessionBindingEvent event) {
+        if (_log.isDebugEnabled()) {
+            _log.debug("The page flow stack is being unbound from the session.");
+        }
+
+        while (!isEmpty()) {
+            PageFlowController jpf = pop().getPageFlow();
+            
+            // Note that this page flow may have been serialized/deserialized, which will cause its transient info
+            // to be lost.  Rehydrate it.
+            HttpSession session = event.getSession();
+            if (session != null) jpf.reinitialize();
+
+            if (!jpf.isLongLivedFlow()) jpf.destroy();
+        }
+    }
+
+    /**
+     * Get a stack of PageFlowControllers, not of PushedPageFlows.
+     */
+    Stack getLegacyStack() {
+        Stack ret = new Stack();
+
+        for (int i = 0; i < _stack.size(); ++i) {
+            ret.push(((PushedPageFlow) _stack.get(i)).getPageFlow());
+        }
+
+        return ret;
+    }
+}

Added: struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowUtils.java
URL: http://svn.apache.org/viewcvs/struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowUtils.java?rev=240168&view=auto
==============================================================================
--- struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowUtils.java (added)
+++ struts/sandbox/trunk/ti/core/src/java/org/apache/ti/pageflow/PageFlowUtils.java Thu Aug 25 22:46:03 2005
@@ -0,0 +1,885 @@
+/*
+ * 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.commons.chain.web.WebContext;
+import org.apache.ti.core.ActionMessage;
+import org.apache.ti.core.urls.FreezableMutableURI;
+import org.apache.ti.core.urls.MutableURI;
+import org.apache.ti.core.urls.URIContext;
+import org.apache.ti.core.urls.URLRewriterService;
+import org.apache.ti.core.urls.URLType;
+import org.apache.ti.core.urltemplates.URLTemplatesFactory;
+import org.apache.ti.pageflow.handler.Handlers;
+import org.apache.ti.pageflow.handler.StorageHandler;
+import org.apache.ti.pageflow.internal.AdapterManager;
+import org.apache.ti.pageflow.internal.InternalConstants;
+import org.apache.ti.pageflow.internal.InternalUtils;
+import org.apache.ti.pageflow.internal.URIContextFactory;
+import org.apache.ti.pageflow.xwork.PageFlowActionContext;
+import org.apache.ti.schema.config.UrlConfig;
+import org.apache.ti.script.common.ImplicitObjectUtil;
+import org.apache.ti.util.config.ConfigUtil;
+import org.apache.ti.util.internal.FileUtils;
+import org.apache.ti.util.internal.InternalStringBuilder;
+import org.apache.ti.util.internal.concurrent.InternalConcurrentHashMap;
+import org.apache.ti.util.logging.Logger;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Utility methods related to Page Flow.
+ */
+public class PageFlowUtils
+        implements PageFlowConstants, InternalConstants {
+
+    private static final Logger _log = Logger.getInstance(PageFlowUtils.class);
+
+    private static final String ACTION_PATH_ATTR = ATTR_PREFIX + "_actionPath";
+    private static final int PAGEFLOW_EXTENSION_LEN = PAGEFLOW_EXTENSION.length();
+    private static final String DEFAULT_AUTORESOLVE_EXTENSIONS[] = new String[]{ACTION_EXTENSION, PAGEFLOW_EXTENSION};
+
+
+    /**
+     * Map of Struts module prefix to Map of form-type-name to form-name.
+     */
+    private static Map/*< String, Map< String, List< String > > >*/ _formNameMaps =
+            new InternalConcurrentHashMap/*< String, Map< String, List< String > > >*/();
+
+
+    /**
+     * Get a URI for the "begin" action in the PageFlowController associated with the given
+     * request URI.
+     *
+     * @return a String that is the URI for the "begin" action in the PageFlowController associated
+     *         with the given request URI.
+     */
+    public static String getBeginActionURI(String requestURI) {
+        // Translate this to a request for the begin action ("begin.do") for this PageFlowController.
+        InternalStringBuilder retVal = new InternalStringBuilder();
+        int lastSlash = requestURI.lastIndexOf('/');
+
+        if (lastSlash != -1) {
+            retVal.append(requestURI.substring(0, lastSlash));
+        }
+
+        retVal.append('/').append(BEGIN_ACTION_NAME).append(ACTION_EXTENSION);
+        return retVal.toString();
+    }
+
+    /**
+     * Get the {@link PageFlowController} that is nesting the current one.
+     *
+     * @return the nesting {@link PageFlowController}, or <code>null</code> if the current one
+     *         is not being nested.
+     */
+    public static PageFlowController getNestingPageFlow() {
+        PageFlowStack jpfStack = PageFlowStack.get(false);
+
+        if (jpfStack != null && !jpfStack.isEmpty()) {
+            PageFlowController top = jpfStack.peek().getPageFlow();
+            return top;
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the current PageFlowController.
+     *
+     * @return the current PageFlowController from the user session, or <code>null</code>
+     *         if there is none.
+     */
+    public static final PageFlowController getCurrentPageFlow() {
+        ActionResolver cur = getCurrentActionResolver();
+        return cur instanceof PageFlowController ? (PageFlowController) cur : null;
+    }
+
+    /**
+     * Get the current ActionResolver.
+     *
+     * @return the current ActionResolver from the user session, or <code>null</code> if there is none.
+     */
+    public static ActionResolver getCurrentActionResolver() {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        
+        //
+        // First see if the current page flow is a long-lived, which is stored in its own attribute.
+        //
+        String currentLongLivedAttrName = InternalUtils.getScopedAttrName(CURRENT_LONGLIVED_ATTR);
+        String currentLongLivedNamespace = (String) sh.getAttribute(currentLongLivedAttrName);
+
+        if (currentLongLivedNamespace != null) {
+            return getLongLivedPageFlow(currentLongLivedNamespace);
+        } else {
+            String currentJpfAttrName = InternalUtils.getScopedAttrName(CURRENT_JPF_ATTR);
+            return (ActionResolver) sh.getAttribute(currentJpfAttrName);
+        }
+    }
+
+    /**
+     * Get the a map of shared flow name to shared flow instance, based on the names defined in the
+     * {@link org.apache.ti.pageflow.annotations.ti.controller#sharedFlowRefs sharedFlowRefs} attribute
+     * of the {@link org.apache.ti.pageflow.annotations.ti.controller &#64;ti.controller} annotation on the
+     * <strong>current page flow</strong>.
+     *
+     * @return a Map of shared flow name (string) to shared flow instance ({@link SharedFlowController}).
+     */
+    public static Map/*< String, SharedFlowController >*/ getSharedFlows() {
+        Map/*< String, SharedFlowController >*/ sharedFlows = ImplicitObjectUtil.getSharedFlow();
+        return sharedFlows != null ? sharedFlows : Collections.EMPTY_MAP;
+    }
+
+    /**
+     * Get the shared flow with the given class name.
+     *
+     * @param sharedFlowClassName the class name of the shared flow to remove.
+     * @return the {@link SharedFlowController} of the given class name which is stored in the user session.
+     */
+    public static SharedFlowController getSharedFlow(String sharedFlowClassName) {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        return (SharedFlowController) sh.getAttribute(SHARED_FLOW_ATTR_PREFIX + sharedFlowClassName);
+    }
+
+    /**
+     * Destroy the current {@link SharedFlowController} of the given class name.
+     *
+     * @param sharedFlowClassName the class name of the current SharedFlowController to destroy.
+     */
+    public static void removeSharedFlow(String sharedFlowClassName) {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        sh.removeAttribute(SHARED_FLOW_ATTR_PREFIX + sharedFlowClassName);
+    }
+
+    /**
+     * Remove a "long-lived" page flow from the session. Once it is created, a long-lived page flow
+     * is never removed from the session unless this method or {@link PageFlowController#remove} is
+     * called.  Navigating to another page flow hides the current long-lived controller, but does not
+     * remove it.
+     */
+    public static void removeLongLivedPageFlow(String namespace) {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        String attrName = InternalUtils.getLongLivedFlowAttr(namespace);
+        attrName = InternalUtils.getScopedAttrName(attrName);
+        sh.removeAttribute(attrName);
+
+        //
+        // Now, if the current page flow is long-lived, remove the reference.
+        //
+        String currentLongLivedAttrName = InternalUtils.getScopedAttrName(CURRENT_LONGLIVED_ATTR);
+        String currentLongLivedNamespace = (String) sh.getAttribute(currentLongLivedAttrName);
+
+        if (namespace.equals(currentLongLivedNamespace)) {
+            sh.removeAttribute(currentLongLivedAttrName);
+        }
+    }
+
+    /**
+     * Get the long-lived page flow instance associated with the given module (directory) path.
+     *
+     * @param namespace the namespace of the long-lived page flow.
+     * @return the long-lived page flow instance associated with the given module, or <code>null</code> if none is found.
+     */
+    public static PageFlowController getLongLivedPageFlow(String namespace) {
+        StorageHandler sh = Handlers.get().getStorageHandler();
+        String attrName = InternalUtils.getLongLivedFlowAttr(namespace);
+        attrName = InternalUtils.getScopedAttrName(attrName);
+        PageFlowController retVal = (PageFlowController) sh.getAttribute(attrName);
+        return retVal;
+    }
+
+    /**
+     * Make any form beans in the given {@link Forward} object available as attributets in the
+     * request/session (as appropriate).
+     *
+     * @param fwd the {@link Forward} object that contains the ActionForm instances to be
+     *            made available in the request/session (as appropriate).
+     */
+    public static void setOutputForms(Forward fwd, boolean overwrite) {
+        if (fwd != null) {
+            setOutputForms(fwd.getOutputForms(), overwrite);
+            InternalUtils.setForwardedFormBean(fwd.getFirstOutputForm());
+        }
+    }
+
+    /**
+     * Make a set of form beans available as attributets in the request/session (as appropriate).
+     *
+     * @param outputForms an array of Object instances to be made available in the
+     *                    request/session (as appropriate).
+     */
+    public static void setOutputForms(Object[] outputForms) {
+        setOutputForms(outputForms, true);
+    }
+
+    /**
+     * Make a set of form beans available as attributets in the request/session (as appropriate).
+     *
+     * @param outputForms an array of Object instances to be made available in the
+     *                    request/session (as appropriate).
+     * @param overwrite   if <code>false</code> a form from <code>fwd</code> will only be set
+     *                    in the request if there is no existing form with the same name.
+     */
+    public static void setOutputForms(Object[] outputForms, boolean overwrite) {
+        for (int i = 0; i < outputForms.length; ++i) {
+            setOutputForm(outputForms[i], overwrite);
+        }
+    }
+
+    /**
+     * Make a form bean available as an attribute in the request/session (as appropriate).
+     *
+     * @param formBean  an Object instance to be made available in the request/session
+     *                  (as appropriate).
+     * @param overwrite if <code>false</code> a form from <code>fwd</code> will only be set
+     *                  in the request if there is no existing form with the same name.
+     */
+    public static void setOutputForm(Object formBean, boolean overwrite) {
+        if (formBean != null) {
+            PageFlowActionContext actionContext = PageFlowActionContext.get();
+            ModuleConfig moduleConfig = actionContext.getModuleConfig();
+            Class formClass = formBean.getClass();
+            
+            //
+            // Get the names of *all* form beans of the desired type, and blast out this instance under all those names.
+            //
+            Map formBeanAttrNames = moduleConfig.getFormBeanAttributeNames();
+            List formNames = (List) formBeanAttrNames.get(formClass.getName());
+            List additionalFormNames = null;
+            
+            //
+            // formNames is a statically-scoped list.  Below, we create a dynamic list of form names that correspond
+            // to *implemented interfaces* of the given form bean class.
+            //
+            Class[] interfaces = formClass.getInterfaces();
+            for (int i = 0; i < interfaces.length; i++) {
+                Class formInterface = interfaces[i];
+                List toAdd = (List) formBeanAttrNames.get(formInterface.getName());
+                if (toAdd != null) {
+                    if (additionalFormNames == null) additionalFormNames = new ArrayList();
+                    additionalFormNames.addAll(toAdd);
+                }
+            }
+
+            if (formNames == null && additionalFormNames == null) {
+                String formName = generateFormBeanName(formClass);
+                InternalUtils.setFormInScope(formName, formBean, overwrite);
+            } else {
+                if (formNames != null) {
+                    for (Iterator i = formNames.iterator(); i.hasNext();) {
+                        String formName = (String) i.next();
+                        InternalUtils.setFormInScope(formName, formBean, overwrite);
+                    }
+                }
+
+                if (additionalFormNames != null) {
+                    for (Iterator i = additionalFormNames.iterator(); i.hasNext();) {
+                        String formName = (String) i.next();
+                        InternalUtils.setFormInScope(formName, formBean, overwrite);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Get the name for the type of a Object instance.  Use a name looked up from
+     * the current Struts module, or, if none is found, create one.
+     *
+     * @param formInstance the Object instance whose type will determine the name.
+     * @return the name found in the Struts module, or, if none is found, a name that is either:
+     *         <ul>
+     *         <li>a camel-cased version of the base class name (minus any package or outer-class
+     *         qualifiers, or, if that name is already taken,</li>
+     *         <li>the full class name, with '.' and '$' replaced by '_'.</li>
+     *         </ul>
+     */
+    public static String getFormBeanName(Object formInstance) {
+        return getFormBeanName(formInstance.getClass());
+    }
+
+    /**
+     * Get the name for an Object type.  Use a name looked up from the current Struts module, or,
+     * if none is found, create one.
+     *
+     * @param formBeanClass the Object-derived class whose type will determine the name.
+     * @return the name found in the Struts module, or, if none is found, a name that is either:
+     *         <ul>
+     *         <li>a camel-cased version of the base class name (minus any package or outer-class
+     *         qualifiers, or, if that name is already taken,</li>
+     *         <li>the full class name, with '.' and '$' replaced by '_'.</li>
+     *         </ul>
+     */
+    public static String getFormBeanName(Class formBeanClass) {
+        ModuleConfig moduleConfig = PageFlowActionContext.get().getModuleConfig();
+        List names = (List) moduleConfig.getFormBeanAttributeNames().get(formBeanClass.getName());
+
+        if (names != null) {
+            assert names.size() > 0;    // getFormNamesFromModuleConfig returns null or a nonempty list
+            return (String) names.get(0);
+        }
+
+        return generateFormBeanName(formBeanClass);
+    }
+
+    /**
+     * Create the name for a form bean type.
+     *
+     * @param formBeanClass the class whose type will determine the name.
+     * @return the name found in the Struts module, or, if none is found, a name that is either:
+     *         <ul>
+     *         <li>a camel-cased version of the base class name (minus any package or outer-class
+     *         qualifiers, or, if that name is already taken,</li>
+     *         <li>the full class name, with '.' and '$' replaced by '_'.</li>
+     *         </ul>
+     */
+    private static String generateFormBeanName(Class formBeanClass) {
+        ModuleConfig moduleConfig = PageFlowActionContext.get().getModuleConfig();
+        String formBeanClassName = formBeanClass.getName();
+        
+        //
+        // A form-bean wasn't found for this type, so we'll create a name.  First try and create
+        // name that is a camelcased version of the classname without all of its package/outer-class
+        // qualifiers.  If one with that name already exists, munge the fully-qualified classname.
+        //
+        String formType = formBeanClassName;
+        int lastQualifier = formType.lastIndexOf('$');
+
+        if (lastQualifier == -1) {
+            lastQualifier = formType.lastIndexOf('.');
+        }
+
+        String formName = formType.substring(lastQualifier + 1);
+        formName = Character.toLowerCase(formName.charAt(0)) + formName.substring(1);
+
+        if (moduleConfig.getFormBeans().get(formName) != null) {
+            formName = formType.replace('.', '_').replace('$', '_');
+            assert moduleConfig.getFormBeans().get(formName) == null : formName;
+        }
+
+        return formName;
+    }
+
+    /**
+     * Get the class name of a {@link PageFlowController}, given the URI to it.
+     *
+     * @param uri the URI to the {@link PageFlowController}, which should be relative to the
+     *            web application root (i.e., it should not include the context path).
+     */
+    public static String getPageFlowClassName(String uri) {
+        assert uri != null;
+        assert uri.length() > 0;
+
+        if (uri.charAt(0) == '/') uri = uri.substring(1);
+
+        assert FileUtils.osSensitiveEndsWith(uri, PAGEFLOW_EXTENSION) : uri;
+        if (FileUtils.osSensitiveEndsWith(uri, PAGEFLOW_EXTENSION)) {
+            uri = uri.substring(0, uri.length() - PAGEFLOW_EXTENSION_LEN);
+        }
+
+        return uri.replace('/', '.');
+    }
+
+    /**
+     * Get the URI for a {@link PageFlowController}, given its class name.
+     *
+     * @param className the name of the {@link PageFlowController} class.
+     * @return a String that is the URI for the {@link PageFlowController}, relative to the web
+     *         application root (i.e., not including the context path).
+     */
+    public static String getPageFlowURI(String className) {
+        return '/' + className.replace('.', '/') + PAGEFLOW_EXTENSION;
+    }
+
+    /**
+     * Get the most recent action URI that was processed by {@link FlowController#execute}.
+     *
+     * @return a String that is the most recent action URI.  This is only valid during a request
+     *         that has been forwarded from the action URI.
+     */
+    public static String getActionPath() {
+        return (String) getWebContext().getRequestScope().get(ACTION_PATH_ATTR);
+    }
+
+    private static WebContext getWebContext() {
+        return PageFlowActionContext.get().getWebContext();
+    }
+
+    /**
+     * Sets the most recent action URI that was processed by {@link FlowController#execute}.
+     */
+    static void setActionPath() {
+        PageFlowActionContext actionContext = PageFlowActionContext.get();
+        getWebContext().getRequestScope().put(ACTION_PATH_ATTR, actionContext.getRequestPath());
+    }
+
+    /**
+     * Tell whether a web application resource requires a secure transport protocol.  This is
+     * determined from web.xml; for example, the following block specifies that all resources under
+     * /login require a secure transport protocol.
+     * <pre>
+     *    &lt;security-constraint&gt;
+     *        &lt;web-resource-collection&gt;
+     *          &lt;web-resource-name&gt;Secure PageFlow - begin&lt;/web-resource-name&gt;
+     *          &lt;url-pattern&gt;/login/*&lt;/url-pattern&gt;
+     *        &lt;/web-resource-collection&gt;
+     *        &lt;user-data-constraint&gt;
+     *           &lt;transport-guarantee&gt;CONFIDENTIAL&lt;/transport-guarantee&gt;
+     *        &lt;/user-data-constraint&gt;
+     *    &lt;/security-constraint&gt;
+     * </pre>
+     *
+     * @param uri a webapp-relative URI for a resource.  There must not be query parameters or a scheme
+     *            on the URI.
+     * @return <code>Boolean.TRUE</code> if a transport-guarantee of <code>CONFIDENTIAL</code> or
+     *         <code>INTEGRAL</code> is associated with the given resource; <code>Boolean.FALSE</code>
+     *         a transport-guarantee of <code>NONE</code> is associated with the given resource; or
+     *         <code>null</code> if there is no transport-guarantee associated with the given resource.
+     */
+    public static SecurityProtocol getSecurityProtocol(String uri) {
+        return AdapterManager.getContainerAdapter().getSecurityProtocol(uri);
+    }
+
+    /**
+     * Set a named action output, which corresponds to an input declared by the <code>pageInput</code> JSP tag.
+     * The actual value can be read from within a JSP using the <code>"pageInput"</code> databinding context.
+     *
+     * @param name  the name of the action output.
+     * @param value the value of the action output.
+     */
+    public static void addActionOutput(String name, Object value) {
+        Map map = InternalUtils.getActionOutputMap(true);
+
+        if (map.containsKey(name)) {
+            if (_log.isWarnEnabled()) {
+                _log.warn("Overwriting action output\"" + name + "\".");
+            }
+        }
+
+        map.put(name, value);
+    }
+
+    /**
+     * Get a named action output that was registered in the current request.
+     *
+     * @param name the name of the action output.
+     * @see #addActionOutput
+     */
+    public static Object getActionOutput(String name) {
+        Map map = InternalUtils.getActionOutputMap(false);
+        return map != null ? map.get(name) : 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.
+     */
+    public static void addActionError(String propertyName, String messageKey, Object[] messageArgs) {
+        InternalUtils.addActionError(propertyName, new ActionMessage(messageKey, messageArgs));
+    }
+
+    /**
+     * 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.
+     */
+    public static void addActionError(String propertyName, String messageKey) {
+        InternalUtils.addActionError(propertyName, new ActionMessage(messageKey, 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 messageArg   an argument to the message
+     */
+    public static void addActionError(String propertyName, String messageKey, Object messageArg) {
+        Object[] messageArgs = new Object[]{messageArg};
+        InternalUtils.addActionError(propertyName, new ActionMessage(messageKey, messageArgs));
+    }
+
+    /**
+     * 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 messageArg1  the first argument to the message
+     * @param messageArg2  the second argument to the message
+     */
+    public static void addActionError(String propertyName, String messageKey, Object messageArg1, Object messageArg2) {
+        Object[] messageArgs = new Object[]{messageArg1, messageArg2};
+        InternalUtils.addActionError(propertyName, new ActionMessage(messageKey, messageArgs));
+    }
+
+    /**
+     * 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 messageArg1  the first argument to the message
+     * @param messageArg2  the second argument to the message
+     * @param messageArg3  the third argument to the message
+     */
+    public static void addActionError(String propertyName, String messageKey, Object messageArg1, Object messageArg2, Object messageArg3) {
+        Object[] messageArgs = new Object[]{messageArg1, messageArg2, messageArg3};
+        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.
+     */
+    public static void addActionErrorExpression(String propertyName, String expression, Object[] messageArgs) {
+        ExpressionMessage msg = new ExpressionMessage(expression, messageArgs);
+        InternalUtils.addActionError(propertyName, msg);
+    }
+    
+    /**
+     * Resolve the given action to a URI by running an entire request-processing cycle on the given ScopedRequest
+     * and ScopedResponse.
+     * @exclude
+     * 
+     * @param actionOverride if not <code>null</code>, this qualified action-path is used to construct an action
+     *                       URI which is set as the request URI.  The action-path <strong>must</strong> begin with '/',
+     *                       which makes it qualified from the webapp root.
+     * @param autoResolveExtensions a list of URI extensions (e.g., ".do", ".jpf") that will be auto-resolved, i.e.,
+     *                              on which this method will be recursively called.  If <code>null</code>, the
+     *                              default extensions ".do" and ".jpf" will be used.
+     */
+    /* TODO: re-add some form of this, for portal/portlet support
+    public static ActionResult strutsLookup( String actionOverride, String[] autoResolveExtensions )
+        throws Exception
+    {
+        ScopedRequest scopedRequest = ScopedUtils.unwrapRequest( request );
+        ScopedResponse scopedResponse = ScopedUtils.unwrapResponse( response );
+        assert scopedRequest != null : request.getClass().getName();
+        assert scopedResponse != null : response.getClass().getName();
+        
+        if ( scopedRequest == null )
+        {
+            throw new IllegalArgumentException( "request must be of type " + ScopedRequest.class.getName() );
+        }
+        if ( scopedResponse == null )
+        {
+            throw new IllegalArgumentException( "response must be of type " + ScopedResponse.class.getName() );
+        }
+        
+        ActionServlet as = InternalUtils.getActionServlet( context );
+        
+        if ( as == null )
+        {
+            _log.error( "There is no initialized ActionServlet.  The ActionServlet must be set to load-on-startup." );
+            return null;
+        }
+        
+        if ( actionOverride != null )
+        {
+            // The action must be fully-qualified with its namespace.
+            assert actionOverride.charAt( 0 ) == '/' : actionOverride;
+            InternalStringBuilder uri = new InternalStringBuilder( scopedRequest.getContextPath() );
+            uri.append( actionOverride );
+            uri.append( PageFlowConstants.ACTION_EXTENSION );
+            scopedRequest.setRequestURI( uri.toString() );
+        }
+
+        //
+        // In case the request was already forwarded once, clear out the recorded forwarded-URI.  This
+        // will allow us to tell whether processing the request actually forwarded somewhere.
+        //
+        scopedRequest.setForwardedURI( null );
+        
+        //
+        // Now process the request.  We create a PageFlowRequestWrapper for pageflow-specific request-scoped info.
+        //
+        PageFlowRequestWrapper wrappedRequest = PageFlowRequestWrapper.wrapRequest( ( HttpServletRequest ) request );
+        as.doGet( wrappedRequest, scopedResponse );  // this just calls process() -- same as doPost()
+
+        String returnURI;
+
+        if ( ! scopedResponse.didRedirect() )
+        {
+            returnURI = scopedRequest.getForwardedURI();
+            
+            if ( autoResolveExtensions == null )
+            {
+                autoResolveExtensions = DEFAULT_AUTORESOLVE_EXTENSIONS;
+            }
+            
+            if ( returnURI != null )
+            {
+                for ( int i = 0; i < autoResolveExtensions.length; ++i )
+                {
+                    if ( FileUtils.uriEndsWith( returnURI, autoResolveExtensions[i] ) )
+                    {
+                        scopedRequest.doForward();
+                        return strutsLookup( context, wrappedRequest, scopedResponse, null, autoResolveExtensions );
+                    }
+                }
+            }
+        }
+        else
+        {
+            returnURI = scopedResponse.getRedirectURI();
+        }
+        
+        DeferredSessionStorageHandler.applyChanges( scopedRequest, context );
+        
+        if ( returnURI != null )
+        {
+            return new ActionResultImpl( returnURI, scopedResponse.didRedirect(), scopedResponse.getStatusCode(),
+                                         scopedResponse.getStatusMessage(), scopedResponse.isError() );
+        }
+        else
+        {
+            return null;
+        }
+    }
+    */
+    
+    /**
+     * Create a raw action URI, which can be modified before being sent through the registered URL rewriting chain
+     * using {@link URLRewriterService#rewriteURL}.  Use {@link #getRewrittenActionURI} to get a fully-rewritten URI.
+     *
+     * @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.
+     */
+    public static MutableURI getActionURI(String actionName)
+            throws URISyntaxException {
+        // TODO: need ActionMapper to be reversible -- it should construct the URI.
+        if (actionName.length() < 1) throw new IllegalArgumentException("actionName must be non-empty");
+
+        PageFlowActionContext actionContext = PageFlowActionContext.get();
+        InternalStringBuilder actionURI = new InternalStringBuilder(actionContext.getRequestContextPath());
+
+        if (actionName.charAt(0) != '/') {
+            actionURI.append(actionContext.getNamespace());
+            actionURI.append('/');
+        }
+
+        actionURI.append(actionName);
+        if (!actionName.endsWith(ACTION_EXTENSION)) actionURI.append(ACTION_EXTENSION);
+
+        FreezableMutableURI uri = new FreezableMutableURI();
+        // TODO: re-add the following line, using some abstraction
+        //uri.setEncoding( response.getCharacterEncoding() );
+        uri.setURI(actionURI.toString(), true);
+        return uri;
+    }
+
+    /**
+     * Create a fully-rewritten URI given an action name 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 params     the additional parameters to include in the URI query.
+     * @param fragment   the fragment (anchor or location) for this url.
+     * @param forXML     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 URI (derived
+     *                            from processing the given action name) into a MutableURI.
+     */
+    public static String getRewrittenActionURI(String actionName, Map params, String fragment, boolean forXML)
+            throws URISyntaxException {
+        MutableURI uri = getActionURI(actionName);
+        if (params != null) uri.addParameters(params, false);
+        if (fragment != null) uri.setFragment(uri.encode(fragment));
+
+        boolean needsToBeSecure = needsToBeSecure(uri.getPath(), true);
+        URLRewriterService.rewriteURL(uri, URLType.ACTION, needsToBeSecure);
+        String key = getURLTemplateKey(URLType.ACTION, needsToBeSecure);
+        URIContext uriContext = URIContextFactory.getInstance(forXML);
+
+        return URLRewriterService.getTemplatedURL(uri, key, uriContext);
+    }
+
+    /**
+     * Create a fully-rewritten URI given a path and parameters.
+     * <p/>
+     * <p> Calls the rewriter service using a type of {@link URLType#RESOURCE}. </p>
+     *
+     * @param path     the path to process into a fully-rewritten URI.
+     * @param params   the additional parameters to include in the URI query.
+     * @param fragment the fragment (anchor or location) for this URI.
+     * @param forXML   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's a problem converting the action URI (derived
+     *                            from processing the given action name) into a MutableURI.
+     */
+    public static String getRewrittenResourceURI(String path, Map params,
+                                                 String fragment, boolean forXML)
+            throws URISyntaxException {
+        return rewriteResourceOrHrefURL(path, params, fragment, forXML, URLType.RESOURCE);
+    }
+
+    /**
+     * Create a fully-rewritten URI given a path and parameters.
+     * <p/>
+     * <p> Calls the rewriter service using a type of {@link URLType#ACTION}. </p>
+     *
+     * @param path     the path to process into a fully-rewritten URI.
+     * @param params   the additional parameters to include in the URI query.
+     * @param fragment the fragment (anchor or location) for this URI.
+     * @param forXML   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's a problem converting the action URI (derived
+     *                            from processing the given action name) into a MutableURI.
+     */
+    public static String getRewrittenHrefURI(String path, Map params, String fragment, boolean forXML)
+            throws URISyntaxException {
+        return rewriteResourceOrHrefURL(path, params, fragment, forXML, URLType.ACTION);
+    }
+
+    private static String rewriteResourceOrHrefURL(String path, Map params, String fragment, boolean forXML, URLType urlType)
+            throws URISyntaxException {
+        boolean encoded = false;
+        UrlConfig urlConfig = ConfigUtil.getConfig().getUrlConfig();
+
+        if (urlConfig != null && urlConfig.isSetUrlEncodeUrls()) {
+            encoded = !urlConfig.getUrlEncodeUrls();
+        }
+
+        FreezableMutableURI uri = new FreezableMutableURI();
+        // TODO: re-add the following line, using some abstraction
+        //uri.setEncoding( response.getCharacterEncoding() );
+        uri.setURI(path, encoded);
+
+        if (params != null) {
+            uri.addParameters(params, false);
+        }
+
+        if (fragment != null) {
+            uri.setFragment(uri.encode(fragment));
+        }
+
+        URIContext uriContext = URIContextFactory.getInstance(forXML);
+        if (uri.isAbsolute()) {
+            return uri.getURIString(uriContext);
+        }
+
+        if (path.length() != 0 && path.charAt(0) != '/') {
+            PageFlowActionContext actionContext = PageFlowActionContext.get();
+            String reqPath = actionContext.getRequestPath();
+            reqPath = reqPath.substring(0, reqPath.lastIndexOf('/') + 1);
+            uri.setPath(reqPath + uri.getPath());
+        }
+
+        boolean needsToBeSecure = needsToBeSecure(uri.getPath(), true);
+        URLRewriterService.rewriteURL(uri, urlType, needsToBeSecure);
+        String key = getURLTemplateKey(urlType, needsToBeSecure);
+
+        return URLRewriterService.getTemplatedURL(uri, key, uriContext);
+    }
+
+    /**
+     * Tell whether a given URI should be written to be secure.
+     *
+     * @param uri              the URI to check.
+     * @param stripContextPath if <code>true</code>, strip the webapp context path from the URI before
+     *                         processing it.
+     * @return <code>true</code> when:
+     *         <ul>
+     *         <li>the given URI is configured in the deployment descriptor to be secure (according to
+     *         {@link SecurityProtocol}), or
+     *         <li>the given URI is not configured in the deployment descriptor, and the current request
+     *         is secure ({@link javax.servlet.http.HttpServletRequest#isSecure} returns
+     *         <code>true</code>).
+     *         </ul>
+     *         <code>false</code> when:
+     *         <ul>
+     *         <li>the given URI is configured explicitly in the deployment descriptor to be unsecure
+     *         (according to {@link SecurityProtocol}), or
+     *         <li>the given URI is not configured in the deployment descriptor, and the current request
+     *         is unsecure ({@link javax.servlet.http.HttpServletRequest#isSecure} returns
+     *         <code>false</code>).
+     *         </ul>
+     */
+    public static boolean needsToBeSecure(String uri, boolean stripContextPath) {
+        PageFlowActionContext actionContext = PageFlowActionContext.get();        
+        
+        // Get the web-app relative path for security check
+        String secureCheck = uri;
+        if (stripContextPath) {
+            String contextPath = actionContext.getRequestContextPath();
+            if (secureCheck.startsWith(contextPath)) {
+                secureCheck = secureCheck.substring(contextPath.length());
+            }
+        }
+
+        boolean secure = false;
+        if (secureCheck.indexOf('?') > -1) {
+            secureCheck = secureCheck.substring(0, secureCheck.indexOf('?'));
+        }
+
+        SecurityProtocol sp = getSecurityProtocol(secureCheck);
+        if (sp.equals(SecurityProtocol.UNSPECIFIED)) {
+            secure = actionContext.isRequestSecure();
+        } else {
+            secure = sp.equals(SecurityProtocol.SECURE);
+        }
+
+        return secure;
+    }
+
+    /**
+     * Returns a key for the URL template type given the URL type and a
+     * flag indicating a secure URL or not.
+     *
+     * @param urlType         the type of URL (ACTION, RESOURCE).
+     * @param needsToBeSecure indicates that the template should be for a secure URL.
+     * @return the key/type of template to use.
+     */
+    public static String getURLTemplateKey(URLType urlType, boolean needsToBeSecure) {
+        String key = URLTemplatesFactory.ACTION_TEMPLATE;
+        if (urlType.equals(URLType.ACTION)) {
+            if (needsToBeSecure) {
+                key = URLTemplatesFactory.SECURE_ACTION_TEMPLATE;
+            } else {
+                key = URLTemplatesFactory.ACTION_TEMPLATE;
+            }
+        } else if (urlType.equals(URLType.RESOURCE)) {
+            if (needsToBeSecure) {
+                key = URLTemplatesFactory.SECURE_RESOURCE_TEMPLATE;
+            } else {
+                key = URLTemplatesFactory.RESOURCE_TEMPLATE;
+            }
+        }
+
+        return key;
+    }
+
+}



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