package com.googlecode.jsonrpc4j; import com.googlecode.jsonrpc4j.spring.rest.JsonRpcRestClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.Socket; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Utilities for create client proxies. */ @SuppressWarnings({"unused", "WeakerAccess"}) public abstract class ProxyUtil { private static final Logger logger = LoggerFactory.getLogger(ProxyUtil.class); /** * Creates a composite service using all of the given * services. * * @param classLoader the {@link ClassLoader} * @param services the service objects * @param allowMultipleInheritance whether or not to allow multiple inheritance * @return the object */ public static Object createCompositeServiceProxy(ClassLoader classLoader, Object[] services, boolean allowMultipleInheritance) { return createCompositeServiceProxy(classLoader, services, null, allowMultipleInheritance); } /** * Creates a composite service using all of the given * services and implementing the given interfaces. * * @param classLoader the {@link ClassLoader} * @param services the service objects * @param serviceInterfaces the service interfaces * @param allowMultipleInheritance whether or not to allow multiple inheritance * @return the object */ public static Object createCompositeServiceProxy(ClassLoader classLoader, Object[] services, Class<?>[] serviceInterfaces, boolean allowMultipleInheritance) { Set<Class<?>> interfaces = collectInterfaces(services, serviceInterfaces); final Map<Class<?>, Object> serviceClassToInstanceMapping = buildServiceMap(services, allowMultipleInheritance, interfaces); // now create the proxy return Proxy.newProxyInstance(classLoader, interfaces.toArray(new Class<?>[0]), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Class<?> clazz = method.getDeclaringClass(); if (clazz == Object.class) { return proxyObjectMethods(method, proxy, args); } return method.invoke(serviceClassToInstanceMapping.get(clazz), args); } }); } private static Set<Class<?>> collectInterfaces(Object[] services, Class<?>[] serviceInterfaces) { Set<Class<?>> interfaces = new HashSet<>(); if (serviceInterfaces != null) { interfaces.addAll(Arrays.asList(serviceInterfaces)); } else { for (Object o : services) { interfaces.addAll(Arrays.asList(o.getClass().getInterfaces())); } } return interfaces; } private static Map<Class<?>, Object> buildServiceMap(Object[] services, boolean allowMultipleInheritance, Set<Class<?>> interfaces) { final Map<Class<?>, Object> serviceMap = new HashMap<>(); for (Class<?> clazz : interfaces) { if (serviceMap.containsKey(clazz) && allowMultipleInheritance) { continue; } else if (serviceMap.containsKey(clazz)) { throw new IllegalArgumentException("Multiple inheritance not allowed " + clazz.getName()); } for (Object o : services) { if (clazz.isInstance(o)) { logger.debug("Using {} for {}", o.getClass().getName(), clazz.getName()); serviceMap.put(clazz, o); break; } } if (!serviceMap.containsKey(clazz)) { throw new IllegalArgumentException("None of the provided services implement " + clazz.getName()); } } return serviceMap; } private static Object proxyObjectMethods(Method method, Object proxyObject, Object[] args) { String name = method.getName(); if (name.equals("toString")) { return proxyObject.getClass().getName() + "@" + System.identityHashCode(proxyObject); } if (name.equals("hashCode")) { return System.identityHashCode(proxyObject); } if (name.equals("equals")) { return proxyObject == args[0]; } throw new RuntimeException(method.getName() + " is not a member of java.lang.Object"); } /** * Creates a {@link Proxy} of the given {@code proxyInterface} * that uses the given {@link JsonRpcClient}. * * @param <T> the proxy type * @param classLoader the {@link ClassLoader} * @param proxyInterface the interface to proxy * @param client the {@link JsonRpcClient} * @param socket the {@link Socket} * @return the proxied interface * @throws IOException if an I/O error occurs when creating the input stream, the output stream, the socket * is closed, the socket is not connected, or the socket input has been shutdown using shutdownInput() */ @SuppressWarnings("WeakerAccess") public static <T> T createClientProxy(ClassLoader classLoader, Class<T> proxyInterface, final JsonRpcClient client, Socket socket) throws IOException { return createClientProxy(classLoader, proxyInterface, client, socket.getInputStream(), socket.getOutputStream()); } /** * Creates a {@link Proxy} of the given {@code proxyInterface} * that uses the given {@link JsonRpcClient}. * * @param <T> the proxy type * @param classLoader the {@link ClassLoader} * @param proxyInterface the interface to proxy * @param client the {@link JsonRpcClient} * @param input the {@link InputStream} * @param output the {@link OutputStream} * @return the proxied interface */ @SuppressWarnings({"unchecked", "WeakerAccess"}) public static <T> T createClientProxy(ClassLoader classLoader, Class<T> proxyInterface, final JsonRpcClient client, final InputStream input, final OutputStream output) { // create and return the proxy return (T) Proxy.newProxyInstance(classLoader, new Class<?>[]{proxyInterface}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (isDeclaringClassAnObject(method)) return proxyObjectMethods(method, proxy, args); final Object arguments = ReflectionUtil.parseArguments(method, args); final String methodName = getMethodName(method); return client.invokeAndReadResponse(methodName, arguments, method.getGenericReturnType(), output, input); } }); } private static boolean isDeclaringClassAnObject(Method method) { return method.getDeclaringClass() == Object.class; } private static String getMethodName(Method method) { final JsonRpcMethod jsonRpcMethod = ReflectionUtil.getAnnotation(method, JsonRpcMethod.class); if (jsonRpcMethod == null) { return method.getName(); } else { return jsonRpcMethod.value(); } } public static <T> T createClientProxy(Class<T> clazz, JsonRpcRestClient client) { return createClientProxy(clazz.getClassLoader(), clazz, client); } /** * Creates a {@link Proxy} of the given {@code proxyInterface} that uses the given {@link JsonRpcHttpClient}. * * @param <T> the proxy type * @param classLoader the {@link ClassLoader} * @param proxyInterface the interface to proxy * @param client the {@link JsonRpcHttpClient} * @return the proxied interface */ public static <T> T createClientProxy(ClassLoader classLoader, Class<T> proxyInterface, final IJsonRpcClient client) { return createClientProxy(classLoader, proxyInterface, client, new HashMap<String, String>()); } /** * Creates a {@link Proxy} of the given {@code proxyInterface} * that uses the given {@link IJsonRpcClient}. * * @param <T> the proxy type * @param classLoader the {@link ClassLoader} * @param proxyInterface the interface to proxy * @param client the {@link JsonRpcHttpClient} * @param extraHeaders extra HTTP headers to be added to each response * @return the proxied interface */ @SuppressWarnings("unchecked") private static <T> T createClientProxy(ClassLoader classLoader, Class<T> proxyInterface, final IJsonRpcClient client, final Map<String, String> extraHeaders) { return (T) Proxy.newProxyInstance(classLoader, new Class<?>[]{proxyInterface}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (isDeclaringClassAnObject(method)) return proxyObjectMethods(method, proxy, args); final Object arguments = ReflectionUtil.parseArguments(method, args); final String methodName = getMethodName(method); return client.invoke(methodName, arguments, method.getGenericReturnType(), extraHeaders); } }); } }