blob: de3f6e611a69d97ba470c31cb7ebe503cf88c47a [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.openejb.core.singleton;
import org.apache.openejb.BeanContext;
import org.apache.openejb.ContainerType;
import org.apache.openejb.InterfaceType;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.ProxyInfo;
import org.apache.openejb.RpcContainer;
import org.apache.openejb.core.ExceptionType;
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.EjbTimerService;
import org.apache.openejb.core.transaction.TransactionPolicy;
import org.apache.openejb.core.webservices.AddressingSupport;
import org.apache.openejb.core.webservices.NoAddressingSupport;
import org.apache.openejb.monitoring.StatsInterceptor;
import org.apache.openejb.spi.SecurityService;
import org.apache.openejb.util.Duration;
import org.apache.xbean.finder.ClassFinder;
import javax.ejb.ConcurrentAccessTimeoutException;
import javax.ejb.EJBAccessException;
import javax.ejb.EJBHome;
import javax.ejb.EJBLocalHome;
import javax.ejb.EJBLocalObject;
import javax.ejb.EJBObject;
import javax.interceptor.AroundInvoke;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import static org.apache.openejb.core.transaction.EjbTransactionUtil.*;
/**
* @org.apache.xbean.XBean element="statelessContainer"
*/
public class SingletonContainer implements RpcContainer {
private SingletonInstanceManager instanceManager;
private HashMap<String, BeanContext> deploymentRegistry = new HashMap<String, BeanContext>();
private final ConcurrentMap<Class<?>, List<Method>> interceptorCache = new ConcurrentHashMap<Class<?>, List<Method>>();
private Object containerID = null;
private SecurityService securityService;
private Duration accessTimeout;
public SingletonContainer(Object id, SecurityService securityService) throws OpenEJBException {
this.containerID = id;
this.securityService = securityService;
instanceManager = new SingletonInstanceManager(securityService);
for (BeanContext beanContext : deploymentRegistry.values()) {
beanContext.setContainer(this);
}
}
public void setAccessTimeout(Duration duration) {
this.accessTimeout = duration;
}
@Override
public synchronized BeanContext[] getBeanContexts() {
return deploymentRegistry.values().toArray(new BeanContext[deploymentRegistry.size()]);
}
@Override
public synchronized BeanContext getBeanContext(Object deploymentID) {
String id = (String) deploymentID;
return deploymentRegistry.get(id);
}
@Override
public ContainerType getContainerType() {
return ContainerType.SINGLETON;
}
@Override
public Object getContainerID() {
return containerID;
}
@Override
public void deploy(BeanContext beanContext) throws OpenEJBException {
instanceManager.deploy(beanContext);
String id = (String) beanContext.getDeploymentID();
synchronized (this) {
deploymentRegistry.put(id, beanContext);
beanContext.setContainer(this);
}
// add it before starting the timer (@PostCostruct)
if (StatsInterceptor.isStatsActivated()) {
StatsInterceptor stats = new StatsInterceptor(beanContext.getBeanClass());
beanContext.addFirstSystemInterceptor(stats);
}
EjbTimerService timerService = beanContext.getEjbTimerService();
if (timerService != null) {
timerService.start();
}
}
@Override
public void start(BeanContext info) throws OpenEJBException {
instanceManager.start(info);
}
@Override
public void stop(BeanContext info) throws OpenEJBException {
info.stop();
}
@Override
public void undeploy(BeanContext beanContext) {
ThreadContext threadContext = new ThreadContext(beanContext, null);
ThreadContext old = ThreadContext.enter(threadContext);
try {
instanceManager.freeInstance(threadContext);
} finally {
ThreadContext.exit(old);
}
EjbTimerService timerService = beanContext.getEjbTimerService();
if (timerService != null) {
timerService.stop();
}
instanceManager.undeploy(beanContext);
synchronized (this) {
String id = (String) beanContext.getDeploymentID();
beanContext.setContainer(null);
beanContext.setContainerData(null);
deploymentRegistry.remove(id);
}
}
/**
* @deprecated use invoke signature without 'securityIdentity' argument.
*/
@Deprecated
@Override
public Object invoke(Object deployID, Method callMethod, Object[] args, Object primKey, Object securityIdentity) throws OpenEJBException {
return invoke(deployID, null, callMethod.getDeclaringClass(), callMethod, args, primKey);
}
@Override
public Object invoke(Object deployID, Class callInterface, Method callMethod, Object[] args, Object primKey) throws OpenEJBException {
return invoke(deployID, null, callInterface, callMethod, args, primKey);
}
@Override
public Object invoke(Object deployID, InterfaceType type, Class callInterface, Method callMethod, Object[] args, Object primKey) throws OpenEJBException {
BeanContext beanContext = this.getBeanContext(deployID);
if (beanContext == null) throw new OpenEJBException("Deployment does not exist in this container. Deployment(id='" + deployID + "'), Container(id='" + containerID + "')");
// Use the backup way to determine call type if null was supplied.
if (type == null) type = beanContext.getInterfaceType(callInterface);
Method runMethod = beanContext.getMatchingBeanMethod(callMethod);
ThreadContext callContext = new ThreadContext(beanContext, primKey);
ThreadContext oldCallContext = ThreadContext.enter(callContext);
try {
boolean authorized = type == InterfaceType.TIMEOUT || getSecurityService().isCallerAuthorized(callMethod, type);
if (!authorized)
throw new org.apache.openejb.ApplicationException(new EJBAccessException("Unauthorized Access by Principal Denied"));
Class declaringClass = callMethod.getDeclaringClass();
if (EJBHome.class.isAssignableFrom(declaringClass) || EJBLocalHome.class.isAssignableFrom(declaringClass)) {
if (callMethod.getName().startsWith("create")) {
return createEJBObject(beanContext, callMethod);
} else
return null;// EJBHome.remove( ) and other EJBHome methods are not process by the container
} else if (EJBObject.class == declaringClass || EJBLocalObject.class == declaringClass) {
return null;// EJBObject.remove( ) and other EJBObject methods are not process by the container
}
Instance instance = instanceManager.getInstance(callContext);
callContext.setCurrentOperation(type == InterfaceType.TIMEOUT ? Operation.TIMEOUT : Operation.BUSINESS);
callContext.setCurrentAllowedStates(null);
callContext.set(Method.class, runMethod);
callContext.setInvokedInterface(callInterface);
return _invoke(callMethod, runMethod, args, instance, callContext, type);
} finally {
ThreadContext.exit(oldCallContext);
}
}
private SecurityService getSecurityService() {
return securityService;
}
protected Object _invoke(Method callMethod, Method runMethod, Object[] args, Instance instance, ThreadContext callContext, InterfaceType callType) throws OpenEJBException {
BeanContext beanContext = callContext.getBeanContext();
Duration accessTimeout = getAccessTimeout(beanContext, runMethod);
boolean read = javax.ejb.LockType.READ.equals(beanContext.getConcurrencyAttribute(runMethod));
final Lock lock = aquireLock(read, accessTimeout, instance, runMethod);
Object returnValue;
try {
TransactionPolicy txPolicy = createTransactionPolicy(beanContext.getTransactionType(callMethod, callType), callContext);
returnValue = null;
try {
if (callType == InterfaceType.SERVICE_ENDPOINT) {
callContext.setCurrentOperation(Operation.BUSINESS_WS);
returnValue = invokeWebService(args, beanContext, runMethod, instance);
} else {
List<InterceptorData> interceptors = beanContext.getMethodInterceptors(runMethod);
InterceptorStack interceptorStack = new InterceptorStack(instance.bean, runMethod, callType == InterfaceType.TIMEOUT ? Operation.TIMEOUT : Operation.BUSINESS, interceptors,
instance.interceptors);
returnValue = interceptorStack.invoke(args);
}
} catch (Throwable e) {// handle reflection exception
ExceptionType type = beanContext.getExceptionType(e);
if (type == ExceptionType.SYSTEM) {
/* System Exception ****************************/
// The bean instance is not put into the pool via instanceManager.poolInstance
// and therefore the instance will be garbage collected and destroyed.
// For this reason the discardInstance method of the StatelessInstanceManager
// does nothing.
handleSystemException(txPolicy, e, callContext);
} else {
/* Application Exception ***********************/
handleApplicationException(txPolicy, e, type == ExceptionType.APPLICATION_ROLLBACK);
}
} finally {
afterInvoke(txPolicy, callContext);
}
} finally {
lock.unlock();
}
return returnValue;
}
private Duration getAccessTimeout(BeanContext beanContext, Method callMethod) {
Duration accessTimeout = beanContext.getAccessTimeout(callMethod);
if (accessTimeout == null) {
accessTimeout = beanContext.getAccessTimeout();
if (accessTimeout == null) {
accessTimeout = this.accessTimeout;
}
}
return accessTimeout;
}
private Lock aquireLock(boolean read, final Duration accessTimeout, final Instance instance, final Method runMethod) {
final Lock lock;
if (read) {
lock = instance.lock.readLock();
} else {
lock = instance.lock.writeLock();
}
boolean lockAcquired;
if (accessTimeout == null || accessTimeout.getTime() < 0) {
// wait indefinitely for a lock
lock.lock();
lockAcquired = true;
} else if (accessTimeout.getTime() == 0) {
// concurrent calls are not allowed, lock only once
lockAcquired = lock.tryLock();
} else {
// try to get a lock within the specified period.
try {
lockAcquired = lock.tryLock(accessTimeout.getTime(), accessTimeout.getUnit());
} catch (InterruptedException e) {
throw (ConcurrentAccessTimeoutException) new ConcurrentAccessTimeoutException("Unable to get " + (read ? "read" : "write") + " lock within specified time on '" + runMethod.getName() + "' method for: " + instance.bean.getClass().getName()).initCause(e);
}
}
// Did we acquire the lock to the current execution?
if (!lockAcquired) {
throw new ConcurrentAccessTimeoutException("Unable to get " + (read ? "read" : "write") + " lock on '" + runMethod.getName() + "' method for: " + instance.bean.getClass().getName());
}
return lock;
}
private Object invokeWebService(Object[] args, BeanContext beanContext, Method runMethod, Instance instance) throws Exception {
if (args.length < 2) {
throw new IllegalArgumentException("WebService calls must follow format {messageContext, interceptor, [arg...]}.");
}
Object messageContext = args[0];
if (messageContext == null) throw new IllegalArgumentException("MessageContext is null.");
// This object will be used as an interceptor in the stack and will be responsible
// for unmarshalling the soap message parts into an argument list that will be
// used for the actual method invocation.
//
// We just need to make it an interceptor in the OpenEJB sense and tack it on the end
// of our stack.
Object interceptor = args[1];
if (interceptor == null) throw new IllegalArgumentException("Interceptor instance is null.");
final Class<?> interceptorClass = interceptor.getClass();
// Add the webservice interceptor to the list of interceptor instances
Map<String, Object> interceptors = new HashMap<String, Object>(instance.interceptors);
{
interceptors.put(interceptorClass.getName(), interceptor);
}
// Create an InterceptorData for the webservice interceptor to the list of interceptorDatas for this method
List<InterceptorData> interceptorDatas = new ArrayList<InterceptorData>();
{
final InterceptorData providerData = new InterceptorData(interceptorClass);
List<Method> aroundInvokes = interceptorCache.get(interceptorClass);
if (aroundInvokes == null) {
aroundInvokes = new ClassFinder(interceptorClass).findAnnotatedMethods(AroundInvoke.class);
if (SingletonContainer.class.getClassLoader() == interceptorClass.getClassLoader()) { // use cache only for server classes
final List<Method> value = new CopyOnWriteArrayList<Method>(aroundInvokes);
aroundInvokes = interceptorCache.putIfAbsent(interceptorClass, value); // ensure it to be thread safe
if (aroundInvokes == null) {
aroundInvokes = value;
}
}
}
providerData.getAroundInvoke().addAll(aroundInvokes);
interceptorDatas.add(0, providerData);
interceptorDatas.addAll(beanContext.getMethodInterceptors(runMethod));
}
InterceptorStack interceptorStack = new InterceptorStack(instance.bean, runMethod, Operation.BUSINESS_WS, interceptorDatas, interceptors);
Object[] params = new Object[runMethod.getParameterTypes().length];
if (messageContext instanceof javax.xml.rpc.handler.MessageContext) {
ThreadContext.getThreadContext().set(javax.xml.rpc.handler.MessageContext.class, (javax.xml.rpc.handler.MessageContext) messageContext);
return interceptorStack.invoke((javax.xml.rpc.handler.MessageContext) messageContext, params);
} else if (messageContext instanceof javax.xml.ws.handler.MessageContext) {
AddressingSupport wsaSupport = NoAddressingSupport.INSTANCE;
for (int i = 2; i < args.length; i++) {
if (args[i] instanceof AddressingSupport) {
wsaSupport = (AddressingSupport) args[i];
}
}
ThreadContext.getThreadContext().set(AddressingSupport.class, wsaSupport);
ThreadContext.getThreadContext().set(javax.xml.ws.handler.MessageContext.class, (javax.xml.ws.handler.MessageContext) messageContext);
return interceptorStack.invoke((javax.xml.ws.handler.MessageContext) messageContext, params);
}
throw new IllegalArgumentException("Uknown MessageContext type: " + messageContext.getClass().getName());
}
protected ProxyInfo createEJBObject(BeanContext beanContext, Method callMethod) {
return new ProxyInfo(beanContext, null);
}
}