Package org.jboss.jca.core.connectionmanager.pool

Source Code of org.jboss.jca.core.connectionmanager.pool.ManagedConnectionPool

/*
* JBoss, Home of Professional Open Source.
* Copyright 2008-2009, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.jca.core.connectionmanager.pool;

import org.jboss.jca.common.JBossResourceException;
import org.jboss.jca.core.connectionmanager.listener.ConnectionListener;
import org.jboss.jca.core.connectionmanager.listener.ConnectionListenerFactory;
import org.jboss.jca.core.connectionmanager.listener.ConnectionState;
import org.jboss.jca.core.connectionmanager.pool.api.PoolConfiguration;
import org.jboss.jca.core.connectionmanager.pool.idle.IdleConnectionRemovalSupport;
import org.jboss.jca.core.connectionmanager.pool.idle.IdleRemover;
import org.jboss.jca.core.connectionmanager.pool.validator.ConnectionValidator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.resource.ResourceException;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.RetryableUnavailableException;
import javax.resource.spi.ValidatingManagedConnectionFactory;
import javax.security.auth.Subject;

import org.jboss.logging.Logger;
import org.jboss.util.UnreachableStatementException;


/**
* Actual internal managed connection pool.
*
* <p>
* Contains and manages the {@link ConnectionListener} instances.
* Each pool strategy can contains several {@link SubPoolContext} instance
* that contains {@link ManagedConnectionPool} internally.
* </p>
*
* <p>
* Each internal managed connection pool instances could be
* differentiated by a key.
* </p>
* @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
* @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
* @author <a href="mailto:weston.price@jboss.com">Weston Price</a>
* @author <a href="mailto:jesper.pedersen@jboss.org">Jesper Pedersen</a>
* @author <a href="mailto:gurkanerdogdu@yahoo.com">Gurkan Erdogdu</a>
* @see AbstractPool
*/
public class ManagedConnectionPool implements IdleConnectionRemovalSupport
{
   /** The log */
   private static Logger log = Logger.getLogger(ManagedConnectionPool.class);  
  
   /** Whether trace is enabled */
   private final boolean trace = log.isTraceEnabled();

   /** The managed connection factory */
   private final ManagedConnectionFactory mcf;

   /** The connection listener factory */
   private final ConnectionListenerFactory clf;

   /** The default subject */
   private final Subject defaultSubject;

   /** The default connection request information */
   private final ConnectionRequestInfo defaultCri;

   /** The pool configuration */
   private final PoolConfiguration poolConfiguration;

   /** Copy of the maximum size from the pooling parameters.
    * Dynamic changes to this value are not compatible with
    * the semaphore which cannot change be dynamically changed.
    */
   private int maxSize;

   /** The available connection event listeners */
   private CopyOnWriteArrayList<ConnectionListener> cls = new CopyOnWriteArrayList<ConnectionListener>();

   /** The permits used to control who can checkout a connection */
   private final Semaphore permits;

   /** The checked out connections */
   private final CopyOnWriteArraySet<ConnectionListener> checkedOut = new CopyOnWriteArraySet<ConnectionListener>();

   /** Whether the pool has been started */
   private AtomicBoolean started = new AtomicBoolean(false);

   /** Whether the pool has been shutdown */
   private AtomicBoolean shutdown = new AtomicBoolean(false);

   /** the max connections ever checked out **/
   private volatile int maxUsedConnections = 0;
  
