diff --git a/plugins/convention/design.txt b/plugins/convention/design.txt new file mode 100644 index 000000000..397956e45 --- /dev/null +++ b/plugins/convention/design.txt @@ -0,0 +1,39 @@ +Design + +Action with no annotations (all the default handlings) +-------------------------- + +Action name is class name. +Action package is based on the either any Namespace annotations on the class or Java package the + action class is defined in or the Java package name and the root Java package, which is determined + based on the package locators or the action package list. In either case, a root package is + identified and the namespace is the packages below the root up to and including the package that + contains the action class. +Action method is execute in all cases. +Results are built first from the Result annotations at the class level. Next, they are built from + the resources available in the web application and in the classpath based on the result location, + namespace of the action and action name (not anything to do with the code element names). Results + are found for common result codes including, success, error, input, failure, etc. If a specific + result doesn't exist using the result code then just the action namespace and action name are used to + locate results. If there are still no results found, than a redirect is added back to the index + action for the namespace, if one exists. + +Action with Action(s) annotations +--------------------------------- + +If the annotation is located on any method other than execute, than the action configuration built + from the annotation is in addition to the action configuration built using the default. Unless there + is an annotation that contains no value, in which case, the method that annotation is defined for + becomes the default action method. If the annotation is defined on the execute method, regardless of + the value of the annotation, no default configuration is built for the class. +If multiple annotations have no value, an error should occur. +If multiple annotations have the same value, an error should occur. + +Action name is taken from the annotation value. If the value is empty, the default action name + is used. +Action package is taken from the annotation value. If the annotation value doesn't start with a slash + character, the default action namespace is used. +Action method is always the method that the annotation is defined on. +Results are built first from the array of Results on the Action annotation. Next, they are built + from the class level Result annotations. Lastly, all default results are created. All Results already + created during this process should not be overridden later in the process. diff --git a/plugins/convention/pom.xml b/plugins/convention/pom.xml new file mode 100644 index 000000000..c330c73d0 --- /dev/null +++ b/plugins/convention/pom.xml @@ -0,0 +1,91 @@ + + + + 4.0.0 + + org.apache.struts + struts2-plugins + 2.1.3-SNAPSHOT + + org.apache.struts + struts2-convention-plugin + jar + Struts 2 Convention Plugin + + + scm:svn:http://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2-convention-plugin + scm:svn:https://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2-convention-plugin + http://svn.apache.org/viewcvs.cgi/struts/sandbox/trunk/struts2-convention-plugin + + + + + + true + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + org.apache.struts + struts2-core + 2.1.3-SNAPSHOT + + + junit + junit + test + 3.8.1 + + + org.easymock + easymock + 2.3 + test + + + javax.servlet + servlet-api + 2.4 + provided + + + javax.servlet + jsp-api + 2.0 + provided + + + \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ActionConfigBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ActionConfigBuilder.java new file mode 100644 index 000000000..499ed313a --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ActionConfigBuilder.java @@ -0,0 +1,38 @@ +/* + * $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; + +/** + *

+ * This interface defines how the action configurations for the current + * web application can be constructed. This must find all actions that + * are not specifically defined in the struts XML files or any plugins. + * Furthermore, it must make every effort to locate all action results + * as well. + *

