/*
* 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.singleton;
import org.apache.openejb.ApplicationException;
import org.apache.openejb.BeanContext;
import org.apache.openejb.BeanType;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.cdi.CdiEjbBean;
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.core.transaction.EjbTransactionUtil;
import org.apache.openejb.core.transaction.TransactionPolicy;
import org.apache.openejb.core.transaction.TransactionType;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.monitoring.LocalMBeanServer;
import org.apache.openejb.monitoring.ManagedMBean;
import org.apache.openejb.monitoring.ObjectNameBuilder;
import org.apache.openejb.monitoring.StatsInterceptor;
import org.apache.openejb.spi.ContainerSystem;
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.NoSuchEJBException;
import javax.ejb.SessionBean;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.xml.ws.WebServiceContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SingletonInstanceManager {
private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, "org.apache.openejb.util.resources");
private final SecurityService securityService;
private final SingletonContext sessionContext;
private final WebServiceContext webServiceContext;
public SingletonInstanceManager(final SecurityService securityService) {
this.securityService = securityService;
sessionContext = new SingletonContext(this.securityService);
webServiceContext = new EjbWsContext(sessionContext);
}
protected void start(final BeanContext beanContext) throws OpenEJBException {
if (beanContext.isLoadOnStartup()) {
initialize(beanContext);
}
}
private void initialize(final BeanContext beanContext) throws OpenEJBException {
try {
final ThreadContext callContext = new ThreadContext(beanContext, null);
final ThreadContext old = ThreadContext.enter(callContext);
try {
getInstance(callContext);
} finally {
ThreadContext.exit(old);
}
} catch (final OpenEJBException e) {
throw new OpenEJBException("Singleton startup failed: " + beanContext.getDeploymentID(), e);
}
}
public Instance getInstance(final ThreadContext callContext) throws OpenEJBException {
final BeanContext beanContext = callContext.getBeanContext();
final Data data = (Data) beanContext.getContainerData();
final AtomicReference<Future<Instance>> singleton = data.singleton;
try {
// Has the singleton been created yet?
// If there is a Future object in the AtomicReference, then
// it's either been created or is being created now.
Future<Instance> singletonFuture = singleton.get();
if (singletonFuture != null) {
return singletonFuture.get();
}
// The singleton has not been created nor is being created
// We will construct this FutureTask and compete with the
// other threads for the right to create the singleton
final FutureTask<Instance> task = new FutureTask<Instance>(new Callable<Instance>() {
public Instance call() throws Exception {
return createInstance(callContext, beanContext);
}
});
do {
// If our FutureTask was the one to win the slot
// than we are the ones responsible for creating
// the singleton while the others wait.
if (singleton.compareAndSet(null, task)) {
task.run();
}
// If we didn't win the slot and no other FutureTask
// has been set by a different thread, than we need
// to try again.
} while ((singletonFuture = singleton.get()) == null);
// At this point we can safely return the singleton
return singletonFuture.get();
} catch (final InterruptedException e) {
Thread.interrupted();
throw new ApplicationException(new NoSuchEJBException("Singleton initialization interrupted").initCause(e));
} catch (final ExecutionException e) {
final Throwable throwable = e.getCause();
if (throwable instanceof ApplicationException) {
throw (ApplicationException) throwable;
}
throw new ApplicationException(new NoSuchEJBException("Singleton initialization failed").initCause(e.getCause()));
}
}
private void initializeDependencies(final BeanContext beanContext) throws OpenEJBException {
final SystemInstance systemInstance = SystemInstance.get();
final ContainerSystem containerSystem = systemInstance.getComponent(ContainerSystem.class);
for (final String dependencyId : beanContext.getDependsOn()) {
final BeanContext dependencyContext = containerSystem.getBeanContext(dependencyId);
if (dependencyContext == null) {
throw new OpenEJBException("Deployment does not exist. Deployment(id='" + dependencyContext + "')");
}
final Object containerData = dependencyContext.getContainerData();
// Bean may not be a singleton or may be a singleton
// managed by a different container implementation
if (containerData instanceof Data) {
final Data data = (Data) containerData;
data.initialize();
}
}
}
private Instance createInstance(final ThreadContext callContext, final BeanContext beanContext) throws ApplicationException {
try {
initializeDependencies(beanContext);
final InstanceContext context = beanContext.newInstance();
if (context.getBean() instanceof SessionBean) {
final Operation originalOperation = callContext.getCurrentOperation();
try {
callContext.setCurrentOperation(Operation.CREATE);
final Method create = beanContext.getCreateMethod();
final InterceptorStack ejbCreate = new InterceptorStack(context.getBean(), create, Operation.CREATE, new ArrayList<InterceptorData>(), new HashMap());
ejbCreate.invoke();
} finally {
callContext.setCurrentOperation(originalOperation);
}
}
final ReadWriteLock lock;
if (beanContext.isBeanManagedConcurrency()) {
// Bean-Managed Concurrency
lock = new BeanManagedLock();
} else {
// Container-Managed Concurrency
lock = new ReentrantReadWriteLock();
}
return new Instance(context.getBean(), context.getInterceptors(), context.getCreationalContext(), lock);
} catch (Throwable e) {
if (e instanceof InvocationTargetException) {
e = ((InvocationTargetException) e).getTargetException();
}
final String t = "The bean instance " + beanContext.getDeploymentID() + " threw a system exception:" + e;
logger.error(t, e);
throw new ApplicationException(new NoSuchEJBException("Singleton failed to initialize").initCause(e));
}
}
public void freeInstance(final ThreadContext callContext) {
final BeanContext beanContext = callContext.getBeanContext();
final Data data = (Data) beanContext.getContainerData();
final Future<Instance> instanceFuture = data.singleton.get();
// Possible the instance was never created
if (instanceFuture == null) {
return;
}
final Instance instance;
try {
instance = instanceFuture.get();
} catch (final InterruptedException e) {
Thread.interrupted();
logger.error("Singleton shutdown failed because the thread was interrupted: " + beanContext.getDeploymentID(), e);
return;
} catch (final ExecutionException e) {
// Instance was never initialized
return;
}
try {
callContext.setCurrentOperation(Operation.PRE_DESTROY);
callContext.setCurrentAllowedStates(null);
final Method remove = instance.bean instanceof SessionBean ? beanContext.getCreateMethod() : null;
final List<InterceptorData> callbackInterceptors = beanContext.getCallbackInterceptors();
final InterceptorStack interceptorStack = new InterceptorStack(instance.bean, remove, Operation.PRE_DESTROY, callbackInterceptors, instance.interceptors);
//Transaction Demarcation for Singleton PostConstruct method
TransactionType transactionType;
if (beanContext.getComponentType() == BeanType.SINGLETON) {
final Set<Method> callbacks = callbackInterceptors.get(callbackInterceptors.size() - 1).getPreDestroy();
if (callbacks.isEmpty()) {
transactionType = TransactionType.RequiresNew;
} else {
transactionType = beanContext.getTransactionType(callbacks.iterator().next());
if (transactionType == TransactionType.Required) {
transactionType = TransactionType.RequiresNew;
}
}
} else {
transactionType = beanContext.isBeanManagedTransaction() ? TransactionType.BeanManaged : TransactionType.NotSupported;
}
final TransactionPolicy transactionPolicy = EjbTransactionUtil.createTransactionPolicy(transactionType, callContext);
try {
//Call the chain
final CdiEjbBean<Object> bean = beanContext.get(CdiEjbBean.class);
if (bean != null) { // TODO: see if it should be called before or after next call
bean.getInjectionTarget().preDestroy(instance.bean);
}
interceptorStack.invoke();
if (instance.creationalContext != null) {
instance.creationalContext.release();
}
} catch (final Throwable e) {
//RollBack Transaction
EjbTransactionUtil.handleSystemException(transactionPolicy, e, callContext);
} finally {
EjbTransactionUtil.afterInvoke(transactionPolicy, callContext);
}
} catch (final Throwable re) {
logger.error("Singleton shutdown failed: " + beanContext.getDeploymentID(), re);
}
}
/**
* This method has no work to do as all instances are removed from
* the pool on getInstance(...) and not returned via poolInstance(...)
* if they threw a system exception.
*
* @param callContext
* @param bean
*/
public void discardInstance(final ThreadContext callContext, final Object bean) {
}
public void deploy(final BeanContext beanContext) throws OpenEJBException {
final Data data = new Data(beanContext);
beanContext.setContainerData(data);
beanContext.set(EJBContext.class, this.sessionContext);
// Create stats interceptor
if (StatsInterceptor.isStatsActivated()) {
final StatsInterceptor stats = new StatsInterceptor(beanContext.getBeanClass());
beanContext.addFirstSystemInterceptor(stats);
final ObjectNameBuilder jmxName = new ObjectNameBuilder("openejb.management");
jmxName.set("J2EEServer", "openejb");
jmxName.set("J2EEApplication", null);
jmxName.set("EJBModule", beanContext.getModuleID());
jmxName.set("SingletonSessionBean", beanContext.getEjbName());
jmxName.set("name", beanContext.getEjbName());
jmxName.set("j2eeType", "Invocations");
// register the invocation stats interceptor
final MBeanServer server = LocalMBeanServer.get();
try {
final ObjectName objectName = jmxName.build();
if (server.isRegistered(objectName)) {
server.unregisterMBean(objectName);
}
server.registerMBean(new ManagedMBean(stats), objectName);
data.add(objectName);
} catch (final Exception e) {
logger.error("Unable to register MBean ", e);
}
}
try {
final Context context = beanContext.getJndiEnc();
context.bind("comp/EJBContext", sessionContext);
context.bind("comp/WebServiceContext", webServiceContext);
context.bind("comp/TimerService", new TimerServiceWrapper());
} catch (final NamingException e) {
throw new OpenEJBException("Failed to bind EJBContext/WebServiceContext/TimerService", e);
}
}
public void undeploy(final BeanContext beanContext) {
final Data data = (Data) beanContext.getContainerData();
if (data == null) {
return;
}
final MBeanServer server = LocalMBeanServer.get();
for (final ObjectName objectName : data.jmxNames) {
try {
server.unregisterMBean(objectName);
} catch (final Exception e) {
logger.error("Unable to unregister MBean " + objectName);
}
}
beanContext.setContainerData(null);
}
private final class Data {
private final AtomicReference<Future<Instance>> singleton = new AtomicReference<Future<Instance>>();
private final List<ObjectName> jmxNames = new ArrayList<ObjectName>();
private final BeanContext info;
public Data(final BeanContext info) {
this.info = info;
}
public ObjectName add(final ObjectName name) {
jmxNames.add(name);
return name;
}
public void initialize() throws OpenEJBException {
SingletonInstanceManager.this.initialize(info);
}
}
private static class BeanManagedLock implements ReadWriteLock {
private final Lock lock = new Lock() {
public void lock() {
}
public void lockInterruptibly() {
}
public Condition newCondition() {
throw new UnsupportedOperationException("newCondition()");
}
public boolean tryLock() {
return true;
}
public boolean tryLock(final long time, final TimeUnit unit) {
return true;
}
public void unlock() {
}
};
public Lock readLock() {
return lock;
}
public Lock writeLock() {
return lock;
}
}
}