/*
 * 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.InterfaceType;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.core.ivm.IntraVmProxy;
import org.apache.openejb.util.proxy.ProxyManager;
import org.apache.webbeans.component.AbstractOwbBean;
import org.apache.webbeans.component.OwbBean;
import org.apache.webbeans.component.ProducerFieldBean;
import org.apache.webbeans.component.ProducerMethodBean;
import org.apache.webbeans.component.WebBeansType;
import org.apache.webbeans.component.creation.BeanAttributesBuilder;
import org.apache.webbeans.component.creation.ObserverMethodsBuilder;
import org.apache.webbeans.component.creation.ProducerFieldBeansBuilder;
import org.apache.webbeans.component.creation.ProducerMethodBeansBuilder;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.config.WebBeansFinder;
import org.apache.webbeans.container.BeanManagerImpl;
import org.apache.webbeans.event.ObserverMethodImpl;
import org.apache.webbeans.exception.WebBeansConfigurationException;
import org.apache.webbeans.portable.events.discovery.BeforeShutdownImpl;
import org.apache.webbeans.portable.events.generics.GProcessSessionBean;
import org.apache.webbeans.proxy.NormalScopeProxyFactory;
import org.apache.webbeans.spi.ResourceInjectionService;
import org.apache.webbeans.spi.SecurityService;
import org.apache.webbeans.spi.TransactionService;
import org.apache.webbeans.spi.plugins.AbstractOwbPlugin;
import org.apache.webbeans.spi.plugins.OpenWebBeansEjbPlugin;
import org.apache.webbeans.spi.plugins.OpenWebBeansJavaEEPlugin;
import org.apache.webbeans.util.GenericsUtil;
import org.apache.webbeans.util.WebBeansUtil;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.ObservesAsync;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Specializes;
import javax.enterprise.inject.Vetoed;
import javax.enterprise.inject.spi.AnnotatedField;
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.ObserverMethod;
import javax.enterprise.inject.spi.PassivationCapable;
import javax.enterprise.inject.spi.Producer;
import javax.enterprise.inject.spi.SessionBeanType;
import javax.inject.Provider;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletContextListener;
import javax.servlet.jsp.tagext.JspTag;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
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.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;


public class CdiPlugin extends AbstractOwbPlugin implements OpenWebBeansJavaEEPlugin, OpenWebBeansEjbPlugin {

    private Map<Class<?>, BeanContext> beans;

    private WebBeansContext webBeansContext;
    private ClassLoader classLoader;

    private Map<Contextual<?>, Object> cacheProxies;

    public void setWebBeansContext(final WebBeansContext webBeansContext) {
        this.webBeansContext = webBeansContext;
        if (webBeansContext == null) {
            return;
        }
        if (!WebappWebBeansContext.class.isInstance(webBeansContext)) {
            cacheProxies = new ConcurrentHashMap<>();
        } else { // share cache of proxies between the whole app otherwise hard to share an EJB between a webapp and the lib part of the app
            final WebBeansContext parent = WebappWebBeansContext.class.cast(webBeansContext).getParent();
            if (parent != null) {
                cacheProxies = CdiPlugin.class.cast(parent.getPluginLoader().getEjbPlugin()).cacheProxies;
            } else {
                cacheProxies = new ConcurrentHashMap<>();
            }
        }
    }


    @Override
    public boolean isEEComponent(final Class<?> impl) {
        return Servlet.class.isAssignableFrom(impl)
                || Filter.class.isAssignableFrom(impl)
                || ServletContextListener.class.isAssignableFrom(impl)
                || JspTag.class.isAssignableFrom(impl);
    }

    @Override
    public void registerEEBeans()
    {
        BeanManagerImpl beanManagerImpl = webBeansContext.getBeanManagerImpl();
        beanManagerImpl.addInternalBean(new org.apache.webbeans.ee.beans.ValidatorBean(webBeansContext));
        beanManagerImpl.addInternalBean(new org.apache.webbeans.ee.beans.ValidatorFactoryBean(webBeansContext));
        beanManagerImpl.addInternalBean(new org.apache.webbeans.ee.beans.UserTransactionBean(webBeansContext));
    }

    public void setClassLoader(final ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void shutDown() {
        super.shutDown();
        // this plugin may have been installed in a non-ejb lifecycle???
        if (beans != null) {
            this.beans.clear();
        }
    }

    public <T> BeanAttributes<T> createBeanAttributes(final AnnotatedType<T> type) {
        return new CdiEjbBean.EJBBeanAttributesImpl(
                findBeanContext(webBeansContext, type.getJavaClass()),
                BeanAttributesBuilder.forContext(webBeansContext).newBeanAttibutes(type).build());
    }

    public void configureDeployments(final List<BeanContext> ejbDeployments) {
        beans = new WeakHashMap<>();
        for (final BeanContext deployment : ejbDeployments) {
            if (deployment.getComponentType().isCdiCompatible()) {
                if (deployment.isLocalbean() && !deployment.isDynamicallyImplemented()) {
                    beans.put(deployment.get(BeanContext.ProxyClass.class).getProxy(), deployment);
                }
                beans.put(deployment.getBeanClass(), deployment);
            }
        }
    }


    public void stop() throws OpenEJBException {
        final ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
        try {
            // Setting context class loader for cleaning
            Thread.currentThread().setContextClassLoader(classLoader);

            // Fire shut down
            webBeansContext.getBeanManagerImpl().fireEvent(new BeforeShutdownImpl());

            // Destroys context
            webBeansContext.getContextsService().destroy(null);

            // Free all plugin resources
            webBeansContext.getPluginLoader().shutDown();

            // Clear extensions
            webBeansContext.getExtensionLoader().clear();

            // Delete Resolutions Cache
            webBeansContext.getBeanManagerImpl().getInjectionResolver().clearCaches();

            // Delete AnnotateTypeCache
            webBeansContext.getAnnotatedElementFactory().clear();

            // Clear the resource injection service
            final CdiResourceInjectionService injectionServices = (CdiResourceInjectionService) webBeansContext.getService(ResourceInjectionService.class);
            injectionServices.clear();

            // Clear singleton list
            WebBeansFinder.clearInstances(WebBeansUtil.getCurrentClassLoader());

        } catch (final Exception e) {
            throw new OpenEJBException(e);
        } finally {
            Thread.currentThread().setContextClassLoader(oldCl);
        }
    }

    @Override
    public <T> T getSupportedService(final Class<T> serviceClass) {
        return supportService(serviceClass) ? serviceClass.cast(this) : null;
    }

    @Override
    public void isManagedBean(final Class<?> clazz) {
    }

    @Override
    public boolean supportService(final Class<?> serviceClass) {
        return serviceClass == TransactionService.class || serviceClass == SecurityService.class;
    }

    @Override
    public Object getSessionBeanProxy(final Bean<?> inBean, final Class<?> interfce, final CreationalContext<?> creationalContext) {
        Object instance = cacheProxies.get(inBean);
        if (instance != null) {
            return instance;
        }

        synchronized (inBean) { // singleton for the app so safe to sync on it
            instance = cacheProxies.get(inBean);
            if (instance != null) {
                return instance;
            }

            final Class<? extends Annotation> scopeClass = inBean.getScope();
            final CdiEjbBean<Object> cdiEjbBean = (CdiEjbBean<Object>) inBean;
            final CreationalContext<Object> cc = (CreationalContext<Object>) creationalContext;

            if (scopeClass == null || Dependent.class == scopeClass) { // no need to add any layer, null = @New
                return cdiEjbBean.createEjb(cc);
            }

            // only stateful normally
            final InstanceBean<Object> bean = new InstanceBean<>(cdiEjbBean);
            if (webBeansContext.getBeanManagerImpl().isNormalScope(scopeClass)) {
                final BeanContext beanContext = cdiEjbBean.getBeanContext();
                final Provider provider = webBeansContext.getNormalScopeProxyFactory().getInstanceProvider(beanContext.getClassLoader(), cdiEjbBean);

                if (!beanContext.isLocalbean()) {
                    final List<Class> interfaces = new ArrayList<>();
                    final InterfaceType type = beanContext.getInterfaceType(interfce);
                    if (type != null) {
                        interfaces.addAll(beanContext.getInterfaces(type));
                    } else { // can happen when looked up from impl instead of API in OWB -> default to business local
                        interfaces.addAll(beanContext.getInterfaces(InterfaceType.BUSINESS_LOCAL));
                    }
                    interfaces.add(Serializable.class);
                    interfaces.add(IntraVmProxy.class);
                    if (BeanType.STATEFUL.equals(beanContext.getComponentType()) || BeanType.MANAGED.equals(beanContext.getComponentType())) {
                        interfaces.add(BeanContext.Removable.class);
                    }

                    try {
                        instance = ProxyManager.newProxyInstance(interfaces.toArray(new Class<?>[interfaces.size()]), new InvocationHandler() {
                            @Override
                            public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
                                try {
                                    return method.invoke(provider.get(), args);
                                } catch (final InvocationTargetException ite) {
                                    throw ite.getCause();
                                }
                            }
                        });
                    } catch (final IllegalAccessException e) {
                        throw new OpenEJBRuntimeException(e);
                    }

                } else {
                    final NormalScopeProxyFactory normalScopeProxyFactory = webBeansContext.getNormalScopeProxyFactory();
                    final Class<?> proxyClass = normalScopeProxyFactory.createProxyClass(beanContext.getClassLoader(), beanContext.getBeanClass());
                    instance = normalScopeProxyFactory.createProxyInstance(proxyClass, provider);
                }

                cacheProxies.put(inBean, instance);
            } else {
                final Context context = webBeansContext.getBeanManagerImpl().getContext(scopeClass);
                instance = context.get(bean, cc);
            }
            bean.setOwbProxy(instance);
            return instance;
        }
    }

    @Override
    public boolean isSessionBean(final Class<?> clazz) {
        // this may be called from a web app without ejbs in which case beans will not have been initialized by openejb.
        return beans != null && beans.containsKey(clazz);
    }

    @Override
    public boolean isNewSessionBean(final Class<?> clazz) {
        // this may be called from a web app without ejbs in which case beans will not have been initialized by openejb.
        return isNewSessionBean(webBeansContext, clazz) || isNewSessionBean(superContext(webBeansContext), clazz);
    }

    @Override
    public <T> Bean<T> defineSessionBean(final Class<T> clazz, final BeanAttributes<T> attributes, final AnnotatedType<T> annotatedType) {
        final BeanContext bc = findBeanContext(webBeansContext, clazz);
        final Class<?> superClass = bc.getManagedClass().getSuperclass();
        if (annotatedType.isAnnotationPresent(Specializes.class)) {
            if (superClass != Object.class && !isSessionBean(superClass)) {
                throw new DefinitionException("You can only specialize another EJB: " + clazz);
            }
            final BeanContext parentBc = findBeanContext(webBeansContext, superClass);
            final List<Class> businessLocalInterfaces = new ArrayList<>(parentBc.getBusinessLocalInterfaces());
            for (final Class<?> api : bc.getBusinessLocalInterfaces()) {
                businessLocalInterfaces.removeAll(GenericsUtil.getTypeClosure(api));
            }
            if (!businessLocalInterfaces.isEmpty()) {
                throw new DefinitionException("You can only specialize another EJB with at least the same API: " + clazz);
            }
        }
        final CdiEjbBean<T> bean = new OpenEJBBeanBuilder<>(bc, webBeansContext, annotatedType, attributes).createBean(clazz, !annotatedType.isAnnotationPresent(Vetoed.class));

        bc.set(CdiEjbBean.class, bean);
        bc.set(CurrentCreationalContext.class, new CurrentCreationalContext());

        validateDisposeMethods(bean);
        validateScope(bean);

        final Set<ObserverMethod<?>> observerMethods;
        if (bean.isEnabled()) {
            observerMethods = new ObserverMethodsBuilder<>(webBeansContext, bean.getAnnotatedType()).defineObserverMethods(bean, true);
        } else {
            observerMethods = new HashSet<>();
        }

        final WebBeansUtil webBeansUtil = webBeansContext.getWebBeansUtil();

        final Set<ProducerFieldBean<?>> producerFields = new ProducerFieldBeansBuilder(bean.getWebBeansContext(), bean.getAnnotatedType()).defineProducerFields(bean);
        final Set<ProducerMethodBean<?>> producerMethods = new ProducerMethodBeansBuilder(bean.getWebBeansContext(), bean.getAnnotatedType()).defineProducerMethods(bean, producerFields);

        final Map<ProducerMethodBean<?>, AnnotatedMethod<?>> annotatedMethods = new HashMap<>();
        for (final ProducerMethodBean<?> producerMethod : producerMethods) {
            final AnnotatedMethod<?> method = webBeansContext.getAnnotatedElementFactory().newAnnotatedMethod(producerMethod.getCreatorMethod(), annotatedType);
            webBeansUtil.inspectDeploymentErrorStack("There are errors that are added by ProcessProducer event observers for "
                    + "ProducerMethods. Look at logs for further details");

            annotatedMethods.put(producerMethod, method);
        }

        final Map<ProducerFieldBean<?>, AnnotatedField<?>> annotatedFields = new HashMap<>();
        for (final ProducerFieldBean<?> producerField : producerFields) {
            if (!Modifier.isStatic(producerField.getCreatorField().getModifiers())) {
                throw new DefinitionException("In an EJB all producer fields should be static");
            }
            webBeansUtil.inspectDeploymentErrorStack("There are errors that are added by ProcessProducer event observers for"
                    + " ProducerFields. Look at logs for further details");

            annotatedFields.put(producerField,
                webBeansContext.getAnnotatedElementFactory().newAnnotatedField(
                    producerField.getCreatorField(),
                    webBeansContext.getAnnotatedElementFactory().newAnnotatedType(producerField.getBeanClass())));
        }

        final Map<ObserverMethod<?>, AnnotatedMethod<?>> observerMethodsMap = new HashMap<>();
        for (final ObserverMethod<?> observerMethod : observerMethods) {
            final ObserverMethodImpl<?> impl = (ObserverMethodImpl<?>) observerMethod;
            final AnnotatedMethod<?> method = impl.getObserverMethod();

            observerMethodsMap.put(observerMethod, method);
        }

        validateProduceMethods(bean, producerMethods);
        validateObserverMethods(bean, observerMethodsMap);

        final BeanManagerImpl beanManager = webBeansContext.getBeanManagerImpl();

        //Fires ProcessManagedBean
        final GProcessSessionBean event = new GProcessSessionBean(Bean.class.cast(bean), annotatedType, bc.getEjbName(), bean.getEjbType());
        beanManager.fireEvent(event, true);
        event.setStarted();
        webBeansUtil.inspectDeploymentErrorStack("There are errors that are added by ProcessSessionBean event observers for managed beans. Look at logs for further details");

        //Fires ProcessProducerMethod
        webBeansUtil.fireProcessProducerMethodBeanEvent(annotatedMethods, annotatedType);
        webBeansUtil.inspectDeploymentErrorStack("There are errors that are added by ProcessProducerMethod event observers for producer method beans. Look at logs for further details");

        //Fires ProcessProducerField
        webBeansUtil.fireProcessProducerFieldBeanEvent(annotatedFields);
        webBeansUtil.inspectDeploymentErrorStack("There are errors that are added by ProcessProducerField event observers for producer field beans. Look at logs for further details");

        //Fire ProcessObserverMethods
        //X TODO ProcessObserverMethod now has a way to SET a new ObserverMethod. So the old method doesn't work anymore
        //X TODO created TOMEE-2117 for it.
        //X webBeansUtil.fireProcessObserverMethodBeanEvent(observerMethodsMap);
        //X webBeansUtil.inspectDeploymentErrorStack("There are errors that are added by ProcessObserverMethod event observers for observer methods. Look at logs for further details");

        if (!webBeansUtil.isAnnotatedTypeDecoratorOrInterceptor(annotatedType)) {
            for (final ProducerMethodBean<?> producerMethod : producerMethods) {
                beanManager.addBean(producerMethod);
            }
            for (final ProducerFieldBean<?> producerField : producerFields) {
                beanManager.addBean(producerField);
            }
        }

        beanManager.addBean(bean);

        return bean;
    }

    private boolean isNewSessionBean(final WebBeansContext ctx, final Class<?> clazz) {
        if (ctx == null) {
            return false;
        }

        final Map<Class<?>, BeanContext> map = pluginBeans(ctx);
        return map != null && (map.containsKey(clazz) || clazz.isInterface() && findBeanContext(ctx, clazz) != null);
    }

    private static WebBeansContext superContext(final WebBeansContext ctx) {
        if (!WebappWebBeansContext.class.isInstance(ctx)) {
            return null;
        }
        return WebappWebBeansContext.class.cast(ctx).getParent();
    }

    private static BeanContext findBeanContext(final WebBeansContext ctx, final Class<?> clazz) {
        final Map<Class<?>, BeanContext> beans = pluginBeans(ctx);

        final BeanContext b = beans.get(clazz);
        if (b != null) {
            return b;
        }

        for (final BeanContext bc : beans.values()) {
            if (bc.isLocalbean()) {
                continue; // see isSessionBean() impl
            }

            final CdiEjbBean<?> cdiEjbBean = bc.get(CdiEjbBean.class);
            if (cdiEjbBean == null) {
                continue;
            }

            for (final Class<?> itf : cdiEjbBean.getBusinessLocalInterfaces()) {
                if (itf.equals(clazz)) {
                    return bc;
                }
            }
        }

        final WebBeansContext parentCtx = superContext(ctx);
        if (parentCtx != null) {
            return findBeanContext(parentCtx, clazz);
        }

        return null;
    }

    @Override
    public <T> Bean<T> defineNewSessionBean(final Class<T> clazz) {
        return new NewCdiEjbBean<>(findBeanContext(webBeansContext, clazz).get(CdiEjbBean.class));
    }

    private static Map<Class<?>, BeanContext> pluginBeans(final WebBeansContext ctx) {
        return CdiPlugin.class.cast(ctx.getPluginLoader().getEjbPlugin()).beans;
    }

    private static void validateObserverMethods(final CdiEjbBean<?> bean, final Map<ObserverMethod<?>, AnnotatedMethod<?>> methods) {
        final BeanContext beanContext = bean.getBeanContext();
        if (beanContext.isLocalbean()) {
            return;
        }

        for (final Map.Entry<ObserverMethod<?>, AnnotatedMethod<?>> m : methods.entrySet()) {
            final Method method = m.getValue().getJavaMember();
            if (!Modifier.isStatic(method.getModifiers())) {
                final Method viewMethod = doResolveViewMethod(bean, method);
                if (viewMethod == null) {
                    throw new WebBeansConfigurationException(
                            "@Observes " + method + " neither in the ejb view of ejb " + bean.getBeanContext().getEjbName() + " nor static");
                } else if (beanContext.getBusinessRemoteInterfaces().contains(viewMethod.getDeclaringClass())) {
                    throw new WebBeansConfigurationException(viewMethod + " observer is defined in a @Remote interface");
                }
            }
            if (m.getValue().getParameters().stream().anyMatch(p -> p.isAnnotationPresent(ObservesAsync.class))) {
                throw new WebBeansConfigurationException("@ObservesAsync " + method + " not supported on EJB in CDI 2");
            }
        }
    }

    private static void validateProduceMethods(final CdiEjbBean<?> bean, final Set<ProducerMethodBean<?>> methods) {
        final BeanContext beanContext = bean.getBeanContext();
        if (beanContext.isLocalbean()) {
            return;
        }

        for (final ProducerMethodBean<?> m : methods) {
            final Method method = m.getCreatorMethod();
            final Method viewMethod = doResolveViewMethod(bean, method);
            if (viewMethod == null || beanContext.getBusinessRemoteInterfaces().contains(viewMethod.getDeclaringClass())) {
                throw new WebBeansConfigurationException("@Produces " + method + " not in a local ejb view of ejb " + beanContext.getEjbName());
            }
        }
    }

    private static void validateScope(final CdiEjbBean<?> bean) {
        final Class<? extends Annotation> scope = bean.getScope();
        final BeanType beanType = bean.getBeanContext().getComponentType();

        if (BeanType.STATELESS.equals(beanType) && !Dependent.class.equals(scope)) {
            throw new WebBeansConfigurationException("@Stateless can only be @Dependent");
        }
        if (BeanType.SINGLETON.equals(beanType) && !Dependent.class.equals(scope) && !ApplicationScoped.class.equals(scope)) {
            throw new WebBeansConfigurationException("@Singleton can only be @Dependent or @ApplicationScoped");
        }
    }

    private static void validateDisposeMethods(final CdiEjbBean<?> bean) {
        if (!bean.getBeanContext().isLocalbean()) {
            for (final Method m : bean.getBeanContext().getBeanClass().getMethods()) {
                if (m.getDeclaringClass().equals(Object.class)) {
                    continue;
                }

                if (m.getParameterTypes().length > 0) {
                    for (final Annotation[] a : m.getParameterAnnotations()) {
                        for (final Annotation ann : a) {
                            final Method method = doResolveViewMethod(bean, m);
                            if (ann.annotationType().equals(Disposes.class) &&
                                    (method == null || bean.getBeanContext().getBusinessRemoteInterfaces().contains(method.getDeclaringClass()))) {
                                throw new WebBeansConfigurationException("@Disposes is forbidden on non business or remote EJB methods");
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public boolean isSingletonBean(final Class<?> clazz) {
        throw new IllegalStateException("Statement should never be reached");
    }

    @Override
    public boolean isStatefulBean(final Class<?> clazz) {
        return BeanType.STATEFUL.equals(beans.get(clazz).getComponentType());
    }

    @Override
    public boolean isStatelessBean(final Class<?> clazz) {
        throw new IllegalStateException("Statement should never be reached");
    }

    public static Method doResolveViewMethod(final Bean<?> component, final Method declaredMethod) {
        if (!(component instanceof CdiEjbBean)) {
            return declaredMethod;
        }

        final CdiEjbBean cdiEjbBean = (CdiEjbBean) component;

        final BeanContext beanContext = cdiEjbBean.getBeanContext();

        for (final Class intface : beanContext.getBusinessLocalInterfaces()) {
            try {
                return intface.getMethod(declaredMethod.getName(), declaredMethod.getParameterTypes());
            } catch (final NoSuchMethodException ignore) {
                // no-op
            }
        }
        for (final Class intface : beanContext.getBusinessRemoteInterfaces()) {
            try {
                return intface.getMethod(declaredMethod.getName(), declaredMethod.getParameterTypes());
            } catch (final NoSuchMethodException ignore) {
                // no-op
            }
        }
        return null;
    }

    @Override
    public Method resolveViewMethod(final Bean<?> component, final Method declaredMethod) {
        final Method m = doResolveViewMethod(component, declaredMethod);
        if (m == null) {
            return declaredMethod;
        }
        return m;
    }

    public void clearProxies() {
        cacheProxies.clear();
    }

    // does pretty much nothing
    // used only to get a layer between our EJB proxies and OWB proxies to let them manage the scope
    // /!\ don't extend AbstractOwbBean without checking equals()
    private static class InstanceBean<T> implements OwbBean<T>, PassivationCapable {
        private final CdiEjbBean<T> bean;
        private T owbProxy;

        public InstanceBean(final CdiEjbBean<T> tCdiEjbBean) {
            bean = tCdiEjbBean;
        }

        @Override
        public Set<Type> getTypes() {
            return bean.getTypes();
        }

        @Override
        public Set<Annotation> getQualifiers() {
            return bean.getQualifiers();
        }

        @Override
        public Class<? extends Annotation> getScope() {
            return bean.getScope();
        }

        @Override
        public String getName() {
            return bean.getName();
        }

        @Override
        public boolean isNullable() {
            return bean.isNullable();
        }

        @Override
        public Set<InjectionPoint> getInjectionPoints() {
            return Collections.emptySet();
        }

        @Override
        public Class<?> getBeanClass() {
            return bean.getBeanClass();
        }

        @Override
        public Set<Class<? extends Annotation>> getStereotypes() {
            return bean.getStereotypes();
        }

        @Override
        public boolean isAlternative() {
            return bean.isAlternative();
        }

        @Override
        public T create(final CreationalContext<T> creationalContext) {
            final T instance = bean.createEjb(creationalContext);
            if (owbProxy != null && SessionBeanType.STATEFUL.equals(bean.getEjbType())) { // we need to be able to remove OWB proxy to remove (statefuls for instance)
                bean.storeStatefulInstance(owbProxy, instance);
            }
            return instance;
        }

        @Override
        public void destroy(final T instance, final CreationalContext<T> cc) {
            if (!SessionBeanType.STATEFUL.equals(bean.getEjbType())) {
                return;
            }

            bean.destroy(instance, cc);
        }

        @Override
        public Producer<T> getProducer() {
            return new EjbProducer<>(this, bean);
        }

        @Override
        public WebBeansType getWebBeansType() {
            return bean.getWebBeansType();
        }

        @Override
        public Class<T> getReturnType() {
            return bean.getReturnType();
        }

        @Override
        public void setSpecializedBean(final boolean specialized) {
            // no-op
        }

        @Override
        public boolean isSpecializedBean() {
            return bean.isSpecializedBean();
        }

        @Override
        public void setEnabled(final boolean enabled) {
            // no-op
        }

        @Override
        public boolean isEnabled() {
            return bean.isEnabled();
        }

        @Override
        public String getId() {
            return bean.getId();
        }

        @Override
        public boolean isPassivationCapable() {
            return bean.isPassivationCapable();
        }

        @Override
        public boolean isDependent() {
            return bean.isDependent();
        }

        @Override
        public WebBeansContext getWebBeansContext() {
            return bean.getWebBeansContext();
        }

        public void setOwbProxy(final T owbProxy) {
            this.owbProxy = owbProxy;
        }

        @Override
        public boolean equals(final Object o) {
            if (AbstractOwbBean.class.isInstance(o)) {
                return bean.equals(o);
            }
            return InstanceBean.class.isInstance(o) && bean.equals(InstanceBean.class.cast(o).bean);
        }

        @Override
        public int hashCode() {
            return bean.hashCode();
        }
    }

    private static class EjbProducer<T> implements Producer<T> {
        private final CdiEjbBean<T> ejb;
        private final InstanceBean<T> instance;

        public EjbProducer(final InstanceBean<T> tInstanceBean, final CdiEjbBean<T> bean) {
            instance = tInstanceBean;
            this.ejb = bean;
        }

        @Override
        public T produce(final CreationalContext<T> creationalContext) {
            return instance.create(creationalContext);
        }

        @Override
        public void dispose(final T instance) {
            ejb.destroyComponentInstance(instance);
        }

        @Override
        public Set<InjectionPoint> getInjectionPoints() {
            return Collections.emptySet();
        }
    }
}
