| /************************************************************** |
| * |
| * 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 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| 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.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.connection.XConnection; |
| import com.sun.star.lang.EventObject; |
| import com.sun.star.lang.XComponent; |
| import com.sun.star.lang.XEventListener; |
| 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.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. |
| * <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> |
| * @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(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 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. |
| * <p> |
| * This method is not part of the provided <code>api</code> |
| * and should only be used by the UNO runtime. |
| * <p> |
| * @deprecated as of UDK 1.0 |
| * <p> |
| * @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. |
| * <p> |
| * @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. |
| * <p> |
| * @return the source environment of this bridge |
| * @see com.sun.star.uno.IBridge#getSourceEnvironment |
| */ |
| public IEnvironment getSourceEnvironment() { |
| return _java_environment; |
| } |
| |
| /** |
| * Gives the destination environment. |
| * <p> |
| * @return the destination environment of this bridge |
| * @see com.sun.star.uno.IBridge#getTargetEnvironment |
| */ |
| public IEnvironment getTargetEnvironment() { |
| return null; |
| } |
| |
| /** |
| * Increases the life count. |
| * <p> |
| * @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. |
| * <p> |
| * @see com.sun.star.uno.IBridge#release |
| */ |
| public void release() { |
| boolean dispose; |
| synchronized (this) { |
| --_life_count; |
| dispose = _life_count <= 0; |
| } |
| if (dispose) { |
| 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 i = disposeListeners.iterator(); i.hasNext();) { |
| ((DisposeListener) i.next()).notifyDispose(this); |
| } |
| |
| _iProtocol.terminate(); |
| |
| 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 |
| * <p> |
| * @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 |
| * <p> |
| * @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.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 <CODE>this</CODE>: |
| private final ArrayList disposeListeners = new ArrayList(); |
| } |