diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java index 58469698f..b04cffb36 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java @@ -777,11 +777,18 @@ public class XmlConfigurationProvider implements ConfigurationProvider { } params.putAll(resultParams); - ResultConfig resultConfig = new ResultConfig.Builder(resultName, resultClass) - .addParams(params) - .location(DomHelper.getLocationObject(element)) - .build(); - results.put(resultConfig.getName(), resultConfig); + Set resultNamesSet = TextParseUtil.commaDelimitedStringToSet(resultName); + if (resultNamesSet.isEmpty()) { + resultNamesSet.add(resultName); + } + + for (String name : resultNamesSet) { + ResultConfig resultConfig = new ResultConfig.Builder(name, resultClass) + .addParams(params) + .location(DomHelper.getLocationObject(element)) + .build(); + results.put(resultConfig.getName(), resultConfig); + } } } diff --git a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultsTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultsTest.java index caa9da0d6..468ea1a18 100644 --- a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultsTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultsTest.java @@ -15,6 +15,7 @@ */ package com.opensymphony.xwork2.config.providers; +import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ActionChainResult; import com.opensymphony.xwork2.SimpleAction; import com.opensymphony.xwork2.config.ConfigurationException; @@ -118,4 +119,57 @@ public class XmlConfigurationProviderResultsTest extends ConfigurationTestBase { assertEquals(chainResult, resultTypes.get("chain")); assertEquals(mockResult, resultTypes.get("mock")); } + + public void testResultNames() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-result-names.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + // execute the configuration + provider.init(configuration); + provider.loadPackages(); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map actionConfigs = pkg.getActionConfigs(); + + // assertions + assertNotNull(actionConfigs); + + Map resultConfigs = actionConfigs.get("noname").getResults(); + assertEquals(1, resultConfigs.size()); + assertTrue(resultConfigs.containsKey(Action.SUCCESS)); + + resultConfigs = actionConfigs.get("success").getResults(); + assertEquals(1, resultConfigs.size()); + assertTrue(resultConfigs.containsKey(Action.SUCCESS)); + + resultConfigs = actionConfigs.get("empty").getResults(); + assertEquals(1, resultConfigs.size()); + assertTrue(resultConfigs.containsKey(Action.SUCCESS)); + + resultConfigs = actionConfigs.get("comma").getResults(); + assertEquals(1, resultConfigs.size()); + assertTrue(resultConfigs.containsKey(" , ")); + + resultConfigs = actionConfigs.get("error-input").getResults(); + assertEquals(2, resultConfigs.size()); + assertTrue(resultConfigs.containsKey(Action.ERROR)); + assertTrue(resultConfigs.containsKey(Action.INPUT)); + + resultConfigs = actionConfigs.get("error-input2").getResults(); + assertEquals(2, resultConfigs.size()); + assertTrue(resultConfigs.containsKey(Action.ERROR)); + assertTrue(resultConfigs.containsKey(Action.INPUT)); + + resultConfigs = actionConfigs.get("noname-error-input").getResults(); + assertEquals(3, resultConfigs.size()); + assertTrue(resultConfigs.containsKey(Action.SUCCESS)); + assertTrue(resultConfigs.containsKey(Action.ERROR)); + assertTrue(resultConfigs.containsKey(Action.INPUT)); + + resultConfigs = actionConfigs.get("noname-error-input2").getResults(); + assertEquals(3, resultConfigs.size()); + assertTrue(resultConfigs.containsKey(Action.SUCCESS)); + assertTrue(resultConfigs.containsKey(Action.ERROR)); + assertTrue(resultConfigs.containsKey(Action.INPUT)); + } } diff --git a/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-names.xml b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-names.xml new file mode 100644 index 000000000..e7405775b --- /dev/null +++ b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-names.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultResultMapBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultResultMapBuilder.java index 87fb9706e..40ea279cd 100644 --- a/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultResultMapBuilder.java +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultResultMapBuilder.java @@ -28,6 +28,7 @@ import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.config.entities.ResultTypeConfig; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.TextParseUtil; import com.opensymphony.xwork2.util.finder.ClassLoaderInterface; import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate; import com.opensymphony.xwork2.util.finder.ResourceFinder; @@ -409,11 +410,13 @@ public class DefaultResultMapBuilder implements ResultMapBuilder { Class actionClass, Map resultsByExtension) { // Check for multiple results on the class for (Result result : results) { - ResultConfig config = createResultConfig(actionClass, - new ResultInfo(result, packageConfig, resultPath, actionClass, resultsByExtension), - packageConfig, result); - if (config != null) { - resultConfigs.put(config.getName(), config); + for (String name : result.name()) { + ResultConfig config = createResultConfig(actionClass, new ResultInfo( + name, result, packageConfig, resultPath, actionClass, + resultsByExtension), packageConfig, result); + if (config != null) { + resultConfigs.put(config.getName(), config); + } } } } @@ -478,9 +481,10 @@ public class DefaultResultMapBuilder implements ResultMapBuilder { this.type = determineType(location, packageConfig, resultsByExtension); } - public ResultInfo(Result result, PackageConfig packageConfig, String resultPath, - Class actionClass, Map resultsByExtension) { - this.name = result.name(); + public ResultInfo(String name, Result result, PackageConfig packageConfig, + String resultPath, Class actionClass, + Map resultsByExtension) { + this.name = name; if (StringUtils.isNotBlank(result.type())) { this.type = result.type(); } else if (StringUtils.isNotBlank(result.location())) { diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Result.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Result.java index f8e9ecd89..f90e7b50c 100644 --- a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Result.java +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Result.java @@ -69,7 +69,7 @@ public @interface Result { * @return The name of the result mapping. This is the value that is returned from the action * method and is used to associate a location with a return value. */ - String name() default com.opensymphony.xwork2.Action.SUCCESS; + String[] name() default com.opensymphony.xwork2.Action.SUCCESS; /** * @return The location of the result within the web application or anywhere on disk. This location diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/DefaultResultMapBuilderTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/DefaultResultMapBuilderTest.java index abda7b355..8a4325b90 100644 --- a/plugins/convention/src/test/java/org/apache/struts2/convention/DefaultResultMapBuilderTest.java +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/DefaultResultMapBuilderTest.java @@ -487,6 +487,64 @@ public class DefaultResultMapBuilderTest extends TestCase { EasyMock.verify(context); } + public void testActionLevelMultipleResultNamesAnnotation() throws Exception { + ServletContext context = EasyMock.createStrictMock(ServletContext.class); + + // Setup some mock jsps + Set resources = new HashSet<>(); + EasyMock.expect(context.getResourcePaths("/WEB-INF/location/namespace/")).andReturn(resources); + EasyMock.replay(context); + + PackageConfig packageConfig = createPackageConfigBuilder("/namespace"); + + this.conventionsService = new ConventionsServiceImpl("/WEB-INF/location"); + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, container, "dispatcher,velocity,freemarker"); + Map results = builder.build(ActionLevelResultsNamesAction.class, getAnnotation(ActionLevelResultsNamesAction.class, "execute", Action.class), "action-level-results", packageConfig); + assertEquals(4, results.size()); + assertEquals("error", results.get("error").getName()); + assertEquals("input", results.get("input").getName()); + assertEquals("success", results.get("success").getName()); + assertEquals("failure", results.get("failure").getName()); + assertEquals(3, results.get("error").getParams().size()); + assertEquals("/WEB-INF/location/namespace/error.jsp", results.get("error").getParams().get("location")); + assertEquals("org.apache.struts2.result.ServletDispatcherResult", results.get("error").getClassName()); + assertEquals("value", results.get("success").getParams().get("key")); + assertEquals("value1", results.get("success").getParams().get("key1")); + assertEquals(3, results.get("input").getParams().size()); + assertEquals("/WEB-INF/location/namespace/error.jsp", results.get("input").getParams().get("location")); + assertEquals("org.apache.struts2.result.ServletDispatcherResult", results.get("input").getClassName()); + assertEquals(3, results.get("failure").getParams().size()); + assertEquals("/WEB-INF/location/namespace/action-failure.jsp", results.get("failure").getParams().get("location")); + assertEquals("org.apache.struts2.result.ServletDispatcherResult", results.get("failure").getClassName()); + assertEquals(3, results.get("success").getParams().size()); + assertEquals("/WEB-INF/location/namespace/action-success.jsp", results.get("success").getParams().get("location")); + assertEquals("org.apache.struts2.result.ServletDispatcherResult", results.get("success").getClassName()); + EasyMock.verify(context); + } + + public void testActionLevelMultipleResultNamesAnnotationNoName() throws Exception { + ServletContext context = EasyMock.createStrictMock(ServletContext.class); + + // Setup some mock jsps + Set resources = new HashSet<>(); + EasyMock.expect(context.getResourcePaths("/WEB-INF/location/namespace/")).andReturn(resources); + EasyMock.replay(context); + + PackageConfig packageConfig = createPackageConfigBuilder("/namespace"); + + this.conventionsService = new ConventionsServiceImpl("/WEB-INF/location"); + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, container, "dispatcher,velocity,freemarker"); + Map results = builder.build(ActionLevelResultsNamesAction.class, getAnnotation(ActionLevelResultsNamesAction.class, "noname", Action.class), "action-level-results", packageConfig); + assertEquals(1, results.size()); + assertEquals("success", results.get("success").getName()); + assertEquals(3, results.get("success").getParams().size()); + assertEquals("value", results.get("success").getParams().get("key")); + assertEquals("value1", results.get("success").getParams().get("key1")); + assertEquals("/WEB-INF/location/namespace/action-success.jsp", results.get("success").getParams().get("location")); + assertEquals("org.apache.struts2.result.ServletDispatcherResult", results.get("success").getClassName()); + EasyMock.verify(context); + } + public void testClassPath() throws Exception { ServletContext context = EasyMock.createNiceMock(ServletContext.class); diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java index 61ed3eb28..dfa1ad622 100644 --- a/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java @@ -291,6 +291,8 @@ public class PackageBasedActionConfigBuilderTest extends TestCase { expect(resultMapBuilder.build(OverrideResultAction.class, getAnnotation(OverrideResultAction.class, "execute", Action.class), "override-result", resultPkg)).andReturn(results); expect(resultMapBuilder.build(GlobalResultAction.class, null, "global-result", globalResultPkg)).andReturn(results); expect(resultMapBuilder.build(GlobalResultOverrideAction.class, null, "global-result-override", globalResultPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionLevelResultsNamesAction.class, getAnnotation(ActionLevelResultsNamesAction.class, "execute", Action.class), "action-level-results-names", resultPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionLevelResultsNamesAction.class, getAnnotation(ActionLevelResultsNamesAction.class, "noname", Action.class), "action-level-results-names", resultPkg)).andReturn(results); /* org.apache.struts2.convention.actions.resultpath */ expect(resultMapBuilder.build(ClassLevelResultPathAction.class, null, "class-level-result-path", resultPathPkg)).andReturn(results); @@ -565,7 +567,7 @@ public class PackageBasedActionConfigBuilderTest extends TestCase { /* org.apache.struts2.convention.actions.result */ pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.result#struts-default#/result"); assertNotNull(pkgConfig); - assertEquals(6, pkgConfig.getActionConfigs().size()); + assertEquals(7, pkgConfig.getActionConfigs().size()); verifyActionConfig(pkgConfig, "class-level-result", ClassLevelResultAction.class, "execute", pkgConfig.getName()); verifyActionConfig(pkgConfig, "class-level-results", ClassLevelResultsAction.class, "execute", pkgConfig.getName()); verifyActionConfig(pkgConfig, "action-level-result", ActionLevelResultAction.class, "execute", pkgConfig.getName()); diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultsNamesAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultsNamesAction.java new file mode 100644 index 000000000..2ae4bb629 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultsNamesAction.java @@ -0,0 +1,47 @@ +/* + * $Id$ + * + * 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.convention.actions.result; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.Result; + +/** + *

+ * This is a test action with multiple results names. + *

+ */ +public class ActionLevelResultsNamesAction { + @Action(results = { + @Result(name={"error", "input"}, location="error.jsp"), + @Result(name="success", location="/WEB-INF/location/namespace/action-success.jsp"), + @Result(name="failure", location="/WEB-INF/location/namespace/action-failure.jsp") + }) + public String execute() { + return null; + } + + @Action(results = { + @Result(location="/WEB-INF/location/namespace/action-success.jsp") + }) + public String noname() { + return null; + } +}