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 2026/07/26 06:26:25 UTC

(struts) 07/08: WW-3784 feat(convention): order annotated wildcard actions most-specific-first

This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch WW-3784-annotated-wildcard-specificity-ordering
in repository https://gitbox.apache.org/repos/asf/struts.git

commit caf474f53847a161fe40b8e61b2cded33284a5ef
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Sun Jul 26 05:55:32 2026 +0200

    WW-3784 feat(convention): order annotated wildcard actions most-specific-first
    
    Sorts each convention-built package's action configs by pattern specificity so a
    specific pattern (some/usefull/*) is matched before a general one (some/*),
    regardless of class-scan order. Also makes convention action ordering deterministic.
    
    Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
 .../PackageBasedActionConfigBuilder.java           | 19 ++++++++
 ...PackageBasedActionConfigBuilderReorderTest.java | 53 ++++++++++++++++++++++
 2 files changed, 72 insertions(+)

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 711b25b72..9054fdae9 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
@@ -69,6 +69,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -795,6 +796,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
 
         buildIndexActions(packageConfigs);
 
+        reorderActionConfigsBySpecificity(packageConfigs);
+
         // Add the new actions to the configuration
         Set<String> packageNames = packageConfigs.keySet();
         for (String packageName : packageNames) {
@@ -802,6 +805,22 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
         }
     }
 
+    /**
+     * Reorders each package's action configs most-specific-first so that annotated wildcard
+     * patterns follow specific-before-general precedence under first-match-wins matching.
+     * XML-defined packages are untouched: only packages built by this convention builder pass
+     * through here.
+     *
+     * @param packageConfigs the packages built during {@link #buildConfiguration(Set)}
+     * @since 7.3.0 (WW-3784)
+     */
+    static void reorderActionConfigsBySpecificity(Map<String, PackageConfig.Builder> packageConfigs) {
+        Comparator<String> bySpecificity = new ActionNameSpecificityComparator();
+        for (PackageConfig.Builder packageConfig : packageConfigs.values()) {
+            packageConfig.reorderActionConfigs(bySpecificity);
+        }
+    }
+
     private Set<String> getAllowedMethods(Class<?> actionClass) {
         List<AllowedMethods> annotations = AnnotationUtils.findAnnotations(actionClass, AllowedMethods.class);
         if (annotations.isEmpty()) {
diff --git a/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderReorderTest.java b/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderReorderTest.java
new file mode 100644
index 000000000..110e2614a
--- /dev/null
+++ b/plugins/convention/src/test/java/org/apache/struts2/convention/PackageBasedActionConfigBuilderReorderTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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 org.apache.struts2.config.entities.ActionConfig;
+import org.apache.struts2.config.entities.PackageConfig;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class PackageBasedActionConfigBuilderReorderTest {
+
+    @Test
+    public void reordersEveryPackageSpecificFirst() {
+        // input insertion order is general-before-specific (the bug scenario)
+        PackageConfig.Builder pkg = new PackageConfig.Builder("test");
+        pkg.addActionConfig("some/*", action("some/*"));
+        pkg.addActionConfig("some/usefull/*", action("some/usefull/*"));
+
+        Map<String, PackageConfig.Builder> packageConfigs = new HashMap<>();
+        packageConfigs.put("test", pkg);
+
+        PackageBasedActionConfigBuilder.reorderActionConfigsBySpecificity(packageConfigs);
+
+        List<String> keys = new ArrayList<>(pkg.build().getActionConfigs().keySet());
+        assertEquals(List.of("some/usefull/*", "some/*"), keys);
+    }
+
+    private ActionConfig action(String name) {
+        return new ActionConfig.Builder("test", name, "com.example.Action").build();
+    }
+}