/*
* Copyright 2016 Guillaume Nodet
*
* 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.ops4j.pax.cdi.extension.impl;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.Destroyed;
import javax.enterprise.context.Initialized;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanAttributes;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.ProcessBean;
import javax.enterprise.inject.spi.ProcessBeanAttributes;
import javax.enterprise.inject.spi.ProcessInjectionPoint;
import javax.enterprise.inject.spi.ProcessInjectionTarget;
import javax.enterprise.inject.spi.ProcessObserverMethod;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Qualifier;
import javax.inject.Singleton;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.ops4j.pax.cdi.api.BundleScoped;
import org.ops4j.pax.cdi.api.Component;
import org.ops4j.pax.cdi.api.Config;
import org.ops4j.pax.cdi.api.Global;
import org.ops4j.pax.cdi.api.PrototypeScoped;
import org.ops4j.pax.cdi.api.Service;
import org.ops4j.pax.cdi.api.SingletonScoped;
import org.ops4j.pax.cdi.api.event.ServiceCdiEvent;
import org.ops4j.pax.cdi.extension.impl.component2.ComponentDescriptor;
import org.ops4j.pax.cdi.extension.impl.component2.ComponentRegistry;
import org.ops4j.pax.cdi.extension.impl.component2.GlobalDescriptor;
import org.ops4j.pax.cdi.extension.impl.component2.BundleContextHolder;
import org.ops4j.pax.cdi.extension.impl.context.BundleScopeContext;
import org.ops4j.pax.cdi.extension.impl.context.PrototypeScopeContext;
import org.ops4j.pax.cdi.extension.impl.context.SingletonScopeContext;
import org.ops4j.pax.cdi.extension.impl.osgi.Registry;
import org.ops4j.pax.cdi.extension.impl.support.DelegatingBeanAttributes;
import org.ops4j.pax.cdi.extension.impl.support.DelegatingInjectionPoint;
import org.ops4j.pax.cdi.extension.impl.support.DelegatingInjectionTarget;
import org.ops4j.pax.cdi.extension.impl.support.Filters;
import org.ops4j.pax.cdi.extension.impl.support.Types;
import org.ops4j.pax.cdi.extension.impl.util.ServiceAddedLiteral;
import org.ops4j.pax.cdi.extension.impl.util.ServiceRemovedLiteral;
import org.osgi.framework.Constants;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ApplicationScoped
public class OsgiExtension2 implements Extension {
private ComponentRegistry componentRegistry;
private GlobalDescriptor global;
public OsgiExtension2() {
}
public ComponentRegistry getComponentRegistry() {
return componentRegistry;
}
public <T> Bean<T> globalDependency(Class<T> clazz, Set<Annotation> qualifiers) {
return global.addGlobalInjectionPoint(clazz, qualifiers);
}
public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery event, BeanManager manager) {
componentRegistry = new ComponentRegistry(manager, BundleContextHolder.getBundleContext());
global = new GlobalDescriptor(componentRegistry);
event.addAnnotatedType(manager.createAnnotatedType(BundleEventBridge.class));
event.addAnnotatedType(manager.createAnnotatedType(ServiceEventBridge.class));
event.addAnnotatedType(manager.createAnnotatedType(BundleContextProducer.class));
event.addScope(SingletonScoped.class, false, false);
}
public <T> void processBeanAttributes(@Observes ProcessBeanAttributes<T> event) {
if (event.getAnnotated().isAnnotationPresent(Component.class)
|| event.getAnnotated().isAnnotationPresent(Global.class)) {
BeanAttributes<T> attr = event.getBeanAttributes();
Class<? extends Annotation> scope = attr.getScope();
if (scope == SingletonScoped.class || scope == BundleScoped.class || scope == PrototypeScoped.class) {
// nothing
} else if (scope == Singleton.class) {
event.setBeanAttributes(new DelegatingBeanAttributes<T>(attr) {
@Override
public Class<? extends Annotation> getScope() {
return SingletonScoped.class;
}
});
} else if (scope == Dependent.class) {
if (event.getAnnotated().isAnnotationPresent(Global.class)) {
// @Global defaults to @PrototypeScoped
event.setBeanAttributes(new DelegatingBeanAttributes<T>(attr) {
@Override
public Class<? extends Annotation> getScope() {
return PrototypeScoped.class;
}
});
} else {
// @Component defaults to @SingletonScoped
event.setBeanAttributes(new DelegatingBeanAttributes<T>(attr) {
@Override
public Class<? extends Annotation> getScope() {
return SingletonScoped.class;
}
});
}
} else {
event.addDefinitionError(new IllegalArgumentException("Unsupported scope " + scope.getSimpleName() + ": " + event.getAnnotated()));
}
}
}
public <T> void processBean(@Observes ProcessBean<T> event) {
@SuppressWarnings("unchecked")
Bean<Object> bean = (Bean) event.getBean();
ComponentDescriptor descriptor = null;
if (event.getAnnotated().isAnnotationPresent(Component.class)
|| event.getAnnotated().isAnnotationPresent(Service.class)) {
if (!event.getAnnotated().isAnnotationPresent(Component.class)
&& !event.getAnnotated().isAnnotationPresent(Global.class)) {
event.addDefinitionError(new IllegalArgumentException(
"Beans annotated with @Service " +
"should be annotated with @Component or @Global: " + event.getAnnotated()));
}
descriptor = componentRegistry.addComponent(bean);
}
for (InjectionPoint ip : event.getBean().getInjectionPoints()) {
if (ip.getAnnotated().isAnnotationPresent(Service.class)
|| ip.getAnnotated().isAnnotationPresent(Component.class)
|| ip.getAnnotated().isAnnotationPresent(Config.class)) {
if (ip.getAnnotated().isAnnotationPresent(Global.class)
|| event.getAnnotated().isAnnotationPresent(Global.class)) {
try {
global.addGlobalInjectionPoint(ip);
} catch (IllegalArgumentException e) {
event.addDefinitionError(e);
}
} else if (!event.getAnnotated().isAnnotationPresent(Component.class)) {
event.addDefinitionError(new IllegalArgumentException(
"Beans with @Service, @Component or @Config injection points " +
"should be annotated with @Component: " + event.getAnnotated()));
} else {
try {
descriptor.addInjectionPoint(ip);
} catch (IllegalArgumentException e) {
event.addDefinitionError(e);
}
}
}
}
}
public <T, X> void processInjectionPoint(@Observes ProcessInjectionPoint<T, X> event) {
if (event.getInjectionPoint().getAnnotated().isAnnotationPresent(Service.class)
|| event.getInjectionPoint().getAnnotated().isAnnotationPresent(Component.class)) {
final String id = UUID.randomUUID().toString();
event.setInjectionPoint(new DelegatingInjectionPoint(event.getInjectionPoint()) {
public Set<Annotation> getQualifiers() {
Set<Annotation> annotations = new HashSet<>(delegate.getQualifiers());
annotations.add(new AnnotationLiteral<Service>() { });
annotations.add(new UniqueIdentifierLitteral(id));
return annotations;
}
});
}
}
public <T> void processInjectionTarget(@Observes ProcessInjectionTarget<T> event) {
for (InjectionPoint ip : event.getInjectionTarget().getInjectionPoints()) {
Annotated annotated = ip.getAnnotated();
if (annotated.isAnnotationPresent(Service.class)
|| annotated.isAnnotationPresent(Component.class)
|| annotated.isAnnotationPresent(Config.class)) {
// if (annotated.isAnnotationPresent(Global.class)) {
// continue;
// }
event.setInjectionTarget(new DelegatingInjectionTarget<T>(event.getInjectionTarget()) {
@Override
public void inject(T instance, CreationalContext<T> ctx) {
super.inject(instance, ctx);
for (InjectionPoint injectionPoint : getInjectionPoints()) {
ComponentDescriptor descriptor = componentRegistry.getDescriptor(injectionPoint.getBean());
if (descriptor != null) {
descriptor.inject(instance, injectionPoint);
}
}
}
});
return;
}
}
}
public void afterBeanDiscovery(@Observes AfterBeanDiscovery event) {
componentRegistry.preStart(event, global);
BeanManager beanManager = componentRegistry.getBeanManager();
SingletonScopeContext serviceContext = new SingletonScopeContext(beanManager);
event.addContext(serviceContext);
BundleScopeContext bundleScopeContext = new BundleScopeContext(beanManager);
event.addContext(bundleScopeContext);
PrototypeScopeContext prototypeScopeContext = new PrototypeScopeContext(beanManager);
event.addContext(prototypeScopeContext);
}
public void applicationScopeInitialized(@Observes @Initialized(ApplicationScoped.class) Object init) {
componentRegistry.start();
Registry.getInstance().register(componentRegistry);
}
public void applicationScopeDestroyed(@Observes @Destroyed(ApplicationScoped.class) Object destroy) {
Registry.getInstance().unregister(componentRegistry);
componentRegistry.stop();
}
@Target({METHOD, FIELD, PARAMETER, TYPE})
@Retention(RUNTIME)
@Qualifier
public @interface UniqueIdentifier {
String id();
}
static class UniqueIdentifierLitteral extends AnnotationLiteral<UniqueIdentifier> implements UniqueIdentifier {
private final String id;
public UniqueIdentifierLitteral(String id) {
this.id = id;
}
@Override
public String id() {
return id;
}
}
private final Set<String> observedFilters = new HashSet<>();
private final Set<Annotation> observedQualifiers = new HashSet<>();
public Set<String> getObservedFilters() {
return observedFilters;
}
public Set<Annotation> getObservedQualifiers() {
return observedQualifiers;
}
public <T, X> void processObserverMethod(@Observes ProcessObserverMethod<T, X> event) {
Set<Annotation> qualifiers = event.getObserverMethod().getObservedQualifiers();
if (qualifiers.contains(new ServiceAddedLiteral())
|| qualifiers.contains(new ServiceRemovedLiteral())) {
List<String> filters = Filters.getSubFilters(qualifiers);
Type observed = event.getObserverMethod().getObservedType();
Class service = Types.getRawType(observed);
if (service == ServiceCdiEvent.class) {
service = Types.getRawType(((ParameterizedType) observed).getActualTypeArguments()[0]);
}
if (service != Object.class) {
String subfilter = "(" + Constants.OBJECTCLASS + "=" + service.getName() + ")";
filters.add(0, subfilter);
}
String filter = Filters.and(filters);
observedFilters.add(filter);
observedQualifiers.addAll(qualifiers);
}
}
}