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 <[email protected]>
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 <[email protected]>
---
 .../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();
+    }
+}

Reply via email to