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

coheigea pushed a commit to branch coheigea/CXF-7647
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit a556b6a6d2123c18ba4aee6b2a2d27d94ecec5e2
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Oct 22 06:41:46 2026 +0100

    CXF-7647: Don't register duplicate endpoints for compiler-generated bridge 
methods
---
 .../org/apache/cxf/jaxrs/utils/ResourceUtils.java  |  39 +++++++-
 .../apache/cxf/jaxrs/utils/ResourceUtilsTest.java  | 103 +++++++++++++++++++++
 2 files changed, 141 insertions(+), 1 deletion(-)

diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java
index 5b29d15b132..1b5b71ad3a3 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java
@@ -336,8 +336,18 @@ public final class ResourceUtils {
         }
         for (Method m : serviceClass.getMethods()) {
             if (m.isBridge() || m.isSynthetic()) {
-                //if a bridge/synthetic method isn't already mapped to 
something, go ahead and do it
                 Method annotatedMethod = 
AnnotationUtils.getAnnotatedMethod(serviceClass, m);
+                if (annotatedMethod == m && m.isBridge()) {
+                    //no annotated method was found for the bridge other than 
itself, i.e. the JAX-RS
+                    //annotations were copied onto the bridge by the compiler 
but with the erased parameter
+                    //types, so if the method it bridges to is already mapped 
then reuse its operation
+                    Method bridged = findBridgedMethod(serviceClass, m);
+                    if (bridged != null && 
md.getOperationResourceInfo(bridged) != null) {
+                        bindResourceMethod(md, m, bridged);
+                        continue;
+                    }
+                }
+                //if a bridge/synthetic method isn't already mapped to 
something, go ahead and do it
                 if (!annotatedMethods.containsKey(annotatedMethod)) {
                     evaluateResourceMethod(cri, enableStatic, md, m, 
annotatedMethod);
                     annotatedMethods.put(annotatedMethod, m);
@@ -352,6 +362,33 @@ public final class ResourceUtils {
         cri.setMethodDispatcher(md);
     }
     
+    private static Method findBridgedMethod(Class<?> serviceClass, Method 
bridge) {
+        Class<?>[] bridgeParamTypes = bridge.getParameterTypes();
+        Method bridged = null;
+        for (Method m : serviceClass.getMethods()) {
+            if (m.isBridge() || m.isSynthetic()
+                || !m.getName().equals(bridge.getName())
+                || m.getDeclaringClass() != bridge.getDeclaringClass()
+                || m.getParameterCount() != bridgeParamTypes.length
+                || 
!bridge.getReturnType().isAssignableFrom(m.getReturnType())) {
+                continue;
+            }
+            Class<?>[] paramTypes = m.getParameterTypes();
+            boolean matches = true;
+            for (int i = 0; i < paramTypes.length && matches; i++) {
+                matches = bridgeParamTypes[i].isAssignableFrom(paramTypes[i]);
+            }
+            if (matches) {
+                if (bridged != null) {
+                    // ambiguous, more than one candidate
+                    return null;
+                }
+                bridged = m;
+            }
+        }
+        return bridged;
+    }
+
     private static void bindResourceMethod(MethodDispatcher md, Method m, 
Method bound) {
         final OperationResourceInfo ori = md.getOperationResourceInfo(bound);
         if (ori != null && !ori.getMethodToInvoke().equals(m)) {
diff --git 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java
 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java
index 7d2d1e75fe1..679be80e0d8 100644
--- 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java
+++ 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java
@@ -30,10 +30,15 @@ import java.util.Set;
 
 import jakarta.validation.constraints.NotNull;
 import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.BeanParam;
 import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PATCH;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.UriInfo;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import org.apache.cxf.BusFactory;
@@ -170,6 +175,48 @@ public class ResourceUtilsTest {
         assertEquals("GET", ori.getHttpMethod());
     }
     
+    @Test
+    public void testInterfaceBridgeMethodsWithBeanParam() throws Exception {
+        ClassResourceInfo cri = 
ResourceUtils.createClassResourceInfo(VersionedUserResource.class,
+            VersionedUserResource.class, true, true);
+        assertNotNull(cri);
+        Set<OperationResourceInfo> oris = 
cri.getMethodDispatcher().getOperationResourceInfos();
+        assertEquals(2, oris.size());
+        for (OperationResourceInfo ori : oris) {
+            Method m = ori.getMethodToInvoke();
+            assertFalse(m.toString(), m.isBridge());
+            assertEquals(UserIdParam.class, m.getParameterTypes()[0]);
+        }
+
+        for (Method m : VersionedUserResource.class.getMethods()) {
+            if (m.isBridge()) {
+                OperationResourceInfo ori = 
cri.getMethodDispatcher().getOperationResourceInfo(m);
+                assertNotNull(m.toString(), ori);
+                assertFalse(ori.getMethodToInvoke().isBridge());
+                assertEquals(m.getName(), ori.getMethodToInvoke().getName());
+            }
+        }
+    }
+
+    @Test
+    public void testBridgeMethodToAnnotatedSuperInterfaceMethod() throws 
Exception {
+        ClassResourceInfo cri = 
ResourceUtils.createClassResourceInfo(OverridingAnnotatedResource.class,
+            OverridingAnnotatedResource.class, true, true);
+        assertNotNull(cri);
+        Set<OperationResourceInfo> oris = 
cri.getMethodDispatcher().getOperationResourceInfos();
+        assertEquals(2, oris.size());
+
+        OperationResourceInfo ori = 
cri.getMethodDispatcher().getOperationResourceInfo(
+            OverridingAnnotatedResource.class.getMethod("update", 
String.class));
+        assertEquals("PUT", ori.getHttpMethod());
+        assertEquals("/own", ori.getURITemplate().getValue());
+
+        ori = cri.getMethodDispatcher().getOperationResourceInfo(
+            OverridingAnnotatedResource.class.getMethod("update", 
Object.class));
+        assertEquals("POST", ori.getHttpMethod());
+        assertEquals("/base", ori.getURITemplate().getValue());
+    }
+
     @Test
     public void testClassResourceInfoWithBridgeMethod() throws Exception {
         ClassResourceInfo cri =
@@ -413,6 +460,62 @@ public class ResourceUtilsTest {
         }
     }
 
+    public abstract static class RequestParam<T> {
+        @PathParam("id")
+        private String id;
+
+        public String getId() {
+            return id;
+        }
+
+        public void setId(String id) {
+            this.id = id;
+        }
+    }
+
+    public static class UserIdParam extends RequestParam<String> {
+
+    }
+
+    public interface VersionedResource<U, I extends RequestParam<String>> {
+        Response update(I idParam, U updateDto);
+
+        default Response updateByPost(I idParam, U updateDto) {
+            return update(idParam, updateDto);
+        }
+    }
+
+    @Path("/users")
+    public interface VersionedUserResource extends VersionedResource<Book, 
UserIdParam> {
+        @PATCH
+        @Path("{id}")
+        @Override
+        Response update(@BeanParam UserIdParam idParam, Book updateDto);
+
+        @POST
+        @Path("{id}")
+        @Override
+        default Response updateByPost(@BeanParam UserIdParam idParam, Book 
updateDto) {
+            return VersionedResource.super.updateByPost(idParam, updateDto);
+        }
+    }
+
+    public interface AnnotatedGenericResource<T> {
+        @POST
+        @Path("base")
+        String update(T dto);
+    }
+
+    @Path("/overriding")
+    public static class OverridingAnnotatedResource implements 
AnnotatedGenericResource<String> {
+        @PUT
+        @Path("own")
+        @Override
+        public String update(String dto) {
+            return dto;
+        }
+    }
+
     @XmlRootElement
     public static class OrderItem {
 

Reply via email to