   /**
    * Create a new ManagedConnectionPool.
    *
    * @param mcf the managed connection factory
    * @param clf the connection listener factory
    * @param subject the subject
    * @param cri the connection request info
    * @param pc the pool configuration
    */
   public ManagedConnectionPool(ManagedConnectionFactory mcf, ConnectionListenerFactory clf, Subject subject,
                                ConnectionRequestInfo cri, PoolConfiguration pc)
   {
      if (mcf == null)
         throw new IllegalArgumentException("MCF is null");

      if (clf == null)
         throw new IllegalArgumentException("CLF is null");

      if (pc == null)
         throw new IllegalArgumentException("PoolConfiguration is null");

      this.mcf = mcf;
      this.clf = clf;
      this.defaultSubject = subject;
      this.defaultCri = cri;
      this.poolConfiguration = pc;
      this.maxSize = pc.getMaxSize();
      this.permits = new Semaphore(this.maxSize, true);
 
      if (pc.isPrefill())
      {
         PoolFiller.fillPool(this);
      }
   }
  
   /**
    * Returns a connection listener that wraps managed connection.
    * @param subject subject
    * @param cri connection request info
    * @return connection listener wrapped managed connection
    * @throws ResourceException exception
    */
   public ConnectionListener getConnection(Subject subject, ConnectionRequestInfo cri) throws ResourceException
   {
      ConnectionListener connectionListener = null;
     
      if (subject == null)
      {
         subject = this.defaultSubject;
      }
     
      if (cri == null)
      {
         cri = this.defaultCri;
      }
     
      //Use in blocked time
      long startWait = System.currentTimeMillis();
     
      try
      {
         //Check connection is available, and if not waits for the blocking timeout
         if (this.permits.tryAcquire(this.poolConfiguration.getBlockingTimeout(), TimeUnit.MILLISECONDS))
         {
            do
            {
               //Check shutdown
               if (this.shutdown.get())
               {
                  permits.release();
                  throw new RetryableUnavailableException("The pool has been shut down");
               }
              
               if (cls.size() > 0)
               {
                  connectionListener = this.cls.remove(this.cls.size() - 1);
                  this.checkedOut.add(connectionListener);
                 
                  //Max used connections, maxSize - permits.aval --> gives current used connection!
                  int size = (maxSize - permits.availablePermits());
                  if (size > maxUsedConnections)
                  {
                     maxUsedConnections = size; 
                  }
                 
                  if (connectionListener != null)
                  {
                     try
                     {
                        //Match connection
                        ConnectionListener matchedConnectionListener =
                           isManagedConnectionMatched(connectionListener, subject, cri);
                       
                        //Connection matched
                        if (matchedConnectionListener != null)
                        {
                           connectionListener = matchedConnectionListener;
                           break;
                        }
                       
                        //Match did not succeed but no exception was thrown.
                        //Either we have the matching strategy wrong or the
                        //connection died while being checked.  We need to
                        //distinguish these cases, but for now we always
                        //destroy the connection.
                        log.warn("Destroying connection that could not be successfully matched: " + connectionListener);
                        removesAndDestorysConnectionListener(connectionListener);
                       
                     }
                     catch (Throwable t)
                     {
                        log.warn("Throwable while trying to match ManagedConnection,destroying connection: "
                              + connectionListener, t);
                        removesAndDestorysConnectionListener(connectionListener);
                     }
                    
                     //We made it here, something went wrong and we should validate if
                     //we should continue attempting to acquire a connection
                     if (this.poolConfiguration.isUseFastFail())
                     {
                        log.trace("Fast failing for connection attempt. No more attempts will " +
                              "be made to acquire connection from pool and a new connection " +
                              "will be created immeadiately");
                        break;
                     }
                    
                  } //connectionListener != null
               } //cls.size > 0              
            }
            while (this.cls.size() > 0);
           
            //Check connection
            if (connectionListener == null)
            {
               //Ok, no connection in the pool. Creates a new managed connection instance!
               connectionListener = createsNewManagedConnection(subject, cri);              
            }
           
         }
         else
         {
            // we timed out
            throw new ResourceException("No ManagedConnections available within configured blocking timeout ( "
                  + this.poolConfiguration.getBlockingTimeout() + " [ms] )");           
         }
        
      }
      catch (InterruptedException e)
      {
         long end = System.currentTimeMillis() - startWait;
         throw new ResourceException("Interrupted while requesting permit! Waited " + end + " ms");        
      }
     
      return connectionListener;
   }
  
