/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package netscape.ldap.util; import java.util.*; import netscape.ldap.*; /** * Class to maintain a pool of individual connections to the * same server. Specify the initial size and the max size * when constructing a pool. Call getConnection() to obtain * a connection from the pool and close() to return it. If * the pool is fully extended and there are no free connections, * getConnection() blocks until a connection has been returned * to the pool.
* Call destroy() to release all connections. *

Example:
*
 * ConnectionPool pool = null;
 * try {
 *     pool = new ConnectionPool( 10, 30,
 *                                "foo.acme.com",389,
 *                                "uid=me, o=acme.com",
 *                                "password" );
 * } catch ( LDAPException e ) {
 *    System.err.println( "Unable to create connection pool" );
 *    System.exit( 1 );
 * }
 * while ( clientsKnocking ) {
 *     String filter = getSearchFilter();
 *     LDAPConnection ld = pool.getConnection();
 *     try {
 *         LDAPSearchResults res = ld.search( BASE, ld.SCOPE_SUB,
 *                                            filter, attrs,
 *                                            false );
 *         pool.close( ld );
 *         while( res.hasMoreElements() ) {
 *             ...
 *
*/ /** * Connection pool, typically used by a server to avoid creating * a new connection for each client * * @version 1.2 **/ public class ConnectionPool { /** * Constructor for specifying all parameters * * @param min initial number of connections * @param max maximum number of connections * @param host hostname of LDAP server * @param port port number of LDAP server * @param authdn DN to authenticate as * @param authpw password for authentication * @exception LDAPException on failure to create connections */ public ConnectionPool( int min, int max, String host, int port, String authdn, String authpw ) throws LDAPException { this( min, max, host, port, authdn, authpw, null ); } /** * Constructor for specifying all parameters, anonymous * identity * * @param min initial number of connections * @param max maximum number of connections * @param host hostname of LDAP server * @param port port number of LDAP server * @exception LDAPException on failure to create connections */ public ConnectionPool( int min, int max, String host, int port) throws LDAPException { this( min, max, host, port, "", ""); } /** * Constructor for using default parameters, anonymous identity * * @param host hostname of LDAP server * @param port port number of LDAP server * @exception LDAPException on failure to create connections */ public ConnectionPool( String host, int port ) throws LDAPException { // poolsize=10,max=20,host,port, // noauth,nopswd this( 10, 20, host, port, "", "" ); } /** * Constructor for using an existing connection to clone * from. *

* The connection to clone must be already established and * the user authenticated. * * @param min initial number of connections * @param max maximum number of connections * @param ldc connection to clone * @exception LDAPException on failure to create connections */ public ConnectionPool( int min, int max, LDAPConnection ldc ) throws LDAPException { this( min, max, ldc.getHost(), ldc.getPort(), ldc.getAuthenticationDN(), ldc.getAuthenticationPassword(), (LDAPConnection)ldc.clone() ); } /* * Constructor for using an existing connection to clone * from * * @param min initial number of connections * @param max maximum number of connections * @param host hostname of LDAP server * @param port port number of LDAP server * @param authdn DN to authenticate as * @param authpw password for authentication * @param ldc connection to clone * @exception LDAPException on failure to create connections */ private ConnectionPool( int min, int max, String host, int port, String authdn, String authpw, LDAPConnection ldc ) throws LDAPException { this.poolSize = min; this.poolMax = max; this.host = host; this.port = port; this.authdn = authdn; this.authpw = authpw; this.ldc = ldc; this.debugMode = false; createPool(); } /** * Destroy the whole pool - called during a shutdown */ public void destroy() { for ( int i = 0; i < pool.size(); i++ ) { disconnect( (LDAPConnectionObject)pool.elementAt(i) ); } pool.removeAllElements(); } /** * Gets a connection from the pool * * If no connections are available, the pool will be * extended if the number of connections is less than * the maximum; if the pool cannot be extended, the method * blocks until a free connection becomes available. * * @return an active connection. */ public LDAPConnection getConnection() { LDAPConnection con; while( (con = getConnFromPool()) == null ) { synchronized( pool ) { try { pool.wait(); } catch ( InterruptedException e ) { } } } return con; } /** * Gets a connection from the pool within a time limit. * * If no connections are available, the pool will be * extended if the number of connections is less than * the maximum; if the pool cannot be extended, the method * blocks until a free connection becomes available or the * time limit is exceeded. * * @param timeout timeout in milliseconds * @return an active connection or null if timed out. */ public LDAPConnection getConnection(int timeout) { LDAPConnection con; while( (con = getConnFromPool()) == null ) { long t1, t0 = System.currentTimeMillis(); if (timeout <= 0) { return con; } synchronized( pool ) { try { pool.wait(timeout); } catch ( InterruptedException e ) { return null; } } t1 = System.currentTimeMillis(); timeout -= (t1 - t0); } return con; } /** * Gets a connection from the pool * * If no connections are available, the pool will be * extended if the number of connections is less than * the maximum; if the pool cannot be extended, the method * returns null. * * @return an active connection or null. */ protected synchronized LDAPConnection getConnFromPool() { LDAPConnection con = null; LDAPConnectionObject ldapconnobj = null; int pSize = pool.size(); // Get an available connection for ( int i = 0; i < pSize; i++ ) { // Get the ConnectionObject from the pool LDAPConnectionObject co = (LDAPConnectionObject)pool.elementAt(i); if ( co.isAvailable() ) { // Conn available? ldapconnobj = co; break; } } if ( ldapconnobj == null ) { // If there there were no conns in pool, can we grow // the pool? if ( (poolMax < 0) || ( (poolMax > 0) && (pSize < poolMax)) ) { // Yes we can grow it int i = addConnection(); // If a new connection was created, use it if ( i >= 0 ) { ldapconnobj = (LDAPConnectionObject)pool.elementAt(i); } } else { debug("All pool connections in use"); } } if ( ldapconnobj != null ) { ldapconnobj.setInUse( true ); // Mark as in use con = ldapconnobj.getLDAPConn(); } return con; } /** * This is our soft close - all we do is mark * the connection as available for others to use. * We also reset the auth credentials in case * they were changed by the caller. * * @param ld a connection to return to the pool */ public synchronized void close( LDAPConnection ld ) { int index = find( ld ); if ( index != -1 ) { LDAPConnectionObject co = (LDAPConnectionObject)pool.elementAt(index); // Reset the auth if necessary if (ldc == null || !ldc.getAuthenticationMethod().equals("sasl")) { boolean reauth = false; //if user bound anon then getAuthenticationDN is null if ( ld.getAuthenticationDN() == null ) { reauth = (authdn != null); } else if ( !ld.getAuthenticationDN().equalsIgnoreCase(authdn) ) { reauth = true; } if (reauth) { try { debug("user changed credentials-resetting"); ld.bind(authdn,authpw); //reauth as proper user } catch (LDAPException e) { debug("unable to reauth during close as "+authdn); debug(e.toString()); } } } co.setInUse( false ); // Mark as available synchronized( pool ) { pool.notifyAll(); } } } /** * Debug method to print the contents of the pool */ public void printPool(){ System.out.println("--ConnectionPool--"); for ( int i = 0; i < pool.size(); i++ ) { LDAPConnectionObject co = (LDAPConnectionObject)pool.elementAt(i); System.out.println( "" + i + "=" + co ); } } private void disconnect( LDAPConnectionObject ldapconnObject ) { if ( ldapconnObject != null ) { if (ldapconnObject.isAvailable()) { LDAPConnection ld = ldapconnObject.getLDAPConn(); if ( (ld != null) && (ld.isConnected()) ) { try { ld.disconnect(); } catch (LDAPException e) { debug("disconnect: "+e.toString()); } } ldapconnObject.setLDAPConn(null); // Clear conn } } } private void createPool() throws LDAPException { // Called by the constructors if ( poolSize <= 0 ) { throw new LDAPException("ConnectionPoolSize invalid"); } if ( poolMax < poolSize ) { debug("ConnectionPoolMax is invalid, set to " + poolSize); poolMax = poolSize; } debug("****Initializing LDAP Pool****"); debug("LDAP host = "+host+" on port "+port); debug("Number of connections="+poolSize); debug("Maximum number of connections="+poolMax); debug("******"); pool = new java.util.Vector(); // Create pool vector setUpPool( poolSize ); // Initialize it } private int addConnection() { int index = -1; debug("adding a connection to pool..."); try { int size = pool.size() + 1; // Add one connection setUpPool( size ); if ( size == pool.size() ) { // New size is size requested? index = size - 1; } } catch (Exception ex) { debug("Adding a connection: "+ex.toString()); } return index; } private synchronized void setUpPool( int size ) throws LDAPException { // Loop on creating connections while( pool.size() < size ) { LDAPConnectionObject co = new LDAPConnectionObject(); // Make LDAP connection, using template if available LDAPConnection newConn = (ldc != null) ? (LDAPConnection)ldc.clone() : new LDAPConnection(); co.setLDAPConn(newConn); try { if ( newConn.isConnected() ) { // If using a template, then reconnect // to create a separate physical connection newConn.reconnect(); } else { // Not using a template, so connect with // simple authentication using ldap v3 try { newConn.connect( 3, host, port, authdn, authpw); } catch (LDAPException connEx) { // fallback to ldap v2 if v3 is not supported if (connEx.getLDAPResultCode() == connEx.PROTOCOL_ERROR) { newConn.connect( 2, host, port, authdn, authpw); } else { throw connEx; } } } } catch ( LDAPException le ) { debug("Creating pool:"+le.toString()); debug("aborting...."); throw le; } co.setInUse( false ); // Mark not in use pool.addElement( co ); } } private int find( LDAPConnection con ) { // Find the matching Connection in the pool if ( con != null ) { for ( int i = 0; i < pool.size(); i++ ) { LDAPConnectionObject co = (LDAPConnectionObject)pool.elementAt(i); if ( co.getLDAPConn() == con ) { return i; } } } return -1; } /** * Sets the debug printout mode. * * @param mode debug mode to use */ public synchronized void setDebug( boolean mode ) { debugMode = mode; } /** * Reports the debug printout mode. * * @return debug mode in use. */ public boolean getDebug() { return debugMode; } private void debug( String s ) { if ( debugMode ) System.out.println("ConnectionPool ("+ new Date()+") : " + s); } private void debug(String s, boolean severe) { if ( debugMode || severe ) { System.out.println("ConnectionPool ("+ new Date()+") : " + s); } } /** * Wrapper for LDAPConnection object in pool */ class LDAPConnectionObject{ /** * Returns the associated LDAPConnection. * * @return the LDAPConnection. * */ LDAPConnection getLDAPConn() { return this.ld; } /** * Sets the associated LDAPConnection * * @param ld the LDAPConnection * */ void setLDAPConn( LDAPConnection ld ) { this.ld = ld; } /** * Marks a connection in use or available * * @param inUse true to mark in use, false if available * */ void setInUse( boolean inUse ) { this.inUse = inUse; } /** * Returns whether the connection is available * for use by another user. * * @return true if available. */ boolean isAvailable() { return !inUse; } /** * Debug method * * @return s user-friendly rendering of the object. */ public String toString() { return "LDAPConnection=" + ld + ",inUse=" + inUse; } private LDAPConnection ld; // LDAP Connection private boolean inUse; // In use? (true = yes) } private int poolSize; // Min pool size private int poolMax; // Max pool size private String host; // LDAP host private int port; // Port to connect at private String authdn; // Identity of connections private String authpw; // Password for authdn private LDAPConnection ldc = null; // Connection to clone private java.util.Vector pool; // the actual pool private boolean debugMode; }