/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/providers/trunk/jldap/src/test/edu/amc/sakai/user/PooledLDAPConnectionFactoryTest.java $
* $Id: PooledLDAPConnectionFactoryTest.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 java.io.UnsupportedEncodingException;
import org.jmock.Mock;
import org.jmock.cglib.MockObjectTestCase;
import com.novell.ldap.LDAPConnection;
import com.novell.ldap.LDAPEntry;
import com.novell.ldap.LDAPException;
import com.novell.ldap.LDAPJSSEStartTLSFactory;
import com.novell.ldap.LDAPSearchResults;
import com.novell.ldap.LDAPTLSSocketFactory;
/**
* Currently verifies a subset of {@link PooledLDAPConnectionFactory}
* features. Specifically, is concerned with verifying fixes to
* object validation which were causing stale connections to remain
* in the pool indefinitely.
*
*
* @author Dan McCallum (dmccallum@unicon.net)
*/
public class PooledLDAPConnectionFactoryTest extends MockObjectTestCase {
private PooledLDAPConnectionFactory factory;
private PooledLDAPConnection conn;
private LDAPSearchResults ldapSearchResults;
private LDAPEntry ldapEntry;
private LdapConnectionLivenessValidator livenessValidator;
private LdapConnectionManager connMgr;
private LdapConnectionManagerConfig connMgrConfig;
private Mock mockConn;
private Mock mockLDAPSearchResults;
private Mock mockLDAPEntry;
private Mock mockLivenessValidator;
private Mock mockConnMgr;
private Mock mockConnMgrConfig;
protected void setUp() throws Exception {
mockConn = mock(PooledLDAPConnection.class, "mockConn");
conn = (PooledLDAPConnection)mockConn.proxy();
factory = new PooledLDAPConnectionFactory() {
@Override
protected PooledLDAPConnection newConnection() {
return conn;
}
};
mockLDAPSearchResults = mock(LDAPSearchResults.class,"mockLDAPSearchResults");
ldapSearchResults = (LDAPSearchResults)mockLDAPSearchResults.proxy();
mockLDAPEntry = mock(LDAPEntry.class, "mockLDAPEntry");
ldapEntry = (LDAPEntry)mockLDAPEntry.proxy();
mockLivenessValidator = new Mock(LdapConnectionLivenessValidator.class);
livenessValidator = (LdapConnectionLivenessValidator)mockLivenessValidator.proxy();
factory.setConnectionLivenessValidator(livenessValidator);
mockConnMgr = new Mock(LdapConnectionManager.class);
connMgr = (LdapConnectionManager) mockConnMgr.proxy();
mockConnMgrConfig = new Mock(LdapConnectionManagerConfig.class);
connMgrConfig = (LdapConnectionManagerConfig) mockConnMgrConfig.proxy();
// don't call setConnectionManager() b/c we don't know what expectations to set
super.setUp();
}
/**
* Verifies that {@link PooledLDAPConnectionFactory} defaults
* its {@link LdapConnectionLivenessValidator} instance at
* construction time,
*/
public void testDefaultsToNativeLdapConnectionLivenessValidator() {
Object livenessValidator = new PooledLDAPConnectionFactory().getConnectionLivenessValidator();
assertTrue("Expected a NativeLdapConnectionLivenessValidator but was [" + livenessValidator + "]",
livenessValidator instanceof NativeLdapConnectionLivenessValidator);
}
/**
* Verifies that {@link PooledLDAPConnectionFactory} is never
* without a {@link LdapConnectionLivenessValidator}.
*/
public void testDefaultsToNativeLdapConnectionLivenessValidatorIfThatPropertySetToNull() {
factory.setConnectionLivenessValidator(null);
Object livenessValidator = factory.getConnectionLivenessValidator();
assertTrue("Expected a NativeLdapConnectionLivenessValidator but was [" + livenessValidator + "]",
livenessValidator instanceof NativeLdapConnectionLivenessValidator);
}
public void testMakeObjectConnectsAndOtherwiseInitializesConnections()
throws LDAPException, UnsupportedEncodingException {
setConnectionManagerConfigExpectations();
factory.setConnectionManager(connMgr);
mockConn.expects(once()).method("setConnectionManager").with(same(connMgr));
mockConn.expects(once()).method("setConstraints").with(NOT_NULL); // TODO make more specific
mockConn.expects(once()).method("connect").
with(eq(connMgrConfig.getLdapHost()), eq(connMgrConfig.getLdapPort())).
after("setConstraints");
mockConn.expects(once()).method("startTLS").after("connect");
mockConn.expects(once()).method("bind").
with(eq(LDAPConnection.LDAP_V3),
eq(connMgrConfig.getLdapUser()),
eq(connMgrConfig.getLdapPassword().getBytes("UTF-8"))).after("connect");
mockConn.expects(once()).method("setBindAttempted").with(eq(false)).after("bind");
// the actual code exercise
assertSame(conn, factory.makeObject());
// TODO how to test socket factory assignment (static call)
}
private void setConnectionManagerConfigExpectations()
{
final String LDAP_HOST = "ldap-host";
final int LDAP_PORT = 389;
final String LDAP_USER = "ldap-user";
final String LDAP_PASS = "ldap-pass";
final LDAPTLSSocketFactory LDAP_SOCKET_FACTORY = new LDAPJSSEStartTLSFactory();
final int LDAP_TIMEOUT = 5000;
final boolean LDAP_FOLLOW_REFERRALS = true;
mockConnMgr.expects(atLeastOnce()).method("getConfig").will(returnValue(connMgrConfig));
mockConnMgrConfig.expects(atLeastOnce()).method("getLdapHost").will(returnValue(LDAP_HOST));
mockConnMgrConfig.expects(atLeastOnce()).method("getLdapPort").will(returnValue(LDAP_PORT));
mockConnMgrConfig.expects(atLeastOnce()).method("isAutoBind").will(returnValue(true));
mockConnMgrConfig.expects(atLeastOnce()).method("getLdapUser").will(returnValue(LDAP_USER));
mockConnMgrConfig.expects(atLeastOnce()).method("getLdapPassword").will(returnValue(LDAP_PASS));
mockConnMgrConfig.expects(atLeastOnce()).method("getSecureSocketFactory").will(returnValue(LDAP_SOCKET_FACTORY));
mockConnMgrConfig.expects(atLeastOnce()).method("isSecureConnection").will(returnValue(true));
mockConnMgrConfig.expects(atLeastOnce()).method("getOperationTimeout").will(returnValue(LDAP_TIMEOUT));
mockConnMgrConfig.expects(atLeastOnce()).method("isFollowReferrals").will(returnValue(LDAP_FOLLOW_REFERRALS));
}
/**
* Verifies that {@link PooledLDAPConnectionFactory#activateObject(Object)}
* passes constraints and an active flag to the given {@link PooledLDAPConnection}.
* Does not actually verify constraint values.
*
* @throws LDAPException test error
*/
public void testActivateObject() throws LDAPException {
// TODO validate constraint assignment
mockConn.expects(once()).method("setConstraints").with(ANYTHING);
mockConn.expects(once()).method("setActive").with(eq(true));
factory.activateObject(conn);
}
/**
* If the client has bound the connection but the factory is not
* running in auto-bind mode, the connection must be invalidated.
*/
public void testValidateObjectLowersActiveFlagIfConnectionHasBeenBoundButAutoBindIsNotSet() {
mockConn.expects(once()).method("isBindAttempted").will(returnValue(true));
// we assume autoBind defaults to false (we'd rather not go through the
// whole process of mocking up the connection manager again)
mockConn.expects(once()).method("setActive").with(eq(false));
assertFalse(factory.validateObject(conn));
}
/**
* Verifies that the {@link PooledLdapConnection} to be validated is re-bound
* as the system user prior to testing the connection for liveness. This
* is only relevant if the client has bound the connection as another user and
* the autoBind behavior has been enabled.
*/
public void testValidateObjectRebindsAsAutoBindUserIfNecessaryPriorToTestingConnectionLiveness()
throws UnsupportedEncodingException {
setConnectionManagerConfigExpectations();
factory.setConnectionManager(connMgr);
mockConn.expects(once()).method("isBindAttempted").will(returnValue(true));
mockConn.expects(once()).method("bind").
with(eq(LDAPConnection.LDAP_V3),
eq(connMgrConfig.getLdapUser()),
eq(connMgrConfig.getLdapPassword().getBytes("UTF-8"))).after("isBindAttempted");
mockConn.expects(once()).method("setBindAttempted").with(eq(false)).after("bind");
mockLivenessValidator.expects(once()).method("isConnectionAlive").will(returnValue(true));
factory.setConnectionLivenessValidator(livenessValidator);
assertTrue(factory.validateObject(conn));
}
/**
* Verifies that a failed rebind during connection validation marks
* the connection as "inactive". This ensures that the connection is
* not returned to the pool by {@link PooledLDAPConnection#finalize()}.
* Technically, a failed rebind does not mean the connection is stale
* but failing to bind as the system user should indicate that
* something is very much wrong, so reallocating a connection is not
* a bad idea. Certainly better than finding oneself caught in an
* endless loop of validating stale connections.
*
*/
public void testValidateObjectLowersActiveFlagIfRebindFails()
throws UnsupportedEncodingException {
setConnectionManagerConfigExpectations();
factory.setConnectionManager(connMgr);
LDAPException bindFailure = new LDAPException();
mockConn.expects(once()).method("isBindAttempted").will(returnValue(true));
mockConn.expects(once()).method("bind").
with(eq(LDAPConnection.LDAP_V3),
eq(connMgrConfig.getLdapUser()),
eq(connMgrConfig.getLdapPassword().getBytes("UTF-8"))).
after("isBindAttempted").will(throwException(bindFailure));
mockConn.expects(once()).method("setActive").with(eq(false)).after("bind");
assertFalse(factory.validateObject(conn));
}
/**
* Verifies that {@link PooledLDAPConnectionFactory#validateObject(Object)}
* does not adjust a {@link PooledLDAPConnection}'s active flag if the
* connection appears to be "alive". Probably overkill, but we've had problems
* that we think may be related to stale connections being returned to the
* pool, so we want to be sure the validate operation is doing exactly what
* we think it should be doing. Also verifies that a {@link PooledLDAPConnection}'s
* search method returns a LDAPEntry.
*
* @throws LDAPException
*/
public void testValidateObjectKeepsActiveFlagUpIfConnectionIsAlive() throws LDAPException {
// will fail if the factory attempts to monkey with the active flag
factory.setConnectionLivenessValidator(livenessValidator);
mockConn.expects(once()).method("isBindAttempted").will(returnValue(false));
mockLivenessValidator.expects(once()).method("isConnectionAlive").will(returnValue(true));
assertTrue(factory.validateObject(conn));
}
/**
* Verifies that {@link PooledLDAPConnectionFactory#validateObject(Object)} lowers
* {@link PooledLDAPConnection}'s active flag if the current
* {@link LdapConnectionLivenessValidator} reports that the connection is not live.
*
* @throws LDAPException test failure
*/
public void testValidateObjectLowersActiveFlagIfConnectionIsNotAlive() throws LDAPException {
factory.setConnectionLivenessValidator(livenessValidator);
mockConn.expects(once()).method("isBindAttempted").will(returnValue(false));
mockLivenessValidator.expects(once()).method("isConnectionAlive").
will(returnValue(false));
mockConn.expects(once()).method("setActive").with(eq(false)).
after(mockLivenessValidator, "isConnectionAlive");
assertFalse(factory.validateObject(conn));
}
public void testValidateObjectLowersActiveFlagIfLivenessValidationThrowsException() {
factory.setConnectionLivenessValidator(livenessValidator);
mockConn.expects(once()).method("isBindAttempted").will(returnValue(false));
mockLivenessValidator.expects(once()).method("isConnectionAlive").will(throwException(new RuntimeException("catch me")));
mockConn.expects(once()).method("setActive").with(eq(false)).after(mockLivenessValidator, "isConnectionAlive");
assertFalse(factory.validateObject(conn));
}
public void testInvalidatesNullObjects() {
assertFalse(factory.validateObject(null));
}
public void testDestroyObjectInvokesDisconnect() throws Exception {
mockConn.expects(once()).method("setActive").with(eq(false));
mockConn.expects(once()).method("disconnect").after("setActive");
factory.destroyObject(conn);
}
public void testDestroyObjectIgnoresNullReferences() {
try {
factory.destroyObject(null);
} catch ( Exception e ) {
fail("Should have ignored null reference");
}
}
public void testDestroyObjectIgnoresNonPoolableLdapConnections() {
try {
factory.destroyObject(mock(LDAPConnection.class).proxy());
} catch ( Exception e ) {
fail("Should have ignored non-poolable connection");
}
}
}