You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2021/09/01 00:35:56 UTC

[cxf] branch 3.4.x-fixes updated: CXF-8578: Bridge methods for covariant return types cannot be invoked on client proxies (#842)

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

reta pushed a commit to branch 3.4.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/3.4.x-fixes by this push:
     new 41cba7f  CXF-8578: Bridge methods for covariant return types cannot be invoked on client proxies (#842)
41cba7f is described below

commit 41cba7f66fc339107fd7544019635535fdb60417
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Tue Aug 31 17:47:09 2021 -0400

    CXF-8578: Bridge methods for covariant return types cannot be invoked on client proxies (#842)
    
    (cherry picked from commit 7dbef5304979fd2e864e0b9643d9e1c5e7da163b)
---
 .../org/apache/cxf/jaxrs/utils/ResourceUtils.java  |  22 ++++-
 .../apache/cxf/jaxrs/utils/ResourceUtilsTest.java  | 104 +++++++++++++++++++++
 .../jaxrs/client/JAXRSClientFactoryBeanTest.java   |  16 ++++
 .../org/apache/cxf/jaxrs/resources/SuperBook.java  |  44 +++++++++
 .../apache/cxf/jaxrs/resources/SuperBookStore.java |  74 +++++++++++++++
 5 files changed, 255 insertions(+), 5 deletions(-)

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 10119d7..04e4c5c 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
@@ -322,15 +322,15 @@ public final class ResourceUtils {
         MethodDispatcher md = new MethodDispatcher();
         Class<?> serviceClass = cri.getServiceClass();
 
-        final Set<Method> annotatedMethods = new HashSet<>();
+        final Map<Method, Method> annotatedMethods = new HashMap<>();
 
         for (Method m : serviceClass.getMethods()) {
             if (!m.isBridge() && !m.isSynthetic()) {
                 //do real methods first
                 Method annotatedMethod = AnnotationUtils.getAnnotatedMethod(serviceClass, m);
-                if (!annotatedMethods.contains(annotatedMethod)) {
+                if (!annotatedMethods.containsKey(annotatedMethod)) {
                     evaluateResourceMethod(cri, enableStatic, md, m, annotatedMethod);
-                    annotatedMethods.add(annotatedMethod);
+                    annotatedMethods.put(annotatedMethod, m);
                 }
             }
         }
@@ -338,14 +338,26 @@ public final class ResourceUtils {
             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 (!annotatedMethods.contains(annotatedMethod)) {
+                if (!annotatedMethods.containsKey(annotatedMethod)) {
                     evaluateResourceMethod(cri, enableStatic, md, m, annotatedMethod);
-                    annotatedMethods.add(annotatedMethod);
+                    annotatedMethods.put(annotatedMethod, m);
+                } else {
+                    // Certain synthetic / bridge methods could be quite useful to handle
+                    // methods with co-variant return types, especially when used with client proxies,
+                    // see please: https://blogs.oracle.com/sundararajan/covariant-return-types-in-java
+                    bindResourceMethod(md, m, annotatedMethods.get(annotatedMethod));
                 }
             }
         }
         cri.setMethodDispatcher(md);
     }
+    
+    private static void bindResourceMethod(MethodDispatcher md, Method m, Method bound) {
+        final OperationResourceInfo ori = md.getOperationResourceInfo(bound);
+        if (ori != null && !ori.getMethodToInvoke().equals(m)) {
+            md.bind(ori, bound, m);
+        }
+    }
 
     private static void evaluateResourceMethod(ClassResourceInfo cri, boolean enableStatic, MethodDispatcher md,
                                                Method m, Method annotatedMethod) {
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 e470f44..f3e89b3 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
@@ -21,6 +21,7 @@ package org.apache.cxf.jaxrs.utils;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -48,12 +49,14 @@ import org.apache.cxf.jaxrs.model.UserResource;
 import org.apache.cxf.jaxrs.resources.Book;
 import org.apache.cxf.jaxrs.resources.BookInterface;
 import org.apache.cxf.jaxrs.resources.Chapter;
+import org.apache.cxf.jaxrs.resources.SuperBook;
 
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 public class ResourceUtilsTest {
@@ -167,6 +170,76 @@ public class ResourceUtilsTest {
         assertNotNull(ori);
         assertEquals("GET", ori.getHttpMethod());
     }
+    
+    @Test
+    public void testClassResourceInfoWithBridgeMethod() throws Exception {
+        ClassResourceInfo cri =
+            ResourceUtils.createClassResourceInfo(ExampleBridgeImpl.class, ExampleBridgeImpl.class, true, true);
+        assertNotNull(cri);
+        assertEquals(1, cri.getMethodDispatcher().getOperationResourceInfos().size());
+        
+        Method m = ExampleBridgeImpl.class.getMethod("get");
+        OperationResourceInfo ori = cri.getMethodDispatcher().getOperationResourceInfo(m);
+        assertNotNull(ori);
+        assertEquals("GET", ori.getHttpMethod());
+        
+        m = Arrays
+            .stream(ExampleBridgeImpl.class.getMethods())
+            .filter(method -> method.getName().equals("get"))
+            .filter(Method::isBridge)
+            .findAny()
+            .orElse(null);
+        
+        ori = cri.getMethodDispatcher().getOperationResourceInfo(m);
+        assertNotNull(ori);
+        assertEquals("GET", ori.getHttpMethod());
+    }
+    
+    @Test
+    public void testGenericClassResourceInfoWithBridgeMethod() throws Exception {
+        ClassResourceInfo cri = ResourceUtils.createClassResourceInfo(GenericExampleBridgeImpl.class, 
+            GenericExampleBridgeImpl.class, true, true);
+        assertNotNull(cri);
+        assertEquals(1, cri.getMethodDispatcher().getOperationResourceInfos().size());
+        
+        Method m = GenericExampleBridgeImpl.class.getMethod("get");
+        OperationResourceInfo ori = cri.getMethodDispatcher().getOperationResourceInfo(m);
+        assertNotNull(ori);
+        assertEquals("GET", ori.getHttpMethod());
+        
+        m = Arrays
+            .stream(GenericExampleBridgeImpl.class.getMethods())
+            .filter(method -> method.getName().equals("get"))
+            .filter(Method::isBridge)
+            .findAny()
+            .orElse(null);
+        
+        ori = cri.getMethodDispatcher().getOperationResourceInfo(m);
+        assertNotNull(ori);
+        assertEquals("GET", ori.getHttpMethod());
+    }
+    
+    @Test
+    public void testGenericClassResourceInfo() throws Exception {
+        ClassResourceInfo cri = ResourceUtils.createClassResourceInfo(GenericExampleImpl.class, 
+                GenericExampleImpl.class, true, true);
+        assertNotNull(cri);
+        assertEquals(1, cri.getMethodDispatcher().getOperationResourceInfos().size());
+        
+        Method m = GenericExampleImpl.class.getMethod("get");
+        OperationResourceInfo ori = cri.getMethodDispatcher().getOperationResourceInfo(m);
+        assertNotNull(ori);
+        assertEquals("GET", ori.getHttpMethod());
+        
+        m = Arrays
+            .stream(GenericExampleImpl.class.getMethods())
+            .filter(method -> method.getName().equals("get"))
+            .filter(Method::isBridge)
+            .findAny()
+            .orElse(null);
+        
+        assertNull(m);
+    }
 
     @Path("/synth-hello")
     protected interface SyntheticHelloInterface<T> {
@@ -301,6 +374,13 @@ public class ResourceUtilsTest {
         @GET
         Book get();
     }
+    
+    @Path("example")
+    public interface GenericExample<T extends Book> {
+
+        @GET
+        T get();
+    }
 
     public static class ExampleImpl implements Example {
 
@@ -309,6 +389,30 @@ public class ResourceUtilsTest {
             return null;
         }
     }
+    
+    public static class ExampleBridgeImpl implements Example {
+
+        @Override
+        public SuperBook get() {
+            return null;
+        }
+    }
+    
+    public static class GenericExampleImpl implements GenericExample<Book> {
+
+        @Override
+        public Book get() {
+            return null;
+        }
+    }
+    
+    public static class GenericExampleBridgeImpl implements GenericExample<Book> {
+
+        @Override
+        public SuperBook get() {
+            return null;
+        }
+    }
 
     @XmlRootElement
     public static class OrderItem {
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBeanTest.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBeanTest.java
index 6089d5b..c705fcb 100644
--- a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBeanTest.java
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBeanTest.java
@@ -44,6 +44,9 @@ import org.apache.cxf.jaxrs.resources.Book;
 import org.apache.cxf.jaxrs.resources.BookInterface;
 import org.apache.cxf.jaxrs.resources.BookStore;
 import org.apache.cxf.jaxrs.resources.BookStoreSubresourcesOnly;
+import org.apache.cxf.jaxrs.resources.BookSuperClass;
+import org.apache.cxf.jaxrs.resources.SuperBook;
+import org.apache.cxf.jaxrs.resources.SuperBookStore;
 import org.apache.cxf.message.Message;
 import org.apache.cxf.phase.AbstractPhaseInterceptor;
 import org.apache.cxf.phase.Phase;
@@ -203,6 +206,19 @@ public class JAXRSClientFactoryBeanTest {
         IProductResource productResourceElement = parts.elementAt("1");
         assertNotNull(productResourceElement);
     }
+    
+    @Test
+    public void testBookAndBridgeMethods() throws Exception {
+        SuperBookStore superBookResource = JAXRSClientFactory.create("http://localhost:9000",
+                SuperBookStore.class);
+        assertNotNull(superBookResource);
+        
+        Book book = ((BookSuperClass)superBookResource).getNewBook("id4", true);
+        assertNotNull(book);
+        
+        SuperBook superBook = (SuperBook)superBookResource.getNewBook("id4", true);
+        assertNotNull(superBook);
+    }
 
     @Test
     public void testVoidResponseAcceptWildcard() throws Exception {
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/resources/SuperBook.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/resources/SuperBook.java
new file mode 100644
index 0000000..79120aa
--- /dev/null
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/resources/SuperBook.java
@@ -0,0 +1,44 @@
+/**
+ * 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.cxf.jaxrs.resources;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+
+@XmlRootElement(name = "SuperBook")
+public class SuperBook extends Book {
+    private long superId;
+    public SuperBook() {
+    }
+
+    public SuperBook(String name, long id, long superId) {
+        super(name, id);
+        this.superId = superId;
+    }
+
+    public void setSuperId(long i) {
+        superId = i;
+    }
+
+    public long getSuperId() {
+        return superId;
+    }
+
+}
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/resources/SuperBookStore.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/resources/SuperBookStore.java
new file mode 100644
index 0000000..ffdd51c
--- /dev/null
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/resources/SuperBookStore.java
@@ -0,0 +1,74 @@
+/**
+ * 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.cxf.jaxrs.resources;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Path("/bookstore/")
+public class SuperBookStore extends BookSuperClass implements BookInterface {
+    public SuperBookStore() {
+    }
+
+    public SuperBook getBook(String id) {
+        return null;
+    }
+
+    @Override
+    public SuperBook getNewBook(String id, Boolean isNew) {
+        return null;
+    }
+
+    @POST
+    @Path("/books")
+    @Consumes(MediaType.APPLICATION_XML)
+    public void addBook(Book book) {
+    }
+
+    @PUT
+    @Path("/books/")
+    public Response updateBook(SuperBook book) {
+        return null;
+    }
+
+    @DELETE
+    @Path("/books/{bookId}/")
+    public Response deleteBook(@PathParam("bookId") String id) {
+        return null;
+    }
+
+    @Override
+    public String getDescription() {
+        return null;
+    }
+
+    public String getAuthor() {
+        return null;
+    }
+}
+
+