/*
* 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;
import org.apache.beehive.netui.pageflow.internal.InternalConstants;
import org.apache.beehive.netui.pageflow.internal.PageFlowRequestWrapper;
import org.apache.beehive.netui.pageflow.internal.InternalUtils;
import org.apache.beehive.netui.pageflow.handler.Handlers;
import org.apache.beehive.netui.pageflow.handler.FlowControllerHandlerContext;
import org.apache.beehive.netui.pageflow.handler.ForwardRedirectHandler;
import org.apache.beehive.netui.util.internal.InternalStringBuilder;
import org.apache.beehive.netui.util.internal.ServletUtils;
import org.apache.beehive.netui.util.logging.Logger;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.action.RequestProcessor;
import javax.servlet.ServletException;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.io.IOException;
import java.util.Map;
/**
* ActionServlet that dynamically registers modules based on naming/location conventions for Struts
* configuration files that are generated by the Page Flow compiler. These files are located in
* /WEB-INF/classes/_pageflow, and are named struts-config-<i>module-name</i>.xml.
* For auto-registration of config files in other locations, the user may specify additional
* {@link ModuleConfigLocator} classes in /WEB-INF/beehive-netui-config.xml using the
* <code><module-config-locators></code> element.
*/
public class PageFlowActionServlet extends AutoRegisterActionServlet
{
private static final Logger _log = Logger.getInstance( PageFlowActionServlet.class );
private Handlers _handlers;
private static final ModuleConfigLocator[] DEFAULT_MODULE_CONFIG_LOCATORS =
{
new DefaultModuleConfigLocator(),
new LegacyModuleConfigLocator()
};
/**
* Get the base list of ModuleConfigLocators, to specify locations for auto-registered Struts modules. By default,
* this ActionServlet auto-registers Struts modules whose configuration files are located at
* "/WEB-INF/classes/_pageflow/struts-config-<i><module></i>". Overriding this method allows
* alternate locations to be specified. When an unrecognized Struts module is requested, each registered
* ModuleConfigLocator is queried for a possible path to the configuration file for the module. If the
* configuration file is found, the module is auto-registered against the file.
*/
protected ModuleConfigLocator[] getDefaultModuleConfigLocators()
{
return DEFAULT_MODULE_CONFIG_LOCATORS;
}
/**
* Default ModuleConfigLocator that looks for Struts module configuration files according to the pattern
* "/WEB-INF/classes/_pageflow/struts-config-<i><module></i>". An instance of this class
* is registered by default.
*
* @see PageFlowActionServlet#getDefaultModuleConfigLocators
*/
public static class DefaultModuleConfigLocator implements ModuleConfigLocator, Serializable
{
public String getModuleConfigPath( String moduleName )
{
InternalStringBuilder moduleConfPath = new InternalStringBuilder( getGenDir() );
moduleConfPath.append( '/' ).append( PageFlowConstants.PAGEFLOW_MODULE_CONFIG_PREFIX );
if ( moduleName.length() > 1 )
{
moduleConfPath.append( moduleName.replace( '/', '-' ) );
}
moduleConfPath.append( PageFlowConstants.PAGEFLOW_MODULE_CONFIG_EXTENSION );
return moduleConfPath.toString();
}
protected String getGenDir()
{
return PageFlowConstants.PAGEFLOW_MODULE_CONFIG_GEN_DIR;
}
}
/**
* ModuleConfigLocator that looks for legacy Struts module configuration files according to the pattern
* "/WEB-INF/struts-config-<i><module></i>". An instance of this class is registered by default.
*
* @see PageFlowActionServlet#getDefaultModuleConfigLocators
*/
protected static class LegacyModuleConfigLocator extends DefaultModuleConfigLocator
{
protected String getGenDir()
{
return InternalConstants.WEBINF_DIR;
}
}
public void init()
throws ServletException
{
//
// Ensure that PageFlowContextListener gets to do its initializations, even if it's not registered in web.xml.
//
ServletContext servletContext = getServletContext();
if ( ! PageFlowContextListener.isInit( servletContext ) )
{
PageFlowContextListener.performInitializations( servletContext );
}
_handlers = Handlers.get( servletContext );
super.init();
}
protected void process( HttpServletRequest request, HttpServletResponse response )
throws IOException, ServletException
{
// If this is a direct request for a shared flow (.jpfs) or a Faces backing bean (.jsfb), return a 404 status.
// These are not web-addressable.
String servletPath = InternalUtils.getDecodedServletPath( request );
if ( servletPath.endsWith( InternalConstants.SHARED_FLOW_EXTENSION ) ||
servletPath.endsWith( InternalConstants.FACES_BACKING_EXTENSION ) )
{
if ( _log.isDebugEnabled() )
{
_log.debug( "Attempt to hit restricted URI " + servletPath + "; 404 error returned." );
}
response.sendError( HttpServletResponse.SC_NOT_FOUND );
return;
}
// First, reinitialize the page flow classloader, for reloading when recompile occurs in dev mode.
FlowControllerHandlerContext handlerContext = new FlowControllerHandlerContext( request, response, null );
_handlers.getReloadableClassHandler().reloadClasses( handlerContext );
super.process( request, response );
}
/**
* Get the webapp-relative path to the Struts module configration file for a given module path. By default,
* this is "/WEB-INF/classes/_pageflow/struts-config-<i><module></i>", but alternate
* locations can be specified by adding {@link ModuleConfigLocator}s.
*
* @param modulePath the Struts module path.
* @return a String that is the path to the Struts configuration file, relative to the web application root.
* @see #getDefaultModuleConfigLocators
*/
public String getModuleConfPath( String modulePath )
{
return super.getModuleConfPath( modulePath );
}
/**
* Struts keeps track of the action servlet URL pattern (e.g., *.do) so it can construct action
* URIs. We want to prevent it from noticing *.jpf so it doesn't use that to construct the URIs.
*
* @exclude
*/
public void addServletMapping( String servletName, String urlPattern )
{
if ( ! urlPattern.endsWith( PageFlowConstants.JPF_EXTENSION ) )
{
super.addServletMapping( servletName, urlPattern );
}
}
/**
* Tell whether the given module can handle the given path. If this is the root module (path=="") and it's a
* Page Flow module, then it shouldn't try to handle any path that has a slash in it -- it only handles local
* actions.
*/
protected boolean moduleCanHandlePath( ModuleConfig moduleConfig, RequestProcessor rp, String servletPath )
{
if ( moduleConfig.getPrefix().equals( "" ) && servletPath.lastIndexOf( '/' ) > 0
&& rp instanceof PageFlowRequestProcessor )
{
return false;
}
return true;
}
/**
* Last chance to handle an unhandled action URI.
* @return <code>true</code> if this method handled it (by forwarding somewhere or writing to the response).
*/
protected boolean processUnhandledAction( HttpServletRequest request, HttpServletResponse response, String uri )
throws IOException, ServletException
{
//
// First check to see if we're already in a forwarded fallback request. If so, just bail.
//
PageFlowRequestWrapper rw = PageFlowRequestWrapper.get( request );
if ( rw.getOriginalServletPath() != null ) return false;
SharedFlowController sharedFlowToTry = null;
String uriBaseName = ServletUtils.getBaseName( uri );
int firstDot = uriBaseName.indexOf( '.' );
int lastDot = uriBaseName.lastIndexOf( '.' );
if ( firstDot != -1 && firstDot != lastDot )
{
String sharedFlowName = uriBaseName.substring( 0, firstDot );
try
{
RequestContext rc = new RequestContext( request, response );
Map defaultSharedFlows = FlowControllerFactory.get( getServletContext() ).getDefaultSharedFlows( rc );
if ( defaultSharedFlows != null )
{
sharedFlowToTry = ( SharedFlowController ) defaultSharedFlows.get( sharedFlowName );
uriBaseName = uriBaseName.substring( firstDot + 1 );
}
}
catch ( ClassNotFoundException e )
{
throw new ServletException( e );
}
catch ( InstantiationException e )
{
throw new ServletException( e );
}
catch ( IllegalAccessException e )
{
throw new ServletException( e );
}
}
else
{
sharedFlowToTry = FlowControllerFactory.getGlobalApp( request, response, getServletContext() );
}
//
// If we couldn't find an appropriate module, try raising the action on the (deprecated) Global.app.
//
if ( sharedFlowToTry != null )
{
InternalStringBuilder sfActionURI = new InternalStringBuilder( sharedFlowToTry.getModulePath() );
sfActionURI.append( '/' );
sfActionURI.append( uriBaseName );
rw.setOriginalServletPath( uri );
ForwardRedirectHandler frh = _handlers.getForwardRedirectHandler();
FlowControllerHandlerContext context = new FlowControllerHandlerContext( request, response, null );
frh.forward( context, sfActionURI.toString() );
return true;
}
return false;
}
}