blob: 009d628569ba5d3c557dd705304900dc47df2d7d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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 org.apache.cocoon.core.container.spring.avalon;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.cocoon.configuration.PropertyHelper;
import org.apache.cocoon.configuration.Settings;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
/**
* This factory bean adds simple pooling support to Spring.
*
* @since 2.2
* @version $Id$
*/
public class PoolableFactoryBean
implements FactoryBean, BeanFactoryAware {
/** Logger (we use the same logging mechanism as Spring!) */
protected final Log log = LogFactory.getLog(getClass());
/** The default max size of the pool. */
public static final String DEFAULT_MAX_POOL_SIZE = "64";
/** All the interfaces for the proxy. */
protected final Class[] interfaces;
/** The pooled component. */
protected final String name;
/** The class. */
protected final Class beanClass;
/** The corresponding bean factory. */
protected BeanFactory beanFactory;
/**
* Object used to synchronize access to the get and put methods
*/
protected final Object semaphore = new Object();
/**
* The maximum size of the pool.
*/
private final int max;
/**
* List of the Poolable instances which are available for use.
*/
private LinkedList ready;
/**
* Store the size of the ready list to optimize operations which require this value.
*/
private int readySize;
/**
* Total number of Poolable instances in the pool
*/
private int size;
/**
* Total number of Poolable instances created
*/
private int highWaterMark;
/** Pool-in-method-name. */
protected String poolInMethodName;
/** Pool-out-method-name. */
protected String poolOutMethodName;
protected Method poolInMethod;
protected Method poolOutMethod;
/**
* Create a PoolableComponentHandler which manages a pool of Components
* created by the specified factory object.
*
* @param name The name of the bean which should be pooled.
*/
public PoolableFactoryBean( String name, String className )
throws Exception {
this(name, className, DEFAULT_MAX_POOL_SIZE, null);
}
/**
* Create a PoolableComponentHandler which manages a pool of Components
* created by the specified factory object.
*
* @param name The name of the bean which should be pooled.
*/
public PoolableFactoryBean( String name, String className, String poolMaxString, Settings settings )
throws Exception {
String value = poolMaxString;
if ( settings != null ) {
value = PropertyHelper.replace(poolMaxString, settings);
}
int poolMax = Integer.valueOf(value).intValue();
this.name = name;
this.max = ( poolMax <= 0 ? Integer.MAX_VALUE : poolMax );
this.beanClass = Class.forName(className);
final HashSet workInterfaces = new HashSet();
// Get *all* interfaces
this.guessWorkInterfaces( this.beanClass, workInterfaces );
// Add AvalonPoolable
workInterfaces.add(AvalonPoolable.class);
this.interfaces = (Class[]) workInterfaces.toArray( new Class[workInterfaces.size()] );
// Create the pool lists.
this.ready = new LinkedList();
}
public void setPoolInMethodName(String poolInMethodName) {
this.poolInMethodName = poolInMethodName;
}
public void setPoolOutMethodName(String poolOutMethodName) {
this.poolOutMethodName = poolOutMethodName;
}
public void initialize() throws Exception {
if ( this.poolInMethodName != null ) {
this.poolInMethod = this.beanClass.getMethod(this.poolInMethodName, null);
} else {
this.poolInMethod = null;
}
if ( this.poolOutMethodName != null ) {
this.poolOutMethod = this.beanClass.getMethod(this.poolOutMethodName, null);
} else {
this.poolOutMethod = null;
}
}
/**
* Dispose of associated Pools and Factories.
*/
public void dispose() {
// Any Poolables in the ready list need to be disposed of
synchronized( this.semaphore ) {
// Remove objects in the ready list.
for( Iterator iter = this.ready.iterator(); iter.hasNext(); ) {
Object poolable = iter.next();
iter.remove();
this.readySize--;
this.permanentlyRemovePoolable( poolable );
}
}
}
/**
* Permanently removes a poolable from the pool's active list and
* destroys it so that it will not ever be reused.
* <p>
* This method is only called by threads that have m_semaphore locked.
*/
protected void permanentlyRemovePoolable( Object poolable ) {
this.size--;
}
/**
* Gets a Poolable from the pool. If there is room in the pool, a new Poolable will be
* created. Depending on the parameters to the constructor, the method may block or throw
* an exception if a Poolable is not available on the pool.
*
* @return Always returns a Poolable. Contract requires that put must always be called with
* the Poolable returned.
* @throws Exception An exception may be thrown as described above or if there is an exception
* thrown by the ObjectFactory's newInstance() method.
*/
public Object getFromPool() throws Exception {
Object poolable;
synchronized( this.semaphore ) {
// Look for a Poolable at the end of the m_ready list
if ( this.readySize > 0 ){
// A poolable is ready and waiting in the pool
poolable = this.ready.removeLast();
this.readySize--;
} else {
// Create a new poolable. May throw an exception if the poolable can not be
// instantiated.
poolable = this.beanFactory.getBean(this.name);
this.size++;
this.highWaterMark = (this.highWaterMark < this.size ? this.size : this.highWaterMark);
}
}
this.exitingPool(poolable);
return poolable;
}
/**
* Returns a poolable to the pool
*
* @param poolable Poolable to return to the pool.
*/
public void putIntoPool( final Object poolable ) {
this.enteringPool(poolable);
synchronized( this.semaphore ) {
if( this.size <= this.max ) {
this.ready.addLast( poolable );
this.readySize++;
} else {
// More Poolables were created than can be held in the pool, so remove.
this.permanentlyRemovePoolable( poolable );
}
}
}
/**
* Handle service specific methods for getting it out of the pool
*/
protected void exitingPool( final Object component )
throws Exception {
if ( this.poolOutMethod != null ) {
this.poolOutMethod.invoke(component, null);
}
}
/**
* Handle service specific methods for putting it into the pool
*/
protected void enteringPool( final Object component ) {
try {
// Handle Recyclable objects
if( component instanceof Recyclable ) {
( (Recyclable)component ).recycle();
}
if ( this.poolInMethod != null ) {
this.poolInMethod.invoke(component, null);
}
} catch (Exception ignore) {
// we ignore exceptions during putting the component back
// into the pool, as this should not bring the system
// down - this is for compatibility with the original
// Avalon implementation
this.log.error("Exception while putting component '" + component + "' back into the pool.", ignore);
}
}
/**
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
*/
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/**
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
public Object getObject() throws Exception {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
this.interfaces,
new PoolableProxyHandler(this));
}
/**
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class getObjectType() {
return this.beanClass;
}
/**
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
public boolean isSingleton() {
return false;
}
/**
* Get a list of interfaces to proxy by scanning through
* all interfaces a class implements.
*
* @param clazz the class
* @param workInterfaces the set of current work interfaces
*/
private void guessWorkInterfaces( final Class clazz,
final Set workInterfaces ) {
if ( null != clazz ) {
this.addInterfaces( clazz.getInterfaces(), workInterfaces );
this.guessWorkInterfaces( clazz.getSuperclass(), workInterfaces );
}
}
/**
* Get a list of interfaces to proxy by scanning through
* all interfaces a class implements.
*
* @param classInterfaces the array of interfaces
* @param workInterfaces the set of current work interfaces
*/
private void addInterfaces( final Class[] classInterfaces,
final Set workInterfaces ) {
for ( int i = 0; i < classInterfaces.length; i++ ) {
workInterfaces.add( classInterfaces[i] );
this.addInterfaces(classInterfaces[i].getInterfaces(), workInterfaces);
}
}
}