From 5af840b1600529ac59ed706cf52d2122e7aadafc Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Wed, 15 Jun 2022 11:06:45 +0200 Subject: [PATCH 01/11] WW-5190 Extracts method name from config for known action name --- .../mapper/DefaultActionMapper.java | 124 ++++++++++-------- 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java index a9679a3b1..9c09e187e 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java @@ -21,6 +21,7 @@ package org.apache.struts2.dispatcher.mapper; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.config.entities.PackageConfig; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.Inject; @@ -29,7 +30,6 @@ import org.apache.commons.lang3.StringUtils; 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.StrutsConstants; import org.apache.struts2.util.PrefixTrie; @@ -115,7 +115,7 @@ public class DefaultActionMapper implements ActionMapper { protected boolean allowDynamicMethodCalls = false; protected boolean allowSlashesInActionNames = false; protected boolean alwaysSelectFullNamespace = false; - protected PrefixTrie prefixTrie = null; + protected PrefixTrie prefixTrie; protected Pattern allowedNamespaceNames = Pattern.compile("[a-zA-Z0-9._/\\-]*"); protected String defaultNamespaceName = "/"; @@ -139,39 +139,35 @@ public class DefaultActionMapper implements ActionMapper { public DefaultActionMapper() { prefixTrie = new PrefixTrie() { { - put(METHOD_PREFIX, new ParameterAction() { - public void execute(String key, ActionMapping mapping) { - if (allowDynamicMethodCalls) { - mapping.setMethod(cleanupMethodName(key.substring(METHOD_PREFIX.length()))); - } + put(METHOD_PREFIX, (ParameterAction) (key, mapping) -> { + if (allowDynamicMethodCalls) { + mapping.setMethod(cleanupMethodName(key.substring(METHOD_PREFIX.length()))); } }); - put(ACTION_PREFIX, new ParameterAction() { - public void execute(final String key, ActionMapping mapping) { - if (allowActionPrefix) { - String name = key.substring(ACTION_PREFIX.length()); - if (allowDynamicMethodCalls) { - int bang = name.indexOf('!'); - if (bang != -1) { - String method = cleanupMethodName(name.substring(bang + 1)); - mapping.setMethod(method); - name = name.substring(0, bang); - } + put(ACTION_PREFIX, (ParameterAction) (key, mapping) -> { + if (allowActionPrefix) { + String name = key.substring(ACTION_PREFIX.length()); + if (allowDynamicMethodCalls) { + int bang = name.indexOf('!'); + if (bang != -1) { + String method = cleanupMethodName(name.substring(bang + 1)); + mapping.setMethod(method); + name = name.substring(0, bang); } - String actionName = cleanupActionName(name); - if (allowSlashesInActionNames && !allowActionCrossNamespaceAccess) { - if (actionName.startsWith("/")) { - actionName = actionName.substring(1); - } - } - if (!allowSlashesInActionNames && !allowActionCrossNamespaceAccess) { - if (actionName.lastIndexOf('/') != -1) { - actionName = actionName.substring(actionName.lastIndexOf('/') + 1); - } - } - mapping.setName(actionName); } + String actionName = cleanupActionName(name); + if (allowSlashesInActionNames && !allowActionCrossNamespaceAccess) { + if (actionName.startsWith("/")) { + actionName = actionName.substring(1); + } + } + if (!allowSlashesInActionNames && !allowActionCrossNamespaceAccess) { + if (actionName.lastIndexOf('/') != -1) { + actionName = actionName.substring(actionName.lastIndexOf('/') + 1); + } + } + mapping.setName(actionName); } }); @@ -293,6 +289,7 @@ public class DefaultActionMapper implements ActionMapper { } parseNameAndNamespace(uri, mapping, configManager); + extractMethodName(mapping, configManager); handleSpecialParameters(request, mapping); return parseActionName(mapping); } @@ -324,9 +321,8 @@ public class DefaultActionMapper implements ActionMapper { public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) { // handle special parameter prefixes. Set uniqueParameters = new HashSet<>(); - Map parameterMap = request.getParameterMap(); - for (Object o : parameterMap.keySet()) { - String key = (String) o; + Map parameterMap = request.getParameterMap(); + for (String key : parameterMap.keySet()) { // Strip off the image button location info, if found if (key.endsWith(".x") || key.endsWith(".y")) { @@ -353,33 +349,33 @@ public class DefaultActionMapper implements ActionMapper { * @param configManager configuration manager */ protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { - String namespace, name; + String actionNamespace, actionName; int lastSlash = uri.lastIndexOf('/'); if (lastSlash == -1) { - namespace = ""; - name = uri; + actionNamespace = ""; + actionName = uri; } else if (lastSlash == 0) { // ww-1046, assume it is the root namespace, it will fallback to // default // namespace anyway if not found in root namespace. - namespace = "/"; - name = uri.substring(lastSlash + 1); + actionNamespace = "/"; + actionName = uri.substring(lastSlash + 1); } else if (alwaysSelectFullNamespace) { // Simply select the namespace as everything before the last slash - namespace = uri.substring(0, lastSlash); - name = uri.substring(lastSlash + 1); + actionNamespace = uri.substring(0, lastSlash); + actionName = uri.substring(lastSlash + 1); } else { // Try to find the namespace in those defined, defaulting to "" Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash); - namespace = ""; + actionNamespace = ""; boolean rootAvailable = false; // Find the longest matching namespace, defaulting to the default for (PackageConfig cfg : config.getPackageConfigs().values()) { String ns = cfg.getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { - if (ns.length() > namespace.length()) { - namespace = ns; + if (ns.length() > actionNamespace.length()) { + actionNamespace = ns; } } if ("/".equals(ns)) { @@ -387,23 +383,23 @@ public class DefaultActionMapper implements ActionMapper { } } - name = uri.substring(namespace.length() + 1); + actionName = uri.substring(actionNamespace.length() + 1); // Still none found, use root namespace if found - if (rootAvailable && "".equals(namespace)) { - namespace = "/"; + if (rootAvailable && "".equals(actionNamespace)) { + actionNamespace = "/"; } } if (!allowSlashesInActionNames) { - int pos = name.lastIndexOf('/'); - if (pos > -1 && pos < name.length() - 1) { - name = name.substring(pos + 1); + int pos = actionName.lastIndexOf('/'); + if (pos > -1 && pos < actionName.length() - 1) { + actionName = actionName.substring(pos + 1); } } - mapping.setNamespace(cleanupNamespaceName(namespace)); - mapping.setName(cleanupActionName(name)); + mapping.setNamespace(cleanupNamespaceName(actionNamespace)); + mapping.setName(cleanupActionName(actionName)); } /** @@ -454,6 +450,30 @@ public class DefaultActionMapper implements ActionMapper { } } + /** + * Reads defined method name for a given action from configuration + * + * @param mapping current instance of {@link ActionMapping} + * @param configurationManager current instance of {@link ConfigurationManager} + */ + protected void extractMethodName(ActionMapping mapping, ConfigurationManager configurationManager) { + String methodName = null; + for (PackageConfig cfg : configurationManager.getConfiguration().getPackageConfigs().values()) { + if (cfg.getNamespace().equals(mapping.getNamespace())) { + ActionConfig actionCfg = cfg.getActionConfigs().get(mapping.getName()); + if (actionCfg != null) { + methodName = actionCfg.getMethodName(); + LOG.trace("Using method: {} for action mapping: {}", methodName, mapping); + } else { + LOG.debug("No action config for action mapping: {}", mapping); + } + break; + } + } + + mapping.setMethod(methodName); + } + /** * Drops the extension from the action name, storing it in the mapping for later use * @@ -551,7 +571,7 @@ public class DefaultActionMapper implements ActionMapper { String extension = lookupExtension(mapping.getExtension()); if (extension != null) { - if (extension.length() == 0 || (extension.length() > 0 && uri.indexOf('.' + extension) == -1)) { + if (extension.length() == 0 || uri.indexOf('.' + extension) == -1) { if (extension.length() > 0) { uri.append(".").append(extension); } From 1c2b491a27b48a0b064b991a6cef63db5e6cb28b Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Wed, 15 Jun 2022 11:07:29 +0200 Subject: [PATCH 02/11] WW-5190 Reuses action proxy if namespace, name and method are match --- .../apache/struts2/dispatcher/Dispatcher.java | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 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 7b9a8e1da..43794c1c5 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java @@ -83,6 +83,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Pattern; @@ -612,20 +613,12 @@ public class Dispatcher { } try { - String namespace = mapping.getNamespace(); - String name = mapping.getName(); - String method = mapping.getMethod(); + String actionNamespace = mapping.getNamespace(); + String actionName = mapping.getName(); + String actionMethod = mapping.getMethod(); - ActionProxy proxy; - - //check if we are probably in an async resuming - ActionInvocation invocation = ActionContext.getContext().getActionInvocation(); - if (invocation == null || invocation.isExecuted()) { - proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, - extraContext, true, false); - } else { - proxy = invocation.getProxy(); - } + LOG.trace("Processing action, namespace: {}, name: {}, method: {}", actionNamespace, actionName, actionMethod); + ActionProxy proxy = prepareActionProxy(extraContext, actionNamespace, actionName, actionMethod); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); @@ -656,6 +649,36 @@ public class Dispatcher { } } + private ActionProxy prepareActionProxy(Map extraContext, String actionNamespace, String actionName, String actionMethod) { + ActionProxy proxy; + //check if we are probably in an async resuming + ActionInvocation invocation = ActionContext.getContext().getActionInvocation(); + if (invocation == null || invocation.isExecuted()) { + LOG.trace("Creating a new action, namespace: {}, name: {}, method: {}", actionNamespace, actionName, actionMethod); + proxy = createActionProxy(actionNamespace, actionName, actionMethod, extraContext); + } else { + proxy = invocation.getProxy(); + if (isSameAction(proxy, actionNamespace, actionName, actionMethod)) { + LOG.trace("Proxy: {} matches requested action, namespace: {}, name: {}, method: {} - reusing proxy", proxy, actionNamespace, actionName, actionMethod); + } else { + LOG.trace("Proxy: {} doesn't match action namespace: {}, name: {}, method: {} - creating new proxy", proxy, actionNamespace, actionName, actionMethod); + proxy = createActionProxy(actionNamespace, actionName, actionMethod, extraContext); + } + } + return proxy; + } + + private 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); + } + + private boolean isSameAction(ActionProxy actionProxy, String namespace, String actionName, String method) { + return Objects.equals(namespace, actionProxy.getNamespace()) + && Objects.equals(actionName, actionProxy.getActionName()) + && Objects.equals(method, actionProxy.getMethod()); + } + /** * Performs logging of missing action/result configuration exception * From 69102e907551a87335231656320c8484072bdecb Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Wed, 15 Jun 2022 11:07:53 +0200 Subject: [PATCH 03/11] WW-5190 Uses wrapped request only when processing request by Struts --- .../dispatcher/filter/StrutsPrepareAndExecuteFilter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java index 73f5860ec..54ee6883d 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java @@ -126,18 +126,18 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter { LOG.trace("Checking if {} is a static resource", uri); boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { - LOG.trace("Assuming uri {} as a normal action", uri); + LOG.trace("Uri {} is not a static resource, assuming action", uri); prepare.setEncodingAndLocale(request, response); prepare.createActionContext(request, response); prepare.assignDispatcherToThread(); - request = prepare.wrapRequest(request); - ActionMapping mapping = prepare.findActionMapping(request, response, true); + HttpServletRequest wrappedRequest = prepare.wrapRequest(request); + ActionMapping mapping = prepare.findActionMapping(wrappedRequest, response, true); if (mapping == null) { LOG.trace("Cannot find mapping for {}, passing to other filters", uri); chain.doFilter(request, response); } else { LOG.trace("Found mapping {} for {}", mapping, uri); - execute.executeAction(request, response, mapping); + execute.executeAction(wrappedRequest, response, mapping); } } } From a6a4529503f1e733feeb322f47c3473339eeee60 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 18 Jun 2022 07:25:26 +0200 Subject: [PATCH 04/11] Fixes logging in Showcase app --- apps/showcase/pom.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/showcase/pom.xml b/apps/showcase/pom.xml index 84c4e32fc..709f2b630 100644 --- a/apps/showcase/pom.xml +++ b/apps/showcase/pom.xml @@ -121,6 +121,11 @@ log4j-jcl ${log4j2.version} + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j2.version} + opensymphony @@ -191,10 +196,6 @@ CTRL+C 8999 - - log4j.configuration - file:${basedir}/src/main/resources/log4j2.xml - slf4j false From 06290cef91779764c35f133d00632cd138cd25c4 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 18 Jun 2022 07:26:13 +0200 Subject: [PATCH 05/11] Fixes showing configuration of an action in Showcase app --- .../showcase/source/ViewSourceAction.java | 329 +++++++++--------- .../src/main/webapp/WEB-INF/viewSource.jsp | 75 ++-- 2 files changed, 194 insertions(+), 210 deletions(-) diff --git a/apps/showcase/src/main/java/org/apache/struts2/showcase/source/ViewSourceAction.java b/apps/showcase/src/main/java/org/apache/struts2/showcase/source/ViewSourceAction.java index 541adccff..ce5b5b949 100644 --- a/apps/showcase/src/main/java/org/apache/struts2/showcase/source/ViewSourceAction.java +++ b/apps/showcase/src/main/java/org/apache/struts2/showcase/source/ViewSourceAction.java @@ -22,16 +22,13 @@ package org.apache.struts2.showcase.source; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.util.ClassLoaderUtil; -import org.apache.struts2.ServletActionContext; import org.apache.struts2.action.ServletContextAware; import javax.servlet.ServletContext; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -42,202 +39,192 @@ import java.util.List; */ public class ViewSourceAction extends ActionSupport implements ServletContextAware { - private String page; - private String className; - private String config; + private String page; + private String className; + private String config; - private List pageLines; - private List classLines; - private List configLines; + private List pageLines; + private List classLines; + private List configLines; - private int configLine; - private int padding = 10; + private int configLine; + private int padding = 10; - private ServletContext servletContext; + private ServletContext servletContext; - public String execute() throws MalformedURLException, IOException { + public String execute() throws IOException { - if (page != null) { + if (page != null) { - InputStream in = ClassLoaderUtil.getResourceAsStream(page.substring(page.indexOf("//") + 1), getClass()); - page = page.replace("//", "/"); + InputStream in = ClassLoaderUtil.getResourceAsStream(page.substring(page.indexOf("//") + 1), getClass()); + page = page.replace("//", "/"); - if (in == null) { - in = servletContext.getResourceAsStream(page); - while (in == null && page.indexOf('/', 1) > 0) { - page = page.substring(page.indexOf('/', 1)); - in = servletContext.getResourceAsStream(page); - } - } - pageLines = read(in, -1); + if (in == null) { + in = servletContext.getResourceAsStream(page); + while (in == null && page.indexOf('/', 1) > 0) { + page = page.substring(page.indexOf('/', 1)); + in = servletContext.getResourceAsStream(page); + } + } + pageLines = read(in, -1); - if (in != null) { - in.close(); - } - } + if (in != null) { + in.close(); + } + } - if (className != null) { - className = "/" + className.replace('.', '/') + ".java"; - InputStream in = getClass().getResourceAsStream(className); - if (in == null) { - in = servletContext.getResourceAsStream("/WEB-INF/src" + className); - } - classLines = read(in, -1); + if (className != null) { + className = "/" + className.replace('.', '/') + ".java"; + InputStream in = getClass().getResourceAsStream(className); + if (in == null) { + in = servletContext.getResourceAsStream("/WEB-INF/src/java" + className); + } + classLines = read(in, -1); - if (in != null) { - in.close(); - } - } + if (in != null) { + in.close(); + } + } - final String rootPath = ServletActionContext.getServletContext().getRealPath("/"); - final String rootPathUnix = (rootPath != null ? rootPath.replace(File.separator, "/") : null); // Make path Unix-like for comparison (e.g. on Windows) - final String rootPathFileURI = "file://" + rootPathUnix; - final String collapsedRootPathFileURI = rootPathFileURI.replace("//", "/"); // Config string may have been transformed - final String rootPathWarFileURI = "war:file://" + rootPathUnix; - final String collapsedRootPathWarFileURI = rootPathWarFileURI.replace("//", "/"); // Config string may have been transformed - - if (config != null && (rootPath == null || config.startsWith(rootPath) || - config.startsWith(rootPathFileURI) || config.startsWith(collapsedRootPathFileURI) || - config.startsWith(rootPathWarFileURI) || config.startsWith(collapsedRootPathWarFileURI))) { - int pos = config.lastIndexOf(':'); - configLine = Integer.parseInt(config.substring(pos + 1)); - config = config.substring(0, pos).replace("//", "/"); - configLines = read(new URL(config).openStream(), configLine); - } - return SUCCESS; - } + if (config != null && config.startsWith("file:/")) { + int pos = config.lastIndexOf(':'); + configLine = Integer.parseInt(config.substring(pos + 1)); + configLines = read(new URL(config.substring(0, pos)).openStream(), configLine); + } + return SUCCESS; + } - /** - * @param className the className to set - */ - public void setClassName(String className) { - if (className != null && className.trim().length() > 0) { - this.className = className; - } - } + /** + * @param className the className to set + */ + public void setClassName(String className) { + if (className != null && className.trim().length() > 0) { + this.className = className; + } + } - /** - * @param config the config to set - */ - public void setConfig(String config) { - if (config != null && config.trim().length() > 0) { - this.config = config; - } - } + /** + * @param config the config to set + */ + public void setConfig(String config) { + if (config != null && config.trim().length() > 0) { + this.config = config; + } + } - /** - * @param page the page to set - */ - public void setPage(String page) { - if (page != null && page.trim().length() > 0) { - this.page = page; - } - } + /** + * @param page the page to set + */ + public void setPage(String page) { + if (page != null && page.trim().length() > 0) { + this.page = page; + } + } - /** - * @param padding the padding to set - */ - public void setPadding(int padding) { - this.padding = padding; - } + /** + * @param padding the padding to set + */ + public void setPadding(int padding) { + this.padding = padding; + } - /** - * @return the classLines - */ - public List getClassLines() { - return classLines; - } + /** + * @return the classLines + */ + public List getClassLines() { + return classLines; + } - /** - * @return the configLines - */ - public List getConfigLines() { - return configLines; - } + /** + * @return the configLines + */ + public List getConfigLines() { + return configLines; + } - /** - * @return the pageLines - */ - public List getPageLines() { - return pageLines; - } + /** + * @return the pageLines + */ + public List getPageLines() { + return pageLines; + } - /** - * @return the className - */ - public String getClassName() { - return className; - } + /** + * @return the className + */ + public String getClassName() { + return className; + } - /** - * @return the config - */ - public String getConfig() { - return config; - } + /** + * @return the config + */ + public String getConfig() { + return config; + } - /** - * @return the page - */ - public String getPage() { - return page; - } + /** + * @return the page + */ + public String getPage() { + return page; + } - /** - * @return the configLine - */ - public int getConfigLine() { - return configLine; - } + /** + * @return the configLine + */ + public int getConfigLine() { + return configLine; + } - /** - * @return the padding - */ - public int getPadding() { - return padding; - } + /** + * @return the padding + */ + public int getPadding() { + return padding; + } - /** - * Reads in a stream, optionally only including the target line number - * and its padding - * - * @param in The input stream - * @param targetLineNumber The target line number, negative to read all - * @return A list of lines - */ - private List read(InputStream in, int targetLineNumber) { - List snippet = null; - if (in != null) { - snippet = new ArrayList(); - int startLine = 0; - int endLine = Integer.MAX_VALUE; - if (targetLineNumber > 0) { - startLine = targetLineNumber - padding; - endLine = targetLineNumber + padding; - } - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + /** + * Reads in a stream, optionally only including the target line number + * and its padding + * + * @param in The input stream + * @param targetLineNumber The target line number, negative to read all + * @return A list of lines + */ + private List read(InputStream in, int targetLineNumber) { + List snippet = null; + if (in != null) { + snippet = new ArrayList<>(); + int startLine = 0; + int endLine = Integer.MAX_VALUE; + if (targetLineNumber > 0) { + startLine = targetLineNumber - padding; + endLine = targetLineNumber + padding; + } + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - int lineno = 0; - String line; - while ((line = reader.readLine()) != null) { - lineno++; - if (lineno >= startLine && lineno <= endLine) { - snippet.add(line); - } - } - } catch (Exception ex) { - // ignoring as snippet not available isn't a big deal - } - } - return snippet; - } + int lineno = 0; + String line; + while ((line = reader.readLine()) != null) { + lineno++; + if (lineno >= startLine && lineno <= endLine) { + snippet.add(line); + } + } + } catch (Exception ex) { + // ignoring as snippet not available isn't a big deal + } + } + return snippet; + } - public void withServletContext(ServletContext arg0) { - this.servletContext = arg0; - } + public void withServletContext(ServletContext arg0) { + this.servletContext = arg0; + } } diff --git a/apps/showcase/src/main/webapp/WEB-INF/viewSource.jsp b/apps/showcase/src/main/webapp/WEB-INF/viewSource.jsp index 1255d2a3b..61bb6fc87 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/viewSource.jsp +++ b/apps/showcase/src/main/webapp/WEB-INF/viewSource.jsp @@ -1,19 +1,19 @@ + + + + + + + + /WEB-INF/dispatcher/dispatch-result.jsp + + + + + /dispatcher/dispatch.action + + + + diff --git a/apps/showcase/src/main/resources/struts.xml b/apps/showcase/src/main/resources/struts.xml index 36d0370c3..cfdac4b39 100644 --- a/apps/showcase/src/main/resources/struts.xml +++ b/apps/showcase/src/main/resources/struts.xml @@ -78,6 +78,8 @@ + + diff --git a/apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp b/apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp index 0d09eb647..d63f61f9a 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp +++ b/apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp @@ -244,6 +244,8 @@
  • Model Driven
  • Async
  • +
  • Dispatcher result - dispatch
  • +
  • Dispatcher result - forward