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

org.apache.webbeans.container.InjectionResolver 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.webbeans.container;

import org.apache.webbeans.annotation.AnyLiteral;
import org.apache.webbeans.annotation.DefaultLiteral;
import org.apache.webbeans.component.AbstractOwbBean;
import org.apache.webbeans.component.AbstractProducerBean;
import org.apache.webbeans.component.InjectionTargetBean;
import org.apache.webbeans.component.ManagedBean;
import org.apache.webbeans.component.OwbBean;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.exception.WebBeansConfigurationException;
import org.apache.webbeans.exception.WebBeansDeploymentException;
import org.apache.webbeans.inject.AlternativesManager;
import org.apache.webbeans.logger.WebBeansLoggerFacade;
import org.apache.webbeans.spi.BDABeansXmlScanner;
import org.apache.webbeans.spi.ScannerService;
import org.apache.webbeans.util.AnnotationUtil;
import org.apache.webbeans.util.Asserts;
import org.apache.webbeans.util.ClassUtil;
import org.apache.webbeans.util.GenericsUtil;
import org.apache.webbeans.util.InjectionExceptionUtil;
import org.apache.webbeans.util.SingleItemSet;
import org.apache.webbeans.util.WebBeansUtil;

import javax.enterprise.event.Event;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.New;
import javax.enterprise.inject.UnproxyableResolutionException;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;
import java.lang.annotation.Annotation;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.apache.webbeans.util.InjectionExceptionUtil.throwAmbiguousResolutionException;

