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