1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.pool;
18  
19  import junit.framework.TestCase;
20  
21  import java.util.List;
22  import java.util.ArrayList;
23  import java.util.NoSuchElementException;
24  
25  import org.apache.commons.pool.impl.GenericKeyedObjectPool;
26  import org.apache.commons.pool.impl.StackKeyedObjectPool;
27  
28  /***
29   * Abstract {@link TestCase} for {@link ObjectPool} implementations.
30   * @author Rodney Waldhoff
31   * @author Sandy McArthur
32   * @version $Revision: 607154 $ $Date: 2007-12-27 18:37:53 -0700 (Thu, 27 Dec 2007) $
33   */
34  public abstract class TestKeyedObjectPool extends TestCase {
35      public TestKeyedObjectPool(String testName) {
36          super(testName);
37      }
38  
39      /***
40       * Create an <code>KeyedObjectPool</code> with the specified factory.
41       * The pool should be in a default configuration and conform to the expected
42       * behaviors described in {@link KeyedObjectPool}.
43       * Generally speaking there should be no limits on the various object counts.
44       */
45      protected abstract KeyedObjectPool makeEmptyPool(KeyedPoolableObjectFactory factory);
46  
47      protected final String KEY = "key";
48  
49      public void testClosedPoolBehavior() throws Exception {
50          final KeyedObjectPool pool;
51          try {
52              pool = makeEmptyPool(new BaseKeyedPoolableObjectFactory() {
53                  public Object makeObject(final Object key) throws Exception {
54                      return new Object();
55                  }
56              });
57          } catch(UnsupportedOperationException uoe) {
58              return; // test not supported
59          }
60  
61          Object o1 = pool.borrowObject(KEY);
62          Object o2 = pool.borrowObject(KEY);
63  
64          pool.close();
65  
66          try {
67              pool.addObject(KEY);
68              fail("A closed pool must throw an IllegalStateException when addObject is called.");
69          } catch (IllegalStateException ise) {
70              // expected
71          }
72  
73          try {
74              pool.borrowObject(KEY);
75              fail("A closed pool must throw an IllegalStateException when borrowObject is called.");
76          } catch (IllegalStateException ise) {
77              // expected
78          }
79  
80          // The following should not throw exceptions just because the pool is closed.
81          assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle(KEY));
82          assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle());
83          pool.getNumActive();
84          pool.getNumActive(KEY);
85          pool.returnObject(KEY, o1);
86          assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle(KEY));
87          assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle());
88          pool.invalidateObject(KEY, o2);
89          pool.clear(KEY);
90          pool.clear();
91          pool.close();
92      }
93  
94      private final Integer ZERO = new Integer(0);
95      private final Integer ONE = new Integer(1);
96  
97      public void testKPOFAddObjectUsage() throws Exception {
98          final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
99          final KeyedObjectPool pool;
100         try {
101             pool = makeEmptyPool(factory);
102         } catch(UnsupportedOperationException uoe) {
103             return; // test not supported
104         }
105         final List expectedMethods = new ArrayList();
106 
107         // addObject should make a new object, pasivate it and put it in the pool
108         pool.addObject(KEY);
109         expectedMethods.add(new MethodCall("makeObject", KEY).returned(ZERO));
110         if (pool instanceof StackKeyedObjectPool) {
111             expectedMethods.add(new MethodCall(
112                     "validateObject", KEY, ZERO).returned(Boolean.TRUE)); 
113         }
114         expectedMethods.add(new MethodCall("passivateObject", KEY, ZERO));
115         assertEquals(expectedMethods, factory.getMethodCalls());
116 
117         //// Test exception handling of addObject
118         reset(pool, factory, expectedMethods);
119 
120         // makeObject Exceptions should be propagated to client code from addObject
121         factory.setMakeObjectFail(true);
122         try {
123             pool.addObject(KEY);
124             fail("Expected addObject to propagate makeObject exception.");
125         } catch (PrivateException pe) {
126             // expected
127         }
128         expectedMethods.add(new MethodCall("makeObject", KEY));
129         assertEquals(expectedMethods, factory.getMethodCalls());
130 
131         clear(factory, expectedMethods);
132 
133         // passivateObject Exceptions should be propagated to client code from addObject
134         factory.setMakeObjectFail(false);
135         factory.setPassivateObjectFail(true);
136         try {
137             pool.addObject(KEY);
138             fail("Expected addObject to propagate passivateObject exception.");
139         } catch (PrivateException pe) {
140             // expected
141         }
142         expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
143         if (pool instanceof StackKeyedObjectPool) {
144             expectedMethods.add(new MethodCall(
145                     "validateObject", KEY, ONE).returned(Boolean.TRUE)); 
146         }
147         expectedMethods.add(new MethodCall("passivateObject", KEY, ONE));
148         assertEquals(expectedMethods, factory.getMethodCalls());
149     }
150 
151     public void testKPOFBorrowObjectUsages() throws Exception {
152         final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
153         final KeyedObjectPool pool;
154         try {
155             pool = makeEmptyPool(factory);
156         } catch(UnsupportedOperationException uoe) {
157             return; // test not supported
158         }
159         final List expectedMethods = new ArrayList();
160         Object obj;
161         
162         if (pool instanceof GenericKeyedObjectPool) {
163             ((GenericKeyedObjectPool) pool).setTestOnBorrow(true);
164         }
165 
166         /// Test correct behavior code paths
167 
168         // existing idle object should be activated and validated
169         pool.addObject(KEY);
170         clear(factory, expectedMethods);
171         obj = pool.borrowObject(KEY);
172         expectedMethods.add(new MethodCall("activateObject", KEY, ZERO));
173         expectedMethods.add(new MethodCall("validateObject", KEY, ZERO).returned(Boolean.TRUE));
174         assertEquals(expectedMethods, factory.getMethodCalls());
175         pool.returnObject(KEY, obj);
176 
177         //// Test exception handling of borrowObject
178         reset(pool, factory, expectedMethods);
179 
180         // makeObject Exceptions should be propagated to client code from borrowObject
181         factory.setMakeObjectFail(true);
182         try {
183             obj = pool.borrowObject(KEY);
184             fail("Expected borrowObject to propagate makeObject exception.");
185         } catch (PrivateException pe) {
186             // expected
187         }
188         expectedMethods.add(new MethodCall("makeObject", KEY));
189         assertEquals(expectedMethods, factory.getMethodCalls());
190 
191 
192         // when activateObject fails in borrowObject, a new object should be borrowed/created
193         reset(pool, factory, expectedMethods);
194         pool.addObject(KEY);
195         clear(factory, expectedMethods);
196 
197         factory.setActivateObjectFail(true);
198         expectedMethods.add(new MethodCall("activateObject", KEY, obj));
199         try {
200             obj = pool.borrowObject(KEY); 
201             fail("Expecting NoSuchElementException");
202         } catch (NoSuchElementException e) {
203             //Activate should fail
204         }
205         // After idle object fails validation, new on is created and activation
206         // fails again for the new one.
207         expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
208         expectedMethods.add(new MethodCall("activateObject", KEY, ONE));
209         TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
210         assertEquals(expectedMethods, factory.getMethodCalls());
211 
212         // when validateObject fails in borrowObject, a new object should be borrowed/created
213         reset(pool, factory, expectedMethods);
214         pool.addObject(KEY);
215         clear(factory, expectedMethods);
216 
217         factory.setValidateObjectFail(true);
218         // testOnBorrow is on, so this will throw when the newly created instance
219         // fails validation
220         try {
221             obj = pool.borrowObject(KEY);
222             fail("Expecting NoSuchElementException");
223         } catch (NoSuchElementException ex) {
224             // expected
225         }
226         // Activate, then validate for idle instance
227         expectedMethods.add(new MethodCall("activateObject", KEY, ZERO));
228         expectedMethods.add(new MethodCall("validateObject", KEY, ZERO));
229         // Make new instance, activate succeeds, validate fails
230         expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
231         expectedMethods.add(new MethodCall("activateObject", KEY, ONE));
232         expectedMethods.add(new MethodCall("validateObject", KEY, ONE));
233         TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
234         assertEquals(expectedMethods, factory.getMethodCalls());
235     }
236 
237     public void testKPOFReturnObjectUsages() throws Exception {
238         final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
239         final KeyedObjectPool pool;
240         try {
241             pool = makeEmptyPool(factory);
242         } catch(UnsupportedOperationException uoe) {
243             return; // test not supported
244         }
245         final List expectedMethods = new ArrayList();
246         Object obj;
247         int idleCount;
248 
249         /// Test correct behavior code paths
250         obj = pool.borrowObject(KEY);
251         clear(factory, expectedMethods);
252 
253         // returned object should be passivated
254         pool.returnObject(KEY, obj);
255         if (pool instanceof StackKeyedObjectPool) {
256             expectedMethods.add(new MethodCall(
257                     "validateObject", KEY, obj).returned(Boolean.TRUE)); 
258         }
259         expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
260         assertEquals(expectedMethods, factory.getMethodCalls());
261 
262         //// Test exception handling of returnObject
263         reset(pool, factory, expectedMethods);
264 
265         // passivateObject should swallow exceptions and not add the object to the pool
266         pool.addObject(KEY);
267         pool.addObject(KEY);
268         pool.addObject(KEY);
269         assertEquals(3, pool.getNumIdle(KEY));
270         obj = pool.borrowObject(KEY);
271         obj = pool.borrowObject(KEY);
272         assertEquals(1, pool.getNumIdle(KEY));
273         assertEquals(2, pool.getNumActive(KEY));
274         clear(factory, expectedMethods);
275         factory.setPassivateObjectFail(true);
276         pool.returnObject(KEY, obj);
277         if (pool instanceof StackKeyedObjectPool) {
278             expectedMethods.add(new MethodCall(
279                     "validateObject", KEY, obj).returned(Boolean.TRUE)); 
280         }
281         expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
282         TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
283         assertEquals(expectedMethods, factory.getMethodCalls());
284         assertEquals(1, pool.getNumIdle(KEY));   // Not added
285         assertEquals(1, pool.getNumActive(KEY)); // But not active
286 
287         reset(pool, factory, expectedMethods);
288         obj = pool.borrowObject(KEY);
289         clear(factory, expectedMethods);
290         factory.setPassivateObjectFail(true);
291         factory.setDestroyObjectFail(true);
292         try {
293             pool.returnObject(KEY, obj);
294             if (!(pool instanceof GenericKeyedObjectPool)) { // ugh, 1.3-compat
295                 fail("Expecting destroyObject exception to be propagated");
296             }
297         } catch (PrivateException ex) {
298             // Expected
299         }
300     }
301 
302     public void testKPOFInvalidateObjectUsages() throws Exception {
303         final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
304         final KeyedObjectPool pool;
305         try {
306             pool = makeEmptyPool(factory);
307         } catch(UnsupportedOperationException uoe) {
308             return; // test not supported
309         }
310         final List expectedMethods = new ArrayList();
311         Object obj;
312 
313         /// Test correct behavior code paths
314 
315         obj = pool.borrowObject(KEY);
316         clear(factory, expectedMethods);
317 
318         // invalidated object should be destroyed
319         pool.invalidateObject(KEY, obj);
320         expectedMethods.add(new MethodCall("destroyObject", KEY, obj));
321         assertEquals(expectedMethods, factory.getMethodCalls());
322 
323         //// Test exception handling of invalidateObject
324         reset(pool, factory, expectedMethods);
325         obj = pool.borrowObject(KEY);
326         clear(factory, expectedMethods);
327         factory.setDestroyObjectFail(true);
328         try {
329             pool.invalidateObject(KEY, obj);
330             fail("Expecting destroy exception to propagate");
331         } catch (PrivateException ex) {
332             // Expected
333         }
334         Thread.sleep(250); // could be defered
335         TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
336         assertEquals(expectedMethods, factory.getMethodCalls());
337     }
338 
339     public void testKPOFClearUsages() throws Exception {
340         final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
341         final KeyedObjectPool pool;
342         try {
343             pool = makeEmptyPool(factory);
344         } catch(UnsupportedOperationException uoe) {
345             return; // test not supported
346         }
347         final List expectedMethods = new ArrayList();
348 
349         /// Test correct behavior code paths
350         PoolUtils.prefill(pool, KEY, 5);
351         pool.clear();
352 
353         //// Test exception handling clear should swallow destory object failures
354         reset(pool, factory, expectedMethods);
355         factory.setDestroyObjectFail(true);
356         PoolUtils.prefill(pool, KEY, 5);
357         pool.clear();
358     }
359 
360     public void testKPOFCloseUsages() throws Exception {
361         final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
362         KeyedObjectPool pool;
363         try {
364             pool = makeEmptyPool(factory);
365         } catch(UnsupportedOperationException uoe) {
366             return; // test not supported
367         }
368         final List expectedMethods = new ArrayList();
369 
370         /// Test correct behavior code paths
371         PoolUtils.prefill(pool, KEY, 5);
372         pool.close();
373 
374 
375         //// Test exception handling close should swallow failures
376         pool = makeEmptyPool(factory);
377         reset(pool, factory, expectedMethods);
378         factory.setDestroyObjectFail(true);
379         PoolUtils.prefill(pool, KEY, 5);
380         pool.close();
381     }
382 
383     public void testToString() throws Exception {
384         final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
385         try {
386             makeEmptyPool(factory).toString();
387         } catch(UnsupportedOperationException uoe) {
388             return; // test not supported
389         }
390     }
391 
392     private void reset(final KeyedObjectPool pool, final FailingKeyedPoolableObjectFactory factory, final List expectedMethods) throws Exception {
393         pool.clear();
394         clear(factory, expectedMethods);
395         factory.reset();
396     }
397 
398     private void clear(final FailingKeyedPoolableObjectFactory factory, final List expectedMethods) {
399         factory.getMethodCalls().clear();
400         expectedMethods.clear();
401     }
402 
403     protected static class FailingKeyedPoolableObjectFactory implements KeyedPoolableObjectFactory {
404         private final List methodCalls = new ArrayList();
405         private int count = 0;
406         private boolean makeObjectFail;
407         private boolean activateObjectFail;
408         private boolean validateObjectFail;
409         private boolean passivateObjectFail;
410         private boolean destroyObjectFail;
411 
412         public FailingKeyedPoolableObjectFactory() {
413         }
414 
415         public void reset() {
416             count = 0;
417             getMethodCalls().clear();
418             setMakeObjectFail(false);
419             setActivateObjectFail(false);
420             setValidateObjectFail(false);
421             setPassivateObjectFail(false);
422             setDestroyObjectFail(false);
423         }
424 
425         public List getMethodCalls() {
426             return methodCalls;
427         }
428 
429         public int getCurrentCount() {
430             return count;
431         }
432 
433         public void setCurrentCount(final int count) {
434             this.count = count;
435         }
436 
437         public boolean isMakeObjectFail() {
438             return makeObjectFail;
439         }
440 
441         public void setMakeObjectFail(boolean makeObjectFail) {
442             this.makeObjectFail = makeObjectFail;
443         }
444 
445         public boolean isDestroyObjectFail() {
446             return destroyObjectFail;
447         }
448 
449         public void setDestroyObjectFail(boolean destroyObjectFail) {
450             this.destroyObjectFail = destroyObjectFail;
451         }
452 
453         public boolean isValidateObjectFail() {
454             return validateObjectFail;
455         }
456 
457         public void setValidateObjectFail(boolean validateObjectFail) {
458             this.validateObjectFail = validateObjectFail;
459         }
460 
461         public boolean isActivateObjectFail() {
462             return activateObjectFail;
463         }
464 
465         public void setActivateObjectFail(boolean activateObjectFail) {
466             this.activateObjectFail = activateObjectFail;
467         }
468 
469         public boolean isPassivateObjectFail() {
470             return passivateObjectFail;
471         }
472 
473         public void setPassivateObjectFail(boolean passivateObjectFail) {
474             this.passivateObjectFail = passivateObjectFail;
475         }
476 
477         public Object makeObject(final Object key) throws Exception {
478             final MethodCall call = new MethodCall("makeObject", key);
479             methodCalls.add(call);
480             int count = this.count++;
481             if (makeObjectFail) {
482                 throw new PrivateException("makeObject");
483             }
484             final Integer obj = new Integer(count);
485             call.setReturned(obj);
486             return obj;
487         }
488 
489         public void activateObject(final Object key, final Object obj) throws Exception {
490             methodCalls.add(new MethodCall("activateObject", key, obj));
491             if (activateObjectFail) {
492                 throw new PrivateException("activateObject");
493             }
494         }
495 
496         public boolean validateObject(final Object key, final Object obj) {
497             final MethodCall call = new MethodCall("validateObject", key, obj);
498             methodCalls.add(call);
499             if (validateObjectFail) {
500                 throw new PrivateException("validateObject");
501             }
502             final boolean r = true;
503             call.returned(new Boolean(r));
504             return r;
505         }
506 
507         public void passivateObject(final Object key, final Object obj) throws Exception {
508             methodCalls.add(new MethodCall("passivateObject", key, obj));
509             if (passivateObjectFail) {
510                 throw new PrivateException("passivateObject");
511             }
512         }
513 
514         public void destroyObject(final Object key, final Object obj) throws Exception {
515             methodCalls.add(new MethodCall("destroyObject", key, obj));
516             if (destroyObjectFail) {
517                 throw new PrivateException("destroyObject");
518             }
519         }
520     }
521 }