Skip to content

Commit

Permalink
issue-42851: port resteasy fix for sub-resources from resteasy/restea…
Browse files Browse the repository at this point in the history
…sy-microprofile PR #241
  • Loading branch information
maxr2011-tech11 committed Sep 2, 2024
1 parent b702e31 commit f72a099
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package io.quarkus.restclient.runtime;

import java.io.Closeable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -22,6 +26,8 @@
import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.inject.spi.InterceptionType;
import jakarta.enterprise.inject.spi.Interceptor;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.ResponseProcessingException;
import jakarta.ws.rs.ext.ParamConverter;
Expand All @@ -30,8 +36,10 @@
import org.jboss.logging.Logger;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.microprofile.client.ExceptionMapping;
import org.jboss.resteasy.microprofile.client.ProxyInvocationHandler;
import org.jboss.resteasy.microprofile.client.RestClientProxy;
import org.jboss.resteasy.microprofile.client.header.ClientHeaderFillingException;
import org.jboss.resteasy.microprofile.client.header.ClientHeaderProviders;

/**
* Quarkus version of {@link org.jboss.resteasy.microprofile.client.ProxyInvocationHandler} retaining the ability to
Expand Down Expand Up @@ -84,14 +92,17 @@ public QuarkusProxyInvocationHandler(final Class<?> restClientInterface,
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (RestClientProxy.class.equals(method.getDeclaringClass())) {
return invokeRestClientProxyMethod(proxy, method, args);
return invokeRestClientProxyMethod(method);
}
// Autocloseable/Closeable
if (method.getName().equals("close") && (args == null || args.length == 0)) {
close();
return null;
}
if (closed.get()) {
// Check if this proxy is closed or the client itself is closed. The client may be closed if this proxy was a
// sub-resource and the resource client itself was closed.
if (closed.get() || client.isClosed()) {
closed.set(true);
throw new IllegalStateException("RestClientProxy is closed");
}

Expand Down Expand Up @@ -162,7 +173,30 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
return new QuarkusInvocationContextImpl(target, method, args, chain, interceptorBindingsMap.get(method)).proceed();
} else {
try {
return method.invoke(target, args);
final Object result = method.invoke(target, args);
final Class<?> returnType = method.getReturnType();
// Check if this is a sub-resource. A sub-resource must be an interface.
if (returnType.isInterface()) {
final Annotation[] annotations = method.getDeclaredAnnotations();
boolean hasPath = false;
boolean hasHttpMethod = false;
// Check the annotations. If the method has one of the @HttpMethod annotations, we will just use the
// current method. If it only has a @Path, then we need to create a proxy for the return type.
for (Annotation annotation : annotations) {
final Class<?> type = annotation.annotationType();
if (type.equals(Path.class)) {
hasPath = true;
} else if (type.getDeclaredAnnotation(HttpMethod.class) != null) {
hasHttpMethod = true;
}
}
if (!hasHttpMethod && hasPath) {
// Create a proxy of the return type re-using the providers and client, but do not add the required
// interfaces for the sub-resource.
return createProxy(returnType, result, false, providerInstances, client, getBeanManager());
}
}
return result;
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof CompletionException) {
Expand Down Expand Up @@ -193,7 +227,56 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
}
}

private Object invokeRestClientProxyMethod(Object proxy, Method method, Object[] args) {
/**
* Creates a proxy for the interface. The proxy will implement the interfaces {@link RestClientProxy} and
* {@link Closeable}.
*
* @param resourceInterface the resource interface to create the proxy for
* @param target the target object for the proxy
* @param providers the providers for the client
* @param client the client to use
* @param beanManager the bean manager used to register {@linkplain ClientHeaderProviders client header providers}
* @return the new proxy
*/
static Object createProxy(final Class<?> resourceInterface, final Object target, final Set<Object> providers,
final ResteasyClient client, final BeanManager beanManager) {
return createProxy(resourceInterface, target, true, providers, client, beanManager);
}

