JdbcMapper/beehive-netui-core/src/main/java/org/apache/beehive/netui/pageflow/internal/DefaultActionForwardHandler...

667 lines
29 KiB
Java

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.struts.config.ExceptionConfig;
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.*;
import org.apache.beehive.netui.pageflow.config.PageFlowExceptionConfig;
import org.apache.beehive.netui.pageflow.config.PageFlowActionMapping;
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 );
}
public ActionForward processForward( FlowControllerHandlerContext context, ActionForward fwd, ActionMapping mapping,
ExceptionConfig exceptionConfig, String actionName,
ModuleConfig altModuleConfig, ActionForm form )
{
boolean isSpecialForward = false;
boolean isReturnToCurrentPage = false;
boolean isNonInheritedGlobalForward = 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, getServletContext() );
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.theCurrentPageInfo();
isReturnToCurrentPage = true;
break;
case Forward.RETURN_TO_PREVIOUS_PAGE:
prevPageInfo = curJpf.thePreviousPageInfo();
break;
case Forward.RETURN_TO_PAGE:
prevPageInfo = flowController.getPreviousPageInfoLegacy( curJpf, request );
break;
default:
assert false : pageFlowFwd.getReturnToType();
prevPageInfo = curJpf.theCurrentPageInfo();
}
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 );
}
else if ( pageFlowFwd.isNestedReturn() )
{
//
// Pop the current PageFlowController (done nesting).
//
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 );
// Check if this is a non-inherited global forward to help determine
// if we need to ensure we use a path local to the current page flow.
if ( mapping != null && mapping.findForwardConfig( pageFlowFwd.getName() ) == null
&& mapping.getModuleConfig().findForwardConfig( pageFlowFwd.getName() ) != null
&& ! pageFlowFwd.hasRelativeToPath() )
{
isNonInheritedGlobalForward = true;
}
}
if ( fwd != null )
{
// It's a normal path. Let the Forward object do some magic if we're specifying that it's relative
// to a particular directory path (as is the case when inheriting local paths from base classes).
// Note that if *either* the exception-config or the action-config specifies a value for
// getLocalPathsRelativeTo(), we use it. Also note that we do not do this for forward to other actions
// or if this is a non-inherited global forward; in those cases we always want paths to be local
// to the current page flow.
String localPathsRelativeTo = null;
if (exceptionConfig != null && exceptionConfig instanceof PageFlowExceptionConfig) {
localPathsRelativeTo = ((PageFlowExceptionConfig) exceptionConfig).getLocalPathsRelativeTo();
}
if (localPathsRelativeTo == null && mapping instanceof PageFlowActionMapping) {
localPathsRelativeTo = ((PageFlowActionMapping) mapping).getLocalPathsRelativeTo();
}
if (localPathsRelativeTo != null) {
String path = fwd.getPath();
if (!path.endsWith(PageFlowConstants.ACTION_EXTENSION) && !isNonInheritedGlobalForward) {
Forward pageFlowFwd = fwd instanceof Forward ? (Forward) fwd : new WrapperForward(fwd);
pageFlowFwd.initializeRelativePath(request, localPathsRelativeTo);
fwd = pageFlowFwd;
}
}
else if ( fwd instanceof Forward )
{
( ( Forward ) fwd ).initializeRelativePath(request, 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".
// However, don't save the current forward as previous if this 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".
// Just set the request attribute indicating that prev page info state was saved.
//
if (!isReturnToCurrentPage) {
flowController.savePreviousPageInfo(fwd, form, mapping, request, getServletContext(), isSpecialForward);
}
else {
request.setAttribute(InternalConstants.SAVED_PREVIOUS_PAGE_INFO_ATTR, Boolean.TRUE);
}
return fwd;
}
private class WrapperForward extends Forward
{
public WrapperForward(ActionForward base)
{
super(base, getServletContext());
}
}
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( "ActionForward -- delegating to ViewRenderer " + vr + " to handle response." );
try
{
request.setAttribute(PageFlowConstants.VIEW_RENDERER_ATTRIBUTE_NAME, vr);
String ext = getServletContext().getInitParameter(PageFlowConstants.VIEW_RENDERING_EXTENSION_PARAM);
if (ext == null || ext.length() == 0) {
ext = PageFlowConstants.DEFAULT_VIEW_RENDERING_EXTENSION;
}
InternalStringBuilder path = new InternalStringBuilder(64);
path.append("/");
path.append(PageFlowConstants.VIEW_RENDERER_URI_COMMAND);
path.append(".").append(ext);
ActionForward fwd = new ActionForward( path.toString(), false );
fwd.setContextRelative( true );
return fwd;
}
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, getServletContext() );
if ( curJpf == null )
{
PageFlowException ex = new NoCurrentPageFlowException( actionName, pageFlowFwd );
InternalUtils.throwPageFlowException( ex, request );
assert false; // throwPageFlowException() must throw.
}
PreviousActionInfo prevActionInfo = curJpf.thePreviousActionInfo();
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, getServletContext() );
String returnAction = pageFlowFwd.getPath();
if ( pfStack.isEmpty() )
{
PageFlowController curJpf = PageFlowUtils.getCurrentPageFlow( request, getServletContext() );
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, getServletContext() );
//
// 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 ) );
}
// Keep track of the fact that we are returning from nesting in this request.
PageFlowRequestWrapper.get(request).setReturningFromNesting(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 ( Throwable e )
{
//
// Yes, we *do* mean to catch Throwable here. It will get re-thrown if the page flow does not handle it.
//
_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.
//
InterceptorForward fwd = pushedPageFlowWrapper.getInterceptedForward();
fwd.rehydrateRequest( request );
return fwd;
}
public ActionForwardHandler getRegisteredActionForwardHandler()
{
return ( ActionForwardHandler ) super.getRegisteredHandler();
}
}