/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2008 by Sun Microsystems, Inc. * * OpenOffice.org - a multi-platform office productivity suite * * $RCSfile: java_remote_bridge.java,v $ * $Revision: 1.45 $ * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ package com.sun.star.lib.uno.bridges.java_remote; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Vector; import com.sun.star.lib.util.DisposeListener; import com.sun.star.lib.util.DisposeNotifier; import com.sun.star.bridge.XBridge; import com.sun.star.bridge.XInstanceProvider; import com.sun.star.comp.loader.FactoryHelper; import com.sun.star.connection.XConnection; import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.EventObject; import com.sun.star.lang.XComponent; import com.sun.star.lang.XEventListener; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.lang.XSingleServiceFactory; import com.sun.star.lang.DisposedException; 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.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.environments.remote.IThreadPool; import com.sun.star.lib.uno.typedesc.MethodDescription; import com.sun.star.lib.uno.typedesc.TypeDescription; import com.sun.star.registry.XRegistryKey; import com.sun.star.uno.IBridge; import com.sun.star.uno.IEnvironment; import com.sun.star.uno.UnoRuntime; import com.sun.star.uno.XInterface; import com.sun.star.uno.Type; import com.sun.star.uno.TypeClass; import com.sun.star.uno.Any; /** * This class implements a remote bridge. Therefor * various interfaces are implemented. *

* The protocol to used is passed by name, the bridge * then looks for it under com.sun.star.lib.uno.protocols. *

* @version $Revision: 1.45 $ $ $Date: 2008-04-11 11:18:08 $ * @author Kay Ramme * @since UDK1.0 */ public class java_remote_bridge implements IBridge, IReceiver, RequestHandler, XBridge, XComponent, DisposeNotifier { /** * When set to true, enables various debugging output. */ static private final boolean DEBUG = false; private final class MessageDispatcher extends Thread { public MessageDispatcher() { super("MessageDispatcher"); } 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(new DisposedException(e.toString())); } } 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 int _life_count = 0; // determines if this bridge is alife, which is controlled by acquire and release calls private final Vector _listeners = new Vector(); 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; } /** * 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). When this bridge is // disposed, all remaining ref holder entries are released. 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; private final Object object; private int count = 1; } private final HashMap refHolders = new HashMap(); // from OID (String) to LinkedList of RefHolder private boolean hasRefHolder(String oid, Type type) { synchronized (refHolders) { LinkedList l = (LinkedList) refHolders.get(oid); if (l != null) { for (Iterator i = l.iterator(); i.hasNext();) { RefHolder rh = (RefHolder) i.next(); if (type.isSupertypeOf(rh.getType())) { return true; } } } } return false; } final void addRefHolder(Object obj, Type type, String oid) { synchronized (refHolders) { LinkedList l = (LinkedList) refHolders.get(oid); if (l == null) { l = new LinkedList(); refHolders.put(oid, l); } boolean found = false; for (Iterator i = l.iterator(); !found && i.hasNext();) { RefHolder rh = (RefHolder) 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 l = (LinkedList) refHolders.get(oid); if (l != null) { for (Iterator i = l.iterator(); i.hasNext();) { RefHolder rh = (RefHolder) i.next(); 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 i1 = refHolders.entrySet().iterator(); i1.hasNext();) { Map.Entry e = (Map.Entry) i1.next(); String oid = (String) e.getKey(); LinkedList l = (LinkedList) e.getValue(); for (Iterator i2 = l.iterator(); i2.hasNext();) { RefHolder rh = (RefHolder) 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); Enumeration elements = _listeners.elements(); while(elements.hasMoreElements()) { XEventListener xEventListener = (XEventListener)elements.nextElement(); try { xEventListener.disposing(eventObject); } catch(com.sun.star.uno.RuntimeException runtimeException) { // we are here not interested in any exceptions } } } /** * Constructs a new bridge. *

* This method is not part of the provided api * and should only be used by the UNO runtime. *

* @deprecated as of UDK 1.0 *

* @param args the custom parameters: arg[0] == protocol_name, arg[1] == xConnection, arg[2] == xInstanceProvider */ 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. *

* @return the object in the source environment * @param object the object to map * @param type the interface under which is to be mapped * @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 synchronized void acquire() { ++ _life_count; if(DEBUG) System.err.println("##### " + getClass().getName() + ".acquire:" + _life_count); } /** * Decreases the life count. * If the life count drops to zero, the bridge disposes itself. *

* @see com.sun.star.uno.IBridge#release */ public void release() { boolean dispose; synchronized (this) { --_life_count; dispose = _life_count <= 0; } if (dispose) { dispose(new com.sun.star.uno.RuntimeException("end of life")); } } public void dispose() { dispose(new com.sun.star.uno.RuntimeException("user dispose")); } private void dispose(Throwable throwable) { synchronized (this) { if (disposed) { return; } disposed = true; } notifyListeners(); for (Iterator i = disposeListeners.iterator(); i.hasNext();) { ((DisposeListener) i.next()).notifyDispose(this); } try { _messageDispatcher.terminate(); _xConnection.close(); if (Thread.currentThread() != _messageDispatcher && _messageDispatcher.isAlive()) { // This is a workaround for a Linux Sun JDK1.3 problem: The // message dispatcher stays in the socket read method, even if // the socket has been closed. Suspending and resuming the // message dispatcher lets it notice the closed socket. Only // use this workaround for Linux JRE 1.3.0 and 1.3.1 from Sun // and Blackdown. This workaround is dangerouse and may // hardlock the VM. if (System.getProperty("os.name", "").toLowerCase().equals( "linux") && System.getProperty("java.version", "").startsWith("1.3.") && (System.getProperty("java.vendor", "").toLowerCase(). indexOf("sun") != -1 || System.getProperty("java.vendor", "").toLowerCase(). indexOf("blackdown") != -1)) { _messageDispatcher.suspend(); _messageDispatcher.resume(); } _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(); if (DEBUG) { if (_life_count != 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); } catch (com.sun.star.io.IOException e) { System.err.println(getClass().getName() + ".dispose - IOException:" + 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 new DisposedException("unexpected " + 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(); boolean goThroughThreadPool = false; 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) { DisposedException d = new DisposedException(e.toString()); dispose(d); throw d; } 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.addElement(xEventListener); } public void removeEventListener(XEventListener xEventListener) { _listeners.removeElement(xEventListener); } // @see NotifyDispose.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 this: private final ArrayList disposeListeners = new ArrayList(); }