/**
* Creates a proxy for the interface.
* <p>
* If {@code addExtendedInterfaces} is set to {@code true}, the proxy will implement the interfaces
* {@link RestClientProxy} and {@link Closeable}.
* </p>
*
* @param resourceInterface the resource interface to create the proxy for
* @param target the target object for the proxy
* @param addExtendedInterfaces {@code true} if the proxy should also implement {@link RestClientProxy} and
* {@link Closeable}
* @param providers the providers for the client
* @param client the client to use
* @param beanManager the bean manager used to register {@linkplain ClientHeaderProviders client header providers}
* @return the new proxy
*/
static Object createProxy(final Class<?> resourceInterface, final Object target, final boolean addExtendedInterfaces,
final Set<Object> providers, final ResteasyClient client, final BeanManager beanManager) {
final Class<?>[] interfaces;
if (addExtendedInterfaces) {
interfaces = new Class<?>[3];
interfaces[1] = RestClientProxy.class;
interfaces[2] = Closeable.class;
} else {
interfaces = new Class[1];
}
interfaces[0] = resourceInterface;
final Object proxy = Proxy.newProxyInstance(getClassLoader(resourceInterface), interfaces,
new ProxyInvocationHandler(resourceInterface, target, Set.copyOf(providers), client));
ClientHeaderProviders.registerForClass(resourceInterface, proxy, beanManager);
return proxy;
}

private Object invokeRestClientProxyMethod(final Method method) {
switch (method.getName()) {
case "getClient":
return client;
Expand Down Expand Up @@ -299,4 +382,20 @@ private static Annotation[] merge(List<Annotation> methodLevelBindings, List<Ann
return merged.toArray(new Annotation[] {});
}

private static ClassLoader getClassLoader(final Class<?> type) {
if (System.getSecurityManager() == null) {
return type.getClassLoader();
}
return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) type::getClassLoader);
}

private static BeanManager getBeanManager() {
try {
CDI<Object> current = CDI.current();
return current != null ? current.getBeanManager() : null;
} catch (IllegalStateException e) {
LOGGER.debug("CDI container is not available", e);
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
import static org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder.PROPERTY_PROXY_PORT;
import static org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder.PROPERTY_PROXY_SCHEME;

import java.io.Closeable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.URI;
Expand Down Expand Up @@ -72,10 +70,8 @@
import org.jboss.resteasy.microprofile.client.ExceptionMapping;
import org.jboss.resteasy.microprofile.client.MethodInjectionFilter;
import org.jboss.resteasy.microprofile.client.RestClientListeners;
import org.jboss.resteasy.microprofile.client.RestClientProxy;
import org.jboss.resteasy.microprofile.client.async.AsyncInterceptorRxInvokerProvider;
import org.jboss.resteasy.microprofile.client.async.AsyncInvocationInterceptorThreadContext;
import org.jboss.resteasy.microprofile.client.header.ClientHeaderProviders;
import org.jboss.resteasy.microprofile.client.header.ClientHeadersRequestFilter;
import org.jboss.resteasy.microprofile.client.impl.MpClient;
import org.jboss.resteasy.microprofile.client.impl.MpClientBuilderImpl;
Expand Down Expand Up @@ -353,16 +349,10 @@ public <T> T build(Class<T> aClass, ClientHttpEngine httpEngine)
.defaultConsumes(MediaType.APPLICATION_JSON)
.defaultProduces(MediaType.APPLICATION_JSON).build();

Class<?>[] interfaces = new Class<?>[3];
interfaces[0] = aClass;
interfaces[1] = RestClientProxy.class;
interfaces[2] = Closeable.class;

final BeanManager beanManager = getBeanManager();
T proxy = (T) Proxy.newProxyInstance(classLoader, interfaces,
new QuarkusProxyInvocationHandler(aClass, actualClient, getLocalProviderInstances(), client, beanManager));
ClientHeaderProviders.registerForClass(aClass, proxy, beanManager);
return proxy;
return aClass.cast(
QuarkusProxyInvocationHandler.createProxy(aClass, actualClient, getLocalProviderInstances(), client,
beanManager));
}

private void configureTrustAll(ResteasyClientBuilder clientBuilder) {
Expand Down

0 comments on commit f72a099

Please sign in to comment.