001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.pool;
018    
019    import junit.framework.TestCase;
020    
021    import java.util.List;
022    import java.util.ArrayList;
023    import java.util.NoSuchElementException;
024    
025    import org.apache.commons.pool.impl.GenericKeyedObjectPool;
026    import org.apache.commons.pool.impl.StackKeyedObjectPool;
027    
028    /**
029     * Abstract {@link TestCase} for {@link ObjectPool} implementations.
030     * @author Rodney Waldhoff
031     * @author Sandy McArthur
032     * @version $Revision: 774099 $ $Date: 2009-05-12 14:29:02 -0700 (Tue, 12 May 2009) $
033     */
034    public abstract class TestKeyedObjectPool extends TestCase {
035        public TestKeyedObjectPool(String testName) {
036            super(testName);
037        }
038    
039        /**
040         * Create an <code>KeyedObjectPool</code> with the specified factory.
041         * The pool should be in a default configuration and conform to the expected
042         * behaviors described in {@link KeyedObjectPool}.
043         * Generally speaking there should be no limits on the various object counts.
044         */
045        protected abstract KeyedObjectPool makeEmptyPool(KeyedPoolableObjectFactory factory);
046    
047        protected final String KEY = "key";
048    
049        public void testClosedPoolBehavior() throws Exception {
050            final KeyedObjectPool pool;
051            try {
052                pool = makeEmptyPool(new BaseKeyedPoolableObjectFactory() {
053                    public Object makeObject(final Object key) throws Exception {
054                        return new Object();
055                    }
056                });
057            } catch(UnsupportedOperationException uoe) {
058                return; // test not supported
059            }
060    
061            Object o1 = pool.borrowObject(KEY);
062            Object o2 = pool.borrowObject(KEY);
063    
064            pool.close();
065    
066            try {
067                pool.addObject(KEY);
068                fail("A closed pool must throw an IllegalStateException when addObject is called.");
069            } catch (IllegalStateException ise) {
070                // expected
071            }
072    
073            try {
074                pool.borrowObject(KEY);
075                fail("A closed pool must throw an IllegalStateException when borrowObject is called.");
076            } catch (IllegalStateException ise) {
077                // expected
078            }
079    
080            // The following should not throw exceptions just because the pool is closed.
081            assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle(KEY));
082            assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle());
083            pool.getNumActive();
084            pool.getNumActive(KEY);
085            pool.returnObject(KEY, o1);
086            assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle(KEY));
087            assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle());
088            pool.invalidateObject(KEY, o2);
089            pool.clear(KEY);
090            pool.clear();
091            pool.close();
092        }
093    
094        private final Integer ZERO = new Integer(0);
095        private final Integer ONE = new Integer(1);
096    
097        public void testKPOFAddObjectUsage() throws Exception {
098            final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
099            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    
248            /// Test correct behavior code paths
249            obj = pool.borrowObject(KEY);
250            clear(factory, expectedMethods);
251    
252            // returned object should be passivated
253            pool.returnObject(KEY, obj);
254            if (pool instanceof StackKeyedObjectPool) {
255                expectedMethods.add(new MethodCall(
256                        "validateObject", KEY, obj).returned(Boolean.TRUE)); 
257            }
258            expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
259            assertEquals(expectedMethods, factory.getMethodCalls());
260    
261            //// Test exception handling of returnObject
262            reset(pool, factory, expectedMethods);
263    
264            // passivateObject should swallow exceptions and not add the object to the pool
265            pool.addObject(KEY);
266            pool.addObject(KEY);
267            pool.addObject(KEY);
268            assertEquals(3, pool.getNumIdle(KEY));
269            obj = pool.borrowObject(KEY);
270            obj = pool.borrowObject(KEY);
271            assertEquals(1, pool.getNumIdle(KEY));
272            assertEquals(2, pool.getNumActive(KEY));
273            clear(factory, expectedMethods);
274            factory.setPassivateObjectFail(true);
275            pool.returnObject(KEY, obj);
276            if (pool instanceof StackKeyedObjectPool) {
277                expectedMethods.add(new MethodCall(
278                        "validateObject", KEY, obj).returned(Boolean.TRUE)); 
279            }
280            expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
281            TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
282            assertEquals(expectedMethods, factory.getMethodCalls());
283            assertEquals(1, pool.getNumIdle(KEY));   // Not added
284            assertEquals(1, pool.getNumActive(KEY)); // But not active
285    
286            reset(pool, factory, expectedMethods);
287            obj = pool.borrowObject(KEY);
288            clear(factory, expectedMethods);
289            factory.setPassivateObjectFail(true);
290            factory.setDestroyObjectFail(true);
291            try {
292                pool.returnObject(KEY, obj);
293                if (!(pool instanceof GenericKeyedObjectPool)) { // ugh, 1.3-compat
294                    fail("Expecting destroyObject exception to be propagated");
295                }
296            } catch (PrivateException ex) {
297                // Expected
298            }
299        }
300    
301        public void testKPOFInvalidateObjectUsages() throws Exception {
302            final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
303            final KeyedObjectPool pool;
304            try {
305                pool = makeEmptyPool(factory);
306            } catch(UnsupportedOperationException uoe) {
307                return; // test not supported
308            }
309            final List expectedMethods = new ArrayList();
310            Object obj;
311    
312            /// Test correct behavior code paths
313    
314            obj = pool.borrowObject(KEY);
315            clear(factory, expectedMethods);
316    
317            // invalidated object should be destroyed
318            pool.invalidateObject(KEY, obj);
319            expectedMethods.add(new MethodCall("destroyObject", KEY, obj));
320            assertEquals(expectedMethods, factory.getMethodCalls());
321    
322            //// Test exception handling of invalidateObject
323            reset(pool, factory, expectedMethods);
324            obj = pool.borrowObject(KEY);
325            clear(factory, expectedMethods);
326            factory.setDestroyObjectFail(true);
327            try {
328                pool.invalidateObject(KEY, obj);
329                fail("Expecting destroy exception to propagate");
330            } catch (PrivateException ex) {
331                // Expected
332            }
333            Thread.sleep(250); // could be defered
334            TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
335            assertEquals(expectedMethods, factory.getMethodCalls());
336        }
337    
338        public void testKPOFClearUsages() throws Exception {
339            final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
340            final KeyedObjectPool pool;
341            try {
342                pool = makeEmptyPool(factory);
343            } catch(UnsupportedOperationException uoe) {
344                return; // test not supported
345            }
346            final List expectedMethods = new ArrayList();
347    
348            /// Test correct behavior code paths
349            PoolUtils.prefill(pool, KEY, 5);
350            pool.clear();
351    
352            //// Test exception handling clear should swallow destory object failures
353            reset(pool, factory, expectedMethods);
354            factory.setDestroyObjectFail(true);
355            PoolUtils.prefill(pool, KEY, 5);
356            pool.clear();
357        }
358    
359        public void testKPOFCloseUsages() throws Exception {
360            final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
361            KeyedObjectPool pool;
362            try {
363                pool = makeEmptyPool(factory);
364            } catch(UnsupportedOperationException uoe) {
365                return; // test not supported
366            }
367            final List expectedMethods = new ArrayList();
368    
369            /// Test correct behavior code paths
370            PoolUtils.prefill(pool, KEY, 5);
371            pool.close();
372    
373    
374            //// Test exception handling close should swallow failures
375            pool = makeEmptyPool(factory);
376            reset(pool, factory, expectedMethods);
377            factory.setDestroyObjectFail(true);
378            PoolUtils.prefill(pool, KEY, 5);
379            pool.close();
380        }
381    
382        public void testToString() throws Exception {
383            final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
384            try {
385                makeEmptyPool(factory).toString();
386            } catch(UnsupportedOperationException uoe) {
387                return; // test not supported
388            }
389        }
390    
391        private void reset(final KeyedObjectPool pool, final FailingKeyedPoolableObjectFactory factory, final List expectedMethods) throws Exception {
392            pool.clear();
393            clear(factory, expectedMethods);
394            factory.reset();
395        }
396    
397        private void clear(final FailingKeyedPoolableObjectFactory factory, final List expectedMethods) {
398            factory.getMethodCalls().clear();
399            expectedMethods.clear();
400        }
401    
402        protected static class FailingKeyedPoolableObjectFactory implements KeyedPoolableObjectFactory {
403            private final List methodCalls = new ArrayList();
404            private int count = 0;
405            private boolean makeObjectFail;
406            private boolean activateObjectFail;
407            private boolean validateObjectFail;
408            private boolean passivateObjectFail;
409            private boolean destroyObjectFail;
410    
411            public FailingKeyedPoolableObjectFactory() {
412            }
413    
414            public void reset() {
415                count = 0;
416                getMethodCalls().clear();
417                setMakeObjectFail(false);
418                setActivateObjectFail(false);
419                setValidateObjectFail(false);
420                setPassivateObjectFail(false);
421                setDestroyObjectFail(false);
422            }
423    
424            public List getMethodCalls() {
425                return methodCalls;
426            }
427    
428            public int getCurrentCount() {
429                return count;
430            }
431    
432            public void setCurrentCount(final int count) {
433                this.count = count;
434            }
435    
436            public boolean isMakeObjectFail() {
437                return makeObjectFail;
438            }
439    
440            public void setMakeObjectFail(boolean makeObjectFail) {
441                this.makeObjectFail = makeObjectFail;
442            }
443    
444            public boolean isDestroyObjectFail() {
445                return destroyObjectFail;
446            }
447    
448            public void setDestroyObjectFail(boolean destroyObjectFail) {
449                this.destroyObjectFail = destroyObjectFail;
450            }
451    
452            public boolean isValidateObjectFail() {
453                return validateObjectFail;
454            }
455    
456            public void setValidateObjectFail(boolean validateObjectFail) {
457                this.validateObjectFail = validateObjectFail;
458            }
459    
460            public boolean isActivateObjectFail() {
461                return activateObjectFail;
462            }
463    
464            public void setActivateObjectFail(boolean activateObjectFail) {
465                this.activateObjectFail = activateObjectFail;
466            }
467    
468            public boolean isPassivateObjectFail() {
469                return passivateObjectFail;
470            }
471    
472            public void setPassivateObjectFail(boolean passivateObjectFail) {
473                this.passivateObjectFail = passivateObjectFail;
474            }
475    
476            public Object makeObject(final Object key) throws Exception {
477                final MethodCall call = new MethodCall("makeObject", key);
478                methodCalls.add(call);
479                int count = this.count++;
480                if (makeObjectFail) {
481                    throw new PrivateException("makeObject");
482                }
483                final Integer obj = new Integer(count);
484                call.setReturned(obj);
485                return obj;
486            }
487    
488            public void activateObject(final Object key, final Object obj) throws Exception {
489                methodCalls.add(new MethodCall("activateObject", key, obj));
490                if (activateObjectFail) {
491                    throw new PrivateException("activateObject");
492                }
493            }
494    
495            public boolean validateObject(final Object key, final Object obj) {
496                final MethodCall call = new MethodCall("validateObject", key, obj);
497                methodCalls.add(call);
498                if (validateObjectFail) {
499                    throw new PrivateException("validateObject");
500                }
501                final boolean r = true;
502                call.returned(new Boolean(r));
503                return r;
504            }
505    
506            public void passivateObject(final Object key, final Object obj) throws Exception {
507                methodCalls.add(new MethodCall("passivateObject", key, obj));
508                if (passivateObjectFail) {
509                    throw new PrivateException("passivateObject");
510                }
511            }
512    
513            public void destroyObject(final Object key, final Object obj) throws Exception {
514                methodCalls.add(new MethodCall("destroyObject", key, obj));
515                if (destroyObjectFail) {
516                    throw new PrivateException("destroyObject");
517                }
518            }
519        }
520    }