This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/4.1.x-fixes by this push:
new 0aae4c911a7 CXF-7647: Don't register duplicate endpoints for
compiler-generated bridge methods (#3618)
0aae4c911a7 is described below
commit 0aae4c911a77179d417e7269779f5b333ce01ac9
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Oct 22 16:44:36 2026 +0100
CXF-7647: Don't register duplicate endpoints for compiler-generated bridge
methods (#3618)
(cherry picked from commit 5085d2090b3dfbd37ea35ec74f34a5dd4a51a21d)
---
.../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 1355d1a1b44..a0ecff1b46d 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 {