/*
* 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.beehive.netui.pageflow.internal;
import org.apache.beehive.netui.util.internal.InternalStringBuilder;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.struts.config.ModuleConfig;
import org.apache.beehive.netui.pageflow.interceptor.action.ActionInterceptor;
import org.apache.beehive.netui.pageflow.interceptor.action.AfterNestedInterceptContext;
import org.apache.beehive.netui.pageflow.interceptor.action.InterceptorForward;
import org.apache.beehive.netui.pageflow.interceptor.InterceptorException;
import org.apache.beehive.netui.pageflow.*;
import org.apache.beehive.netui.pageflow.handler.FlowControllerHandlerContext;
import org.apache.beehive.netui.pageflow.handler.ActionForwardHandler;
import org.apache.beehive.netui.util.logging.Logger;
import org.apache.beehive.netui.script.common.ImplicitObjectUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletContext;
public class DefaultActionForwardHandler
extends DefaultHandler
implements ActionForwardHandler
{
private static final Logger _log = Logger.getInstance( DefaultActionForwardHandler.class );
public DefaultActionForwardHandler( ServletContext servletContext )
{
init( null, null, servletContext );
}
/**
* Perform any necessary updates to the request and user session (including updates to the
* PageFlowController stack), based on the given ActionForward.
*
* @param context the current FlowControllerHandlerContext.
* @param fwd the Struts ActionForward that determines the next URI to be displayed.
* @param mapping the Struts ActionMapping for the current action being processed.
* @param actionName the name of the Struts action being processed.
* @param altModuleConfig an alternate module config (e.g., Global.app's ModuleConfig) from which to
* resolve a forward if it can't be resolved from the given ActionMapping.
* @return the ActionForward object to pass to Struts for actual Servlet forwarding.
*/
public ActionForward doForward( FlowControllerHandlerContext context, ActionForward fwd, ActionMapping mapping,
String actionName, ModuleConfig altModuleConfig, ActionForm form )
{
boolean isSpecialForward = false;
boolean isReturnToCurrentPage = false;
assert context.getRequest() instanceof HttpServletRequest : "don't support ServletRequest currently.";
HttpServletRequest request = ( HttpServletRequest ) context.getRequest();
FlowController flowController = context.getFlowController();
//
// There is a special forward ("auto"), which signals us to render using a registered ViewRenderer.
// This is used as part of popup window support.
//
if ( fwd != null && PageFlowConstants.AUTO_VIEW_RENDER_FORWARD_NAME.equals( fwd.getName() ) )
{
return getRegisteredActionForwardHandler().doAutoViewRender( context, mapping, form );
}
if ( fwd != null && fwd instanceof Forward )
{
Forward pageFlowFwd = ( Forward ) fwd;
pageFlowFwd.initialize( mapping, flowController, request );
pageFlowFwd.setAlternateModuleConfig( altModuleConfig );
if ( ! pageFlowFwd.doesResolve() )
{
PageFlowException ex =
new UnresolvableForwardException( pageFlowFwd.getName(), actionName, flowController );
InternalUtils.throwPageFlowException( ex, request );
}
//
// If it's a return-to-page, do what's necessary to return to the previous page,
// with its state intact.
//
if ( pageFlowFwd.isReturnToPage() )
{
isSpecialForward = true;
//
// We need access to _previousPageInfo from the *current PageFlow*. That is
// most likely this FlowController, but if it's Global.app, then we don't want
// to use that.
//
PageFlowController curJpf = PageFlowUtils.getCurrentPageFlow( request );
if ( curJpf == null )
{
PageFlowException ex = new NoCurrentPageFlowException( actionName, pageFlowFwd );
InternalUtils.throwPageFlowException( ex, request );
assert false; // throwPageFlowException() must throw.
}
PreviousPageInfo prevPageInfo;
switch ( pageFlowFwd.getReturnToType() )
{
case Forward.RETURN_TO_CURRENT_PAGE:
prevPageInfo = curJpf.getCurrentPageInfo();
isReturnToCurrentPage = true;
break;
case Forward.RETURN_TO_PREVIOUS_PAGE:
prevPageInfo = curJpf.getPreviousPageInfo();
break;
case Forward.RETURN_TO_PAGE:
prevPageInfo = flowController.getPreviousPageInfoLegacy( curJpf, request );
break;
default:
assert false : pageFlowFwd.getReturnToType();
prevPageInfo = curJpf.getCurrentPageInfo();
}
fwd =
getRegisteredActionForwardHandler().doReturnToPage( context, prevPageInfo, curJpf, form, actionName, pageFlowFwd );
if ( prevPageInfo != null )
{
mapping = prevPageInfo.getMapping();
if ( form == null ) form = prevPageInfo.getForm();
}
if ( _log.isDebugEnabled() )
{
_log.debug( "return-to-page: " + ( fwd != null ? fwd.getPath() : "[null]" ) );
}
}
else if ( pageFlowFwd.isReturnToAction() )
{
isSpecialForward = true;
fwd = getRegisteredActionForwardHandler().doReturnToAction( context, actionName, pageFlowFwd );
}
//
// See if we should pop the current PageFlowController (done nesting).
//
if ( pageFlowFwd.isNestedReturn() )
{
isSpecialForward = true;
fwd = getRegisteredActionForwardHandler().doNestingReturn( context, pageFlowFwd, mapping, form );
}
//
// Set ActionForms specified in the Forward. Note that this overwrites any forms restored
// during return-to="page".
//
PageFlowUtils.setOutputForms( mapping, pageFlowFwd, request );
InternalUtils.addActionOutputs( pageFlowFwd.getActionOutputs() , request, true );
}
if ( fwd != null )
{
if ( _log.isDebugEnabled() )
{
if ( fwd.getRedirect() )
{
_log.debug( "Redirecting to " + fwd.getPath() );
}
else
{
_log.debug( "Forwarding to " + fwd.getPath() );
}
}
}
else
{
_log.debug( "null ActionForward -- not doing any forward or redirect." );
}
//
// Save info on this forward for return-to="currentPage" or return-to="previousPage". But, don't save
// the info if the current forward is a return-to="currentPage" -- we don't want this to turn into
// the page that's seen for *both* return-to="currentPage" and return-to="previousPage".
//
if ( ! isReturnToCurrentPage )
{
flowController.savePreviousPageInfo( fwd, form, mapping, request, getServletContext(), isSpecialForward );
}
return fwd;
}
public ActionForward doAutoViewRender( FlowControllerHandlerContext context, ActionMapping mapping, ActionForm form )
{
assert context.getRequest() instanceof HttpServletRequest : "don't support ServletRequest currently.";
assert context.getResponse() instanceof HttpServletResponse : "don't support ServletResponse currently.";
HttpServletRequest request = ( HttpServletRequest ) context.getRequest();
HttpServletResponse response = ( HttpServletResponse ) context.getResponse();
ViewRenderer vr = PageFlowRequestWrapper.get( request ).getViewRenderer();
if ( vr != null )
{
_log.debug( "null ActionForward -- delegating to ViewRenderer " + vr + " to handle response." );
try
{
vr.renderView( request, response, getServletContext() );
}
catch ( Throwable th )
{
try
{
return context.getFlowController().handleException( th, mapping, form, request, response );
}
catch ( Exception e )
{
_log.error( "Exception thrown while handling exception in ViewRenderer " + vr + ": "
+ e.getMessage(), th );
}
}
}
else
{
_log.error( "Auto-render forward " + PageFlowConstants.AUTO_VIEW_RENDER_FORWARD_NAME
+ " used, but no ViewRenderer " + "was registered -- not doing any forward or redirect." );
}
return null;
}
/**
* Get an ActionForward to the original page that was visible before the previous action.
*/
public ActionForward doReturnToPage( FlowControllerHandlerContext context, PreviousPageInfo prevPageInfo,
PageFlowController currentPageFlow, ActionForm currentForm,
String actionName, Forward pageFlowFwd )
{
assert context.getRequest() instanceof HttpServletRequest : "don't support ServletRequest currently.";
HttpServletRequest request = ( HttpServletRequest ) context.getRequest();
if ( prevPageInfo == null )
{
if ( _log.isInfoEnabled() )
{
_log.info( "Attempted return-to-page, but previous page info was missing." );
}
PageFlowException ex = new NoPreviousPageException( actionName, pageFlowFwd, currentPageFlow );
InternalUtils.throwPageFlowException( ex, request );
}
//
// Figure out what URI to return to, and set the original form in the request or session.
//
ActionForward retFwd = prevPageInfo.getForward();
ActionMapping prevMapping = prevPageInfo.getMapping();
//
// Restore any forms that are specified by this Forward (overwrite the original forms).
//
if ( retFwd instanceof Forward )
{
PageFlowUtils.setOutputForms( prevMapping, ( Forward ) retFwd, request, false );
InternalUtils.addActionOutputs( ( ( Forward ) retFwd ).getActionOutputs(), request, false );
}
//
// If the user hit the previous page directly (without going through an action), prevMapping will be null.
//
if ( prevMapping != null )
{
//
// If the currently-posted form is of the right type, initialize the page with that (but we don't overwrite
// the form that was set above).
//
if ( currentForm != null ) PageFlowUtils.setOutputForm( prevMapping, currentForm, request, false );
//
// Initialize the page with the original form it got forwarded (but we don't overwrite the form that was
// set above).
//
InternalUtils.setFormInScope( prevMapping.getName(), prevPageInfo.getForm(), prevMapping, request, false );
}
//
// If we're forwarding to a page in a different pageflow, we need to make sure the returned ActionForward has
// the right module path, and that it has contextRelative=true.
//
FlowController flowController = context.getFlowController();
if ( ! retFwd.getContextRelative() && flowController != currentPageFlow )
{
retFwd = new ActionForward( retFwd.getName(),
currentPageFlow.getModulePath() + retFwd.getPath(),
retFwd.getRedirect(),
true );
}
if ( _log.isDebugEnabled() )
{
_log.debug( "Return-to-page in PageFlowController " + flowController.getClass().getName()
+ ": original URI " + retFwd.getPath() );
}
if ( retFwd != null )
{
//
// If the new (return-to) Forward specifies a redirect value explicitly, use that; otherwise
// use the redirect value from the original Forward.
//
if ( pageFlowFwd.hasExplicitRedirectValue() ) retFwd.setRedirect( pageFlowFwd.getRedirect() );
//
// If there's a query string, override the previous query string.
//
String fwdPath = retFwd.getPath();
String newQueryString = pageFlowFwd.getQueryString();
int existingQueryPos = fwdPath.indexOf( '?' );
//
// If the new Forward (the one with Jpf.NavigateTo.currentPage/previousPage) has a query string, use that.
// Otherwise, if the old Forward has no query string, restore the one from the PreviousPageInfo if
// appropriate.
//
if ( newQueryString != null )
{
// Chop off the old query string if necessary.
if ( existingQueryPos != -1 ) fwdPath = fwdPath.substring( 0, existingQueryPos );
retFwd.setPath( fwdPath + newQueryString );
}
else if ( existingQueryPos == -1 )
{
retFwd.setPath( fwdPath + getQueryString( pageFlowFwd, prevPageInfo ) );
}
}
PageFlowRequestWrapper.get( request ).setPreviousPageInfo( prevPageInfo );
return retFwd;
}
public ActionForward doReturnToAction( FlowControllerHandlerContext context, String actionName, Forward pageFlowFwd )
{
assert context.getRequest() instanceof HttpServletRequest : "don't support ServletRequest currently.";
HttpServletRequest request = ( HttpServletRequest ) context.getRequest();
//
// We need access to _previousPageInfo from the *current PageFlow*. That is
// most likely this FlowController, but if it's Global.app, then we don't want
// to use that.
//
PageFlowController curJpf = PageFlowUtils.getCurrentPageFlow( request );
if ( curJpf == null )
{
PageFlowException ex = new NoCurrentPageFlowException( actionName, pageFlowFwd );
InternalUtils.throwPageFlowException( ex, request );
assert false; // throwPageFlowException() must throw.
}
PreviousActionInfo prevActionInfo = curJpf.getPreviousActionInfo();
if ( prevActionInfo != null )
{
String actionURI = prevActionInfo.getActionURI();
if ( _log.isDebugEnabled() ) _log.debug( "return-to-action: " + actionURI );
//
// If there's no form specified in this return-to-action forward, then use the original form that was saved
// in the action. Only do this if we're not doing a redirect, which precludes request attributes.
//
if ( ! pageFlowFwd.isRedirect() && prevActionInfo.getForm() != null
&& pageFlowFwd.getFirstOutputForm( request ) == null )
{
pageFlowFwd.addOutputForm( prevActionInfo.getForm() );
}
String query = getQueryString( pageFlowFwd, prevActionInfo );
ActionForward fwd = new ActionForward( actionURI + query, pageFlowFwd.getRedirect() );
fwd.setContextRelative( true );
return fwd;
}
else
{
if ( _log.isInfoEnabled() )
{
_log.info( "Attempted return-to-action, but previous action info was missing." );
}
PageFlowException ex = new NoPreviousActionException( actionName, pageFlowFwd, curJpf );
InternalUtils.throwPageFlowException( ex, request );
assert false; // previous method always throws
return null;
}
}
private static String getQueryString( Forward pageFlowFwd, PreviousInfo previousInfo )
{
String query = pageFlowFwd.getQueryString();
if ( query == null ) query = "";
//
// If the restoreQueryString attribute was set, use the query string from the original action URI.
//
boolean restoreQueryString = pageFlowFwd.doesRestoreQueryString();
if ( restoreQueryString )
{
String prevQuery = previousInfo.getQueryString();
if ( prevQuery != null ) query += ( query.length() > 0 ? "&" : "?" ) + prevQuery;
}
return query;
}
public ActionForward doNestingReturn( FlowControllerHandlerContext context, Forward pageFlowFwd,
ActionMapping mapping, ActionForm form )
{
assert context.getRequest() instanceof HttpServletRequest : "don't support ServletRequest currently.";
assert context.getResponse() instanceof HttpServletResponse : "don't support ServletResponse currently.";
HttpServletRequest request = ( HttpServletRequest ) context.getRequest();
HttpServletResponse response = ( HttpServletResponse ) context.getResponse();
PageFlowStack pfStack = PageFlowStack.get( request );
String returnAction = pageFlowFwd.getPath();
if ( pfStack.isEmpty() )
{
PageFlowController curJpf = PageFlowUtils.getCurrentPageFlow( request );
if ( _log.isInfoEnabled() )
{
_log.info( "Tried to pop from empty PageFlow stack. Current = "
+ curJpf.getClass().getName() );
}
if ( _log.isWarnEnabled() )
{
InternalStringBuilder msg = new InternalStringBuilder( "Tried to pop from empty PageFlow stack." );
msg.append( " Current page flow is " );
msg.append( curJpf != null ? curJpf.getClass().getName() : null );
_log.warn( msg.append( '.' ).toString() );
}
PageFlowException ex = new EmptyNestingStackException( returnAction, curJpf );
InternalUtils.throwPageFlowException( ex, request );
}
// Only nested PageFlowControllers can have return actions.
assert context.getFlowController() instanceof PageFlowController
: context.getFlowController().getClass().getName() + " is not a " + PageFlowController.class.getName();
ActionForward exceptionFwd =
( ( PageFlowController ) context.getFlowController() ).exitNesting( request, response, mapping, form );
if ( exceptionFwd != null ) return exceptionFwd;
PageFlowStack.PushedPageFlow pushedPageFlowWrapper = pfStack.pop( request );
PageFlowController poppedPageFlow = pushedPageFlowWrapper.getPageFlow();
if ( _log.isDebugEnabled() )
{
_log.debug( "Popped PageFlowController " + poppedPageFlow + " from the nesting stack" );
}
InternalUtils.setCurrentPageFlow( poppedPageFlow, request );
//
// If an ActionInterceptor forwarded to the nested page flow, give it a chance to change the URI as the nested
// flow is returning. If it doesn't, we'll go to the originally-intended Forward.
//
ActionInterceptor interceptor = pushedPageFlowWrapper.getInterceptor();
if ( interceptor != null )
{
return getRegisteredActionForwardHandler().handleInterceptorReturn( context, poppedPageFlow,
pushedPageFlowWrapper, returnAction,
mapping, form, interceptor );
}
//
// Raise the returned action on the popped pageflow.
//
assert returnAction.charAt( 0 ) != '/' : returnAction;
if ( _log.isDebugEnabled() )
{
_log.debug( "Action on popped PageFlowController is " + returnAction );
}
InternalStringBuilder returnActionPath = new InternalStringBuilder( poppedPageFlow.getModulePath() );
returnActionPath.append( '/' ).append( returnAction ).append( PageFlowConstants.ACTION_EXTENSION );
//
// Store the returned form in the request.
//
ActionForm retForm = pageFlowFwd.getFirstOutputForm( request );
if ( retForm != null )
{
InternalUtils.setForwardedFormBean( request, retForm );
ImplicitObjectUtil.loadOutputFormBean( request, InternalUtils.unwrapFormBean( retForm ) );
}
// TODO: delete this deprecated feature (following line). This is the Jpf.NavigateTo.page value.
request.setAttribute( InternalConstants.RETURNING_FROM_NESTING_ATTR, Boolean.TRUE );
//
// Forward to the return-action on the nesting page flow.
//
ActionForward fwd = new ActionForward( returnActionPath.toString(), false );
fwd.setContextRelative( true );
return fwd;
}
public ActionForward handleInterceptorReturn( FlowControllerHandlerContext context,
PageFlowController poppedPageFlow,
PageFlowStack.PushedPageFlow pushedPageFlowWrapper,
String returnAction, ActionMapping actionMapping,
ActionForm form, ActionInterceptor interceptor )
{
assert context.getRequest() instanceof HttpServletRequest : "don't support ServletRequest currently.";
assert context.getResponse() instanceof HttpServletResponse : "don't support ServletResponse currently.";
HttpServletRequest request = ( HttpServletRequest ) context.getRequest();
HttpServletResponse response = ( HttpServletResponse ) context.getResponse();
PageFlowRequestWrapper.get( request ).setReturningFromActionIntercept( true );
try
{
AfterNestedInterceptContext interceptorContext =
new AfterNestedInterceptContext( request, response, getServletContext(), poppedPageFlow,
pushedPageFlowWrapper.getInterceptedForward(),
pushedPageFlowWrapper.getInterceptedActionName(),
returnAction );
interceptor.afterNestedIntercept( interceptorContext );
if ( interceptorContext.hasInterceptorForward() )
{
InterceptorForward fwd = interceptorContext.getInterceptorForward();
if ( _log.isDebugEnabled() )
{
InternalStringBuilder message = new InternalStringBuilder();
message.append( "Interceptor " );
message.append( interceptor.getClass().getName() );
message.append( " after nested page flow: " );
if ( fwd != null )
{
message.append( "forwarding to " );
message.append( fwd.getPath() );
}
else
{
message.append( "returned InterceptorForward is null." );
}
_log.debug( message.toString() );
}
if ( fwd != null ) fwd.rehydrateRequest( request );
return fwd;
}
}
catch ( InterceptorException e )
{
_log.error( "Exception in " + interceptor.getClass().getName() + ".afterNestedIntercept", e );
try
{
return poppedPageFlow.handleException( e, actionMapping, form, request, response );
}
catch ( Exception anotherException )
{
_log.error( "Exception thrown while handling exception.", anotherException );
}
}
//
// The interceptor declined to forward us anywhere -- just go to the originally-intended Forward.
//
return pushedPageFlowWrapper.getInterceptedForward();
}
public ActionForwardHandler getRegisteredActionForwardHandler()
{
return ( ActionForwardHandler ) super.getRegisteredHandler();
}
}