/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.config.web.tiles;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.ForwardConfig;
import org.apache.struts.tiles.ComponentContext;
import org.apache.struts.tiles.ComponentDefinition;
import org.apache.struts.tiles.Controller;
import org.apache.struts.tiles.DefinitionsFactoryException;
import org.apache.struts.tiles.DefinitionsUtil;
import org.apache.struts.tiles.NoSuchDefinitionException;
import org.apache.struts.upload.MultipartRequestWrapper;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.struts.DelegatingActionProxy;
import org.springframework.web.struts.DelegatingActionUtils;
import org.springframework.web.struts.DelegatingRequestProcessor;
import org.springframework.web.struts.DelegatingTilesRequestProcessor;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Subclass of Struts' TilesRequestProcessor that looks up Spring-managed
* Struts 1.1 Actions defined in ContextLoaderPlugIn's WebApplicationContext.
*
* <p>Behaves like
* {@link DelegatingRequestProcessor DelegatingRequestProcessor},
* but also provides the Tiles functionality of the original TilesRequestProcessor.
* As there's just a single central class to customize in Struts, we have to provide
* another subclass here, covering both the Tiles and the Spring delegation aspect.
*
* <p>The default implementation delegates to the DelegatingActionUtils
* class as fas as possible, to reuse as much code as possible despite
* the need to provide two RequestProcessor subclasses. If you need to
* subclass yet another RequestProcessor, take this class as a template,
* delegating to DelegatingActionUtils just like it.
*
* @author Juergen Hoeller
* @since 1.0.2
* @see DelegatingRequestProcessor
* @see DelegatingActionProxy
* @see DelegatingActionUtils
*/
public class MultipleDelegatingTilesRequestProcessor extends DelegatingTilesRequestProcessor {
/**
* Overloaded method from Struts' RequestProcessor.
* Forward or redirect to the specified destination by the specified
* mechanism.
* This method catches the Struts' actionForward call. It checks if the
* actionForward is done on a Tiles definition name. If true, process the
* definition and insert it. If false, call the original parent's method.
* @param request The servlet request we are processing.
* @param response The servlet response we are creating.
* @param forward The ActionForward controlling where we go next.
*
* @exception IOException if an input/output error occurs.
* @exception ServletException if a servlet exception occurs.
*/
protected void processForwardConfig(HttpServletRequest request, HttpServletResponse response,
ForwardConfig forward) throws IOException, ServletException {
// Required by struts contract
if (forward == null) {
return;
}
if (log.isDebugEnabled()) {
log.debug("processForwardConfig(" + forward.getPath() + ", "
+ forward.getContextRelative() + ")");
}
// Try to process the definition.
if (processTilesDefinition(forward.getPath(), /*forward.getContextRelative()*/
false, request, response)) {
if (log.isDebugEnabled()) {
log.debug(" '" + forward.getPath() + "' - processed as definition");
}
return;
}
if (log.isDebugEnabled()) {
log.debug(" '" + forward.getPath() + "' - processed as uri");
}
// forward doesn't contain a definition, let parent do processing
super.processForwardConfig(request, response, forward);
}
/**
* Process a Tile definition name.
* This method tries to process the parameter <code>definitionName</code> as a definition name.
* It returns <code>true</code> if a definition has been processed, or <code>false</code> otherwise.
* Parameter <code>contextRelative</code> is not used in this implementation.
*
* @param definitionName Definition name to insert.
* @param contextRelative Is the definition marked contextRelative ?
* @param request Current page request.
* @param response Current page response.
* @return <code>true</code> if the method has processed uri as a definition name, <code>false</code> otherwise.
*/
protected boolean processTilesDefinition(String definitionName, boolean contextRelative,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// Do we do a forward (original behavior) or an include ?
boolean doInclude = false;
// Controller associated to a definition, if any
Controller controller = null;
// Computed uri to include
String uri = null;
ComponentContext tileContext = null;
try {
// Get current tile context if any.
// If context exist, we will do an include
tileContext = ComponentContext.getContext(request);
doInclude = (tileContext != null);
ComponentDefinition definition = null;
// Process tiles definition names only if a definition factory exist,
// and definition is found.
if (definitionsFactory != null) {
// Get definition of tiles/component corresponding to uri.
try {
definition = definitionsFactory.getDefinition(definitionName, request,
getServletContext());
} catch (NoSuchDefinitionException ex) {
// Ignore not found
log.debug("NoSuchDefinitionException " + ex.getMessage());
}
if (definition != null) { // We have a definition.
// We use it to complete missing attribute in context.
// We also get uri, controller.
uri = definition.getPath();
controller = definition.getOrCreateController();
if (tileContext == null) {
tileContext = new ComponentContext(definition.getAttributes());
ComponentContext.setContext(tileContext, request);
} else {
tileContext.addMissing(definition.getAttributes());
}
}
}
// Process definition set in Action, if any.
definition = DefinitionsUtil.getActionDefinition(request);
if (definition != null) { // We have a definition.
// We use it to complete missing attribute in context.
// We also overload uri and controller if set in definition.
if (definition.getPath() != null) {
uri = definition.getPath();
}
if (definition.getOrCreateController() != null) {
controller = definition.getOrCreateController();
}
if (tileContext == null) {
tileContext = new ComponentContext(definition.getAttributes());
ComponentContext.setContext(tileContext, request);
} else {
tileContext.addMissing(definition.getAttributes());
}
}
} catch (java.lang.InstantiationException ex) {
log.error("Can't create associated controller", ex);
throw new ServletException("Can't create associated controller", ex);
} catch (DefinitionsFactoryException ex) {
throw new ServletException(ex);
}
// Have we found a definition ?
if (uri == null) {
return false;
}
// Execute controller associated to definition, if any.
if (controller != null) {
try {
controller.execute(tileContext, request, response, getServletContext());
} catch (Exception e) {
throw new ServletException(e);
}
}
// If request comes from a previous Tile, do an include.
// This allows to insert an action in a Tile.
if (log.isDebugEnabled()) {
log.debug("uri=" + uri + " doInclude=" + doInclude);
}
if (doInclude) {
doInclude(uri, request, response);
} else {
doForward(uri, request, response); // original behavior
}
return true;
}
/**
* <p>Process a forward requested by this mapping (if any). Return
* <code>true</code> if standard processing should continue, or
* <code>false</code> if we have already handled this request.</p>
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param mapping The ActionMapping we are using
*/
protected boolean processForward(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping) throws IOException, ServletException {
// Are we going to processing this request?
String forward = mapping.getForward();
if (forward == null) {
return (true);
}
internalModuleRelativeForward(forward, request, response);
return (false);
}
/**
* <p>Process an include requested by this mapping (if any). Return
* <code>true</code> if standard processing should continue, or
* <code>false</code> if we have already handled this request.</p>
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param mapping The ActionMapping we are using
*/
protected boolean processInclude(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping) throws IOException, ServletException {
// Are we going to processing this request?
String include = mapping.getInclude();
if (include == null) {
return (true);
}
internalModuleRelativeInclude(include, request, response);
return (false);
}
/**
* <p>Do a module relative forward to specified URI using request dispatcher.
* URI is relative to the current module. The real URI is compute by prefixing
* the module name.</p>
* <p>This method is used internally and is not part of the public API. It is
* advised to not use it in subclasses. </p>
*
* @param uri Module-relative URI to forward to
* @param request Current page request
* @param response Current page response
*
* @since Struts 1.1
*/
protected void internalModuleRelativeForward(String uri, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
// Construct a request dispatcher for the specified path
uri = moduleConfig.getPrefix() + uri;
// Delegate the processing of this request
// :FIXME: - exception handling?
if (log.isDebugEnabled()) {
log.debug(" Delegating via forward to '" + uri + "'");
}
doForward(uri, request, response);
}
/**
* <p>Do a module relative include to specified URI using request dispatcher.
* URI is relative to the current module. The real URI is compute by prefixing
* the module name.</p>
* <p>This method is used internally and is not part of the public API. It is
* advised to not use it in subclasses.</p>
*
* @param uri Module-relative URI to include
* @param request Current page request
* @param response Current page response
*
* @since Struts 1.1
*/
protected void internalModuleRelativeInclude(String uri, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
// Construct a request dispatcher for the specified path
uri = moduleConfig.getPrefix() + uri;
// Delegate the processing of this request
// FIXME - exception handling?
if (log.isDebugEnabled()) {
log.debug(" Delegating via include to '" + uri + "'");
}
doInclude(uri, request, response);
}
/**
* <p>Do a forward to specified URI using a <code>RequestDispatcher</code>.
* This method is used by all internal method needing to do a forward.</p>
*
* @param uri Context-relative URI to forward to
* @param request Current page request
* @param response Current page response
* @since Struts 1.1
*/
protected void doForward(String uri, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// Unwrap the multipart request, if there is one.
if (request instanceof MultipartRequestWrapper) {
request = ((MultipartRequestWrapper) request).getRequest();
}
RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
if (rd == null) {
URL resource = WebApplicationContextUtils.getWebApplicationContext(getServletContext())
.getResource(uri).getURL();
response.sendRedirect(resource.toString());
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
getInternal().getMessage("requestDispatcher", uri));
return;
}
rd.forward(request, response);
}
/**
* <p>Do an include of specified URI using a <code>RequestDispatcher</code>.
* This method is used by all internal method needing to do an include.</p>
*
* @param uri Context-relative URI to include
* @param request Current page request
* @param response Current page response
* @since Struts 1.1
*/
protected void doInclude(String uri, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// Unwrap the multipart request, if there is one.
if (request instanceof MultipartRequestWrapper) {
request = ((MultipartRequestWrapper) request).getRequest();
}
RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
if (rd == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
getInternal().getMessage("requestDispatcher", uri));
return;
}
rd.include(request, response);
}
}