summaryrefslogtreecommitdiff
path: root/jurt/com/sun/star/lib/uno/bridges/java_remote/java_remote_bridge.java
blob: 56fc0b4c905ee5b2356d62336ee6fc086dd326ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
package com.sun.star.lib.uno.bridges.java_remote;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import com.sun.star.bridge.XBridge;
import com.sun.star.bridge.XInstanceProvider;
import com.sun.star.connection.XConnection;
import com.sun.star.lang.DisposedException;
import com.sun.star.lang.EventObject;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XEventListener;
import com.sun.star.lib.uno.environments.java.java_environment;
import com.sun.star.lib.uno.environments.remote.IProtocol;
import com.sun.star.lib.uno.environments.remote.IReceiver;
import com.sun.star.lib.uno.environments.remote.IThreadPool;
import com.sun.star.lib.uno.environments.remote.Job;
import com.sun.star.lib.uno.environments.remote.Message;
import com.sun.star.lib.uno.environments.remote.ThreadId;
import com.sun.star.lib.uno.environments.remote.ThreadPoolManager;
import com.sun.star.lib.uno.typedesc.MethodDescription;
import com.sun.star.lib.uno.typedesc.TypeDescription;
import com.sun.star.lib.util.DisposeListener;
import com.sun.star.lib.util.DisposeNotifier;
import com.sun.star.uno.Any;
import com.sun.star.uno.IBridge;
import com.sun.star.uno.IEnvironment;
import com.sun.star.uno.Type;
import com.sun.star.uno.TypeClass;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XInterface;

/**
 * This class implements a remote bridge.
 *
 * <p>Therefore various interfaces are implemented.</p>
 *
 * <p>The protocol to used is passed by name, the bridge
 * then looks for it under <code>com.sun.star.lib.uno.protocols</code>.</p>
 *
 * @since       UDK1.0
 */
