You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@cxf.apache.org by "ASF GitHub Bot (JIRA)" <ji...@apache.org> on 2018/12/12 20:17:00 UTC

[jira] [Work logged] (CXF-7922) Unable to invoke default interface methods in proxy-based clients

     [ https://issues.apache.org/jira/browse/CXF-7922?focusedWorklogId=174669&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-174669 ]

ASF GitHub Bot logged work on CXF-7922:
---------------------------------------

                Author: ASF GitHub Bot
            Created on: 12/Dec/18 20:16
            Start Date: 12/Dec/18 20:16
    Worklog Time Spent: 10m 
      Work Description: andymc12 closed pull request #480: [CXF-7922] Invoke default interface methods on client interfaces
URL: https://github.com/apache/cxf/pull/480
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
index a567f83a04f..6aaee1c623d 100644
--- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
@@ -21,11 +21,16 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.annotation.Annotation;
+import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.lang.reflect.Type;
 import java.net.URI;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -154,6 +159,55 @@ private void initValuesMap(Object... varValues) {
         }
     }
 
+    private static class WrappedException extends Exception {
+        final Throwable wrapped;
+        WrappedException(Throwable wrapped) {
+            this.wrapped = wrapped;
+        }
+        Throwable getWrapped() {
+            return wrapped;
+        }
+    }
+
+    private static Object invokeDefaultMethod(Class<?> declaringClass, Object o, Method m, Object[] params)
+        throws Throwable {
+
+        try {
+            return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+                @Override
+                public Object run() throws Exception {
+                    try {
+                        final MethodHandles.Lookup lookup = MethodHandles.publicLookup()
+                                .in(declaringClass);
+
+                        // force private access so unreflectSpecial can invoke the interface's default method
+                        final Field f = MethodHandles.Lookup.class.getDeclaredField("allowedModes");
+                        final int modifiers = f.getModifiers();
+                        if (Modifier.isFinal(modifiers)) {
+                            final Field modifiersField = Field.class.getDeclaredField("modifiers");
+                            modifiersField.setAccessible(true);
+                            modifiersField.setInt(f, modifiers & ~Modifier.FINAL);
+                            f.setAccessible(true);
+                            f.set(lookup, MethodHandles.Lookup.PRIVATE);
+                        }
+
+                        return lookup.unreflectSpecial(m, declaringClass)
+                                     .bindTo(o)
+                                     .invokeWithArguments(params);
+                    } catch (Throwable t) {
+                        throw new WrappedException(t);
+                    }
+                }
+            });
+        } catch (PrivilegedActionException pae) {
+            Throwable wrapped = pae.getCause();
+            if (wrapped instanceof WrappedException) {
+                throw ((WrappedException)wrapped).getWrapped();
+            }
+            throw wrapped;
+        }
+    }
+
     /**
      * Updates the current state if Client method is invoked, otherwise
      * does the remote invocation or returns a new proxy if subresource
@@ -171,6 +225,9 @@ public Object invoke(Object o, Method m, Object[] params) throws Throwable {
         resetResponse();
         OperationResourceInfo ori = cri.getMethodDispatcher().getOperationResourceInfo(m);
         if (ori == null) {
+            if (m.isDefault()) {
+                return invokeDefaultMethod(declaringClass, o, m, params);
+            }
             reportInvalidResourceMethod(m, "INVALID_RESOURCE_METHOD");
         }
 
diff --git a/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilderTest.java b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilderTest.java
index b1e88558127..54be86ecd65 100644
--- a/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilderTest.java
+++ b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilderTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.cxf.microprofile.client;
 
+import java.io.IOException;
 import java.net.URI;
 import java.net.URL;
 
@@ -163,6 +164,24 @@ public void testClientPropertiesAreSet() throws Exception {
             WebClientUtil.getClientConfigFromProxy(client).getRequestContext().get("hello"));
     }
 
+    @Test
+    public void testCanInvokeDefaultInterfaceMethods() throws Exception {
+        MyClient client = RestClientBuilder.newBuilder()
+            .register(InvokedMethodClientRequestFilter.class)
+            .baseUri(new URI("http://localhost:8080/neverUsed"))
+            .build(MyClient.class);
+        assertEquals("defaultValue", client.myDefaultMethod(false));
+    }
+
+    @Test(expected = IOException.class)
+    public void testCanInvokeDefaultInterfaceMethodsWithException() throws Exception {
+        MyClient client = RestClientBuilder.newBuilder()
+            .register(InvokedMethodClientRequestFilter.class)
+            .baseUri(new URI("http://localhost:8080/neverUsed"))
+            .build(MyClient.class);
+        client.myDefaultMethod(true);
+        fail("Should have thrown IOException");
+    }
     private void fail(Response r, String failureMessage) {
         System.out.println(r.getStatus());
         fail(failureMessage);
diff --git a/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/mock/MyClient.java b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/mock/MyClient.java
index c3808456f6e..beafc977d75 100644
--- a/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/mock/MyClient.java
+++ b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/mock/MyClient.java
@@ -18,6 +18,8 @@
  */
 package org.apache.cxf.microprofile.client.mock;
 
+import java.io.IOException;
+
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.core.Response;
@@ -26,4 +28,11 @@
 public interface MyClient {
     @GET
     Response get();
+
+    default String myDefaultMethod(boolean throwException) throws IOException {
+        if (throwException) {
+            throw new IOException("expected");
+        }
+        return "defaultValue";
+    }
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Issue Time Tracking
-------------------

            Worklog Id:     (was: 174669)
            Time Spent: 10m
    Remaining Estimate: 0h

> Unable to invoke default interface methods in proxy-based clients
> -----------------------------------------------------------------
>
>                 Key: CXF-7922
>                 URL: https://issues.apache.org/jira/browse/CXF-7922
>             Project: CXF
>          Issue Type: Bug
>          Components: JAX-RS
>    Affects Versions: 3.2.7
>            Reporter: Andy McCright
>            Assignee: Andy McCright
>            Priority: Minor
>          Time Spent: 10m
>  Remaining Estimate: 0h
>
> {{When invoking a default method on an interface used as a MicroProfile Rest Client interface (and presumably the JAX-RS proxy-based client), I see the following exception:}}
> {{javax.ws.rs.ProcessingException: Method org.apache.cxf.microprofile.client.mock.MyClient.myDefaultMethod is not a valid resource method}}
> {{ at org.apache.cxf.microprofile.client.CxfTypeSafeClientBuilderTest.testCanInvokeDefaultInterfaceMethods(CxfTypeSafeClientBuilderTest.java:172)}}
>  
> Invoking default interface methods from a dynamic proxy's InvocationHandler is outlined in this article:
> [https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/]
>  
> {{This type of operation becomes critical when combining the MicroProfile Rest Client with MP Fault Tolerance annotations like this:}}
> {{public interface MyClient {}}
> {{    @GET}}
> {{    @Fallback(fallbackMethod="useFallback")}}
> {{    String tryActualRemoteRequest();}}
> {{}}{{    default String useFallback() {}}
> {{        return "useful default value";}}
> {{    }}}
> {{}}}



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)