blob: 61ec879d48a77283390f208c3a32c36d2a831fa2 [file] [log] [blame]
/**
* 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.spring;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import org.apache.camel.Routes;
import org.apache.camel.builder.ErrorHandlerBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultLifecycleStrategy;
import org.apache.camel.management.DefaultInstrumentationAgent;
import org.apache.camel.management.InstrumentationLifecycleStrategy;
import org.apache.camel.model.IdentifiedType;
import org.apache.camel.model.InterceptDefinition;
import org.apache.camel.model.OnExceptionDefinition;
import org.apache.camel.model.ProceedDefinition;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.RouteBuilderDefinition;
import org.apache.camel.model.RouteContainer;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.config.PropertiesDefinition;
import org.apache.camel.model.dataformat.DataFormatsDefinition;
import org.apache.camel.processor.interceptor.Debugger;
import org.apache.camel.processor.interceptor.Delayer;
import org.apache.camel.processor.interceptor.TraceFormatter;
import org.apache.camel.processor.interceptor.Tracer;
import org.apache.camel.spi.ClassResolver;
import org.apache.camel.spi.LifecycleStrategy;
import org.apache.camel.spi.PackageScanClassResolver;
import org.apache.camel.spi.Registry;
import org.apache.camel.util.ProcessorTypeHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
/**
* A Spring {@link FactoryBean} to create and initialize a
* {@link SpringCamelContext} and install routes either explicitly configured in
* Spring XML or found by searching the classpath for Java classes which extend
* {@link RouteBuilder} using the nested {@link #setPackages(String[])}.
*
* @version $Revision$
*/
@XmlRootElement(name = "camelContext")
@XmlAccessorType(XmlAccessType.FIELD)
public class CamelContextFactoryBean extends IdentifiedType implements RouteContainer, FactoryBean, InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener {
private static final Log LOG = LogFactory.getLog(CamelContextFactoryBean.class);
@XmlAttribute(required = false)
private Boolean autowireRouteBuilders = Boolean.TRUE;
@XmlAttribute(required = false)
private Boolean trace;
@XmlAttribute(required = false)
private Long delay;
@XmlAttribute(required = false)
private String errorHandlerRef;
@XmlAttribute(required = false)
private Boolean shouldStartContext = Boolean.TRUE;
@XmlElement(name = "properties", required = false)
private PropertiesDefinition properties;
@XmlElement(name = "package", required = false)
private String[] packages = {};
@XmlElement(name = "jmxAgent", type = CamelJMXAgentDefinition.class, required = false)
private CamelJMXAgentDefinition camelJMXAgent;
@XmlElements({
@XmlElement(name = "beanPostProcessor", type = CamelBeanPostProcessor.class, required = false),
@XmlElement(name = "template", type = CamelTemplateFactoryBean.class, required = false),
@XmlElement(name = "proxy", type = CamelProxyFactoryDefinition.class, required = false),
@XmlElement(name = "export", type = CamelServiceExporterDefinition.class, required = false)})
private List beans;
@XmlElement(name = "routeBuilder", required = false)
private List<RouteBuilderDefinition> builderRefs = new ArrayList<RouteBuilderDefinition>();
@XmlElement(name = "endpoint", required = false)
private List<EndpointFactoryBean> endpoints;
@XmlElement(name = "dataFormats", required = false)
private DataFormatsDefinition dataFormats;
@XmlElement(name = "onException", required = false)
private List<OnExceptionDefinition> exceptionClauses = new ArrayList<OnExceptionDefinition>();
@XmlElement(name = "intercept", required = false)
private List<InterceptDefinition> intercepts = new ArrayList<InterceptDefinition>();
@XmlElement(name = "route", required = false)
private List<RouteDefinition> routes = new ArrayList<RouteDefinition>();
@XmlTransient
private SpringCamelContext context;
@XmlTransient
private RouteBuilder routeBuilder;
@XmlTransient
private List<Routes> additionalBuilders = new ArrayList<Routes>();
@XmlTransient
private ApplicationContext applicationContext;
@XmlTransient
private ClassLoader contextClassLoaderOnStart;
@XmlTransient
private BeanPostProcessor beanPostProcessor;
public CamelContextFactoryBean() {
// Lets keep track of the class loader for when we actually do start things up
contextClassLoaderOnStart = Thread.currentThread().getContextClassLoader();
}
public Object getObject() throws Exception {
return getContext();
}
public Class getObjectType() {
return SpringCamelContext.class;
}
public boolean isSingleton() {
return true;
}
public ClassLoader getContextClassLoaderOnStart() {
return contextClassLoaderOnStart;
}
public List<Routes> getAdditionalBuilders() {
return additionalBuilders;
}
public void afterPropertiesSet() throws Exception {
// TODO there should be a neater way to do this!
if (properties != null) {
getContext().setProperties(properties.asMap());
}
// set the resolvers first
PackageScanClassResolver packageResolver = getBeanForType(PackageScanClassResolver.class);
if (packageResolver != null) {
getContext().setPackageScanClassResolver(packageResolver);
}
ClassResolver classResolver = getBeanForType(ClassResolver.class);
if (classResolver != null) {
getContext().setClassResolver(classResolver);
}
Debugger debugger = getBeanForType(Debugger.class);
if (debugger != null) {
getContext().addInterceptStrategy(debugger);
}
Tracer tracer = getBeanForType(Tracer.class);
if (tracer != null) {
// use formatter if there is a TraceFormatter bean defined
TraceFormatter formatter = getBeanForType(TraceFormatter.class);
if (formatter != null) {
tracer.setFormatter(formatter);
}
getContext().addInterceptStrategy(tracer);
}
Delayer delayer = getBeanForType(Delayer.class);
if (delayer != null) {
getContext().addInterceptStrategy(delayer);
}
// set the lifecycle strategy if defined
LifecycleStrategy lifecycleStrategy = getBeanForType(LifecycleStrategy.class);
if (lifecycleStrategy != null) {
getContext().setLifecycleStrategy(lifecycleStrategy);
}
// set the strategy if defined
Registry registry = getBeanForType(Registry.class);
if (registry != null) {
getContext().setRegistry(registry);
}
// Set the application context and camelContext for the beanPostProcessor
if (beanPostProcessor != null) {
if (beanPostProcessor instanceof ApplicationContextAware) {
((ApplicationContextAware)beanPostProcessor).setApplicationContext(applicationContext);
}
if (beanPostProcessor instanceof CamelBeanPostProcessor) {
((CamelBeanPostProcessor)beanPostProcessor).setCamelContext(getContext());
}
}
// setup the intercepts
for (RouteDefinition route : routes) {
if (exceptionClauses != null) {
route.getOutputs().addAll(exceptionClauses);
}
for (InterceptDefinition intercept : intercepts) {
List<ProcessorDefinition<?>> outputs = new ArrayList<ProcessorDefinition<?>>();
List<ProcessorDefinition<?>> exceptionHandlers = new ArrayList<ProcessorDefinition<?>>();
for (ProcessorDefinition output : route.getOutputs()) {
if (output instanceof OnExceptionDefinition) {
exceptionHandlers.add(output);
} else {
outputs.add(output);
}
}
// clearing the outputs
route.clearOutput();
// add exception handlers as top children
route.getOutputs().addAll(exceptionHandlers);
// add the interceptor but we must do some pre configuration beforehand
intercept.afterPropertiesSet();
InterceptDefinition proxy = intercept.createProxy();
route.addOutput(proxy);
route.pushBlock(proxy.getProceed());
// if there is a proceed in the interceptor proxy then we should add
// the current outputs to out route so we will proceed and continue to route to them
ProceedDefinition proceed = ProcessorTypeHelper.findFirstTypeInOutputs(proxy.getOutputs(), ProceedDefinition.class);
if (proceed != null) {
proceed.getOutputs().addAll(outputs);
}
}
}
if (dataFormats != null) {
getContext().setDataFormats(dataFormats.asMap());
}
// lets force any lazy creation
getContext().addRouteDefinitions(routes);
if (camelJMXAgent != null && camelJMXAgent.isDisabled()) {
LOG.debug("JMXAgent disabled");
getContext().setLifecycleStrategy(new DefaultLifecycleStrategy());
} else if (camelJMXAgent != null) {
LOG.debug("JMXAgent enabled");
if (lifecycleStrategy != null) {
LOG.warn("lifecycleStrategy will be overriden by InstrumentationLifecycleStrategy");
}
DefaultInstrumentationAgent agent = new DefaultInstrumentationAgent();
agent.setConnectorPort(camelJMXAgent.getConnectorPort());
agent.setCreateConnector(camelJMXAgent.isCreateConnector());
agent.setMBeanObjectDomainName(camelJMXAgent.getMbeanObjectDomainName());
agent.setMBeanServerDefaultDomain(camelJMXAgent.getMbeanServerDefaultDomain());
agent.setRegistryPort(camelJMXAgent.getRegistryPort());
agent.setServiceUrlPath(camelJMXAgent.getServiceUrlPath());
agent.setUsePlatformMBeanServer(camelJMXAgent.isUsePlatformMBeanServer());
getContext().setLifecycleStrategy(new InstrumentationLifecycleStrategy(agent));
}
if (LOG.isDebugEnabled()) {
LOG.debug("Found JAXB created routes: " + getRoutes());
}
findRouteBuilders();
installRoutes();
}
@SuppressWarnings("unchecked")
private <T> T getBeanForType(Class<T> clazz) {
T bean = null;
String[] names = getApplicationContext().getBeanNamesForType(clazz, true, true);
if (names.length == 1) {
bean = (T) getApplicationContext().getBean(names[0], clazz);
}
if (bean == null) {
ApplicationContext parentContext = getApplicationContext().getParent();
if (parentContext != null) {
names = parentContext.getBeanNamesForType(clazz, true, true);
if (names.length == 1) {
bean = (T) parentContext.getBean(names[0], clazz);
}
}
}
return bean;
}
public void destroy() throws Exception {
getContext().stop();
}
public void onApplicationEvent(ApplicationEvent event) {
if (LOG.isDebugEnabled()) {
LOG.debug("Publishing spring-event: " + event);
}
if (event instanceof ContextRefreshedEvent) {
// now lets start the CamelContext so that all its possible
// dependencies are initialized
try {
LOG.debug("Starting the context now!");
getContext().start();
} catch (Exception e) {
throw wrapRuntimeCamelException(e);
}
}
}
// Properties
// -------------------------------------------------------------------------
public SpringCamelContext getContext() throws Exception {
if (context == null) {
context = createContext();
}
return context;
}
public void setContext(SpringCamelContext context) {
this.context = context;
}
public List<RouteDefinition> getRoutes() {
return routes;
}
public void setRoutes(List<RouteDefinition> routes) {
this.routes = routes;
}
public List<InterceptDefinition> getIntercepts() {
return intercepts;
}
public void setIntercepts(List<InterceptDefinition> intercepts) {
this.intercepts = intercepts;
}
public RouteBuilder getRouteBuilder() {
return routeBuilder;
}
/**
* Set a single {@link RouteBuilder} to be used to create the default routes
* on startup
*/
public void setRouteBuilder(RouteBuilder routeBuilder) {
this.routeBuilder = routeBuilder;
}
/**
* Set a collection of {@link RouteBuilder} instances to be used to create
* the default routes on startup
*/
public void setRouteBuilders(RouteBuilder[] builders) {
for (RouteBuilder builder : builders) {
additionalBuilders.add(builder);
}
}
public ApplicationContext getApplicationContext() {
if (applicationContext == null) {
throw new IllegalArgumentException("No applicationContext has been injected!");
}
return applicationContext;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public PropertiesDefinition getProperties() {
return properties;
}
public void setProperties(PropertiesDefinition properties) {
this.properties = properties;
}
public String[] getPackages() {
return packages;
}
/**
* Sets the package names to be recursively searched for Java classes which
* extend {@link RouteBuilder} to be auto-wired up to the
* {@link SpringCamelContext} as a route. Note that classes are excluded if
* they are specifically configured in the spring.xml
*
* @param packages the package names which are recursively searched
*/
public void setPackages(String[] packages) {
this.packages = packages;
}
public void setBeanPostProcessor(BeanPostProcessor postProcessor) {
this.beanPostProcessor = postProcessor;
}
public BeanPostProcessor getBeanPostProcessor() {
return beanPostProcessor;
}
public void setCamelJMXAgent(CamelJMXAgentDefinition agent) {
camelJMXAgent = agent;
}
public Boolean getTrace() {
return trace;
}
public void setTrace(Boolean trace) {
this.trace = trace;
}
public Long getDelay() {
return delay;
}
public void setDelay(Long delay) {
this.delay = delay;
}
public CamelJMXAgentDefinition getCamelJMXAgent() {
return camelJMXAgent;
}
public List<RouteBuilderDefinition> getBuilderRefs() {
return builderRefs;
}
public void setBuilderRefs(List<RouteBuilderDefinition> builderRefs) {
this.builderRefs = builderRefs;
}
/**
* If enabled this will force all {@link RouteBuilder} classes configured in the Spring
* {@link ApplicationContext} to be registered automatically with this CamelContext.
*/
public void setAutowireRouteBuilders(Boolean autowireRouteBuilders) {
this.autowireRouteBuilders = autowireRouteBuilders;
}
public String getErrorHandlerRef() {
return errorHandlerRef;
}
/**
* Sets the name of the error handler object used to default the error handling strategy
*
* @param errorHandlerRef the Spring bean ref of the error handler
*/
public void setErrorHandlerRef(String errorHandlerRef) {
this.errorHandlerRef = errorHandlerRef;
}
public Boolean getShouldStartContext() {
return shouldStartContext;
}
public void setShouldStartContext(Boolean shouldStartContext) {
this.shouldStartContext = shouldStartContext;
}
// Implementation methods
// -------------------------------------------------------------------------
/**
* Create the context
*/
protected SpringCamelContext createContext() {
SpringCamelContext ctx = new SpringCamelContext(getApplicationContext());
ctx.setName(getId());
if (trace != null) {
ctx.setTrace(trace);
}
if (delay != null) {
ctx.setDelay(delay);
}
if (errorHandlerRef != null) {
ErrorHandlerBuilder errorHandlerBuilder = (ErrorHandlerBuilder) getApplicationContext().getBean(errorHandlerRef, ErrorHandlerBuilder.class);
if (errorHandlerBuilder == null) {
throw new IllegalArgumentException("Could not find bean: " + errorHandlerRef);
}
ctx.setErrorHandlerBuilder(errorHandlerBuilder);
}
if (shouldStartContext != null) {
ctx.setShouldStartContext(shouldStartContext);
}
return ctx;
}
/**
* Strategy to install all available routes into the context
*/
protected void installRoutes() throws Exception {
if (autowireRouteBuilders != null && autowireRouteBuilders) {
Map builders = getApplicationContext().getBeansOfType(RouteBuilder.class, true, true);
if (builders != null) {
for (Object builder : builders.values()) {
getContext().addRoutes((RouteBuilder) builder);
}
}
}
for (Routes routeBuilder : additionalBuilders) {
getContext().addRoutes(routeBuilder);
}
if (routeBuilder != null) {
getContext().addRoutes(routeBuilder);
}
// lets add route builders added from references
if (builderRefs != null) {
for (RouteBuilderDefinition builderRef : builderRefs) {
RouteBuilder builder = builderRef.createRouteBuilder(getContext());
getContext().addRoutes(builder);
}
}
}
/**
* Strategy method to try find {@link RouteBuilder} instances on the
* classpath
*/
protected void findRouteBuilders() throws Exception, InstantiationException {
if (getPackages() != null && getPackages().length > 0) {
RouteBuilderFinder finder = new RouteBuilderFinder(getContext(), getPackages(), getContextClassLoaderOnStart(),
getBeanPostProcessor(), getContext().getPackageScanClassResolver());
finder.appendBuilders(getAdditionalBuilders());
}
}
public void setDataFormats(DataFormatsDefinition dataFormats) {
this.dataFormats = dataFormats;
}
public DataFormatsDefinition getDataFormats() {
return dataFormats;
}
public void setExceptionClauses(List<OnExceptionDefinition> exceptionClauses) {
this.exceptionClauses = exceptionClauses;
}
public List<OnExceptionDefinition> getExceptionClauses() {
return exceptionClauses;
}
}