   /**
    * Removes and destroys given connection.
    * @param connectionListener connection listener
    */
   private void removesAndDestorysConnectionListener(ConnectionListener connectionListener)
   {
      this.checkedOut.remove(connectionListener);
     
      //Destroy it
      doDestroy(connectionListener);                             
   }
  
   /**
    * Returns given listener if there is a matched connection false ow.
    * @param connectionListener connection listener
    * @param subject subject
    * @param cri connection request info
    * @return true if there is a matched connection false ow.
    */
   private ConnectionListener isManagedConnectionMatched(ConnectionListener connectionListener,
         Subject subject, ConnectionRequestInfo crithrows ResourceException  
   {
      ManagedConnection managedConnection = connectionListener.getManagedConnection();
      managedConnection = this.mcf.matchManagedConnections(Collections.singleton(managedConnection), subject , cri);
     
      //There is a match
      if (managedConnection != null)
      {
         if (trace)
         {
            log.trace("supplying ManagedConnection from pool: " + connectionListener)
         }
        
         connectionListener.grantPermit(true);
        
         return connectionListener;
      }
     
      return null;
   }
  
   /**
    * Creates a new connection listener.
    * @param subject subject instance
    * @param cri connection request info
    * @return new connection listener
    * @throws ResourceException
    */
   private ConnectionListener createsNewManagedConnection(Subject subject, ConnectionRequestInfo cri)
      throws ResourceException
   {
      ConnectionListener cl = null;
      try
      {
         //No, the pool was empty, so we have to make a new one.
         cl = createsConnectionEventListener(subject, cri);

         checkedOut.add(cl);
         int size = (maxSize - permits.availablePermits());
         if (size > maxUsedConnections)
         {
            maxUsedConnections = size; 
         }

         if (!started.get())
         {
            started.set(true);
            if (poolConfiguration.getMinSize() > 0)
            {
               PoolFiller.fillPool(this)
            }
         }

         if (trace)
            log.trace("supplying new ManagedConnection: " + cl)
        
         cl.grantPermit(true);
        
         return cl;
      }
      catch (Throwable t)
      {
         log.warn("Throwable while attempting to get a new connection: " + cl, t);
         //return permit and rethrow
        
         checkedOut.remove(cl);
         permits.release();
         JBossResourceException.rethrowAsResourceException("Unexpected throwable while trying to create a connection: "
               + cl, t);
         throw new UnreachableStatementException();
      }     
   }
  
  
   /**
    * Create a connection event listener
    *
    * @param subject the subject
    * @param cri the connection request information
    * @return the new listener
    * @throws ResourceException for any error
    */
   private ConnectionListener createsConnectionEventListener(Subject subject, ConnectionRequestInfo cri)
      throws ResourceException
   {
      ManagedConnection mc = mcf.createManagedConnection(subject, cri);
      try
      {
         return clf.createConnectionListener(mc, this);
      }
      catch (ResourceException re)
      {
         mc.destroy();
         throw re;
      }
   }
  
  
   /**
    * Destroy a connection
    *
    * @param cl the connection to destroy
    */
   private void doDestroy(ConnectionListener cl)
   {
      if (cl.getState() == ConnectionState.DESTROYED)
      {
         log.trace("ManagedConnection is already destroyed " + cl);
         return;
      }

      cl.setState(ConnectionState.DESTROYED);
     
      try
      {
         cl.getManagedConnection().destroy();
      }
      catch (Throwable t)
      {
         log.debug("Exception destroying ManagedConnection " + cl, t);
      }

   }  
  
   /**
    * {@inheritDoc}
    */
   public void removeIdleConnections()
   {
      ArrayList<ConnectionListener> destroy = null;
      long timeout = System.currentTimeMillis() - poolConfiguration.getIdleTimeout();
     
      while (true)
      {
         // Nothing left to destroy
         if (cls.size() == 0)
            break;

         // Check the first in the list
         ConnectionListener cl = cls.get(0);
         if (cl.isTimedOut(timeout) && shouldRemove())
         {
            // We need to destroy this one
            cls.remove(0);
            if (destroy == null)
            {
               destroy = new ArrayList<ConnectionListener>()
            }
           
            destroy.add(cl);
         }
         else
         {
            //They were inserted chronologically, so if this one isn't timed out, following ones won't be either.
            break;
         }
      }

      // We found some connections to destroy
      if (destroy != null)
      {
         for (int i = 0; i < destroy.size(); ++i)
         {
            ConnectionListener cl = destroy.get(i);
            if (trace)
            {
               log.trace("Destroying timedout connection " + cl)
            }
           
            doDestroy(cl);
         }

         // We destroyed something, check the minimum.
         if (!shutdown.get() && poolConfiguration.getMinSize() > 0)
         {
            PoolFiller.fillPool(this)
         }

//         // Empty sub-pool
//         if (jmcp != null)
//         {
//            jmcp.getPoolingStrategy().emptySubPool(this); 
//         }
      }
     
   }
  
   /**
    * Returns true if check is ok.
    * @return true if check is ok.
    */
   private boolean shouldRemove()
   {     
      boolean remove = true;
     
      if (this.poolConfiguration.isStrictMin())
      {
         remove = cls.size() > poolConfiguration.getMinSize();
        
         log.trace("StrictMin is active. Current connection will be removed is " + remove);
        
      }
     
      return remove;
     
   }
  
  
   /**
    * Initialize the subpool
    */
   public void initialize()
   {
      if (this.poolConfiguration.getIdleTimeout() != 0L)
      {
         //Register removal support
         IdleRemover.registerPool(this, this.poolConfiguration.getIdleTimeout());
      }
     
      if (this.poolConfiguration.getBackgroundValidationInterval() > 0)
      {
         log.debug("Registering for background validation at interval " +
               this.poolConfiguration.getBackgroundValidationInterval());        
        
         //Register validation
         ConnectionValidator.registerPool(this, this.poolConfiguration.getBackgroundValidationInterval());
      }

      shutdown.set(false);     
   }
  
   /**
    * Return connection to the pool.
    * @param cl connection listener
    * @param kill kill connection
    */
   public void returnConnection(ConnectionListener cl, boolean kill)
   {
      if (cl.getState().equals(ConnectionState.DESTROYED))
      {
         returnConnectionWithDestroyedState(cl);
         return;
      }
     
      if (trace)
      {
         log.trace("putting ManagedConnection back into pool kill=" + kill + " cl=" + cl);
      }
     
      returnConnectionWithKillState(cl, kill);
     
   }
  
   /**
    * Connection is returned with destroyed state.
    * @param cl connection listener
    */
   private void returnConnectionWithDestroyedState(ConnectionListener cl)
   {
      if (this.trace)
      {
         log.trace("ManagedConnection is being returned after it was destroyed" + cl);
      }
     
      if (cl.hasPermit())
      {
         cl.grantPermit(false);
         this.permits.release();
      }           
   }
  
   /**
    * Connection is returned with destroyed state.
    * @param cl connection listener
    */
   private void returnConnectionWithKillState(ConnectionListener cl, boolean kill)
   {
      try
      {
         cl.getManagedConnection().cleanup();
      }
      catch (ResourceException re)
      {
         log.warn("ResourceException cleaning up ManagedConnection: " + cl, re);
         kill = true;
      }
     
      // We need to destroy this one
      if (cl.getState().equals(ConnectionState.DESTROY))
      {
         kill = true;
         checkedOut.remove(cl);
      }

      // This is really an error
      if (!kill && cls.size() >= poolConfiguration.getMaxSize())
      {
         log.warn("Destroying returned connection, maximum pool size exceeded " + cl);
         kill = true;
      }

      // If we are destroying, check the connection is not in the pool
      if (kill)
      {
         // Adrian Brock: A resource adapter can asynchronously notify us that
         // a connection error occurred.
         // This could happen while the connection is not checked out.
         // e.g. JMS can do this via an ExceptionListener on the connection.
         // I have twice had to reinstate this line of code, PLEASE DO NOT REMOVE IT!
         cls.remove(cl);
      }
      // return to the pool
      else
      {
         cl.used();
         if (!cls.contains(cl))
         {
            cls.add(cl)
         }
         else
         {
            log.warn("Attempt to return connection twice (ignored): " + cl, new Throwable("STACKTRACE"))
         }
      }

      if (cl.hasPermit())
      {
         // release semaphore
         cl.grantPermit(false);
         permits.release();
      }

      if (kill)
      {
         if (trace)
         {
            log.trace("Destroying returned connection " + cl)
         }
        
         doDestroy(cl);
      }
     
   }

   /**
    * Pool is shut down.
    */
   public void shutdown()
   {
      shutdown.set(true);
     
      //Unregister from idle check
      IdleRemover.unregisterPool(this);
     
      //Unregister from connection validation check
      ConnectionValidator.unregisterPool(this);
     
      //Destroy connections
      flush();
   }
  
   /**
    * Flush pool.
    */
   public void flush()
   {
      ArrayList<ConnectionListener> destroyList = new ArrayList<ConnectionListener>();
     
      if (this.trace)
      {
         log.trace("Flushing pool checkedOut=" + checkedOut + " inPool=" + cls);
      }
     
      Iterator<ConnectionListener> itCheckOut = this.checkedOut.iterator();
      ConnectionListener listener = null;
      while (itCheckOut.hasNext())
      {
         listener = itCheckOut.next();
         listener.setState(ConnectionState.DESTROY);
      }
     
      itCheckOut = this.cls.iterator();
      while (itCheckOut.hasNext())
      {
         listener = itCheckOut.next();
         destroyList.add(listener);
      }
     
      for (ConnectionListener listenerDestroy : destroyList)
      {
         if (this.trace)
         {
            log.trace("Destroying flushed connection " + listenerDestroy);
         }
        
         doDestroy(listenerDestroy);
      }
     
      // We destroyed something, check the minimum.
      if (!shutdown.get() && poolConfiguration.getMinSize() > 0)
      {
         PoolFiller.fillPool(this)
      }     
   }  
  
   /**
    * Checks that pool is empty or not
    * @return true if is emtpy false otherwise
    */
   boolean isEmpty()
   {
      return this.cls.size() == 0;
   }
  
   /**
    * Gets connection listeners.
    * @return connection listeners
    */
   Set<ConnectionListener> getConnectionListeners()
   {
      Set<ConnectionListener> cls = new HashSet<ConnectionListener>();
      Iterator<ConnectionListener> it = this.cls.iterator();
      while (it.hasNext())
      {
         cls.add(it.next());
      }
     
      it = this.checkedOut.iterator();
      while (it.hasNext())
      {
         cls.add(it.next());
      }
     
      return cls;
   }
  
   /**
    * Returns true if pool is not shut down.
    * @return true if pool is not shut down
    */
   public boolean isRunning()
   {
      return !shutdown.get();
   }
  
   /**
    * Fill to min.
    */
   public void fillToMin()
   {
      while (true)
      {
         // Get a permit - avoids a race when the pool is nearly full
         // Also avoids unnessary fill checking when all connections are checked out
         try
         {
            if (permits.tryAcquire(poolConfiguration.getBlockingTimeout(), TimeUnit.MILLISECONDS))
            {
               try
               {
                  //pool shuts down
                  if (shutdown.get())
                  {
                     return
                  }

                  // We already have enough connections -- TODO
                  //if (getMinSize() - connectionCounter.getGuaranteedCount() <= 0)
                  //{
                  return
                  //}

                  /* -- TODO
                  // Create a connection to fill the pool
                  try
                  {
                     ConnectionListener cl = createsConnectionEventListener(defaultSubject, defaultCri);
                     if (trace)
                     {
                        log.trace("Filling pool cl=" + cl); 
                     }
                    
                     cls.add(cl);
                  }
                  catch (ResourceException re)
                  {
                     log.warn("Unable to fill pool ", re);
                     return;
                  }
                  */
               }
               finally
               {
                  permits.release();
               }
            }
         }
         catch (InterruptedException ignored)
         {
            log.trace("Interrupted while requesting permit in fillToMin");
         }
      }     
   }
  
   /**
    * Guard against configurations or
    * dynamic changes that may increase the minimum
    * beyond the maximum
    */
   private int getMinSize()
   {
      if (this.poolConfiguration.getMinSize() > this.maxSize)
      {
         return maxSize; 
      }
     
      return this.poolConfiguration.getMinSize();
   }
  
   /**
    * Validate connecitons.
    * @throws Exception for exception
    */
   @SuppressWarnings("unchecked")
   public void validateConnections() throws Exception
   {
      if (this.trace)
      {
         log.trace("Attempting to  validate connections for pool " + this)
      }

      if (this.permits.tryAcquire(this.poolConfiguration.getBlockingTimeout(), TimeUnit.MILLISECONDS))
      {
         boolean destroyed = false;
         try
         {
            while (true)
            {
               ConnectionListener cl = null;
               if (cls.size() == 0)
               {
                  break;
               }

               cl = removeForFrequencyCheck();

               if (cl == null)
               {
                  break;
               }

               try
               {
                  Set<ManagedConnection> candidateSet = Collections.singleton(cl.getManagedConnection());
                  if (mcf instanceof ValidatingManagedConnectionFactory)
                  {
                     ValidatingManagedConnectionFactory vcf = (ValidatingManagedConnectionFactory) mcf;
                     candidateSet = vcf.getInvalidConnections(candidateSet);

                     if (candidateSet != null && candidateSet.size() > 0)
                     {
                        if (!cl.getState().equals(ConnectionState.DESTROY))
                        {
                           doDestroy(cl);
                           destroyed = true;
                        }
                     }
                  }
                  else
                  {
                     log.warn("warning: background validation was specified with a " +
                           "non compliant ManagedConnectionFactory interface.");
                  }
               }
               finally
               {
                  if (!destroyed)
                  {
                     returnForFrequencyCheck(cl);
                  }
               }
            }
         }
         finally
         {
            permits.release();
           
            //Check min size pool after validation
            if (destroyed && !shutdown.get() && poolConfiguration.getMinSize() > 0)
            {
               PoolFiller.fillPool(this);
            }
         }
      }
   }
  
   /**
    * Remove for frequency check.
    * @return connection listener
    */
   private ConnectionListener removeForFrequencyCheck()
   {
      log.debug("Checking for connection within frequency");
      ConnectionListener cl = null;
      for (Iterator<ConnectionListener> iter = cls.iterator(); iter.hasNext();)
      {
         cl = iter.next();
         long lastCheck = cl.getLastValidatedTime();

         if ((System.currentTimeMillis() - lastCheck) >= poolConfiguration.getBackgroundValidationInterval())
         {
            cls.remove(cl);
            break;
         }
         else
         {
            cl = null;
         }
      }
     
      return cl;
   }
  
   /**
    * Returns connection to pool again.
    * @param cl connection listener
    */
   private void returnForFrequencyCheck(ConnectionListener cl)
   {

      log.debug("Returning for connection within frequency");

      cl.setLastValidatedTime(System.currentTimeMillis());
      cls.add(cl);
   }

}
TOP

Related Classes of org.jboss.jca.core.connectionmanager.pool.ManagedConnectionPool

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.