//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library.tools;
import java.util.*;
/**
* A LoadBalancer manages a free pool of <i>Baton</i> objects representing the
* right of a thread to perform a resource-intensive task. For example, you
* could create a LoadBalancer that represents the task "query all students"
* and assign it an initial pool of 5 Batons, meaning at most 5 threads will be
* able to carry out this task at once. A thread must check out a Baton in
* order to perform the task, and must release it back to the LoadBalancer
* when finished.<p>
*
* Refer to the Baton class for a description of how to use the LoadBalancer
* and Baton classes and the LoadBalancerListener interface. These classes can
* be used to introduce internal load balancing into an agent to significantly
* improve scalability when connecting to tens or hundreds of zones
* concurrently.<p>
*
* @author Edustructures LLC
* @version ADK 1.0
*/
public class LoadBalancer
{
public static boolean TRACE = false;
/**
* LoadBalancer ID
*/
protected Object fID;
/**
* Pool of batons
*/
protected Stack fPool = new Stack();
/**
* Maximum number of Batons
*/
protected int fSize;
/**
* checkoutBaton timeout value
*/
protected long fTimeout;
/**
* Global dictionary of LoadBalancers
* @see #define
*/
protected static HashMap sDict = new HashMap();
/**
* Flagged true when the pool is emptied
*/
protected boolean fEmptied = false;
/**
* Listeners
*/
protected Vector fListeners = null;
/**
* Constructs a LoadBalancer to represent a specific logical task.<p>
*
* @param id A unique arbitrary ID that the agent will use to request this
* LoadBalancer (e.g. "Request_StudentPersonal")
* @param batons The number of Batons that will be available to threads
* @param timeout The timeout period (in milliseconds) applied to the
* <code>checkoutBaton</code> method. The timeout period should be less
* than the HTTP or other transport timeout period so that the connection
* to the ZIS does not timeout before the load balancer does.
*/
public LoadBalancer( Object id, int batons, long timeout )
{
if( id == null )
throw new IllegalArgumentException("id cannot be null");
fID = id;
fTimeout = timeout;
fSize = batons;
// Create pool of batons
int _batons = Math.max(1,batons);
for( int i = 0; i < _batons; i++ )
fPool.push( new Baton() );
}
/**
* Define a LoadBalancer that may be subsequently returned by the <code>lookup</code> method.<p>
* @param balancer A LoadBalancer instance
*/
public static void define( LoadBalancer balancer )
{
synchronized( sDict ) {
sDict.put( balancer.fID, balancer );
}
}
/**
* Lookup a LoadBalancer that was previously defined by the <code>define</code> method.<p>
* @param id The ID of the LoadBalancer to obtain
* @return The LoadBalancer or null if no LoadBalancer with this id has been
* previously defined by the <code>define</code> method
*/
public static LoadBalancer lookup( Object id )
{
synchronized( sDict ) {
return (LoadBalancer)sDict.get( id );
}
}
/**
* Check-out a Baton.
*/
public Baton checkoutBaton()
{
Baton b = null;
synchronized( fPool )
{
if( fPool.size() == 0 )
{
try
{
// Wait for an instance to become available
if( TRACE )
System.out.println("Waiting for baton to become available (" + Thread.currentThread().getName() + ")");
fPool.wait( fTimeout );
if( TRACE )
System.out.println("Done waiting for baton to become available (" + Thread.currentThread().getName() + ")");
}
catch( InterruptedException ie )
{
}
}
if( fPool.size() > 0 )
b = (Baton)fPool.pop();
if( TRACE )
System.out.println( b == null ? "No baton available" : "Got a baton (there are " + fPool.size() + " left)" );
if( fPool.size() == 0 )
fEmptied = true;
}
return b;
}
/**
* Check-in a Baton.
*/
public void checkinBaton( Baton baton )
{
if( baton != null )
{
synchronized( fPool )
{
fPool.push( baton );
if( TRACE )
System.out.println("Baton checked in; there are now "+fPool.size());
if( fEmptied && fPool.size() >= ( fSize == 1 ? 1 : 2 ) )
{
if( TRACE )
System.out.println("Notifying listeners that batons are now available");
// Notify all listeners that Batons are once again available
fEmptied = false;
if( fListeners != null ) {
while(fListeners.size() > 0) {
LoadBalancerListener l = (LoadBalancerListener)fListeners.remove(0);
l.onBatonsAvailable( this );
}
}
}
try
{
// Notify all waiting threads that a Baton is available
if( TRACE )
System.out.println("Notifying threads that baton is returned to free pool");
fPool.notifyAll();
} catch( Throwable thr ) {
}
}
}
}
/**
* Gets the current load (the number of Batons in use)
*/
public int getLoad()
{
synchronized( fPool ) {
return fSize - fPool.size();
}
}
/**
* Gets the total number of Batons
*/
public int getTotalBatons()
{
return fSize;
}
/**
* Gets the number of Batons
*/
public int getFreeBatons()
{
synchronized( fPool ) {
return fPool.size();
}
}
/**
* Register a LoadBalancerListener with this LoadBalancer. The listener will
* be called when the free pool is empty and subsequently contains at least
* two Batons (or one Baton if this LoadBalancer was defined to have a pool
* size of one).
*/
public void addLoadBalancerListener( LoadBalancerListener listener )
{
if( fListeners == null )
fListeners = new Vector();
fListeners.addElement( listener );
}
}