1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 package com.sun.star.lib.uno.bridges.java_remote;
24 
25 import com.sun.star.bridge.XBridge;
26 import com.sun.star.bridge.XInstanceProvider;
27 import com.sun.star.comp.connections.PipedConnection;
28 import com.sun.star.connection.XConnection;
29 import com.sun.star.container.NoSuchElementException;
30 import com.sun.star.lib.uno.environments.java.java_environment;
31 import com.sun.star.lib.uno.typeinfo.MethodTypeInfo;
32 import com.sun.star.lib.uno.typeinfo.TypeInfo;
33 import com.sun.star.uno.IQueryInterface;
34 import com.sun.star.uno.Type;
35 import com.sun.star.uno.UnoRuntime;
36 import com.sun.star.uno.XInterface;
37 import util.WaitUnreachable;
38 
39 import org.junit.Test;
40 import static org.junit.Assert.*;
41 
42 public final class java_remote_bridge_Test {
43     @Test
test()44     public void test() throws Exception {
45         String protocol = "urp";
46 
47         XConnection connectionA = new PipedConnection(new Object[0]);
48         XConnection connectionB = new PipedConnection(
49             new Object[] { connectionA });
50         java_remote_bridge bridgeA = new java_remote_bridge(
51             new java_environment(null), null,
52             new Object[] { protocol, connectionA, new TestInstanceProvider() });
53         java_remote_bridge bridgeB = new java_remote_bridge(
54             new java_environment(null), null,
55             new Object[] { protocol, connectionB, null });
56 
57         testGetInstance(bridgeA, bridgeB);
58         testLifeCycle(bridgeA, bridgeB);
59     }
60 
testGetInstance(XBridge bridgeA, XBridge bridgeB)61     private void testGetInstance(XBridge bridgeA, XBridge bridgeB) {
62         assertTrue("return null",
63                bridgeB.getInstance(TestInstanceProvider.NAME_NULL) == null);
64 
65         try {
66             bridgeB.getInstance(TestInstanceProvider.NAME_RUNTIME_EXCEPTION);
67             fail("throw RuntimeException");
68         } catch (com.sun.star.uno.RuntimeException e) {
69             assertTrue("throw RuntimeException",
70                    e.getMessage().indexOf(
71                        TestInstanceProvider.NAME_RUNTIME_EXCEPTION) != -1);
72         }
73 
74         try {
75             bridgeB.getInstance(
76                 TestInstanceProvider.NAME_NO_SUCH_ELEMENT_EXCEPTION);
77             fail("throw NoSuchElementException");
78         } catch (com.sun.star.uno.RuntimeException e) {
79             assertTrue("throw NoSuchElementException",
80                    e.getMessage().indexOf(
81                        TestInstanceProvider.NAME_NO_SUCH_ELEMENT_EXCEPTION)
82                    != -1);
83         }
84 
85         try {
86             bridgeA.getInstance(TestInstanceProvider.NAME_ANYTHING);
87             fail("no instance provider");
88         } catch (com.sun.star.uno.RuntimeException e) {
89             assertTrue("no instance provider",
90                    e.getMessage().startsWith("unknown OID "));
91         }
92     }
93 
testLifeCycle(java_remote_bridge bridgeA, java_remote_bridge bridgeB)94     private void testLifeCycle(java_remote_bridge bridgeA,
95                                java_remote_bridge bridgeB)
96         throws InterruptedException
97     {
98         // Repeatedly, objects are mapped from bridgeA to bridgeB, where proxies
99         // for those objects (for the XInterface and TestInterface facets) are
100         // created.  The proxies at bridgeB keep both bridges alive; after those
101         // proxies have been garbage-collected, both bridges should be disposed.
102         // It does not work to map a local object from bridgeA to bridgeB, as
103         // bridgeB would find this object as a local one, too (via the shared,
104         // static localObjects Registry in java_environment): bridgeB would not
105         // create a proxy, would rather send back a "release" to bridgeA, and
106         // both bridges would be disposed while the first object is being
107         // mapped.  Therefore, a HACK is used to install TestProxy objects
108         // (which behave as if they got mapped in to bridgeA from somewhere
109         // else) at bridgeA and map those.
110 
111         final int COUNT = 100;
112         XInterface[] proxyBXInterface = new XInterface[COUNT];
113         TestInterface[] proxyBTestInterface = new TestInterface[COUNT];
114         for (int i = 0; i < COUNT; ++i) {
115             String name = "TestOID" + i;
116             Object proxyA = new TestProxy(name);
117             bridgeA.getSourceEnvironment().registerInterface(
118                 proxyA, new String[] { name }, new Type(XInterface.class));
119 
120             proxyBXInterface[i] = (XInterface) bridgeB.getInstance(name);
121 
122             // map object:
123             proxyBTestInterface[i] = UnoRuntime.queryInterface(
124                 TestInterface.class, proxyBXInterface[i]);
125             proxyBTestInterface[i].function();
126 
127             // remap object once:
128             TestInterface remapped = UnoRuntime.queryInterface(
129                 TestInterface.class, proxyBXInterface[i]);
130             remapped.function();
131 
132             // remap object twice:
133             remapped = UnoRuntime.queryInterface(
134                 TestInterface.class, proxyBXInterface[i]);
135             remapped.function();
136         }
137 
138         assertTrue("calls of object method", TestProxy.getCount() == 3 * COUNT);
139 
140         // The following checks rely on the implementation detail that mapping
141         // different facets of a UNO object (XInterface and TestInterface) leads
142         // to different proxies:
143 
144         assertTrue("bridge A life count", bridgeA.getLifeCount() == 2 * COUNT);
145         assertTrue("bridge B life count", bridgeB.getLifeCount() == 2 * COUNT);
146         assertTrue("proxy count", ProxyFactory.getDebugCount() == 2 * COUNT);
147 
148         System.out.println("waiting for proxies to become unreachable:");
149         for (int i = 0; i < COUNT; ++i) {
150             WaitUnreachable u1 = new WaitUnreachable(proxyBXInterface[i]);
151             WaitUnreachable u2 = new WaitUnreachable(proxyBTestInterface[i]);
152             proxyBXInterface[i] = null;
153             proxyBTestInterface[i] = null;
154             u1.waitUnreachable();
155             u2.waitUnreachable();
156         }
157         // For whatever strange reason, this sleep seems to be necessary to
158         // reliably ensure that even the last proxy's finalization is over
159         // before the following assure is executed:
160         Thread.sleep(1000);
161 
162         assertTrue("proxy count", ProxyFactory.getDebugCount() == 0);
163 
164         System.out.println("waiting for pending messages to be done");
165         while (bridgeA.getLifeCount() != 0 || bridgeB.getLifeCount() != 0) {
166             Thread.sleep(100);
167         }
168 
169         assertTrue("Zero bridge A life count", bridgeA.getLifeCount() == 0);
170         assertTrue("Zero bridge B life count", bridgeB.getLifeCount() == 0);
171         assertTrue("Zero proxy count", ProxyFactory.getDebugCount() == 0);
172     }
173 
174     public interface TestInterface extends XInterface {
function()175         void function();
176 
177         TypeInfo[] UNOTYPEINFO = new TypeInfo[] {
178             new MethodTypeInfo("function", 0, 0) };
179     }
180 
181     private static final class TestInstanceProvider
182         implements XInstanceProvider
183     {
getInstance(String name)184         public Object getInstance(String name) throws NoSuchElementException {
185             if (name.equals(NAME_NULL)) {
186                 return null;
187             } else if (name.equals(NAME_RUNTIME_EXCEPTION)) {
188                 throw new com.sun.star.uno.RuntimeException(
189                     getClass().getName() + ", throwing: " + name);
190             } else if (name.equals(NAME_NO_SUCH_ELEMENT_EXCEPTION)) {
191                 throw new NoSuchElementException(
192                     getClass().getName() + ", throwing: " + name);
193             } else {
194                 throw new IllegalStateException();
195             }
196         }
197 
198         public static final String NAME_NULL = "return null";
199         public static final String NAME_RUNTIME_EXCEPTION
200         = "throw RuntimeException";
201         public static final String NAME_NO_SUCH_ELEMENT_EXCEPTION
202         = "throw NoSuchElementException";
203         public static final String NAME_ANYTHING = "anything";
204     }
205 
206     private static final class TestProxy
207         implements com.sun.star.lib.uno.Proxy, IQueryInterface, XInterface,
208             TestInterface
209     {
TestProxy(String oid)210         public TestProxy(String oid) {
211             this.oid = oid;
212         }
213 
queryInterface(Type type)214         public Object queryInterface(Type type) {
215             // type should be either XInterface or TestInterface...
216             return this;
217         }
218 
isSame(Object object)219         public boolean isSame(Object object) {
220             return object instanceof TestProxy
221                 && oid.equals(((TestProxy) object).oid);
222         }
223 
getOid()224         public String getOid() {
225             return oid;
226         }
227 
function()228         public void function() {
229             synchronized (getClass()) {
230                 ++count;
231             }
232         }
233 
getCount()234         public static synchronized int getCount() {
235             return count;
236         }
237 
238         private final String oid;
239 
240         private static int count = 0;
241     }
242 }
243