/*
* 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.component2;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.AlterableContext;
import javax.enterprise.context.spi.Context;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionPoint;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.felix.scr.impl.metadata.ComponentMetadata;
import org.apache.felix.scr.impl.metadata.PropertyMetadata;
import org.apache.felix.scr.impl.metadata.ReferenceMetadata;
import org.apache.felix.scr.impl.metadata.ServiceMetadata;
import org.ops4j.pax.cdi.api.Attribute;
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.Contract;
import org.ops4j.pax.cdi.api.Contracts;
import org.ops4j.pax.cdi.api.Dynamic;
import org.ops4j.pax.cdi.api.Greedy;
import org.ops4j.pax.cdi.api.Immediate;
import org.ops4j.pax.cdi.api.Optional;
import org.ops4j.pax.cdi.api.Properties;
import org.ops4j.pax.cdi.api.Property;
import org.ops4j.pax.cdi.api.PrototypeScoped;
import org.ops4j.pax.cdi.api.Service;
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.support.Configurable;
import org.ops4j.pax.cdi.extension.impl.support.Filters;
import org.ops4j.pax.cdi.extension.impl.support.IterableInstance;
import org.ops4j.pax.cdi.extension.impl.support.PrivateRegistryWrapper;
import org.ops4j.pax.cdi.extension.impl.support.SimpleBean;
import org.ops4j.pax.cdi.extension.impl.support.Types;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.service.component.ComponentContext;
public class ComponentDescriptor extends AbstractDescriptor {
private final Bean<Object> bean;
private final Map<InjectionPoint, Supplier<Object>> instanceSuppliers = new HashMap<>();
private final ThreadLocal<ComponentContext> context = new ThreadLocal<>();
private final List<Bean<?>> producers = new ArrayList<>();
public ComponentDescriptor(Bean<Object> bean, ComponentRegistry registry) {
super(registry);
this.bean = bean;
boolean immediate = false;
boolean hasService = false;
Set<String> names = new HashSet<>();
for (Annotation annotation : bean.getQualifiers()) {
if (annotation instanceof Immediate) {
immediate = true;
} else if (annotation instanceof Service) {
hasService = true;
} else if (annotation instanceof Contract) {
names.add(((Contract) annotation).value().getName());
} else if (annotation instanceof Contracts) {
for (Contract ctr : ((Contracts) annotation).value()) {
names.add(ctr.value().getName());
}
} else if (annotation instanceof Property) {
Property prop = (Property) annotation;
PropertyMetadata propMeta = new PropertyMetadata();
propMeta.setName(prop.name());
propMeta.setValue(prop.value());
propMeta.setType(prop.type());
addProperty(propMeta);
} else if (annotation instanceof Properties) {
for (Property prop : ((Properties) annotation).value()) {
PropertyMetadata propMeta = new PropertyMetadata();
propMeta.setName(prop.name());
propMeta.setValue(prop.value());
propMeta.setType(prop.type());
addProperty(propMeta);
}
} else {
Class<? extends Annotation> annClass = annotation.annotationType();
Attribute attr = annClass.getAnnotation(Attribute.class);
if (attr != null) {
String name = attr.value();
Object value;
try {
Method[] methods = annClass.getDeclaredMethods();
if (methods != null && methods.length == 1) {
value = methods[0].invoke(annotation);
} else {
throw new IllegalArgumentException("Bad attribute " + annClass);
}
} catch (Throwable t) {
throw new RuntimeException(t);
}
getProperties().put(name, value);
}
}
}
ServiceMetadata serviceMetadata = new ServiceMetadata();
if (hasService) {
if (names.isEmpty()) {
for (Class cl : bean.getBeanClass().getInterfaces()) {
names.add(cl.getName());
}
}
if (names.isEmpty()) {
names.add(bean.getBeanClass().getName());
}
for (String name : names) {
serviceMetadata.addProvide(name);
}
} else {
addAllClasses(serviceMetadata, bean.getBeanClass());
getProperties().put(PrivateRegistryWrapper.PRIVATE, true);
}
if (bean.getScope() == PrototypeScoped.class
|| bean.getScope() == Dependent.class) {
serviceMetadata.setScope("prototype");
} else if (bean.getScope() == BundleScoped.class) {
serviceMetadata.setScope("bundle");
} else {
serviceMetadata.setScope("singleton");
}
String name = bean.getName();
if (name == null) {
name = bean.getBeanClass().getName();
}
setName(name);
setImmediate(immediate);
setImplementationClassName(Object.class.getName());
setConfigurationPolicy(ComponentMetadata.CONFIGURATION_POLICY_IGNORE);
getProperties().put(ComponentDescriptor.class.getName(), this);
getProperties().put(ComponentRegistry.class.getName(), registry);
setService(serviceMetadata);
}
public Bean<Object> getBean() {
return bean;
}
private void addAllClasses(ServiceMetadata serviceMetadata, Class<?> beanClass) {
serviceMetadata.addProvide(beanClass.getName());
for (Class<?> itf : beanClass.getInterfaces()) {
addAllClasses(serviceMetadata, itf);
}
if (beanClass.getSuperclass() != null) {
addAllClasses(serviceMetadata, beanClass.getSuperclass());
}
}
public void addInjectionPoint(final InjectionPoint injectionPoint) {
Service ref = injectionPoint.getAnnotated().getAnnotation(Service.class);
Component cmp = injectionPoint.getAnnotated().getAnnotation(Component.class);
Config cfg = injectionPoint.getAnnotated().getAnnotation(Config.class);
Type type = injectionPoint.getType();
final Class clazz;
final boolean multiple;
if (type instanceof ParameterizedType) {
Type raw = ((ParameterizedType) type).getRawType();
if (raw == Instance.class) {
multiple = true;
clazz = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
multiple = false;
clazz = (Class) ((ParameterizedType) type).getRawType();
}
} else {
if (type == Instance.class) {
throw new IllegalArgumentException();
}
multiple = false;
clazz = (Class) type;
}
if (cfg != null) {
if (ref != null) {
throw new IllegalArgumentException("Only one of @Service or @Config can be set on injection point: " + injectionPoint);
}
if (multiple) {
throw new IllegalArgumentException("Illegal use of Instance<?> on configuration: " + clazz.getName() + ": " + injectionPoint);
}
if (!clazz.isAnnotation()) {
throw new IllegalArgumentException("Configuration class should be an annotation: " + clazz.getName() + ": " + injectionPoint);
}
Config config = injectionPoint.getAnnotated().getAnnotation(Config.class);
String pid = config.pid().isEmpty() ? clazz.getName() : config.pid();
boolean optional = injectionPoint.getAnnotated().isAnnotationPresent(Optional.class);
setConfigurationPolicy(optional ? ComponentMetadata.CONFIGURATION_POLICY_OPTIONAL : ComponentMetadata.CONFIGURATION_POLICY_REQUIRE);
setConfigurationPid(new String[]{ pid });
producers.add(new SimpleBean<>(clazz, Dependent.class, injectionPoint, new Supplier<Object>() {
@Override
public Object get() {
return ComponentDescriptor.this.createConfig(clazz);
}
}));
}
else {
List<String> subFilters = Filters.getSubFilters(injectionPoint.getAnnotated().getAnnotations());
if (ref == null) {
subFilters.add("(" + PrivateRegistryWrapper.PRIVATE + "=true)");
}
String filter = Filters.and(subFilters);
boolean optional = injectionPoint.getAnnotated().isAnnotationPresent(Optional.class);
boolean greedy = injectionPoint.getAnnotated().isAnnotationPresent(Greedy.class);
final boolean dynamic = injectionPoint.getAnnotated().isAnnotationPresent(Dynamic.class);
ReferenceMetadata reference = new ReferenceMetadata();
reference.setName(injectionPoint.toString());
reference.setInterface(clazz.getName());
reference.setTarget(filter);
reference.setCardinality(optional ? multiple ? "0..n" : "0..1" : multiple ? "1..n" : "1..1");
reference.setPolicy(dynamic ? "dynamic" : "static");
reference.setPolicyOption(greedy ? "greedy" : "reluctant");
addDependency(reference);
Supplier<Object> supplier = new Supplier<Object>() {
@Override
public Object get() {
return ComponentDescriptor.this.getService(injectionPoint, multiple, dynamic);
}
};
producers.add(new SimpleBean<>(clazz, Dependent.class, injectionPoint, supplier));
instanceSuppliers.put(injectionPoint, supplier);
}
}
public ComponentContext getComponentContext() {
return context.get();
}
@SuppressWarnings("unchecked")
protected Object createConfig(Class<?> clazz) {
ComponentContext cc = context.get();
Map<String, Object> cfg = (Map) cc.getProperties();
return Configurable.create(clazz, cfg != null ? cfg : new Hashtable<>());
}
protected Object getService(final InjectionPoint injectionPoint, boolean isInstance, boolean dynamic) {
final ComponentContext cc = context.get();
if (cc == null) {
throw new IllegalStateException("Can not obtain @Component instance: " + injectionPoint);
}
if (dynamic && isInstance) {
Iterable<Object> iterable = new Iterable<Object>() {
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
final Object[] services = cc.locateServices(injectionPoint.toString());
int idx;
public boolean hasNext() {
return services != null && idx < services.length;
}
public Object next() {
return services[idx++];
}
};
}
};
return new IterableInstance<>(iterable);
}
else if (isInstance) {
final Object[] services = cc.locateServices(injectionPoint.toString());
Iterable<Object> iterable = new Iterable<Object>() {
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
int idx;
public boolean hasNext() {
return services != null && idx < services.length;
}
public Object next() {
return services[idx++];
}
};
}
};
return new IterableInstance<>(iterable);
}
else if (dynamic) {
Class<Object> clazz = Types.getRawType(injectionPoint.getType());
ClassLoader cl = registry.getBundleContext().getBundle().adapt(BundleWiring.class).getClassLoader();
return Proxy.newProxyInstance(cl, new Class[]{ clazz },
new InvocationHandler() {
@Override
public Object invoke(Object p, Method method, Object[] args) throws Throwable {
Object t = cc.locateService(injectionPoint.toString());
return t != null ? method.invoke(t, args) : null;
}
});
}
else {
return cc.locateService(injectionPoint.toString());
}
}
public List<Bean<?>> getProducers() {
return producers;
}
public Object activate(ComponentContext cc) {
this.context.set(cc);
try {
BeanManager beanManager = registry.getBeanManager();
Context context = beanManager.getContext(bean.getScope());
if (context instanceof BundleScopeContext) {
((BundleScopeContext) context).setClientBundle(cc.getUsingBundle());
}
try {
return context.get(bean, beanManager.createCreationalContext(bean));
} finally {
if (context instanceof BundleScopeContext) {
((BundleScopeContext) context).setClientBundle(null);
}
}
} finally {
this.context.set(null);
}
}
public void deactivate(ComponentContext cc) {
this.context.set(cc);
try {
BeanManager beanManager = registry.getBeanManager();
AlterableContext context = (AlterableContext) beanManager.getContext(bean.getScope());
if (context instanceof PrototypeScopeContext) {
((PrototypeScopeContext) context).setService(cc.getComponentInstance().getInstance());
} else if (context instanceof BundleScopeContext) {
((BundleScopeContext) context).setClientBundle(cc.getUsingBundle());
}
try {
context.destroy(bean);
} finally {
if (context instanceof PrototypeScopeContext) {
((PrototypeScopeContext) context).setService(null);
} else if (context instanceof BundleScopeContext) {
((BundleScopeContext) context).setClientBundle(null);
}
}
} finally {
this.context.set(null);
}
}
public void inject(Object instance, InjectionPoint injectionPoint) {
Supplier<Object> supplier = instanceSuppliers.get(injectionPoint);
if (supplier != null) {
Field field = ((AnnotatedField) injectionPoint.getAnnotated()).getJavaMember();
field.setAccessible(true);
try {
field.set(instance, supplier.get());
}
catch (IllegalAccessException exc) {
throw new RuntimeException(exc);
}
}
}
@Override
public String toString() {
return "Component[" + "bean=" + bean + ']';
}
}