/* * 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.cdi; import org.apache.openejb.BeanContext; import org.apache.openejb.BeanType; import org.apache.openejb.OpenEJBRuntimeException; import org.apache.openejb.assembler.classic.ProxyInterfaceResolver; import org.apache.openejb.core.ivm.BaseEjbProxyHandler; import org.apache.openejb.core.transaction.TransactionType; import org.apache.openejb.util.proxy.LocalBeanProxyFactory; import org.apache.openejb.util.proxy.ProxyManager; import org.apache.webbeans.component.BeanAttributesImpl; import org.apache.webbeans.component.InterceptedMarker; import org.apache.webbeans.config.DeploymentValidationService; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.container.InjectionTargetFactoryImpl; import org.apache.webbeans.context.creational.CreationalContextImpl; import org.apache.webbeans.ejb.common.component.BaseEjbBean; import org.apache.webbeans.intercept.InterceptorResolutionService; import org.apache.webbeans.portable.InjectionTargetImpl; import org.apache.webbeans.util.GenericsUtil; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.UndeclaredThrowableException; import java.rmi.NoSuchObjectException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import javax.decorator.Decorator; import javax.ejb.NoSuchEJBException; import javax.ejb.Remove; import javax.enterprise.context.Dependent; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.Typed; import javax.enterprise.inject.spi.AnnotatedMethod; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanAttributes; import javax.enterprise.inject.spi.DefinitionException; import javax.enterprise.inject.spi.InjectionPoint; import javax.enterprise.inject.spi.InjectionTarget; import javax.enterprise.inject.spi.SessionBeanType; import javax.interceptor.Interceptor; import javax.transaction.UserTransaction; public class CdiEjbBean<T> extends BaseEjbBean<T> implements InterceptedMarker, DeploymentValidationService.BeanInterceptorInfoProvider { private final Map<Integer, Object> dependentSFSBToBeRemoved = new ConcurrentHashMap<Integer, Object>(); private final BeanContext beanContext; private final boolean isDependentAndStateful; private final boolean passivable; // initialized a bit later in the lifecycle but could be final otherwise private BeanContext.BusinessLocalBeanHome homeLocalBean; private BeanContext.BusinessLocalHome home; private BeanContext.BusinessRemoteHome remote; public CdiEjbBean(final BeanContext beanContext, final WebBeansContext webBeansContext, final AnnotatedType<T> at, final BeanAttributes<T> attributes) { this(beanContext, webBeansContext, beanContext.getManagedClass(), at, new EjbInjectionTargetFactory<T>(beanContext, at, webBeansContext), attributes); EjbInjectionTargetImpl.class.cast(getInjectionTarget()).setCdiEjbBean(this); } public CdiEjbBean(final BeanContext bc, final WebBeansContext webBeansContext, final Class beanClass, final AnnotatedType<T> at, final InjectionTargetFactoryImpl<T> factory, final BeanAttributes<T> attributes) { super(webBeansContext, toSessionType(bc.getComponentType()), at, new EJBBeanAttributesImpl<T>(bc, attributes), beanClass, factory); this.beanContext = bc; bc.set(Bean.class, this); passivatingId = bc.getDeploymentID() + getReturnType().getName(); final boolean stateful = BeanType.STATEFUL.equals(bc.getComponentType()); final boolean isDependent = getScope().equals(Dependent.class); isDependentAndStateful = isDependent && stateful; if (webBeansContext.getBeanManagerImpl().isPassivatingScope(getScope()) && stateful) { if (!getBeanContext().isPassivable()) { throw new DefinitionException( getBeanContext().getBeanClass() + " is a not apssivation-capable @Stateful with a scope " + getScope().getSimpleName() + " which need passivation"); } passivable = true; } else { passivable = false; } if (!isDependent) { for (final Type type : attributes.getTypes()) { if (ParameterizedType.class.isInstance(type)) { throw new DefinitionException("Parameterized session bean should be @Dependent: " + beanClass); } } } if (getAnnotatedType().isAnnotationPresent(Interceptor.class) || getAnnotatedType().isAnnotationPresent(Decorator.class)) { throw new DefinitionException("An EJB can't be an interceptor or a decorator: " + beanClass); } } @Override public InterceptorResolutionService.BeanInterceptorInfo interceptorInfo() { return EjbInjectionTargetImpl.class.cast(getInjectionTarget()).getInterceptorInfo(); } @Override public boolean isPassivationCapable() { return passivable; } public BeanContext getBeanContext() { return this.beanContext; } private static SessionBeanType toSessionType(final BeanType beanType) { switch (beanType) { case SINGLETON: return SessionBeanType.SINGLETON; case MESSAGE_DRIVEN: // OWB implementation test stateful or not so do we really care? case STATELESS: case MANAGED: // can't be stateful since it will prevent every integration using ManagedBean to get injections to work + it is never used return SessionBeanType.STATELESS; case STATEFUL: return SessionBeanType.STATEFUL; default: throw new IllegalStateException("Unknown Session BeanType " + beanType); } } public String getEjbName() { return this.beanContext.getEjbName(); } public boolean needsBeanLocalViewAddedToTypes() { return beanContext.isLocalbean() && beanContext.getBeanClass().getAnnotation(Typed.class) == null; } @Override public List<Class<?>> getBusinessLocalInterfaces() { final List<Class<?>> classes = new ArrayList<Class<?>>(); for (final Type t : getTypes()) { if (Class.class.isInstance(t)) { classes.add(Class.class.cast(t)); } } return classes; } public void destroyComponentInstance(final T instance) { if (getScope() == null || Dependent.class == getScope()) { destroyStatefulSessionBeanInstance(instance, null); } else { destroyScopedStateful(instance, null); } } @Override protected void destroyStatefulSessionBeanInstance(final T proxyInstance, final Object unused) { if (proxyInstance instanceof BeanContext.Removable) { try { ((BeanContext.Removable) proxyInstance).$$remove(); } catch (final NoSuchEJBException nsee) { // no-op } catch (final UndeclaredThrowableException nsoe) { if (!(nsoe.getCause() instanceof NoSuchObjectException)) { throw nsoe; } } catch (final Exception e) { if (!(e instanceof NoSuchObjectException)) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new OpenEJBRuntimeException(e); } } } } @Override public List<Method> getRemoveMethods() { // Should we delegate to super and merge both? if (beanContext.isLocalbean()) { return findRemove(beanContext.getBeanClass(), beanContext.getBeanClass()); } return findRemove(beanContext.getBeanClass(), beanContext.getBusinessLocalInterface()); } private List<Method> findRemove(final Class<?> beanClass, final Class<?> beanInterface) { final List<Method> toReturn = new ArrayList<Method>(); // Get all the public methods of the bean class and super class final Method[] methods = beanClass.getMethods(); // Search for methods annotated with @Remove for (final Method method : methods) { final Remove annotation = method.getAnnotation(Remove.class); if (annotation != null) { // Get the corresponding method into the bean interface final Method interfaceMethod; try { interfaceMethod = beanInterface.getMethod(method.getName(), method.getParameterTypes()); toReturn.add(interfaceMethod); } catch (final SecurityException e) { e.printStackTrace(); } catch (final NoSuchMethodException e) { // The method can not be into the interface in which case we // don't wonder of } } } return toReturn; } protected T createEjb(final CreationalContext<T> creationalContext) { final CurrentCreationalContext currentCreationalContext = beanContext.get(CurrentCreationalContext.class); final CreationalContext existing = currentCreationalContext.get(); currentCreationalContext.set(creationalContext); try { final T instance; if (homeLocalBean != null) { instance = (T) homeLocalBean.create(); } else if (home != null) { instance = (T) home.create(); } else if (remote != null) { instance = (T) remote.create(); } else { // shouldn't be called for an MDB throw new IllegalStateException("no interface to proxy for ejb " + beanContext.getEjbName() + ", is this is a MDB maybe you shouldn't use a scope?"); } if (isDependentAndStateful) { CreationalContextImpl.class.cast(creationalContext).addDependent(this, instance); } return instance; } finally { currentCreationalContext.set(existing); } } private void destroyScopedStateful(final T instance, final CreationalContext<T> cc) { try { instance.hashCode(); // force the instance to be created - otherwise we'll miss @PreDestroy for instance } catch (final NoSuchEJBException e) { InvocationHandler handler = null; if (LocalBeanProxyFactory.isProxy(instance.getClass())) { handler = LocalBeanProxyFactory.getInvocationHandler(instance); } else if (ProxyManager.isProxyClass(instance.getClass())) { handler = ProxyManager.getInvocationHandler(instance); } if (BaseEjbProxyHandler.class.isInstance(handler) && !BaseEjbProxyHandler.class.cast(handler).isValid()) { return; // already destroyed } // else log error logger.log(Level.FINE, "The stateful instance " + instance + " can't be removed since it was invalidated", e); return; } final Object ejbInstance = dependentSFSBToBeRemoved.remove(System.identityHashCode(instance)); if (ejbInstance != null) { destroyStatefulSessionBeanInstance((T) ejbInstance, cc); } else { destroyStatefulSessionBeanInstance(instance, cc); } } public void storeStatefulInstance(final Object proxy, final T instance) { dependentSFSBToBeRemoved.put(System.identityHashCode(proxy), instance); } public void initInternals() { final List<Class> classes = beanContext.getBusinessLocalInterfaces(); final boolean noLocalInterface = classes.isEmpty(); if (beanContext.getComponentType().isMessageDriven()) { homeLocalBean = null; home = null; remote = null; } else if (beanContext.isLocalbean() || (noLocalInterface && beanContext.getBusinessRemoteInterfaces().isEmpty() /*EJB2*/)) { homeLocalBean = beanContext.getBusinessLocalBeanHome(); home = null; remote = null; } else if (!noLocalInterface) { final Class<?> mainInterface = classes.get(0); final List<Class> interfaces = ProxyInterfaceResolver.getInterfaces(beanContext.getBeanClass(), mainInterface, classes); interfaces.addAll(beanContext.getBusinessRemoteInterfaces()); home = beanContext.getBusinessLocalHome(interfaces, mainInterface); homeLocalBean = null; remote = null; } else { final Class<?> mainInterface = beanContext.getBusinessRemoteInterface(); final List<Class> interfaces = ProxyInterfaceResolver.getInterfaces(beanContext.getBeanClass(), mainInterface, beanContext.getBusinessRemoteInterfaces()); remote = beanContext.getBusinessRemoteHome(interfaces, mainInterface); home = null; homeLocalBean = null; } } public static class EJBBeanAttributesImpl<T> extends BeanAttributesImpl<T> { // TODO: move it in its own class private final BeanContext beanContext; private final Set<Type> ejbTypes; public EJBBeanAttributesImpl(final BeanContext bc, final BeanAttributes<T> beanAttributes) { super(beanAttributes, false); this.beanContext = bc; this.ejbTypes = new HashSet<Type>(); initTypes(); } @Override public Set<Type> getTypes() { return ejbTypes; } public void initTypes() { if (beanContext.isLocalbean()) { addApiTypes(ejbTypes, beanContext.getBeanClass()); } if (beanContext.getProxyClass() != null) { addApiTypes(ejbTypes, beanContext.getProxyClass()); } final List<Class> cl = beanContext.getBusinessLocalInterfaces(); if (cl != null && !cl.isEmpty()) { final Map<Class<?>, Type> apis = new HashMap<>(cl.size()); for (final Type t : beanContext.getManagedClass().getGenericInterfaces()) { if (ParameterizedType.class.isInstance(t)) { try { apis.put(Class.class.cast(ParameterizedType.class.cast(t).getRawType()), t); } catch (final Throwable th) { // no-op } } } for (final Class<?> c : cl) { final Type type = apis.get(c); ejbTypes.addAll(GenericsUtil.getTypeClosure(type != null ? type : c)); } } /* not in EJB types - 3.2.2 of cdi 1.2 final List<Class> clRemote = beanContext.getBusinessRemoteInterfaces(); if (clRemote != null && !clRemote.isEmpty()) { for (final Class<?> c : clRemote) { ejbTypes.add(c); // parentInterfaces(c), but is it useful in practise? } } */ ejbTypes.add(Object.class); } private static void addApiTypes(final Collection<Type> clazzes, final Class<?> beanClass) { final Typed typed = beanClass.getAnnotation(Typed.class); if (typed == null || typed.value().length == 0) { Type current = beanClass; while (current != null && Object.class != current) { clazzes.add(current); // TODO: better loop current = Class.class.isInstance(current) ? Class.class.cast(current).getGenericSuperclass() : null; } } else { Collections.addAll(clazzes, typed.value()); } } } public static class EjbInjectionTargetFactory<T> extends InjectionTargetFactoryImpl<T> { private final BeanContext beanContext; public EjbInjectionTargetFactory(final BeanContext bc, final AnnotatedType<T> annotatedType, final WebBeansContext webBeansContext) { super(annotatedType, webBeansContext); this.beanContext = bc; } @Override public InjectionTarget<T> createInjectionTarget(final Bean<T> bean) { final EjbInjectionTargetImpl<T> injectionTarget = new EjbInjectionTargetImpl<T>(getAnnotatedType(), createInjectionPoints(bean), getWebBeansContext()); final InjectionTarget<T> it = getWebBeansContext().getWebBeansUtil().fireProcessInjectionTargetEvent(injectionTarget, getAnnotatedType()).getInjectionTarget(); for (final InjectionPoint ip : it.getInjectionPoints()) { if (ip.getType() != UserTransaction.class) { continue; } if (beanContext.getTransactionType() != TransactionType.BeanManaged) { throw new DefinitionException("@Inject UserTransaction is only valid for BeanManaged beans"); } } if (!EjbInjectionTargetImpl.class.isInstance(it)) { return new EjbInjectionTargetImpl<T>(injectionTarget, it); } return it; } @Override protected List<AnnotatedMethod<?>> getPostConstructMethods() { return Collections.emptyList(); } @Override protected List<AnnotatedMethod<?>> getPreDestroyMethods() { return Collections.emptyList(); } } public static class EjbInjectionTargetImpl<T> extends InjectionTargetImpl<T> { private CdiEjbBean<T> bean; private InjectionTarget<T> delegate; public EjbInjectionTargetImpl(final AnnotatedType<T> annotatedType, final Set<InjectionPoint> points, final WebBeansContext webBeansContext) { super(annotatedType, points, webBeansContext, Collections.<AnnotatedMethod<?>>emptyList(), Collections.<AnnotatedMethod<?>>emptyList()); } public EjbInjectionTargetImpl(final EjbInjectionTargetImpl<T> original, final InjectionTarget<T> delegate) { super(original.annotatedType, original.getInjectionPoints(), original.webBeansContext, Collections.<AnnotatedMethod<?>>emptyList(), Collections.<AnnotatedMethod<?>>emptyList()); this.delegate = delegate; } public void setCdiEjbBean(final CdiEjbBean<T> bean) { this.bean = bean; } @Override protected void defineLifecycleInterceptors(final Bean<T> bean, final AnnotatedType<T> annotatedType, final WebBeansContext webBeansContext) { if (!isDynamicBean(bean)) { super.defineLifecycleInterceptors(bean, annotatedType, webBeansContext); } } @Override public void defineInterceptorStack(final Bean<T> bean, final AnnotatedType<T> annotatedType, final WebBeansContext webBeansContext) { super.defineInterceptorStack(bean, isDynamicBean(bean) ? (AnnotatedType<T>) webBeansContext.getAnnotatedElementFactory() .newAnnotatedType(CdiEjbBean.class.cast(bean).getBeanContext().getManagedClass()) : annotatedType, webBeansContext); } @Override protected boolean needsProxy() { return !bean.beanContext.isDynamicallyImplemented() && super.needsProxy(); } @Override public T produce(final CreationalContext<T> creationalContext) { if (delegate == null) { return bean.createEjb(creationalContext); } return delegate.produce(creationalContext); } @Override public void dispose(final T instance) { if (delegate == null) { bean.destroyComponentInstance(instance); } else { delegate.dispose(instance); } } @Override public Set<InjectionPoint> getInjectionPoints() { if (delegate == null) { return super.getInjectionPoints(); } return delegate.getInjectionPoints(); } @Override public void inject(final T instance, final CreationalContext<T> ctx) { if (delegate == null) { super.inject(instance, ctx); } else { delegate.inject(instance, ctx); } } @Override public void postConstruct(final T instance) { if (delegate == null) { super.postConstruct(instance); } else { delegate.postConstruct(instance); } } @Override public void preDestroy(final T instance) { if (delegate == null) { super.preDestroy(instance); } else { delegate.preDestroy(instance); } } public T createNewPojo(final CreationalContext<T> creationalContext) { final CreationalContextImpl<T> ccImpl = CreationalContextImpl.class.cast(creationalContext); // super.produce(cc) will not work since we need the unproxied instance - decorator case return (T) super.produce(super.createInterceptorInstances(ccImpl), ccImpl); } private static boolean isDynamicBean(final Bean<?> bean) { return CdiEjbBean.class.isInstance(bean) && CdiEjbBean.class.cast(bean).beanContext.isDynamicallyImplemented(); } } }