/**
 * Injection point resolver class.
 * 

* It is a singleton class per BeanManager. It is * responsible for resolving the bean instances at the injection points for * its bean manager. *

* * @version $Rev$ $Date$ */ public class InjectionResolver { private static final Logger logger = WebBeansLoggerFacade.getLogger(InjectionResolver.class); /** * Bean Manager */ private WebBeansContext webBeansContext; private AlternativesManager alternativesManager; /** * This Map contains all resolved beans via it's type and qualifiers. * If a bean have resolved as not existing, the entry will contain null as value. * The Long key is a hashCode, see * {@link BeanCacheKey#BeanCacheKey(boolean, Type, String, java.util.function.Function, Annotation...)} */ private Map>> resolvedBeansByType = new ConcurrentHashMap<>(); /** * This Map contains all resolved beans via it's ExpressionLanguage name. */ private Map>> resolvedBeansByName = new ConcurrentHashMap<>(); /** * Whether the container is in startup mode. * Set to {@code false} immediately before the BeforeDeploymentValidation event gets fired. */ private boolean startup; private boolean fastMatching; private Bean> instanceBean; private Bean> eventBean; /** * Creates a new injection resolve for given bean manager. * * @param webBeansContext WebBeansContext */ public InjectionResolver(WebBeansContext webBeansContext) { this.webBeansContext = webBeansContext; alternativesManager = webBeansContext.getAlternativesManager(); startup = true; fastMatching = false; instanceBean = webBeansContext.getWebBeansUtil().getInstanceBean(); eventBean = webBeansContext.getWebBeansUtil().getEventBean(); } public void setFastMatching(boolean fastMatching) { this.fastMatching = fastMatching; } public void setStartup(boolean startup) { this.startup = startup; } /** * Clear caches. */ public void clearCaches() { resolvedBeansByName.clear(); resolvedBeansByType.clear(); } /** * Check the type of the injection point. *

* Injection point type can not be {@link java.lang.reflect.TypeVariable}. *

* * @param injectionPoint injection point * @throws WebBeansConfigurationException if not obey the rule */ public void checkInjectionPointType(InjectionPoint injectionPoint) { Type type = injectionPoint.getType(); //Check for injection point type variable if (ClassUtil.isTypeVariable(type)) { throw new WebBeansConfigurationException("Injection point type : " + injectionPoint + " can not define Type Variable generic type"); } //Check for raw event type (10.3.2) if (type == Event.class) { throw new WebBeansConfigurationException("Injection point type : " + injectionPoint + " needs to define type argument for " + Event.class.getName()); } if (type == Instance.class) { throw new WebBeansConfigurationException("Injection point type : " + injectionPoint + " needs to define type argument for " + Instance.class.getName()); } // not that happy about this check here and at runtime but few TCKs test Weld behavior only... Bean bean = resolve(implResolveByType(false, type, injectionPoint.getQualifiers().toArray(new Annotation[injectionPoint.getQualifiers().size()])), injectionPoint); if (bean != null && ManagedBean.class.isInstance(bean)) { try { ManagedBean.class.cast(bean).valid(); } catch (UnproxyableResolutionException ure) { throw new WebBeansDeploymentException(ure); } } } /** * Check that a valid enabled bean exists in the deployment for the given * injection point definition. * * @param injectionPoint injection point * @throws WebBeansConfigurationException If bean is not available in the current deployment for given injection */ public void checkInjectionPoint(InjectionPoint injectionPoint) { WebBeansUtil.checkInjectionPointNamedQualifier(injectionPoint); Type type = injectionPoint.getType(); if (ClassUtil.isTypeVariable(type)) { throw new WebBeansConfigurationException("Injection point type : " + injectionPoint + " type can not be defined as Typevariable or Wildcard type!"); } if (webBeansContext.getBeanManagerImpl().isAfterBeanDiscoveryDone()) { Annotation[] qualifiers = new Annotation[injectionPoint.getQualifiers().size()]; qualifiers = injectionPoint.getQualifiers().toArray(qualifiers); // OWB-890 some 3rd party InjectionPoints return null in getBean(); Class injectionPointClass = Object.class; // the fallback Bean injectionPointBean = injectionPoint.getBean(); if (injectionPointBean != null) { injectionPointClass = injectionPointBean.getBeanClass(); } if (injectionPointClass == null && type instanceof Class) { injectionPointClass = (Class) type; } Set> beanSet = implResolveByType(injectionPoint.isDelegate(), type, injectionPointClass, qualifiers); if (beanSet.isEmpty()) { if (qualifiers.length == 1 && qualifiers[0].annotationType().equals(New.class)) { createNewBean(injectionPoint, type, qualifiers, beanSet); } } Bean bean = resolve(beanSet, injectionPoint); if (bean == null) { Class clazz; if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; clazz = (Class) pt.getRawType(); } else { clazz = (Class) type; } InjectionExceptionUtil.throwUnsatisfiedResolutionException(clazz, injectionPoint, qualifiers); } } } /** * Returns bean for injection point. * * @param injectionPoint injection point declaration * @return bean for injection point */ public Bean getInjectionPointBean(InjectionPoint injectionPoint) { Type type = injectionPoint.getType(); Class clazz; if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; clazz = (Class) pt.getRawType(); } else { clazz = (Class) type; } Set qualSet = injectionPoint.getQualifiers(); Annotation[] qualifiers = qualSet.toArray(new Annotation[qualSet.size()]); Set> beanSet = implResolveByType(injectionPoint.isDelegate(), type, clazz, qualifiers); if (beanSet.isEmpty()) { if (qualifiers.length == 1 && qualifiers[0].annotationType().equals(New.class)) { createNewBean(injectionPoint, type, qualifiers, beanSet); } else { InjectionExceptionUtil.throwUnsatisfiedResolutionException(clazz, injectionPoint, qualifiers); } } return resolve(beanSet, injectionPoint); } private void createNewBean(InjectionPoint injectionPoint, Type type, Annotation[] qualifiers, Set> beanSet) { New newQualifier = (New) qualifiers[0]; Class newType; if (newQualifier.value() == New.class) { newType = ClassUtil.getClass(type); } else { newType = newQualifier.value(); } Set> beans = implResolveByType(injectionPoint.isDelegate(), newType, injectionPoint.getBean().getBeanClass(), AnyLiteral.INSTANCE); if (beans.isEmpty()) { beanSet.add(webBeansContext.getWebBeansUtil().createNewComponent(newType)); } else { // we just need the bean for the injection points. So when we find an InjectionTargetBean, we can just take it. for (Bean bean: beans) { if (bean instanceof InjectionTargetBean) { beanSet.add(webBeansContext.getWebBeansUtil().createNewComponent((OwbBean)bean, (Class)newType)); break; } } if (beanSet.isEmpty()) { //Hmm, no InjectionTargetBean available, then we have to create the injection points on our own beanSet.add(webBeansContext.getWebBeansUtil().createNewComponent((Class)newType)); } } } private Bean getInstanceOrEventInjectionBean(Type type) { Class clazz; if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; clazz = (Class) pt.getRawType(); if (clazz.isAssignableFrom(Instance.class)) { return instanceBean; } if (clazz.isAssignableFrom(Event.class)) { return eventBean; } } return null; } /** * Returns set of beans for given bean name. * * @param name bean name * @return set of beans for given bean name */ @SuppressWarnings("unchecked") public Set> implResolveByName(String name) { Asserts.assertNotNull(name, "name parameter"); String cacheKey = name; Set> resolvedComponents = resolvedBeansByName.get(cacheKey); if (resolvedComponents != null) { return resolvedComponents; } resolvedComponents = new HashSet<>(); Set> deployedComponents = webBeansContext.getBeanManagerImpl().getBeans(); Iterator> it = deployedComponents.iterator(); //Finding all beans with given name while (it.hasNext()) { Bean component = it.next(); if (component.getName() != null) { if (component.getName().equals(name)) { resolvedComponents.add(component); } } } if (resolvedComponents.isEmpty()) { // maintain negative cache but use standard empty set so we can garbage collect resolvedBeansByName.put(cacheKey, Collections.EMPTY_SET); } else { resolvedBeansByName.put(cacheKey, resolvedComponents); } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "DEBUG_ADD_BYNAME_CACHE_BEANS", cacheKey); } return resolvedComponents; } /** * Resolution by type. * * @param isDelegate whether the InjectionPoint is for a {@link javax.decorator.Delegate} * @param injectionPointType injection point api type * @param qualifiers qualifiers of the injection point * @return set of resolved beans */ public Set> implResolveByType(boolean isDelegate, Type injectionPointType, Annotation... qualifiers) { return implResolveByType(isDelegate, injectionPointType, null, qualifiers); } private String getBDABeansXMLPath(Class injectionPointBeanClass) { if (injectionPointBeanClass == null) { return null; } ScannerService scannerService = webBeansContext.getScannerService(); BDABeansXmlScanner beansXMLScanner = scannerService.getBDABeansXmlScanner(); return beansXMLScanner.getBeansXml(injectionPointBeanClass); } /** * Resolution by type. * * @param isDelegate whether the InjectionPoint is for a {@link javax.decorator.Delegate} * @param injectionPointType injection point api type * @param qualifiers qualifiers of the injection point * @return set of resolved beans */ public Set> implResolveByType(boolean isDelegate, Type injectionPointType, Class injectionPointClass, Annotation... qualifiers) { ScannerService scannerService = webBeansContext.getScannerService(); String bdaBeansXMLFilePath = null; if (scannerService.isBDABeansXmlScanningEnabled()) { bdaBeansXMLFilePath = getBDABeansXMLPath(injectionPointClass); } boolean currentQualifier = false; if (qualifiers.length == 0) { qualifiers = DefaultLiteral.ARRAY; currentQualifier = true; } Set> resolvedComponents; BeanCacheKey cacheKey = null; if (!startup) { // we only cache and validate once the set of Beans is final, otherwise we would cache crap validateInjectionPointType(injectionPointType); cacheKey = new BeanCacheKey(isDelegate, injectionPointType, bdaBeansXMLFilePath, this::findQualifierModel, qualifiers); resolvedComponents = resolvedBeansByType.get(cacheKey); if (resolvedComponents != null) { return resolvedComponents; } } resolvedComponents = new HashSet<>(); boolean returnAll = injectionPointType.equals(Object.class) && currentQualifier; for (Bean component : webBeansContext.getBeanManagerImpl().getBeans()) { // no need to check instanceof OwbBean as we always wrap in a // ThirdpartyBeanImpl at least if (!((OwbBean) component).isEnabled()) { continue; } if (returnAll) { resolvedComponents.add(component); } else { if (fastMatching) { for (Type componentApiType : component.getTypes()) { if (ClassUtil.isRawClassEquals(injectionPointType, componentApiType)) { resolvedComponents.add(component); break; } } } else { for (Type componentApiType : component.getTypes()) { if (GenericsUtil.satisfiesDependency( isDelegate, AbstractProducerBean.class.isInstance(component), injectionPointType, componentApiType, new HashMap<>())) { resolvedComponents.add(component); break; } } } } } if (!returnAll) { // Look for qualifiers resolvedComponents = findByQualifier(resolvedComponents, injectionPointType, qualifiers); // have an additional round of checks for assignability of parameterized types. Set> byParameterizedType = findByParameterizedType(resolvedComponents, injectionPointType, isDelegate); if (byParameterizedType.isEmpty()) { resolvedComponents = findByBeanType(resolvedComponents, injectionPointType, isDelegate); } else { resolvedComponents = byParameterizedType; } } if (resolvedComponents.isEmpty()) { // for Instance or Event creation we provided special Beans // because they actually needs to fit every Qualifier Bean specialBean = getInstanceOrEventInjectionBean(injectionPointType); if (specialBean != null) { resolvedComponents.add(specialBean); } } if (resolvedComponents.isEmpty()) { findNewBean(resolvedComponents, injectionPointType, qualifiers); } if (!startup && !resolvedComponents.isEmpty()) { resolvedBeansByType.put(cacheKey, resolvedComponents); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "DEBUG_ADD_BYTYPE_CACHE_BEANS", cacheKey); } } return resolvedComponents; } private void findNewBean(Set> resolvedComponents, Type injectionPointType, Annotation[] qualifiers) { if (qualifiers.length == 1 && New.class.equals(qualifiers[0].annotationType())) { // happen in TCKs, shouldn't be the case in real apps New newQualifier = (New)qualifiers[0]; Class beanClass; if (newQualifier.value() != New.class) { beanClass = newQualifier.value(); } else { beanClass = GenericsUtil.getRawType(injectionPointType); } resolvedComponents.add(webBeansContext.getWebBeansUtil().createNewComponent(beanClass)); } } private Set> findByBeanType(Set> allComponents, Type injectionPointType, boolean isDelegate) { Set> resolved = new HashSet<>(); for (Bean bean : allComponents) { boolean isProducer = AbstractProducerBean.class.isInstance(bean); for (Type type : bean.getTypes()) { if (GenericsUtil.satisfiesDependency(isDelegate, isProducer, injectionPointType, type, new HashMap<>())) { resolved.add(bean); } if (!ClassUtil.isParametrizedType(injectionPointType) && ClassUtil.isRawClassEquals(injectionPointType, type)) { resolved.add(bean); } } } return resolved; } private Set> findByParameterizedType(Set> allComponents, Type injectionPointType, boolean isDelegate) { Bean rawProducerBean = null; Set> resolvedComponents = new HashSet<>(); for (Bean component : allComponents) { boolean isProducer = AbstractProducerBean.class.isInstance(component); for (Type componentApiType : component.getTypes()) { if (GenericsUtil.satisfiesDependency(isDelegate, isProducer, injectionPointType, componentApiType, new HashMap<>())) { resolvedComponents.add(component); break; } else if (isProducer && componentApiType instanceof Class && ClassUtil.isRawClassEquals(injectionPointType, componentApiType)) { rawProducerBean = component; } } } if (resolvedComponents.isEmpty() && rawProducerBean != null) { resolvedComponents.add(rawProducerBean); } return resolvedComponents; } /** * Verify that we have a legal Type at the injection point. * CDI can basically only handle Class and ParameterizedType injection points atm. * @throws WebBeansConfigurationException on TypeVariable, WildcardType and GenericArrayType * @throws IllegalArgumentException if the type is not yet supported by the spec. */ private void validateInjectionPointType(Type injectionPointType) { if (injectionPointType instanceof TypeVariable || injectionPointType instanceof WildcardType || injectionPointType instanceof GenericArrayType) { throw new WebBeansConfigurationException("Injection point cannot define Type Variable " + injectionPointType); } if (!(injectionPointType instanceof Class) && !(injectionPointType instanceof ParameterizedType)) { throw new IllegalArgumentException("Unsupported type " + injectionPointType.getClass()); } } /** * Gets alternatives from set. * * @param beans resolved set * @return contains alternatives */ public Set> findByAlternatives(Set> beans) { // first check whether we have Alternatives with a Priority annotation List> prioritizedAlternatives = alternativesManager.getPrioritizedAlternatives(); for (Class alternativeClazz : prioritizedAlternatives) { for (Bean bean: beans) { if (alternativeClazz.equals(bean.getBeanClass())) { return new SingleItemSet<>(bean); } } } // if none such Alternative got found let's check the 'old' alternatives from beans.xml Set> alternativeSet = new HashSet<>(); Set> enableSet = new HashSet<>(); for (Bean bean : beans) { if (bean.isAlternative() || (bean instanceof AbstractProducerBean && ((AbstractProducerBean) bean).getOwnerComponent().isAlternative())) { alternativeSet.add(bean); } else { if (alternativeSet.isEmpty()) { AbstractOwbBean temp = (AbstractOwbBean) bean; if (temp.isEnabled()) { enableSet.add(bean); } } } } if (!alternativeSet.isEmpty()) { return alternativeSet; } // return enableSet; } /** * resolve any ambiguity by checking for Alternatives. * If any @Alternative exists, then we pick the one with the * highest priority. * * @param beans * @param injectionPoint only used for logging. Can be null. * @param * @return the single resolved bean, null if none is activated * @throws javax.enterprise.inject.AmbiguousResolutionException if more than 1 bean is active */ public Bean resolve(Set> beans, InjectionPoint injectionPoint) { if (beans == null || beans.isEmpty()) { return null; } if (beans.size() == 1) { // if there is only one Bean left, then there is for sure no ambiguity. return beans.iterator().next(); } Set set = resolveAll(beans); if (set.isEmpty()) { return null; } if(set.size() > 1) { throwAmbiguousResolutionException(set, null, injectionPoint); } return (Bean)set.iterator().next(); } public Set> resolveAll(Set> beans) { if (beans == null || beans.isEmpty()) { return Collections.emptySet(); } Set set = findByAlternatives(beans); if (set == null || set.isEmpty()) { return Collections.emptySet(); } return set; } /** * Returns filtered bean set according to the qualifiers. * * @param remainingSet bean set for filtering by qualifier * @param annotations qualifiers on injection point * @return filtered bean set according to the qualifiers */ private Set> findByQualifier(Set> remainingSet, Type type, Annotation... annotations) { Iterator> it = remainingSet.iterator(); Set> result = new HashSet<>(); while (it.hasNext()) { Bean component = it.next(); Set qTypes = component.getQualifiers(); int i = 0; for (Annotation annot : annotations) { Iterator itQualifiers = qTypes.iterator(); while (itQualifiers.hasNext()) { Annotation qualifier = itQualifiers.next(); if (annot.annotationType().equals(qualifier.annotationType())) { AnnotatedType at = findQualifierModel(qualifier.annotationType()); if (at == null) { if (AnnotationUtil.isCdiAnnotationEqual(qualifier, annot)) { i++; } } else { if (AnnotationUtil.isCdiAnnotationEqual(at, qualifier, annot)) { i++; } } } } } if (i == annotations.length) { result.add(component); } } return result; } private AnnotatedType findQualifierModel(final Class qualifier) { return webBeansContext.getBeanManagerImpl().getAdditionalAnnotatedTypeQualifiers().get(qualifier); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy