/* * 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.stateless; 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.SystemException; import org.apache.openejb.api.resource.DestroyableResource; import org.apache.openejb.cdi.CurrentCreationalContext; 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.security.AbstractSecurityService; 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.DaemonThreadFactory; import org.apache.openejb.util.Duration; import org.apache.openejb.util.Pool; import org.apache.xbean.finder.ClassFinder; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executors; import javax.interceptor.AroundInvoke; import javax.security.auth.login.LoginException; import static org.apache.openejb.core.transaction.EjbTransactionUtil.afterInvoke; import static org.apache.openejb.core.transaction.EjbTransactionUtil.createTransactionPolicy; import static org.apache.openejb.core.transaction.EjbTransactionUtil.handleApplicationException; import static org.apache.openejb.core.transaction.EjbTransactionUtil.handleSystemException; /** * @org.apache.xbean.XBean element="statelessContainer" */ public class StatelessContainer implements org.apache.openejb.RpcContainer, DestroyableResource { private final ConcurrentMap<Class<?>, List<Method>> interceptorCache = new ConcurrentHashMap<Class<?>, List<Method>>(); private final StatelessInstanceManager instanceManager; private final Map<String, BeanContext> deploymentRegistry = new ConcurrentHashMap<String, BeanContext>(); private final Object containerID; private final SecurityService securityService; public StatelessContainer(final Object id, final SecurityService securityService, final Duration accessTimeout, final Duration closeTimeout, final Pool.Builder poolBuilder, final int callbackThreads, final boolean useOneSchedulerThreadByBean, final int evictionThreads) { this.containerID = id; this.securityService = securityService; this.instanceManager = new StatelessInstanceManager( securityService, accessTimeout, closeTimeout, poolBuilder, callbackThreads, useOneSchedulerThreadByBean ? null : Executors.newScheduledThreadPool(Math.max(evictionThreads, 1), new DaemonThreadFactory(id))); } @Override public BeanContext[] getBeanContexts() { return this.deploymentRegistry.values().toArray(new BeanContext[this.deploymentRegistry.size()]); } @Override public BeanContext getBeanContext(final Object deploymentID) { final String id = (String) deploymentID; return deploymentRegistry.get(id); } @Override public ContainerType getContainerType() { return ContainerType.STATELESS; } @Override public Object getContainerID() { return containerID; } @Override public void deploy(final BeanContext beanContext) throws OpenEJBException { final String id = (String) beanContext.getDeploymentID(); deploymentRegistry.put(id, beanContext); beanContext.setContainer(this); // add it before starting the timer (@PostCostruct) if (StatsInterceptor.isStatsActivated()) { final StatsInterceptor stats = new StatsInterceptor(beanContext.getBeanClass()); beanContext.addFirstSystemInterceptor(stats); } } @Override public void start(final BeanContext beanContext) throws OpenEJBException { this.instanceManager.deploy(beanContext); final EjbTimerService timerService = beanContext.getEjbTimerService(); if (timerService != null) { timerService.start(); } } @Override public void stop(final BeanContext beanContext) throws OpenEJBException { beanContext.stop(); } @Override public void undeploy(final BeanContext beanContext) { this.instanceManager.undeploy(beanContext); final String id = (String) beanContext.getDeploymentID(); beanContext.setContainer(null); beanContext.setContainerData(null); this.deploymentRegistry.remove(id); } @SuppressWarnings("unchecked") @Override public Object invoke(final Object deployID, InterfaceType type, final Class callInterface, final Method callMethod, final Object[] args, final Object primKey) throws OpenEJBException { final BeanContext beanContext = this.getBeanContext(deployID); if (beanContext == null) { final String msg = "Deployment does not exist in this container. Deployment(id='" + deployID + "'), Container(id='" + containerID + "')"; throw new OpenEJBException(msg); } // Use the backup way to determine call type if null was supplied. if (type == null) { type = beanContext.getInterfaceType(callInterface); } final Method runMethod = beanContext.getMatchingBeanMethod(callMethod); final ThreadContext callContext = new ThreadContext(beanContext, primKey); final ThreadContext oldCallContext = ThreadContext.enter(callContext); Instance bean = null; final CurrentCreationalContext currentCreationalContext = beanContext.get(CurrentCreationalContext.class); Object runAs = null; try { if (oldCallContext != null) { final BeanContext oldBc = oldCallContext.getBeanContext(); if (oldBc.getRunAsUser() != null || oldBc.getRunAs() != null) { runAs = AbstractSecurityService.class.cast(securityService).overrideWithRunAsContext(callContext, beanContext, oldBc); } } //Check auth before overriding context final boolean authorized = type == InterfaceType.TIMEOUT || this.securityService.isCallerAuthorized(callMethod, type); if (!authorized) { throw new org.apache.openejb.ApplicationException(new javax.ejb.EJBAccessException("Unauthorized Access by Principal Denied")); } final Class declaringClass = callMethod.getDeclaringClass(); if (javax.ejb.EJBHome.class.isAssignableFrom(declaringClass) || javax.ejb.EJBLocalHome.class.isAssignableFrom(declaringClass)) { if (callMethod.getName().startsWith("create")) { return new ProxyInfo(beanContext, null); } else { return null; // EJBHome.remove( ) and other EJBHome methods are not process by the container } } else if (javax.ejb.EJBObject.class == declaringClass || javax.ejb.EJBLocalObject.class == declaringClass) { return null; // EJBObject.remove( ) and other EJBObject methods are not process by the container } bean = this.instanceManager.getInstance(callContext); callContext.setCurrentOperation(type == InterfaceType.TIMEOUT ? Operation.TIMEOUT : Operation.BUSINESS); callContext.set(Method.class, runMethod); callContext.setInvokedInterface(callInterface); if (currentCreationalContext != null) { currentCreationalContext.set(bean.creationalContext); } return _invoke(callMethod, runMethod, args, bean, callContext, type); } finally { if (runAs != null) { try { securityService.associate(runAs); } catch (final LoginException e) { // no-op } } if (bean != null) { if (callContext.isDiscardInstance()) { this.instanceManager.discardInstance(callContext, bean); } else { this.instanceManager.poolInstance(callContext, bean); } } ThreadContext.exit(oldCallContext); if (currentCreationalContext != null) { currentCreationalContext.remove(); } } } @SuppressWarnings("ThrowFromFinallyBlock") private Object _invoke(final Method callMethod, final Method runMethod, final Object[] args, final Instance instance, final ThreadContext callContext, final InterfaceType type) throws OpenEJBException { final BeanContext beanContext = callContext.getBeanContext(); final TransactionPolicy txPolicy = createTransactionPolicy(beanContext.getTransactionType(callMethod, type), callContext); Object returnValue = null; try { if (type == InterfaceType.SERVICE_ENDPOINT) { callContext.setCurrentOperation(Operation.BUSINESS_WS); returnValue = invokeWebService(args, beanContext, runMethod, instance); } else { final List<InterceptorData> interceptors = beanContext.getMethodInterceptors(runMethod); final Operation operation = type == InterfaceType.TIMEOUT ? Operation.TIMEOUT : Operation.BUSINESS; final InterceptorStack interceptorStack = new InterceptorStack(instance.bean, runMethod, operation, interceptors, instance.interceptors); returnValue = interceptorStack.invoke(args); } } catch (final Throwable re) {// handle reflection exception final ExceptionType exceptionType = beanContext.getExceptionType(re); if (exceptionType == 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. // In case of StrictPooling flag being set to true we also release the semaphore // in the discardInstance method of the instanceManager. callContext.setDiscardInstance(true); handleSystemException(txPolicy, re, callContext); } else { /* Application Exception ***********************/ handleApplicationException(txPolicy, re, exceptionType == ExceptionType.APPLICATION_ROLLBACK); } } finally { try { afterInvoke(txPolicy, callContext); } catch (final SystemException | RuntimeException e) { callContext.setDiscardInstance(true); throw e; } } return returnValue; } private Object invokeWebService(final Object[] args, final BeanContext beanContext, final Method runMethod, final Instance instance) throws Exception { if (args.length < 2) { throw new IllegalArgumentException("WebService calls must follow format {messageContext, interceptor, [arg...]}."); } final Object messageContext = args[0]; // 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. final Object interceptor = args[1]; final Class<?> interceptorClass = interceptor.getClass(); // Add the webservice interceptor to the list of interceptor instances final Map<String, Object> interceptors = new HashMap<String, Object>(instance.interceptors); interceptors.put(interceptor.getClass().getName(), interceptor); // Create an InterceptorData for the webservice interceptor to the list of interceptorDatas for this method final List<InterceptorData> interceptorDatas = new ArrayList<InterceptorData>(); final InterceptorData providerData = new InterceptorData(interceptorClass); providerData.getAroundInvoke().addAll(retrieveAroundInvokes(interceptorClass)); interceptorDatas.add(0, providerData); interceptorDatas.addAll(beanContext.getMethodInterceptors(runMethod)); final InterceptorStack interceptorStack = new InterceptorStack(instance.bean, runMethod, Operation.BUSINESS_WS, interceptorDatas, interceptors); final Object[] params = new Object[runMethod.getParameterTypes().length]; final ThreadContext threadContext = ThreadContext.getThreadContext(); Object returnValue = null; if (messageContext instanceof javax.xml.rpc.handler.MessageContext) { threadContext.set(javax.xml.rpc.handler.MessageContext.class, (javax.xml.rpc.handler.MessageContext) messageContext); returnValue = 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.set(AddressingSupport.class, wsaSupport); threadContext.set(javax.xml.ws.handler.MessageContext.class, (javax.xml.ws.handler.MessageContext) messageContext); returnValue = interceptorStack.invoke((javax.xml.ws.handler.MessageContext) messageContext, params); } return returnValue; } private List<Method> retrieveAroundInvokes(final Class<?> interceptorClass) { final List<Method> cached = this.interceptorCache.get(interceptorClass); if (cached != null) { return cached; } final ClassFinder finder = new ClassFinder(interceptorClass); List<Method> annotated = finder.findAnnotatedMethods(AroundInvoke.class); if (StatelessContainer.class.getClassLoader() == interceptorClass.getClassLoader()) { // use cache only for server classes final List<Method> value = new CopyOnWriteArrayList<Method>(annotated); annotated = this.interceptorCache.putIfAbsent(interceptorClass, annotated); // ensure it to be thread safe if (annotated == null) { annotated = value; } } return annotated; } @Override public void destroyResource() { this.instanceManager.destroy(); } }