/** * EasyBeans * Copyright (C) 2009-2012 Bull S.A.S. * Contact: easybeans@ow2.org * * This library 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 any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * -------------------------------------------------------------------------- * $Id: SingletonSessionFactory.java 5747 2011-02-28 17:12:27Z benoitf $ * -------------------------------------------------------------------------- */ package org.ow2.easybeans.container.session.singleton; import static org.ow2.easybeans.api.OperationState.BUSINESS_METHOD; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.ejb.NoSuchEJBException; import javax.ejb.Timer; import org.ow2.easybeans.api.EZBContainer; import org.ow2.easybeans.api.FactoryException; import org.ow2.easybeans.api.OperationState; import org.ow2.easybeans.api.bean.EasyBeansSingletonSB; import org.ow2.easybeans.api.bean.info.IAccessTimeoutInfo; import org.ow2.easybeans.api.bean.info.IApplicationExceptionInfo; import org.ow2.easybeans.api.bean.info.ILockTypeInfo; import org.ow2.easybeans.api.bean.info.IMethodInfo; import org.ow2.easybeans.api.event.bean.EZBEventBeanInvocation; import org.ow2.easybeans.container.session.JPoolWrapperFactory; import org.ow2.easybeans.container.session.PoolWrapper; import org.ow2.easybeans.container.session.SessionFactory; import org.ow2.easybeans.container.session.stateful.ConcurrentBuilderException; import org.ow2.easybeans.event.bean.EventBeanInvocationEnd; import org.ow2.easybeans.event.bean.EventBeanInvocationError; import org.ow2.easybeans.naming.J2EEManagedObjectNamingHelper; import org.ow2.easybeans.rpc.JEJBResponse; import org.ow2.easybeans.rpc.api.EJBLocalRequest; import org.ow2.easybeans.rpc.api.EJBResponse; import org.ow2.easybeans.rpc.api.RPCException; import org.ow2.util.auditreport.api.IAuditID; import org.ow2.util.log.Log; import org.ow2.util.log.LogFactory; import org.ow2.util.pool.api.PoolException; import org.ow2.util.pool.impl.JPool; import org.ow2.util.pool.impl.enhanced.EnhancedPool; import org.ow2.util.pool.impl.enhanced.PoolConfiguration; import org.ow2.util.pool.impl.enhanced.manager.optional.IPoolItemRemoveManager; /** * Defines the factory that will manage singleton session beans. * @author Florent Benoit */ public class SingletonSessionFactory extends SessionFactory<EasyBeansSingletonSB> implements IPoolItemRemoveManager<EasyBeansSingletonSB> { /** * Logger. */ private static final Log LOGGER = LogFactory.getLog(SingletonSessionFactory.class); /** * Lock used by this factory. */ private ReadWriteLock lock = null; /** * Singleton Bean Instance. */ private volatile EasyBeansSingletonSB singletonBean = null; /** * Builds a new factory with a given name and its container. * @param className name of this factory (name of class that is managed) * @param container the root component of this factory. * @throws FactoryException if class can't be loaded. */ public SingletonSessionFactory(final String className, final EZBContainer container) throws FactoryException { super(className, container); // Only one instance PoolConfiguration poolConfig = new PoolConfiguration(); poolConfig.setMax(1); poolConfig.setMin(1); poolConfig.setSpare(0); // Use of the old pool ? if (Boolean.getBoolean(OLD_POOL)) { JPool<EasyBeansSingletonSB, Long> jPool = new JPool<EasyBeansSingletonSB, Long>( new JPoolWrapperFactory<EasyBeansSingletonSB, Long>(this)); jPool.setPoolConfiguration(poolConfig); setPool(jPool); } else { // new pool EnhancedPool<EasyBeansSingletonSB> singletonPool = getManagementPool().getEnhancedPoolFactory().createEnhancedPool( this); singletonPool.setPoolConfiguration(poolConfig); setPool(new PoolWrapper<EasyBeansSingletonSB>(singletonPool)); } // pool this.lock = new ReentrantReadWriteLock(); } /** * Gets a bean for the given id. * @param beanId id of the expected bean. * @return a Stateless bean. * @throws IllegalArgumentException if bean is not found. */ @Override protected EasyBeansSingletonSB getBean(final Long beanId) throws IllegalArgumentException { if (this.singletonBean != null) { return this.singletonBean; } try { return getPool().get(); } catch (PoolException e) { throw new IllegalArgumentException("Cannot get element in the pool", e); } } /** * Do a local call on a method of this factory. * @param localCallRequest the given request * @return response with the value of the call and the bean ID (if any) */ @Override public EJBResponse localCall(final EJBLocalRequest localCallRequest) { // build EJB Response EJBResponse ejbResponse = new JEJBResponse(); // get method hash Long methodHash = localCallRequest.getMethodHash(); // Get data about the invoked method IMethodInfo methodInfo = getMethodInfoHashes().get(methodHash); IAccessTimeoutInfo accessTimeout = null; // Locking strategy ILockTypeInfo methodLockType = null; // Update methods info if (methodInfo != null) { accessTimeout = methodInfo.getAccessTimeout(); methodLockType = methodInfo.getLockType(); } // Define lock based on the strategy. Lock lock = null; if (ILockTypeInfo.READ == methodLockType) { lock = this.lock.readLock(); } else { lock = this.lock.writeLock(); } // getAccess is used if accessTimeout with value >=0 is used boolean getAccess = true; if (accessTimeout != null) { // Infinite wait if (accessTimeout.value() == -1) { lock.lock(); } else if (accessTimeout.value() >= 0) { try { LOGGER.debug("Trying to lock bean with value ''{0}'' and timeunit ''{1}''", Long.valueOf(accessTimeout .value()), accessTimeout.unit()); getAccess = lock.tryLock(accessTimeout.value(), accessTimeout.unit()); } catch (InterruptedException e) { ejbResponse.setRPCException(new RPCException("Cannot get a lock for the stateful instance", e)); return ejbResponse; } } } else { // Serialize concurrent calls, so wait until the lock is liberated lock.lock(); } // We've tried to get the lock for the given time and this has been denied. // Do not need to unlock as the lock was not obtained if (!getAccess) { // Timeout exception if (accessTimeout != null) { RPCException rpcException = null; if (accessTimeout.value() == 0) { // Concurrent access is denied rpcException = new RPCException(ConcurrentBuilderException .buildConcurrentException("Unable to get a concurrent access on bean '" + getClassName() + "' and method '" + getHashes().get(methodHash) + "'.")); } else { // Unable to get access during the elapsed time, so throw a // ConcurrentAccessTimeoutException rpcException = new RPCException(ConcurrentBuilderException .buildConcurrentTimeoutException("Unable to get a concurrent access with an accessTimeout of '" + accessTimeout + "' on bean '" + getClassName() + "' and method '" + getHashes().get(methodHash) + "'.")); } ejbResponse.setRPCException(rpcException); return ejbResponse; } } // If we're here, it means that we've got the lock. so don't forget to unlock try { // Get an instance of the bean if (this.singletonBean == null) { try { this.singletonBean = getBean(null); } catch (IllegalArgumentException e) { ejbResponse.setRPCException(new RPCException("Cannot get element in the pool", e)); return ejbResponse; } catch (NoSuchEJBException e) { ejbResponse.setRPCException(new RPCException("Bean has been removed", e)); return ejbResponse; } } Method m = getHashes().get(methodHash); if (m == null) { ejbResponse.setRPCException(new RPCException("Cannot find method called on the bean '" + getClassName() + "'.")); return ejbResponse; } Object value = null; // set ClassLoader ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getContainer().getClassLoader()); // Busines interface String oldInvokedBusinessInterface = getInvokedBusinessInterfaceNameThreadLocal().get(); getInvokedBusinessInterfaceNameThreadLocal().set(localCallRequest.getInvokedBusinessInterfaceName()); // Operation state OperationState oldState = getOperationState(); getOperationStateThreadLocal().set(BUSINESS_METHOD); // Dispatch the bean invocation begin event. String methodEventProviderId = getJ2EEManagedObjectId() + "/" + J2EEManagedObjectNamingHelper.getMethodSignature(m) + "@Local"; boolean enabledEvent = !localCallRequest.isCalledFromRemoteRequest(); EZBEventBeanInvocation event = null; long number = 0; IAuditID previousID = null; // Compute and send begin event only if required if (enabledEvent) { // Invocation ID if (getCurrentInvocationID() != null) { previousID = getCurrentInvocationID().newInvocation(); } event = getInvocationEventBegin(methodEventProviderId, localCallRequest.getMethodArgs()); number = event.getInvocationNumber(); getEventDispatcher().dispatch(event); } try { value = m.invoke(this.singletonBean, localCallRequest.getMethodArgs()); } catch (IllegalArgumentException e) { ejbResponse.setRPCException(new RPCException(e)); if (enabledEvent) { getEventDispatcher().dispatch(new EventBeanInvocationError(methodEventProviderId, number, e)); } } catch (IllegalAccessException e) { ejbResponse.setRPCException(new RPCException(e)); if (enabledEvent) { getEventDispatcher().dispatch(new EventBeanInvocationError(methodEventProviderId, number, e)); } } catch (InvocationTargetException e) { Throwable cause = e.getCause(); RPCException rpcException = new RPCException(cause); // ApplicationException ? IApplicationExceptionInfo applicationException = getBeanInfo().getApplicationException(cause); if (applicationException != null) { rpcException.setApplicationException(); } ejbResponse.setRPCException(rpcException); if (enabledEvent) { getEventDispatcher().dispatch(new EventBeanInvocationError(methodEventProviderId, number, e)); } } finally { if (enabledEvent) { getEventDispatcher().dispatch(new EventBeanInvocationEnd(methodEventProviderId, number, value)); // Restore previous ID if (getCurrentInvocationID() != null) { getCurrentInvocationID().setAuditID(previousID); } } Thread.currentThread().setContextClassLoader(oldClassLoader); getInvokedBusinessInterfaceNameThreadLocal().set(oldInvokedBusinessInterface); getOperationStateThreadLocal().set(oldState); } ejbResponse.setValue(value); } finally { // release lock lock.unlock(); } return ejbResponse; } /** * Start the factory. * @throws FactoryException if the startup fails. */ @Override public void start() throws FactoryException { super.start(); // Not yet instantiated ? if ((getSessionBeanInfo().isStartup() || getSessionBeanInfo().getTimersInfo().size() > 0) && this.singletonBean == null) { try { this.singletonBean = getBean(null); } catch (RuntimeException e) { throw new FactoryException("Cannot initialize Singleton bean", e); } } } /** * Stops the factory. */ @Override public void stop() { try { // Stop all timers Collection<Timer> timers = getTimerService().getTimers(); for (Timer timer : timers) { timer.cancel(); } } finally { // push back into the pool if (this.singletonBean != null) { try { getPool().release(this.singletonBean); } catch (PoolException e) { LOGGER.error("Unable to release singleton bean", e); } } super.stop(); } } }