/* * 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.openejb.core.mdb; import org.apache.openejb.BeanContext; import org.apache.openejb.OpenEJBException; import org.apache.openejb.core.BaseContext; import org.apache.openejb.core.InstanceContext; import org.apache.openejb.core.Operation; import org.apache.openejb.core.ThreadContext; import org.apache.openejb.core.interceptor.InterceptorData; import org.apache.openejb.core.interceptor.InterceptorStack; import org.apache.openejb.core.timer.TimerServiceWrapper; import org.apache.openejb.spi.SecurityService; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import javax.ejb.EJBContext; import javax.ejb.MessageDrivenBean; import javax.naming.Context; import javax.naming.NamingException; import javax.resource.spi.UnavailableException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * A MdbInstanceFactory creates instances of message driven beans for a single instance. This class differs from other * instance managers in OpenEJB as it doesn't do pooling and it creates instances for only a single EJB deployment. * </p> * The MdbContainer assumes that the resouce adapter is pooling message endpoints so a second level of pooling in the * container would be inefficient. This is true of all known resouce adapters in opensource (ActiveMQ), so if this is * a poor assumption for your resource adapter, contact the OpenEJB developers. * </p> * This class can optionally limit the number of bean instances and therefore the message endpoints available to the * resource adapter. */ public class MdbInstanceFactory { private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, "org.apache.openejb.util.resources"); private final BeanContext beanContext; private final int instanceLimit; private int instanceCount; private final MdbContext mdbContext; /** * Creates a MdbInstanceFactory for a single specific deployment. * * @param beanContext the deployment for which instances will be created * @param securityService the transaction manager for this container system * @param instanceLimit the maximal number of instances or <= 0 if unlimited */ public MdbInstanceFactory(final BeanContext beanContext, final SecurityService securityService, final int instanceLimit) throws OpenEJBException { this.beanContext = beanContext; this.instanceLimit = instanceLimit; mdbContext = new MdbContext(securityService); try { final Context context = beanContext.getJndiEnc(); context.bind("comp/EJBContext", mdbContext); context.bind("comp/TimerService", new TimerServiceWrapper()); } catch (final NamingException e) { throw new OpenEJBException("Failed to bind EJBContext/TimerService", e); } beanContext.set(EJBContext.class, this.mdbContext); } /** * Gets the maximal number of instances that can exist at any time. * * @return the maximum number of instances or <= 0 if unlimitied */ public int getInstanceLimit() { return instanceLimit; } /** * Gets the current number of created instances. * * @return the current number of instances created */ public synchronized int getInstanceCount() { return instanceCount; } /** * Creates a new mdb instance preforming all necessary lifecycle callbacks * * @param ignoreInstanceCount * @return a new message driven bean instance * @throws UnavailableException if the instance limit has been exceeded or * if an exception occurs while creating the bean instance */ public Object createInstance(final boolean ignoreInstanceCount) throws UnavailableException { if (!ignoreInstanceCount) { synchronized (this) { // check the instance limit if (instanceLimit > 0 && instanceCount >= instanceLimit) { throw new UnavailableException("Only " + instanceLimit + " instances can be created"); } // increment the instance count instanceCount++; } } try { final Object bean = constructBean(); return bean; } catch (final UnavailableException e) { // decrement the instance count if (!ignoreInstanceCount) { synchronized (this) { instanceCount--; } } throw e; } } /** * Frees an instance no longer needed by the resource adapter. This method makes all the necessary lifecycle * callbacks and decrements the instance count. This method should not be used to disposed of beans that have * thrown a system exception. Instead the discardInstance method should be called. * * @param instance the bean instance to free * @param ignoredInstanceCount */ public void freeInstance(final Instance instance, final boolean ignoredInstanceCount) { if (instance == null) { throw new NullPointerException("bean is null"); } // decrement the instance count if (!ignoredInstanceCount) { synchronized (this) { instanceCount--; } } final ThreadContext callContext = ThreadContext.getThreadContext(); final Operation originalOperation = callContext == null ? null : callContext.getCurrentOperation(); final BaseContext.State[] originalAllowedStates = callContext == null ? null : callContext.getCurrentAllowedStates(); try { // call post destroy method if (callContext != null) { callContext.setCurrentOperation(Operation.PRE_DESTROY); } final Method remove = instance.bean instanceof MessageDrivenBean ? MessageDrivenBean.class.getMethod("ejbRemove") : null; final List<InterceptorData> callbackInterceptors = beanContext.getCallbackInterceptors(); final InterceptorStack interceptorStack = new InterceptorStack(instance.bean, remove, Operation.PRE_DESTROY, callbackInterceptors, instance.interceptors); interceptorStack.invoke(); if (instance.creationalContext != null) { instance.creationalContext.release(); } } catch (final Throwable re) { MdbInstanceFactory.logger.error("The bean instance " + instance.bean + " threw a system exception:" + re, re); } finally { if (callContext != null) { callContext.setCurrentOperation(originalOperation); callContext.setCurrentAllowedStates(originalAllowedStates); } } } /** * Recreates a bean instance that has thrown a system exception. As required by the EJB specification, lifecycle * callbacks are not invoked. To normally free a bean instance call the freeInstance method. * * @param bean the bean instance to discard * @return the new replacement bean instance */ public Object recreateInstance(final Object bean) throws UnavailableException { if (bean == null) { throw new NullPointerException("bean is null"); } final Object newBean = constructBean(); return newBean; } private Object constructBean() throws UnavailableException { final BeanContext beanContext = this.beanContext; final ThreadContext callContext = new ThreadContext(beanContext, null, Operation.INJECTION); final ThreadContext oldContext = ThreadContext.enter(callContext); try { final InstanceContext context = beanContext.newInstance(); if (context.getBean() instanceof MessageDrivenBean) { callContext.setCurrentOperation(Operation.CREATE); final Method create = beanContext.getCreateMethod(); final InterceptorStack ejbCreate = new InterceptorStack(context.getBean(), create, Operation.CREATE, new ArrayList(), new HashMap()); ejbCreate.invoke(); } return new Instance(context.getBean(), context.getInterceptors(), context.getCreationalContext()); } catch (Throwable e) { if (e instanceof InvocationTargetException) { e = ((InvocationTargetException) e).getTargetException(); } final String message = "The bean instance threw a system exception:" + e; MdbInstanceFactory.logger.error(message, e); throw new UnavailableException(message, e); } finally { ThreadContext.exit(oldContext); } } }