From 946737c81184f4b8c398a58e852faae197d6f84e Mon Sep 17 00:00:00 2001 From: Kusal Kithul-Godage Date: Tue, 2 Jan 2024 03:29:54 +1100 Subject: [PATCH] WW-5382 Rework Dispatcher injections --- .../apache/struts2/dispatcher/Dispatcher.java | 91 +- .../struts2/dispatcher/ExecuteOperations.java | 4 +- .../struts2/dispatcher/InitOperations.java | 2 +- .../struts2/dispatcher/PrepareOperations.java | 8 +- .../struts2/util/StrutsTestCaseHelper.java | 2 +- .../struts2/dispatcher/DispatcherTest.java | 6 +- .../struts2/validators/DWRValidator.java | 33 +- .../portlet/dispatcher/Jsr168Dispatcher.java | 1348 ++++++++--------- .../OldDecorator2NewStrutsDecorator.java | 403 ++--- 9 files changed, 969 insertions(+), 928 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java index 6fab13b72..c534069c3 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java @@ -64,6 +64,7 @@ import org.apache.struts2.config.StrutsBeanSelectionProvider; import org.apache.struts2.config.StrutsJavaConfiguration; import org.apache.struts2.config.StrutsJavaConfigurationProvider; import org.apache.struts2.config.StrutsXmlConfigurationProvider; +import org.apache.struts2.dispatcher.mapper.ActionMapper; import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.dispatcher.multipart.MultiPartRequest; import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper; @@ -119,6 +120,10 @@ public class Dispatcher { */ private static final List dispatcherListeners = new CopyOnWriteArrayList<>(); + /** + * This field exists so {@link #getContainer()} can determine whether to (re-)inject this instance in the case of + * a {@link ConfigurationManager} reload. + */ private Container injectedContainer; /** @@ -146,11 +151,6 @@ public class Dispatcher { */ private String multipartSaveDir; - /** - * Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_PARSER} setting - */ - private String multipartHandlerName; - /** * Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_ENABLED} */ @@ -194,6 +194,11 @@ public class Dispatcher { * Store ConfigurationManager instance, set on init. */ protected ConfigurationManager configurationManager; + private ObjectFactory objectFactory; + private ActionProxyFactory actionProxyFactory; + private LocaleProviderFactory localeProviderFactory; + private StaticContentLoader staticContentLoader; + private ActionMapper actionMapper; /** * Provide the dispatcher instance for the current thread. @@ -213,6 +218,13 @@ public class Dispatcher { Dispatcher.instance.set(instance); } + /** + * Removes the dispatcher instance for this thread. + */ + public static void clearInstance() { + Dispatcher.instance.remove(); + } + /** * Add a dispatcher lifecycle listener. * @@ -308,9 +320,12 @@ public class Dispatcher { multipartSaveDir = val; } - @Inject(StrutsConstants.STRUTS_MULTIPART_PARSER) + /** + * @deprecated since 6.4.0, no replacement. + */ + @Deprecated public void setMultipartHandler(String val) { - multipartHandlerName = val; + // no-op } @Inject(value = StrutsConstants.STRUTS_MULTIPART_ENABLED, required = false) @@ -328,6 +343,10 @@ public class Dispatcher { this.valueStackFactory = valueStackFactory; } + public ValueStackFactory getValueStackFactory() { + return valueStackFactory; + } + @Inject(StrutsConstants.STRUTS_HANDLE_EXCEPTION) public void setHandleException(String handleException) { this.handleException = Boolean.parseBoolean(handleException); @@ -348,12 +367,48 @@ public class Dispatcher { this.errorHandler = errorHandler; } + @Inject + public void setObjectFactory(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + @Inject + public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) { + this.actionProxyFactory = actionProxyFactory; + } + + public ActionProxyFactory getActionProxyFactory() { + return actionProxyFactory; + } + + @Inject + public void setLocaleProviderFactory(LocaleProviderFactory localeProviderFactory) { + this.localeProviderFactory = localeProviderFactory; + } + + @Inject + public void setStaticContentLoader(StaticContentLoader staticContentLoader) { + this.staticContentLoader = staticContentLoader; + } + + public StaticContentLoader getStaticContentLoader() { + return staticContentLoader; + } + + @Inject + public void setActionMapper(ActionMapper actionMapper) { + this.actionMapper = actionMapper; + } + + public ActionMapper getActionMapper() { + return actionMapper; + } + /** * Releases all instances bound to this dispatcher instance. */ public void cleanup() { // clean up ObjectFactory - ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class); if (objectFactory == null) { LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed"); } @@ -540,10 +595,6 @@ public class Dispatcher { loadConfigPaths("struts-deferred.xml"); } - private Container init_PreloadConfiguration() { - return getContainer(); - } - /** * Load configurations, including both XML and zero-configuration strategies, * and update optional settings, including whether to reload configurations and resource files. @@ -684,7 +735,6 @@ public class Dispatcher { } protected ActionProxy createActionProxy(String namespace, String name, String method, Map extraContext) { - ActionProxyFactory actionProxyFactory = getContainer().getInstance(ActionProxyFactory.class); return actionProxyFactory.createActionProxy(namespace, name, method, extraContext, true, false); } @@ -860,6 +910,7 @@ public class Dispatcher { * @param response The response */ public void prepare(HttpServletRequest request, HttpServletResponse response) { + getContainer(); // Init ContainerHolder and reinject this instance IF ConfigurationManager was reloaded String encoding = null; if (defaultEncoding != null) { encoding = defaultEncoding; @@ -932,15 +983,12 @@ public class Dispatcher { } if (isMultipartSupportEnabled(request) && isMultipartRequest(request)) { - MultiPartRequest multiPartRequest = getMultiPartRequest(); - LocaleProviderFactory localeProviderFactory = getContainer().getInstance(LocaleProviderFactory.class); - request = new MultiPartRequestWrapper( - multiPartRequest, - request, - getSaveDir(), - localeProviderFactory.createLocaleProvider(), - disableRequestAttributeValueStackLookup + getMultiPartRequest(), + request, + getSaveDir(), + localeProviderFactory.createLocaleProvider(), + disableRequestAttributeValueStackLookup ); } else { request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); @@ -1065,5 +1113,4 @@ public class Dispatcher { } return ContainerHolder.get(); } - } diff --git a/core/src/main/java/org/apache/struts2/dispatcher/ExecuteOperations.java b/core/src/main/java/org/apache/struts2/dispatcher/ExecuteOperations.java index 8671688de..769ca0a39 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/ExecuteOperations.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/ExecuteOperations.java @@ -18,8 +18,8 @@ */ package org.apache.struts2.dispatcher; -import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.RequestUtils; +import org.apache.struts2.dispatcher.mapper.ActionMapping; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -54,7 +54,7 @@ public class ExecuteOperations { resourcePath = request.getPathInfo(); } - StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class); + StaticContentLoader staticResourceLoader = dispatcher.getStaticContentLoader(); if (staticResourceLoader.canHandle(resourcePath)) { staticResourceLoader.findStaticResource(resourcePath, request, response); // The framework did its job here diff --git a/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java b/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java index 819e7cdb9..367aeba55 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java @@ -57,7 +57,7 @@ public class InitOperations { * @return the static content loader */ public StaticContentLoader initStaticContentLoader(HostConfig filterConfig, Dispatcher dispatcher) { - StaticContentLoader loader = dispatcher.getContainer().getInstance(StaticContentLoader.class); + StaticContentLoader loader = dispatcher.getStaticContentLoader(); loader.setHostConfig(filterConfig); return loader; } diff --git a/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java b/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java index a643de310..6888c5b7a 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java @@ -20,13 +20,11 @@ package org.apache.struts2.dispatcher; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.util.ValueStack; -import com.opensymphony.xwork2.util.ValueStackFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.RequestUtils; import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsException; -import org.apache.struts2.dispatcher.mapper.ActionMapper; import org.apache.struts2.dispatcher.mapper.ActionMapping; import javax.servlet.ServletException; @@ -78,7 +76,7 @@ public class PrepareOperations { dispatcher.cleanUpRequest(request); } finally { ActionContext.clear(); - Dispatcher.setInstance(null); + Dispatcher.clearInstance(); devModeOverride.remove(); } }); @@ -101,7 +99,7 @@ public class PrepareOperations { } else { ctx = ServletActionContext.getActionContext(request); //checks if we are probably in an async if (ctx == null) { - ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); + ValueStack stack = dispatcher.getValueStackFactory().createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null)); ctx = ActionContext.of(stack.getContext()).bind(); } @@ -188,7 +186,7 @@ public class PrepareOperations { Object mappingAttr = request.getAttribute(STRUTS_ACTION_MAPPING_KEY); if (mappingAttr == null || forceLookup) { try { - mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); + mapping = dispatcher.getActionMapper().getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } else { diff --git a/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java b/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java index 0f9a1bdf9..418db330c 100644 --- a/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java +++ b/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java @@ -59,7 +59,7 @@ public class StrutsTestCaseHelper { public static void tearDown() { (new Dispatcher(null, null)).cleanUpAfterInit(); // Clear ContainerHolder - Dispatcher.setInstance(null); + Dispatcher.clearInstance(); ActionContext.clear(); } diff --git a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java index f633b7c01..888466720 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java @@ -251,12 +251,8 @@ public class DispatcherTest extends StrutsJUnit4InternalTestCase { @Test public void testObjectFactoryDestroy() { - dispatcher = spy(dispatcher); - Container spiedContainer = spy(container); - doReturn(spiedContainer).when(dispatcher).getContainer(); - InnerDestroyableObjectFactory destroyedObjectFactory = new InnerDestroyableObjectFactory(); - doReturn(destroyedObjectFactory).when(spiedContainer).getInstance(ObjectFactory.class); + dispatcher.setObjectFactory(destroyedObjectFactory); assertFalse(destroyedObjectFactory.destroyed); dispatcher.cleanup(); diff --git a/plugins/dwr/src/main/java/org/apache/struts2/validators/DWRValidator.java b/plugins/dwr/src/main/java/org/apache/struts2/validators/DWRValidator.java index 0809ea0e2..f189cd182 100644 --- a/plugins/dwr/src/main/java/org/apache/struts2/validators/DWRValidator.java +++ b/plugins/dwr/src/main/java/org/apache/struts2/validators/DWRValidator.java @@ -18,31 +18,26 @@ */ package org.apache.struts2.validators; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.struts2.ServletActionContext; +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ActionProxyFactory; +import com.opensymphony.xwork2.DefaultActionInvocation; +import com.opensymphony.xwork2.ValidationAwareSupport; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.interceptor.ValidationAware; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.struts2.dispatcher.ApplicationMap; import org.apache.struts2.dispatcher.Dispatcher; import org.apache.struts2.dispatcher.HttpParameters; import org.apache.struts2.dispatcher.RequestMap; import org.apache.struts2.dispatcher.SessionMap; - import org.directwebremoting.WebContextFactory; -import com.opensymphony.xwork2.Action; -import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.ActionProxyFactory; -import com.opensymphony.xwork2.DefaultActionInvocation; -import com.opensymphony.xwork2.interceptor.ValidationAware; -import com.opensymphony.xwork2.ValidationAwareSupport; -import com.opensymphony.xwork2.config.entities.ActionConfig; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; /** *

@@ -87,7 +82,7 @@ public class DWRValidator { res); try { - ActionProxyFactory actionProxyFactory = du.getContainer().getInstance(ActionProxyFactory.class); + ActionProxyFactory actionProxyFactory = du.getActionProxyFactory(); ActionProxy proxy = actionProxyFactory.createActionProxy(namespace, actionName, null, ctx, true, true); proxy.execute(); Object action = proxy.getAction(); diff --git a/plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java b/plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java index 394ef6042..29b563dac 100644 --- a/plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java +++ b/plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java @@ -1,674 +1,674 @@ -/* - * 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. - */ -package org.apache.struts2.portlet.dispatcher; - -import com.opensymphony.xwork2.ActionContext; -import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.ActionProxyFactory; -import com.opensymphony.xwork2.config.ConfigurationException; -import com.opensymphony.xwork2.inject.Container; -import org.apache.commons.lang3.LocaleUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.struts2.StrutsConstants; -import org.apache.struts2.StrutsException; -import org.apache.struts2.StrutsStatics; -import org.apache.struts2.dispatcher.ApplicationMap; -import org.apache.struts2.dispatcher.Dispatcher; -import org.apache.struts2.dispatcher.DispatcherConstants; -import org.apache.struts2.dispatcher.HttpParameters; -import org.apache.struts2.dispatcher.RequestMap; -import org.apache.struts2.dispatcher.SessionMap; -import org.apache.struts2.dispatcher.mapper.ActionMapper; -import org.apache.struts2.dispatcher.mapper.ActionMapping; -import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper; -import org.apache.struts2.portlet.PortletApplicationMap; -import org.apache.struts2.portlet.PortletConstants; -import org.apache.struts2.portlet.PortletPhase; -import org.apache.struts2.portlet.PortletRequestMap; -import org.apache.struts2.portlet.PortletSessionMap; -import org.apache.struts2.portlet.context.PortletActionContext; -import org.apache.struts2.portlet.servlet.PortletServletContext; -import org.apache.struts2.portlet.servlet.PortletServletRequest; -import org.apache.struts2.portlet.servlet.PortletServletResponse; -import org.apache.struts2.dispatcher.AttributeMap; - -import javax.portlet.ActionRequest; -import javax.portlet.ActionResponse; -import javax.portlet.GenericPortlet; -import javax.portlet.PortletConfig; -import javax.portlet.PortletException; -import javax.portlet.PortletMode; -import javax.portlet.PortletRequest; -import javax.portlet.PortletResponse; -import javax.portlet.RenderRequest; -import javax.portlet.RenderResponse; -import javax.portlet.WindowState; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import static org.apache.struts2.portlet.PortletConstants.ACTION_PARAM; -import static org.apache.struts2.portlet.PortletConstants.ACTION_RESET; -import static org.apache.struts2.portlet.PortletConstants.DEFAULT_ACTION_FOR_MODE; -import static org.apache.struts2.portlet.PortletConstants.DEFAULT_ACTION_NAME; -import static org.apache.struts2.portlet.PortletConstants.MODE_NAMESPACE_MAP; -import static org.apache.struts2.portlet.PortletConstants.MODE_PARAM; -import static org.apache.struts2.portlet.PortletConstants.PORTLET_CONFIG; -import static org.apache.struts2.portlet.PortletConstants.PORTLET_NAMESPACE; -import static org.apache.struts2.portlet.PortletConstants.REQUEST; -import static org.apache.struts2.portlet.PortletConstants.RESPONSE; - -/** - * - *

- * Struts JSR-168 portlet dispatcher. Similar to the WW2 Servlet dispatcher, - * but adjusted to a portal environment. The portlet is configured through the portlet.xml - * descriptor. Examples and descriptions follow below: - *

- * - * - * @author Nils-Helge Garli - * @author Rainer Hermanns - * - *

Init parameters

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
NameDescriptionDefault value
portletNamespaceThe namespace for the portlet in the xwork configuration. This - * namespace is prepended to all action lookups, and makes it possible to host multiple - * portlets in the same portlet application. If this parameter is set, the complete namespace - * will be /portletNamespace/modeNamespace/actionNameThe default namespace
viewNamespaceBase namespace in the xwork configuration for the view portlet - * modeThe default namespace
editNamespaceBase namespace in the xwork configuration for the edit portlet - * modeThe default namespace
helpNamespaceBase namespace in the xwork configuration for the help portlet - * modeThe default namespace
defaultViewActionDefault action to invoke in the view portlet mode if no action is - * specifieddefault
defaultEditActionDefault action to invoke in the edit portlet mode if no action is - * specifieddefault
defaultHelpActionDefault action to invoke in the help portlet mode if no action is - * specifieddefault
- * - *

Example:

- *
- * 
- *
- * <init-param>
- *     <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
- *     <name>viewNamespace</name>
- *     <value>/view</value>
- * </init-param>
- * <init-param>
- *    <!-- The default action to invoke in view mode -->
- *    <name>defaultViewAction</name>
- *    <value>index</value>
- * </init-param>
- * <init-param>
- *     <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
- *     <name>editNamespace</name>
- *     <value>/edit</value>
- * </init-param>
- * <init-param>
- *     <!-- The default action to invoke in view mode -->
- *     <name>defaultEditAction</name>
- *     <value>index</value>
- * </init-param>
- * <init-param>
- *     <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
- *     <name>helpNamespace</name>
- *     <value>/help</value>
- * </init-param>
- * <init-param>
- *     <!-- The default action to invoke in view mode -->
- *     <name>defaultHelpAction</name>
- *     <value>index</value>
- * </init-param>
- *
- * 
- * 
- */ -public class Jsr168Dispatcher extends GenericPortlet implements StrutsStatics { - - private static final Logger LOG = LogManager.getLogger(Jsr168Dispatcher.class); - - protected String portletNamespace = null; - - private ActionProxyFactory factory = null; - private Map modeMap = new HashMap(3); - private Map actionMap = new HashMap(3); - private Dispatcher dispatcherUtils; - private ActionMapper actionMapper; - private Container container; - private ServletContext servletContext; - - /** - * Initialize the portlet with the init parameters from portlet.xml - * - * @param cfg portlet configuration - * @throws PortletException in case of errors - */ - public void init(PortletConfig cfg) throws PortletException { - super.init(cfg); - LOG.debug("Initializing portlet {}", getPortletName()); - - Map params = new HashMap(); - for (Enumeration e = cfg.getInitParameterNames(); e.hasMoreElements(); ) { - String name = (String) e.nextElement(); - String value = cfg.getInitParameter(name); - params.put(name, value); - } - - servletContext = new PortletServletContext(cfg.getPortletContext()); - dispatcherUtils = new Dispatcher(servletContext, params); - dispatcherUtils.init(); - - // For testability - if (factory == null) { - factory = dispatcherUtils.getContainer().getInstance(ActionProxyFactory.class); - } - portletNamespace = cfg.getInitParameter("portletNamespace"); - LOG.debug("PortletNamespace: {}", portletNamespace); - - parseModeConfig(actionMap, cfg, PortletMode.VIEW, "viewNamespace", - "defaultViewAction"); - parseModeConfig(actionMap, cfg, PortletMode.EDIT, "editNamespace", - "defaultEditAction"); - parseModeConfig(actionMap, cfg, PortletMode.HELP, "helpNamespace", - "defaultHelpAction"); - parseModeConfig(actionMap, cfg, new PortletMode("config"), "configNamespace", - "defaultConfigAction"); - parseModeConfig(actionMap, cfg, new PortletMode("about"), "aboutNamespace", - "defaultAboutAction"); - parseModeConfig(actionMap, cfg, new PortletMode("print"), "printNamespace", - "defaultPrintAction"); - parseModeConfig(actionMap, cfg, new PortletMode("preview"), "previewNamespace", - "defaultPreviewAction"); - parseModeConfig(actionMap, cfg, new PortletMode("edit_defaults"), - "editDefaultsNamespace", "defaultEditDefaultsAction"); - if (StringUtils.isEmpty(portletNamespace)) { - portletNamespace = ""; - } - - container = dispatcherUtils.getContainer(); - actionMapper = container.getInstance(ActionMapper.class); - } - - /** - * Parse the mode to namespace mappings configured in portlet.xml - * - * @param actionMap The map with mode <-> default action mapping. - * @param portletConfig The PortletConfig. - * @param portletMode The PortletMode. - * @param nameSpaceParam Name of the init parameter where the namespace for the mode - * is configured. - * @param defaultActionParam Name of the init parameter where the default action to - * execute for the mode is configured. - */ - void parseModeConfig(Map actionMap, PortletConfig portletConfig, - PortletMode portletMode, String nameSpaceParam, - String defaultActionParam) { - String namespace = portletConfig.getInitParameter(nameSpaceParam); - if (StringUtils.isEmpty(namespace)) { - namespace = ""; - } - modeMap.put(portletMode, namespace); - String defaultAction = portletConfig.getInitParameter(defaultActionParam); - String method = null; - if (StringUtils.isEmpty(defaultAction)) { - defaultAction = DEFAULT_ACTION_NAME; - } - if (defaultAction.indexOf('!') >= 0) { - method = defaultAction.substring(defaultAction.indexOf('!') + 1); - defaultAction = defaultAction.substring(0, defaultAction.indexOf('!')); - } - StringBuilder fullPath = new StringBuilder(); - if (StringUtils.isNotEmpty(portletNamespace)) { - fullPath.append(portletNamespace); - } - if (StringUtils.isNotEmpty(namespace)) { - fullPath.append(namespace).append("/"); - } else { - fullPath.append("/"); - } - fullPath.append(defaultAction); - ActionMapping mapping = new ActionMapping(); - mapping.setName(getActionName(fullPath.toString())); - mapping.setNamespace(getNamespace(fullPath.toString())); - if (method != null) { - mapping.setMethod(method); - } - actionMap.put(portletMode, mapping); - } - - /** - * Service an action from the event phase. - * - * @param request action request - * @param response action response - * @throws PortletException in case of errors - * @throws IOException in case of IO errors - * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest, - * javax.portlet.ActionResponse) - */ - public void processAction(ActionRequest request, ActionResponse response) - throws PortletException, IOException { - if (LOG.isDebugEnabled()) { - LOG.debug("Entering processAction in mode ", request.getPortletMode().toString()); - } - resetActionContext(); - try { - serviceAction(request, response, getRequestMap(request), getParameterMap(request), - getSessionMap(request), getApplicationMap(), - portletNamespace, PortletPhase.ACTION_PHASE); - if (LOG.isDebugEnabled()) LOG.debug("Leaving processAction"); - } finally { - ActionContext.clear(); - } - } - - /** - * Service an action from the render phase. - * - * @param request render request - * @param response render response - * @throws PortletException in case of errors - * @throws IOException in case of IO errors - * @see javax.portlet.Portlet#render(javax.portlet.RenderRequest, - * javax.portlet.RenderResponse) - */ - public void render(RenderRequest request, RenderResponse response) - throws PortletException, IOException { - - if (LOG.isDebugEnabled()) { - LOG.debug("Entering render in mode ", request.getPortletMode().toString()); - } - resetActionContext(); - response.setTitle(getTitle(request)); - if (!request.getWindowState().equals(WindowState.MINIMIZED)) { - try { - // Check to see if an event set the render to be included directly - serviceAction(request, response, getRequestMap(request), getParameterMap(request), - getSessionMap(request), getApplicationMap(), - portletNamespace, PortletPhase.RENDER_PHASE); - if (LOG.isDebugEnabled()) LOG.debug("Leaving render"); - } finally { - resetActionContext(); - } - } - } - - /** - * Reset the action context. - */ - void resetActionContext() { - ActionContext.clear(); - } - - /** - * Merges all application and portlet attributes into a single - * HashMap to represent the entire Action context. - * - * @param requestMap a Map of all request attributes. - * @param parameterMap a Map of all request parameters. - * @param sessionMap a Map of all session attributes. - * @param applicationMap a Map of all servlet context attributes. - * @param request the PortletRequest object. - * @param response the PortletResponse object. - * @param servletRequest the HttpServletRequest object. - * @param servletResponse the HttpServletResponse object. - * @param servletContext the ServletContext object. - * @param portletConfig the PortletConfig object. - * @param phase The portlet phase (render or action, see - * {@link PortletConstants}) - * @return a HashMap representing the Action context. - * @throws IOException in case of IO errors - */ - public Map createContextMap(Map requestMap, Map parameterMap, - Map sessionMap, Map applicationMap, - PortletRequest request, PortletResponse response, HttpServletRequest servletRequest, - HttpServletResponse servletResponse, ServletContext servletContext, - PortletConfig portletConfig, PortletPhase phase) throws IOException { - - // TODO Must put http request/response objects into map for use with - container.inject(servletRequest); - - // ServletActionContext - Map extraContext = ActionContext.of(new HashMap<>()) - .withServletRequest(servletRequest) - .withServletResponse(servletResponse) - .withServletContext(servletContext) - .withParameters(HttpParameters.create(parameterMap).build()) - .withSession(sessionMap) - .withApplication(applicationMap) - .withLocale(getLocale(request)) - .with(StrutsStatics.STRUTS_PORTLET_CONTEXT, getPortletContext()) - .with(REQUEST, request) - .with(RESPONSE, response) - .with(PORTLET_CONFIG, portletConfig) - .with(PORTLET_NAMESPACE, portletNamespace) - .with(DEFAULT_ACTION_FOR_MODE, actionMap.get(request.getPortletMode())) - // helpers to get access to request/session/application scope - .with(DispatcherConstants.REQUEST, requestMap) - .with(DispatcherConstants.SESSION, sessionMap) - .with(DispatcherConstants.APPLICATION, applicationMap) - .with(DispatcherConstants.PARAMETERS, parameterMap) - .with(MODE_NAMESPACE_MAP, modeMap) - .with(PortletConstants.DEFAULT_ACTION_MAP, actionMap) - .with(PortletConstants.PHASE, phase) - .getContextMap(); - - AttributeMap attrMap = new AttributeMap(extraContext); - extraContext.put("attr", attrMap); - - return extraContext; - } - - protected Locale getLocale(PortletRequest request) { - String defaultLocale = container.getInstance(String.class, StrutsConstants.STRUTS_LOCALE); - Locale locale; - if (defaultLocale != null) { - try { - locale = LocaleUtils.toLocale(defaultLocale); - } catch (IllegalArgumentException e) { - LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, defaulting to request locale [{}]", - defaultLocale, request.getLocale()), e); - locale = request.getLocale(); - } - } else { - locale = request.getLocale(); - } - return locale; - } - - /** - * Loads the action and executes it. This method first creates the action - * context from the given parameters then loads an ActionProxy - * from the given action name and namespace. After that, the action is - * executed and output channels throught the response object. - * - * @param request the HttpServletRequest object. - * @param response the HttpServletResponse object. - * @param requestMap a Map of request attributes. - * @param parameterMap a Map of request parameters. - * @param sessionMap a Map of all session attributes. - * @param applicationMap a Map of all application attributes. - * @param portletNamespace the namespace or context of the action. - * @param phase The portlet phase (render or action, see {@link PortletConstants}) - * @throws PortletException in case of errors - */ - public void serviceAction(PortletRequest request, PortletResponse response, Map requestMap, Map parameterMap, - Map sessionMap, Map applicationMap, String portletNamespace, - PortletPhase phase) throws PortletException { - if (LOG.isDebugEnabled()) LOG.debug("serviceAction"); - Dispatcher.setInstance(dispatcherUtils); - String actionName = null; - String namespace; - try { - HttpServletRequest servletRequest = new PortletServletRequest(request, getPortletContext()); - HttpServletResponse servletResponse = createPortletServletResponse(response); - if (phase.isAction()) { - servletRequest = dispatcherUtils.wrapRequest(servletRequest); - if (servletRequest instanceof MultiPartRequestWrapper) { - // Multipart request. Request parameters are encoded in the multipart data, - // so we need to manually add them to the parameter map. - parameterMap.putAll(servletRequest.getParameterMap()); - } - } - container.inject(servletRequest); - ActionMapping mapping = getActionMapping(request, servletRequest); - actionName = mapping.getName(); - if ("renderDirect".equals(actionName)) { - namespace = request.getParameter(PortletConstants.RENDER_DIRECT_NAMESPACE); - } else { - namespace = mapping.getNamespace(); - } - Map extraContext = createContextMap(requestMap, parameterMap, - sessionMap, applicationMap, request, response, servletRequest, servletResponse, - servletContext, getPortletConfig(), phase); - extraContext.put(PortletConstants.ACTION_MAPPING, mapping); - if (LOG.isDebugEnabled()) { - LOG.debug("Creating action proxy for name = " + actionName + ", namespace = " + namespace); - } - ActionProxy proxy = factory.createActionProxy(namespace, actionName, mapping.getMethod(), extraContext); - request.setAttribute("struts.valueStack", proxy.getInvocation().getStack()); - proxy.execute(); - } catch (ConfigurationException e) { - if (LOG.isErrorEnabled()) { - LOG.error("Could not find action", e); - } - throw new PortletException("Could not find action " + actionName, e); - } catch (Exception e) { - if (LOG.isErrorEnabled()) { - LOG.error("Could not execute action", e); - } - throw new PortletException("Error executing action " + actionName, e); - } finally { - Dispatcher.setInstance(null); - } - } - - /** - * Returns a Map of all application attributes. Copies all attributes from - * the {@link PortletActionContext}into an {@link ApplicationMap}. - * - * @return a Map of all application attributes. - */ - protected Map getApplicationMap() { - return new PortletApplicationMap(getPortletContext()); - } - - /** - * Gets the namespace of the action from the request. The namespace is the - * same as the portlet mode. E.g, view mode is mapped to namespace - * view, and edit mode is mapped to the namespace - * edit - * - * @param portletRequest the PortletRequest object. - * @param servletRequest the ServletRequest to use - * @return the namespace of the action. - */ - protected ActionMapping getActionMapping(final PortletRequest portletRequest, final HttpServletRequest servletRequest) { - ActionMapping mapping; - String actionPath = getDefaultActionPath(portletRequest); - if (resetAction(portletRequest)) { - mapping = actionMap.get(portletRequest.getPortletMode()); - } else { - actionPath = servletRequest.getParameter(ACTION_PARAM); - if (StringUtils.isEmpty(actionPath)) { - mapping = actionMap.get(portletRequest.getPortletMode()); - } else { - - // Use the usual action mapper, but it is expecting an action extension - // on the uri, so we add the default one, which should be ok as the - // portlet is a portlet first, a servlet second - mapping = actionMapper.getMapping(servletRequest, dispatcherUtils.getConfigurationManager()); - } - } - - if (mapping == null) { - throw new StrutsException("Unable to locate action mapping for request, probably due to an invalid action path: " + actionPath); - } - return mapping; - } - - protected String getDefaultActionPath(PortletRequest portletRequest) { - return null; - } - - /** - * Get the namespace part of the action path. - * - * @param actionPath Full path to action - * @return The namespace part. - */ - String getNamespace(String actionPath) { - int idx = actionPath.lastIndexOf('/'); - String namespace = ""; - if (idx >= 0) { - namespace = actionPath.substring(0, idx); - } - return namespace; - } - - /** - * Get the action name part of the action path. - * - * @param actionPath Full path to action - * @return The action name. - */ - String getActionName(String actionPath) { - int idx = actionPath.lastIndexOf('/'); - String action = actionPath; - if (idx >= 0) { - action = actionPath.substring(idx + 1); - } - return action; - } - - /** - * Returns a Map of all request parameters. This implementation just calls - * {@link PortletRequest#getParameterMap()}. - * - * @param request the PortletRequest object. - * @return a Map of all request parameters. - * @throws IOException if an exception occurs while retrieving the parameter - * map. - */ - protected Map getParameterMap(PortletRequest request) throws IOException { - return new HashMap(request.getParameterMap()); - } - - /** - * Returns a Map of all request attributes. The default implementation is to - * wrap the request in a {@link RequestMap}. Override this method to - * customize how request attributes are mapped. - * - * @param request the PortletRequest object. - * @return a Map of all request attributes. - */ - protected Map getRequestMap(PortletRequest request) { - return new PortletRequestMap(request); - } - - /** - * Returns a Map of all session attributes. The default implementation is to - * wrap the reqeust in a {@link SessionMap}. Override this method to - * customize how session attributes are mapped. - * - * @param request the PortletRequest object. - * @return a Map of all session attributes. - */ - protected Map getSessionMap(PortletRequest request) { - return new PortletSessionMap(request); - } - - /** - * Convenience method to ease testing. - * - * @param factory action proxy factory - */ - protected void setActionProxyFactory(ActionProxyFactory factory) { - this.factory = factory; - } - - /** - * Check to see if the action parameter is valid for the current portlet mode. If the portlet - * mode has been changed with the portal widgets, the action name is invalid, since the - * action name belongs to the previous executing portlet mode. If this method evaluates to - * true the default<Mode>Action is used instead. - * - * @param request The portlet request. - * @return true if the action should be reset. - */ - private boolean resetAction(PortletRequest request) { - boolean reset = false; - Map paramMap = request.getParameterMap(); - String[] modeParam = (String[]) paramMap.get(MODE_PARAM); - if (modeParam != null && modeParam.length == 1) { - String originatingMode = modeParam[0]; - String currentMode = request.getPortletMode().toString(); - if (!currentMode.equals(originatingMode)) { - reset = true; - } - } - if (reset) { - request.setAttribute(ACTION_RESET, Boolean.TRUE); - } else { - request.setAttribute(ACTION_RESET, Boolean.FALSE); - } - return reset; - } - - public void destroy() { - if (dispatcherUtils != null) { - dispatcherUtils.cleanup(); - } else { - if (LOG.isWarnEnabled()) { - LOG.warn("Something is seriously wrong, DispatcherUtil is not initialized (null) "); - } - } - } - - /** - * @param actionMapper the actionMapper to set - */ - public void setActionMapper(ActionMapper actionMapper) { - this.actionMapper = actionMapper; - } - - /** - * Method to create a PortletServletResponse matching the used Portlet API, to be overridden for JSR286 Dispatcher. - * - * @param response The Response used for building the wrapper. - * @return The wrapper response for Servlet bound usage. - */ - protected PortletServletResponse createPortletServletResponse(PortletResponse response) { - return new PortletServletResponse(response); - } - -} +/* + * 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. + */ +package org.apache.struts2.portlet.dispatcher; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ActionProxyFactory; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.inject.Container; +import org.apache.commons.lang3.LocaleUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.StrutsException; +import org.apache.struts2.StrutsStatics; +import org.apache.struts2.dispatcher.ApplicationMap; +import org.apache.struts2.dispatcher.AttributeMap; +import org.apache.struts2.dispatcher.Dispatcher; +import org.apache.struts2.dispatcher.DispatcherConstants; +import org.apache.struts2.dispatcher.HttpParameters; +import org.apache.struts2.dispatcher.RequestMap; +import org.apache.struts2.dispatcher.SessionMap; +import org.apache.struts2.dispatcher.mapper.ActionMapper; +import org.apache.struts2.dispatcher.mapper.ActionMapping; +import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper; +import org.apache.struts2.portlet.PortletApplicationMap; +import org.apache.struts2.portlet.PortletConstants; +import org.apache.struts2.portlet.PortletPhase; +import org.apache.struts2.portlet.PortletRequestMap; +import org.apache.struts2.portlet.PortletSessionMap; +import org.apache.struts2.portlet.context.PortletActionContext; +import org.apache.struts2.portlet.servlet.PortletServletContext; +import org.apache.struts2.portlet.servlet.PortletServletRequest; +import org.apache.struts2.portlet.servlet.PortletServletResponse; + +import javax.portlet.ActionRequest; +import javax.portlet.ActionResponse; +import javax.portlet.GenericPortlet; +import javax.portlet.PortletConfig; +import javax.portlet.PortletException; +import javax.portlet.PortletMode; +import javax.portlet.PortletRequest; +import javax.portlet.PortletResponse; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; +import javax.portlet.WindowState; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import static org.apache.struts2.portlet.PortletConstants.ACTION_PARAM; +import static org.apache.struts2.portlet.PortletConstants.ACTION_RESET; +import static org.apache.struts2.portlet.PortletConstants.DEFAULT_ACTION_FOR_MODE; +import static org.apache.struts2.portlet.PortletConstants.DEFAULT_ACTION_NAME; +import static org.apache.struts2.portlet.PortletConstants.MODE_NAMESPACE_MAP; +import static org.apache.struts2.portlet.PortletConstants.MODE_PARAM; +import static org.apache.struts2.portlet.PortletConstants.PORTLET_CONFIG; +import static org.apache.struts2.portlet.PortletConstants.PORTLET_NAMESPACE; +import static org.apache.struts2.portlet.PortletConstants.REQUEST; +import static org.apache.struts2.portlet.PortletConstants.RESPONSE; + +/** + * + *

+ * Struts JSR-168 portlet dispatcher. Similar to the WW2 Servlet dispatcher, + * but adjusted to a portal environment. The portlet is configured through the portlet.xml + * descriptor. Examples and descriptions follow below: + *

+ * + * + * @author Nils-Helge Garli + * @author Rainer Hermanns + * + *

Init parameters

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameDescriptionDefault value
portletNamespaceThe namespace for the portlet in the xwork configuration. This + * namespace is prepended to all action lookups, and makes it possible to host multiple + * portlets in the same portlet application. If this parameter is set, the complete namespace + * will be /portletNamespace/modeNamespace/actionNameThe default namespace
viewNamespaceBase namespace in the xwork configuration for the view portlet + * modeThe default namespace
editNamespaceBase namespace in the xwork configuration for the edit portlet + * modeThe default namespace
helpNamespaceBase namespace in the xwork configuration for the help portlet + * modeThe default namespace
defaultViewActionDefault action to invoke in the view portlet mode if no action is + * specifieddefault
defaultEditActionDefault action to invoke in the edit portlet mode if no action is + * specifieddefault
defaultHelpActionDefault action to invoke in the help portlet mode if no action is + * specifieddefault
+ * + *

Example:

+ *
+ * 
+ *
+ * <init-param>
+ *     <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
+ *     <name>viewNamespace</name>
+ *     <value>/view</value>
+ * </init-param>
+ * <init-param>
+ *    <!-- The default action to invoke in view mode -->
+ *    <name>defaultViewAction</name>
+ *    <value>index</value>
+ * </init-param>
+ * <init-param>
+ *     <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
+ *     <name>editNamespace</name>
+ *     <value>/edit</value>
+ * </init-param>
+ * <init-param>
+ *     <!-- The default action to invoke in view mode -->
+ *     <name>defaultEditAction</name>
+ *     <value>index</value>
+ * </init-param>
+ * <init-param>
+ *     <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
+ *     <name>helpNamespace</name>
+ *     <value>/help</value>
+ * </init-param>
+ * <init-param>
+ *     <!-- The default action to invoke in view mode -->
+ *     <name>defaultHelpAction</name>
+ *     <value>index</value>
+ * </init-param>
+ *
+ * 
+ * 
+ */ +public class Jsr168Dispatcher extends GenericPortlet implements StrutsStatics { + + private static final Logger LOG = LogManager.getLogger(Jsr168Dispatcher.class); + + protected String portletNamespace = null; + + private ActionProxyFactory factory = null; + private Map modeMap = new HashMap(3); + private Map actionMap = new HashMap(3); + private Dispatcher dispatcherUtils; + private ActionMapper actionMapper; + private Container container; + private ServletContext servletContext; + + /** + * Initialize the portlet with the init parameters from portlet.xml + * + * @param cfg portlet configuration + * @throws PortletException in case of errors + */ + public void init(PortletConfig cfg) throws PortletException { + super.init(cfg); + LOG.debug("Initializing portlet {}", getPortletName()); + + Map params = new HashMap(); + for (Enumeration e = cfg.getInitParameterNames(); e.hasMoreElements(); ) { + String name = (String) e.nextElement(); + String value = cfg.getInitParameter(name); + params.put(name, value); + } + + servletContext = new PortletServletContext(cfg.getPortletContext()); + dispatcherUtils = new Dispatcher(servletContext, params); + dispatcherUtils.init(); + + // For testability + if (factory == null) { + factory = dispatcherUtils.getActionProxyFactory(); + } + portletNamespace = cfg.getInitParameter("portletNamespace"); + LOG.debug("PortletNamespace: {}", portletNamespace); + + parseModeConfig(actionMap, cfg, PortletMode.VIEW, "viewNamespace", + "defaultViewAction"); + parseModeConfig(actionMap, cfg, PortletMode.EDIT, "editNamespace", + "defaultEditAction"); + parseModeConfig(actionMap, cfg, PortletMode.HELP, "helpNamespace", + "defaultHelpAction"); + parseModeConfig(actionMap, cfg, new PortletMode("config"), "configNamespace", + "defaultConfigAction"); + parseModeConfig(actionMap, cfg, new PortletMode("about"), "aboutNamespace", + "defaultAboutAction"); + parseModeConfig(actionMap, cfg, new PortletMode("print"), "printNamespace", + "defaultPrintAction"); + parseModeConfig(actionMap, cfg, new PortletMode("preview"), "previewNamespace", + "defaultPreviewAction"); + parseModeConfig(actionMap, cfg, new PortletMode("edit_defaults"), + "editDefaultsNamespace", "defaultEditDefaultsAction"); + if (StringUtils.isEmpty(portletNamespace)) { + portletNamespace = ""; + } + + container = dispatcherUtils.getContainer(); + actionMapper = container.getInstance(ActionMapper.class); + } + + /** + * Parse the mode to namespace mappings configured in portlet.xml + * + * @param actionMap The map with mode <-> default action mapping. + * @param portletConfig The PortletConfig. + * @param portletMode The PortletMode. + * @param nameSpaceParam Name of the init parameter where the namespace for the mode + * is configured. + * @param defaultActionParam Name of the init parameter where the default action to + * execute for the mode is configured. + */ + void parseModeConfig(Map actionMap, PortletConfig portletConfig, + PortletMode portletMode, String nameSpaceParam, + String defaultActionParam) { + String namespace = portletConfig.getInitParameter(nameSpaceParam); + if (StringUtils.isEmpty(namespace)) { + namespace = ""; + } + modeMap.put(portletMode, namespace); + String defaultAction = portletConfig.getInitParameter(defaultActionParam); + String method = null; + if (StringUtils.isEmpty(defaultAction)) { + defaultAction = DEFAULT_ACTION_NAME; + } + if (defaultAction.indexOf('!') >= 0) { + method = defaultAction.substring(defaultAction.indexOf('!') + 1); + defaultAction = defaultAction.substring(0, defaultAction.indexOf('!')); + } + StringBuilder fullPath = new StringBuilder(); + if (StringUtils.isNotEmpty(portletNamespace)) { + fullPath.append(portletNamespace); + } + if (StringUtils.isNotEmpty(namespace)) { + fullPath.append(namespace).append("/"); + } else { + fullPath.append("/"); + } + fullPath.append(defaultAction); + ActionMapping mapping = new ActionMapping(); + mapping.setName(getActionName(fullPath.toString())); + mapping.setNamespace(getNamespace(fullPath.toString())); + if (method != null) { + mapping.setMethod(method); + } + actionMap.put(portletMode, mapping); + } + + /** + * Service an action from the event phase. + * + * @param request action request + * @param response action response + * @throws PortletException in case of errors + * @throws IOException in case of IO errors + * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest, + * javax.portlet.ActionResponse) + */ + public void processAction(ActionRequest request, ActionResponse response) + throws PortletException, IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("Entering processAction in mode ", request.getPortletMode().toString()); + } + resetActionContext(); + try { + serviceAction(request, response, getRequestMap(request), getParameterMap(request), + getSessionMap(request), getApplicationMap(), + portletNamespace, PortletPhase.ACTION_PHASE); + if (LOG.isDebugEnabled()) LOG.debug("Leaving processAction"); + } finally { + ActionContext.clear(); + } + } + + /** + * Service an action from the render phase. + * + * @param request render request + * @param response render response + * @throws PortletException in case of errors + * @throws IOException in case of IO errors + * @see javax.portlet.Portlet#render(javax.portlet.RenderRequest, + * javax.portlet.RenderResponse) + */ + public void render(RenderRequest request, RenderResponse response) + throws PortletException, IOException { + + if (LOG.isDebugEnabled()) { + LOG.debug("Entering render in mode ", request.getPortletMode().toString()); + } + resetActionContext(); + response.setTitle(getTitle(request)); + if (!request.getWindowState().equals(WindowState.MINIMIZED)) { + try { + // Check to see if an event set the render to be included directly + serviceAction(request, response, getRequestMap(request), getParameterMap(request), + getSessionMap(request), getApplicationMap(), + portletNamespace, PortletPhase.RENDER_PHASE); + if (LOG.isDebugEnabled()) LOG.debug("Leaving render"); + } finally { + resetActionContext(); + } + } + } + + /** + * Reset the action context. + */ + void resetActionContext() { + ActionContext.clear(); + } + + /** + * Merges all application and portlet attributes into a single + * HashMap to represent the entire Action context. + * + * @param requestMap a Map of all request attributes. + * @param parameterMap a Map of all request parameters. + * @param sessionMap a Map of all session attributes. + * @param applicationMap a Map of all servlet context attributes. + * @param request the PortletRequest object. + * @param response the PortletResponse object. + * @param servletRequest the HttpServletRequest object. + * @param servletResponse the HttpServletResponse object. + * @param servletContext the ServletContext object. + * @param portletConfig the PortletConfig object. + * @param phase The portlet phase (render or action, see + * {@link PortletConstants}) + * @return a HashMap representing the Action context. + * @throws IOException in case of IO errors + */ + public Map createContextMap(Map requestMap, Map parameterMap, + Map sessionMap, Map applicationMap, + PortletRequest request, PortletResponse response, HttpServletRequest servletRequest, + HttpServletResponse servletResponse, ServletContext servletContext, + PortletConfig portletConfig, PortletPhase phase) throws IOException { + + // TODO Must put http request/response objects into map for use with + container.inject(servletRequest); + + // ServletActionContext + Map extraContext = ActionContext.of(new HashMap<>()) + .withServletRequest(servletRequest) + .withServletResponse(servletResponse) + .withServletContext(servletContext) + .withParameters(HttpParameters.create(parameterMap).build()) + .withSession(sessionMap) + .withApplication(applicationMap) + .withLocale(getLocale(request)) + .with(StrutsStatics.STRUTS_PORTLET_CONTEXT, getPortletContext()) + .with(REQUEST, request) + .with(RESPONSE, response) + .with(PORTLET_CONFIG, portletConfig) + .with(PORTLET_NAMESPACE, portletNamespace) + .with(DEFAULT_ACTION_FOR_MODE, actionMap.get(request.getPortletMode())) + // helpers to get access to request/session/application scope + .with(DispatcherConstants.REQUEST, requestMap) + .with(DispatcherConstants.SESSION, sessionMap) + .with(DispatcherConstants.APPLICATION, applicationMap) + .with(DispatcherConstants.PARAMETERS, parameterMap) + .with(MODE_NAMESPACE_MAP, modeMap) + .with(PortletConstants.DEFAULT_ACTION_MAP, actionMap) + .with(PortletConstants.PHASE, phase) + .getContextMap(); + + AttributeMap attrMap = new AttributeMap(extraContext); + extraContext.put("attr", attrMap); + + return extraContext; + } + + protected Locale getLocale(PortletRequest request) { + String defaultLocale = container.getInstance(String.class, StrutsConstants.STRUTS_LOCALE); + Locale locale; + if (defaultLocale != null) { + try { + locale = LocaleUtils.toLocale(defaultLocale); + } catch (IllegalArgumentException e) { + LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, defaulting to request locale [{}]", + defaultLocale, request.getLocale()), e); + locale = request.getLocale(); + } + } else { + locale = request.getLocale(); + } + return locale; + } + + /** + * Loads the action and executes it. This method first creates the action + * context from the given parameters then loads an ActionProxy + * from the given action name and namespace. After that, the action is + * executed and output channels throught the response object. + * + * @param request the HttpServletRequest object. + * @param response the HttpServletResponse object. + * @param requestMap a Map of request attributes. + * @param parameterMap a Map of request parameters. + * @param sessionMap a Map of all session attributes. + * @param applicationMap a Map of all application attributes. + * @param portletNamespace the namespace or context of the action. + * @param phase The portlet phase (render or action, see {@link PortletConstants}) + * @throws PortletException in case of errors + */ + public void serviceAction(PortletRequest request, PortletResponse response, Map requestMap, Map parameterMap, + Map sessionMap, Map applicationMap, String portletNamespace, + PortletPhase phase) throws PortletException { + if (LOG.isDebugEnabled()) LOG.debug("serviceAction"); + Dispatcher.setInstance(dispatcherUtils); + String actionName = null; + String namespace; + try { + HttpServletRequest servletRequest = new PortletServletRequest(request, getPortletContext()); + HttpServletResponse servletResponse = createPortletServletResponse(response); + if (phase.isAction()) { + servletRequest = dispatcherUtils.wrapRequest(servletRequest); + if (servletRequest instanceof MultiPartRequestWrapper) { + // Multipart request. Request parameters are encoded in the multipart data, + // so we need to manually add them to the parameter map. + parameterMap.putAll(servletRequest.getParameterMap()); + } + } + container.inject(servletRequest); + ActionMapping mapping = getActionMapping(request, servletRequest); + actionName = mapping.getName(); + if ("renderDirect".equals(actionName)) { + namespace = request.getParameter(PortletConstants.RENDER_DIRECT_NAMESPACE); + } else { + namespace = mapping.getNamespace(); + } + Map extraContext = createContextMap(requestMap, parameterMap, + sessionMap, applicationMap, request, response, servletRequest, servletResponse, + servletContext, getPortletConfig(), phase); + extraContext.put(PortletConstants.ACTION_MAPPING, mapping); + if (LOG.isDebugEnabled()) { + LOG.debug("Creating action proxy for name = " + actionName + ", namespace = " + namespace); + } + ActionProxy proxy = factory.createActionProxy(namespace, actionName, mapping.getMethod(), extraContext); + request.setAttribute("struts.valueStack", proxy.getInvocation().getStack()); + proxy.execute(); + } catch (ConfigurationException e) { + if (LOG.isErrorEnabled()) { + LOG.error("Could not find action", e); + } + throw new PortletException("Could not find action " + actionName, e); + } catch (Exception e) { + if (LOG.isErrorEnabled()) { + LOG.error("Could not execute action", e); + } + throw new PortletException("Error executing action " + actionName, e); + } finally { + Dispatcher.clearInstance(); + } + } + + /** + * Returns a Map of all application attributes. Copies all attributes from + * the {@link PortletActionContext}into an {@link ApplicationMap}. + * + * @return a Map of all application attributes. + */ + protected Map getApplicationMap() { + return new PortletApplicationMap(getPortletContext()); + } + + /** + * Gets the namespace of the action from the request. The namespace is the + * same as the portlet mode. E.g, view mode is mapped to namespace + * view, and edit mode is mapped to the namespace + * edit + * + * @param portletRequest the PortletRequest object. + * @param servletRequest the ServletRequest to use + * @return the namespace of the action. + */ + protected ActionMapping getActionMapping(final PortletRequest portletRequest, final HttpServletRequest servletRequest) { + ActionMapping mapping; + String actionPath = getDefaultActionPath(portletRequest); + if (resetAction(portletRequest)) { + mapping = actionMap.get(portletRequest.getPortletMode()); + } else { + actionPath = servletRequest.getParameter(ACTION_PARAM); + if (StringUtils.isEmpty(actionPath)) { + mapping = actionMap.get(portletRequest.getPortletMode()); + } else { + + // Use the usual action mapper, but it is expecting an action extension + // on the uri, so we add the default one, which should be ok as the + // portlet is a portlet first, a servlet second + mapping = actionMapper.getMapping(servletRequest, dispatcherUtils.getConfigurationManager()); + } + } + + if (mapping == null) { + throw new StrutsException("Unable to locate action mapping for request, probably due to an invalid action path: " + actionPath); + } + return mapping; + } + + protected String getDefaultActionPath(PortletRequest portletRequest) { + return null; + } + + /** + * Get the namespace part of the action path. + * + * @param actionPath Full path to action + * @return The namespace part. + */ + String getNamespace(String actionPath) { + int idx = actionPath.lastIndexOf('/'); + String namespace = ""; + if (idx >= 0) { + namespace = actionPath.substring(0, idx); + } + return namespace; + } + + /** + * Get the action name part of the action path. + * + * @param actionPath Full path to action + * @return The action name. + */ + String getActionName(String actionPath) { + int idx = actionPath.lastIndexOf('/'); + String action = actionPath; + if (idx >= 0) { + action = actionPath.substring(idx + 1); + } + return action; + } + + /** + * Returns a Map of all request parameters. This implementation just calls + * {@link PortletRequest#getParameterMap()}. + * + * @param request the PortletRequest object. + * @return a Map of all request parameters. + * @throws IOException if an exception occurs while retrieving the parameter + * map. + */ + protected Map getParameterMap(PortletRequest request) throws IOException { + return new HashMap(request.getParameterMap()); + } + + /** + * Returns a Map of all request attributes. The default implementation is to + * wrap the request in a {@link RequestMap}. Override this method to + * customize how request attributes are mapped. + * + * @param request the PortletRequest object. + * @return a Map of all request attributes. + */ + protected Map getRequestMap(PortletRequest request) { + return new PortletRequestMap(request); + } + + /** + * Returns a Map of all session attributes. The default implementation is to + * wrap the reqeust in a {@link SessionMap}. Override this method to + * customize how session attributes are mapped. + * + * @param request the PortletRequest object. + * @return a Map of all session attributes. + */ + protected Map getSessionMap(PortletRequest request) { + return new PortletSessionMap(request); + } + + /** + * Convenience method to ease testing. + * + * @param factory action proxy factory + */ + protected void setActionProxyFactory(ActionProxyFactory factory) { + this.factory = factory; + } + + /** + * Check to see if the action parameter is valid for the current portlet mode. If the portlet + * mode has been changed with the portal widgets, the action name is invalid, since the + * action name belongs to the previous executing portlet mode. If this method evaluates to + * true the default<Mode>Action is used instead. + * + * @param request The portlet request. + * @return true if the action should be reset. + */ + private boolean resetAction(PortletRequest request) { + boolean reset = false; + Map paramMap = request.getParameterMap(); + String[] modeParam = (String[]) paramMap.get(MODE_PARAM); + if (modeParam != null && modeParam.length == 1) { + String originatingMode = modeParam[0]; + String currentMode = request.getPortletMode().toString(); + if (!currentMode.equals(originatingMode)) { + reset = true; + } + } + if (reset) { + request.setAttribute(ACTION_RESET, Boolean.TRUE); + } else { + request.setAttribute(ACTION_RESET, Boolean.FALSE); + } + return reset; + } + + public void destroy() { + if (dispatcherUtils != null) { + dispatcherUtils.cleanup(); + } else { + if (LOG.isWarnEnabled()) { + LOG.warn("Something is seriously wrong, DispatcherUtil is not initialized (null) "); + } + } + } + + /** + * @param actionMapper the actionMapper to set + */ + public void setActionMapper(ActionMapper actionMapper) { + this.actionMapper = actionMapper; + } + + /** + * Method to create a PortletServletResponse matching the used Portlet API, to be overridden for JSR286 Dispatcher. + * + * @param response The Response used for building the wrapper. + * @return The wrapper response for Servlet bound usage. + */ + protected PortletServletResponse createPortletServletResponse(PortletResponse response) { + return new PortletServletResponse(response); + } + +} diff --git a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsDecorator.java b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsDecorator.java index f5a0cd1a8..989141044 100644 --- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsDecorator.java +++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsDecorator.java @@ -1,199 +1,204 @@ -/* - * 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. - */ -package org.apache.struts2.sitemesh; - -import com.opensymphony.module.sitemesh.RequestConstants; -import com.opensymphony.sitemesh.Content; -import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext; -import com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator; -import com.opensymphony.xwork2.*; -import com.opensymphony.xwork2.interceptor.PreResultListener; -import com.opensymphony.xwork2.util.ValueStack; -import com.opensymphony.xwork2.util.ValueStackFactory; -import freemarker.template.Configuration; -import org.apache.struts2.ServletActionContext; -import org.apache.struts2.dispatcher.Dispatcher; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Locale; - -/** - * Adapts a SiteMesh 2 {@link com.opensymphony.module.sitemesh.Decorator} to a - * SiteMesh 3 {@link com.opensymphony.sitemesh.Decorator}. - * - * @since SiteMesh 3 - */ -public abstract class OldDecorator2NewStrutsDecorator extends BaseWebAppDecorator implements RequestConstants { - - protected com.opensymphony.module.sitemesh.Decorator oldDecorator; - private static String customEncoding; - - public OldDecorator2NewStrutsDecorator(com.opensymphony.module.sitemesh.Decorator oldDecorator) { - this.oldDecorator = oldDecorator; - } - - public OldDecorator2NewStrutsDecorator() { - oldDecorator = null; - } - - - /** - * Applies the decorator, using the relevent contexts - * - * @param content The content - * @param request The servlet request - * @param response The servlet response - * @param servletContext The servlet context - * @param ctx The action context for this request, populated with the server state - */ - protected abstract void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, ActionContext ctx) throws ServletException, IOException; - - /** - * Applies the decorator, creating the relevent contexts and delegating to - * the extended applyDecorator(). - * - * @param content The content - * @param request The servlet request - * @param response The servlet response - * @param servletContext The servlet context - * @param webAppContext The web app context - */ - - protected void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, SiteMeshWebAppContext webAppContext) throws IOException, ServletException { - - // see if the URI path (webapp) is set - if (oldDecorator.getURIPath() != null) { - // in a security conscious environment, the servlet container - // may return null for a given URL - if (servletContext.getContext(oldDecorator.getURIPath()) != null) { - servletContext = servletContext.getContext(oldDecorator.getURIPath()); - } - } - - ActionContext ctx = ServletActionContext.getActionContext(request); - if (ctx == null) { - // ok, one isn't associated with the request, so let's create one using the current Dispatcher - ValueStack vs = Dispatcher.getInstance().getContainer().getInstance(ValueStackFactory.class).createValueStack(); - vs.getContext().putAll(Dispatcher.getInstance().createContextMap(request, response, null)); - ctx = ActionContext.of(vs.getContext()); - if (ctx.getActionInvocation() == null) { - // put in a dummy ActionSupport so basic functionality still works - ActionSupport action = new ActionSupport(); - vs.push(action); - ctx.withActionInvocation(new DummyActionInvocation(action)); - } - } - - // delegate to the actual page decorator - render(content, request, response, servletContext, ctx); - - } - - /** - * Returns the locale used for the {@link freemarker.template.Configuration#getTemplate(String, java.util.Locale)} call. The base implementation - * simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if - * the action does not the configuration's locale is returned. Override this method to provide different behaviour, - */ - protected Locale getLocale(ActionInvocation invocation, Configuration configuration) { - if (invocation.getAction() instanceof LocaleProvider) { - return ((LocaleProvider) invocation.getAction()).getLocale(); - } else { - return configuration.getLocale(); - } - } - - - /** - * Gets the L18N encoding of the system. The default is UTF-8. - */ - protected String getEncoding() { - String encoding = customEncoding; - if (encoding == null) { - encoding = System.getProperty("file.encoding"); - } - if (encoding == null) { - encoding = "UTF-8"; - } - return encoding; - } - - - static class DummyActionInvocation implements ActionInvocation { - - ActionSupport action; - - public DummyActionInvocation(ActionSupport action) { - this.action = action; - } - - public Object getAction() { - return action; - } - - public boolean isExecuted() { - return false; - } - - public ActionContext getInvocationContext() { - return null; - } - - public ActionProxy getProxy() { - return null; - } - - public Result getResult() throws Exception { - return null; - } - - public String getResultCode() { - return null; - } - - public void setResultCode(String resultCode) { - } - - public ValueStack getStack() { - return null; - } - - public void addPreResultListener(PreResultListener listener) { - } - - public String invoke() throws Exception { - return null; - } - - public String invokeActionOnly() throws Exception { - return null; - } - - public void setActionEventListener(ActionEventListener listener) { - } - - public void init(ActionProxy proxy) { - } - - } - -} +/* + * 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. + */ +package org.apache.struts2.sitemesh; + +import com.opensymphony.module.sitemesh.RequestConstants; +import com.opensymphony.sitemesh.Content; +import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext; +import com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionEventListener; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.LocaleProvider; +import com.opensymphony.xwork2.Result; +import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.util.ValueStack; +import freemarker.template.Configuration; +import org.apache.struts2.ServletActionContext; +import org.apache.struts2.dispatcher.Dispatcher; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Locale; + +/** + * Adapts a SiteMesh 2 {@link com.opensymphony.module.sitemesh.Decorator} to a + * SiteMesh 3 {@link com.opensymphony.sitemesh.Decorator}. + * + * @since SiteMesh 3 + */ +public abstract class OldDecorator2NewStrutsDecorator extends BaseWebAppDecorator implements RequestConstants { + + protected com.opensymphony.module.sitemesh.Decorator oldDecorator; + private static String customEncoding; + + public OldDecorator2NewStrutsDecorator(com.opensymphony.module.sitemesh.Decorator oldDecorator) { + this.oldDecorator = oldDecorator; + } + + public OldDecorator2NewStrutsDecorator() { + oldDecorator = null; + } + + + /** + * Applies the decorator, using the relevent contexts + * + * @param content The content + * @param request The servlet request + * @param response The servlet response + * @param servletContext The servlet context + * @param ctx The action context for this request, populated with the server state + */ + protected abstract void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, ActionContext ctx) throws ServletException, IOException; + + /** + * Applies the decorator, creating the relevent contexts and delegating to + * the extended applyDecorator(). + * + * @param content The content + * @param request The servlet request + * @param response The servlet response + * @param servletContext The servlet context + * @param webAppContext The web app context + */ + + protected void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, SiteMeshWebAppContext webAppContext) throws IOException, ServletException { + + // see if the URI path (webapp) is set + if (oldDecorator.getURIPath() != null) { + // in a security conscious environment, the servlet container + // may return null for a given URL + if (servletContext.getContext(oldDecorator.getURIPath()) != null) { + servletContext = servletContext.getContext(oldDecorator.getURIPath()); + } + } + + ActionContext ctx = ServletActionContext.getActionContext(request); + if (ctx == null) { + // ok, one isn't associated with the request, so let's create one using the current Dispatcher + ValueStack vs = Dispatcher.getInstance().getValueStackFactory().createValueStack(); + vs.getContext().putAll(Dispatcher.getInstance().createContextMap(request, response, null)); + ctx = ActionContext.of(vs.getContext()); + if (ctx.getActionInvocation() == null) { + // put in a dummy ActionSupport so basic functionality still works + ActionSupport action = new ActionSupport(); + vs.push(action); + ctx.withActionInvocation(new DummyActionInvocation(action)); + } + } + + // delegate to the actual page decorator + render(content, request, response, servletContext, ctx); + + } + + /** + * Returns the locale used for the {@link freemarker.template.Configuration#getTemplate(String, java.util.Locale)} call. The base implementation + * simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if + * the action does not the configuration's locale is returned. Override this method to provide different behaviour, + */ + protected Locale getLocale(ActionInvocation invocation, Configuration configuration) { + if (invocation.getAction() instanceof LocaleProvider) { + return ((LocaleProvider) invocation.getAction()).getLocale(); + } else { + return configuration.getLocale(); + } + } + + + /** + * Gets the L18N encoding of the system. The default is UTF-8. + */ + protected String getEncoding() { + String encoding = customEncoding; + if (encoding == null) { + encoding = System.getProperty("file.encoding"); + } + if (encoding == null) { + encoding = "UTF-8"; + } + return encoding; + } + + + static class DummyActionInvocation implements ActionInvocation { + + ActionSupport action; + + public DummyActionInvocation(ActionSupport action) { + this.action = action; + } + + public Object getAction() { + return action; + } + + public boolean isExecuted() { + return false; + } + + public ActionContext getInvocationContext() { + return null; + } + + public ActionProxy getProxy() { + return null; + } + + public Result getResult() throws Exception { + return null; + } + + public String getResultCode() { + return null; + } + + public void setResultCode(String resultCode) { + } + + public ValueStack getStack() { + return null; + } + + public void addPreResultListener(PreResultListener listener) { + } + + public String invoke() throws Exception { + return null; + } + + public String invokeActionOnly() throws Exception { + return null; + } + + public void setActionEventListener(ActionEventListener listener) { + } + + public void init(ActionProxy proxy) { + } + + } + +}