/**
* 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.camel.cdi.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessBean;
import javax.enterprise.inject.spi.ProcessInjectionTarget;
import javax.enterprise.inject.spi.ProcessProducerMethod;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Consume;
import org.apache.camel.EndpointInject;
import org.apache.camel.Produce;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.cdi.CdiCamelContext;
import org.apache.camel.cdi.ContextName;
import org.apache.camel.impl.DefaultCamelBeanPostProcessor;
import org.apache.camel.model.RouteContainer;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.ReflectionHelper;
import org.apache.deltaspike.core.api.provider.BeanProvider;
import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder;
/**
* Set of camel specific hooks for CDI.
*/
public class CamelExtension implements Extension {
private static class InjectLiteral extends AnnotationLiteral<Inject> {
private static final long serialVersionUID = 1L;
}
CamelContextMap camelContextMap;
private final Set<Bean<?>> eagerBeans = new HashSet<Bean<?>>();
private final Map<String, CamelContextConfig> camelContextConfigMap = new HashMap<String, CamelContextConfig>();
private final List<CamelContextBean> camelContextBeans = new ArrayList<CamelContextBean>();
public CamelExtension() {
}
/**
* If no context name is specified then default it to the value from
* the {@link org.apache.camel.cdi.ContextName} annotation
*/
public static String getCamelContextName(String context, ContextName annotation) {
if (ObjectHelper.isEmpty(context) && annotation != null) {
return annotation.value();
}
return context;
}
/**
* Process camel context aware bean definitions.
*
* @param process Annotated type.
* @throws Exception In case of exceptions.
*/
protected void contextAwareness(@Observes ProcessAnnotatedType<CamelContextAware> process) throws Exception {
AnnotatedType<CamelContextAware> at = process.getAnnotatedType();
Method method = at.getJavaClass().getMethod("setCamelContext", CamelContext.class);
AnnotatedTypeBuilder<CamelContextAware> builder = new AnnotatedTypeBuilder<CamelContextAware>()
.readFromType(at)
.addToMethod(method, new InjectLiteral());
process.setAnnotatedType(builder.create());
}
protected void detectRouteBuilders(@Observes ProcessAnnotatedType<?> process) throws Exception {
AnnotatedType<?> annotatedType = process.getAnnotatedType();
ContextName annotation = annotatedType.getAnnotation(ContextName.class);
Class<?> javaClass = annotatedType.getJavaClass();
if (annotation != null && isRoutesBean(javaClass)) {
addRouteBuilderBean(annotatedType, annotation);
}
}
private void addRouteBuilderBean(final AnnotatedType<?> process, ContextName annotation) {
final CamelContextConfig config = getCamelConfig(annotation.value());
config.addRouteBuilderBean(process);
}
/**
* Disable creation of default CamelContext bean and rely on context created
* and managed by extension.
*
* @param process Annotated type.
*/
protected void disableDefaultContext(@Observes ProcessAnnotatedType<? extends CamelContext> process) {
process.veto();
}
/**
* Registers managed camel bean.
*
* @param abd After bean discovery event.
* @param manager Bean manager.
*/
protected void registerManagedCamelContext(@Observes AfterBeanDiscovery abd, BeanManager manager) {
// lets ensure we have at least one camel context
if (camelContextConfigMap.isEmpty()) {
abd.addBean(new CamelContextBean(manager));
} else {
Set<Map.Entry<String, CamelContextConfig>> entries = camelContextConfigMap.entrySet();
for (Map.Entry<String, CamelContextConfig> entry : entries) {
String name = entry.getKey();
CamelContextConfig config = entry.getValue();
CamelContextBean camelContextBean = new CamelContextBean(manager, "CamelContext:" + name,
name, config);
camelContextBeans.add(camelContextBean);
abd.addBean(camelContextBean);
}
}
}
/**
* Lets detect all beans annotated with @Consume and
* beans of type {@link RouteBuilder} which are annotated with {@link org.apache.camel.cdi.ContextName}
* so they can be auto-registered
*/
public void detectConsumeBeans(@Observes ProcessBean<?> event) {
final Bean<?> bean = event.getBean();
Class<?> beanClass = bean.getBeanClass();
ReflectionHelper.doWithMethods(beanClass, new ReflectionHelper.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Consume consume = method.getAnnotation(Consume.class);
if (consume != null) {
eagerBeans.add(bean);
}
}
});
}
/**
* Lets detect all beans annotated of type {@link RouteBuilder}
* which are annotated with {@link org.apache.camel.cdi.ContextName}
* so they can be auto-registered
*/
public void detectRouteBuilderBeans(@Observes ProcessBean<?> event) {
final Bean<?> bean = event.getBean();
Class<?> beanClass = bean.getBeanClass();
if (isRoutesBean(beanClass)) {
addRouteBuilderBean(bean, beanClass.getAnnotation(ContextName.class));
}
}
private void addRouteBuilderBean(Bean<?> bean, ContextName annotation) {
if (annotation != null) {
String contextName = annotation.value();
CamelContextConfig config = getCamelConfig(contextName);
config.addRouteBuilderBean(bean);
}
}
private CamelContextConfig getCamelConfig(final String contextName) {
CamelContextConfig config = camelContextConfigMap.get(contextName);
if (config == null) {
config = new CamelContextConfig();
camelContextConfigMap.put(contextName, config);
}
return config;
}
/**
* Lets detect all producer methods creating instances of {@link RouteBuilder} which are annotated with {@link org
* .apache.camel.cdi.ContextName}
* so they can be auto-registered
*/
public void detectProducerRoutes(@Observes ProcessProducerMethod<?, ?> event) {
Annotated annotated = event.getAnnotated();
ContextName annotation = annotated.getAnnotation(ContextName.class);
Class<?> returnType = event.getAnnotatedProducerMethod().getJavaMember().getReturnType();
if (isRoutesBean(returnType)) {
addRouteBuilderBean(event.getBean(), annotation);
}
}
/**
* Lets force the CDI container to create all beans annotated with @Consume so that the consumer becomes active
*/
public void startConsumeBeans(@Observes AfterDeploymentValidation event, BeanManager beanManager) throws Exception {
for (CamelContextBean bean : camelContextBeans) {
String name = bean.getCamelContextName();
CamelContext context = getCamelContext(name);
if (context == null) {
throw new IllegalStateException(
"CamelContext '" + name + "' has not been injected into the CamelContextMap");
}
bean.configureCamelContext((CdiCamelContext) context);
}
for (Bean<?> bean : eagerBeans) {
// force lazy creation to start the consumer
CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean);
beanManager.getReference(bean, bean.getBeanClass(), creationalContext);
}
}
/**
* Lets perform injection of all beans which use Camel annotations
*/
public void onInjectionTarget(@Observes ProcessInjectionTarget<?> event) {
final InjectionTarget injectionTarget = event.getInjectionTarget();
AnnotatedType annotatedType = event.getAnnotatedType();
final Class<Object> beanClass = annotatedType.getJavaClass();
// TODO this is a bit of a hack - what should the bean name be?
final String beanName = injectionTarget.toString();
ContextName contextName = annotatedType.getAnnotation(ContextName.class);
final BeanAdapter adapter = createBeanAdapter(beanClass, contextName);
if (!adapter.isEmpty()) {
DelegateInjectionTarget newTarget = new DelegateInjectionTarget(injectionTarget) {
@Override
public void postConstruct(Object instance) {
super.postConstruct(instance);
// now lets do the post instruct to inject our Camel injections
adapter.inject(CamelExtension.this, instance, beanName);
}
};
event.setInjectionTarget(newTarget);
}
}
/**
* Perform injection on an existing bean such as a test case which is created directly by a testing framework.
* <p/>
* This is because BeanProvider.injectFields() does not invoke the onInjectionTarget() method so the injection
* of @Produce / @EndpointInject and processing of the @Consume annotations are not performed.
*/
public void inject(Object bean) {
Class<?> beanClass = bean.getClass();
ContextName contextName = beanClass.getAnnotation(ContextName.class);
final BeanAdapter adapter = createBeanAdapter(beanClass, contextName);
if (!adapter.isEmpty()) {
// TODO this is a bit of a hack - what should the bean name be?
final String beanName = bean.toString();
adapter.inject(this, bean, beanName);
}
}
private BeanAdapter createBeanAdapter(Class<?> beanClass, ContextName contextName) {
final BeanAdapter adapter = new BeanAdapter(contextName);
ReflectionHelper.doWithFields(beanClass, new ReflectionHelper.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
Produce produce = field.getAnnotation(Produce.class);
if (produce != null && !injectAnnotatedField(field)) {
adapter.addProduceField(field);
}
EndpointInject endpointInject = field.getAnnotation(EndpointInject.class);
if (endpointInject != null) {
adapter.addEndpointField(field);
}
}
});
ReflectionHelper.doWithMethods(beanClass, new ReflectionHelper.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Consume consume = method.getAnnotation(Consume.class);
if (consume != null) {
adapter.addConsumeMethod(method);
}
Produce produce = method.getAnnotation(Produce.class);
if (produce != null) {
adapter.addProduceMethod(method);
}
EndpointInject endpointInject = method.getAnnotation(EndpointInject.class);
if (endpointInject != null) {
adapter.addEndpointMethod(method);
}
}
});
return adapter;
}
protected DefaultCamelBeanPostProcessor getPostProcessor(String context) {
CamelContext camelContext = getCamelContext(context);
if (camelContext != null) {
return new DefaultCamelBeanPostProcessor(camelContext);
} else {
throw new IllegalArgumentException("No such CamelContext '" + context + "' available!");
}
}
protected CamelContext getCamelContext(String context) {
if (camelContextMap == null) {
camelContextMap = BeanProvider.getContextualReference(CamelContextMap.class);
ObjectHelper.notNull(camelContextMap, "Could not resolve CamelContextMap");
}
return camelContextMap.getCamelContext(context);
}
/**
* Returns true if this field is annotated with @Inject
*/
protected static boolean injectAnnotatedField(Field field) {
return field.getAnnotation(Inject.class) != null;
}
protected boolean isRoutesBean(Class<?> returnType) {
return (RoutesBuilder.class.isAssignableFrom(returnType) || RouteContainer.class.isAssignableFrom(returnType))
&& !Modifier.isAbstract(returnType.getModifiers());
}
}