package co.codewizards.cloudstore.ls.client; import static co.codewizards.cloudstore.core.util.AssertUtil.*; import static co.codewizards.cloudstore.core.util.Util.*; import java.io.Closeable; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.Uid; import co.codewizards.cloudstore.core.util.ExceptionUtil; import co.codewizards.cloudstore.core.util.ReflectionUtil; import co.codewizards.cloudstore.ls.client.handler.InverseServiceRequestHandlerThread; import co.codewizards.cloudstore.ls.core.LsConfig; import co.codewizards.cloudstore.ls.core.invoke.ClassInfo; import co.codewizards.cloudstore.ls.core.invoke.ClassInfoMap; import co.codewizards.cloudstore.ls.core.invoke.ClassManager; import co.codewizards.cloudstore.ls.core.invoke.DelayedMethodInvocationResponse; import co.codewizards.cloudstore.ls.core.invoke.IncDecRefCountQueue; import co.codewizards.cloudstore.ls.core.invoke.Invoker; import co.codewizards.cloudstore.ls.core.invoke.MethodInvocationRequest; import co.codewizards.cloudstore.ls.core.invoke.MethodInvocationResponse; import co.codewizards.cloudstore.ls.core.invoke.ObjectManager; import co.codewizards.cloudstore.ls.core.invoke.ObjectRef; import co.codewizards.cloudstore.ls.core.invoke.RemoteObjectProxy; import co.codewizards.cloudstore.ls.core.invoke.RemoteObjectProxyFactory; import co.codewizards.cloudstore.ls.core.invoke.RemoteObjectProxyInvocationHandler; import co.codewizards.cloudstore.ls.core.provider.JavaNativeWithObjectRefMessageBodyReader; import co.codewizards.cloudstore.ls.core.provider.JavaNativeWithObjectRefMessageBodyWriter; import co.codewizards.cloudstore.ls.rest.client.LocalServerRestClient; import co.codewizards.cloudstore.ls.rest.client.request.GetDelayedMethodInvocationResponse; import co.codewizards.cloudstore.ls.rest.client.request.InvokeMethod; /** * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class LocalServerClient implements Invoker, Closeable { private static final Logger logger = LoggerFactory.getLogger(LocalServerClient.class); private volatile InverseServiceRequestHandlerThread inverseServiceRequestHandlerThread; private LocalServerRestClient localServerRestClient; private final ObjectManager objectManager = ObjectManager.getInstance(new Uid()); // needed for inverse references as used by listeners! { objectManager.setNeverEvict(true); } private final ClassInfoMap classInfoMap = new ClassInfoMap(); private final IncDecRefCountQueue incDecRefCountQueue = new IncDecRefCountQueue(this); @Override public ClassInfoMap getClassInfoMap() { return classInfoMap; } private static final class Holder { public static final LocalServerClient instance = new LocalServerClient(); } public static LocalServerClient getInstance() { return Holder.instance; } public final synchronized LocalServerRestClient getLocalServerRestClient() { if (localServerRestClient == null) { localServerRestClient = _getLocalServerRestClient(); final ObjectRefConverterFactoryImpl objectRefConverterFactory = new ObjectRefConverterFactoryImpl(this); localServerRestClient.registerRestComponent(new JavaNativeWithObjectRefMessageBodyReader(objectRefConverterFactory)); localServerRestClient.registerRestComponent(new JavaNativeWithObjectRefMessageBodyWriter(objectRefConverterFactory)); } return localServerRestClient; } protected LocalServerRestClient _getLocalServerRestClient() { return LocalServerRestClient.getInstance(); } protected LocalServerClient() { if (LsConfig.isLocalServerEnabled()) { inverseServiceRequestHandlerThread = new InverseServiceRequestHandlerThread(this); inverseServiceRequestHandlerThread.start(); } } @Override public ObjectManager getObjectManager() { return objectManager; } @Override public <T> T invokeStatic(final Class<?> clazz, final String methodName, final Object ... arguments) { assertNotNull(clazz, "clazz"); assertNotNull(methodName, "methodName"); if (! LsConfig.isLocalServerEnabled()) return ReflectionUtil.invokeStatic(clazz, methodName, arguments); return invokeStatic(clazz.getName(), methodName, (String[]) null, arguments); } @Override public <T> T invokeStatic(final String className, final String methodName, final Object ... arguments) { assertNotNull(className, "className"); assertNotNull(methodName, "methodName"); if (! LsConfig.isLocalServerEnabled()) return ReflectionUtil.invokeStatic(getClassOrFail(className), methodName, arguments); return invokeStatic(className, methodName, (String[]) null, arguments); } @Override public <T> T invokeStatic(final Class<?> clazz, final String methodName, final Class<?>[] argumentTypes, final Object ... arguments) { assertNotNull(clazz, "clazz"); assertNotNull(methodName, "methodName"); if (! LsConfig.isLocalServerEnabled()) return ReflectionUtil.invokeStatic(clazz, methodName, argumentTypes, arguments); return invokeStatic(clazz.getName(), methodName, toClassNames(argumentTypes), arguments); } @Override public <T> T invokeStatic(final String className, final String methodName, final String[] argumentTypeNames, final Object ... arguments) { assertNotNull(className, "className"); assertNotNull(methodName, "methodName"); if (! LsConfig.isLocalServerEnabled()) return ReflectionUtil.invokeStatic(getClassOrFail(className), methodName, getClassesOrFail(argumentTypeNames), arguments); final MethodInvocationRequest methodInvocationRequest = MethodInvocationRequest.forStaticInvocation( className, methodName, argumentTypeNames, arguments); return invoke(methodInvocationRequest); } @Override public <T> T invokeConstructor(final Class<T> clazz, final Object ... arguments) { assertNotNull(clazz, "clazz"); if (! LsConfig.isLocalServerEnabled()) return ReflectionUtil.invokeConstructor(clazz, arguments); return invokeConstructor(clazz.getName(), (String[]) null, arguments); } @Override public <T> T invokeConstructor(final String className, final Object ... arguments) { assertNotNull(className, "className"); if (! LsConfig.isLocalServerEnabled()) return cast(ReflectionUtil.invokeConstructor(getClassOrFail(className), arguments)); return invokeConstructor(className, (String[]) null, arguments); } @Override public <T> T invokeConstructor(final Class<T> clazz, final Class<?>[] argumentTypes, final Object ... arguments) { assertNotNull(clazz, "clazz"); if (! LsConfig.isLocalServerEnabled()) return ReflectionUtil.invokeConstructor(clazz, argumentTypes, arguments); return invokeConstructor(clazz.getName(), toClassNames(argumentTypes), arguments); } @Override public <T> T invokeConstructor(final String className, final String[] argumentTypeNames, final Object ... arguments) { assertNotNull(className, "className"); if (! LsConfig.isLocalServerEnabled()) return cast(ReflectionUtil.invokeConstructor(getClassOrFail(className), getClassesOrFail(argumentTypeNames), arguments)); final MethodInvocationRequest methodInvocationRequest = MethodInvocationRequest.forConstructorInvocation( className, argumentTypeNames, arguments); return invoke(methodInvocationRequest); } @Override public <T> T invoke(final Object object, final String methodName, final Object ... arguments) { assertNotNull(object, "object"); assertNotNull(methodName, "methodName"); if (! LsConfig.isLocalServerEnabled()) return cast(ReflectionUtil.invoke(object, methodName, arguments)); if (!(object instanceof RemoteObjectProxy) && !(object instanceof Serializable)) throw new IllegalArgumentException("object is neither an instance of RemoteObjectProxy nor Serializable!"); return invoke(object, methodName, (Class<?>[]) null, arguments); } @Override public <T> T invoke(final Object object, final String methodName, final Class<?>[] argumentTypes, final Object... arguments) { assertNotNull(object, "object"); assertNotNull(methodName, "methodName"); if (! LsConfig.isLocalServerEnabled()) return cast(ReflectionUtil.invoke(object, methodName, argumentTypes, arguments)); return invoke(object, methodName, toClassNames(argumentTypes), arguments); } @Override public <T> T invoke(final Object object, final String methodName, final String[] argumentTypeNames, final Object... arguments) { assertNotNull(object, "object"); assertNotNull(methodName, "methodName"); if (! LsConfig.isLocalServerEnabled()) return cast(ReflectionUtil.invoke(object, methodName, getClassesOrFail(argumentTypeNames), arguments)); final MethodInvocationRequest methodInvocationRequest = MethodInvocationRequest.forObjectInvocation( object, methodName, argumentTypeNames, arguments); return invoke(methodInvocationRequest); } private Class<?>[] getClassesOrFail(final String[] classNames) { assertNotNull(classNames, "classNames"); final Class<?>[] result = new Class<?>[classNames.length]; for (int i = 0; i < classNames.length; i++) result[i] = getClassOrFail(classNames[i]); return result; } private Class<?> getClassOrFail(final String className) { assertNotNull(className, "className"); ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader == null) loader = getClass().getClassLoader(); try { return Class.forName(className, true, loader); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } private String[] toClassNames(Class<?> ... classes) { final String[] classNames; if (classes == null) classNames = null; else { classNames = new String[classes.length]; for (int i = 0; i < classes.length; i++) classNames[i] = classes[i].getName(); } return classNames; } private <T> T invoke(final MethodInvocationRequest methodInvocationRequest) { assertNotNull(methodInvocationRequest, "methodInvocationRequest"); MethodInvocationResponse methodInvocationResponse = getLocalServerRestClient().execute( new InvokeMethod(methodInvocationRequest)); while (methodInvocationResponse instanceof DelayedMethodInvocationResponse) { final DelayedMethodInvocationResponse dmir = (DelayedMethodInvocationResponse) methodInvocationResponse; final Uid delayedResponseId = dmir.getDelayedResponseId(); methodInvocationResponse = getLocalServerRestClient().execute( new GetDelayedMethodInvocationResponse(delayedResponseId)); } final Object result = methodInvocationResponse.getResult(); if (methodInvocationResponse.getWritableArguments() != null) copyWritableArgumentsBack(methodInvocationRequest.getArguments(), methodInvocationResponse.getWritableArguments()); return cast(result); } private void copyWritableArgumentsBack(final Object[] requestArguments, final Object[] responseArguments) { assertNotNull(requestArguments, "requestArguments"); assertNotNull(responseArguments, "responseArguments"); for (int i = 0; i < responseArguments.length; ++i) { final Object responseArgument = responseArguments[i]; if (responseArgument != null) copyWritableArgumentBack(requestArguments[i], responseArgument); } } private void copyWritableArgumentBack(final Object requestArgument, final Object responseArgument) { assertNotNull(requestArgument, "requestArgument"); assertNotNull(responseArgument, "responseArgument"); if (requestArgument.getClass().isArray()) { final int length = Array.getLength(requestArgument); for (int i = 0; i < length; ++i) { final Object value = Array.get(responseArgument, i); Array.set(requestArgument, i, value); } } else throw new UnsupportedOperationException("No idea how to copy this back! requestArgument=" + requestArgument); } private RemoteObjectProxy _createRemoteObjectProxy(final ObjectRef objectRef, final Class<?>[] interfaces) { final ClassLoader classLoader = this.getClass().getClassLoader(); return (RemoteObjectProxy) Proxy.newProxyInstance(classLoader, interfaces, new RemoteObjectProxyInvocationHandler(this, objectRef)); } private Class<?>[] getInterfaces(final ObjectRef objectRef) { ClassInfo classInfo = classInfoMap.getClassInfo(objectRef.getClassId()); if (classInfo == null) { classInfo = objectRef.getClassInfo(); if (classInfo == null) throw new IllegalStateException("There is no ClassInfo in the ClassInfoMap and neither in the ObjectRef! " + objectRef); classInfoMap.putClassInfo(classInfo); objectRef.setClassInfo(null); } final ClassManager classManager = objectManager.getClassManager(); final Set<String> interfaceNames = classInfo.getInterfaceNames(); final List<Class<?>> interfaces = new ArrayList<>(interfaceNames.size() + 1); for (final String interfaceName : interfaceNames) { Class<?> iface = null; try { iface = classManager.getClassOrFail(interfaceName); } catch (RuntimeException x) { if (ExceptionUtil.getCause(x, ClassNotFoundException.class) == null) throw x; } if (iface != null) interfaces.add(iface); } interfaces.add(RemoteObjectProxy.class); return interfaces.toArray(new Class<?>[interfaces.size()]); } @Override public void incRefCount(final ObjectRef objectRef, final Uid refId) { incDecRefCountQueue.incRefCount(objectRef, refId); } @Override public void decRefCount(final ObjectRef objectRef, final Uid refId) { incDecRefCountQueue.decRefCount(objectRef, refId); } @Override protected void finalize() throws Throwable { close(); super.finalize(); } @Override public void close() { final Thread thread = inverseServiceRequestHandlerThread; if (thread != null) { inverseServiceRequestHandlerThread = null; thread.interrupt(); try { thread.join(); } catch (InterruptedException e) { doNothing(); } } objectManager.setNeverEvict(false); if (LsConfig.isLocalServerEnabled()) { try { invokeStatic(ObjectRef.class, ObjectRef.VIRTUAL_METHOD_CLOSE_OBJECT_MANAGER, (Class<?>[])null, (Object[]) null); } catch (Exception x) { logger.error("close: " + x, x); } } } public Object getRemoteObjectProxyOrCreate(final ObjectRef objectRef) { return objectManager.getRemoteObjectProxyManager().getRemoteObjectProxyOrCreate(objectRef, new RemoteObjectProxyFactory() { @Override public RemoteObjectProxy createRemoteObjectProxy(final ObjectRef objectRef) { final Class<?>[] interfaces = getInterfaces(objectRef); return _createRemoteObjectProxy(objectRef, interfaces); } }); } }