You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2015/09/28 20:53:05 UTC
[16/33] struts git commit: Adds annotation to support AllowedMethods
Adds annotation to support AllowedMethods
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/ab5fb27d
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/ab5fb27d
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/ab5fb27d
Branch: refs/heads/master
Commit: ab5fb27d368f8857e137bc3a319c21fc85b519ee
Parents: dd406fb
Author: Lukasz Lenart <lu...@gmail.com>
Authored: Thu Sep 3 13:03:55 2015 +0200
Committer: Lukasz Lenart <lu...@gmail.com>
Committed: Thu Sep 3 13:03:55 2015 +0200
----------------------------------------------------------------------
.../xwork2/config/entities/PackageConfig.java | 13 ++++-
.../PackageBasedActionConfigBuilder.java | 30 ++++++++--
.../convention/annotation/AllowedMethods.java | 30 ++++++++++
.../PackageBasedActionConfigBuilderTest.java | 60 ++++++++++++++++++--
.../ClassLevelAllowedMethodsAction.java | 10 ++++
.../PackageLevelAllowedMethodsAction.java | 7 +++
.../actions/allowedmethods/package-info.java | 23 ++++++++
.../PackageLevelAllowedMethodsChildAction.java | 9 +++
8 files changed, 171 insertions(+), 11 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/ab5fb27d/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
index 19ce36f..093985c 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
@@ -531,7 +531,18 @@ public class PackageConfig extends Located implements Comparable, Serializable,
}
public Set<String> getGlobalAllowedMethods() {
- return target.globalAllowedMethods;
+ Set <String> allowedMethods = target.globalAllowedMethods;
+ allowedMethods.addAll(getParentsAllowedMethods(target.parents));
+ return allowedMethods;
+ }
+
+ public Set<String> getParentsAllowedMethods(List<PackageConfig> parents) {
+ Set<String> allowedMethods = new HashSet<>();
+ for (PackageConfig parent : parents) {
+ allowedMethods.addAll(parent.globalAllowedMethods);
+ allowedMethods.addAll(getParentsAllowedMethods(parent.getParents()));
+ }
+ return allowedMethods;
}
public Builder addGlobalAllowedMethods(Set<String> allowedMethods) {
http://git-wip-us.apache.org/repos/asf/struts/blob/ab5fb27d/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java
----------------------------------------------------------------------
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
index b4a8be9..64a0e98 100644
--- a/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java
+++ b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java
@@ -42,6 +42,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.convention.annotation.*;
+import org.apache.struts2.convention.annotation.AllowedMethods;
import java.io.IOException;
import java.lang.reflect.Method;
@@ -652,6 +653,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
String actionPackage = actionClass.getPackage().getName();
LOG.debug("Processing class [{}] in package [{}]", actionClass.getName(), actionPackage);
+ Set<String> allowedMethods = getAllowedMethods(actionClass);
+
// Determine the default namespace and action name
List<String> namespaces = determineActionNamespace(actionClass);
for (String namespace : namespaces) {
@@ -692,7 +695,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
// Build the default
if (!found) {
- createActionConfig(defaultPackageConfig, actionClass, defaultActionName, DEFAULT_METHOD, null);
+ createActionConfig(defaultPackageConfig, actionClass, defaultActionName, DEFAULT_METHOD, null, allowedMethods);
}
}
@@ -706,14 +709,14 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
actionClass, action);
}
- createActionConfig(pkgCfg, actionClass, defaultActionName, method, action);
+ createActionConfig(pkgCfg, actionClass, defaultActionName, method, action, allowedMethods);
}
}
// 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 && actionAnnotation == null && actionsAnnotation == null) {
- createActionConfig(defaultPackageConfig, actionClass, defaultActionName, null, actionAnnotation);
+ createActionConfig(defaultPackageConfig, actionClass, defaultActionName, null, actionAnnotation, allowedMethods);
}
//if there are @Actions or @Action at the class level, create the mappings for them
@@ -721,9 +724,9 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
if (actionsAnnotation != null) {
List<Action> actionAnnotations = checkActionsAnnotation(actionsAnnotation);
for (Action actionAnnotation2 : actionAnnotations)
- createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation2);
+ createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation2, allowedMethods);
} else if (actionAnnotation != null)
- createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation);
+ createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation, allowedMethods);
}
}
@@ -736,6 +739,15 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
}
+ private Set<String> getAllowedMethods(Class<?> actionClass) {
+ AllowedMethods annotation = AnnotationUtils.findAnnotation(actionClass, AllowedMethods.class);
+ if (annotation == null) {
+ return Collections.emptySet();
+ } else {
+ return TextParseUtil.commaDelimitedStringToSet(annotation.value());
+ }
+ }
+
/**
* Interfaces, enums, annotations, and abstract classes cannot be instantiated.
* @param actionClass class to check
@@ -896,7 +908,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* @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) {
+ String actionMethod, Action annotation, Set<String> allowedMethods) {
String className = actionClass.getName();
if (annotation != null) {
actionName = annotation.value() != null && annotation.value().equals(Action.DEFAULT_VALUE) ? actionName : annotation.value();
@@ -909,6 +921,12 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
ActionConfig.Builder actionConfig = new ActionConfig.Builder(pkgCfg.getName(), actionName, className);
actionConfig.methodName(actionMethod);
+ if (!allowedMethods.contains(actionMethod)) {
+ actionConfig.addAllowedMethod(actionMethod);
+ }
+ actionConfig.addAllowedMethod(allowedMethods);
+ actionConfig.addAllowedMethod(pkgCfg.getGlobalAllowedMethods());
+
if (LOG.isDebugEnabled()) {
LOG.debug("Creating action config for class [{}], name [{}] and package name [{}] in namespace [{}]",
actionClass.toString(), actionName, pkgCfg.getName(), pkgCfg.getNamespace());
http://git-wip-us.apache.org/repos/asf/struts/blob/ab5fb27d/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/AllowedMethods.java
----------------------------------------------------------------------
diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/AllowedMethods.java b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/AllowedMethods.java
new file mode 100644
index 0000000..8e3c9ab
--- /dev/null
+++ b/plugins/convention/src/main/java/org/apache/struts2/convention/annotation/AllowedMethods.java
@@ -0,0 +1,30 @@
+package org.apache.struts2.convention.annotation;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <!-- START SNIPPET: javadoc -->
+ * <p>
+ * This annotation allows actions to specify allowed action methods
+ * to limit access to any other public action's methods
+ * </p>
+ *
+ * <p>
+ * This annotation can be used directly on Action classes or
+ * in the <strong>package-info.java</strong> class in order
+ * to specify global allowed methods for all sub-packages.
+ * </p>
+ * <!-- END SNIPPET: javadoc -->
+ */
+@Target({ElementType.TYPE, ElementType.PACKAGE})
+@Retention(value = RetentionPolicy.RUNTIME)
+public @interface AllowedMethods {
+
+ String value() default ActionConfig.DEFAULT_METHOD;
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/ab5fb27d/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java
----------------------------------------------------------------------
diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java
index 38403a0..61ed3eb 100644
--- a/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java
+++ b/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderTest.java
@@ -29,6 +29,7 @@ import com.opensymphony.xwork2.factory.DefaultResultFactory;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Scope.Strategy;
import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
+import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.fs.DefaultFileManager;
import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory;
import com.opensymphony.xwork2.util.reflection.ReflectionException;
@@ -37,6 +38,9 @@ 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.action.*;
+import org.apache.struts2.convention.actions.allowedmethods.ClassLevelAllowedMethodsAction;
+import org.apache.struts2.convention.actions.allowedmethods.PackageLevelAllowedMethodsAction;
+import org.apache.struts2.convention.actions.allowedmethods.sub.PackageLevelAllowedMethodsChildAction;
import org.apache.struts2.convention.actions.chain.ChainedAction;
import org.apache.struts2.convention.actions.defaultinterceptor.SingleActionNameAction2;
import org.apache.struts2.convention.actions.exception.ExceptionsActionLevelAction;
@@ -125,8 +129,10 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
new ResultTypeConfig.Builder("chain",
ActionChainResult.class.getName()).defaultResultParam("actionName").build()};
+ Set<String> globalAllowedMethods = TextParseUtil.commaDelimitedStringToSet("execute,browse,cancel,input");
+
PackageConfig strutsDefault = makePackageConfig("struts-default", null, null, "dispatcher",
- defaultResults, defaultInterceptors, defaultInterceptorStacks);
+ defaultResults, defaultInterceptors, defaultInterceptorStacks, globalAllowedMethods);
PackageConfig packageLevelParentPkg = makePackageConfig("package-level", null, null, null);
PackageConfig classLevelParentPkg = makePackageConfig("class-level", null, null, null);
@@ -151,6 +157,16 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
"/parentpackage", packageLevelParentPkg, null);
PackageConfig packageLevelSubPkg = makePackageConfig("org.apache.struts2.convention.actions.parentpackage.sub#package-level#/parentpackage/sub",
"/parentpackage/sub", packageLevelParentPkg, null);
+
+ // Unexpected method call build(class org.apache.struts2.convention.actions.allowedmethods.PackageLevelAllowedMethodsAction, null, "package-level-allowed-methods", PackageConfig: [org.apache.struts2.convention.actions.allowedmethods#struts-default#/allowedmethods] for namespace [/allowedmethods] with parents [[PackageConfig: [struts-default] for namespace [] with parents [[]]]]):
+ PackageConfig packageLevelAllowedMethodsPkg = makePackageConfig("org.apache.struts2.convention.actions.allowedmethods#struts-default#/allowedmethods",
+ "/allowedmethods", strutsDefault, null);
+ PackageConfig packageLevelAllowedMethodsSubPkg = makePackageConfig("org.apache.struts2.convention.actions.allowedmethods.sub#struts-default#/allowedmethods/sub",
+ "/allowedmethods/sub", strutsDefault, null);
+
+ PackageConfig classLevelAllowedMethodsPkg = makePackageConfig("org.apache.struts2.convention.actions.allowedmethods#struts-default#/allowedmethods",
+ "/allowedmethods", strutsDefault, null);
+
PackageConfig differentPkg = makePackageConfig("org.apache.struts2.convention.actions.parentpackage#class-level#/parentpackage",
"/parentpackage", classLevelParentPkg, null);
PackageConfig differentSubPkg = makePackageConfig("org.apache.struts2.convention.actions.parentpackage.sub#class-level#/parentpackage/sub",
@@ -261,6 +277,11 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
expect(resultMapBuilder.build(ClassLevelParentPackageAction.class, null, "class-level-parent-package", differentPkg)).andReturn(results);
expect(resultMapBuilder.build(ClassLevelParentPackageChildAction.class, null, "class-level-parent-package-child", differentSubPkg)).andReturn(results);
+ /* org.apache.struts2.convention.actions.allowedmethods */
+ expect(resultMapBuilder.build(ClassLevelAllowedMethodsAction.class, null, "class-level-allowed-methods", classLevelAllowedMethodsPkg)).andReturn(results);
+ expect(resultMapBuilder.build(PackageLevelAllowedMethodsAction.class, null, "package-level-allowed-methods", packageLevelAllowedMethodsPkg)).andReturn(results);
+ expect(resultMapBuilder.build(PackageLevelAllowedMethodsChildAction.class, null, "package-level-allowed-methods-child", packageLevelAllowedMethodsSubPkg)).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);
@@ -450,7 +471,7 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
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.idx.idx2#struts-default#/idx/idx2");
/* org.apache.struts2.convention.actions.defaultinterceptor */
pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.defaultinterceptor#struts-default#/defaultinterceptor");
@@ -514,6 +535,33 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
verifyActionConfig(pkgConfig, "package-level-parent-package-child", PackageLevelParentPackageChildAction.class, "execute", pkgConfig.getName());
assertEquals("package-level", pkgConfig.getParents().get(0).getName());
+ /* org.apache.struts2.convention.actions.allowedmethods class level */
+ pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.allowedmethods#struts-default#/allowedmethods");
+ assertNotNull(pkgConfig);
+ assertEquals(2, pkgConfig.getActionConfigs().size());
+ verifyActionConfig(pkgConfig, "class-level-allowed-methods", ClassLevelAllowedMethodsAction.class, "execute", pkgConfig.getName());
+ assertEquals("struts-default", pkgConfig.getParents().get(0).getName());
+
+ ActionConfig actionConfig = pkgConfig.getActionConfigs().get("class-level-allowed-methods");
+ assertEquals(actionConfig.getAllowedMethods().size(), 5);
+ assertTrue(actionConfig.getAllowedMethods().contains("execute"));
+ assertTrue(actionConfig.getAllowedMethods().contains("end"));
+ assertTrue(actionConfig.getAllowedMethods().contains("input"));
+
+ /* org.apache.struts2.convention.actions.allowedmethods.sub package level */
+ pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.allowedmethods.sub#struts-default#/allowedmethods/sub");
+ assertNotNull(pkgConfig);
+ assertEquals(1, pkgConfig.getActionConfigs().size());
+ verifyActionConfig(pkgConfig, "package-level-allowed-methods-child", PackageLevelAllowedMethodsChildAction.class, "execute", pkgConfig.getName());
+ assertEquals("struts-default", pkgConfig.getParents().get(0).getName());
+
+ actionConfig = pkgConfig.getActionConfigs().get("package-level-allowed-methods-child");
+ assertEquals(actionConfig.getAllowedMethods().size(), 6);
+ assertTrue(actionConfig.getAllowedMethods().contains("execute"));
+ assertTrue(actionConfig.getAllowedMethods().contains("home"));
+ assertTrue(actionConfig.getAllowedMethods().contains("start"));
+ assertTrue(actionConfig.getAllowedMethods().contains("input"));
+
/* org.apache.struts2.convention.actions.result */
pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.result#struts-default#/result");
assertNotNull(pkgConfig);
@@ -631,12 +679,12 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
private PackageConfig makePackageConfig(String name, String namespace, PackageConfig parent,
String defaultResultType, ResultTypeConfig... results) {
- return makePackageConfig(name, namespace, parent, defaultResultType, results, null, null);
+ return makePackageConfig(name, namespace, parent, defaultResultType, results, null, null, null);
}
private PackageConfig makePackageConfig(String name, String namespace, PackageConfig parent,
String defaultResultType, ResultTypeConfig[] results, List<InterceptorConfig> interceptors,
- List<InterceptorStackConfig> interceptorStacks) {
+ List<InterceptorStackConfig> interceptorStacks, Set<String> globalAllowedMethods) {
PackageConfig.Builder builder = new PackageConfig.Builder(name);
if (namespace != null) {
builder.namespace(namespace);
@@ -663,6 +711,10 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
}
}
+ if (globalAllowedMethods != null) {
+ builder.addGlobalAllowedMethods(globalAllowedMethods);
+ }
+
return new MyPackageConfig(builder.build());
}
http://git-wip-us.apache.org/repos/asf/struts/blob/ab5fb27d/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/ClassLevelAllowedMethodsAction.java
----------------------------------------------------------------------
diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/ClassLevelAllowedMethodsAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/ClassLevelAllowedMethodsAction.java
new file mode 100644
index 0000000..24ea489
--- /dev/null
+++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/ClassLevelAllowedMethodsAction.java
@@ -0,0 +1,10 @@
+package org.apache.struts2.convention.actions.allowedmethods;
+
+import org.apache.struts2.convention.annotation.AllowedMethods;
+
+@AllowedMethods("end")
+public class ClassLevelAllowedMethodsAction {
+
+ public String execute() { return null; }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/ab5fb27d/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/PackageLevelAllowedMethodsAction.java
----------------------------------------------------------------------
diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/PackageLevelAllowedMethodsAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/PackageLevelAllowedMethodsAction.java
new file mode 100644
index 0000000..eab0895
--- /dev/null
+++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/PackageLevelAllowedMethodsAction.java
@@ -0,0 +1,7 @@
+package org.apache.struts2.convention.actions.allowedmethods;
+
+public class PackageLevelAllowedMethodsAction {
+
+ public String execute() { return null; }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/ab5fb27d/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/package-info.java
----------------------------------------------------------------------
diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/package-info.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/package-info.java
new file mode 100644
index 0000000..461131f
--- /dev/null
+++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/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.AllowedMethods("home,start")
+package org.apache.struts2.convention.actions.allowedmethods;
+
http://git-wip-us.apache.org/repos/asf/struts/blob/ab5fb27d/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/sub/PackageLevelAllowedMethodsChildAction.java
----------------------------------------------------------------------
diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/sub/PackageLevelAllowedMethodsChildAction.java b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/sub/PackageLevelAllowedMethodsChildAction.java
new file mode 100644
index 0000000..4a6223d
--- /dev/null
+++ b/plugins/convention/src/test/java/org/apache/struts2/convention/actions/allowedmethods/sub/PackageLevelAllowedMethodsChildAction.java
@@ -0,0 +1,9 @@
+package org.apache.struts2.convention.actions.allowedmethods.sub;
+
+import org.apache.struts2.convention.actions.allowedmethods.PackageLevelAllowedMethodsAction;
+
+public class PackageLevelAllowedMethodsChildAction extends PackageLevelAllowedMethodsAction {
+
+ public String execute() { return null; }
+
+}