1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 package com.sun.star.lib.uno.bridges.java_remote;
20 
21 
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.Map;
30 import java.util.concurrent.atomic.AtomicInteger;
31 
32 import com.sun.star.bridge.XBridge;
33 import com.sun.star.bridge.XInstanceProvider;
34 import com.sun.star.connection.XConnection;
35 import com.sun.star.lang.DisposedException;
36 import com.sun.star.lang.EventObject;
37 import com.sun.star.lang.XComponent;
38 import com.sun.star.lang.XEventListener;
39 import com.sun.star.lib.uno.environments.java.java_environment;
40 import com.sun.star.lib.uno.environments.remote.IProtocol;
41 import com.sun.star.lib.uno.environments.remote.IReceiver;
42 import com.sun.star.lib.uno.environments.remote.IThreadPool;
43 import com.sun.star.lib.uno.environments.remote.Job;
44 import com.sun.star.lib.uno.environments.remote.Message;
45 import com.sun.star.lib.uno.environments.remote.ThreadId;
46 import com.sun.star.lib.uno.environments.remote.ThreadPoolManager;
47 import com.sun.star.lib.uno.typedesc.MethodDescription;
48 import com.sun.star.lib.uno.typedesc.TypeDescription;
49 import com.sun.star.lib.util.DisposeListener;
50 import com.sun.star.lib.util.DisposeNotifier;
51 import com.sun.star.uno.Any;
52 import com.sun.star.uno.IBridge;
53 import com.sun.star.uno.IEnvironment;
54 import com.sun.star.uno.Type;
55 import com.sun.star.uno.TypeClass;
56 import com.sun.star.uno.UnoRuntime;
57 import com.sun.star.uno.XInterface;
58 
59 /**
60  * This class implements a remote bridge.
61  *
62  * <p>Therefore various interfaces are implemented.</p>
63  *
64  * <p>The protocol to used is passed by name, the bridge
65  * then looks for it under <code>com.sun.star.lib.uno.protocols</code>.</p>
66  *
67  * @since       UDK1.0
68  */
69 public class java_remote_bridge
70     implements IBridge, IReceiver, RequestHandler, XBridge, XComponent,
71         DisposeNotifier
72 {
73     /**
74      * When set to true, enables various debugging output.
75      */
76     private static final boolean DEBUG = false;
77 
78     private final class MessageDispatcher extends Thread {
MessageDispatcher()79         public MessageDispatcher() {
80             super("MessageDispatcher");
81         }
82 
83         @Override
run()84         public void run() {
85             try {
86                 for (;;) {
87                     synchronized (this) {
88                         if (terminate) {
89                             break;
90                         }
91                     }
92                     Message msg = _iProtocol.readMessage();
93                     Object obj = null;
94                     if (msg.isRequest()) {
95                         String oid = msg.getObjectId();
96                         Type type = new Type(msg.getType());
97                         int fid = msg.getMethod().getIndex();
98                         if (fid == MethodDescription.ID_RELEASE) {
99                             _java_environment.revokeInterface(oid, type);
100                             remRefHolder(type, oid);
101                             if (msg.isSynchronous()) {
102                                 sendReply(false, msg.getThreadId(), null);
103                             }
104                             continue;
105                         }
106                         obj = _java_environment.getRegisteredInterface(
107                             oid, type);
108                         if (obj == null
109                             && fid == MethodDescription.ID_QUERY_INTERFACE)
110                         {
111                             if (_xInstanceProvider == null) {
112                                 sendReply(
113                                     true, msg.getThreadId(),
114                                     new com.sun.star.uno.RuntimeException(
115                                         "unknown OID " + oid));
116                                 continue;
117                             } else {
118                                 UnoRuntime.setCurrentContext(
119                                     msg.getCurrentContext());
120                                 try {
121                                     obj = _xInstanceProvider.getInstance(oid);
122                                 } catch (com.sun.star.uno.RuntimeException e) {
123                                     sendReply(true, msg.getThreadId(), e);
124                                     continue;
125                                 } catch (Exception e) {
126                                     sendReply(
127                                         true, msg.getThreadId(),
128                                         new com.sun.star.uno.RuntimeException(
129                                             e.toString()));
130                                     continue;
131                                 } finally {
132                                     UnoRuntime.setCurrentContext(null);
133                                 }
134                             }
135                         }
136                     }
137                     _iThreadPool.putJob(
138                         new Job(obj, java_remote_bridge.this, msg));
139                 }
140             } catch (Throwable e) {
141                 dispose(e);
142             }
143         }
144 
terminate()145         public synchronized void terminate() {
146             terminate = true;
147         }
148 
149         private boolean terminate = false;
150     }
151 
152     protected XConnection       _xConnection;
153 
154     protected XInstanceProvider _xInstanceProvider;
155 
156     protected String            _name = "remote";
157     private final String protocol;
158     protected IProtocol         _iProtocol;
159     protected IEnvironment      _java_environment;
160     protected MessageDispatcher _messageDispatcher;
161     protected final AtomicInteger _life_count = new AtomicInteger();    // determines if this bridge is alive, which is controlled by acquire and release calls
162 
163     private final ArrayList<XEventListener> _listeners = new ArrayList<XEventListener>();
164 
165     protected IThreadPool       _iThreadPool;
166 
167     // Variable disposed must only be used while synchronized on this object:
168     private boolean disposed = false;
169 
170     /**
171      * This method is for testing only.
172      */
getLifeCount()173     int getLifeCount() {
174         return _life_count.get();
175     }
176 
177     /**
178      * This method is for testing only.
179      */
getProtocol()180     IProtocol getProtocol() {
181         return _iProtocol;
182     }
183 
184     /**
185      * The ref holder stuff strongly holds objects mapped out via this bridge
186      * (the java_environment only holds them weakly).
187      *
188      * <p>When this bridge is disposed, all remaining ref holder entries are
189      * released.</p>
190      */
191     private static final class RefHolder {
RefHolder(Type type, Object object)192         public RefHolder(Type type, Object object) {
193             this.type = type;
194             this.object = object;
195         }
196 
getType()197         public Type getType() {
198             return type;
199         }
200 
acquire()201         public void acquire() {
202             ++count;
203         }
204 
release()205         public boolean release() {
206             return --count == 0;
207         }
208 
209         private final Type type;
210         @SuppressWarnings("unused")
211         private final Object object;
212         private int count = 1;
213     }
214 
215     private final HashMap<String, LinkedList<RefHolder>> refHolders = new HashMap<String, LinkedList<RefHolder>>();
216         // from OID (String) to LinkedList of RefHolder
217 
hasRefHolder(String oid, Type type)218     private boolean hasRefHolder(String oid, Type type) {
219         synchronized (refHolders) {
220             LinkedList<RefHolder> l = refHolders.get(oid);
221             if (l != null) {
222                 for (RefHolder rh : l) {
223                     if (type.isSupertypeOf(rh.getType())) {
224                         return true;
225                     }
226                 }
227             }
228         }
229         return false;
230     }
231 
addRefHolder(Object obj, Type type, String oid)232     final void addRefHolder(Object obj, Type type, String oid) {
233         synchronized (refHolders) {
234             LinkedList<RefHolder> l = refHolders.get(oid);
235             if (l == null) {
236                 l = new LinkedList<RefHolder>();
237                 refHolders.put(oid, l);
238             }
239             boolean found = false;
240             for (Iterator<RefHolder> i = l.iterator(); !found && i.hasNext();) {
241                 RefHolder rh = i.next();
242                 if (rh.getType().equals(type)) {
243                     found = true;
244                     rh.acquire();
245                 }
246             }
247             if (!found) {
248                 l.add(new RefHolder(type, obj));
249             }
250         }
251         acquire();
252     }
253 
remRefHolder(Type type, String oid)254     final void remRefHolder(Type type, String oid) {
255         synchronized (refHolders) {
256             LinkedList<RefHolder> l = refHolders.get(oid);
257             if (l == null) {
258                 return;
259             }
260             for (RefHolder rh : l) {
261                 if (rh.getType().equals(type)) {
262                     try {
263                         if (rh.release()) {
264                             l.remove(rh);
265                             if (l.isEmpty()) {
266                                 refHolders.remove(oid);
267                             }
268                         }
269                     } finally {
270                         release();
271                     }
272                     break;
273                 }
274             }
275         }
276     }
277 
freeHolders()278     final void freeHolders() {
279         synchronized (refHolders) {
280             for (Iterator<Map.Entry<String,LinkedList<RefHolder>>> i1 = refHolders.entrySet().iterator(); i1.hasNext();)
281             {
282                 Map.Entry<String,LinkedList<RefHolder>> e = i1.next();
283                 String oid = e.getKey();
284                 LinkedList<RefHolder> l = e.getValue();
285                 for (Iterator<RefHolder> i2 = l.iterator(); i2.hasNext();) {
286                     RefHolder rh = i2.next();
287                     for (boolean done = false; !done;) {
288                         done = rh.release();
289                         _java_environment.revokeInterface(oid, rh.getType());
290                         release();
291                     }
292                 }
293             }
294             refHolders.clear();
295         }
296     }
297 
java_remote_bridge( IEnvironment java_environment, IEnvironment remote_environment, Object[] args)298     public java_remote_bridge(
299         IEnvironment java_environment, IEnvironment remote_environment,
300         Object[] args)
301         throws Exception
302     {
303         _java_environment = java_environment;
304         String proto = (String) args[0];
305         _xConnection = (XConnection) args[1];
306         _xInstanceProvider = (XInstanceProvider) args[2];
307         if (args.length > 3) {
308             _name = (String) args[3];
309         }
310         String attr;
311         int i = proto.indexOf(',');
312         if (i >= 0) {
313             protocol = proto.substring(0, i);
314             attr = proto.substring(i + 1);
315         } else {
316             protocol = proto;
317             attr = null;
318         }
319         _iProtocol = (IProtocol) Class.forName(
320             "com.sun.star.lib.uno.protocols." + protocol + "." + protocol).
321             getConstructor(
322                 new Class[] {
323                     IBridge.class, String.class, InputStream.class,
324                     OutputStream.class }).
325             newInstance(
326                 new Object[] {
327                     this, attr,
328                     new XConnectionInputStream_Adapter(_xConnection),
329                     new XConnectionOutputStream_Adapter(_xConnection) });
330         proxyFactory = new ProxyFactory(this, this);
331         _iThreadPool = ThreadPoolManager.create();
332         _messageDispatcher = new MessageDispatcher();
333         _messageDispatcher.start();
334         _iProtocol.init();
335     }
336 
notifyListeners()337     private void notifyListeners() {
338         EventObject eventObject = new EventObject(this);
339 
340         Iterator<XEventListener> elements = _listeners.iterator();
341         while(elements.hasNext()) {
342             XEventListener xEventListener = elements.next();
343 
344             try {
345                 xEventListener.disposing(eventObject);
346             }
347             catch(com.sun.star.uno.RuntimeException runtimeException) {
348                 // we are here not interested in any exceptions
349             }
350         }
351     }
352 
353     /**
354      * Constructs a new bridge.
355      * <p> This method is not part of the provided <code>api</code>
356      * and should only be used by the UNO runtime.</p>
357      *
358      * @param  args the custom parameters: arg[0] == protocol_name,
359      * arg[1] == xConnection, arg[2] == xInstanceProvider.
360      *
361      * @deprecated as of UDK 1.0
362      */
363     @Deprecated
java_remote_bridge(Object args[])364     public java_remote_bridge(Object args[]) throws Exception {
365         this(UnoRuntime.getEnvironment("java", null), UnoRuntime.getEnvironment("remote", null), args);
366     }
367 
368     /**
369      *
370      * @see com.sun.star.uno.IBridge#mapInterfaceTo
371      */
mapInterfaceTo(Object object, Type type)372     public Object mapInterfaceTo(Object object, Type type) {
373         checkDisposed();
374         if (object == null) {
375             return null;
376         } else {
377             String[] oid = new String[1];
378             object = _java_environment.registerInterface(object, oid, type);
379             if (!proxyFactory.isProxy(object)) {
380                 // This branch must be taken iff object either is no proxy at
381                 // all or a proxy from some other bridge.  There are objects
382                 // that behave like objects for this bridge but that are not
383                 // detected as such by proxyFactory.isProxy.  The only known
384                 // case of such objects is com.sun.star.comp.beans.Wrapper,
385                 // which implements com.sun.star.lib.uno.Proxy and effectively
386                 // is a second proxy around a proxy that can be from this
387                 // bridge.  For that case, there is no problem, however:  Since
388                 // the proxies generated by ProxyFactory send each
389                 // queryInterface to the original object (i.e., they do not
390                 // short-circuit requests for a super-interface to themselves),
391                 // there will always be an appropriate ProxyFactory-proxy
392                 // registered at the _java_environment, so that the object
393                 // returned by _java_environment.registerInterface will never be
394                 // a com.sun.star.comp.beans.Wrapper.
395                 addRefHolder(object, type, oid[0]);
396             }
397             return oid[0];
398         }
399     }
400 
401     /**
402      * Maps an object from destination environment to the source environment.
403      *
404      * @param      oId        the object to map.
405      * @param      type       the interface under which is to be mapped.
406      * @return     the object in the source environment.
407      *
408      * @see                   com.sun.star.uno.IBridge#mapInterfaceFrom
409      */
mapInterfaceFrom(Object oId, Type type)410     public Object mapInterfaceFrom(Object oId, Type type) {
411         checkDisposed();
412         // TODO  What happens if an exception is thrown after the call to
413         // acquire, but before it is guaranteed that a pairing release will be
414         // called eventually?
415         acquire();
416         String oid = (String) oId;
417         Object object = _java_environment.getRegisteredInterface(oid, type);
418         if (object == null) {
419             object = _java_environment.registerInterface(
420                 proxyFactory.create(oid, type), new String[] { oid }, type);
421                 // the proxy sends a release when finalized
422         } else if (!hasRefHolder(oid, type)) {
423             sendInternalRequest(oid, type, "release", null);
424         }
425         return object;
426     }
427 
428     /**
429      * Gives the source environment.
430      *
431      * @return   the source environment of this bridge.
432      * @see      com.sun.star.uno.IBridge#getSourceEnvironment
433      */
getSourceEnvironment()434     public IEnvironment getSourceEnvironment() {
435         return _java_environment;
436     }
437 
438     /**
439      * Gives the destination environment.
440      *
441      * @return   the destination environment of this bridge.
442      * @see      com.sun.star.uno.IBridge#getTargetEnvironment
443      */
getTargetEnvironment()444     public IEnvironment getTargetEnvironment() {
445         return null;
446     }
447 
448     /**
449      * Increases the life count.
450      *
451      * @see com.sun.star.uno.IBridge#acquire
452      */
acquire()453     public void acquire() {
454         if(DEBUG) {
455             int x = _life_count.incrementAndGet();
456             System.err.println("##### " + getClass().getName() + ".acquire:" + x);
457         } else {
458             _life_count.incrementAndGet();
459         }
460     }
461 
462     /**
463      * Decreases the life count.
464      *
465      * <p>If the life count drops to zero, the bridge disposes itself.</p>
466      *
467      * @see com.sun.star.uno.IBridge#release
468      */
release()469     public void release() {
470         int x = _life_count.decrementAndGet();
471         if (x <= 0) {
472             dispose(new Throwable("end of life"));
473         }
474     }
475 
dispose()476     public void dispose() {
477         dispose(new Throwable("user dispose"));
478     }
479 
dispose(Throwable throwable)480     private void dispose(Throwable throwable) {
481         synchronized (this) {
482             if (disposed) {
483                 return;
484             }
485             disposed = true;
486         }
487 
488         notifyListeners();
489         for (Iterator<DisposeListener> i = disposeListeners.iterator(); i.hasNext();) {
490             i.next().notifyDispose(this);
491         }
492 
493         _iProtocol.terminate();
494 
495         try {
496             _messageDispatcher.terminate();
497 
498             try {
499                 _xConnection.close();
500             } catch (com.sun.star.io.IOException e) {
501                 System.err.println(
502                     getClass().getName() + ".dispose - IOException:" + e);
503             }
504 
505             if (Thread.currentThread() != _messageDispatcher
506                 && _messageDispatcher.isAlive())
507             {
508                 _messageDispatcher.join(1000);
509                 if (_messageDispatcher.isAlive()) {
510                     _messageDispatcher.interrupt();
511                     _messageDispatcher.join();
512                 }
513             }
514 
515             // interrupt all jobs queued by this bridge
516             _iThreadPool.dispose(throwable);
517 
518             // release all out-mapped objects and all in-mapped proxies:
519             freeHolders();
520             // assert _java_environment instanceof java_environment;
521             ((java_environment) _java_environment).revokeAllProxies();
522 
523             proxyFactory.dispose();
524 
525             if (DEBUG) {
526                 if (_life_count.get() != 0) {
527                     System.err.println(getClass().getName()
528                                        + ".dispose - life count (proxies left):"
529                                        + _life_count);
530                 }
531                 _java_environment.list();
532             }
533 
534             // clear members
535             _xConnection        = null;
536             _java_environment   = null;
537             _messageDispatcher  = null;
538         } catch (InterruptedException e) {
539             System.err.println(getClass().getName()
540                                + ".dispose - InterruptedException:" + e);
541         }
542     }
543 
544     /**
545      *
546      * @see com.sun.star.bridge.XBridge#getInstance
547      */
getInstance(String instanceName)548     public Object getInstance(String instanceName) {
549         Type t = new Type(XInterface.class);
550         return sendInternalRequest(
551             instanceName, t, "queryInterface", new Object[] { t });
552     }
553 
554     /**
555      * Gives the name of this bridge.
556      *
557      * @return  the name of this bridge.
558      * @see     com.sun.star.bridge.XBridge#getName
559      */
getName()560     public String getName() {
561         return _name;
562     }
563 
564     /**
565      * Gives a description of the connection type and protocol used.
566      *
567      * @return  connection type and protocol.
568      * @see     com.sun.star.bridge.XBridge#getDescription
569      */
getDescription()570     public String getDescription() {
571         return protocol + "," + _xConnection.getDescription();
572     }
573 
sendReply(boolean exception, ThreadId threadId, Object result)574     public void sendReply(boolean exception, ThreadId threadId, Object result) {
575         if (DEBUG) {
576             System.err.println("##### " + getClass().getName() + ".sendReply: "
577                                + exception + " " + result);
578         }
579 
580         checkDisposed();
581 
582         try {
583             _iProtocol.writeReply(exception, threadId, result);
584         } catch (IOException e) {
585             dispose(e);
586             throw (DisposedException)
587                 (new DisposedException("unexpected " + e).initCause(e));
588         } catch (RuntimeException e) {
589             dispose(e);
590             throw e;
591         } catch (Error e) {
592             dispose(e);
593             throw e;
594         }
595     }
596 
sendRequest( String oid, Type type, String operation, Object[] params)597     public Object sendRequest(
598         String oid, Type type, String operation, Object[] params)
599         throws Throwable
600     {
601         Object result = null;
602 
603         checkDisposed();
604 
605         ThreadId threadId = _iThreadPool.getThreadId();
606         Object handle = _iThreadPool.attach(threadId);
607         try {
608             boolean sync;
609             try {
610                 sync = _iProtocol.writeRequest(
611                     oid, TypeDescription.getTypeDescription(type), operation,
612                     threadId, params);
613             } catch (IOException e) {
614                 dispose(e);
615                 throw (DisposedException)
616                     new DisposedException(e.toString()).initCause(e);
617             }
618             if (sync && Thread.currentThread() != _messageDispatcher) {
619                 result = _iThreadPool.enter(handle, threadId);
620             }
621         } finally {
622             _iThreadPool.detach(handle, threadId);
623             if(operation.equals("release"))
624                 release(); // kill this bridge, if this was the last proxy
625         }
626 
627         if(DEBUG) System.err.println("##### " + getClass().getName() + ".sendRequest left:" + result);
628 
629         // On the wire (at least in URP), the result of queryInterface is
630         // transported as an ANY, but in Java it shall be transported as a
631         // direct reference to the UNO object (represented as a Java Object),
632         // never boxed in a com.sun.star.uno.Any:
633         if (operation.equals("queryInterface") && result instanceof Any) {
634             Any a = (Any) result;
635             if (a.getType().getTypeClass() == TypeClass.INTERFACE) {
636                 result = a.getObject();
637             } else {
638                 result = null; // should never happen
639             }
640         }
641 
642         return result;
643     }
644 
sendInternalRequest( String oid, Type type, String operation, Object[] arguments)645     private Object sendInternalRequest(
646         String oid, Type type, String operation, Object[] arguments)
647     {
648         try {
649             return sendRequest(oid, type, operation, arguments);
650         } catch (Error e) {
651             throw e;
652         } catch (RuntimeException e) {
653             throw e;
654         } catch (Throwable e) {
655             throw new RuntimeException("Unexpected " + e);
656         }
657     }
658 
659     /**
660      * Methods XComponent.
661      */
addEventListener(XEventListener xEventListener)662     public void addEventListener(XEventListener xEventListener) {
663         _listeners.add(xEventListener);
664     }
665 
removeEventListener(XEventListener xEventListener)666     public void removeEventListener(XEventListener xEventListener) {
667         _listeners.remove(xEventListener);
668     }
669 
670     /**
671      *
672      * @see DisposeNotifier#addDisposeListener
673      */
addDisposeListener(DisposeListener listener)674     public void addDisposeListener(DisposeListener listener) {
675         synchronized (this) {
676             if (!disposed) {
677                 disposeListeners.add(listener);
678                 return;
679             }
680         }
681         listener.notifyDispose(this);
682     }
683 
684     /**
685      * This function must only be called while synchronized on this object.
686      */
checkDisposed()687     private synchronized void checkDisposed() {
688         if (disposed) {
689             throw new DisposedException("java_remote_bridge " + this
690                                         + " is disposed");
691         }
692     }
693 
694     private final ProxyFactory proxyFactory;
695 
696     // Access to disposeListeners must be synchronized on <CODE>this</CODE>:
697     private final ArrayList<DisposeListener> disposeListeners = new ArrayList<DisposeListener>();
698 }
699 
700 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
701