Java tutorial
/********************************************************************************** * $URL$ * $Id$ *********************************************************************************** * * 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.net.InetAddress; import java.net.UnknownHostException; import java.util.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.pool.PoolableObjectFactory; import com.novell.ldap.LDAPConnection; import com.novell.ldap.LDAPConstraints; import com.novell.ldap.LDAPException; import com.novell.ldap.LDAPTLSSocketFactory; /** * An object factory for managing <code>PooledLDAPConnection<code>s * within the commons-pool library. Handles creating, configuring, * connecting, securing, and binding the connections as they pass * in and out of the pool. * @see LdapConnectionManager * @see LdapConnectionManagerConfig * @author John Lewis, Unicon Inc */ public class PooledLDAPConnectionFactory implements PoolableObjectFactory { /** Class-specific logger */ private static Log log = LogFactory.getLog(PooledLDAPConnectionFactory.class); /** the controlling connection manager */ private LdapConnectionManager connectionManager; /** connection information */ private String host; private int port; private String binddn; private byte[] bindpw; private boolean autoBind; /** flag to tell us if we are using TLS */ private boolean useTLS; /** standard set of LDAP constraints */ private LDAPConstraints standardConstraints; private LdapConnectionLivenessValidator livenessValidator; public PooledLDAPConnectionFactory() { this.livenessValidator = newDefaultConnectionLivenessValidator(); } /** * Constructs a new PooledLDAPConnection object, including: * passing it the connection manager so it can return itself * to the pool if it falls out of scope, setting a default * set of constraints, connecting to the server, initiating * TLS if needed. Lastly it optionally binds as the "auto-bind * user" and clears the bindAttempted flag so we will know if * a user of the connection attempts to rebind it. */ public Object makeObject() throws LDAPException { if (log.isDebugEnabled()) log.debug("makeObject()"); PooledLDAPConnection conn = newConnection(); if (log.isDebugEnabled()) log.debug("makeObject(): instantiated connection"); conn.setConnectionManager(connectionManager); if (log.isDebugEnabled()) log.debug("makeObject(): assigned connection ConnectionManager"); conn.setConstraints(standardConstraints); if (log.isDebugEnabled()) log.debug("makeObject(): assigned connection constraints"); conn.connect(getHost(), port); if (log.isDebugEnabled()) log.debug("makeObject(): connected connection"); if (useTLS) { if (log.isDebugEnabled()) log.debug("makeObject(): attempting to initiate TLS"); conn.startTLS(); if (log.isDebugEnabled()) log.debug("makeObject(): successfully initiated TLS"); } if (this.autoBind) { if (log.isDebugEnabled()) log.debug("makeObject(): binding connection to default bind DN [" + binddn + "]"); conn.bind(LDAPConnection.LDAP_V3, binddn, bindpw); if (log.isDebugEnabled()) log.debug("makeObject(): successfully bound connection to default bind DN [" + binddn + "]"); } conn.setBindAttempted(false); if (log.isDebugEnabled()) log.debug("makeObject(): reset connection bindAttempted flag"); return conn; } protected PooledLDAPConnection newConnection() { return new PooledLDAPConnection(); } /** * Activates a PooledLDAPConnection as it is being loaned out from the * pool, including: ensuring the default constraints are set and * setting the active flag so that it can return itself to the pool * if it falls out of scope. */ public void activateObject(Object obj) throws LDAPException { if (log.isDebugEnabled()) log.debug("activateObject()"); if (obj instanceof PooledLDAPConnection) { PooledLDAPConnection conn = (PooledLDAPConnection) obj; conn.setConstraints(standardConstraints); if (log.isDebugEnabled()) log.debug("activateObject(): assigned connection constraints"); conn.setActive(true); if (log.isDebugEnabled()) log.debug("activateObject(): set connection active flag"); } else { if (log.isDebugEnabled()) { log.debug("activateObject(): connection not of expected type [" + (obj == null ? "null" : obj.getClass().getName()) + "] nothing to do"); } } } /** * Passivates a PooledLDAPConnection as it is being returned to * the pool, including: clearing the active flag so that it won't * attempt to return itself to the pool. */ public void passivateObject(Object obj) throws LDAPException { if (log.isDebugEnabled()) log.debug("passivateObject()"); if (obj instanceof PooledLDAPConnection) { PooledLDAPConnection conn = (PooledLDAPConnection) obj; conn.setActive(false); if (log.isDebugEnabled()) log.debug("passivateObject(): unset connection active flag"); } else { if (log.isDebugEnabled()) { log.debug("passivateObject(): connection not of expected type [" + (obj == null ? "null" : obj.getClass().getName()) + "] nothing to do"); } } } /** * Validates a PooledLDAPConnection by checking if the connection * is alive and ensuring it is properly bound as the autoBind user. * If a borrower attempted to rebind the connection, then the * bindAttempted flag will be true -- in that case rebind it as * the autoBind user and clear the bindAttempted flag. */ public boolean validateObject(Object obj) { if (log.isDebugEnabled()) log.debug("validateObject()"); if (obj == null) { if (log.isDebugEnabled()) log.debug("validateObject(): received null object reference, returning false"); return false; } if (obj instanceof PooledLDAPConnection) { PooledLDAPConnection conn = (PooledLDAPConnection) obj; if (log.isDebugEnabled()) log.debug("validateObject(): received PooledLDAPConnection object to validate"); // ensure we're always bound as the system user so the liveness // search can succeed (it actually uses the system user's account as // the base DN) if (conn.isBindAttempted()) { if (log.isDebugEnabled()) log.debug("validateObject(): connection bindAttempted flag is set"); if (!(autoBind)) { if (log.isDebugEnabled()) { log.debug( "validateObject(): last borrower attempted bind operation, but no default bind credentials available, invalidating connection"); } conn.setActive(false); if (log.isDebugEnabled()) log.debug( "validateObject(): unset connection bindAttempted flag due to missing default bind credentials, returning false"); return false; } try { if (log.isDebugEnabled()) { log.debug( "validateObject(): last borrower attempted bind operation - rebinding with defaults [bind dn: " + binddn + "]"); } conn.bind(LDAPConnection.LDAP_V3, binddn, bindpw); if (log.isDebugEnabled()) log.debug("validateObject(): successfully bound connection [bind dn: " + binddn + "]"); conn.setBindAttempted(false); if (log.isDebugEnabled()) log.debug("validateObject(): reset connection bindAttempted flag"); } catch (Exception e) { log.error("validateObject(): unable to rebind pooled connection", e); conn.setActive(false); if (log.isDebugEnabled()) log.debug( "validateObject(): unset connection active flag due to bind failure, returning false"); return false; } } if (log.isDebugEnabled()) log.debug("validateObject(): beginning connection liveness testing"); try { if (!(livenessValidator.isConnectionAlive(conn))) { if (log.isInfoEnabled()) log.info("validateObject(): connection failed liveness test, " + conn.getHost()); conn.setActive(false); if (log.isDebugEnabled()) log.debug( "validateObject(): unset connection active flag on stale connection, returning false"); return false; } } catch (Exception e) { log.error("validateObject(): unable to test connection liveness", e); conn.setActive(false); if (log.isDebugEnabled()) log.debug( "validateObject(): unset connection active flag due to liveness test error, returning false"); return false; } } else { // we know the ref is not null if (log.isDebugEnabled()) { log.debug("validateObject(): connection not of expected type [" + obj.getClass().getName() + "] nothing to do"); } } if (log.isDebugEnabled()) { log.debug("validateObject(): connection appears to be valid, returning true"); } return true; } /** * Cleans up a PooledLDAPConnection that is about to be destroyed by * invoking {@link PooledLDAPConnection#disconnect()}. To ensure that the * object is not inadvertently returned to the pool again by a * <code>finalize()</code> call, the connection's active flag is lowered * prior to the <code>disconnect()</code> call. Does nothing but log a pair of * debug messages if the received object is not a {@link PooledLDAPConnection}. */ public void destroyObject(Object obj) throws Exception { if (log.isDebugEnabled()) log.debug("destroyObject()"); if (obj instanceof PooledLDAPConnection) { ((PooledLDAPConnection) obj).setActive(false); ((PooledLDAPConnection) obj).disconnect(); } else { if (log.isDebugEnabled()) { log.debug("destroyObject(): connection not of expected type [" + (obj == null ? "null" : obj.getClass().getName()) + "] nothing to do"); } } } /** * Gives the LdapConnectionMananger that the Factory is using * to configure its PooledLDAPConnections. * @return */ public LdapConnectionManager getConnectionManager() { return connectionManager; } /** Sets the LdapConnectionManager that the Factory will use * to configure and manage its PooledLDAPConnections. This includes * gathering all the connection information (host, port, user, passord), * setting the SocketFactory, determining if we are using TLS, and * creating the default constraints. * @param connectionManager the LdapConnectionManager to use */ public void setConnectionManager(LdapConnectionManager connectionManager) { this.connectionManager = connectionManager; // collect connection information this.host = connectionManager.getConfig().getLdapHost(); this.port = connectionManager.getConfig().getLdapPort(); this.autoBind = connectionManager.getConfig().isAutoBind(); if (this.autoBind) { this.binddn = connectionManager.getConfig().getLdapUser(); try { this.bindpw = connectionManager.getConfig().getLdapPassword().getBytes("UTF8"); } catch (Exception e) { throw new RuntimeException("unable to encode bind password", e); } } // determine if we are using TLS useTLS = (connectionManager.getConfig().isSecureConnection() && (connectionManager.getConfig().getSecureSocketFactory() instanceof LDAPTLSSocketFactory)); // set up the standard constraints standardConstraints = new LDAPConstraints(); standardConstraints.setTimeLimit(connectionManager.getConfig().getOperationTimeout()); standardConstraints.setReferralFollowing(connectionManager.getConfig().isFollowReferrals()); } /** * Assign a strategy for verifying {@link LDAPConnection} * liveness. Defaults to an instance of * {@link NativeLdapConnectionLivenessValidator}. * * @param livenessValidator an object implementing a * liveness validation strategy. Pass <code>null</code> * to force default behavior. */ public void setConnectionLivenessValidator(LdapConnectionLivenessValidator livenessValidator) { if (livenessValidator == null) { livenessValidator = newDefaultConnectionLivenessValidator(); } this.livenessValidator = livenessValidator; } /** * As implemented, will return a new {@link NativeLdapConnectionLivenessValidator}. * * @return the default {@link LdapConnectionLivenessValidator} strategy. */ protected LdapConnectionLivenessValidator newDefaultConnectionLivenessValidator() { return new NativeLdapConnectionLivenessValidator(); } /** * Access the strategy for verifying {@link LDAPConnection} * liveness. Will not return <code>null</code>. * * @return the current connection liveness validator. */ public LdapConnectionLivenessValidator getConnectionLivenessValidator() { return this.livenessValidator; } /** * Get the host to connect to. Attempt to resolve all addresses for a hostname when round robin DNS * is used. * * We do this resolution low down in the stack as we don't want the results cached at all. * Unless configured otherwise the JVM will cache DNS lookups. Even if this is just for 30 seconds * (default in 1.6/1.7) it means when the pool is getting prebuilt at startup the connections will * all point to the same server. * * @return A hostname or a space separated string of IP addresses to connect to. */ protected String getHost() { List<InetAddress> addresses = new ArrayList<InetAddress>(); // The host may already be space separated. StringTokenizer hosts = new StringTokenizer(host, " "); while (hosts.hasMoreTokens()) { try { addresses.addAll(Arrays.asList(InetAddress.getAllByName(hosts.nextToken()))); } catch (UnknownHostException e) { if (log.isDebugEnabled()) { log.debug("Failed to resolve " + host + " not handling now, will deal with later."); } } } if (addresses.size() > 1) { StringBuilder resolvedHosts = new StringBuilder(); // So that we don't always connect to the same host. // Is need on platforms that don't do round robin DNS well. Collections.shuffle(addresses); for (InetAddress address : addresses) { resolvedHosts.append(address.getHostAddress() + " "); } return resolvedHosts.toString(); } else { // Just return the configured hostname and let it be resolved when making the connection. return host; } } }