blob: bd1cb976bb1e79ff0cc0e04250900b8bf225404d [file] [log] [blame]
/**
* Licensed 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.aries.cdi.container.internal.container;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;
import javax.enterprise.event.ObservesAsync;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedMember;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedParameter;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.aries.cdi.container.internal.annotated.AnnotatedTypeImpl;
import org.apache.aries.cdi.container.internal.model.BeansModel;
import org.apache.aries.cdi.container.internal.model.ComponentPropertiesModel;
import org.apache.aries.cdi.container.internal.model.ExtendedActivationTemplateDTO;
import org.apache.aries.cdi.container.internal.model.ExtendedComponentTemplateDTO;
import org.apache.aries.cdi.container.internal.model.ExtendedConfigurationTemplateDTO;
import org.apache.aries.cdi.container.internal.model.OSGiBean;
import org.apache.aries.cdi.container.internal.model.ReferenceModel;
import org.apache.aries.cdi.container.internal.model.ReferenceModel.Builder;
import org.apache.aries.cdi.container.internal.util.Annotates;
import org.apache.aries.cdi.container.internal.util.Reflection;
import org.osgi.service.cdi.ComponentType;
import org.osgi.service.cdi.ConfigurationPolicy;
import org.osgi.service.cdi.MaximumCardinality;
import org.osgi.service.cdi.ServiceScope;
import org.osgi.service.cdi.annotations.ComponentProperties;
import org.osgi.service.cdi.annotations.ComponentScoped;
import org.osgi.service.cdi.annotations.FactoryComponent;
import org.osgi.service.cdi.annotations.PID;
import org.osgi.service.cdi.annotations.Reference;
import org.osgi.service.cdi.annotations.SingleComponent;
import org.osgi.service.cdi.reference.BindBeanServiceObjects;
import org.osgi.service.cdi.reference.BindService;
import org.osgi.service.cdi.reference.BindServiceReference;
import org.osgi.service.cdi.runtime.dto.template.ComponentTemplateDTO;
public class Discovery {
private static final List<Type> BIND_TYPES = Arrays.asList(BindService.class, BindBeanServiceObjects.class, BindServiceReference.class);
public Discovery(ContainerState containerState) {
_containerState = containerState;
_beansModel = _containerState.beansModel();
_containerTemplate = _containerState.containerDTO().template.components.get(0);
}
public void discover() {
_beansModel.getOSGiBeans().stream().forEach(osgiBean -> {
osgiBean.found(true);
AnnotatedType<?> annotatedType = new AnnotatedTypeImpl<>(osgiBean.getBeanClass());
try {
String beanName = Annotates.beanName(annotatedType);
Class<? extends Annotation> beanScope = Annotates.beanScope(annotatedType);
Map<String, Object> componentProperties = Annotates.componentProperties(annotatedType);
ServiceScope serviceScope = Annotates.serviceScope(annotatedType);
List<String> serviceTypes = Annotates.serviceClassNames(annotatedType);
if (annotatedType.isAnnotationPresent(SingleComponent.class)) {
doFactoryOrSingleComponent(osgiBean, osgiBean.getBeanClass(), annotatedType, beanName, serviceTypes, serviceScope, componentProperties, ComponentType.SINGLE);
}
else if (annotatedType.isAnnotationPresent(FactoryComponent.class)) {
doFactoryOrSingleComponent(osgiBean, osgiBean.getBeanClass(), annotatedType, beanName, serviceTypes, serviceScope, componentProperties, ComponentType.FACTORY);
}
else if (annotatedType.isAnnotationPresent(ComponentScoped.class)) {
_componentScoped.add(osgiBean);
}
else {
discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedType, null, beanScope, serviceTypes, serviceScope, componentProperties);
}
}
catch (Exception e) {
_containerState.error(e);
return;
}
annotatedType.getConstructors().stream().filter(this::isInject).flatMap(annotatedConstructor -> annotatedConstructor.getParameters().stream()).forEach(
annotatedParameter ->
processAnnotated(annotatedParameter, annotatedParameter.getBaseType(), Annotates.qualifiers(annotatedParameter), osgiBean)
);
annotatedType.getFields().stream().filter(this::isInject).forEach(
annotatedField ->
processAnnotated(annotatedField, annotatedField.getBaseType(), Annotates.qualifiers(annotatedField), osgiBean)
);
annotatedType.getFields().stream().filter(this::isProduces).forEach(
annotatedField -> {
Class<? extends Annotation> beanScope = Annotates.beanScope(annotatedField);
Map<String, Object> componentProperties = Annotates.componentProperties(annotatedField);
ServiceScope serviceScope = Annotates.serviceScope(annotatedField);
List<String> serviceTypes = Annotates.serviceClassNames(annotatedField);
discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedField, annotatedField, beanScope, serviceTypes, serviceScope, componentProperties);
}
);
annotatedType.getMethods().stream().forEach(annotatedMethod -> {
if (isInjectOrProduces(annotatedMethod)) {
annotatedMethod.getParameters().stream().forEach(
annotatedParameter -> processAnnotated(annotatedParameter, annotatedParameter.getBaseType(), Annotates.qualifiers(annotatedParameter), osgiBean)
);
if (isProduces(annotatedMethod)) {
Class<? extends Annotation> beanScope = Annotates.beanScope(annotatedMethod);
Map<String, Object> componentProperties = Annotates.componentProperties(annotatedMethod);
ServiceScope serviceScope = Annotates.serviceScope(annotatedMethod);
List<String> serviceTypes = Annotates.serviceClassNames(annotatedMethod);
discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedMethod, annotatedMethod, beanScope, serviceTypes, serviceScope, componentProperties);
}
}
else if (isDisposeOrObserves(annotatedMethod)) {
annotatedMethod.getParameters().subList(1, annotatedMethod.getParameters().size()).stream().forEach(
annotatedParameter -> processAnnotated(annotatedParameter, annotatedParameter.getBaseType(), Annotates.qualifiers(annotatedParameter), osgiBean)
);
}
});
});
postProcessComponentScopedBeans();
}
<X> boolean isInject(AnnotatedMember<X> annotatedMember) {
return (annotatedMember.isAnnotationPresent(Inject.class) && !annotatedMember.isStatic());
}
<X> boolean isInjectOrProduces(AnnotatedMember<X> annotatedMember) {
return (annotatedMember.isAnnotationPresent(Inject.class) && !annotatedMember.isStatic()) ||
annotatedMember.isAnnotationPresent(Produces.class);
}
<X> boolean isProduces(AnnotatedMember<X> annotatedMember) {
return annotatedMember.isAnnotationPresent(Produces.class);
}
<X> boolean isDisposeOrObserves(AnnotatedMethod<X> annotatedMethod) {
return !annotatedMethod.getParameters().isEmpty() && (annotatedMethod.getParameters().get(0).isAnnotationPresent(Disposes.class) ||
annotatedMethod.getParameters().get(0).isAnnotationPresent(Observes.class) ||
annotatedMethod.getParameters().get(0).isAnnotationPresent(ObservesAsync.class));
}
<X> void processAnnotated(Annotated annotated, Type injectionPointType, Set<Annotation> qualifiers, OSGiBean osgiBean) {
if (BIND_TYPES.contains(Reflection.getRawType(annotated.getBaseType()))) {
Builder builder = new ReferenceModel.Builder(annotated);
try {
ReferenceModel referenceModel = builder.type(injectionPointType).build();
osgiBean.addReference(referenceModel.toDTO());
}
catch (Exception e) {
_containerState.error(e);
}
}
else {
Reference reference = annotated.getAnnotation(Reference.class);
ComponentProperties componentProperties = annotated.getAnnotation(ComponentProperties.class);
if (reference != null) {
doReference(osgiBean, annotated, injectionPointType, reference, componentProperties);
}
else if (componentProperties != null) {
doComponentProperties(osgiBean, injectionPointType, qualifiers);
}
}
}
void postProcessComponentScopedBeans() {
_containerState.containerDTO().template.components.stream().filter(
template -> template.type != ComponentType.CONTAINER
).map(
template -> (ExtendedComponentTemplateDTO)template
).forEach(
template -> {
_componentScoped.forEach(
osgiBean -> {
if (osgiBean.getComponent() == null) {
osgiBean.setComponent(_containerState, template);
}
String className = osgiBean.getBeanClass().getName();
if (!template.beans.contains(className)) {
template.beans.add(className);
}
}
);
}
);
}
void doComponentProperties(OSGiBean osgiBean, Type injectionPointType, Set<Annotation> qualifiers) {
try {
ComponentPropertiesModel configurationModel = new ComponentPropertiesModel.Builder(
injectionPointType
).declaringClass(
osgiBean.getBeanClass()
).qualifiers(
qualifiers
).build();
osgiBean.addConfiguration(_containerState, configurationModel.toDTO());
}
catch (Exception e) {
_containerState.error(e);
}
}
void discoverActivations(OSGiBean osgiBean, Class<?> declaringClass, Annotated annotated, AnnotatedMember<?> producer, Class<? extends Annotation> scope, List<String> serviceTypeNames, ServiceScope serviceScope, Map<String, Object> componentProperties) {
String className = declaringClass.getName();
if (!_containerTemplate.beans.contains(className)) {
_containerTemplate.beans.add(className);
}
if (!serviceTypeNames.isEmpty()) {
if (!scope.equals(ApplicationScoped.class) &&
!scope.equals(Dependent.class)) {
_containerState.error(
new IllegalStateException(
String.format(
"@Service can only be used on @ApplicationScoped, @Dependent, @SingleComponent, and @FactoryComponent: %s",
osgiBean.getBeanClass())));
return;
}
ExtendedActivationTemplateDTO activationTemplate = new ExtendedActivationTemplateDTO();
activationTemplate.cdiScope = scope;
activationTemplate.declaringClass = declaringClass;
activationTemplate.producer = producer;
activationTemplate.properties = componentProperties;
activationTemplate.scope = serviceScope;
activationTemplate.serviceClasses = serviceTypeNames;
_containerTemplate.activations.add(activationTemplate);
}
osgiBean.setComponent(_containerState, _containerTemplate);
}
void doFactoryOrSingleComponent(
OSGiBean osgiBean, Class<?> declaringClass, Annotated annotated, String beanName,
List<String> serviceTypes, ServiceScope serviceScope,
Map<String, Object> componentProperties, ComponentType componentType) {
Set<Annotation> qualifiers = Annotates.qualifiers(annotated);
qualifiers.removeIf(Named.class::isInstance);
ExtendedComponentTemplateDTO componentTemplate = new ExtendedComponentTemplateDTO();
componentTemplate.activations = new CopyOnWriteArrayList<>();
ExtendedActivationTemplateDTO activationTemplate = new ExtendedActivationTemplateDTO();
activationTemplate.declaringClass = declaringClass;
activationTemplate.properties = Collections.emptyMap();
activationTemplate.scope = serviceScope;
activationTemplate.serviceClasses = serviceTypes;
componentTemplate.activations.add(activationTemplate);
componentTemplate.beanClass = declaringClass;
componentTemplate.qualifiers = qualifiers;
componentTemplate.beans = new CopyOnWriteArrayList<>();
componentTemplate.configurations = new CopyOnWriteArrayList<>();
componentTemplate.name = beanName;
componentTemplate.properties = componentProperties;
componentTemplate.references = new CopyOnWriteArrayList<>();
componentTemplate.type = componentType;
annotated.getAnnotations(PID.class).stream().forEach(
PID -> {
String pid = PID.value();
ExtendedConfigurationTemplateDTO configurationTemplate = new ExtendedConfigurationTemplateDTO();
configurationTemplate.declaringClass = declaringClass;
configurationTemplate.maximumCardinality = MaximumCardinality.ONE;
configurationTemplate.pid = Optional.of(pid).map(
s -> (s.equals("$") || s.equals("")) ? componentTemplate.name : s
).orElse(componentTemplate.name);
if (componentType == ComponentType.SINGLE) {
configurationTemplate.pid = (pid.equals("$") || pid.equals("")) ? componentTemplate.name : pid;
}
configurationTemplate.policy = PID.policy();
componentTemplate.configurations.add(configurationTemplate);
}
);
if (componentType == ComponentType.SINGLE) {
if (componentTemplate.configurations.isEmpty()) {
ExtendedConfigurationTemplateDTO configurationTemplate = new ExtendedConfigurationTemplateDTO();
configurationTemplate.declaringClass = declaringClass;
configurationTemplate.maximumCardinality = MaximumCardinality.ONE;
configurationTemplate.pid = componentTemplate.name;
configurationTemplate.policy = ConfigurationPolicy.OPTIONAL;
componentTemplate.configurations.add(configurationTemplate);
}
}
else {
ExtendedConfigurationTemplateDTO configurationTemplate = new ExtendedConfigurationTemplateDTO();
configurationTemplate.declaringClass = declaringClass;
configurationTemplate.maximumCardinality = MaximumCardinality.MANY;
configurationTemplate.pid = Optional.ofNullable(
annotated.getAnnotation(FactoryComponent.class)
).map(FactoryComponent::value).map(
v -> (v.equals("$") || v.equals("")) ? componentTemplate.name : v
).orElse(componentTemplate.name);
configurationTemplate.policy = ConfigurationPolicy.REQUIRED;
componentTemplate.configurations.add(configurationTemplate);
}
componentTemplate.beans.add(declaringClass.getName());
_containerState.containerDTO().template.components.add(componentTemplate);
osgiBean.setComponent(_containerState, componentTemplate);
}
void doReference(OSGiBean osgiBean, Annotated annotated, Type injectionPointType, Reference reference, ComponentProperties componentProperties) {
if (componentProperties != null) {
_containerState.error(
new IllegalArgumentException(
String.format(
"Cannot use @Reference and @Configuration on the same injection point {}",
injectionPointType))
);
return;
}
Builder builder = null;
if (annotated instanceof AnnotatedParameter) {
builder = new ReferenceModel.Builder(annotated);
}
else {
builder = new ReferenceModel.Builder(annotated);
}
try {
ReferenceModel referenceModel = builder.type(injectionPointType).build();
osgiBean.addReference(referenceModel.toDTO());
}
catch (Exception e) {
_containerState.error(e);
}
}
private final BeansModel _beansModel;
private final Set<OSGiBean> _componentScoped = new HashSet<>();
private final ComponentTemplateDTO _containerTemplate;
private final ContainerState _containerState;
}