001package co.codewizards.cloudstore.ls.client;
002
003import static co.codewizards.cloudstore.core.util.Util.*;
004import static java.util.Objects.*;
005
006import java.io.Closeable;
007import java.io.Serializable;
008import java.lang.reflect.Array;
009import java.lang.reflect.Proxy;
010import java.util.ArrayList;
011import java.util.List;
012import java.util.Set;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017import co.codewizards.cloudstore.core.Uid;
018import co.codewizards.cloudstore.core.util.ExceptionUtil;
019import co.codewizards.cloudstore.core.util.ReflectionUtil;
020import co.codewizards.cloudstore.ls.client.handler.InverseServiceRequestHandlerThread;
021import co.codewizards.cloudstore.ls.core.LsConfig;
022import co.codewizards.cloudstore.ls.core.invoke.ClassInfo;
023import co.codewizards.cloudstore.ls.core.invoke.ClassInfoMap;
024import co.codewizards.cloudstore.ls.core.invoke.ClassManager;
025import co.codewizards.cloudstore.ls.core.invoke.DelayedMethodInvocationResponse;
026import co.codewizards.cloudstore.ls.core.invoke.IncDecRefCountQueue;
027import co.codewizards.cloudstore.ls.core.invoke.Invoker;
028import co.codewizards.cloudstore.ls.core.invoke.MethodInvocationRequest;
029import co.codewizards.cloudstore.ls.core.invoke.MethodInvocationResponse;
030import co.codewizards.cloudstore.ls.core.invoke.ObjectManager;
031import co.codewizards.cloudstore.ls.core.invoke.ObjectRef;
032import co.codewizards.cloudstore.ls.core.invoke.RemoteObjectProxy;
033import co.codewizards.cloudstore.ls.core.invoke.RemoteObjectProxyFactory;
034import co.codewizards.cloudstore.ls.core.invoke.RemoteObjectProxyInvocationHandler;
035import co.codewizards.cloudstore.ls.core.provider.JavaNativeWithObjectRefMessageBodyReader;
036import co.codewizards.cloudstore.ls.core.provider.JavaNativeWithObjectRefMessageBodyWriter;
037import co.codewizards.cloudstore.ls.rest.client.LocalServerRestClient;
038import co.codewizards.cloudstore.ls.rest.client.request.GetDelayedMethodInvocationResponse;
039import co.codewizards.cloudstore.ls.rest.client.request.InvokeMethod;
040
041/**
042 * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co
043 */
044public class LocalServerClient implements Invoker, Closeable {
045
046        private static final Logger logger = LoggerFactory.getLogger(LocalServerClient.class);
047
048        private volatile InverseServiceRequestHandlerThread inverseServiceRequestHandlerThread;
049
050        private LocalServerRestClient localServerRestClient;
051        private final ObjectManager objectManager = ObjectManager.getInstance(new Uid()); // needed for inverse references as used by listeners!
052        {
053                objectManager.setNeverEvict(true);
054        }
055        private final ClassInfoMap classInfoMap = new ClassInfoMap();
056
057        private final IncDecRefCountQueue incDecRefCountQueue = new IncDecRefCountQueue(this);
058
059        @Override
060        public ClassInfoMap getClassInfoMap() {
061                return classInfoMap;
062        }
063
064        private static final class Holder {
065                public static final LocalServerClient instance = new LocalServerClient();
066        }
067
068        public static LocalServerClient getInstance() {
069                return Holder.instance;
070        }
071
072        public final synchronized LocalServerRestClient getLocalServerRestClient() {
073                if (localServerRestClient == null) {
074                        localServerRestClient = _getLocalServerRestClient();
075
076                        final ObjectRefConverterFactoryImpl objectRefConverterFactory = new ObjectRefConverterFactoryImpl(this);
077                        localServerRestClient.registerRestComponent(new JavaNativeWithObjectRefMessageBodyReader(objectRefConverterFactory));
078                        localServerRestClient.registerRestComponent(new JavaNativeWithObjectRefMessageBodyWriter(objectRefConverterFactory));
079                }
080                return localServerRestClient;
081        }
082
083        protected LocalServerRestClient _getLocalServerRestClient() {
084                return LocalServerRestClient.getInstance();
085        }
086
087        protected LocalServerClient() {
088                if (LsConfig.isLocalServerEnabled()) {
089                        inverseServiceRequestHandlerThread = new InverseServiceRequestHandlerThread(this);
090                        inverseServiceRequestHandlerThread.start();
091                }
092        }
093
094        @Override
095        public ObjectManager getObjectManager() {
096                return objectManager;
097        }
098
099        @Override
100        public <T> T invokeStatic(final Class<?> clazz, final String methodName, final Object ... arguments) {
101                requireNonNull(clazz, "clazz");
102                requireNonNull(methodName, "methodName");
103                if (! LsConfig.isLocalServerEnabled())
104                        return ReflectionUtil.invokeStatic(clazz, methodName, arguments);
105
106                return invokeStatic(clazz.getName(), methodName, (String[]) null, arguments);
107        }
108
109        @Override
110        public <T> T invokeStatic(final String className, final String methodName, final Object ... arguments) {
111                requireNonNull(className, "className");
112                requireNonNull(methodName, "methodName");
113                if (! LsConfig.isLocalServerEnabled())
114                        return ReflectionUtil.invokeStatic(getClassOrFail(className), methodName, arguments);
115
116                return invokeStatic(className, methodName, (String[]) null, arguments);
117        }
118
119        @Override
120        public <T> T invokeStatic(final Class<?> clazz, final String methodName, final Class<?>[] argumentTypes, final Object ... arguments) {
121                requireNonNull(clazz, "clazz");
122                requireNonNull(methodName, "methodName");
123                if (! LsConfig.isLocalServerEnabled())
124                        return ReflectionUtil.invokeStatic(clazz, methodName, argumentTypes, arguments);
125
126                return invokeStatic(clazz.getName(), methodName, toClassNames(argumentTypes), arguments);
127        }
128
129        @Override
130        public <T> T invokeStatic(final String className, final String methodName, final String[] argumentTypeNames, final Object ... arguments) {
131                requireNonNull(className, "className");
132                requireNonNull(methodName, "methodName");
133                if (! LsConfig.isLocalServerEnabled())
134                        return ReflectionUtil.invokeStatic(getClassOrFail(className), methodName, getClassesOrFail(argumentTypeNames), arguments);
135
136                final MethodInvocationRequest methodInvocationRequest = MethodInvocationRequest.forStaticInvocation(
137                                className, methodName, argumentTypeNames, arguments);
138
139                return invoke(methodInvocationRequest);
140        }
141
142        @Override
143        public <T> T invokeConstructor(final Class<T> clazz, final Object ... arguments) {
144                requireNonNull(clazz, "clazz");
145                if (! LsConfig.isLocalServerEnabled())
146                        return ReflectionUtil.invokeConstructor(clazz, arguments);
147
148                return invokeConstructor(clazz.getName(), (String[]) null, arguments);
149        }
150
151        @Override
152        public <T> T invokeConstructor(final String className, final Object ... arguments) {
153                requireNonNull(className, "className");
154                if (! LsConfig.isLocalServerEnabled())
155                        return cast(ReflectionUtil.invokeConstructor(getClassOrFail(className), arguments));
156
157                return invokeConstructor(className, (String[]) null, arguments);
158        }
159
160        @Override
161        public <T> T invokeConstructor(final Class<T> clazz, final Class<?>[] argumentTypes, final Object ... arguments) {
162                requireNonNull(clazz, "clazz");
163                if (! LsConfig.isLocalServerEnabled())
164                        return ReflectionUtil.invokeConstructor(clazz, argumentTypes, arguments);
165
166                return invokeConstructor(clazz.getName(), toClassNames(argumentTypes), arguments);
167        }
168
169        @Override
170        public <T> T invokeConstructor(final String className, final String[] argumentTypeNames, final Object ... arguments) {
171                requireNonNull(className, "className");
172                if (! LsConfig.isLocalServerEnabled())
173                        return cast(ReflectionUtil.invokeConstructor(getClassOrFail(className), getClassesOrFail(argumentTypeNames), arguments));
174
175                final MethodInvocationRequest methodInvocationRequest = MethodInvocationRequest.forConstructorInvocation(
176                                className, argumentTypeNames, arguments);
177
178                return invoke(methodInvocationRequest);
179        }
180
181        @Override
182        public <T> T invoke(final Object object, final String methodName, final Object ... arguments) {
183                requireNonNull(object, "object");
184                requireNonNull(methodName, "methodName");
185                if (! LsConfig.isLocalServerEnabled())
186                        return cast(ReflectionUtil.invoke(object, methodName, arguments));
187
188                if (!(object instanceof RemoteObjectProxy) && !(object instanceof Serializable))
189                        throw new IllegalArgumentException("object is neither an instance of RemoteObjectProxy nor Serializable!");
190
191                return invoke(object, methodName, (Class<?>[]) null, arguments);
192        }
193
194        @Override
195        public <T> T invoke(final Object object, final String methodName, final Class<?>[] argumentTypes, final Object... arguments) {
196                requireNonNull(object, "object");
197                requireNonNull(methodName, "methodName");
198                if (! LsConfig.isLocalServerEnabled())
199                        return cast(ReflectionUtil.invoke(object, methodName, argumentTypes, arguments));
200
201                return invoke(object, methodName, toClassNames(argumentTypes), arguments);
202        }
203
204        @Override
205        public <T> T invoke(final Object object, final String methodName, final String[] argumentTypeNames, final Object... arguments) {
206                requireNonNull(object, "object");
207                requireNonNull(methodName, "methodName");
208                if (! LsConfig.isLocalServerEnabled())
209                        return cast(ReflectionUtil.invoke(object, methodName, getClassesOrFail(argumentTypeNames), arguments));
210
211                final MethodInvocationRequest methodInvocationRequest = MethodInvocationRequest.forObjectInvocation(
212                                object, methodName, argumentTypeNames, arguments);
213
214                return invoke(methodInvocationRequest);
215        }
216
217        private Class<?>[] getClassesOrFail(final String[] classNames) {
218                requireNonNull(classNames, "classNames");
219                final Class<?>[] result = new Class<?>[classNames.length];
220                for (int i = 0; i < classNames.length; i++)
221                        result[i] = getClassOrFail(classNames[i]);
222
223                return result;
224        }
225
226        private Class<?> getClassOrFail(final String className) {
227                requireNonNull(className, "className");
228                ClassLoader loader = Thread.currentThread().getContextClassLoader();
229                if (loader == null)
230                        loader = getClass().getClassLoader();
231
232                try {
233                        return Class.forName(className, true, loader);
234                } catch (ClassNotFoundException e) {
235                        throw new RuntimeException(e);
236                }
237        }
238
239        private String[] toClassNames(Class<?> ... classes) {
240                final String[] classNames;
241                if (classes == null)
242                        classNames = null;
243                else {
244                        classNames = new String[classes.length];
245                        for (int i = 0; i < classes.length; i++)
246                                classNames[i] = classes[i].getName();
247                }
248                return classNames;
249        }
250
251        private <T> T invoke(final MethodInvocationRequest methodInvocationRequest) {
252                requireNonNull(methodInvocationRequest, "methodInvocationRequest");
253
254                MethodInvocationResponse methodInvocationResponse = getLocalServerRestClient().execute(
255                                new InvokeMethod(methodInvocationRequest));
256
257                while (methodInvocationResponse instanceof DelayedMethodInvocationResponse) {
258                        final DelayedMethodInvocationResponse dmir = (DelayedMethodInvocationResponse) methodInvocationResponse;
259                        final Uid delayedResponseId = dmir.getDelayedResponseId();
260
261                        methodInvocationResponse = getLocalServerRestClient().execute(
262                                        new GetDelayedMethodInvocationResponse(delayedResponseId));
263                }
264
265                final Object result = methodInvocationResponse.getResult();
266                if (methodInvocationResponse.getWritableArguments() != null)
267                        copyWritableArgumentsBack(methodInvocationRequest.getArguments(), methodInvocationResponse.getWritableArguments());
268
269                return cast(result);
270        }
271
272        private void copyWritableArgumentsBack(final Object[] requestArguments, final Object[] responseArguments) {
273                requireNonNull(requestArguments, "requestArguments");
274                requireNonNull(responseArguments, "responseArguments");
275
276                for (int i = 0; i < responseArguments.length; ++i) {
277                        final Object responseArgument = responseArguments[i];
278                        if (responseArgument != null)
279                                copyWritableArgumentBack(requestArguments[i], responseArgument);
280                }
281        }
282
283        private void copyWritableArgumentBack(final Object requestArgument, final Object responseArgument) {
284                requireNonNull(requestArgument, "requestArgument");
285                requireNonNull(responseArgument, "responseArgument");
286
287                if (requestArgument.getClass().isArray()) {
288                        final int length = Array.getLength(requestArgument);
289                        for (int i = 0; i < length; ++i) {
290                                final Object value = Array.get(responseArgument, i);
291                                Array.set(requestArgument, i, value);
292                        }
293                }
294                else
295                        throw new UnsupportedOperationException("No idea how to copy this back! requestArgument=" + requestArgument);
296        }
297
298        private RemoteObjectProxy _createRemoteObjectProxy(final ObjectRef objectRef, final Class<?>[] interfaces) {
299                final ClassLoader classLoader = this.getClass().getClassLoader();
300                return (RemoteObjectProxy) Proxy.newProxyInstance(classLoader, interfaces,
301                                new RemoteObjectProxyInvocationHandler(this, objectRef));
302        }
303
304        private Class<?>[] getInterfaces(final ObjectRef objectRef) {
305                ClassInfo classInfo = classInfoMap.getClassInfo(objectRef.getClassId());
306                if (classInfo == null) {
307                        classInfo = objectRef.getClassInfo();
308                        if (classInfo == null)
309                                throw new IllegalStateException("There is no ClassInfo in the ClassInfoMap and neither in the ObjectRef! " + objectRef);
310
311                        classInfoMap.putClassInfo(classInfo);
312                        objectRef.setClassInfo(null);
313                }
314
315                final ClassManager classManager = objectManager.getClassManager();
316                final Set<String> interfaceNames = classInfo.getInterfaceNames();
317                final List<Class<?>> interfaces = new ArrayList<>(interfaceNames.size() + 1);
318                for (final String interfaceName : interfaceNames) {
319                        Class<?> iface = null;
320                        try {
321                                iface = classManager.getClassOrFail(interfaceName);
322                        } catch (RuntimeException x) {
323                                if (ExceptionUtil.getCause(x, ClassNotFoundException.class) == null)
324                                        throw x;
325                        }
326                        if (iface != null)
327                                interfaces.add(iface);
328                }
329                interfaces.add(RemoteObjectProxy.class);
330                return interfaces.toArray(new Class<?>[interfaces.size()]);
331        }
332
333        @Override
334        public void incRefCount(final ObjectRef objectRef, final Uid refId) {
335                incDecRefCountQueue.incRefCount(objectRef, refId);
336        }
337
338        @Override
339        public void decRefCount(final ObjectRef objectRef, final Uid refId) {
340                incDecRefCountQueue.decRefCount(objectRef, refId);
341        }
342
343        @Override
344        protected void finalize() throws Throwable {
345                close();
346                super.finalize();
347        }
348
349        @Override
350        public void close() {
351                final Thread thread = inverseServiceRequestHandlerThread;
352                if (thread != null) {
353                        inverseServiceRequestHandlerThread = null;
354                        thread.interrupt();
355                        try {
356                                thread.join();
357                        } catch (InterruptedException e) {
358                                doNothing();
359                        }
360                }
361
362                objectManager.setNeverEvict(false);
363
364                if (LsConfig.isLocalServerEnabled()) {
365                        try {
366                                invokeStatic(ObjectRef.class, ObjectRef.VIRTUAL_METHOD_CLOSE_OBJECT_MANAGER, (Class<?>[])null, (Object[]) null);
367                        } catch (Exception x) {
368                                logger.error("close: " + x, x);
369                        }
370                }
371        }
372
373        public Object getRemoteObjectProxyOrCreate(final ObjectRef objectRef) {
374                return objectManager.getRemoteObjectProxyManager().getRemoteObjectProxyOrCreate(objectRef, new RemoteObjectProxyFactory() {
375                        @Override
376                        public RemoteObjectProxy createRemoteObjectProxy(final ObjectRef objectRef) {
377                                final Class<?>[] interfaces = getInterfaces(objectRef);
378                                return _createRemoteObjectProxy(objectRef, interfaces);
379                        }
380                });
381        }
382
383        protected Uid getLocalProcessId() {
384                return LsConfig.getProcessId();
385        }
386
387        protected Uid getRemoteProcessId() {
388                final Uid result = invokeStatic(LsConfig.class, "getProcessId");
389                return result;
390        }
391
392        public boolean isLocalServerInSeparateProcess() {
393                final Uid localProcessId = getLocalProcessId();
394                final Uid remoteProcessId = getRemoteProcessId();
395                return ! localProcessId.equals(remoteProcessId);
396        }
397}