All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.openejb.cdi.WebappBeanManager Maven / Gradle / Ivy

There is a newer version: 10.0.0-M3
Show newest version
/*
 * 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.util.reflection.Reflections;
import org.apache.webbeans.component.BuiltInOwbBean;
import org.apache.webbeans.component.ExtensionBean;
import org.apache.webbeans.component.OwbBean;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.container.BeanManagerImpl;
import org.apache.webbeans.context.creational.CreationalContextImpl;
import org.apache.webbeans.event.EventMetadataImpl;
import org.apache.webbeans.util.Asserts;
import org.apache.webbeans.util.WebBeansUtil;

import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.UnsatisfiedResolutionException;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.InterceptionType;
import javax.enterprise.inject.spi.Interceptor;
import javax.enterprise.inject.spi.ObserverMethod;
import javax.enterprise.inject.spi.PassivationCapable;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class WebappBeanManager extends BeanManagerImpl {
    private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
    private static final ThreadLocal USE_PARENT_BM = new ThreadLocal<>();
    private final WebappWebBeansContext webappCtx;
    private final InheritedBeanFilter filter;
    private Set> deploymentBeans;
    private boolean started/* = false*/;

    public WebappBeanManager(final WebappWebBeansContext ctx) {
        super(ctx);
        webappCtx = ctx;
        deploymentBeans = super.getBeans(); // use the parent one while starting
        Reflections.set(this, "injectionResolver", new WebAppInjectionResolver(ctx));
        filter = new InheritedBeanFilter(this);
    }

    @Override
    public void fireEvent(final Object event, final EventMetadataImpl metadata, final boolean isLifecycleEvent) {
        getNotificationManager().fireEvent(event, metadata, isLifecycleEvent);
        if (isEvent(event)) {
            final BeanManagerImpl parentBm = getParentBm();
            if (parentBm != null) {
                parentBm.getNotificationManager().fireEvent(event, metadata, isLifecycleEvent);
            }
        }
    }

    @Override
    public List> resolveInterceptors(final InterceptionType type, final Annotation... interceptorBindings) {
        final List> interceptors = super.resolveInterceptors(type, interceptorBindings);
        final List> parentInterceptors = getParentBm().resolveInterceptors(type, interceptorBindings);
        for (final Interceptor i : parentInterceptors) {
            if (!interceptors.contains(i)) {
                interceptors.add(i);
            }
        }
        return interceptors;
    }

    @Override
    public  Set> resolveObserverMethods(final T event, final EventMetadataImpl metadata) {
        final Set> set = new HashSet<>();
        set.addAll(getNotificationManager().resolveObservers(event, metadata, false));

        if (isEvent(event)) {
            final BeanManagerImpl parentBm = getParentBm();
            if (parentBm != null) {
                set.addAll(parentBm.getNotificationManager().resolveObservers(event, metadata, false));
            }
        } // else nothing since extensions are loaded by classloader so we already have it

        return set;
    }

    @Override
    public Object getInjectableReference(final InjectionPoint injectionPoint, final CreationalContext ctx) {
        Asserts.assertNotNull(injectionPoint, "injectionPoint parameter");
        if(injectionPoint == null)  {
            return null;
        }

        final BeanManagerImpl parentBm = getParentBm();
        final Boolean existing = USE_PARENT_BM.get();
        if (existing != null && existing) { // shortcut the whole logic to keep the threadlocal set up correctly
            if (parentBm == null) {
                return null;
            }
            return parentBm.getInjectableReference(injectionPoint, ctx);
        }

        // we can do it cause there is caching but we shouldn't - easy way to overide OWB actually
        final Bean injectedBean = (Bean)getInjectionResolver().getInjectionPointBean(injectionPoint);
        try {
            if (parentBm != null && injectedBean != null && injectedBean == parentBm.getInjectionResolver().getInjectionPointBean(injectionPoint)) {
                USE_PARENT_BM.set(true);
                try {
                    return parentBm.getInjectableReference(injectionPoint, ctx);
                } finally {
                    USE_PARENT_BM.remove();
                }
            }
        } catch (final UnsatisfiedResolutionException ure) {
            // skip, use this bean
        }
        return super.getInjectableReference(injectionPoint, ctx);
    }

    @Override
    public  CreationalContextImpl createCreationalContext(final Contextual contextual) {
        try {
            return super.createCreationalContext(contextual);
        } catch (final RuntimeException e) { // can happen?
            try {
                return getParentBm().createCreationalContext(contextual);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }

    @Override
    public boolean isNormalScope(final Class annotationType) {
        try {
            return super.isNormalScope(annotationType);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().isNormalScope(annotationType);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }

    @Override
    public boolean isPassivatingScope(final Class annotationType) {
        try {
            return super.isPassivatingScope(annotationType);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().isPassivatingScope(annotationType);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }


    @Override
    public boolean isQualifier(final Class annotationType) {
        try {
            return super.isQualifier(annotationType);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().isQualifier(annotationType);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }

    @Override
    public boolean isInterceptorBinding(final Class annotationType) {
        try {
            return super.isInterceptorBinding(annotationType);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().isInterceptorBinding(annotationType);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }


    @Override
    public boolean isStereotype(final Class annotationType) {
        try {
            return super.isStereotype(annotationType);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().isStereotype(annotationType);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }

    @Override
    public Set getInterceptorBindingDefinition(final Class qualifier) {
        try {
            return super.getInterceptorBindingDefinition(qualifier);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().getInterceptorBindingDefinition(qualifier);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }

    @Override
    public Context getContext(final Class scope) {
        try {
            return super.getContext(scope);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().getContext(scope);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }

    @Override
    public ELResolver getELResolver() {
        return new WebAppElResolver(super.getELResolver(), getParentBm().getELResolver());
    }

    @Override
    public  AnnotatedType createAnnotatedType(final Class type) {
        try {
            return super.createAnnotatedType(type);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().createAnnotatedType(type);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }

    @Override
    public  InjectionTarget createInjectionTarget(final AnnotatedType type) {
        try {
            return super.createInjectionTarget(type);
        } catch (final RuntimeException e) {
            try {
                return getParentBm().createInjectionTarget(type);
            } catch (final RuntimeException ignored) {
                throw e;
            }
        }
    }

    @Override
    public ExpressionFactory wrapExpressionFactory(final ExpressionFactory expressionFactory) {
        return super.wrapExpressionFactory(expressionFactory);
    }

    public BeanManagerImpl getParentBm() {
        final WebBeansContext parent = webappCtx.getParent();
        return parent != null ? parent.getBeanManagerImpl() : null;
    }

    @Override
    public boolean isInUse() {
        return super.isInUse() || getParentBm().isInUse();
    }

    @Override
    public Set> getComponents() {
        if (!started) {
            // probably not yet merged (afterStart())
            // so reuse parent beans
            // this can happen for validations
            return new IteratorSet<>(
                new MultipleIterator<>(
                    filter,
                    deploymentBeans.iterator(),
                    getParentBm().getComponents().iterator()));
        }
        return deploymentBeans;
    }

    @Override
    public Set> getBeans() {
        return deploymentBeans;
    }

    @Override
    public  void addAdditionalAnnotatedType(Object extension, AnnotatedType inAnnotatedType, String id) {
        super.addAdditionalAnnotatedType(extension, inAnnotatedType, id);
    }

    @Override
    public Bean getPassivationCapableBean(final String id) {
        final Bean bean = super.getPassivationCapableBean(id);
        if (bean == null && getParentBm() != null) {
            return getParentBm().getPassivationCapableBean(id);
        }
        return bean;
    }

    public void afterStart() {
        started = true;
        deploymentBeans = mergeBeans();
        webappCtx.getBeanManagerImpl().getInjectionResolver().clearCaches(); // to force new resolution with new beans
    }

    private Set> mergeBeans() {
        final Set> allBeans = new CopyOnWriteArraySet<>(); // override parent one with a "webapp" bean list
        final BeanManagerImpl parentBm = getParentBm();
        if (parentBm != null) {
            for (final Bean bean : parentBm.getBeans()) {
                if (filter.accept(bean)) {
                    allBeans.add(bean);
                }
            }
        }
        allBeans.addAll(super.getBeans());
        return allBeans;
    }

    public void beforeStop() {
        // no-op
    }

    private boolean isEvent(final Object event) {
        return !WebBeansUtil.isDefaultExtensionBeanEventType(event.getClass())
                && !webappCtx.getWebBeansUtil().isContainerEventType(event);
    }

    private interface Filter {
        boolean accept(A a);
    }

    private static final class InheritedBeanFilter implements Filter> {
        private final WebappBeanManager beanManager;

        private InheritedBeanFilter(final WebappBeanManager beanManager) {
            this.beanManager = beanManager;
        }

        @Override
        public boolean accept(final Bean bean) {
            if (BuiltInOwbBean.class.isInstance(bean) || ExtensionBean.class.isInstance(bean)) {
                return false;
            }
            if (OwbBean.class.isInstance(bean)) {
                final OwbBean owbBean = OwbBean.class.cast(bean);
                if (owbBean.isPassivationCapable())
                {
                    if (hasBean(owbBean.getId()))
                    {
                        return false;
                    }
                }
            } else if (PassivationCapable.class.isInstance(bean)) {
                if (hasBean(PassivationCapable.class.cast(bean).getId())) {
                    return false;
                }
            }

            final Set qualifiers = bean.getQualifiers();
            return beanManager.getBeans(
                    bean.getBeanClass(),
                    qualifiers.isEmpty() ? EMPTY_ANNOTATIONS : qualifiers.toArray(new Annotation[qualifiers.size()])).isEmpty();
        }

        private boolean hasBean(final String id) {
            return beanManager.getPassivationCapableBean(id) != null;
        }
    }

    private static final class MultipleIterator implements Iterator {
        private final Iterator[] delegates;
        private final Filter filter;

        private A next/* = null*/;
        private int idx/* = 0*/;

        /**
         * @param filter    used to filter delegates from index 1 to N-1 (0 is not filtered)
         * @param delegates iterator this Iterator merges, one delegates is mandatory
         */
        private MultipleIterator(final Filter filter, final Iterator... delegates) {
            this.filter = filter;
            this.delegates = delegates;
        }

        @Override
        public boolean hasNext() {
            for (; idx < delegates.length; idx++) {
                while (delegates[idx].hasNext()) {
                    next = delegates[idx].next();
                    if (idx == 0 || filter.accept(next)) { // we accept all items of first iterator
                        return true;
                    }
                }
            }
            return false;
        }

        @Override
        public A next() {
            return next;
        }

        @Override
        public void remove() {
            delegates[idx].remove();
        }
    }

    // hack set, only use it for Set which are used as Iterator
    // case of getComponent
    private static final class IteratorSet extends HashSet {
        private final Iterator it;

        private IteratorSet(final Iterator it) {
            this.it = it;
        }

        @Override
        public Iterator iterator() {
            return it;
        }
    }
}