/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/providers/trunk/jldap/src/test/edu/amc/sakai/user/GenericObjectPoolTest.java $ * $Id: GenericObjectPoolTest.java 105079 2012-02-24 23:08:11Z ottenhoff@longsight.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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 edu.amc.sakai.user; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; import org.jmock.Mock; import org.jmock.MockObjectTestCase; /** * This class exists to demonstrate lifecycle method firing from * {@link GenericObjectPool}. It was specififcally motivated by * problems with stale {@link PooledLDAPConnection} objects * being returned to the pool. * * <p>The fixture is intended to mimic the pool created by * {@link PoolingLdapConnectionManager#init()} and new tests * should probably be created if that behavior/configuration * changes.</p> * * <p>Again, keep in mind that this class exists purely * for demonstation purposes, i.e. to learn about the pooling * library, not to verify LDAP provider behaviors. Also keep * in mind that pool behaviors, including lifecycle method * invocation, are highly dependent on configuration, so * the behaviors verified in this class may not hold for * a different fixture configuration</p> * * @author Dan McCallum (dmccallum@unicon.net) * */ public class GenericObjectPoolTest extends MockObjectTestCase { private GenericObjectPool pool; private PoolableObjectFactory factory; private Mock mockFactory; protected void setUp() throws Exception { mockFactory = new Mock(PoolableObjectFactory.class); factory = (PoolableObjectFactory)mockFactory.proxy(); pool = new GenericObjectPool(factory, 1, // maxActive GenericObjectPool.WHEN_EXHAUSTED_BLOCK, // whenExhaustedAction 60000, // maxWait (millis) 1, // maxIdle true, // testOnBorrow false // testOnReturn ); super.setUp(); } /** * Verifies that {@link GenericObjectPool#borrowObject()} fires * {@link PoolableObjectFactory#makeObject()}, * {@link PoolableObjectFactory#activateObject(Object)}, * and {@link PoolableObjectFactory#validateObject(Object)}, in that order. * * @throws Exception test error */ public void testBorrowObjectFiresMakeActivateAndValidate() throws Exception { Object pooledObject = new Object(); // expectations are implemented as a stack, so are searched in the reverse // order from which they were created mockFactory.expects(once()).method("validateObject").with(same(pooledObject)).will(returnValue(true)); mockFactory.expects(once()).method("activateObject").with(same(pooledObject)); mockFactory.expects(once()).method("makeObject").will(returnValue(pooledObject)); Object borrowedObject = pool.borrowObject(); assertSame("Unexpected object returned from pool", pooledObject, borrowedObject); } /** * Verifies that {@link GenericObjectPool} makes no subsequent calls * to {@link PoolableObjectFactory#makeObject()} following a failed * {@link PoolableObjectFactory#validateObject(Object)} <em>on a newly * created poolable object</em>. This was unexpected behavior -- * initially this was a test case that verified the pool falling back * to alloc a second new object when validation on a new object failed. * It makes sense -- without a retry limit, the pool could fall into * an endless alloc-validate loop. * * <p>Note that this test also verifies that the pool fires * {@link PoolableObjectFactory#destroyObject(Object)} when validation * fails</p> * * @throws Exception test error */ public void testWillNotRetryMakeObjectOnFailedValidate() throws Exception { Object pooledObject1 = new Object(); mockFactory.expects(once()).method("makeObject"). will(returnValue(pooledObject1)); mockFactory.expects(once()).method("activateObject"). with(same(pooledObject1)).after("makeObject"); mockFactory.expects(once()).method("validateObject"). with(same(pooledObject1)).after("activateObject").will(returnValue(false)); mockFactory.expects(once()).method("destroyObject"). with(same(pooledObject1)).after("validateObject"); try { pool.borrowObject(); fail("Should failed to borrow object"); } catch ( Exception e ) { // success } // rely on JMock to validate mockFactory } /** * Similar to {@link #testWillNotRetryMakeObjectOnFailedValidate()} but * verifies that the pool attempts new object creation when validation * fails on an object <em>already present in the pool</em>. * * @throws Exception test error */ public void testAttemptsToPoolNewObjectOnFailedValidationOfPooledObject() throws Exception { Object pooledObject1 = new Object(); Object pooledObject2 = new Object(); // expectations are implemented as a stack, so are searched in the reverse // order from which they were create mockFactory.expects(once()).method("validateObject"). with(same(pooledObject2)).will(returnValue(true)); mockFactory.expects(once()).method("activateObject").with(same(pooledObject2)); // alloc a second object mockFactory.expects(once()).method("makeObject"). will(returnValue(pooledObject2)); // make sure the original object is destroyed mockFactory.expects(once()).method("destroyObject").with(same(pooledObject1)); // a second borrow should reactivate and reverify the original object -- we'll // fail the validation to force alloc of a new poolable object mockFactory.expects(once()).method("validateObject"). with(same(pooledObject1)).will(returnValue(false)); mockFactory.expects(once()).method("activateObject").with(same(pooledObject1)); // client adds the object back to the pool mockFactory.expects(once()).method("passivateObject").with(same(pooledObject1)); // pass the validation, pooledObject1 should be in the pool mockFactory.expects(once()).method("validateObject"). with(same(pooledObject1)).will(returnValue(true)); mockFactory.expects(once()).method("activateObject").with(same(pooledObject1)); mockFactory.expects(once()).method("makeObject"). will(returnValue(pooledObject1)); Object borrowedObject1 = pool.borrowObject(); // something of a sanity check assertSame("Unexpected object returned from pool", pooledObject1, borrowedObject1); pool.returnObject(borrowedObject1); Object borrowedObject2 = pool.borrowObject(); // now make sure we got a branch new object assertSame("Unexpected object returned from pool", pooledObject2, borrowedObject2); } /** * Verifies a suspicion that if a null reference is returned to the pool, the pool * will return that reference to subsequent {@link GenericObjectPool#borrowObject()} * calls, so long as the object passes activation and validation lifecycle phases. * * @throws Exception */ public void testPoolAllowsNullObjectReferencesToBeReturnedAndSubsequentlyBorrowed() throws Exception { mockFactory.expects(once()).method("passivateObject").with(NULL); mockFactory.expects(once()).method("activateObject").with(NULL).after("passivateObject"); // this is really the important expectation -- the underlying factory must be // implemented such that it fails to identify "null" objects as invalid mockFactory.expects(once()).method("validateObject").with(NULL).after("activateObject").will(returnValue(true)); pool.returnObject(null); // the code exercise assertNull(pool.borrowObject()); } }