/** * EasyBeans * Copyright (C) 2006-2008 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: SessionFactory.java 5747 2011-02-28 17:12:27Z benoitf $ * -------------------------------------------------------------------------- */ package org.ow2.easybeans.container.session; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.atomic.AtomicInteger; import javax.ejb.EJBException; import javax.ejb.Timer; import javax.ejb.TimerService; import org.ow2.easybeans.api.EZBContainer; import org.ow2.easybeans.api.FactoryException; import org.ow2.easybeans.api.bean.EasyBeansSB; import org.ow2.easybeans.api.bean.EasyBeansSLSB; import org.ow2.easybeans.api.bean.info.IBeanInfo; import org.ow2.easybeans.api.bean.info.IMethodInfo; import org.ow2.easybeans.api.container.EZBSessionContext; import org.ow2.easybeans.api.event.bean.EZBEventBeanInvocation; import org.ow2.easybeans.container.AbsFactory; import org.ow2.easybeans.container.info.SessionBeanInfo; import org.ow2.easybeans.event.bean.EventBeanInvocationEnd; import org.ow2.easybeans.event.bean.EventBeanInvocationError; import org.ow2.easybeans.naming.J2EEManagedObjectNamingHelper; import org.ow2.easybeans.proxy.client.ClientRPCInvocationHandler; import org.ow2.easybeans.rpc.EJBLocalRequestImpl; import org.ow2.easybeans.rpc.JEJBResponse; import org.ow2.easybeans.rpc.api.EJBLocalRequest; import org.ow2.easybeans.rpc.api.EJBRemoteRequest; import org.ow2.easybeans.rpc.api.EJBResponse; import org.ow2.easybeans.rpc.api.RPCException; import org.ow2.easybeans.rpc.util.Hash; 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.enhanced.api.basic.CreatePoolItemException; /** * This class manages the session bean and its creation/lifecycle. * @param <PoolType> the type of bean instance. * @author Florent Benoit */ public abstract class SessionFactory<PoolType extends EasyBeansSB<PoolType>> extends AbsFactory<PoolType> { /** * Flag used to recreate EasyBeans Proxy on the client side as with IIOP/JacORB the dynamic proxy serialization is failing. */ private static final boolean RECREATE_DYNAMIC_PROXY = Boolean.getBoolean("easybeans.recreate.dynamic.proxy"); /** * Number of tries when an instance cannot be created. */ protected static final int TRIES_CREATION = 10; /** * Logger. */ private static Log logger = LogFactory.getLog(SessionFactory.class); /** * Session Desc (deployment info). */ private SessionBeanInfo sessionBeanInfo = null; /** * Inherited Local thread used to keep the invoked business interface. */ private InheritableThreadLocal<String> invokedBusinessInterfaceNameThreadLocal; /** * Manage failures when creating an instance. */ private AtomicInteger creationFails = 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 SessionFactory(final String className, final EZBContainer container) throws FactoryException { super(className, container); this.invokedBusinessInterfaceNameThreadLocal = new InheritableThreadLocal<String>(); this.creationFails = new AtomicInteger(0); } /** * Stops the factory. */ @Override public void stop() { // stop pool try { getPool().stop(); } catch (PoolException e) { logger.error("Problem when stopping the factory", e); } finally { super.stop(); } } /** * @return information of the current bean. */ public IBeanInfo getBeanInfo() { return this.sessionBeanInfo; } /** * @return information of the current bean. */ public SessionBeanInfo getSessionBeanInfo() { return this.sessionBeanInfo; } /** * Sets the information object for a session bean. * @param sessionBeanInfo information on the bean. */ public void setSessionBeanInfo(final SessionBeanInfo sessionBeanInfo) { this.sessionBeanInfo = sessionBeanInfo; } /** * Creates an instance. * @throws CreatePoolItemException if instance cannot be created. * @return the created instance. */ public PoolType createPoolItem() throws CreatePoolItemException { // If instance can't be created, abort int fails = this.creationFails.get(); Long waitTime = WAITING_TIME_BEFORE_CREATION; if (fails > TRIES_CREATION) { // factory is broken waitTime = null; } PoolType instance = null; ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getContainer().getClassLoader()); try { try { instance = getBeanClass().newInstance(); } catch (InstantiationException e) { this.creationFails.incrementAndGet(); logger.error("Unable to create a new instance of the class ''{0}''", getBeanClass().getName(), e); throw new CreatePoolItemException(waitTime, "Cannot create a new instance", e); } catch (IllegalAccessException e) { this.creationFails.incrementAndGet(); logger.error("Unable to create a new instance of the class ''{0}''", getBeanClass().getName(), e); throw new CreatePoolItemException(waitTime, "Cannot create a new instance", e); } catch (RuntimeException e) { this.creationFails.incrementAndGet(); logger.error("Unable to create a new instance of the class ''{0}''", getBeanClass().getName(), e); throw new CreatePoolItemException(waitTime, "Cannot create a new instance", e); } catch (Exception e) { this.creationFails.incrementAndGet(); logger.error("Unable to create a new instance of the class ''{0}''", getBeanClass().getName(), e); throw new CreatePoolItemException(waitTime, "Cannot create a new instance", e); } catch (Error e) { this.creationFails.incrementAndGet(); logger.error("Unable to create a new instance of the class ''{0}''", getBeanClass().getName(), e); // null as factory is broken throw new CreatePoolItemException(waitTime, "Cannot create a new instance", e); } } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } // Set the factory instance.setEasyBeansFactory(this); instance.setEasyBeansInvocationContextFactory(getInvocationContextFactory()); // Init the session Context EZBSessionContext<SessionFactory<?>> sessionContext = new EasyBeansSessionContext<SessionFactory<?>>(this); instance.setEasyBeansContext(sessionContext); oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getContainer().getClassLoader()); try { // Call injection try { injectResources(instance); } catch (PoolException e) { this.creationFails.incrementAndGet(); logger.error("Unable to perform injection of resources in the instance of the class ''{0}''", getBeanClass() .getName(), e); throw new CreatePoolItemException(waitTime, "Cannot perform injection of resources in the instance of the class '" + getBeanClass().getName() + "'.", e); } catch (RuntimeException e) { this.creationFails.incrementAndGet(); logger.error("Unable to perform injection of resources in the instance of the class ''{0}''", getBeanClass() .getName(), e); throw new CreatePoolItemException(waitTime, "Cannot perform injection of resources in the instance of the class '" + getBeanClass().getName() + "'.", e); } catch (Exception e) { this.creationFails.incrementAndGet(); logger.error("Unable to perform injection of resources in the instance of the class ''{0}''", getBeanClass() .getName(), e); throw new CreatePoolItemException(waitTime, "Cannot perform injection of resources in the instance of the class '" + getBeanClass().getName() + "'.", e); } catch (Error e) { this.creationFails.incrementAndGet(); logger.error("Unable to perform injection of resources in the instance of the class ''{0}''", getBeanClass() .getName(), e); throw new CreatePoolItemException(waitTime, "Cannot perform injection of resources in the instance of the class '" + getBeanClass().getName() + "'.", e); } // post construct callback postConstruct(instance); } catch (RuntimeException e) { this.creationFails.incrementAndGet(); logger.error("Unable to perform postconstruct on a new instance of the class ''{0}''", getBeanClass().getName(), e); throw new CreatePoolItemException(waitTime, "Cannot perform postConstruct on the new instance", e); } catch (Exception e) { this.creationFails.incrementAndGet(); logger.error("Unable to perform postconstruct on a new instance of the class ''{0}''", getBeanClass().getName(), e); throw new CreatePoolItemException(waitTime, "Cannot perform postConstruct on the new instance", e); } catch (Error e) { this.creationFails.incrementAndGet(); logger.error("Unable to perform postconstruct on a new instance of the class ''{0}''", getBeanClass().getName(), e); throw new CreatePoolItemException(waitTime, "Cannot perform postConstruct on the new instance", e); } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } // reset counter this.creationFails.set(0); return instance; } /** * A request comes to the bean factory and needs to be handled.<br> * A response is done which contains the answer. * @param request the EJB request. * @return a response that have been processed by the factory. */ @Override public EJBResponse rpcInvoke(final EJBRemoteRequest request) { Method calledMethod = getHashes().get(request.getMethodHash()); // Invalid method if (calledMethod == null) { logger.debug("Requested method {0} is not present on the bean class ''{1}''", request.getMethodName(), getBeanClass()); return new JEJBResponse(); } // Get Args (use context classloader for the Serialization) Object[] args = null; ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getContainer().getClassLoader()); try { args = request.getMethodArgs(); } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } // Invocation ID IAuditID previousID = null; if (getCurrentInvocationID() != null) { previousID = getCurrentInvocationID().newInvocation(); } // Dispatch the bean invocation begin event. String methodEventProviderId = getJ2EEManagedObjectId() + "/" + J2EEManagedObjectNamingHelper.getMethodSignature(calledMethod) + "@Remote"; EZBEventBeanInvocation event = getInvocationEventBegin(methodEventProviderId, args); long number = event.getInvocationNumber(); getEventDispatcher().dispatch(event); try { EJBLocalRequestImpl localRequest = new EJBLocalRequestImpl(request.getMethodHash(), args, request.getBeanId(), request .getInvokedBusinessInterfaceName()); // Avoid to send events as local calls localRequest.setCalledFromRemoteRequest(true); // call the method EJBResponse result = localCall(localRequest); // Create the bean invocation end event. event = new EventBeanInvocationEnd(methodEventProviderId, number, result); // JacORB enabled ? Needs to return the invocation handler instead of the dynamic proxy to the client proxy if (RECREATE_DYNAMIC_PROXY) { Object value = result.getValue(); if (value != null) { try { InvocationHandler handler = Proxy.getInvocationHandler(value); if (handler instanceof ClientRPCInvocationHandler && ((ClientRPCInvocationHandler) handler).isBusinessObjectMode()) { result.setValue(handler); return result; } } catch (IllegalArgumentException e) { logger.debug("Not a proxy instance", e); } } } return result; } catch (Exception ex) { // Create the bean invocation error event. event = new EventBeanInvocationError(methodEventProviderId, number, ex); throw new RuntimeException(ex); } finally { // Restore previous ID if (getCurrentInvocationID() != null) { getCurrentInvocationID().setAuditID(previousID); } getEventDispatcher().dispatch(event); } } /** * 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. */ protected abstract PoolType getBean(final Long beanId) throws IllegalArgumentException; /** * 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) */ public abstract EJBResponse localCall(final EJBLocalRequest localCallRequest); /** * @return the local thread that is keeping invoked Business Interface name. */ protected InheritableThreadLocal<String> getInvokedBusinessInterfaceNameThreadLocal() { return this.invokedBusinessInterfaceNameThreadLocal; } /** * Callback used when the given element will be removed. * @param instance the given instance to be removed */ public void poolItemRemoved(final PoolType instance) { super.remove(instance); instance.setEasyBeansRemoved(true); } /** * Notified when the timer service send a Timer object. * It has to call the Timed method. * @param timer the given timer object that will be given to the timer method. * @param methodInfo the method to use for the callback if applied on a specific method */ public void notifyTimeout(final Timer timer, final IMethodInfo methodInfo) { // If receiving a call when factory is stopped, ignore this call if (!isStarted()) { logger.warn("Received a timer call but the factory has been stopped, so ignore this call"); return; } if (methodInfo != null) { // Get method hash from the method info long hashMethod = Hash.hashMethod(methodInfo.getName(), methodInfo.getDescriptor()); Object[] parameters = null; // If timer argument is specified on the method, send it if (methodInfo.getParameters().size() == 1) { parameters = new Object[] {timer}; } // Needs to call a specific method on the bean if (methodInfo != null) { EJBLocalRequest request = new EJBLocalRequestImpl(Long.valueOf(hashMethod), parameters, Long.valueOf(0), TimerService.class.getName()); EJBResponse response = localCall(request); RPCException exception = response.getRPCException(); if (exception != null) { logger.error("Unable to call the timer callback", exception); } return; } } // Get Bean PoolType bean = getBean(null); // Set ClassLoader ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getContainer().getClassLoader()); // Call the timer method on the bean try { bean.timeoutCallByEasyBeans(timer); } finally { // Reset classloader Thread.currentThread().setContextClassLoader(oldClassLoader); if (bean.getClass().isAssignableFrom(EasyBeansSLSB.class)) { try { getPool().release(bean); } catch (PoolException e) { throw new EJBException("cannot release bean", e); } } } } }