+ */ +public interface ActionConfigBuilder { + /** + * Builds all the action configurations and stores them into the XWork configuration instance + * via XWork dependency injetion. + */ + void buildActionConfigs(); +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ActionNameBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ActionNameBuilder.java new file mode 100644 index 000000000..89444053d --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ActionNameBuilder.java @@ -0,0 +1,37 @@ +/* + * $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; + +/** + *

+ * This interface defines the method that is used to create action + * names based on the name of a class. + *

+ */ +public interface ActionNameBuilder { + /** + * Given the name of the class, this method should build an action name. + * + * @param className The class name. + * @return The action name and never null. + */ + String build(String className); +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathConfigurationProvider.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathConfigurationProvider.java new file mode 100644 index 000000000..bc67e708f --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathConfigurationProvider.java @@ -0,0 +1,81 @@ +/* + * $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; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +/** + *

+ * This class is a configuration provider for the XWork configuration + * system. This is really the only way to truly handle loading of the + * packages, actions and results correctly. This doesn't contain any + * logic and instead delegates to the configured instance of the + * {@link ActionConfigBuilder} interface. + *

+ */ +public class ClasspathConfigurationProvider implements ConfigurationProvider { + private ActionConfigBuilder actionConfigBuilder; + + @Inject + public ClasspathConfigurationProvider(ActionConfigBuilder actionConfigBuilder) { + this.actionConfigBuilder = actionConfigBuilder; + } + + /** + * Not used. + */ + public void destroy() { + } + + /** + * Not used. + */ + public void init(Configuration configuration) { + } + + /** + * Does nothing. + */ + public void register(ContainerBuilder containerBuilder, LocatableProperties locatableProperties) + throws ConfigurationException { + } + + /** + * Loads the packages using the {@link ActionConfigBuilder}. + * + * @throws ConfigurationException + */ + public void loadPackages() throws ConfigurationException { + actionConfigBuilder.buildActionConfigs(); + } + + /** + * @return Always false. + */ + public boolean needsReload() { + return false; + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionUnknownHandler.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionUnknownHandler.java new file mode 100644 index 000000000..56af1d640 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionUnknownHandler.java @@ -0,0 +1,418 @@ +/* + * $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; + +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.apache.struts2.util.ClassLoaderUtils; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.Result; +import com.opensymphony.xwork2.UnknownHandler; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + *

+ * This class is the default unknown handler for all of the Convention + * plugin integration with XWork. This handles instances when a URL doesn't + * have an action associated with it but does a viable result and also the + * instance where an action returns a result code that isn't already + * configured but there is a viable result for the code. + *

+ * + *

+ * This class also handles all of the index actions using redirects + * and actions in nested packages. For example, if there is an action + * /foo/index and the URL /foo is used, + * this will render the index action in the /foo namespace. + *

+ */ +public class ConventionUnknownHandler implements UnknownHandler { + private static final Logger LOG = LoggerFactory.getLogger(ConventionUnknownHandler.class); + protected Configuration configuration; + protected ObjectFactory objectFactory; + protected ServletContext servletContext; + protected ResultMapBuilder resultMapBuilder; + protected String defaultParentPackageName; + protected PackageConfig parentPackage; + + private boolean redirectToSlash; + private ConventionsService conventionsService; + private String nameSeparator; + + /** + * Constructs the unknown handler. + * + * @param configuration The XWork configuration. + * @param objectFactory The XWork object factory used to create result instances. + * @param servletContext The servlet context used to help build the action configurations. + * @param resultMapBuilder The result map builder that is used to create results. + * @param conventionsService The conventions service used to get all the conventions and + * configurations. + * @param defaultParentPackageName The default XWork package that the unknown handler will use as + * the parent package for new actions and results. + * @param redirectToSlash A boolean parameter that controls whether or not this will handle + * unknown actions in the same manner as Apache, Tomcat and other web servers. This + * handling will send back a redirect for URLs such as /foo to /foo/ if there doesn't + * exist an action that responds to /foo. + * @param nameSeparator The character used as word separator in the action names. "-" by default + */ + @Inject + public ConventionUnknownHandler(Configuration configuration, ObjectFactory objectFactory, + ServletContext servletContext, ResultMapBuilder resultMapBuilder, + ConventionsService conventionsService, + @Inject("struts.convention.default.parent.package") String defaultParentPackageName, + @Inject("struts.convention.redirect.to.slash") String redirectToSlash, + @Inject("struts.convention.action.name.separator") String nameSeparator) { + this.configuration = configuration; + this.objectFactory = objectFactory; + this.servletContext = servletContext; + this.resultMapBuilder = resultMapBuilder; + this.conventionsService = conventionsService; + this.defaultParentPackageName = defaultParentPackageName; + this.nameSeparator = nameSeparator; + + this.parentPackage = configuration.getPackageConfig(defaultParentPackageName); + if (parentPackage == null) { + throw new ConfigurationException("Unknown default parent package [" + defaultParentPackageName + "]"); + } + + this.redirectToSlash = Boolean.parseBoolean(redirectToSlash); + } + + public ActionConfig handleUnknownAction(String namespace, String actionName) + throws XWorkException { + // Strip the namespace if it is just a slash + if (namespace == null || "/".equals(namespace)) { + namespace = ""; + } + + Map resultsByExtension = conventionsService.getResultTypesByExtension(parentPackage); + String pathPrefix = determinePath(null, namespace); + ActionConfig actionConfig = null; + + // Try /idx/action.jsp if actionName is not empty, otherwise it will just be /.jsp + if (!actionName.equals("")) { + Resource resource = findResource(resultsByExtension, pathPrefix, actionName); + if (resource != null) { + actionConfig = buildActionConfig(resource.path, resultsByExtension.get(resource.ext)); + } + } + + if (actionConfig == null) { + Resource resource = findResource(resultsByExtension, pathPrefix, actionName, "/index"); + + // If the URL is /foo and there is an action we can redirect to, send the redirect to /foo/. + // However, if that action is not in the same namespace, it is the default, so I'm not going + // to return that. + if (!actionName.equals("") && redirectToSlash) { + ResultTypeConfig redirectResultTypeConfig = parentPackage.getAllResultTypeConfigs().get("redirect"); + String redirectNamespace = namespace + "/" + actionName; + if (LOG.isTraceEnabled()) { + LOG.trace("Checking if there is an action named index in the namespace [#0]", + redirectNamespace); + } + + actionConfig = configuration.getRuntimeConfiguration().getActionConfig(redirectNamespace, "index"); + if (actionConfig != null) { + if (LOG.isTraceEnabled()) + LOG.trace("Found action config"); + + PackageConfig packageConfig = configuration.getPackageConfig(actionConfig.getPackageName()); + if (redirectNamespace.equals(packageConfig.getNamespace())) { + if (LOG.isTraceEnabled()) + LOG.trace("Action is not a default - redirecting"); + return buildActionConfig(redirectNamespace + "/", redirectResultTypeConfig); + } + + if (LOG.isTraceEnabled()) + LOG.trace("Action was a default - NOT redirecting"); + } + + if (resource != null) { + return buildActionConfig(redirectNamespace + "/", redirectResultTypeConfig); + } + } + + if (resource != null) { + // Otherwise, if the URL is /foo or /foo/ look for index pages in /foo/ + actionConfig = buildActionConfig(resource.path, resultsByExtension.get(resource.ext)); + } + } + + return actionConfig; + } + + /** + * Finds a resource using the given path parts and all of the extensions in the map. + * + * @param resultsByExtension Map of extension to result type config objects. + * @param parts The parts of the resource. + * @return The resource path or null. + */ + protected Resource findResource(Map resultsByExtension, String... parts) { + for (String ext : resultsByExtension.keySet()) { + String path = string(parts) + "." + ext; + if (LOG.isTraceEnabled()) { + LOG.trace("Checking for [#0]", path); + } + + try { + if (servletContext.getResource(path) != null) { + return new Resource(path, ext); + } + } catch (MalformedURLException e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to parse path to the web application resource [#0] skipping...", path); + } + } + + return null; + } + + protected ActionConfig buildActionConfig(String path, ResultTypeConfig resultTypeConfig) { + Map results = new HashMap(); + HashMap params = new HashMap(); + if (resultTypeConfig.getParams() != null) { + params.putAll(resultTypeConfig.getParams()); + } + params.put(resultTypeConfig.getDefaultResultParam(), path); + +// PackageConfig pkg = configuration.getPackageConfig(defaultParentPackageName); +// List interceptors = InterceptorBuilder.constructInterceptorReference(pkg, +// pkg.getFullDefaultInterceptorRef(), Collections.EMPTY_MAP, null, objectFactory); + ResultConfig config = new ResultConfig.Builder(Action.SUCCESS, resultTypeConfig.getClassName()). + addParams(params).build(); + results.put(Action.SUCCESS, config); + + //addInterceptors(interceptors). + return new ActionConfig.Builder(defaultParentPackageName, "execute", ActionSupport.class.getName()). + addResultConfigs(results).build(); + } + + private Result scanResultsByExtension(String ns, String actionName, String pathPrefix, + String resultCode, ActionContext actionContext) { + Map resultsByExtension = conventionsService.getResultTypesByExtension(parentPackage); + Result result = null; + for (String ext : resultsByExtension.keySet()) { + if (LOG.isTraceEnabled()) { + String fqan = ns + "/" + actionName; + LOG.trace("Trying to locate the correct result for the FQ action [#0]" + + " with an file extension of [#1] in the directory [#2] " + "and a result code of [#3]", + fqan, ext, pathPrefix, resultCode); + } + + String path = string(pathPrefix, actionName, nameSeparator, resultCode, "." , ext); + result = findResult(path, resultCode, ext, actionContext, resultsByExtension); + if (result != null) { + break; + } + + path = string(pathPrefix, actionName, "." , ext); + result = findResult(path, resultCode, ext, actionContext, resultsByExtension); + if (result != null) { + break; + } + + // Issue #6 - Scan for result-code as page name + path = string(pathPrefix, resultCode, "." , ext); + result = findResult(path, resultCode, ext, actionContext, resultsByExtension); + if (result != null) { + break; + } + + } + return result; + } + + public Result handleUnknownResult(ActionContext actionContext, String actionName, + ActionConfig actionConfig, String resultCode) throws XWorkException { + + PackageConfig pkg = configuration.getPackageConfig(actionConfig.getPackageName()); + String ns = pkg.getNamespace(); + String pathPrefix = determinePath(actionConfig, ns); + + Result result = scanResultsByExtension(ns, actionName, pathPrefix, resultCode, actionContext); + + if (result == null) { + // Try /idx/action/index.jsp + Map resultsByExtension = conventionsService.getResultTypesByExtension(pkg); + for (String ext : resultsByExtension.keySet()) { + if (LOG.isTraceEnabled()) { + String fqan = ns + "/" + actionName; + LOG.trace("Checking for [#0/index.#1]", fqan, ext); + } + + String path = string(pathPrefix, actionName, "/index", nameSeparator, resultCode, ".", ext); + result = findResult(path, resultCode, ext, actionContext, resultsByExtension); + if (result != null) { + break; + } + + path = string(pathPrefix, actionName, "/index." , ext); + result = findResult(path, resultCode, ext, actionContext, resultsByExtension); + if (result != null) { + break; + } + } + } + + if (result == null && resultCode != null) { + //try to find an action to chain to. If the source action is "foo" and + //the result is "bar", we will try to find an action called "foo-bar" + //in the same package + String chainedTo = new StringBuilder(actionName).append(nameSeparator).append(resultCode).toString(); + ActionConfig chainedToConfig = pkg.getActionConfigs().get(chainedTo); + if (chainedToConfig != null) { + if (LOG.isTraceEnabled()) { + LOG.trace("Action [#0] used as chain result for [#1] and result [#2]", chainedTo, actionName, resultCode); + } + + ResultTypeConfig chainResultType = pkg.getAllResultTypeConfigs().get("chain"); + result = buildResult(chainedTo, resultCode, chainResultType, actionContext); + } + } + + return result; + } + + protected Result findResult(String path, String resultCode, String ext, ActionContext actionContext, + Map resultsByExtension) { + try { + boolean traceEnabled = LOG.isTraceEnabled(); + if (traceEnabled) + LOG.trace("Checking ServletContext for [#0]", path); + + if (servletContext.getResource(path) != null) { + if (traceEnabled) + LOG.trace("Found"); + return buildResult(path, resultCode, resultsByExtension.get(ext), actionContext); + } + + if (traceEnabled) + LOG.trace("Checking ClasLoader for #0", path); + + String classLoaderPath = path.startsWith("/") ? path.substring(1, path.length()) : path; + if (ClassLoaderUtils.getResource(classLoaderPath, getClass()) != null) { + if (traceEnabled) + LOG.trace("Found"); + return buildResult(path, resultCode, resultsByExtension.get(ext), actionContext); + } + } catch (MalformedURLException e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to parse template path: [#0] skipping...", path); + } + + return null; + } + + protected Result buildResult(String path, String resultCode, ResultTypeConfig config, ActionContext invocationContext) { + String resultClass = config.getClassName(); + + Map params = new LinkedHashMap(); + if (config.getParams() != null) { + params.putAll(config.getParams()); + } + params.put(config.getDefaultResultParam(), path); + + ResultConfig resultConfig = new ResultConfig.Builder(resultCode, resultClass).addParams(params).build(); + try { + return objectFactory.buildResult(resultConfig, invocationContext.getContextMap()); + } catch (Exception e) { + throw new XWorkException("Unable to build convention result", e, resultConfig); + } + } + + protected String string(String... parts) { + StringBuilder sb = new StringBuilder(); + for (String part : parts) { + sb.append(part); + } + return sb.toString(); + } + + /** + * Determines the result path prefix that the request URL is for, minus the action name. This includes + * the base result location and the namespace, with all the slashes handled. + * + * @param actionConfig (Optional) The might be a ConventionActionConfig, from which we can get the + * default base result location of that specific action. + * @param namespace The current URL namespace. + * @return The path prefix and never null. + */ + protected String determinePath(ActionConfig actionConfig, String namespace) { + String finalPrefix = conventionsService.determineResultPath(actionConfig); + + if (!finalPrefix.endsWith("/")) { + finalPrefix += "/"; + } + + if (namespace == null || "/".equals(namespace)) { + namespace = ""; + } + + if (namespace.length() > 0) { + if (namespace.startsWith("/")) { + namespace = namespace.substring(1); + } + + if (!namespace.endsWith("/")) { + namespace += "/"; + } + } + + return finalPrefix + namespace; + } + + /** + * Not used + */ + public Object handleUnknownActionMethod(Object action, String methodName) throws NoSuchMethodException { + throw new NoSuchMethodException(); + } + + public static class Resource { + final String path; + final String ext; + + public Resource(String path, String ext) { + this.path = path; + this.ext = ext; + } + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionsService.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionsService.java new file mode 100644 index 000000000..0b9d17ad1 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionsService.java @@ -0,0 +1,67 @@ +/* + * $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; + +import java.util.Map; + +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; + +/** + *

+ * This interface defines the conventions that are used by the convention plugin. + * In most cases the methods on this class will provide the best default for any + * values and also handle locating overrides of the default via the annotations + * that are part of the plugin. + *

+ */ +public interface ConventionsService { + /** + * Locates the result location from annotations on the action class or the package or returns the + * default if no annotations are present. + * + * @param actionClass The action class. + * @return The result location if it is set in the annotations. Otherwise, the default result + * location is returned. + */ + String determineResultPath(Class actionClass); + + /** + * Delegates to the other method but first looks up the Action's class using the given class name. + * + * @param actionConfig (Optional) The configuration for the action that the result is being + * built for or null if the default result path is needed. + * @return The result location if it is set in the annotations for the class of the ActionConfig. + * Otherwise, the default result location is returned. If null is passed in, the default + * is returned, + */ + String determineResultPath(ActionConfig actionConfig); + + /** + * Returns a mapping between the result type strings and the {@link ResultTypeConfig} instances + * based on the {@link PackageConfig} given. + * + * @param packageConfig The PackageConfig to get the result types for. + * @return The result types or an empty Map of nothing is configured. + */ + Map getResultTypesByExtension(PackageConfig packageConfig); +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionsServiceImpl.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionsServiceImpl.java new file mode 100644 index 000000000..21aea039a --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ConventionsServiceImpl.java @@ -0,0 +1,119 @@ +/* + * $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; + +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +import org.apache.struts2.convention.annotation.AnnotationTools; +import org.apache.struts2.convention.annotation.ResultPath; + +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; +import com.opensymphony.xwork2.inject.Inject; + +/** + *

+ * This class is the implementation of the {@link ConventionsService} + * interface and provides all of the defaults and annotation handling. + *

+ */ +public class ConventionsServiceImpl implements ConventionsService { + private String resultPath; + + /** + * Constructs a new instance. + * + * @param resultPath The result path that is configured in the Struts configuration files using + * the constant name of struts.convention.result.path. + */ + @Inject + public ConventionsServiceImpl(@Inject("struts.convention.result.path") String resultPath) { + this.resultPath = resultPath; + } + + /** + * {@inheritDoc} + */ + public String determineResultPath(Class actionClass) { + String localResultPath = resultPath; + ResultPath resultPathAnnotation = AnnotationTools.findAnnotation(actionClass, ResultPath.class); + if (resultPathAnnotation != null) { + if (resultPathAnnotation.value().equals("") && resultPathAnnotation.property().equals("")) { + throw new ConfigurationException("The ResultPath annotation must have either" + + " a value or property specified."); + } + + String property = resultPathAnnotation.property(); + if (property.equals("")) { + localResultPath = resultPathAnnotation.value(); + } else { + try { + ResourceBundle strutsBundle = ResourceBundle.getBundle("struts"); + localResultPath = strutsBundle.getString(property); + } catch (Exception e) { + throw new ConfigurationException("The action class [" + actionClass + "] defines" + + " a @ResultPath annotation and a property definition however the" + + " struts.properties could not be found in the classpath using ResourceBundle" + + " OR the bundle exists but the property [" + property + "] is not defined" + + " in the file.", e); + } + } + } + + return localResultPath; + } + + /** + * {@inheritDoc} + */ + public String determineResultPath(ActionConfig actionConfig) { + if (actionConfig == null) { + return resultPath; + } + + try { + return determineResultPath(Class.forName(actionConfig.getClassName())); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Invalid action class configuration that references an unknown " + + "class named [" + actionConfig.getClassName() + "]", e); + } + } + + /** + * {@inheritDoc} + */ + public Map getResultTypesByExtension(PackageConfig packageConfig) { + Map results = packageConfig.getAllResultTypeConfigs(); + + Map resultsByExtension = new HashMap(); + resultsByExtension.put("jsp", results.get("dispatcher")); + resultsByExtension.put("vm", results.get("velocity")); + resultsByExtension.put("ftl", results.get("freemarker")); + // Issue 22 - Add html and htm as default result extensions + resultsByExtension.put("html", results.get("dispatcher")); + resultsByExtension.put("htm", results.get("dispatcher")); + return resultsByExtension; + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultActionNameBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultActionNameBuilder.java new file mode 100644 index 000000000..f0e028c98 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultActionNameBuilder.java @@ -0,0 +1,74 @@ +/* + * $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; + +import com.opensymphony.xwork2.inject.Inject; + +/** + *

+ * This class strips the word Action from the end of the class name + * and possibly lowercases the name as well depending on the value of the + * constant struts.convention.action.name.lowercase. If the + * constant is set to true, this class will lowercase all + * action names. + *

+ */ +public class DefaultActionNameBuilder implements ActionNameBuilder { + private String actionSuffix = "Action"; + private boolean lowerCase; + + @Inject + public DefaultActionNameBuilder(@Inject(value="struts.convention.action.name.lowercase") String lowerCase) { + this.lowerCase = Boolean.parseBoolean(lowerCase); + } + + /** + * @param actionSuffix (Optional) Classes that end with these value will be mapped as actions + * (defaults to "Action") + */ + @Inject(value = "struts.convention.action.suffix", required = false) + public void setActionSuffix(String actionSuffix) { + if (!StringTools.isTrimmedEmpty(actionSuffix)) { + this.actionSuffix = actionSuffix; + } + } + + public String build(String className) { + String actionName = className; + + // Truncate Action suffix if found + if (actionName.endsWith(actionSuffix)) { + actionName = actionName.substring(0, actionName.length() - actionSuffix.length()); + } + + // Force initial letter of action to lowercase, if desired + if ((lowerCase) && (actionName.length() > 1)) { + int lowerPos = actionName.lastIndexOf('/') + 1; + StringBuilder sb = new StringBuilder(); + sb.append(actionName.substring(0, lowerPos)); + sb.append(Character.toLowerCase(actionName.charAt(lowerPos))); + sb.append(actionName.substring(lowerPos + 1)); + actionName = sb.toString(); + } + + return actionName; + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultInterceptorMapBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultInterceptorMapBuilder.java new file mode 100644 index 000000000..0eed1ea2a --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultInterceptorMapBuilder.java @@ -0,0 +1,109 @@ +/* + * $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; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.AnnotationTools; +import org.apache.struts2.convention.annotation.InterceptorRef; +import org.apache.struts2.convention.annotation.InterceptorRefs; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.providers.InterceptorBuilder; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.AnnotationUtils; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + *

+ * Builds interceptor mappings from annotations. + *

+ */ +public class DefaultInterceptorMapBuilder implements InterceptorMapBuilder { + private static final Logger LOG = LoggerFactory + .getLogger(DefaultInterceptorMapBuilder.class); + + private Configuration configuration; + + public List build(Class actionClass, PackageConfig.Builder builder, + String actionName, Action annotation) { + List interceptorList = new ArrayList( + 10); + + //from @InterceptorRefs annotation + InterceptorRefs interceptorRefs = AnnotationTools.findAnnotation(actionClass, InterceptorRefs.class); + if (interceptorRefs != null) + interceptorList.addAll(build(interceptorRefs.value(), actionName, builder)); + + //from @InterceptorRef annotation + InterceptorRef interceptorRef = AnnotationTools.findAnnotation(actionClass, InterceptorRef.class); + if (interceptorRef != null) + interceptorList.addAll(build(new InterceptorRef[] {interceptorRef}, actionName, builder)); + + //from @Action annotation + if (annotation != null) { + InterceptorRef[] interceptors = annotation.interceptorRefs(); + if (interceptors != null) { + interceptorList.addAll(build(interceptors, actionName, builder)); + } + } + + return interceptorList; + } + + protected List build(InterceptorRef[] interceptors, String actionName, PackageConfig.Builder builder) { + List interceptorList = new ArrayList( + 10); + for (InterceptorRef interceptor : interceptors) { + if (LOG.isTraceEnabled()) + LOG.trace("Adding interceptor [#0] to [#1]", + interceptor.value(), actionName); + Map params = StringTools.createParameterMap(interceptor + .params()); + interceptorList.addAll(buildInterceptorList(builder, + interceptor, params)); + } + + return interceptorList; + } + + protected List buildInterceptorList( + PackageConfig.Builder builder, InterceptorRef ref, Map params) { + return InterceptorBuilder.constructInterceptorReference(builder, ref + .value(), params, builder.build().getLocation(), + (ObjectFactory) configuration.getContainer().getInstance( + ObjectFactory.class)); + } + + @Inject + public void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } +} 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 new file mode 100644 index 000000000..09903d7bc --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/DefaultResultMapBuilder.java @@ -0,0 +1,462 @@ +/* + * $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; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; + +import org.apache.struts2.convention.annotation.Result; +import org.apache.struts2.convention.annotation.Results; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.finder.ResourceFinder; +import com.opensymphony.xwork2.util.finder.Test; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + *

+ * This class implements the ResultMapBuilder and traverses the web + * application content directory looking for reasonably named JSPs and + * other result types as well as annotations. This naming is in this + * form: + *

+ * + *
+ * /resultPath/namespace/action-<result>.jsp
+ * 
+ * + *

+ * If there are any files in these locations than a result is created + * for each one and the result names is the last portion of the file + * name up to the . (dot). + *

+ * + *

+ * When results are found, new ResultConfig instances are created. The + * result config that is created has a number of thing to be aware of: + *

+ * + *
    + *
  • The result config contains the location parameter, which is + * required by most result classes to figure out where to find the result. + * In addition, the config has all the parameters from the default result-type + * configuration.
  • + *
+ * + *

+ * After loading the files in the web application, this class will then + * use any annotations on the action class to override what was found in + * the web application files. These annotations are the {@link Result} + * and {@link Results} annotations. These two annotations allow an action + * to supply different or non-forward based results for specific return + * values of an action method. + *

+ * + *

+ * The result path used by this class for locating JSPs and other + * such result files can be set using the Struts2 constant named + * struts.convention.result.path or using the + * {@link org.apache.struts2.convention.annotation.ResultPath} + * annotation. + *

+ * + *

+ * This class will also locate and configure Results in the classpath, + * including velocity and FreeMarker templates inside the classpath. + *

+ * + *

+ * All results that are conigured from resources are given a type corresponding + * to the resources extension. The extensions and types are given in the + * table below: + *

+ * + * + * + *
ExtensionType
.jspdispatcher + *
.htmldispatcher + *
.htmdispatcher + *
.vmvelocity + *
.ftlfreemarker + *
+ */ +public class DefaultResultMapBuilder implements ResultMapBuilder { + private static final Logger LOG = LoggerFactory.getLogger(DefaultResultMapBuilder.class); + private final ServletContext servletContext; + private Set relativeResultTypes; + private ConventionsService conventionsService; + private boolean flatResultLayout = true; + + /** + * Constructs the SimpleResultMapBuilder using the given result location. + * + * @param servletContext The ServletContext for finding the resources of the web application. + * @param conventionsService The service used to assist in finding configuration and conventions. + * @param relativeResultTypes The list of result types that can have locations that are relative + * and the result location (which is the resultPath plus the namespace) prepended to them. + */ + @Inject + public DefaultResultMapBuilder(ServletContext servletContext, ConventionsService conventionsService, + @Inject("struts.convention.relative.result.types") String relativeResultTypes) { + this.servletContext = servletContext; + this.relativeResultTypes = new HashSet(Arrays.asList(relativeResultTypes.split("\\s*[,]\\s*"))); + this.conventionsService = conventionsService; + } + + /** + * @param flatResultLayout If 'true' result resources will be expected to be in the form + * ${namespace}/${actionName}-${result}.${extension}, otherwise in the form + * ${namespace}/${actionName}/${result}.${extension} + */ + @Inject("struts.convention.result.flatLayout") + public void setFlatResultLayout(String flatResultLayout) { + this.flatResultLayout = "true".equals(flatResultLayout); + } + + /** + * {@inheritDoc} + */ + public Map build(Class actionClass, + org.apache.struts2.convention.annotation.Action annotation, String actionName, + PackageConfig packageConfig) { + + // Get the default result location from the annotation or configuration + String defaultResultPath = conventionsService.determineResultPath(actionClass); + + // Add a slash + if (!defaultResultPath.endsWith("/")) { + defaultResultPath = defaultResultPath + "/"; + } + + // Check for resources with the action name + final String namespace = packageConfig.getNamespace(); + if (namespace != null && namespace.startsWith("/")) { + defaultResultPath = defaultResultPath + namespace.substring(1); + } else if (namespace != null) { + defaultResultPath = defaultResultPath + namespace; + } + + if (LOG.isTraceEnabled()) { + LOG.trace("Using final calculated namespace [#0]", namespace); + } + + // Add that ending slash for concatentation + if (!defaultResultPath.endsWith("/")) { + defaultResultPath += "/"; + } + + String resultPrefix = defaultResultPath + actionName; + + Map results = new HashMap(); + Map resultsByExtension = conventionsService.getResultTypesByExtension(packageConfig); + createFromResources(actionClass, results, defaultResultPath, resultPrefix, actionName, + packageConfig, resultsByExtension); + if (annotation != null && annotation.results() != null && annotation.results().length > 0) { + createFromAnnotations(results, defaultResultPath, packageConfig, annotation.results(), + actionClass, resultsByExtension); + } + + Results resultsAnn = actionClass.getAnnotation(Results.class); + if (resultsAnn != null) { + createFromAnnotations(results, defaultResultPath, packageConfig, resultsAnn.value(), + actionClass, resultsByExtension); + } + + Result resultAnn = actionClass.getAnnotation(Result.class); + if (resultAnn != null) { + createFromAnnotations(results, defaultResultPath, packageConfig, new Result[]{resultAnn}, + actionClass, resultsByExtension); + } + + return results; + } + + /** + * Creates any result types from the resources available in the web application. This scans the + * web application resources using the servlet context. + * + * @param actionClass The action class the results are being built for. + * @param results The results map to put the result configs created into. + * @param resultPath The calculated path to the resources. + * @param resultPrefix The prefix for the result. This is usually /resultPath/actionName. + * @param actionName The action name which is used only for logging in this implementation. + * @param packageConfig The package configuration which is passed along in order to determine + * @param resultsByExtension The map of extensions to result type configuration instances. + */ + protected void createFromResources(Class actionClass, Map results, + final String resultPath, final String resultPrefix, final String actionName, + PackageConfig packageConfig, Map resultsByExtension) { + if (LOG.isTraceEnabled()) { + LOG.trace("Searching for results in the Servlet container at [#0]" + + " with result prefix of [#1]", resultPath, resultPrefix); + } + + // Build from web application using the ServletContext + @SuppressWarnings("unchecked") + Set paths = servletContext.getResourcePaths(flatResultLayout ? resultPath : resultPrefix); + if (paths != null) { + for (String path : paths) { + if (LOG.isTraceEnabled()) { + LOG.trace("Processing resource path [#0]", path); + } + + makeResults(actionClass, path, resultPrefix, results, packageConfig, resultsByExtension); + } + } + + // Building from the classpath + String classPathLocation = resultPath.startsWith("/") ? + resultPath.substring(1, resultPath.length()) : resultPath; + if (LOG.isTraceEnabled()) { + LOG.trace("Searching for results in the class path at [#0]" + + " with a result prefix of [#1] and action name [#2]", classPathLocation, resultPrefix, + actionName); + } + + ResourceFinder finder = new ResourceFinder(classPathLocation); + try { + Map matches = finder.getResourcesMap(""); + if (matches != null) { + Test resourceTest = getResourceTest(resultPath, actionName); + for (Map.Entry entry : matches.entrySet()) { + if (resourceTest.test(entry.getValue())) { + if (LOG.isTraceEnabled()) { + LOG.trace("Processing URL [#0]", entry.getKey()); + } + + String urlStr = entry.getValue().toString(); + int index = urlStr.lastIndexOf(resultPrefix); + String path = urlStr.substring(index); + makeResults(actionClass, path, resultPrefix, results, packageConfig, resultsByExtension); + } + + } + } + } catch (IOException ex) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to scan directory [#0] for results", ex, classPathLocation); + } + + } + + private Test getResourceTest(final String resultPath, final String actionName) { + return new Test() { + public boolean test(URL url) { + String urlStr = url.toString(); + int index = urlStr.lastIndexOf(resultPath); + String path = urlStr.substring(index + resultPath.length()); + return path.startsWith(actionName); + } + }; + } + + /** + * Makes all the results for the given path. + * + * @param actionClass The action class the results are being built for. + * @param path The path to build the result for. + * @param resultPrefix The is the result prefix which is the result location plus the action name. + * This is used to determine if the path contains a result code or not. + * @param results The Map to place the result(s) + * @param packageConfig The package config the results belong to. + * @param resultsByExtension The map of extensions to result type configuration instances. + */ + protected void makeResults(Class actionClass, String path, String resultPrefix, + Map results, PackageConfig packageConfig, + Map resultsByExtension) { + if (path.startsWith(resultPrefix)) { + int indexOfDot = path.indexOf('.', resultPrefix.length()); + + // This case is when the path doesn't contain a result code + if (indexOfDot == resultPrefix.length() || !flatResultLayout) { + if (LOG.isTraceEnabled()) { + LOG.trace("The result file [#0] has no result code and therefore" + + " will be associated with success, input and error by default. This might" + + " be overridden by another result file or an annotation.", path); + } + + if (!results.containsKey(Action.SUCCESS)) { + ResultConfig success = createResultConfig(actionClass, + new ResultInfo(Action.SUCCESS, path, packageConfig, resultsByExtension), + packageConfig, null); + results.put(Action.SUCCESS, success); + } + if (!results.containsKey(Action.INPUT)) { + ResultConfig input = createResultConfig(actionClass, + new ResultInfo(Action.INPUT, path, packageConfig, resultsByExtension), + packageConfig, null); + results.put(Action.INPUT, input); + } + if (!results.containsKey(Action.ERROR)) { + ResultConfig error = createResultConfig(actionClass, + new ResultInfo(Action.ERROR, path, packageConfig, resultsByExtension), + packageConfig, null); + results.put(Action.ERROR, error); + } + + // This case is when the path contains a result code + } else if (indexOfDot > resultPrefix.length()) { + if (LOG.isTraceEnabled()) { + LOG.trace("The result file [#0] has a result code and therefore" + + " will be associated with only that result code.", path); + } + + String resultCode = path.substring(resultPrefix.length() + 1, indexOfDot); + ResultConfig result = createResultConfig(actionClass, + new ResultInfo(resultCode, path, packageConfig, resultsByExtension), + packageConfig, null); + results.put(resultCode, result); + } + } + } + + protected void createFromAnnotations(Map resultConfigs, + String resultPath, PackageConfig packageConfig, Result[] results, + 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); + } + } + } + + /** + * Creates the result configuration for the single result annotation. This will use all the + * information from the annotation and anything that isn't specified will be fetched from the + * PackageConfig defaults (if they exist). + * + * @param actionClass The action class the results are being built for. + * @param info The result info that is used to create the ResultConfig instance. + * @param packageConfig The PackageConfig to use to fetch defaults for result and parameters. + * @param result (Optional) The result annotation to pull additional information from. + * @return The ResultConfig or null if the Result annotation is given and the annotation is + * targeted to some other action than this one. + */ + @SuppressWarnings(value = {"unchecked"}) + protected ResultConfig createResultConfig(Class actionClass, ResultInfo info, + PackageConfig packageConfig, Result result) { + // Look up by the type that was determined from the annotation or by the extension in the + // ResultInfo class + ResultTypeConfig resultTypeConfig = packageConfig.getAllResultTypeConfigs().get(info.type); + if (resultTypeConfig == null) { + throw new ConfigurationException("The Result type [" + info.type + "] which is" + + " defined in the Result annotation on the class [" + actionClass + "] or determined" + + " by the file extension or is the default result type for the PackageConfig of the" + + " action, could not be found as a result-type defined for the Struts/XWork package [" + + packageConfig.getName() + "]"); + } + + // Add the default parameters for the result type config (if any) + HashMap params = new HashMap(); + if (resultTypeConfig.getParams() != null) { + params.putAll(resultTypeConfig.getParams()); + } + + // Handle the annotation + if (result != null) { + params.putAll(StringTools.createParameterMap(result.params())); + } + + // Map the location to the default param for the result or a param named location + if (info.location != null) { + String defaultParamName = resultTypeConfig.getDefaultResultParam(); + if (!params.containsKey(defaultParamName)) { + params.put(defaultParamName, info.location); + } + } else if (LOG.isWarnEnabled()){ + LOG.warn("Result [#0] for action class [#1] is missing the location", info.name, + actionClass.getSimpleName()); + } + + return new ResultConfig.Builder(info.name, resultTypeConfig.getClassName()).addParams(params).build(); + } + + class ResultInfo { + public final String name; + public final String location; + public final String type; + + public ResultInfo(String name, String location, PackageConfig packageConfig, + Map resultsByExtension) { + this.name = name; + this.location = location; + this.type = determineType(location, packageConfig, resultsByExtension); + } + + public ResultInfo(Result result, PackageConfig packageConfig, String resultPath, + Class actionClass, Map resultsByExtension) { + this.name = result.name(); + if (!StringTools.isTrimmedEmpty(result.type())) { + this.type = result.type(); + } else if (!StringTools.isTrimmedEmpty(result.location())) { + this.type = determineType(result.location(), packageConfig, resultsByExtension); + } else { + throw new ConfigurationException("The action class [" + actionClass + "] contains a " + + "result annotation that has no type parameter and no location parameter. One of " + + "these must be defined."); + } + + // See if we can handle relative locations or not + if (!StringTools.isTrimmedEmpty(result.location())) { + if (relativeResultTypes.contains(this.type) && !result.location().startsWith("/")) { + location = resultPath + result.location(); + } else { + location = result.location(); + } + } else { + this.location = null; + } + } + + String determineType(String location, PackageConfig packageConfig, + Map resultsByExtension) { + int indexOfDot = location.lastIndexOf("."); + if (indexOfDot > 0) { + String extension = location.substring(indexOfDot + 1); + ResultTypeConfig resultTypeConfig = resultsByExtension.get(extension); + if (resultTypeConfig != null) { + return resultTypeConfig.getName(); + } else + throw new ConfigurationException("Unable to find a result type for extension [" + extension + "] " + + "in location attribute [" + location + "]."); + } else { + return packageConfig.getFullDefaultResultType(); + } + } + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/InterceptorMapBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/InterceptorMapBuilder.java new file mode 100644 index 000000000..c57235f5f --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/InterceptorMapBuilder.java @@ -0,0 +1,48 @@ +/* + * $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; + +import java.util.List; + +import org.apache.struts2.convention.annotation.Action; + +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.PackageConfig; + +/** + *

+ * This interface defines how interceptors are built from + * annotations. + *

+ */ +public interface InterceptorMapBuilder { + /** + * Builds the interceptor configurations given the action information. + * + * @param actionClass The class of the action. + * @param annotation The action annotation. + * @param actionName The action name. + * @param builder The package configuration builder. + * @return The mapping of the interceptors. If there were none found + * then this should return an empty List. + */ + List build(Class actionClass, PackageConfig.Builder builder, String actionName, Action annotation); +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java new file mode 100644 index 000000000..370ba8ed8 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java @@ -0,0 +1,797 @@ +/* + * $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; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.Actions; +import org.apache.struts2.convention.annotation.AnnotationTools; +import org.apache.struts2.convention.annotation.DefaultInterceptorRef; +import org.apache.struts2.convention.annotation.ExceptionMapping; +import org.apache.struts2.convention.annotation.ExceptionMappings; +import org.apache.struts2.convention.annotation.Namespace; +import org.apache.struts2.convention.annotation.Namespaces; +import org.apache.struts2.convention.annotation.ParentPackage; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.finder.ClassFinder; +import com.opensymphony.xwork2.util.finder.Test; +import com.opensymphony.xwork2.util.finder.UrlSet; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + *

+ * This class implements the ActionConfigBuilder interface. + *

+ */ +public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { + private static final Logger LOG = LoggerFactory.getLogger(PackageBasedActionConfigBuilder.class); + private final Configuration configuration; + private final ActionNameBuilder actionNameBuilder; + private final ResultMapBuilder resultMapBuilder; + private final InterceptorMapBuilder interceptorMapBuilder; + private final ObjectFactory objectFactory; + private final String defaultParentPackage; + private final boolean redirectToSlash; + private String[] actionPackages; + private String[] excludePackages; + private String[] packageLocators; + private String[] excludeJars; + private String packageLocatorsBasePackage; + private boolean disableJarScanning = true; + private boolean disableActionScanning = false; + private boolean disablePackageLocatorsScanning = false; + private String actionSuffix = "Action"; + private boolean checkImplementsAction = true; + private boolean mapAllMatches = false; + + /** + * Constructs actions based on a list of packages. + * + * @param configuration The XWork configuration that the new package configs and action configs + * are added to. + * @param actionNameBuilder The action name builder used to convert action class names to action + * names. + * @param resultMapBuilder The result map builder used to create ResultConfig mappings for each + * action. + * @param interceptorMapBuilder The interceptor map builder used to create InterceptorConfig mappings for each + * action. + * @param objectFactory The ObjectFactory used to create the actions and such. + * @param redirectToSlash A boolean parameter that controls whether or not this will create an + * action for indexes. If this is set to true, index actions are not created because + * the unknown handler will redirect from /foo to /foo/. The only action that is created + * is to the empty action in the namespace (e.g. the namespace /foo and the action ""). + * @param defaultParentPackage The default parent package for all the configuration. + */ + @Inject + public PackageBasedActionConfigBuilder(Configuration configuration, ActionNameBuilder actionNameBuilder, + ResultMapBuilder resultMapBuilder, InterceptorMapBuilder interceptorMapBuilder, ObjectFactory objectFactory, + @Inject("struts.convention.redirect.to.slash") String redirectToSlash, + @Inject("struts.convention.default.parent.package") String defaultParentPackage) { + + // Validate that the parameters are okay + this.configuration = configuration; + this.actionNameBuilder = actionNameBuilder; + this.resultMapBuilder = resultMapBuilder; + this.interceptorMapBuilder = interceptorMapBuilder; + this.objectFactory = objectFactory; + this.redirectToSlash = Boolean.parseBoolean(redirectToSlash); + + if (LOG.isTraceEnabled()) { + LOG.trace("Setting action default parent package to [#0]", defaultParentPackage); + } + + this.defaultParentPackage = defaultParentPackage; + } + + /** + * @param disableActionScanning Disable scanning for actions + */ + @Inject(value = "struts.convention.action.disableScanning", required = false) + public void setDisableActionScanning(String disableActionScanning) { + this.disableActionScanning = "true".equals(disableActionScanning); + } + + /** + * @param exlcudeJars Comma separated list of regular expressions of jars to be exluded. + * Ignored if "struts.convention.action.disableJarScanning" is true + */ + @Inject(value = "struts.convention.action.excludeJars", required = false) + public void setExcludeJars(String excludeJars) { + this.excludeJars = excludeJars.split("\\s*[,]\\s*");; + } + + /** + * @param disableJarScanning Disable scanning jar files for actions + */ + @Inject(value = "struts.convention.action.disableJarScanning", required = false) + public void setDisableJarScanning(String disableJarScanning) { + this.disableJarScanning = "true".equals(disableJarScanning); + } + + /** + * @param disableActionScanning If set to true, only the named packages will be scanned + */ + @Inject(value = "struts.convention.package.locators.disable", required = false) + public void setDisablePackageLocatorsScanning(String disablePackageLocatorsScanning) { + this.disablePackageLocatorsScanning = "true".equals(disablePackageLocatorsScanning); + } + + /** + * @param actionPackages (Optional) An optional list of action packages that this should create + * configuration for. + */ + @Inject(value = "struts.convention.action.packages", required = false) + public void setActionPackages(String actionPackages) { + if (!StringTools.isTrimmedEmpty(actionPackages)) { + this.actionPackages = actionPackages.split("\\s*[,]\\s*"); + } + } + + /** + * @param actionPackages (Optional) Map classes that implement com.opensymphony.xwork2.Action + * as actions + */ + @Inject(value = "struts.convention.action.checkImplementsAction", required = false) + public void setCheckImplementsAction(String checkImplementsAction) { + this.checkImplementsAction = "true".equals(checkImplementsAction); + } + + /** + * @param actionSuffix (Optional) Classes that end with these value will be mapped as actions + * (defaults to "Action") + */ + @Inject(value = "struts.convention.action.suffix", required = false) + public void setActionSuffix(String actionSuffix) { + if (!StringTools.isTrimmedEmpty(actionSuffix)) { + this.actionSuffix = actionSuffix; + } + } + + /** + * @param excludePackages (Optional) A list of packages that should be skipped when building + * configuration. + */ + @Inject(value = "struts.convention.exclude.packages", required = false) + public void setExcludePackages(String excludePackages) { + if (!StringTools.isTrimmedEmpty(excludePackages)) { + this.excludePackages = excludePackages.split("\\s*[,]\\s*"); + } + } + + /** + * @param packageLocators (Optional) A list of names used to find action packages. + */ + @Inject(value = "struts.convention.package.locators", required = false) + public void setPackageLocators(String packageLocators) { + this.packageLocators = packageLocators.split("\\s*[,]\\s*"); + } + + /** + * @param packageLocatorsBasePackage (Optional) If set, only packages that start with this + * name will be scanned for actions. + */ + @Inject(value = "struts.convention.package.locators.basePackage", required = false) + public void setPackageLocatorsBase(String packageLocatorsBasePackage) { + this.packageLocatorsBasePackage = packageLocatorsBasePackage; + } + + /** + * @param mapAllMatches (Optional) Map actions that match the "*${Suffix}" pattern + * even if they don't have a default method. The mapping from + * the url to the action will be delegated the action mapper. + */ + @Inject(value = "struts.convention.action.mapAllMatches", required = false) + public void setMapAllMatches(String mapAllMatches) { + this.mapAllMatches = "true".equals(mapAllMatches); + } + + /** + * Builds the action configurations by loading all classes in the packages specified by the + * property struts.convention.action.packages and then figuring out which classes implement Action + * or have Action in their name. Next, if this class is in a Java package that hasn't been + * inspected a new PackageConfig (XWork) is created for that Java package using the Java package + * name. This will contain all the ActionConfigs for all the Action classes that are discovered + * within that Java package. Next, each class is inspected for the {@link ParentPackage} + * annotation which is used to control the parent package for a specific action. Lastly, the + * {@link ResultMapBuilder} is used to create ResultConfig instances of the action. + */ + public void buildActionConfigs() { + if (!disableActionScanning ) { + if (actionPackages == null && packageLocators == null) { + throw new ConfigurationException("At least a list of action packages or action package locators " + + "must be given using one of the properties [struts.convention.action.packages] or " + + "[struts.convention.package.locators]"); + } + + if (LOG.isTraceEnabled()) { + LOG.trace("Loading action configurations"); + if (actionPackages != null) { + LOG.trace("Actions being loaded from action packages " + Arrays.asList(actionPackages)); + } + if (packageLocators != null) { + LOG.trace("Actions being loaded using package locators " + Arrays.asList(packageLocators)); + } + if (excludePackages != null) { + LOG.trace("Excluding actions from packages " + Arrays.asList(excludePackages)); + } + } + + Set classes = findActions(); + buildConfiguration(classes); + } + } + + @SuppressWarnings("unchecked") + protected Set findActions() { + Set classes = new HashSet(); + try { + if (actionPackages != null || (packageLocators != null && !disablePackageLocatorsScanning)) { + ClassFinder finder = new ClassFinder(getClassLoader(), buildUrlSet().getUrls(), true); + + // named packages + if (actionPackages != null) { + for (String packageName : actionPackages) { + Test test = getPackageFinderTest(packageName); + classes.addAll(finder.findClasses(test)); + } + } + + //package locators + if (packageLocators != null && !disablePackageLocatorsScanning) { + for (String packageLocator : packageLocators) { + Test test = getPackageLocatorTest(packageLocator); + classes.addAll(finder.findClasses(test)); + } + } + } + } catch (Exception ex) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to scan named packages", ex); + } + + return classes; + } + + private UrlSet buildUrlSet() throws IOException { + UrlSet urlSet = new UrlSet(getClassLoader()); + + urlSet = urlSet.exclude(ClassLoader.getSystemClassLoader().getParent()); + urlSet = urlSet.excludeJavaExtDirs(); + urlSet = urlSet.excludeJavaEndorsedDirs(); + urlSet = urlSet.excludeJavaHome(); + urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", "")); + urlSet = urlSet.exclude(".*/JavaVM.framework/.*"); + + if (disableJarScanning) { + urlSet = urlSet.exclude(".*?jar(!/)?"); + } else if (excludeJars != null) { + for (String pattern : excludeJars) { + urlSet = urlSet.exclude(pattern.trim()); + } + } + + return urlSet; + } + + private ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + protected Test getPackageFinderTest(final String packageName) { + // so "my.package" does not match "my.package2.test" + final String strictPackageName = packageName + "."; + return new Test() { + public boolean test(ClassFinder.ClassInfo classInfo) { + String classPackageName = classInfo.getPackageName(); + boolean inPackage = classPackageName.equals(packageName) || classPackageName.startsWith(strictPackageName); + boolean nameMatches = classInfo.getName().endsWith(actionSuffix); + + try { + return inPackage && (nameMatches || (checkImplementsAction && com.opensymphony.xwork2.Action.class.isAssignableFrom(classInfo.get()))); + } catch (ClassNotFoundException ex) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to load class [#0]", ex, classInfo.getName()); + return false; + } + } + }; + } + + protected Test getPackageLocatorTest(final String packageLocator) { + return new Test() { + public boolean test(ClassFinder.ClassInfo classInfo) { + String packageName = classInfo.getPackageName(); + if (packageName.length() > 0 && (packageLocatorsBasePackage == null || packageName.startsWith(packageLocatorsBasePackage))) { + String[] splitted = packageName.split("\\."); + + boolean packageMatches = StringTools.contains(splitted, packageLocator, false); + boolean nameMatches = classInfo.getName().endsWith(actionSuffix); + + try { + return packageMatches && (nameMatches || (checkImplementsAction && com.opensymphony.xwork2.Action.class.isAssignableFrom(classInfo.get()))); + } catch (ClassNotFoundException ex) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to load class [#0]", ex, classInfo.getName()); + return false; + } + } else + return false; + } + }; + } + + @SuppressWarnings("unchecked") + protected void buildConfiguration(Set classes) { + Map packageConfigs = new HashMap(); + + for (Class actionClass : classes) { + // Skip all interfaces, enums, annotations, and abstract classes + if (actionClass.isAnnotation() || actionClass.isInterface() || actionClass.isEnum() || + (actionClass.getModifiers() & Modifier.ABSTRACT) != 0) { + continue; + } + + // Tell the ObjectFactory about this class + try { + objectFactory.getClassInstance(actionClass.getName()); + } catch (ClassNotFoundException e) { + // Impossible + new Throwable().printStackTrace(); + System.exit(1); + } + + // Determine the action package + String actionPackage = actionClass.getPackage().getName(); + if (LOG.isDebugEnabled()) { + LOG.debug("Processing class [#0] in package [#1]", actionClass.getName(), actionPackage); + } + + // Determine the default namespace and action name + List namespaces = determineActionNamespace(actionClass); + for (String namespace : namespaces) { + String defaultActionName = determineActionName(actionClass); + String defaultActionMethod = "execute"; + PackageConfig.Builder defaultPackageConfig = getPackageConfig(packageConfigs, namespace, + actionPackage, actionClass, null); + + // Verify that the annotations have no errors and also determine if the default action + // configuration should still be built or not. + Map> map = getActionAnnotations(actionClass); + Set actionNames = new HashSet(); + if (!map.containsKey(defaultActionMethod) && ReflectionTools.containsMethod(actionClass, defaultActionMethod)) { + boolean found = false; + for (String method : map.keySet()) { + List actions = map.get(method); + for (Action action : actions) { + + // Check if there are duplicate action names in the annotations. + String actionName = action.value().equals(Action.DEFAULT_VALUE) ? defaultActionName : action.value(); + if (actionNames.contains(actionName)) { + throw new ConfigurationException("The action class [" + actionClass + + "] contains two methods with an action name annotation whose value " + + "is the same (they both might be empty as well)."); + } else { + actionNames.add(actionName); + } + + // Check this annotation is the default action + if (action.value().equals(Action.DEFAULT_VALUE)) { + found = true; + } + } + } + + // Build the default + if (!found) { + createActionConfig(defaultPackageConfig, actionClass, defaultActionName, defaultActionMethod, null); + } + } + + // Build the actions for the annotations + for (String method : map.keySet()) { + List actions = map.get(method); + for (Action action : actions) { + PackageConfig.Builder pkgCfg = defaultPackageConfig; + if (action.value().contains("/")) { + pkgCfg = getPackageConfig(packageConfigs, namespace, actionPackage, + actionClass, action); + } + + createActionConfig(pkgCfg, actionClass, defaultActionName, method, action); + } + } + + // some actions will not have any @Action or a default method, like the rest actions + // where the action mapper is the one that finds the right method at runtime + if (map.isEmpty() && mapAllMatches) { + Action actionAnnotation = actionClass.getAnnotation(Action.class); + createActionConfig(defaultPackageConfig, actionClass, defaultActionName, null, actionAnnotation); + } + } + } + + buildIndexActions(packageConfigs); + + // Add the new actions to the configuration + Set packageNames = packageConfigs.keySet(); + for (String packageName : packageNames) { + configuration.addPackageConfig(packageName, packageConfigs.get(packageName).build()); + } + } + + /** + * Determines the namespace(s) for the action based on the action class. If there is a {@link Namespace} + * annotation on the class (including parent classes) or on the package that the class is in, than + * it is used. Otherwise, the Java package name that the class is in is used in conjunction with + * either the struts.convention.action.packages or struts.convention.package.locators + * configuration values. These are used to determine which part of the Java package name should + * be converted into the namespace for the XWork PackageConfig. + * + * @param actionClass The action class. + * @return The namespace or an empty string. + */ + protected List determineActionNamespace(Class actionClass) { + List namespaces = new ArrayList(); + + // Check if there is a class or package level annotation for the namespace + //single namespace + Namespace namespaceAnnotation = AnnotationTools.findAnnotation(actionClass, Namespace.class); + if (namespaceAnnotation != null) { + if (LOG.isTraceEnabled()) { + LOG.trace("Using non-default action namespace from Namespace annotation of [#0]", namespaceAnnotation.value()); + } + + namespaces.add(namespaceAnnotation.value()); + } + + //multiple annotations + Namespaces namespacesAnnotation = AnnotationTools.findAnnotation(actionClass, Namespaces.class); + if (namespacesAnnotation != null) { + if (LOG.isTraceEnabled()) { + StringBuilder sb = new StringBuilder(); + for (Namespace namespace : namespacesAnnotation.value()) + sb.append(namespace.value()).append(","); + sb.deleteCharAt(sb.length() - 1); + LOG.trace("Using non-default action namespaces from Namespaces annotation of [#0]", sb.toString()); + } + + for (Namespace namespace : namespacesAnnotation.value()) + namespaces.add(namespace.value()); + } + + //don't use default if there are annotations + if (!namespaces.isEmpty()) + return namespaces; + + String pkg = actionClass.getPackage().getName(); + String pkgPart = null; + if (actionPackages != null) { + for (String actionPackage : actionPackages) { + if (pkg.startsWith(actionPackage)) { + pkgPart = actionClass.getName().substring(actionPackage.length() + 1); + } + } + } + + if (pkgPart == null && packageLocators != null) { + for (String packageLocator : packageLocators) { + int index = pkg.lastIndexOf(packageLocator); + + // This ensures that the match is at the end, beginning or has a dot on each side of it + if (index >= 0 && (index + packageLocator.length() == pkg.length() || index == 0 || + (pkg.charAt(index - 1) == '.' && pkg.charAt(index + packageLocator.length()) == '.'))) { + pkgPart = actionClass.getName().substring(index + packageLocator.length() + 1); + } + } + } + + if (pkgPart != null) { + final int indexOfDot = pkgPart.lastIndexOf('.'); + if (indexOfDot >= 0) { + String convertedNamespace = actionNameBuilder.build(pkgPart.substring(0, indexOfDot)); + namespaces.add("/" + convertedNamespace.replace('.', '/')); + return namespaces; + } + } + + namespaces.add(""); + return namespaces; + } + + /** + * Converts the class name into an action name using the ActionNameBuilder. + * + * @param actionClass The action class. + * @return The action name. + */ + protected String determineActionName(Class actionClass) { + String actionName = actionNameBuilder.build(actionClass.getSimpleName()); + if (LOG.isTraceEnabled()) { + LOG.trace("Got actionName for class [#0] of [#1]", actionClass.toString(), actionName); + } + + return actionName; + } + + /** + * Locates all of the {@link Actions} and {@link Action} annotations on methods within the Action + * class and its parent classes. + * + * @param actionClass The action class. + * @return The list of annotations or an empty list if there are none. + */ + protected Map> getActionAnnotations(Class actionClass) { + Method[] methods = actionClass.getMethods(); + Map> map = new HashMap>(); + for (Method method : methods) { + Actions actionsAnnotation = method.getAnnotation(Actions.class); + if (actionsAnnotation != null) { + Action[] actionArray = actionsAnnotation.value(); + boolean valuelessSeen = false; + List actions = new ArrayList(); + for (Action ann : actionArray) { + if (ann.value().equals(Action.DEFAULT_VALUE) && !valuelessSeen) { + valuelessSeen = true; + } else if (ann.value().equals(Action.DEFAULT_VALUE)) { + throw new ConfigurationException("You may only add a single Action " + + "annotation that has no value parameter."); + } + + actions.add(ann); + } + + map.put(method.getName(), actions); + } else { + Action ann = method.getAnnotation(Action.class); + if (ann != null) { + map.put(method.getName(), Arrays.asList(ann)); + } + } + } + + return map; + } + + /** + * Creates a single ActionConfig object. + * + * @param pkgCfg The package the action configuration instance will belong to. + * @param actionClass The action class. + * @param actionName The name of the action. + * @param actionMethod The method that the annotation was on (if the annotation is not null) or + * the default method (execute). + * @param annotation The ActionName annotation that might override the action name and possibly + */ + protected void createActionConfig(PackageConfig.Builder pkgCfg, Class actionClass, String actionName, + String actionMethod, Action annotation) { + if (annotation != null) { + actionName = annotation.value() != null && annotation.value().equals(Action.DEFAULT_VALUE) ? + actionName : annotation.value(); + actionName = StringTools.lastToken(actionName, "/"); + } + + ActionConfig.Builder actionConfig = new ActionConfig.Builder(pkgCfg.getName(), + actionName, actionClass.getName()); + actionConfig.methodName(actionMethod); + + if (LOG.isDebugEnabled()) { + LOG.debug("Creating action config for class [#0], name [#1] and package name [#2] in namespace [#3]", + actionClass.toString(), actionName, pkgCfg.getName(), pkgCfg.getNamespace()); + } + + //build interceptors + List interceptors = interceptorMapBuilder.build(actionClass, pkgCfg, actionName, annotation); + actionConfig.addInterceptors(interceptors); + + //build results + Map results = resultMapBuilder.build(actionClass, annotation, actionName, pkgCfg.build()); + actionConfig.addResultConfigs(results); + + //add params + if (annotation != null) + actionConfig.addParams(StringTools.createParameterMap(annotation.params())); + + //add exception mappings from annotation + if (annotation != null && annotation.exceptionMappings() != null) + actionConfig.addExceptionMappings(buildExceptionMappings(annotation.exceptionMappings(), actionName)); + + //add exception mapping from class + ExceptionMappings exceptionMappings = actionClass.getAnnotation(ExceptionMappings.class); + if (exceptionMappings != null) + actionConfig.addExceptionMappings(buildExceptionMappings(exceptionMappings.value(), actionName)); + + //add + pkgCfg.addActionConfig(actionName, actionConfig.build()); + + //check if an action with the same name exists on that package (from XML config probably) + PackageConfig existingPkg = configuration.getPackageConfig(pkgCfg.getName()); + if (existingPkg != null) { + // there is a package already with that name, check action + ActionConfig existingActionConfig = existingPkg.getActionConfigs().get(actionName); + if (existingActionConfig != null && LOG.isWarnEnabled()) + LOG.warn("Duplicated action definition in package [#0] with name [#1]. First definition was loaded from [#3]", pkgCfg.getName(), actionName, existingActionConfig.getLocation().toString()); + } + } + + private List buildExceptionMappings(ExceptionMapping[] exceptions, String actionName) { + List exceptionMappings = new ArrayList(); + + for (ExceptionMapping exceptionMapping : exceptions) { + if (LOG.isTraceEnabled()) + LOG.trace("Mapping exception [#0] to result [#1] for action [#2]", exceptionMapping.exception(), + exceptionMapping.result(), actionName); + ExceptionMappingConfig.Builder builder = new ExceptionMappingConfig.Builder(null, exceptionMapping + .exception(), exceptionMapping.result()); + if (exceptionMapping.params() != null) + builder.addParams(StringTools.createParameterMap(exceptionMapping.params())); + exceptionMappings.add(builder.build()); + } + + return exceptionMappings; + } + + private PackageConfig.Builder getPackageConfig(final Map packageConfigs, + String actionNamespace, final String actionPackage, final Class actionClass, + Action action) { + if (action != null && !action.value().equals(Action.DEFAULT_VALUE)) { + if (LOG.isTraceEnabled()) { + LOG.trace("Using non-default action namespace from the Action annotation of [#0]", action.value()); + } + actionNamespace = StringTools.upToLastToken(action.value(), "/"); + } + + // Next grab the parent annotation from the class + ParentPackage parent = AnnotationTools.findAnnotation(actionClass, ParentPackage.class); + String parentName = null; + if (parent != null) { + if (LOG.isTraceEnabled()) { + LOG.trace("Using non-default parent package from annotation of [#0]", parent.value()); + } + + parentName = parent.value(); + } + + // Finally use the default + if (parentName == null) { + parentName = defaultParentPackage; + } + + if (parentName == null) { + throw new ConfigurationException("Unable to determine the parent XWork package for the action class [" + + actionClass.getName() + "]"); + } + + PackageConfig parentPkg = configuration.getPackageConfig(parentName); + if (parentPkg == null) { + throw new ConfigurationException("Unable to locate parent package [" + parentName + "]"); + } + + // Grab based on package-namespace and if it exists, we need to ensure the existing one has + // the correct parent package. If not, we need to create a new package config + String name = actionPackage + "#" + parentPkg.getName() + "#" + actionNamespace; + PackageConfig.Builder pkgConfig = packageConfigs.get(name); + if (pkgConfig == null) { + pkgConfig = new PackageConfig.Builder(name).namespace(actionNamespace).addParent(parentPkg); + packageConfigs.put(name, pkgConfig); + + //check for @DefaultInterceptorRef in the package + DefaultInterceptorRef defaultInterceptorRef = AnnotationTools.findAnnotation(actionClass, DefaultInterceptorRef.class); + if (defaultInterceptorRef != null) { + pkgConfig.defaultInterceptorRef(defaultInterceptorRef.value()); + + if (LOG.isTraceEnabled()) + LOG.trace("Setting [#0] as the default interceptor ref for [#1]", defaultInterceptorRef.value(), pkgConfig.getName()); + } + } + + if (LOG.isTraceEnabled()) { + LOG.trace("Created package config named [#0] with a namespace [#1]", name, actionNamespace); + } + + return pkgConfig; + } + + /** + * Determine all the index handling actions and results based on this logic: + * + * 1. Loop over all the namespaces such as /foo and see if it has an action named index + * 2. If an action doesn't exists in the parent namespace of the same name, create an action + * in the parent namespace of the same name as the namespace that points to the index + * action in the namespace. e.g. /foo -> /foo/index + * 3. Create the action in the namespace for empty string if it doesn't exist. e.g. /foo/ + * the action is "" and the namespace is /foo + * + * @param packageConfigs Used to store the actions. + */ + protected void buildIndexActions(Map packageConfigs) { + Map byNamespace = new HashMap(); + Collection values = packageConfigs.values(); + for (PackageConfig.Builder packageConfig : values) { + byNamespace.put(packageConfig.getNamespace(), packageConfig); + } + + // Step #1 + Set namespaces = byNamespace.keySet(); + for (String namespace : namespaces) { + // First see if the namespace has an index action + PackageConfig.Builder pkgConfig = byNamespace.get(namespace); + ActionConfig indexActionConfig = pkgConfig.build().getAllActionConfigs().get("index"); + if (indexActionConfig == null) { + continue; + } + + // Step #2 + if (!redirectToSlash) { + int lastSlash = namespace.lastIndexOf('/'); + if (lastSlash >= 0) { + String parentAction = namespace.substring(lastSlash + 1); + String parentNamespace = namespace.substring(0, lastSlash); + PackageConfig.Builder parent = byNamespace.get(parentNamespace); + if (parent == null || parent.build().getAllActionConfigs().get(parentAction) == null) { + if (parent == null) { + parent = new PackageConfig.Builder(parentNamespace).namespace(parentNamespace). + addParents(pkgConfig.build().getParents()); + packageConfigs.put(parentNamespace, parent); + } + + if (parent.build().getAllActionConfigs().get(parentAction) == null) { + parent.addActionConfig(parentAction, indexActionConfig); + } + } else if (LOG.isTraceEnabled()) { + LOG.trace("The parent namespace [#0] already contains " + + "an action [#1]", parentNamespace, parentAction); + } + } + } + + // Step #3 + if (pkgConfig.build().getAllActionConfigs().get("") == null) { + if (LOG.isTraceEnabled()) { + LOG.trace("Creating index ActionConfig with an action name of [] for the action " + + "class [#0]", indexActionConfig.getClassName()); + } + + pkgConfig.addActionConfig("", indexActionConfig); + } + } + } +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ReflectionTools.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ReflectionTools.java new file mode 100644 index 000000000..d9f89c44b --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ReflectionTools.java @@ -0,0 +1,65 @@ +/* + * $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; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + *

+ * This class has some reflection helpers. + *

+ */ +public class ReflectionTools { + /** + * Determines if the class given contains the method. + * + * @param klass The class to check for the method. + * @param method The method name. + * @param parameterTypes The parameter types of the method. + * @return True if the method exists, false if not. + */ + public static boolean containsMethod(Class klass, String method, Class... parameterTypes) { + try { + klass.getMethod(method, parameterTypes); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Retrieves the annotation from the given method in the given class. + * + * @param klass The class. + * @param methodName The method. + * @param annotationClass The annotation to get. + * @return The annotation or null if it doesn't exist. + */ + public static T getAnnotation(Class klass, String methodName, Class annotationClass) { + try { + Method method = klass.getMethod(methodName); + return method.getAnnotation(annotationClass); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/ResultMapBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/ResultMapBuilder.java new file mode 100644 index 000000000..1457aac9f --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/ResultMapBuilder.java @@ -0,0 +1,50 @@ +/* + * $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; + +import java.util.Map; + +import org.apache.struts2.convention.annotation.Action; + +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; + +/** + *

+ * This interface defines how results are constructed for an Action. + * The action information is supplied and the result is a mapping of + * ResultConfig instances to the result name. + *

+ */ +public interface ResultMapBuilder { + /** + * Builds the result configurations given the action information. + * + * @param actionClass The class of the action. + * @param annotation The action annotation. + * @param actionName The action name. + * @param packageConfig The package configuration that the action will be added to. + * @return The mapping of the result names to the result configurations. If there were none found + * than this should return an empty Map. + */ + Map build(Class actionClass, Action annotation, String actionName, + PackageConfig packageConfig); +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/SEOActionNameBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/SEOActionNameBuilder.java new file mode 100644 index 000000000..b2a093df6 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/SEOActionNameBuilder.java @@ -0,0 +1,94 @@ +/* + * $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; + +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + *

+ * This class converts the class name into a SEO friendly name by recognizing + * camel casing and inserting dashes. This also converts everything to + * lower case if desired. And this will also strip off the word Action + * from the class name. + *

+ */ +public class SEOActionNameBuilder implements ActionNameBuilder { + private static final Logger LOG = LoggerFactory.getLogger(SEOActionNameBuilder.class); + private String actionSuffix = "Action"; + private boolean lowerCase; + private String separator; + + @Inject + public SEOActionNameBuilder(@Inject(value="struts.convention.action.name.lowercase") String lowerCase, + @Inject(value="struts.convention.action.name.separator") String separator) { + this.lowerCase = Boolean.parseBoolean(lowerCase); + this.separator = separator; + } + + /** + * @param actionSuffix (Optional) Classes that end with these value will be mapped as actions + * (defaults to "Action") + */ + @Inject(value = "struts.convention.action.suffix", required = false) + public void setActionSuffix(String actionSuffix) { + if (!StringTools.isTrimmedEmpty(actionSuffix)) { + this.actionSuffix = actionSuffix; + } + } + + public String build(String className) { + String actionName = className; + + // Truncate Action suffix if found + if (actionName.endsWith(actionSuffix)) { + actionName = actionName.substring(0, actionName.length() - actionSuffix.length()); + } + + // Convert to underscores + char[] ca = actionName.toCharArray(); + StringBuilder build = new StringBuilder("" + ca[0]); + boolean lower = true; + for (int i = 1; i < ca.length; i++) { + char c = ca[i]; + if (Character.isUpperCase(c) && lower) { + build.append(separator); + lower = false; + } else if (!Character.isUpperCase(c)) { + lower = true; + } + + build.append(c); + } + + actionName = build.toString(); + if (lowerCase) { + actionName = actionName.toLowerCase(); + } + + if (LOG.isTraceEnabled()) { + LOG.trace("Changed action name from [#0] to [#1]", className, actionName); + } + + return actionName; + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/StringTools.java b/plugins/convention/src/main/java/org/apache/struts2/convention/StringTools.java new file mode 100644 index 000000000..336d9fea9 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/StringTools.java @@ -0,0 +1,86 @@ +/* + * $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; + +import java.util.HashMap; +import java.util.Map; + +import com.opensymphony.xwork2.config.ConfigurationException; + +/** + *

+ * This class is a String helper. + *

+ */ +public class StringTools { + public static boolean isTrimmedEmpty(String s) { + return s == null || s.trim().length() == 0; + } + + public static String lastToken(String str, String s) { + int index = str.lastIndexOf(s); + if (index >= 0) { + return str.substring(index + 1); + } + + return str; + } + + public static boolean contains(String[] strings, String value, boolean ignoreCase) { + if (strings != null) { + for (String string : strings) { + if (string.equals(value) || (ignoreCase && string.equalsIgnoreCase(value))) + return true; + } + } + + return false; + } + + public static String upToLastToken(String str, String s) { + int index = str.lastIndexOf(s); + if (index >= 0) { + return str.substring(0, index); + } + + return ""; + } + + public static Map createParameterMap(String[] parms) { + Map map = new HashMap(); + int subtract = parms.length % 2; + if (subtract != 0) { + throw new ConfigurationException( + "'params' is a string array " + + "and they must be in a key value pair configuration. It looks like you" + + " have specified an odd number of parameters and there should only be an even number." + + " (e.g. params = {\"key\", \"value\"})"); + } + + for (int i = 0; i < parms.length; i = i + 2) { + String key = parms[i]; + String value = parms[i + 1]; + map.put(key, value); + } + + return map; + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Action.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Action.java new file mode 100644 index 000000000..fc8020f4d --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Action.java @@ -0,0 +1,95 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation can be used to control the URL that maps to + * a specific method in an Action class. By default, the method + * that is invoked is the execute method of the action and the + * URL is based on the package and class names. This annotation + * allows developers to change the URL or invoke a different + * method. This also allows developers to specify multiple URLs + * that will be handled by a single class or a single method. + *

+ *

+ * This can also be used via the {@link Actions} annotation + * to associate multiple URLs with a single method. + *

+ *

+ * Here's an example: + *

+ * + *
+ * public class MyAction implements Action {
+ *   {@code @Action("/foo/bar")}
+ *   public String execute() {}
+ * }
+ * 
+ * + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Action { + String DEFAULT_VALUE = "DEFAULT_VALUE"; + + /** + * Allows actions to specify different URLs rather than the default that is based on the package + * and action name. This also allows methods other than execute() to be invoked or multiple URLs + * to map to a single class or a single method to handle multiple URLs. + * + * @return The action URL. + */ + String value() default DEFAULT_VALUE; + + /** + * Allows action methods to specifically control the results for specific return values. These + * results are not used for other method/URL invocations on the action. These are only used for + * the URL that this action is associated with. + * + * @return The results for the action. + */ + Result[] results() default {}; + + /** + * Allows action methods to specify what interceptors must be applied to it. + * @return Interceptors to be applied to the action + */ + InterceptorRef[] interceptorRefs() default {}; + + /** + * @return The parameters passed to the action. This is a list of strings that form a name/value + * pair chain since creating a Map for annotations is not possible. An example would be: + * {"key", "value", "key2", "value2"}. + */ + String[] params() default {}; + + /** + * @return Maps return codes to exceptions. The "exceptions" interceptor must be applied to the action. + */ + ExceptionMapping[] exceptionMappings() default {}; +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Actions.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Actions.java new file mode 100644 index 000000000..d32549e42 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Actions.java @@ -0,0 +1,40 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation allows for multiple {@link Action} annotations + * to be used on a single method. + *

+ * + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Actions { + Action[] value() default {}; +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/AnnotationTools.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/AnnotationTools.java new file mode 100644 index 000000000..cab649fb0 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/AnnotationTools.java @@ -0,0 +1,51 @@ +/* + * $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.annotation; + +import java.lang.annotation.Annotation; + +/** + *

+ * This class provides helper methods for dealing with annotations. + *

+ */ +public class AnnotationTools { + + /** + * Returns the annotation on the given class or the package of the class. This searchs up the + * class hierarchy and the package hierarchy. + * + * @param klass The class to search for the annotation. + * @param annotationClass The Class of the annotation. + * @return The annotation or null. + */ + public static T findAnnotation(Class klass, Class annotationClass) { + T ann = klass.getAnnotation(annotationClass); + while (ann == null && klass != null) { + ann = klass.getPackage().getAnnotation(annotationClass); + if (ann == null) { + klass = klass.getSuperclass(); + } + } + + return ann; + } +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/DefaultInterceptorRef.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/DefaultInterceptorRef.java new file mode 100644 index 000000000..01762d53b --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/DefaultInterceptorRef.java @@ -0,0 +1,39 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines the default interceptor for all actions + * in this package + */ +@Target({ElementType.PACKAGE}) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface DefaultInterceptorRef { + /** + * @return The interceptor name. + */ + String value(); +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ExceptionMapping.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ExceptionMapping.java new file mode 100644 index 000000000..587dc1f6c --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ExceptionMapping.java @@ -0,0 +1,54 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * Adds an exception mapping to an action + *

+ * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ExceptionMapping { + /** + * @return Result name + */ + String result(); + + /** + * @return Class name of the exception to be thrown + */ + String exception(); + + /** + * @return The parameters passed to the exception. This is a list of strings that form a name/value + * pair chain since creating a Map for annotations is not possible. An example would be: + * {"key", "value", "key2", "value2"}. + */ + String[] params() default {}; +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ExceptionMappings.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ExceptionMappings.java new file mode 100644 index 000000000..a517b9276 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ExceptionMappings.java @@ -0,0 +1,44 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation allows a class to define more than one {@link ExceptionMapping} + * annotations. These exception mappings will be on all actions defined in the annotated + * class, they are not global exception mappings. + *

+ * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ExceptionMappings { + /** + * @return Exception mappings + */ + ExceptionMapping[] value(); +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/InterceptorRef.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/InterceptorRef.java new file mode 100644 index 000000000..d427dfc89 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/InterceptorRef.java @@ -0,0 +1,52 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation allows an interceptor to be applied to an action. If + * this annotation is used at the class level, then the interceptor + * will be applied to all actions defined on that class, and will be applied + * before the ones defined at the method level. + *

+ * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface InterceptorRef { + /** + * @return name of the interceptor or interceptor stack + */ + String value(); + + /** + * @return The parameters passed to the interceptor. This is a list of strings that form a name/value + * pair chain, since creating a Map for annotations is not possible. An example would be: + * {"key", "value", "key2", "value2"}. + */ + String[] params() default {}; +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/InterceptorRefs.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/InterceptorRefs.java new file mode 100644 index 000000000..c878760ea --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/InterceptorRefs.java @@ -0,0 +1,40 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation allows a class to define more than one {@link InterceptorRef} + * annotations. + *

+ * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface InterceptorRefs { + InterceptorRef[] value(); +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Namespace.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Namespace.java new file mode 100644 index 000000000..a0c4739c4 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Namespace.java @@ -0,0 +1,93 @@ +/* + * $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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; + +/** + * + *

+ * This annotation defines how actions can modify the namespace + * that they are added to. This overrides the behavior of the + * Convention plugin which by default uses the package names for + * namespaces. Since XWork packages are created by the Convention + * plugin via the Java packages that the actions exist in, there + * is some tricky handling of XWork parent packages and namespaces + * of the XWork packages for the Convention plugin discovered + * actions so that two actions in the same package can specify + * different parents and namespaces without collision. + *

+ * + *

+ * In order to handle this correctly, the name of the XWork + * package that actions are placed into is built using this + * format: + *

+ * + *
+ * <java-package>#<parent-xwork-package>#<namespace>
+ * 
+ * + *

+ * This mechanism will guarantee that two actions in the same + * Java package can specify different parent packages (using the + * {@link org.apache.struts2.convention.annotation.ParentPackage} annotation) + * and namespaces (using this annotation). + *

+ * + *

+ * The value of a Namespace annotation should specify the portion of + * the action URL between the context path and the action name. For + * example: + *

+ *
+ * @Namespace("/careers/job-postings-overview/job-postings")
+ * 
+ * + *

+ * This annotation can also be placed inside the special Java file + * named package-info.java, which allows package + * level annotations. If this is used in this manner it changes the + * default namespace for all actions within that Java package. The + * search order for the namespace of a particular class is therefore: + *

+ * + *
    + *
  1. If the {@link Action} annotation exists within the action and + * specifies a full URI (i.e. it starts with a / character)
  2. + *
  3. Any Namespace annotations placed on individual action classes
  4. + *
  5. Any Namespace annotations placed in the package-info.java file
  6. + *
  7. The namespace as determined using the Java package name and the + * standard convention based naming.
  8. + *
+ * + */ +@Target({ElementType.PACKAGE, ElementType.TYPE}) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface Namespace { + /** + * @return The namespace value. + */ + String value(); +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Namespaces.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Namespaces.java new file mode 100644 index 000000000..e043c030b --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Namespaces.java @@ -0,0 +1,39 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation allows actions to be defined in more than one {@link Namespace} + *

+ * + */ +@Target({ElementType.PACKAGE, ElementType.TYPE}) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface Namespaces { + Namespace[] value(); +} diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ParentPackage.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ParentPackage.java new file mode 100644 index 000000000..7a2c6f81b --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ParentPackage.java @@ -0,0 +1,78 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation allows actions to modify the parent package + * that they are using. Since XWork packages are created by + * the Convention plugin via the Java packages that the actions + * exist in, there is some tricky handling of XWork parent packages + * and namespaces of the XWork packages for discovered + * actions so that two actions in the same package can specify + * different parents and namespaces without collision. + *

+ * + *

+ * In order to handle this correctly, the name of the XWork + * package that actions are placed into is built using this + * format: + *

+ * + *
+ * <java-package>#<parent-xwork-package>#<namespace>
+ * 
+ * + *

+ * This mechanism will guarantee that two actions in the same + * Java package can specify different parent packages (using this + * annotation) and namespaces (using the {@link Namespace} annotation). + *

+ * + *

+ * This annotation can be used directly on Action classes or + * in the package-info.java class in order + * to specify the default XWork parent package for all actions + * in the Java package. The search order for XWork parent packages + * is therefore: + *

+ * + *
    + *
  1. Any ParentPackage annotations placed on individual action classes
  2. + *
  3. Any ParentPackage annotations placed in the package-info.java file
  4. + *
  5. The struts configuration property struts.convention.default.parent.package
  6. + *
+ * + */ +@Target({ElementType.TYPE, ElementType.PACKAGE}) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface ParentPackage { + /** + * @return The parent package. + */ + String value(); +} \ No newline at end of file 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 new file mode 100644 index 000000000..b1aff2291 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Result.java @@ -0,0 +1,95 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation is used to specify non-convention based results for + * the Struts convention handling. This annotation is added to a class + * and can be used to specify the result location for a specific result + * code from an action method. Furthermore, this can also be used to + * handle results only for specific action methods within an action class + * (if there are multiple). + *

+ * + *

+ * When this annotation is used on an action class, it generates results + * that are applicable to all of the actions URLs defined in the class. + * These are considered global results for that class. Here is an example + * of a global result: + *

+ * + *
+ * {@code @Result(name="fail", location="failed.jsp")}
+ * public class MyAction {
+ * }
+ * 
+ * + *

+ * This annotation can also be used inside an {@link Action} annotation + * on specific methods. This usage will define results for that specific + * action URL. Here is an example of an action URL specific result: + *

+ * + *
+ * {@code @Action(results={@Result(name="success", location="/", type="redirect")})}
+ * public String execute() {
+ * }
+ * 
+ * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +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(); + + /** + * @return The location of the result within the web application or anywhere on disk. This location + * can be relative if the type of this result is one of the pre-defined relative result + * types (these default to dispatcher, velocity and freemarker). Or this location can be + * absolute relative to the root of the web application or the classpath (since velocity + * and freemarker templates can be loaded via the classpath). + */ + String location() default ""; + + /** + * @return The type of the result. This is usually setup in the struts.xml or struts-plugin.xml + * and is a simple name that is mapped to a result Class. + */ + String type() default ""; + + /** + * @return The parameters passed to the result. This is a list of strings that form a name/value + * pair chain since creating a Map for annotations is not possible. An example would be: + * {"key", "value", "key2", "value2"}. + */ + String[] params() default {}; +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ResultPath.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ResultPath.java new file mode 100644 index 000000000..320fdc72a --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/ResultPath.java @@ -0,0 +1,75 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation allows the path to the results be to changed on a class + * by class basis. This will favor the property name setting and then the + * value. + *

+ * + *

+ * This is used when locating results for an action. In most cases a result + * is a JSP or some type of template (Velocity for example). In order to + * figure out which results are associated with an action, this class can be + * used to set the base directory that the Convention plugin looks at when + * trying to figure out the correct results. For example, if there is an action: + *

+ * + *
+ * com.example.foo.DoSomething
+ * 
+ * + *

+ * The Convention plugin might find that the namespace is foo and the action + * name is do-something and will need to find the results. Using this annotation + * you can set the base path of the results to something like + * /WEB-INF/jsps so that the Convention plugin will look in the + * web application for files of this pattern: + *

+ * + *
+ * /WEB-INF/jsps/foo/do-something-<resultCode>.ext
+ * 
+ * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.PACKAGE}) +public @interface ResultPath { + /** + * @return The result path to use for this action, instead of the default. + */ + String value() default ""; + + /** + * @return The name of the property from the struts.properties file that contains the result + * path for the action that contains this annotation. This property must be set + * and the struts.properties file must exist in the root of the classpath. + */ + String property() default ""; +} \ No newline at end of file diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Results.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Results.java new file mode 100644 index 000000000..156ef9af5 --- /dev/null +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/Results.java @@ -0,0 +1,40 @@ +/* + * $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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

+ * This annotation allows a class to define more than one {@link Result} + * annotations. + *

+ * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Results { + Result[] value(); +} \ No newline at end of file diff --git a/plugins/convention/src/main/resources/LICENSE.txt b/plugins/convention/src/main/resources/LICENSE.txt new file mode 100644 index 000000000..dd5b3a58a --- /dev/null +++ b/plugins/convention/src/main/resources/LICENSE.txt @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/plugins/convention/src/main/resources/NOTICE.txt b/plugins/convention/src/main/resources/NOTICE.txt new file mode 100644 index 000000000..cd13ec449 --- /dev/null +++ b/plugins/convention/src/main/resources/NOTICE.txt @@ -0,0 +1,5 @@ +Apache Struts +Copyright 2000-2007 The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). \ No newline at end of file diff --git a/plugins/convention/src/main/resources/struts-plugin.xml b/plugins/convention/src/main/resources/struts-plugin.xml new file mode 100644 index 000000000..4fc42efd0 --- /dev/null +++ b/plugins/convention/src/main/resources/struts-plugin.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/convention/src/main/resources/struts.properties b/plugins/convention/src/main/resources/struts.properties new file mode 100644 index 000000000..584a0098c --- /dev/null +++ b/plugins/convention/src/main/resources/struts.properties @@ -0,0 +1,18 @@ +struts.convention.action.excludeJars = .*/activemq-(core|ra)-[\\d.]+.jar(!/)?, \ + .*/catalina.*?jar(!/)?, \ + .*/tomcat.*?jar(!/)?, \ + .*/junit-[\\d.]+.jar(!/)?, \ + .*/log4j-[\\d.]+.jar(!/)?, \ + .*/xwork-[\\d.]+.jar(!/)?, \ + .*/ognl-[\\d.]+.jar(!/)?, \ + .*/aopalliance-[\\d.]+.jar(!/)?, \ + .*/jstl-[\\d.]+.jar(!/)?, \ + .*/dwr-[\\d.]+.jar(!/)?, \ + .*/freemarker-[\\d.]+.jar(!/)?, \ + .*/servlet-api-[\\d.]+.jar(!/)?, \ + .*/sitemesh-[\\d.]+.jar(!/)?, \ + .*/commons-(beanutils|el|digester|fileupload|codec|chain|logging|cli|pool|lang|collections|dbcp)-[\\d.]+.jar(!/)?, \ + .*/spring-(beans|context|core|mock|web|jdbc)-[\\d.]+.jar(!/)?, \ + .*/velocity-[\\d.]+.jar(!/)?, \ + .*/velocity-(dep|tools)-[\\d.]+.jar(!/)?, \ + .*/struts2-(config-browser-plugin|core|dojo-plugin|dwr-plugin|jsf-plugin|sitemesh-plugin|spring-plugin|struts1-plugin|tiles-plugin)-[\\d.]+.jar(!/)? \ No newline at end of file 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 new file mode 100644 index 000000000..55baf600b --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/DefaultResultMapBuilderTest.java @@ -0,0 +1,327 @@ +/* + * $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; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.servlet.ServletContext; + +import junit.framework.TestCase; + +import static org.apache.struts2.convention.ReflectionTools.*; +import org.apache.struts2.convention.actions.NoAnnotationAction; +import org.apache.struts2.convention.actions.result.ActionLevelResultAction; +import org.apache.struts2.convention.actions.result.ActionLevelResultsAction; +import org.apache.struts2.convention.actions.result.ClassLevelResultAction; +import org.apache.struts2.convention.actions.result.ClassLevelResultsAction; +import org.apache.struts2.convention.actions.resultpath.ClassLevelResultPathAction; +import org.apache.struts2.convention.annotation.Action; +import org.easymock.EasyMock; + +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; + +/** + *

+ * This class tests the simple result map builder. + *

+ */ +public class DefaultResultMapBuilderTest extends TestCase { + public void testBuild() throws Exception { + ServletContext context = mockServletContext("/WEB-INF/location"); + + // Test with a slash + PackageConfig packageConfig = createPackageConfigBuilder("/namespace"); + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/location"), "dispatcher,velocity,freemarker"); + Map results = builder.build(NoAnnotationAction.class, null, "action", packageConfig); + verify(context, "/WEB-INF/location", results, false); + + // Test without a slash + context = mockServletContext("/WEB-INF/location"); + packageConfig = createPackageConfigBuilder("namespace"); + builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/location"), "dispatcher,velocity,freemarker"); + results = builder.build(NoAnnotationAction.class, null, "action", packageConfig); + verify(context, "/WEB-INF/location", results, false); + } + + public void testNull() throws Exception { + ServletContext context = EasyMock.createStrictMock(ServletContext.class); + EasyMock.expect(context.getResourcePaths("/WEB-INF/location/namespace/")).andReturn(null); + EasyMock.replay(context); + + // Test with a slash + PackageConfig packageConfig = createPackageConfigBuilder("/namespace"); + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/location"), "dispatcher,velocity,freemarker"); + Map results = builder.build(NoAnnotationAction.class, null, "action", packageConfig); + assertEquals(0, results.size()); + EasyMock.verify(context); + } + + public void testResultPath() throws Exception { + ServletContext context = mockServletContext("/class-level"); + + // Test with a result path + PackageConfig packageConfig = createPackageConfigBuilder("/namespace"); + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/not-used"), "dispatcher,velocity,freemarker"); + Map results = builder.build(ClassLevelResultPathAction.class, null, "action", packageConfig); + verify(context, "/class-level", results, false); + } + + public void testFromServletContext() throws Exception { + ServletContext context = EasyMock.createStrictMock(ServletContext.class); + + // Setup some mock jsps + Set resources = new HashSet(); + resources.add("/WEB-INF/location/namespace/no-annotation.ftl"); + resources.add("/WEB-INF/location/namespace/no-annotation-success.jsp"); + resources.add("/WEB-INF/location/namespace/no-annotation-failure.jsp"); + EasyMock.expect(context.getResourcePaths("/WEB-INF/location/namespace/")).andReturn(resources); + EasyMock.replay(context); + + PackageConfig packageConfig = createPackageConfigBuilder("/namespace"); + + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/location"), "dispatcher,velocity,freemarker"); + Map results = builder.build(NoAnnotationAction.class, null, "no-annotation", packageConfig); + assertEquals(4, results.size()); + assertEquals("success", results.get("success").getName()); + assertEquals(3, results.get("success").getParams().size()); + assertEquals("org.apache.struts2.dispatcher.ServletDispatcherResult", results.get("success").getClassName()); + assertEquals("/WEB-INF/location/namespace/no-annotation-success.jsp", results.get("success").getParams().get("location")); + assertEquals(1, results.get("input").getParams().size()); + assertEquals("org.apache.struts2.views.freemarker.FreemarkerResult", results.get("input").getClassName()); + assertEquals("/WEB-INF/location/namespace/no-annotation.ftl", results.get("input").getParams().get("location")); + assertEquals(1, results.get("error").getParams().size()); + assertEquals("org.apache.struts2.views.freemarker.FreemarkerResult", results.get("error").getClassName()); + assertEquals("/WEB-INF/location/namespace/no-annotation.ftl", results.get("error").getParams().get("location")); + assertEquals(3, results.get("failure").getParams().size()); + assertEquals("org.apache.struts2.dispatcher.ServletDispatcherResult", results.get("success").getClassName()); + assertEquals("/WEB-INF/location/namespace/no-annotation-failure.jsp", results.get("failure").getParams().get("location")); + EasyMock.verify(context); + + } + + public void testClassLevelSingleResultAnnotation() 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"); + + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/location"), "dispatcher,velocity,freemarker"); + Map results = builder.build(ClassLevelResultAction.class, null, "class-level-result", packageConfig); + assertEquals(1, results.size()); + assertEquals("error", results.get("error").getName()); + assertEquals(3, results.get("error").getParams().size()); + assertEquals("org.apache.struts2.dispatcher.ServletDispatcherResult", results.get("error").getClassName()); + assertEquals("/WEB-INF/location/namespace/error.jsp", results.get("error").getParams().get("location")); + assertEquals("value", results.get("error").getParams().get("key")); + assertEquals("value1", results.get("error").getParams().get("key1")); + EasyMock.verify(context); + } + + public void testClassLevelMultipleResultAnnotation() 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"); + + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/location"), "dispatcher,velocity,freemarker"); + Map results = builder.build(ClassLevelResultsAction.class, null, "class-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("org.apache.struts2.dispatcher.ServletDispatcherResult", results.get("error").getClassName()); + assertEquals("/WEB-INF/location/namespace/error.jsp", results.get("error").getParams().get("location")); + assertEquals("ann-value", results.get("error").getParams().get("key")); + assertEquals("ann-value1", results.get("error").getParams().get("key1")); + assertEquals(1, results.get("input").getParams().size()); + assertEquals("foo.action", results.get("input").getParams().get("actionName")); + assertEquals("org.apache.struts2.dispatcher.ServletActionRedirectResult", 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.dispatcher.ServletDispatcherResult", results.get("failure").getClassName()); + assertEquals("value", results.get("failure").getParams().get("key")); + assertEquals("value1", results.get("failure").getParams().get("key1")); + 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.dispatcher.ServletDispatcherResult", results.get("success").getClassName()); + assertEquals("value", results.get("success").getParams().get("key")); + assertEquals("value1", results.get("success").getParams().get("key1")); + EasyMock.verify(context); + } + + public void testActionLevelSingleResultAnnotation() 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"); + + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/location"), "dispatcher,velocity,freemarker"); + Map results = builder.build(ActionLevelResultAction.class, getAnnotation(ActionLevelResultAction.class, "execute", Action.class), "action-level-result", packageConfig); + assertEquals(1, results.size()); + assertEquals("success", results.get("success").getName()); + assertEquals(3, results.get("success").getParams().size()); + assertEquals("org.apache.struts2.dispatcher.ServletDispatcherResult", results.get("success").getClassName()); + assertEquals("/WEB-INF/location/namespace/action-success.jsp", results.get("success").getParams().get("location")); + assertEquals("value", results.get("success").getParams().get("key")); + assertEquals("value1", results.get("success").getParams().get("key1")); + EasyMock.verify(context); + } + + public void testActionLevelMultipleResultAnnotation() 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"); + + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/location"), "dispatcher,velocity,freemarker"); + Map results = builder.build(ActionLevelResultsAction.class, getAnnotation(ActionLevelResultsAction.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("org.apache.struts2.dispatcher.ServletDispatcherResult", results.get("error").getClassName()); + assertEquals("/WEB-INF/location/namespace/error.jsp", results.get("error").getParams().get("location")); + assertEquals("value", results.get("success").getParams().get("key")); + assertEquals("value1", results.get("success").getParams().get("key1")); + assertEquals(1, results.get("input").getParams().size()); + assertEquals("foo.action", results.get("input").getParams().get("actionName")); + assertEquals("org.apache.struts2.dispatcher.ServletActionRedirectResult", 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.dispatcher.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.dispatcher.ServletDispatcherResult", results.get("success").getClassName()); + EasyMock.verify(context); + } + + public void testClassPath() throws Exception { + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + + ResultTypeConfig resultType = new ResultTypeConfig.Builder("freemarker", "org.apache.struts2.dispatcher.ServletDispatcherResult"). + defaultResultParam("location").build(); + PackageConfig packageConfig = new PackageConfig.Builder("package"). + defaultResultType("dispatcher").addResultTypeConfig(resultType).build(); + + DefaultResultMapBuilder builder = new DefaultResultMapBuilder(context, new ConventionsServiceImpl("/WEB-INF/component"), "dispatcher,velocity,freemarker"); + Map results = builder.build(NoAnnotationAction.class, null, "no-annotation", packageConfig); + assertEquals(4, results.size()); + assertEquals("input", results.get("input").getName()); + assertEquals("error", results.get("error").getName()); + assertEquals("success", results.get("success").getName()); + assertEquals("foo", results.get("foo").getName()); + assertEquals(1, results.get("success").getParams().size()); + assertEquals("/WEB-INF/component/no-annotation.ftl", results.get("success").getParams().get("location")); + assertEquals(1, results.get("input").getParams().size()); + assertEquals("/WEB-INF/component/no-annotation.ftl", results.get("input").getParams().get("location")); + assertEquals(1, results.get("error").getParams().size()); + assertEquals("/WEB-INF/component/no-annotation.ftl", results.get("error").getParams().get("location")); + assertEquals(1, results.get("foo").getParams().size()); + assertEquals("/WEB-INF/component/no-annotation-foo.ftl", results.get("foo").getParams().get("location")); + } + + private PackageConfig createPackageConfigBuilder(String namespace) { + ResultTypeConfig resultType = new ResultTypeConfig.Builder("dispatcher", "org.apache.struts2.dispatcher.ServletDispatcherResult"). + addParam("key", "value").addParam("key1", "value1").defaultResultParam("location").build(); + + ResultTypeConfig redirect = new ResultTypeConfig.Builder("redirectAction", + "org.apache.struts2.dispatcher.ServletActionRedirectResult").defaultResultParam("actionName").build(); + + ResultTypeConfig ftlResultType = new ResultTypeConfig.Builder("freemarker", + "org.apache.struts2.views.freemarker.FreemarkerResult").defaultResultParam("location").build(); + + return new PackageConfig.Builder("package"). + namespace(namespace). + defaultResultType("dispatcher"). + addResultTypeConfig(resultType). + addResultTypeConfig(redirect). + addResultTypeConfig(ftlResultType).build(); + } + + private ServletContext mockServletContext(String resultPath) { + ServletContext context = EasyMock.createStrictMock(ServletContext.class); + + // Setup some mock jsps + Set resources = new HashSet(); + resources.add(resultPath + "/namespace/action.jsp"); + resources.add(resultPath + "/namespace/action-success.jsp"); + resources.add(resultPath + "/namespace/action-failure.jsp"); + EasyMock.expect(context.getResourcePaths(resultPath + "/namespace/")).andReturn(resources); + EasyMock.replay(context); + return context; + } + + private void verify(ServletContext context, String resultPath, Map results, + boolean redirect) { + assertEquals(4, results.size()); + assertEquals("success", results.get("success").getName()); + assertEquals("input", results.get("input").getName()); + assertEquals("error", results.get("error").getName()); + assertEquals("failure", results.get("failure").getName()); + assertEquals(3, results.get("success").getParams().size()); + assertEquals(resultPath + "/namespace/action-success.jsp", results.get("success").getParams().get("location")); + assertEquals("value", results.get("success").getParams().get("key")); + assertEquals("value1", results.get("success").getParams().get("key1")); + assertEquals(3, results.get("failure").getParams().size()); + assertEquals(resultPath + "/namespace/action-failure.jsp", results.get("failure").getParams().get("location")); + assertEquals("value", results.get("failure").getParams().get("key")); + assertEquals("value1", results.get("failure").getParams().get("key1")); + + if (redirect) { + assertEquals(1, results.get("input").getParams().size()); + assertEquals("foo.action", results.get("input").getParams().get("actionName")); + } else { + assertEquals(3, results.get("input").getParams().size()); + assertEquals(resultPath + "/namespace/action.jsp", results.get("input").getParams().get("location")); + assertEquals("value", results.get("input").getParams().get("key")); + assertEquals("value1", results.get("input").getParams().get("key1")); + } + + assertEquals(3, results.get("error").getParams().size()); + assertEquals(resultPath + "/namespace/action.jsp", results.get("error").getParams().get("location")); + assertEquals("value", results.get("error").getParams().get("key")); + assertEquals("value1", results.get("error").getParams().get("key1")); + EasyMock.verify(context); + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..79585199e --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java @@ -0,0 +1,632 @@ +/* + * $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; + +import static org.apache.struts2.convention.ReflectionTools.getAnnotation; +import static org.easymock.EasyMock.checkOrder; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.verify; + +import java.util.*; +import java.net.MalformedURLException; +import java.lang.reflect.Method; + +import junit.framework.TestCase; + +import org.apache.struts2.convention.actions.DefaultResultPathAction; +import org.apache.struts2.convention.actions.NoAnnotationAction; +import org.apache.struts2.convention.actions.Skip; +import org.apache.struts2.convention.actions.chain.ChainedAction; +import org.apache.struts2.convention.actions.action.ActionNameAction; +import org.apache.struts2.convention.actions.action.ActionNamesAction; +import org.apache.struts2.convention.actions.action.SingleActionNameAction; +import org.apache.struts2.convention.actions.action.TestAction; +import org.apache.struts2.convention.actions.action.TestExtends; +import org.apache.struts2.convention.actions.defaultinterceptor.SingleActionNameAction2; +import org.apache.struts2.convention.actions.exception.ExceptionsActionLevelAction; +import org.apache.struts2.convention.actions.exception.ExceptionsMethodLevelAction; +import org.apache.struts2.convention.actions.interceptor.ActionLevelInterceptor2Action; +import org.apache.struts2.convention.actions.interceptor.ActionLevelInterceptor3Action; +import org.apache.struts2.convention.actions.interceptor.ActionLevelInterceptorAction; +import org.apache.struts2.convention.actions.interceptor.InterceptorsAction; +import org.apache.struts2.convention.actions.namespace.ActionLevelNamespaceAction; +import org.apache.struts2.convention.actions.namespace.ClassLevelNamespaceAction; +import org.apache.struts2.convention.actions.namespace.PackageLevelNamespaceAction; +import org.apache.struts2.convention.actions.namespace2.DefaultNamespaceAction; +import org.apache.struts2.convention.actions.namespace3.ActionLevelNamespacesAction; +import org.apache.struts2.convention.actions.namespace4.ActionAndPackageLevelNamespacesAction; +import org.apache.struts2.convention.actions.params.ActionParamsMethodLevelAction; +import org.apache.struts2.convention.actions.parentpackage.ClassLevelParentPackageAction; +import org.apache.struts2.convention.actions.parentpackage.PackageLevelParentPackageAction; +import org.apache.struts2.convention.actions.result.ActionLevelResultAction; +import org.apache.struts2.convention.actions.result.ActionLevelResultsAction; +import org.apache.struts2.convention.actions.result.ClassLevelResultAction; +import org.apache.struts2.convention.actions.result.ClassLevelResultsAction; +import org.apache.struts2.convention.actions.resultpath.ClassLevelResultPathAction; +import org.apache.struts2.convention.actions.resultpath.PackageLevelResultPathAction; +import org.apache.struts2.convention.actions.skip.Index; +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.Actions; +import org.apache.struts2.dispatcher.ServletDispatcherResult; +import org.easymock.EasyMock; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.Result; +import com.opensymphony.xwork2.ActionChainResult; +import com.opensymphony.xwork2.util.reflection.ReflectionException; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.config.entities.InterceptorConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.InterceptorStackConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; +import com.opensymphony.xwork2.config.impl.DefaultConfiguration; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Scope.Strategy; +import com.opensymphony.xwork2.ognl.OgnlReflectionProvider; +import com.opensymphony.xwork2.ognl.OgnlUtil; + +import javax.servlet.ServletContext; + +/** + *

+ * This is a test for the package based name builder. + *

+ */ +public class PackageBasedActionConfigBuilderTest extends TestCase { + public void testActionPackages() throws MalformedURLException { + run("org.apache.struts2.convention.actions", null, null); + } + + public void testPackageLocators() throws MalformedURLException { + run(null, "actions,dontfind", null); + } + + private void run(String actionPackages, String packageLocators, String excludePackages) throws MalformedURLException { + //setup interceptors + List defaultInterceptors = new ArrayList(); + defaultInterceptors.add(makeInterceptorConfig("interceptor-1")); + defaultInterceptors.add(makeInterceptorConfig("interceptor-2")); + defaultInterceptors.add(makeInterceptorConfig("interceptor-3")); + + //setup interceptor stacks + List defaultInterceptorStacks = new ArrayList(); + InterceptorMapping interceptor1 = new InterceptorMapping("interceptor-1", new TestInterceptor()); + InterceptorMapping interceptor2 = new InterceptorMapping("interceptor-2", new TestInterceptor()); + defaultInterceptorStacks.add(makeInterceptorStackConfig("stack-1", interceptor1, interceptor2)); + + //setup results + ResultTypeConfig[] defaultResults = new ResultTypeConfig[]{new ResultTypeConfig.Builder("dispatcher", + ServletDispatcherResult.class.getName()).defaultResultParam("location").build(), + new ResultTypeConfig.Builder("chain", + ActionChainResult.class.getName()).defaultResultParam("actionName").build()}; + + PackageConfig strutsDefault = makePackageConfig("struts-default", null, null, "dispatcher", + defaultResults, defaultInterceptors, defaultInterceptorStacks); + + PackageConfig packageLevelParentPkg = makePackageConfig("package-level", null, null, null); + PackageConfig classLevelParentPkg = makePackageConfig("class-level", null, null, null); + + PackageConfig rootPkg = makePackageConfig("org.apache.struts2.convention.actions#struts-default#", + "", strutsDefault, null); + PackageConfig paramsPkg = makePackageConfig("org.apache.struts2.convention.actions.params#struts-default#/params", + "/params", strutsDefault, null); + PackageConfig defaultInterceptorPkg = makePackageConfig("org.apache.struts2.convention.actions.defaultinterceptor#struts-default#/defaultinterceptor", + "/defaultinterceptor", strutsDefault, null); + PackageConfig exceptionPkg = makePackageConfig("org.apache.struts2.convention.actions.exception#struts-default#/exception", + "/exception", strutsDefault, null); + PackageConfig actionPkg = makePackageConfig("org.apache.struts2.convention.actions.action#struts-default#/action", + "/action", strutsDefault, null); + PackageConfig idxPkg = makePackageConfig("org.apache.struts2.convention.actions.idx#struts-default#/idx", + "/idx", strutsDefault, null); + PackageConfig idx2Pkg = makePackageConfig("org.apache.struts2.convention.actions.idx.idx2#struts-default#/idx/idx2", + "/idx/idx2", strutsDefault, null); + PackageConfig interceptorRefsPkg = makePackageConfig("org.apache.struts2.convention.actions.interceptor#struts-default#/interceptor", + "/interceptor", strutsDefault, null); + PackageConfig packageLevelPkg = makePackageConfig("org.apache.struts2.convention.actions.parentpackage#package-level#/parentpackage", + "/parentpackage", packageLevelParentPkg, null); + PackageConfig differentPkg = makePackageConfig("org.apache.struts2.convention.actions.parentpackage#class-level#/parentpackage", + "/parentpackage", classLevelParentPkg, null); + PackageConfig pkgLevelNamespacePkg = makePackageConfig("org.apache.struts2.convention.actions.namespace#struts-default#/package-level", + "/package-level", strutsDefault, null); + PackageConfig classLevelNamespacePkg = makePackageConfig("org.apache.struts2.convention.actions.namespace#struts-default#/class-level", + "/class-level", strutsDefault, null); + PackageConfig actionLevelNamespacePkg = makePackageConfig("org.apache.struts2.convention.actions.namespace#struts-default#/action-level", + "/action-level", strutsDefault, null); + PackageConfig defaultNamespacePkg = makePackageConfig("org.apache.struts2.convention.actions.namespace2#struts-default#/namespace2", + "/namespace2", strutsDefault, null); + PackageConfig namespaces1Pkg = makePackageConfig("org.apache.struts2.convention.actions.namespace3#struts-default#/namespaces1", + "/namespaces1", strutsDefault, null); + PackageConfig namespaces2Pkg = makePackageConfig("org.apache.struts2.convention.actions.namespace3#struts-default#/namespaces2", + "/namespaces2", strutsDefault, null); + PackageConfig namespaces3Pkg = makePackageConfig("org.apache.struts2.convention.actions.namespace4#struts-default#/namespaces3", + "/namespaces3", strutsDefault, null); + PackageConfig namespaces4Pkg = makePackageConfig("org.apache.struts2.convention.actions.namespace4#struts-default#/namespaces4", + "/namespaces4", strutsDefault, null); + PackageConfig resultPkg = makePackageConfig("org.apache.struts2.convention.actions.result#struts-default#/result", + "/result", strutsDefault, null); + PackageConfig resultPathPkg = makePackageConfig("org.apache.struts2.convention.actions.resultpath#struts-default#/resultpath", + "/resultpath", strutsDefault, null); + PackageConfig skipPkg = makePackageConfig("org.apache.struts2.convention.actions.skip#struts-default#/skip", + "/skip", strutsDefault, null); + PackageConfig chainPkg = makePackageConfig("org.apache.struts2.convention.actions.chain#struts-default#/chain", + "/chain", strutsDefault, null); + + ResultMapBuilder resultMapBuilder = createStrictMock(ResultMapBuilder.class); + checkOrder(resultMapBuilder, false); + Map results = new HashMap(); + + /* org.apache.struts2.convention.actions.action */ + expect(resultMapBuilder.build(ActionNameAction.class, getAnnotation(ActionNameAction.class, "run1", Action.class), "action1", actionPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionNameAction.class, getAnnotation(ActionNameAction.class, "run2", Action.class), "action2", actionPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionNamesAction.class, getAnnotation(ActionNamesAction.class, "run", Actions.class).value()[0], "actions1", actionPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionNamesAction.class, getAnnotation(ActionNamesAction.class, "run", Actions.class).value()[1], "actions2", actionPkg)).andReturn(results); + expect(resultMapBuilder.build(SingleActionNameAction.class, getAnnotation(SingleActionNameAction.class, "run", Action.class), "action", actionPkg)).andReturn(results); + expect(resultMapBuilder.build(TestAction.class, null, "test", actionPkg)).andReturn(results); + expect(resultMapBuilder.build(TestExtends.class, null, "test-extends", actionPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.idx */ + /* org.apache.struts2.convention.actions.idx.idx2 */ + expect(resultMapBuilder.build(org.apache.struts2.convention.actions.idx.Index.class, null, "index", idxPkg)).andReturn(results); + expect(resultMapBuilder.build(org.apache.struts2.convention.actions.idx.idx2.Index.class, null, "index", idx2Pkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.params */ + expect(resultMapBuilder.build(ActionParamsMethodLevelAction.class, getAnnotation(ActionParamsMethodLevelAction.class, "run1", Action.class), "actionParam1", paramsPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.defaultinterceptor */ + expect(resultMapBuilder.build(SingleActionNameAction2.class, getAnnotation(SingleActionNameAction2.class, "execute", Action.class), "action345", defaultInterceptorPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.exception */ + expect(resultMapBuilder.build(ExceptionsMethodLevelAction.class, getAnnotation(ExceptionsMethodLevelAction.class, "run1", Action.class), "exception1", exceptionPkg)).andReturn(results); + expect(resultMapBuilder.build(ExceptionsActionLevelAction.class, getAnnotation(ExceptionsActionLevelAction.class, "execute", Action.class), "exceptions-action-level", exceptionPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.interceptor */ + expect(resultMapBuilder.build(InterceptorsAction.class, getAnnotation(InterceptorsAction.class, "run1", Action.class), "action100", interceptorRefsPkg)).andReturn(results); + expect(resultMapBuilder.build(InterceptorsAction.class, getAnnotation(InterceptorsAction.class, "run2", Action.class), "action200", interceptorRefsPkg)).andReturn(results); + expect(resultMapBuilder.build(InterceptorsAction.class, getAnnotation(InterceptorsAction.class, "run3", Action.class), "action300", interceptorRefsPkg)).andReturn(results); + expect(resultMapBuilder.build(InterceptorsAction.class, getAnnotation(InterceptorsAction.class, "run4", Action.class), "action400", interceptorRefsPkg)).andReturn(results); + + expect(resultMapBuilder.build(ActionLevelInterceptorAction.class, getAnnotation(ActionLevelInterceptorAction.class, "run1", Action.class), "action500", interceptorRefsPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionLevelInterceptorAction.class, getAnnotation(ActionLevelInterceptorAction.class, "run2", Action.class), "action600", interceptorRefsPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionLevelInterceptorAction.class, getAnnotation(ActionLevelInterceptorAction.class, "run3", Action.class), "action700", interceptorRefsPkg)).andReturn(results); + + expect(resultMapBuilder.build(ActionLevelInterceptor2Action.class, getAnnotation(ActionLevelInterceptor2Action.class, "run1", Action.class), "action800", interceptorRefsPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionLevelInterceptor3Action.class, getAnnotation(ActionLevelInterceptor3Action.class, "run1", Action.class), "action900", interceptorRefsPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.namespace */ + expect(resultMapBuilder.build(ActionLevelNamespaceAction.class, getAnnotation(ActionLevelNamespaceAction.class, "execute", Action.class), "action", actionLevelNamespacePkg)).andReturn(results); + expect(resultMapBuilder.build(ClassLevelNamespaceAction.class, null, "class-level-namespace", classLevelNamespacePkg)).andReturn(results); + expect(resultMapBuilder.build(PackageLevelNamespaceAction.class, null, "package-level-namespace", pkgLevelNamespacePkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.namespace2 */ + expect(resultMapBuilder.build(DefaultNamespaceAction.class, null, "default-namespace", defaultNamespacePkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.namespace3 */ + expect(resultMapBuilder.build(ActionLevelNamespacesAction.class, null, "action-level-namespaces", namespaces1Pkg)).andReturn(results); + expect(resultMapBuilder.build(ActionLevelNamespacesAction.class, null, "action-level-namespaces", namespaces2Pkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.namespace4 */ + expect(resultMapBuilder.build(ActionAndPackageLevelNamespacesAction.class, null, "action-and-package-level-namespaces", namespaces3Pkg)).andReturn(results); + expect(resultMapBuilder.build(ActionAndPackageLevelNamespacesAction.class, null, "action-and-package-level-namespaces", namespaces4Pkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.parentpackage */ + expect(resultMapBuilder.build(PackageLevelParentPackageAction.class, null, "package-level-parent-package", packageLevelPkg)).andReturn(results); + expect(resultMapBuilder.build(ClassLevelParentPackageAction.class, null, "class-level-parent-package", differentPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.result */ + expect(resultMapBuilder.build(ClassLevelResultAction.class, null, "class-level-result", resultPkg)).andReturn(results); + expect(resultMapBuilder.build(ClassLevelResultsAction.class, null, "class-level-results", resultPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionLevelResultAction.class, getAnnotation(ActionLevelResultAction.class, "execute", Action.class), "action-level-result", resultPkg)).andReturn(results); + expect(resultMapBuilder.build(ActionLevelResultsAction.class, getAnnotation(ActionLevelResultsAction.class, "execute", Action.class), "action-level-results", resultPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.resultpath */ + expect(resultMapBuilder.build(ClassLevelResultPathAction.class, null, "class-level-result-path", resultPathPkg)).andReturn(results); + expect(resultMapBuilder.build(PackageLevelResultPathAction.class, null, "package-level-result-path", resultPathPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions */ + expect(resultMapBuilder.build(NoAnnotationAction.class, null, "no-annotation", rootPkg)).andReturn(results); + expect(resultMapBuilder.build(DefaultResultPathAction.class, null, "default-result-path", rootPkg)).andReturn(results); + expect(resultMapBuilder.build(Skip.class, null, "skip", rootPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.skip */ + expect(resultMapBuilder.build(Index.class, null, "index", skipPkg)).andReturn(results); + + /* org.apache.struts2.convention.actions.chain */ + expect(resultMapBuilder.build(ChainedAction.class, getAnnotation(ChainedAction.class, "foo", Action.class), "foo", chainPkg)).andReturn(results); + expect(resultMapBuilder.build(ChainedAction.class, getAnnotation(ChainedAction.class, "bar", Action.class), "foo-bar", chainPkg)).andReturn(results); + + EasyMock.replay(resultMapBuilder); + + Configuration configuration = new DefaultConfiguration() { + + @Override + public Container getContainer() { + return new DummyContainer(); + } + + }; + + configuration.addPackageConfig("struts-default", strutsDefault); + configuration.addPackageConfig("package-level", packageLevelParentPkg); + configuration.addPackageConfig("class-level", classLevelParentPkg); + + ActionNameBuilder actionNameBuilder = new SEOActionNameBuilder("true", "-"); + ObjectFactory of = new ObjectFactory(); + DefaultInterceptorMapBuilder interceptorBuilder = new DefaultInterceptorMapBuilder(); + interceptorBuilder.setConfiguration(configuration); + PackageBasedActionConfigBuilder builder = new PackageBasedActionConfigBuilder(configuration, + actionNameBuilder, resultMapBuilder, interceptorBuilder ,of, "false", "struts-default"); + builder.setDisableJarScanning("true"); + if (actionPackages != null) { + builder.setActionPackages(actionPackages); + } + if (packageLocators != null) { + builder.setPackageLocators(packageLocators); + } + if (excludePackages != null) { + builder.setExcludePackages(excludePackages); + } + builder.setPackageLocatorsBase("org.apache.struts2.convention.actions"); + builder.buildActionConfigs(); + verify(resultMapBuilder); + + /* org.apache.struts2.convention.actions.action */ + PackageConfig pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.action#struts-default#/action"); + assertNotNull(pkgConfig); + assertEquals(7, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "action1", ActionNameAction.class, "run1", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "action2", ActionNameAction.class, "run2", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "actions1", ActionNamesAction.class, "run", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "actions2", ActionNamesAction.class, "run", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "action", SingleActionNameAction.class, "run", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "test", TestAction.class, "execute", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "test-extends", TestExtends.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.namespace3 */ + //action on namespace1 (action level) + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.namespace3#struts-default#/namespaces1"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "action-level-namespaces", ActionLevelNamespacesAction.class, "execute", pkgConfig.getName()); + + //action on namespace2 (action level) + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.namespace3#struts-default#/namespaces2"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "action-level-namespaces", ActionLevelNamespacesAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.namespace4 */ + //action on namespace3 (action level) + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.namespace4#struts-default#/namespaces3"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "action-and-package-level-namespaces", ActionAndPackageLevelNamespacesAction.class, "execute", pkgConfig.getName()); + + //action on namespace4 (package level) + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.namespace4#struts-default#/namespaces4"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "action-and-package-level-namespaces", ActionAndPackageLevelNamespacesAction.class, "execute", pkgConfig.getName()); + + + + /* org.apache.struts2.convention.actions.params */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.params#struts-default#/params"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + ActionConfig ac = pkgConfig.getAllActionConfigs().get("actionParam1"); + assertNotNull(ac); + Map params = ac.getParams(); + assertNotNull(params); + assertEquals(2, params.size()); + assertEquals("val1", params.get("param1")); + assertEquals("val2", params.get("param2")); + + /* org.apache.struts2.convention.actions.params */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.exception#struts-default#/exception"); + assertNotNull(pkgConfig); + assertEquals(2, pkgConfig.getActionConfigs().size()); + + ac = pkgConfig.getAllActionConfigs().get("exception1"); + assertNotNull(ac); + List exceptions = ac.getExceptionMappings(); + assertNotNull(exceptions); + assertEquals(2, exceptions.size()); + ExceptionMappingConfig exception = exceptions.get(0); + assertEquals("NPE1", exception.getExceptionClassName()); + assertEquals("success", exception.getResult()); + exception = exceptions.get(1); + assertEquals("NPE2", exception.getExceptionClassName()); + assertEquals("success", exception.getResult()); + params = exception.getParams(); + assertNotNull(params); + assertEquals(1, params.size()); + assertEquals("val1", params.get("param1")); + + ac = pkgConfig.getAllActionConfigs().get("exceptions-action-level"); + assertNotNull(ac); + exceptions = ac.getExceptionMappings(); + assertNotNull(exceptions); + assertEquals(2, exceptions.size()); + exception = exceptions.get(0); + assertEquals("NPE1", exception.getExceptionClassName()); + assertEquals("success", exception.getResult()); + exception = exceptions.get(1); + assertEquals("NPE2", exception.getExceptionClassName()); + assertEquals("success", exception.getResult()); + params = exception.getParams(); + assertNotNull(params); + assertEquals(1, params.size()); + assertEquals("val1", params.get("param1")); + + /* org.apache.struts2.convention.actions.idx */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.idx#struts-default#/idx"); + assertNotNull(pkgConfig); + assertEquals(3, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "", org.apache.struts2.convention.actions.idx.Index.class, "execute", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "index", org.apache.struts2.convention.actions.idx.Index.class, "execute", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "idx2", org.apache.struts2.convention.actions.idx.idx2.Index.class, "execute", + "org.apache.struts2.convention.actions.idx.idx2#struts-default#/idx/idx2"); + + /* org.apache.struts2.convention.actions.defaultinterceptor */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.defaultinterceptor#struts-default#/defaultinterceptor"); + assertNotNull(pkgConfig); + assertEquals("validationWorkflowStack", pkgConfig.getDefaultInterceptorRef()); + + /* org.apache.struts2.convention.actions.idx.idx2 */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.idx.idx2#struts-default#/idx/idx2"); + assertNotNull(pkgConfig); + assertEquals(2, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "", org.apache.struts2.convention.actions.idx.idx2.Index.class, "execute", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "index", org.apache.struts2.convention.actions.idx.idx2.Index.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.namespace action level */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.namespace#struts-default#/action-level"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "action", ActionLevelNamespaceAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.namespace class level */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.namespace#struts-default#/class-level"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "class-level-namespace", ClassLevelNamespaceAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.namespace package level */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.namespace#struts-default#/package-level"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "package-level-namespace", PackageLevelNamespaceAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.namespace2 */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.namespace2#struts-default#/namespace2"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "default-namespace", DefaultNamespaceAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.parentpackage class level */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.parentpackage#class-level#/parentpackage"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "class-level-parent-package", ClassLevelParentPackageAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.parentpackage package level */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.parentpackage#package-level#/parentpackage"); + assertNotNull(pkgConfig); + assertEquals(1, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "package-level-parent-package", PackageLevelParentPackageAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.result */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.result#struts-default#/result"); + assertNotNull(pkgConfig); + assertEquals(4, 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()); + verifyActionConfig(pkgConfig, "action-level-results", ActionLevelResultsAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.resultpath */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.resultpath#struts-default#/resultpath"); + assertNotNull(pkgConfig); + assertEquals(2, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "class-level-result-path", ClassLevelResultPathAction.class, "execute", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "package-level-result-path", PackageLevelResultPathAction.class, "execute", pkgConfig.getName()); + + /* org.apache.struts2.convention.actions.interceptorRefs */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.interceptor#struts-default#/interceptor"); + assertNotNull(pkgConfig); + assertEquals(9, pkgConfig.getActionConfigs().size()); + verifyActionConfigInterceptors(pkgConfig, "action100", "interceptor-1"); + verifyActionConfigInterceptors(pkgConfig, "action200", "interceptor-1", "interceptor-2"); + verifyActionConfigInterceptors(pkgConfig, "action300", "interceptor-1", "interceptor-2"); + verifyActionConfigInterceptors(pkgConfig, "action400", "interceptor-1", "interceptor-1", "interceptor-2"); + + // Interceptors at class level + verifyActionConfigInterceptors(pkgConfig, "action500", "interceptor-1"); + verifyActionConfigInterceptors(pkgConfig, "action600", "interceptor-1", "interceptor-2"); + verifyActionConfigInterceptors(pkgConfig, "action700", "interceptor-1", "interceptor-1", "interceptor-2"); + + //multiple interceptor at class level + verifyActionConfigInterceptors(pkgConfig, "action800", "interceptor-1", "interceptor-2"); + verifyActionConfigInterceptors(pkgConfig, "action900", "interceptor-1", "interceptor-1", "interceptor-2"); + + /* org.apache.struts2.convention.actions */ + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions#struts-default#"); + assertNotNull(pkgConfig); + System.out.println("actions " + pkgConfig.getActionConfigs()); + assertEquals(4, pkgConfig.getActionConfigs().size()); + verifyActionConfig(pkgConfig, "no-annotation", NoAnnotationAction.class, "execute", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "default-result-path", DefaultResultPathAction.class, "execute", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "skip", Skip.class, "execute", pkgConfig.getName()); + verifyActionConfig(pkgConfig, "idx", org.apache.struts2.convention.actions.idx.Index.class, "execute", + "org.apache.struts2.convention.actions.idx#struts-default#/idx"); + + //test unknown handler automatic chaining + pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.chain#struts-default#/chain"); + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.replay(context); + + ObjectFactory workingFactory = configuration.getContainer().getInstance(ObjectFactory.class); + ConventionUnknownHandler uh = new ConventionUnknownHandler(configuration, workingFactory, context, resultMapBuilder, new ConventionsServiceImpl(""), "struts-default", null, "-"); + ActionContext actionContext = new ActionContext(Collections.EMPTY_MAP); + + Result result = uh.handleUnknownResult(actionContext, "foo", pkgConfig.getActionConfigs().get("foo"), "bar"); + assertNotNull(result); + assertTrue(result instanceof ActionChainResult); + ActionChainResult chainResult = (ActionChainResult) result; + ActionChainResult chainResultToCompare = new ActionChainResult("/chain", "foo-bar", "bar"); + } + + private void verifyActionConfig(PackageConfig pkgConfig, String actionName, Class actionClass, + String methodName, String packageName) { + ActionConfig ac = pkgConfig.getAllActionConfigs().get(actionName); + assertNotNull(ac); + assertEquals(actionClass.getName(), ac.getClassName()); + assertEquals(methodName, ac.getMethodName()); + assertEquals(packageName, ac.getPackageName()); + } + + private void verifyActionConfigInterceptors(PackageConfig pkgConfig, String actionName, String... refs) { + ActionConfig ac = pkgConfig.getAllActionConfigs().get(actionName); + assertNotNull(ac); + List interceptorMappings = ac.getInterceptors(); + for (int i = 0; i < interceptorMappings.size(); i++) { + InterceptorMapping interceptorMapping = interceptorMappings.get(i); + assertEquals(refs[i], interceptorMapping.getName()); + } + } + + private PackageConfig makePackageConfig(String name, String namespace, PackageConfig parent, + String defaultResultType, ResultTypeConfig... results) { + return makePackageConfig(name, namespace, parent, defaultResultType, results, null, null); + } + + private PackageConfig makePackageConfig(String name, String namespace, PackageConfig parent, + String defaultResultType, ResultTypeConfig[] results, List interceptors, + List interceptorStacks) { + PackageConfig.Builder builder = new PackageConfig.Builder(name); + if (namespace != null) { + builder.namespace(namespace); + } + if (parent != null) { + builder.addParent(parent); + } + if (defaultResultType != null) { + builder.defaultResultType(defaultResultType); + } + if (results != null) { + for (ResultTypeConfig result : results) { + builder.addResultTypeConfig(result); + } + } + if (interceptors != null) { + for (InterceptorConfig ref : interceptors) { + builder.addInterceptorConfig(ref); + } + } + if (interceptorStacks != null) { + for (InterceptorStackConfig ref : interceptorStacks) { + builder.addInterceptorStackConfig(ref); + } + } + + return new MyPackageConfig(builder.build()); + } + + private InterceptorConfig makeInterceptorConfig(String name) { + InterceptorConfig.Builder builder = new InterceptorConfig.Builder(name, "org.apache.struts2.convention.TestInterceptor"); + return builder.build(); + } + + private InterceptorStackConfig makeInterceptorStackConfig(String name, InterceptorMapping... interceptors) { + InterceptorStackConfig.Builder builder = new InterceptorStackConfig.Builder(name); + for (InterceptorMapping interceptor : interceptors) + builder.addInterceptor(interceptor); + return builder.build(); + } + + public class MyPackageConfig extends PackageConfig { + protected MyPackageConfig(PackageConfig packageConfig) { + super(packageConfig); + } + + public boolean equals(Object obj) { + PackageConfig other = (PackageConfig) obj; + return getName().equals(other.getName()) && getNamespace().equals(other.getNamespace()) && + getParents().get(0) == other.getParents().get(0) && getParents().size() == other.getParents().size(); + } + } + + public class DummyContainer implements Container { + + public T getInstance(Class type) { + try { + T obj = type.newInstance(); + if (obj instanceof ObjectFactory) { + ((ObjectFactory)obj).setReflectionProvider(new OgnlReflectionProvider() { + + @Override + public void setProperties(Map properties, Object o) { + } + + public void setProperties(Map properties, Object o, Map context, boolean throwPropertyExceptions) throws ReflectionException { + if (o instanceof ActionChainResult) { + ((ActionChainResult)o).setActionName(properties.get("actionName")); + } + } + }); + } + return obj; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public T getInstance(Class type, String name) { + return null; + } + + public Set getInstanceNames(Class type) { + return null; + } + + public void inject(Object o) { + + } + + public T inject(Class implementation) { + return null; + } + + public void removeScopeStrategy() { + + } + + public void setScopeStrategy(Strategy scopeStrategy) { + } + + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/ReflectionToolsTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/ReflectionToolsTest.java new file mode 100644 index 000000000..9b85e7a0d --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/ReflectionToolsTest.java @@ -0,0 +1,35 @@ +/* + * $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; + +import junit.framework.TestCase; + +/** + *

+ * This tests the reflection tools. + *

+ */ +public class ReflectionToolsTest extends TestCase { + public void testContainsMethod() { + assertTrue(ReflectionTools.containsMethod(this.getClass(), "testContainsMethod")); + assertFalse(ReflectionTools.containsMethod(this.getClass(), "badMethod")); + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/SEOActionNameBuilderTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/SEOActionNameBuilderTest.java new file mode 100644 index 000000000..2d9a4de22 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/SEOActionNameBuilderTest.java @@ -0,0 +1,46 @@ +/* + * $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; + +import junit.framework.TestCase; + +/** + *

+ * This class tests the SEO name builder. + *

+ */ +public class SEOActionNameBuilderTest extends TestCase { + public void testBuild() throws Exception { + SEOActionNameBuilder builder = new SEOActionNameBuilder("true", "_"); + assertEquals("foo", builder.build("Foo")); + assertEquals("foo", builder.build("FooAction")); + assertEquals("foo_bar", builder.build("FooBarAction")); + assertEquals("foo_bar_baz", builder.build("FooBarBazAction")); + } + + public void testDash() throws Exception { + SEOActionNameBuilder builder = new SEOActionNameBuilder("true", "-"); + assertEquals("foo", builder.build("Foo")); + assertEquals("foo", builder.build("FooAction")); + assertEquals("foo-bar", builder.build("FooBarAction")); + assertEquals("foo-bar-baz", builder.build("FooBarBazAction")); + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/StringToolsTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/StringToolsTest.java new file mode 100644 index 000000000..811d4b8ea --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/StringToolsTest.java @@ -0,0 +1,51 @@ +/* + * $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; + +import junit.framework.TestCase; + +/** + *

+ * This class tests the string tools. + *

+ */ +public class StringToolsTest extends TestCase { + public void testEmpty() { + assertTrue(StringTools.isTrimmedEmpty(null)); + assertTrue(StringTools.isTrimmedEmpty("")); + assertTrue(StringTools.isTrimmedEmpty(" ")); + assertFalse(StringTools.isTrimmedEmpty("f")); + assertFalse(StringTools.isTrimmedEmpty(" f ")); + } + + public void testLastToken() { + assertEquals("bar", StringTools.lastToken("/foo/bar", "/")); + assertEquals("baz", StringTools.lastToken("/foo/bar/baz", "/")); + assertEquals("baz", StringTools.lastToken("baz", "/")); + } + + public void testUpToLastToken() { + assertEquals("/foo", StringTools.upToLastToken("/foo/bar", "/")); + assertEquals("/foo/bar", StringTools.upToLastToken("/foo/bar/baz", "/")); + assertEquals("", StringTools.upToLastToken("/foo", "/")); + assertEquals("", StringTools.upToLastToken("foo", "/")); + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/TestInterceptor.java b/plugins/convention/src/test/java/org/apache/struts2/convention/TestInterceptor.java new file mode 100644 index 000000000..92b9fe724 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/TestInterceptor.java @@ -0,0 +1,21 @@ +package org.apache.struts2.convention; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.AbstractInterceptor; + +public class TestInterceptor extends AbstractInterceptor { + private String string1; + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + return null; + } + + public String getString1() { + return string1; + } + + public void setString1(String string1) { + this.string1 = string1; + } +} diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/DefaultResultPathAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/DefaultResultPathAction.java new file mode 100644 index 000000000..636953e43 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/DefaultResultPathAction.java @@ -0,0 +1,32 @@ +/* + * $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; + +/** + *

+ * This class is a test action with the default result path. + *

+ */ +public class DefaultResultPathAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/NoAnnotationAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/NoAnnotationAction.java new file mode 100644 index 000000000..224987e03 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/NoAnnotationAction.java @@ -0,0 +1,32 @@ +/* + * $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; + +/** + *

+ * This is a struts action with no annotations. + *

+ */ +public class NoAnnotationAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/Skip.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/Skip.java new file mode 100644 index 000000000..3e7e517c5 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/Skip.java @@ -0,0 +1,29 @@ +/* + * $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; + +import com.opensymphony.xwork2.Action; + +public class Skip implements Action { + public String execute() throws Exception { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/ActionNameAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/ActionNameAction.java new file mode 100644 index 000000000..5071586de --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/ActionNameAction.java @@ -0,0 +1,40 @@ +/* + * $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.action; + +import org.apache.struts2.convention.annotation.Action; + +/** + *

+ * This is a test action. + *

+ */ +public class ActionNameAction { + @Action("action1") + public String run1() { + return null; + } + + @Action("action2") + public String run2() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/ActionNamesAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/ActionNamesAction.java new file mode 100644 index 000000000..bded4e037 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/ActionNamesAction.java @@ -0,0 +1,39 @@ +/* + * $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.action; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.Actions; + +/** + *

+ * This class is a test action. + *

+ */ +public class ActionNamesAction { + @Actions({ + @Action("actions1"), + @Action("actions2") + }) + public String run() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/SingleActionNameAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/SingleActionNameAction.java new file mode 100644 index 000000000..699bad1a2 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/SingleActionNameAction.java @@ -0,0 +1,35 @@ +/* + * $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.action; + +import org.apache.struts2.convention.annotation.Action; + +/** + *

+ * This is a test action. + *

+ */ +public class SingleActionNameAction { + @Action("action") + public String run() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestAction.java new file mode 100644 index 000000000..cfd90e40b --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestAction.java @@ -0,0 +1,32 @@ +/* + * $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.action; + +/** + *

+ * This is a test action. + *

+ */ +public class TestAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestBase.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestBase.java new file mode 100644 index 000000000..3b7d83ce6 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestBase.java @@ -0,0 +1,27 @@ +/* + * $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.action; + +import com.opensymphony.xwork2.ActionSupport; + +public abstract class TestBase extends ActionSupport { + +} diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestExtends.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestExtends.java new file mode 100644 index 000000000..074d60d5c --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/action/TestExtends.java @@ -0,0 +1,25 @@ +/* + * $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.action; + +public class TestExtends extends TestBase { + +} diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/chain/ChainedAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/chain/ChainedAction.java new file mode 100644 index 000000000..f481c40d7 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/chain/ChainedAction.java @@ -0,0 +1,35 @@ +/* + * $Id: ActionNamesAction.java 655902 2008-05-13 15:15:12Z bpontarelli $ + * + * 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.chain; + +import org.apache.struts2.convention.annotation.Action; + +public class ChainedAction { + @Action("foo") + public String foo() { + return "bar"; + } + + @Action("foo-bar") + public String bar() { + return null; + } +} diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/defaultinterceptor/SingleActionNameAction2.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/defaultinterceptor/SingleActionNameAction2.java new file mode 100644 index 000000000..703ba9898 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/defaultinterceptor/SingleActionNameAction2.java @@ -0,0 +1,35 @@ +/* + * $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.defaultinterceptor; + +import org.apache.struts2.convention.annotation.Action; + +/** + *

+ * This is a test action. + *

+ */ +public class SingleActionNameAction2 implements com.opensymphony.xwork2.Action{ + @Action("action345") + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/defaultinterceptor/package-info.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/defaultinterceptor/package-info.java new file mode 100644 index 000000000..fc05e762a --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/defaultinterceptor/package-info.java @@ -0,0 +1,22 @@ +/* + * $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. + */ +@org.apache.struts2.convention.annotation.DefaultInterceptorRef("validationWorkflowStack") +package org.apache.struts2.convention.actions.defaultinterceptor; \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/exception/ExceptionsActionLevelAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/exception/ExceptionsActionLevelAction.java new file mode 100644 index 000000000..4e7fc49c5 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/exception/ExceptionsActionLevelAction.java @@ -0,0 +1,35 @@ +/* + * $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.exception; + +import org.apache.struts2.convention.annotation.ExceptionMapping; +import org.apache.struts2.convention.annotation.ExceptionMappings; + +@ExceptionMappings({ + @ExceptionMapping(exception = "NPE1", result = "success"), + @ExceptionMapping(exception = "NPE2", result = "success", params = {"param1", "val1"}) +}) +public class ExceptionsActionLevelAction { + + public String execute() throws Exception { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/exception/ExceptionsMethodLevelAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/exception/ExceptionsMethodLevelAction.java new file mode 100644 index 000000000..925cf8f1e --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/exception/ExceptionsMethodLevelAction.java @@ -0,0 +1,35 @@ +/* + * $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.exception; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.ExceptionMapping; + +public class ExceptionsMethodLevelAction { + + @Action(value = "exception1", exceptionMappings = { + @ExceptionMapping(exception = "NPE1", result = "success"), + @ExceptionMapping(exception = "NPE2", result = "success", params = {"param1", "val1"}) + }) + public String run1() throws Exception { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/idx/Index.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/idx/Index.java new file mode 100644 index 000000000..124306a91 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/idx/Index.java @@ -0,0 +1,29 @@ +/* + * $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.idx; + +import com.opensymphony.xwork2.Action; + +public class Index implements Action { + public String execute() throws Exception { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/idx/idx2/Index.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/idx/idx2/Index.java new file mode 100644 index 000000000..e4388bd10 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/idx/idx2/Index.java @@ -0,0 +1,29 @@ +/* + * $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.idx.idx2; + +import com.opensymphony.xwork2.Action; + +public class Index implements Action { + public String execute() throws Exception { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptor2Action.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptor2Action.java new file mode 100644 index 000000000..80705942a --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptor2Action.java @@ -0,0 +1,42 @@ +/* + * $Id: ActionLevelResultAction.java 655902 2008-05-13 15:15:12Z bpontarelli $ + * + * 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.interceptor; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.InterceptorRef; +import org.apache.struts2.convention.annotation.InterceptorRefs; + +/** + *

+ * This is a test action with 2 interceptors at the action level. + *

+ */ +@InterceptorRefs({ + @InterceptorRef("interceptor-1"), + @InterceptorRef("interceptor-2") +}) +public class ActionLevelInterceptor2Action { + + @Action(value = "action800") + public String run1() throws Exception { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptor3Action.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptor3Action.java new file mode 100644 index 000000000..37f429ca9 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptor3Action.java @@ -0,0 +1,42 @@ +/* + * $Id: ActionLevelResultAction.java 655902 2008-05-13 15:15:12Z bpontarelli $ + * + * 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.interceptor; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.InterceptorRef; +import org.apache.struts2.convention.annotation.InterceptorRefs; + +/** + *

+ * This is a test action with 1 interceptor and 1 stack at the action level. + *

+ */ +@InterceptorRefs({ + @InterceptorRef("interceptor-1"), + @InterceptorRef("stack-1") +}) +public class ActionLevelInterceptor3Action { + + @Action(value = "action900") + public String run1() throws Exception { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptorAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptorAction.java new file mode 100644 index 000000000..474fc62c4 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/ActionLevelInterceptorAction.java @@ -0,0 +1,48 @@ +/* + * $Id: ActionLevelResultAction.java 655902 2008-05-13 15:15:12Z bpontarelli $ + * + * 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.interceptor; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.InterceptorRef; + +/** + *

+ * This is a test action with one interceptor at the action level. + *

+ */ +@InterceptorRef("interceptor-1") +public class ActionLevelInterceptorAction { + + @Action(value = "action500") + public String run1() throws Exception { + return null; + } + + @Action(value = "action600", interceptorRefs = @InterceptorRef("interceptor-2")) + public String run2() throws Exception { + return null; + } + + @Action(value = "action700", interceptorRefs = @InterceptorRef("stack-1")) + public String run3() throws Exception { + return null; + } +} diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/InterceptorsAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/InterceptorsAction.java new file mode 100644 index 000000000..40150e6d4 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/interceptor/InterceptorsAction.java @@ -0,0 +1,51 @@ +/* + * $Id: ActionLevelResultAction.java 655902 2008-05-13 15:15:12Z bpontarelli $ + * + * 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.interceptor; + +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.InterceptorRef; + +/** + *

+ * This is a test action with multiple interceptors. + *

+ */ +public class InterceptorsAction { + @Action(value = "action100", interceptorRefs = @InterceptorRef("interceptor-1")) + public String run1() { + return null; + } + + @Action(value = "action200", interceptorRefs = @InterceptorRef("stack-1")) + public String run2() { + return null; + } + + @Action(value = "action300", interceptorRefs = {@InterceptorRef("interceptor-1"), @InterceptorRef("interceptor-2")}) + public String run3() { + return null; + } + + @Action(value = "action400", interceptorRefs = {@InterceptorRef("interceptor-1"), @InterceptorRef("stack-1")}) + public String run4() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/ActionLevelNamespaceAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/ActionLevelNamespaceAction.java new file mode 100644 index 000000000..908c8cec5 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/ActionLevelNamespaceAction.java @@ -0,0 +1,35 @@ +/* + * $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.namespace; + +import org.apache.struts2.convention.annotation.Action; + +/** + *

+ * This class uses the action level annotation override. + *

+ */ +public class ActionLevelNamespaceAction { + @Action("/action-level/action") + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/ClassLevelNamespaceAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/ClassLevelNamespaceAction.java new file mode 100644 index 000000000..e73e4078c --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/ClassLevelNamespaceAction.java @@ -0,0 +1,35 @@ +/* + * $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.namespace; + +import org.apache.struts2.convention.annotation.Namespace; + +/** + *

+ * This class uses the class level annotation override. + *

+ */ +@Namespace("/class-level") +public class ClassLevelNamespaceAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/PackageLevelNamespaceAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/PackageLevelNamespaceAction.java new file mode 100644 index 000000000..88447a39a --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/PackageLevelNamespaceAction.java @@ -0,0 +1,32 @@ +/* + * $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.namespace; + +/** + *

+ * This class uses the package level annotation. + *

+ */ +public class PackageLevelNamespaceAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/package-info.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/package-info.java new file mode 100644 index 000000000..e90de8e05 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace/package-info.java @@ -0,0 +1,22 @@ +/* + * $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. + */ +@org.apache.struts2.convention.annotation.Namespace("/package-level") +package org.apache.struts2.convention.actions.namespace; \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace2/DefaultNamespaceAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace2/DefaultNamespaceAction.java new file mode 100644 index 000000000..153246362 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace2/DefaultNamespaceAction.java @@ -0,0 +1,32 @@ +/* + * $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.namespace2; + +/** + *

+ * This class uses the package level annotation. + *

+ */ +public class DefaultNamespaceAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace3/ActionLevelNamespacesAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace3/ActionLevelNamespacesAction.java new file mode 100644 index 000000000..1d1d0b956 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace3/ActionLevelNamespacesAction.java @@ -0,0 +1,34 @@ +/* + * $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.namespace3; + +import org.apache.struts2.convention.annotation.Namespace; +import org.apache.struts2.convention.annotation.Namespaces; + +@Namespaces({ + @Namespace("/namespaces1"), + @Namespace("/namespaces2") +}) +public class ActionLevelNamespacesAction { + public String execute() { + return null; + } +} diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace4/ActionAndPackageLevelNamespacesAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace4/ActionAndPackageLevelNamespacesAction.java new file mode 100644 index 000000000..61ac8d76c --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace4/ActionAndPackageLevelNamespacesAction.java @@ -0,0 +1,33 @@ +/* + * $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.namespace4; + +import org.apache.struts2.convention.annotation.Namespace; +import org.apache.struts2.convention.annotation.Namespaces; + +@Namespaces({ + @Namespace("/namespaces3") +}) +public class ActionAndPackageLevelNamespacesAction { + public String execute() { + return null; + } +} diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace4/package-info.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace4/package-info.java new file mode 100644 index 000000000..07407a7f0 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/namespace4/package-info.java @@ -0,0 +1,22 @@ +/* + * $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. + */ +@org.apache.struts2.convention.annotation.Namespace("/namespaces4") +package org.apache.struts2.convention.actions.namespace4; \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/params/ActionParamsMethodLevelAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/params/ActionParamsMethodLevelAction.java new file mode 100644 index 000000000..3ee2dbecb --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/params/ActionParamsMethodLevelAction.java @@ -0,0 +1,49 @@ +/* + * $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.params; + +import org.apache.struts2.convention.annotation.Action; + +public class ActionParamsMethodLevelAction { + private String param1; + private String param2; + + @Action(value = "actionParam1", params = {"param1", "val1", "param2", "val2"}) + public String run1() throws Exception { + return null; + } + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + public String getParam2() { + return param2; + } + + public void setParam2(String param2) { + this.param2 = param2; + } +} diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/ClassLevelParentPackageAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/ClassLevelParentPackageAction.java new file mode 100644 index 000000000..bcabeb942 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/ClassLevelParentPackageAction.java @@ -0,0 +1,35 @@ +/* + * $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.parentpackage; + +import org.apache.struts2.convention.annotation.ParentPackage; + +/** + *

+ * This is a parent package usage action. + *

+ */ +@ParentPackage("class-level") +public class ClassLevelParentPackageAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/PackageLevelParentPackageAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/PackageLevelParentPackageAction.java new file mode 100644 index 000000000..ebe3f1706 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/PackageLevelParentPackageAction.java @@ -0,0 +1,32 @@ +/* + * $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.parentpackage; + +/** + *

+ * This is a parent package usage action. + *

+ */ +public class PackageLevelParentPackageAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/package-info.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/package-info.java new file mode 100644 index 000000000..434b24d28 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/parentpackage/package-info.java @@ -0,0 +1,23 @@ +/* + * $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. + */ +@org.apache.struts2.convention.annotation.ParentPackage("package-level") +package org.apache.struts2.convention.actions.parentpackage; + diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultAction.java new file mode 100644 index 000000000..bf188cf9a --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultAction.java @@ -0,0 +1,38 @@ +/* + * $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. + *

+ */ +public class ActionLevelResultAction { + @Action(results = { + @Result(name="success", location="/WEB-INF/location/namespace/action-success.jsp") + }) + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultsAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultsAction.java new file mode 100644 index 000000000..27d1a5ad3 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ActionLevelResultsAction.java @@ -0,0 +1,41 @@ +/* + * $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. + *

+ */ +public class ActionLevelResultsAction { + @Action(results = { + @Result(name="error", location="error.jsp"), + @Result(name="input", location="foo.action", type="redirectAction"), + @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; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ClassLevelResultAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ClassLevelResultAction.java new file mode 100644 index 000000000..91fe4debc --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ClassLevelResultAction.java @@ -0,0 +1,35 @@ +/* + * $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.Result; + +/** + *

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

+ */ +@Result(name="error", location="error.jsp", params={"key", "value", "key1", "value1"}) +public class ClassLevelResultAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ClassLevelResultsAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ClassLevelResultsAction.java new file mode 100644 index 000000000..8e28f04e4 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/result/ClassLevelResultsAction.java @@ -0,0 +1,41 @@ +/* + * $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.Result; +import org.apache.struts2.convention.annotation.Results; + +/** + *

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

+ */ +@Results({ + @Result(name="error", location="error.jsp", params={"key", "ann-value", "key1", "ann-value1"}), + @Result(name="input", location="foo.action", type="redirectAction"), + @Result(name="success", location="/WEB-INF/location/namespace/action-success.jsp"), + @Result(name="failure", location="/WEB-INF/location/namespace/action-failure.jsp") +}) +public class ClassLevelResultsAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/ClassLevelResultPathAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/ClassLevelResultPathAction.java new file mode 100644 index 000000000..67d4d16ea --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/ClassLevelResultPathAction.java @@ -0,0 +1,35 @@ +/* + * $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.resultpath; + +import org.apache.struts2.convention.annotation.ResultPath; + +/** + *

+ * This class is a test action with the default result path. + *

+ */ +@ResultPath("/class-level") +public class ClassLevelResultPathAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/PackageLevelResultPathAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/PackageLevelResultPathAction.java new file mode 100644 index 000000000..473eeac8e --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/PackageLevelResultPathAction.java @@ -0,0 +1,32 @@ +/* + * $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.resultpath; + +/** + *

+ * This class is a test action with the default result path. + *

+ */ +public class PackageLevelResultPathAction { + public String execute() { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/package-info.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/package-info.java new file mode 100644 index 000000000..b4af5a0a2 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/resultpath/package-info.java @@ -0,0 +1,23 @@ +/* + * $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. + */ +@org.apache.struts2.convention.annotation.ResultPath("/package-level") +package org.apache.struts2.convention.actions.resultpath; + diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/skip/Index.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/skip/Index.java new file mode 100644 index 000000000..987be9358 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/skip/Index.java @@ -0,0 +1,29 @@ +/* + * $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.skip; + +import com.opensymphony.xwork2.Action; + +public class Index implements Action { + public String execute() throws Exception { + return null; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/annotation/AnnotationToolsTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/annotation/AnnotationToolsTest.java new file mode 100644 index 000000000..94b307ed0 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/annotation/AnnotationToolsTest.java @@ -0,0 +1,45 @@ +/* + * $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.annotation; + +import junit.framework.TestCase; + +import org.apache.struts2.convention.actions.namespace.PackageLevelNamespaceAction; +import org.apache.struts2.convention.actions.resultpath.ClassLevelResultPathAction; + +/** + *

+ * This class tests the annotation tools. + *

+ */ +public class AnnotationToolsTest extends TestCase { + public void testFindAnnotationOnClass() { + ResultPath rl = AnnotationTools.findAnnotation(ClassLevelResultPathAction.class, ResultPath.class); + assertNotNull(rl); + assertEquals("/class-level", rl.value()); + } + + public void testFindAnnotationOnPackage() { + Namespace ns = AnnotationTools.findAnnotation(PackageLevelNamespaceAction.class, Namespace.class); + assertNotNull(ns); + assertEquals("/package-level", ns.value()); + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/dontfind/DontFindMeAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/dontfind/DontFindMeAction.java new file mode 100644 index 000000000..11baeaba2 --- /dev/null +++ b/plugins/convention/src/test/java/org/apache/struts2/convention/dontfind/DontFindMeAction.java @@ -0,0 +1,31 @@ +/* + * $Id: ActionNamesAction.java 655902 2008-05-13 15:15:12Z bpontarelli $ + * + * 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.dontfind; + +import org.apache.struts2.convention.annotation.Action; + +//should never be found by the scanner +public class DontFindMeAction { + @Action("foo") + public String foo() { + return "bar"; + } +} \ No newline at end of file diff --git a/plugins/convention/src/test/resources/WEB-INF/component/no-annotation-foo.ftl b/plugins/convention/src/test/resources/WEB-INF/component/no-annotation-foo.ftl new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/convention/src/test/resources/WEB-INF/component/no-annotation.ftl b/plugins/convention/src/test/resources/WEB-INF/component/no-annotation.ftl new file mode 100644 index 000000000..e69de29bb