1   /***
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Exoffice Technologies.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Exoffice Technologies. Exolab is a registered
23   *    trademark of Exoffice Technologies.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2005 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: DisconnectionTestCase.java,v 1.4 2006/12/16 12:37:17 tanderson Exp $
44   */
45  package org.exolab.jms.net.invoke;
46  
47  import java.rmi.RemoteException;
48  import java.util.Map;
49  import java.util.HashMap;
50  
51  import EDU.oswego.cs.dl.util.concurrent.Latch;
52  
53  import org.exolab.jms.net.Callback;
54  import org.exolab.jms.net.CallbackService;
55  import org.exolab.jms.net.CallbackServiceImpl;
56  import org.exolab.jms.net.connector.Caller;
57  import org.exolab.jms.net.connector.CallerListener;
58  import org.exolab.jms.net.orb.ORB;
59  import org.exolab.jms.net.proxy.Proxy;
60  import org.exolab.jms.net.registry.Registry;
61  
62  
63  /***
64   * Tests disconnection.
65   *
66   * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
67   * @version $Revision: 1.4 $ $Date: 2006/12/16 12:37:17 $
68   */
69  public class DisconnectionTestCase extends ORBTestCase {
70  
71      /***
72       * Construct a new <code>DisconnectionTestCase</code>.
73       *
74       * @param name the name of test case
75       * @param uri  the server export URI
76       */
77      public DisconnectionTestCase(String name, String uri) {
78          super(name, uri);
79      }
80  
81      /***
82       * Construct a new <code>DisconnectionTestCase</code>.
83       *
84       * @param name       the name of test case
85       * @param uri        the server export URI
86       * @param properties connection properties. May be <code>null</code>
87       */
88      public DisconnectionTestCase(String name, String uri, Map properties) {
89          super(name, uri, properties);
90      }
91  
92      /***
93       * Construct a new <code>DisconnectionTestCase</code>.
94       *
95       * @param name     the name of test case
96       * @param uri      the server export URI
97       * @param routeURI the route URI
98       */
99      public DisconnectionTestCase(String name, String uri, String routeURI) {
100         super(name, uri, routeURI);
101     }
102 
103     /***
104      * Construct a new <code>DisconnectionTestCase</code>.
105      *
106      * @param name            the name of test case
107      * @param uri             the export URI
108      * @param routeURI        the route URI
109      * @param connectionProps connection properties. May be <code>null</code>
110      * @param acceptorProps   acceptor properites. May be <code>null</code>
111      */
112     public DisconnectionTestCase(String name, String uri, String routeURI,
113                                  Map connectionProps, Map acceptorProps) {
114         super(name, uri, routeURI, connectionProps, acceptorProps);
115     }
116 
117     /***
118      * Verifies that the client is notified when the server is shut down.
119      *
120      * @throws Exception for any error
121      */
122     public void testServerDisconnect() throws Exception {
123         final Latch latch = new Latch();
124         CallerListener listener = new CallerListener() {
125             public void disconnected(Caller caller) {
126                 latch.release();
127             }
128         };
129         ORB client = getClientORB();
130         client.addCallerListener(getServerURI(), listener);
131 
132         ORB server = getORB();
133         server.getRegistry();
134 
135         // get the registry proxy. This will establish a connection to the
136         // server.
137         Registry registry = getRegistry();
138         assertNotNull(registry);
139 
140         server.shutdown();
141 
142         if (!latch.attempt(10 * 1000)) {
143             fail("CallerListener not notified of disconnection");
144         }
145     }
146 
147     /***
148      * Verifies that the server is notified when the client is shut down.
149      *
150      * @throws Exception for any error
151      */
152     public void testClientDisconnect() throws Exception {
153         Latch latch = new Latch();
154         ORB server = getORB();
155         CallbackServer serviceImpl = new CallbackServer(server, latch);
156 
157         Proxy proxy = server.exportObject(serviceImpl);
158         server.getRegistry().bind("service", proxy);
159 
160         ORB client = getClientORB();
161         Registry registry = client.getRegistry(getConnectionProperties());
162         CallbackService service = (CallbackService) registry.lookup("service");
163 
164         LoggingCallback callback = new LoggingCallback();
165         Callback callbackProxy = (Callback) client.exportObjectTo(callback,
166                                                                   getServerURI());
167         service.addCallback(callbackProxy);
168 
169         assertNull(serviceImpl.getException());
170         client.shutdown();
171 
172         if (!latch.attempt(10 * 1000)) {
173             fail("CallerListener not notified of disconnection");
174         }
175         assertNull(serviceImpl.getException());
176     }
177 
178     /***
179      * Verifies that the client is notified when the connection is closed
180      * through inactivity.
181      *
182      * @throws Exception for any error
183      */
184     public void testInactive() throws Exception {
185         final Latch latch = new Latch();
186         CallerListener listener = new CallerListener() {
187             public void disconnected(Caller caller) {
188                 latch.release();
189             }
190         };
191 
192         ORB server = getORB();
193         CallbackServer serviceImpl = new CallbackServer(server, latch);
194 
195         Proxy proxy = server.exportObject(serviceImpl);
196         server.getRegistry().bind("service", proxy);
197 
198         ORB client = getClientORB();
199         client.addCallerListener(getServerURI(), listener);
200 
201         Registry registry = getRegistry(); // will establish a connection
202         assertNotNull(registry);
203         CallbackService service = (CallbackService) registry.lookup("service");
204         assertNotNull(service);
205 
206         // make sure the connection isn't reaped through inactivity
207         // while there are proxies associated with it.
208         for (int i = 0; i < 10; ++i) {
209             Runtime.getRuntime().gc();
210             if (latch.attempt(1000)) {
211                 break;
212             }
213         }
214         if (latch.attempt(0)) {
215             fail("Connection terminated when there were active proxies");
216         }
217 
218         // clear registry proxy and ensure the connection isn't reaped.
219         // Registry proxy is constructed differently to those serialized
220         // over the wire.
221 
222         registry = null;
223         for (int i = 0; i < 10; ++i) {
224             Runtime.getRuntime().gc();
225             if (latch.attempt(1000)) {
226                 break;
227             }
228         }
229         if (latch.attempt(0)) {
230             fail("Connection terminated when there were active proxies");
231         }
232 
233         // clear proxy reference so the connection can be GC'ed
234         service = null;
235 
236         // wait for the notification. Need to force the GC to run...
237         for (int i = 0; i < 10; ++i) {
238             Runtime.getRuntime().gc();
239             if (latch.attempt(1000)) {
240                 break;
241             }
242         }
243         if (!latch.attempt(0)) {
244             fail("CallerListener not notified of disconnection");
245         }
246     }
247 
248    /***
249      * Returns properties for configuring the client ORB.
250      * This configures the default connection pool to reap connections every
251      * 5 seconds.
252      *
253      * @return the properties for configuring the client ORB.
254      */
255     protected Map getClientProperties() {
256        Map properties = super.getClientProperties();
257        return addReapIntervalProperty(properties);
258     }
259 
260     /***
261      * Returns the acceptor properties to use when accepting connections.
262      * This configures the default connection pool to reap connections every
263      * 5 seconds.
264      *
265      * @return the acceptor properties, or <code>null</code> if the default
266      *         connection properties should be used
267      * @throws Exception for any error
268      */
269     protected Map getAcceptorProperties() throws Exception {
270         Map properties = super.getAcceptorProperties();
271         return addReapIntervalProperty(properties);
272     }
273 
274     /***
275      * Adds a 5 second reap interval property to ORB configuration properties.
276      *
277      * @param properties the properties to add to. May be <code>null</code>
278      * @return the updated configuration properties
279      */
280     private Map addReapIntervalProperty(Map properties) {
281         if (properties == null) {
282             properties = new HashMap();
283         }
284         properties.put("org.exolab.jms.net.pool.reapInterval", "5");
285         return properties;
286     }
287 
288     /***
289      * {@link CallbackService} implementation that detects disconnection of its
290      * client.
291      */
292     private static class CallbackServer extends CallbackServiceImpl
293             implements CallerListener {
294 
295         /***
296          * The ORB.
297          */
298         private final ORB _orb;
299 
300         /***
301          * The latch to notify when disconnect() is invoked.
302          */
303         private final Latch _latch;
304 
305         /***
306          * Any exception raised during the test. Should be null.
307          */
308         private Exception _exception;
309 
310 
311         /***
312          * Construct a new <code>CallbackServer</code>.
313          *
314          * @param orb   the ORB to use
315          * @param latch the latch to notify when disconnect() is invoked.
316          */
317         public CallbackServer(ORB orb, Latch latch) {
318             _orb = orb;
319             _latch = latch;
320         }
321 
322         /***
323          * Register a callback.
324          *
325          * @param callback the callback to register
326          */
327         public synchronized void addCallback(Callback callback) {
328             super.addCallback(callback);
329             try {
330                 Caller caller = _orb.getCaller();
331                 _orb.addCallerListener(caller.getRemoteURI().toString(), this);
332             } catch (RemoteException exception) {
333                 _exception = exception;
334             }
335         }
336 
337         /***
338          * Notifies that a caller has been disconnected.
339          *
340          * @param caller the caller that was disconnected
341          */
342         public void disconnected(Caller caller) {
343             try {
344                 _latch.release();
345                 _orb.removeCallerListener(caller.getRemoteURI().toString(),
346                                           this);
347             } catch (RemoteException exception) {
348                 _exception = exception;
349             }
350         }
351 
352         /***
353          * Returns any exception raised.
354          *
355          * @return any exception raised, or <code>null</code>
356          */
357         public Exception getException() {
358             return _exception;
359         }
360     }
361 }