public class java_remote_bridge
    implements IBridge, IReceiver, RequestHandler, XBridge, XComponent,
        DisposeNotifier
{
    /**
     * When set to true, enables various debugging output.
     */
    private static final boolean DEBUG = false;

    private final class MessageDispatcher extends Thread {
        public MessageDispatcher() {
            super("MessageDispatcher");
        }

        @Override
        public void run() {
            try {
                for (;;) {
                    synchronized (this) {
                        if (terminate) {
                            break;
                        }
                    }
                    Message msg = _iProtocol.readMessage();
                    Object obj = null;
                    if (msg.isRequest()) {
                        String oid = msg.getObjectId();
                        Type type = new Type(msg.getType());
                        int fid = msg.getMethod().getIndex();
                        if (fid == MethodDescription.ID_RELEASE) {
                            _java_environment.revokeInterface(oid, type);
                            remRefHolder(type, oid);
                            if (msg.isSynchronous()) {
                                sendReply(false, msg.getThreadId(), null);
                            }
                            continue;
                        }
                        obj = _java_environment.getRegisteredInterface(
                            oid, type);
                        if (obj == null
                            && fid == MethodDescription.ID_QUERY_INTERFACE)
                        {
                            if (_xInstanceProvider == null) {
                                sendReply(
                                    true, msg.getThreadId(),
                                    new com.sun.star.uno.RuntimeException(
                                        "unknown OID " + oid));
                                continue;
                            } else {
                                UnoRuntime.setCurrentContext(
                                    msg.getCurrentContext());
                                try {
                                    obj = _xInstanceProvider.getInstance(oid);
                                } catch (com.sun.star.uno.RuntimeException e) {
                                    sendReply(true, msg.getThreadId(), e);
                                    continue;
                                } catch (Exception e) {
                                    sendReply(
                                        true, msg.getThreadId(),
                                        new com.sun.star.uno.RuntimeException(
                                            e.toString()));
                                    continue;
                                } finally {
                                    UnoRuntime.setCurrentContext(null);
                                }
                            }
                        }
                    }
                    _iThreadPool.putJob(
                        new Job(obj, java_remote_bridge.this, msg));
                }
            } catch (Throwable e) {
                dispose(e);
            }
        }

        public synchronized void terminate() {
            terminate = true;
        }

        private boolean terminate = false;
    }

    protected XConnection       _xConnection;

    protected XInstanceProvider _xInstanceProvider;

    protected String            _name = "remote";
    private final String protocol;
    protected IProtocol         _iProtocol;
    protected IEnvironment      _java_environment;
    protected MessageDispatcher _messageDispatcher;
    protected final AtomicInteger _life_count = new AtomicInteger();    // determines if this bridge is alive, which is controlled by acquire and release calls

    private final ArrayList<XEventListener> _listeners = new ArrayList<XEventListener>();

    protected IThreadPool       _iThreadPool;

    // Variable disposed must only be used while synchronized on this object:
    private boolean disposed = false;

    /**
     * This method is for testing only.
     */
    int getLifeCount() {
        return _life_count.get();
    }

    /**
     * This method is for testing only.
     */
    IProtocol getProtocol() {
        return _iProtocol;
    }

    /**
     * The ref holder stuff strongly holds objects mapped out via this bridge
     * (the java_environment only holds them weakly).
     *
     * <p>When this bridge is disposed, all remaining ref holder entries are
     * released.</p>
     */
    private static final class RefHolder {
        public RefHolder(Type type, Object object) {
            this.type = type;
            this.object = object;
        }

        public Type getType() {
            return type;
        }

        public void acquire() {
            ++count;
        }

        public boolean release() {
            return --count == 0;
        }

        private final Type type;
        @SuppressWarnings("unused")
        private final Object object;
        private int count = 1;
    }

    private final HashMap<String, LinkedList<RefHolder>> refHolders = new HashMap<String, LinkedList<RefHolder>>();
        // from OID (String) to LinkedList of RefHolder

    private boolean hasRefHolder(String oid, Type type) {
        synchronized (refHolders) {
            LinkedList<RefHolder> l = refHolders.get(oid);
            if (l != null) {
                for (RefHolder rh : l) {
                    if (type.isSupertypeOf(rh.getType())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    final void addRefHolder(Object obj, Type type, String oid) {
        synchronized (refHolders) {
            LinkedList<RefHolder> l = refHolders.get(oid);
            if (l == null) {
                l = new LinkedList<RefHolder>();
                refHolders.put(oid, l);
            }
            boolean found = false;
            for (Iterator<RefHolder> i = l.iterator(); !found && i.hasNext();) {
                RefHolder rh = i.next();
                if (rh.getType().equals(type)) {
                    found = true;
                    rh.acquire();
                }
            }
            if (!found) {
                l.add(new RefHolder(type, obj));
            }
        }
        acquire();
    }

    final void remRefHolder(Type type, String oid) {
        synchronized (refHolders) {
            LinkedList<RefHolder> l = refHolders.get(oid);
            if (l == null) {
                return;
            }
            for (RefHolder rh : l) {
                if (rh.getType().equals(type)) {
                    try {
                        if (rh.release()) {
                            l.remove(rh);
                            if (l.isEmpty()) {
                                refHolders.remove(oid);
                            }
                        }
                    } finally {
                        release();
                    }
                    break;
                }
            }
        }
    }

    final void freeHolders() {
        synchronized (refHolders) {
            for (Iterator<Map.Entry<String,LinkedList<RefHolder>>> i1 = refHolders.entrySet().iterator(); i1.hasNext();)
            {
                Map.Entry<String,LinkedList<RefHolder>> e = i1.next();
                String oid = e.getKey();
                LinkedList<RefHolder> l = e.getValue();
                for (Iterator<RefHolder> i2 = l.iterator(); i2.hasNext();) {
                    RefHolder rh = i2.next();
                    for (boolean done = false; !done;) {
                        done = rh.release();
                        _java_environment.revokeInterface(oid, rh.getType());
                        release();
                    }
                }
            }
            refHolders.clear();
        }
    }

    public java_remote_bridge(
        IEnvironment java_environment, IEnvironment remote_environment,
        Object[] args)
        throws Exception
    {
        _java_environment = java_environment;
        String proto = (String) args[0];
        _xConnection = (XConnection) args[1];
        _xInstanceProvider = (XInstanceProvider) args[2];
        if (args.length > 3) {
            _name = (String) args[3];
        }
        String attr;
        int i = proto.indexOf(',');
        if (i >= 0) {
            protocol = proto.substring(0, i);
            attr = proto.substring(i + 1);
        } else {
            protocol = proto;
            attr = null;
        }
        _iProtocol = (IProtocol) Class.forName(
            "com.sun.star.lib.uno.protocols." + protocol + "." + protocol).
            getConstructor(
                new Class[] {
                    IBridge.class, String.class, InputStream.class,
                    OutputStream.class }).
            newInstance(
                new Object[] {
                    this, attr,
                    new XConnectionInputStream_Adapter(_xConnection),
                    new XConnectionOutputStream_Adapter(_xConnection) });
        proxyFactory = new ProxyFactory(this, this);
        _iThreadPool = ThreadPoolManager.create();
        _messageDispatcher = new MessageDispatcher();
        _messageDispatcher.start();
        _iProtocol.init();
    }

    private void notifyListeners() {
        EventObject eventObject = new EventObject(this);

        Iterator<XEventListener> elements = _listeners.iterator();
        while(elements.hasNext()) {
            XEventListener xEventListener = elements.next();

            try {
                xEventListener.disposing(eventObject);
            }
            catch(com.sun.star.uno.RuntimeException runtimeException) {
                // we are here not interested in any exceptions
            }
        }
    }

    /**
     * Constructs a new bridge.
     * <p> This method is not part of the provided <code>api</code>
     * and should only be used by the UNO runtime.</p>
     *
     * @param  args the custom parameters: arg[0] == protocol_name,
     * arg[1] == xConnection, arg[2] == xInstanceProvider.
     *
     * @deprecated as of UDK 1.0
     */
    public java_remote_bridge(Object args[]) throws Exception {
        this(UnoRuntime.getEnvironment("java", null), UnoRuntime.getEnvironment("remote", null), args);
    }

    /**
     *
     * @see com.sun.star.uno.IBridge#mapInterfaceTo
     */
    public Object mapInterfaceTo(Object object, Type type) {
        checkDisposed();
        if (object == null) {
            return null;
        } else {
            String[] oid = new String[1];
            object = _java_environment.registerInterface(object, oid, type);
            if (!proxyFactory.isProxy(object)) {
                // This branch must be taken iff object either is no proxy at
                // all or a proxy from some other bridge.  There are objects
                // that behave like objects for this bridge but that are not
                // detected as such by proxyFactory.isProxy.  The only known
                // case of such objects is com.sun.star.comp.beans.Wrapper,
                // which implements com.sun.star.lib.uno.Proxy and effectively
                // is a second proxy around a proxy that can be from this
                // bridge.  For that case, there is no problem, however:  Since
                // the proxies generated by ProxyFactory send each
                // queryInterface to the original object (i.e., they do not
                // short-circuit requests for a super-interface to themselves),
                // there will always be an appropriate ProxyFactory-proxy
                // registered at the _java_environment, so that the object
                // returned by _java_environment.registerInterface will never be
                // a com.sun.star.comp.beans.Wrapper.
                addRefHolder(object, type, oid[0]);
            }
            return oid[0];
        }
    }

    /**
     * Maps an object from destination environment to the source environment.
     *
     * @param      oId        the object to map.
     * @param      type       the interface under which is to be mapped.
     * @return     the object in the source environment.
     *
     * @see                   com.sun.star.uno.IBridge#mapInterfaceFrom
     */
    public Object mapInterfaceFrom(Object oId, Type type) {
        checkDisposed();
        // TODO  What happens if an exception is thrown after the call to
        // acquire, but before it is guaranteed that a pairing release will be
        // called eventually?
        acquire();
        String oid = (String) oId;
        Object object = _java_environment.getRegisteredInterface(oid, type);
        if (object == null) {
            object = _java_environment.registerInterface(
                proxyFactory.create(oid, type), new String[] { oid }, type);
                // the proxy sends a release when finalized
        } else if (!hasRefHolder(oid, type)) {
            sendInternalRequest(oid, type, "release", null);
        }
        return object;
    }

    /**
     * Gives the source environment.
     *
     * @return   the source environment of this bridge.
     * @see      com.sun.star.uno.IBridge#getSourceEnvironment
     */
    public IEnvironment getSourceEnvironment() {
        return _java_environment;
    }

    /**
     * Gives the destination environment.
     *
     * @return   the destination environment of this bridge.
     * @see      com.sun.star.uno.IBridge#getTargetEnvironment
     */
    public IEnvironment getTargetEnvironment() {
        return null;
    }

    /**
     * Increases the life count.
     *
     * @see com.sun.star.uno.IBridge#acquire
     */
    public void acquire() {
        if(DEBUG) {
            int x = _life_count.incrementAndGet();
            System.err.println("##### " + getClass().getName() + ".acquire:" + x);
        } else {
            _life_count.incrementAndGet();
        }
    }

    /**
     * Decreases the life count.
     *
     * <p>If the life count drops to zero, the bridge disposes itself.</p>
     *
     * @see com.sun.star.uno.IBridge#release
     */
    public void release() {
        int x = _life_count.decrementAndGet();
        if (x <= 0) {
            dispose(new Throwable("end of life"));
        }
    }

    public void dispose() {
        dispose(new Throwable("user dispose"));
    }

    private void dispose(Throwable throwable) {
        synchronized (this) {
            if (disposed) {
                return;
            }
            disposed = true;
        }

        notifyListeners();
        for (Iterator<DisposeListener> i = disposeListeners.iterator(); i.hasNext();) {
            i.next().notifyDispose(this);
        }

        _iProtocol.terminate();

        try {
            _messageDispatcher.terminate();

            try {
                _xConnection.close();
            } catch (com.sun.star.io.IOException e) {
                System.err.println(
                    getClass().getName() + ".dispose - IOException:" + e);
            }

            if (Thread.currentThread() != _messageDispatcher
                && _messageDispatcher.isAlive())
            {
                _messageDispatcher.join(1000);
                if (_messageDispatcher.isAlive()) {
                    _messageDispatcher.interrupt();
                    _messageDispatcher.join();
                }
            }

            // interrupt all jobs queued by this bridge
            _iThreadPool.dispose(throwable);

            // release all out-mapped objects and all in-mapped proxies:
            freeHolders();
            // assert _java_environment instanceof java_environment;
            ((java_environment) _java_environment).revokeAllProxies();

            proxyFactory.dispose();

            if (DEBUG) {
                if (_life_count.get() != 0) {
                    System.err.println(getClass().getName()
                                       + ".dispose - life count (proxies left):"
                                       + _life_count);
                }
                _java_environment.list();
            }

            // clear members
            _xConnection        = null;
            _java_environment   = null;
            _messageDispatcher  = null;
        } catch (InterruptedException e) {
            System.err.println(getClass().getName()
                               + ".dispose - InterruptedException:" + e);
        }
    }

    /**
     *
     * @see com.sun.star.bridge.XBridge#getInstance
     */
    public Object getInstance(String instanceName) {
        Type t = new Type(XInterface.class);
        return sendInternalRequest(
            instanceName, t, "queryInterface", new Object[] { t });
    }

    /**
     * Gives the name of this bridge.
     *
     * @return  the name of this bridge.
     * @see     com.sun.star.bridge.XBridge#getName
     */
    public String getName() {
        return _name;
    }

    /**
     * Gives a description of the connection type and protocol used.
     *
     * @return  connection type and protocol.
     * @see     com.sun.star.bridge.XBridge#getDescription
     */
    public String getDescription() {
        return protocol + "," + _xConnection.getDescription();
    }

    public void sendReply(boolean exception, ThreadId threadId, Object result) {
        if (DEBUG) {
            System.err.println("##### " + getClass().getName() + ".sendReply: "
                               + exception + " " + result);
        }

        checkDisposed();

        try {
            _iProtocol.writeReply(exception, threadId, result);
        } catch (IOException e) {
            dispose(e);
            throw (DisposedException)
                (new DisposedException("unexpected " + e).initCause(e));
        } catch (RuntimeException e) {
            dispose(e);
            throw e;
        } catch (Error e) {
            dispose(e);
            throw e;
        }
    }

    public Object sendRequest(
        String oid, Type type, String operation, Object[] params)
        throws Throwable
    {
        Object result = null;

        checkDisposed();

        ThreadId threadId = _iThreadPool.getThreadId();
        Object handle = _iThreadPool.attach(threadId);
        try {
            boolean sync;
            try {
                sync = _iProtocol.writeRequest(
                    oid, TypeDescription.getTypeDescription(type), operation,
                    threadId, params);
            } catch (IOException e) {
                dispose(e);
                throw (DisposedException)
                    new DisposedException(e.toString()).initCause(e);
            }
            if (sync && Thread.currentThread() != _messageDispatcher) {
                result = _iThreadPool.enter(handle, threadId);
            }
        } finally {
            _iThreadPool.detach(handle, threadId);
            if(operation.equals("release"))
                release(); // kill this bridge, if this was the last proxy
        }

        if(DEBUG) System.err.println("##### " + getClass().getName() + ".sendRequest left:" + result);

        // On the wire (at least in URP), the result of queryInterface is
        // transported as an ANY, but in Java it shall be transported as a
        // direct reference to the UNO object (represented as a Java Object),
        // never boxed in a com.sun.star.uno.Any:
        if (operation.equals("queryInterface") && result instanceof Any) {
            Any a = (Any) result;
            if (a.getType().getTypeClass() == TypeClass.INTERFACE) {
                result = a.getObject();
            } else {
                result = null; // should never happen
            }
        }

        return result;
    }

    private Object sendInternalRequest(
        String oid, Type type, String operation, Object[] arguments)
    {
        try {
            return sendRequest(oid, type, operation, arguments);
        } catch (Error e) {
            throw e;
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new RuntimeException("Unexpected " + e);
        }
    }

    /**
     * Methods XComponent.
     */
    public void addEventListener(XEventListener xEventListener) {
        _listeners.add(xEventListener);
    }

    public void removeEventListener(XEventListener xEventListener) {
        _listeners.remove(xEventListener);
    }

    /**
     *
     * @see DisposeNotifier#addDisposeListener
     */
    public void addDisposeListener(DisposeListener listener) {
        synchronized (this) {
            if (!disposed) {
                disposeListeners.add(listener);
                return;
            }
        }
        listener.notifyDispose(this);
    }

    /**
     * This function must only be called while synchronized on this object.
     */
    private synchronized void checkDisposed() {
        if (disposed) {
            throw new DisposedException("java_remote_bridge " + this
                                        + " is disposed");
        }
    }

    private final ProxyFactory proxyFactory;

    // Access to disposeListeners must be synchronized on <CODE>this</CODE>:
    private final ArrayList<DisposeListener> disposeListeners = new ArrayList<DisposeListener>();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */