001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.spring;
018
019 import java.util.ArrayList;
020 import java.util.List;
021 import java.util.Map;
022
023 import javax.xml.bind.annotation.XmlAccessType;
024 import javax.xml.bind.annotation.XmlAccessorType;
025 import javax.xml.bind.annotation.XmlAttribute;
026 import javax.xml.bind.annotation.XmlElement;
027 import javax.xml.bind.annotation.XmlElements;
028 import javax.xml.bind.annotation.XmlRootElement;
029 import javax.xml.bind.annotation.XmlTransient;
030
031 import org.apache.camel.Routes;
032 import org.apache.camel.builder.ErrorHandlerBuilder;
033 import org.apache.camel.builder.RouteBuilder;
034 import org.apache.camel.impl.DefaultLifecycleStrategy;
035 import org.apache.camel.management.DefaultInstrumentationAgent;
036 import org.apache.camel.management.InstrumentationLifecycleStrategy;
037 import org.apache.camel.model.IdentifiedType;
038 import org.apache.camel.model.InterceptDefinition;
039 import org.apache.camel.model.OnExceptionDefinition;
040 import org.apache.camel.model.ProceedDefinition;
041 import org.apache.camel.model.ProcessorDefinition;
042 import org.apache.camel.model.RouteBuilderDefinition;
043 import org.apache.camel.model.RouteContainer;
044 import org.apache.camel.model.RouteDefinition;
045 import org.apache.camel.model.config.PropertiesDefinition;
046 import org.apache.camel.model.dataformat.DataFormatsDefinition;
047 import org.apache.camel.processor.interceptor.Debugger;
048 import org.apache.camel.processor.interceptor.Delayer;
049 import org.apache.camel.processor.interceptor.TraceFormatter;
050 import org.apache.camel.processor.interceptor.Tracer;
051 import org.apache.camel.spi.ClassResolver;
052 import org.apache.camel.spi.LifecycleStrategy;
053 import org.apache.camel.spi.PackageScanClassResolver;
054 import org.apache.camel.spi.Registry;
055 import org.apache.camel.util.ProcessorTypeHelper;
056 import org.apache.commons.logging.Log;
057 import org.apache.commons.logging.LogFactory;
058 import org.springframework.beans.factory.DisposableBean;
059 import org.springframework.beans.factory.FactoryBean;
060 import org.springframework.beans.factory.InitializingBean;
061 import org.springframework.beans.factory.config.BeanPostProcessor;
062 import org.springframework.context.ApplicationContext;
063 import org.springframework.context.ApplicationContextAware;
064 import org.springframework.context.ApplicationEvent;
065 import org.springframework.context.ApplicationListener;
066 import org.springframework.context.event.ContextRefreshedEvent;
067
068 import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
069
070 /**
071 * A Spring {@link FactoryBean} to create and initialize a
072 * {@link SpringCamelContext} and install routes either explicitly configured in
073 * Spring XML or found by searching the classpath for Java classes which extend
074 * {@link RouteBuilder} using the nested {@link #setPackages(String[])}.
075 *
076 * @version $Revision: 751648 $
077 */
078 @XmlRootElement(name = "camelContext")
079 @XmlAccessorType(XmlAccessType.FIELD)
080 public class CamelContextFactoryBean extends IdentifiedType implements RouteContainer, FactoryBean, InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener {
081 private static final Log LOG = LogFactory.getLog(CamelContextFactoryBean.class);
082
083 @XmlAttribute(required = false)
084 private Boolean autowireRouteBuilders = Boolean.TRUE;
085 @XmlAttribute(required = false)
086 private Boolean trace;
087 @XmlAttribute(required = false)
088 private Long delay;
089 @XmlAttribute(required = false)
090 private String errorHandlerRef;
091 @XmlAttribute(required = false)
092 private Boolean shouldStartContext = Boolean.TRUE;
093 @XmlElement(name = "properties", required = false)
094 private PropertiesDefinition properties;
095 @XmlElement(name = "package", required = false)
096 private String[] packages = {};
097 @XmlElement(name = "jmxAgent", type = CamelJMXAgentDefinition.class, required = false)
098 private CamelJMXAgentDefinition camelJMXAgent;
099 @XmlElements({
100 @XmlElement(name = "beanPostProcessor", type = CamelBeanPostProcessor.class, required = false),
101 @XmlElement(name = "template", type = CamelTemplateFactoryBean.class, required = false),
102 @XmlElement(name = "proxy", type = CamelProxyFactoryDefinition.class, required = false),
103 @XmlElement(name = "export", type = CamelServiceExporterDefinition.class, required = false)})
104 private List beans;
105 @XmlElement(name = "routeBuilder", required = false)
106 private List<RouteBuilderDefinition> builderRefs = new ArrayList<RouteBuilderDefinition>();
107 @XmlElement(name = "endpoint", required = false)
108 private List<EndpointFactoryBean> endpoints;
109 @XmlElement(name = "dataFormats", required = false)
110 private DataFormatsDefinition dataFormats;
111 @XmlElement(name = "onException", required = false)
112 private List<OnExceptionDefinition> exceptionClauses = new ArrayList<OnExceptionDefinition>();
113 @XmlElement(name = "intercept", required = false)
114 private List<InterceptDefinition> intercepts = new ArrayList<InterceptDefinition>();
115 @XmlElement(name = "route", required = false)
116 private List<RouteDefinition> routes = new ArrayList<RouteDefinition>();
117 @XmlTransient
118 private SpringCamelContext context;
119 @XmlTransient
120 private RouteBuilder routeBuilder;
121 @XmlTransient
122 private List<Routes> additionalBuilders = new ArrayList<Routes>();
123 @XmlTransient
124 private ApplicationContext applicationContext;
125 @XmlTransient
126 private ClassLoader contextClassLoaderOnStart;
127 @XmlTransient
128 private BeanPostProcessor beanPostProcessor;
129
130 public CamelContextFactoryBean() {
131 // Lets keep track of the class loader for when we actually do start things up
132 contextClassLoaderOnStart = Thread.currentThread().getContextClassLoader();
133 }
134
135 public Object getObject() throws Exception {
136 return getContext();
137 }
138
139 public Class getObjectType() {
140 return SpringCamelContext.class;
141 }
142
143 public boolean isSingleton() {
144 return true;
145 }
146
147 public ClassLoader getContextClassLoaderOnStart() {
148 return contextClassLoaderOnStart;
149 }
150
151 public List<Routes> getAdditionalBuilders() {
152 return additionalBuilders;
153 }
154
155 public void afterPropertiesSet() throws Exception {
156 // TODO there should be a neater way to do this!
157 if (properties != null) {
158 getContext().setProperties(properties.asMap());
159 }
160 // set the resolvers first
161 PackageScanClassResolver packageResolver = getBeanForType(PackageScanClassResolver.class);
162 if (packageResolver != null) {
163 getContext().setPackageScanClassResolver(packageResolver);
164 }
165 ClassResolver classResolver = getBeanForType(ClassResolver.class);
166 if (classResolver != null) {
167 getContext().setClassResolver(classResolver);
168 }
169
170 Debugger debugger = getBeanForType(Debugger.class);
171 if (debugger != null) {
172 getContext().addInterceptStrategy(debugger);
173 }
174
175 Tracer tracer = getBeanForType(Tracer.class);
176 if (tracer != null) {
177 // use formatter if there is a TraceFormatter bean defined
178 TraceFormatter formatter = getBeanForType(TraceFormatter.class);
179 if (formatter != null) {
180 tracer.setFormatter(formatter);
181 }
182 getContext().addInterceptStrategy(tracer);
183 }
184
185 Delayer delayer = getBeanForType(Delayer.class);
186 if (delayer != null) {
187 getContext().addInterceptStrategy(delayer);
188 }
189
190 // set the lifecycle strategy if defined
191 LifecycleStrategy lifecycleStrategy = getBeanForType(LifecycleStrategy.class);
192 if (lifecycleStrategy != null) {
193 getContext().setLifecycleStrategy(lifecycleStrategy);
194 }
195
196 // set the strategy if defined
197 Registry registry = getBeanForType(Registry.class);
198 if (registry != null) {
199 getContext().setRegistry(registry);
200 }
201
202 // Set the application context and camelContext for the beanPostProcessor
203 if (beanPostProcessor != null) {
204 if (beanPostProcessor instanceof ApplicationContextAware) {
205 ((ApplicationContextAware)beanPostProcessor).setApplicationContext(applicationContext);
206 }
207 if (beanPostProcessor instanceof CamelBeanPostProcessor) {
208 ((CamelBeanPostProcessor)beanPostProcessor).setCamelContext(getContext());
209 }
210 }
211
212 // setup the intercepts
213 for (RouteDefinition route : routes) {
214
215 if (exceptionClauses != null) {
216 route.getOutputs().addAll(exceptionClauses);
217 }
218
219 for (InterceptDefinition intercept : intercepts) {
220 List<ProcessorDefinition<?>> outputs = new ArrayList<ProcessorDefinition<?>>();
221 List<ProcessorDefinition<?>> exceptionHandlers = new ArrayList<ProcessorDefinition<?>>();
222 for (ProcessorDefinition output : route.getOutputs()) {
223 if (output instanceof OnExceptionDefinition) {
224 exceptionHandlers.add(output);
225 } else {
226 outputs.add(output);
227 }
228 }
229
230 // clearing the outputs
231 route.clearOutput();
232
233 // add exception handlers as top children
234 route.getOutputs().addAll(exceptionHandlers);
235
236 // add the interceptor but we must do some pre configuration beforehand
237 intercept.afterPropertiesSet();
238 InterceptDefinition proxy = intercept.createProxy();
239 route.addOutput(proxy);
240 route.pushBlock(proxy.getProceed());
241
242 // if there is a proceed in the interceptor proxy then we should add
243 // the current outputs to out route so we will proceed and continue to route to them
244 ProceedDefinition proceed = ProcessorTypeHelper.findFirstTypeInOutputs(proxy.getOutputs(), ProceedDefinition.class);
245 if (proceed != null) {
246 proceed.getOutputs().addAll(outputs);
247 }
248 }
249
250 }
251
252 if (dataFormats != null) {
253 getContext().setDataFormats(dataFormats.asMap());
254 }
255
256 // lets force any lazy creation
257 getContext().addRouteDefinitions(routes);
258
259 if (camelJMXAgent != null && camelJMXAgent.isDisabled()) {
260 LOG.debug("JMXAgent disabled");
261 getContext().setLifecycleStrategy(new DefaultLifecycleStrategy());
262 } else if (camelJMXAgent != null) {
263 LOG.debug("JMXAgent enabled");
264
265 if (lifecycleStrategy != null) {
266 LOG.warn("lifecycleStrategy will be overriden by InstrumentationLifecycleStrategy");
267 }
268
269 DefaultInstrumentationAgent agent = new DefaultInstrumentationAgent();
270 agent.setConnectorPort(camelJMXAgent.getConnectorPort());
271 agent.setCreateConnector(camelJMXAgent.isCreateConnector());
272 agent.setMBeanObjectDomainName(camelJMXAgent.getMbeanObjectDomainName());
273 agent.setMBeanServerDefaultDomain(camelJMXAgent.getMbeanServerDefaultDomain());
274 agent.setRegistryPort(camelJMXAgent.getRegistryPort());
275 agent.setServiceUrlPath(camelJMXAgent.getServiceUrlPath());
276 agent.setUsePlatformMBeanServer(camelJMXAgent.isUsePlatformMBeanServer());
277
278 getContext().setLifecycleStrategy(new InstrumentationLifecycleStrategy(agent));
279 }
280
281 if (LOG.isDebugEnabled()) {
282 LOG.debug("Found JAXB created routes: " + getRoutes());
283 }
284 findRouteBuilders();
285 installRoutes();
286 }
287
288 @SuppressWarnings("unchecked")
289 private <T> T getBeanForType(Class<T> clazz) {
290 T bean = null;
291 String[] names = getApplicationContext().getBeanNamesForType(clazz, true, true);
292 if (names.length == 1) {
293 bean = (T) getApplicationContext().getBean(names[0], clazz);
294 }
295 if (bean == null) {
296 ApplicationContext parentContext = getApplicationContext().getParent();
297 if (parentContext != null) {
298 names = parentContext.getBeanNamesForType(clazz, true, true);
299 if (names.length == 1) {
300 bean = (T) parentContext.getBean(names[0], clazz);
301 }
302 }
303 }
304 return bean;
305
306 }
307
308 public void destroy() throws Exception {
309 getContext().stop();
310 }
311
312 public void onApplicationEvent(ApplicationEvent event) {
313 if (LOG.isDebugEnabled()) {
314 LOG.debug("Publishing spring-event: " + event);
315 }
316
317 if (event instanceof ContextRefreshedEvent) {
318 // now lets start the CamelContext so that all its possible
319 // dependencies are initialized
320 try {
321 LOG.debug("Starting the context now!");
322 getContext().start();
323 } catch (Exception e) {
324 throw wrapRuntimeCamelException(e);
325 }
326 }
327 }
328
329 // Properties
330 // -------------------------------------------------------------------------
331 public SpringCamelContext getContext() throws Exception {
332 if (context == null) {
333 context = createContext();
334 }
335 return context;
336 }
337
338 public void setContext(SpringCamelContext context) {
339 this.context = context;
340 }
341
342 public List<RouteDefinition> getRoutes() {
343 return routes;
344 }
345
346 public void setRoutes(List<RouteDefinition> routes) {
347 this.routes = routes;
348 }
349
350 public List<InterceptDefinition> getIntercepts() {
351 return intercepts;
352 }
353
354 public void setIntercepts(List<InterceptDefinition> intercepts) {
355 this.intercepts = intercepts;
356 }
357
358 public RouteBuilder getRouteBuilder() {
359 return routeBuilder;
360 }
361
362 /**
363 * Set a single {@link RouteBuilder} to be used to create the default routes
364 * on startup
365 */
366 public void setRouteBuilder(RouteBuilder routeBuilder) {
367 this.routeBuilder = routeBuilder;
368 }
369
370 /**
371 * Set a collection of {@link RouteBuilder} instances to be used to create
372 * the default routes on startup
373 */
374 public void setRouteBuilders(RouteBuilder[] builders) {
375 for (RouteBuilder builder : builders) {
376 additionalBuilders.add(builder);
377 }
378 }
379
380 public ApplicationContext getApplicationContext() {
381 if (applicationContext == null) {
382 throw new IllegalArgumentException("No applicationContext has been injected!");
383 }
384 return applicationContext;
385 }
386
387 public void setApplicationContext(ApplicationContext applicationContext) {
388 this.applicationContext = applicationContext;
389 }
390
391 public PropertiesDefinition getProperties() {
392 return properties;
393 }
394
395 public void setProperties(PropertiesDefinition properties) {
396 this.properties = properties;
397 }
398
399 public String[] getPackages() {
400 return packages;
401 }
402
403 /**
404 * Sets the package names to be recursively searched for Java classes which
405 * extend {@link RouteBuilder} to be auto-wired up to the
406 * {@link SpringCamelContext} as a route. Note that classes are excluded if
407 * they are specifically configured in the spring.xml
408 *
409 * @param packages the package names which are recursively searched
410 */
411 public void setPackages(String[] packages) {
412 this.packages = packages;
413 }
414
415 public void setBeanPostProcessor(BeanPostProcessor postProcessor) {
416 this.beanPostProcessor = postProcessor;
417 }
418
419 public BeanPostProcessor getBeanPostProcessor() {
420 return beanPostProcessor;
421 }
422
423 public void setCamelJMXAgent(CamelJMXAgentDefinition agent) {
424 camelJMXAgent = agent;
425 }
426
427 public Boolean getTrace() {
428 return trace;
429 }
430
431 public void setTrace(Boolean trace) {
432 this.trace = trace;
433 }
434
435 public Long getDelay() {
436 return delay;
437 }
438
439 public void setDelay(Long delay) {
440 this.delay = delay;
441 }
442
443 public CamelJMXAgentDefinition getCamelJMXAgent() {
444 return camelJMXAgent;
445 }
446
447 public List<RouteBuilderDefinition> getBuilderRefs() {
448 return builderRefs;
449 }
450
451 public void setBuilderRefs(List<RouteBuilderDefinition> builderRefs) {
452 this.builderRefs = builderRefs;
453 }
454
455 /**
456 * If enabled this will force all {@link RouteBuilder} classes configured in the Spring
457 * {@link ApplicationContext} to be registered automatically with this CamelContext.
458 */
459 public void setAutowireRouteBuilders(Boolean autowireRouteBuilders) {
460 this.autowireRouteBuilders = autowireRouteBuilders;
461 }
462
463 public String getErrorHandlerRef() {
464 return errorHandlerRef;
465 }
466
467 /**
468 * Sets the name of the error handler object used to default the error handling strategy
469 *
470 * @param errorHandlerRef the Spring bean ref of the error handler
471 */
472 public void setErrorHandlerRef(String errorHandlerRef) {
473 this.errorHandlerRef = errorHandlerRef;
474 }
475
476 public Boolean getShouldStartContext() {
477 return shouldStartContext;
478 }
479
480 public void setShouldStartContext(Boolean shouldStartContext) {
481 this.shouldStartContext = shouldStartContext;
482 }
483
484 // Implementation methods
485 // -------------------------------------------------------------------------
486
487 /**
488 * Create the context
489 */
490 protected SpringCamelContext createContext() {
491 SpringCamelContext ctx = new SpringCamelContext(getApplicationContext());
492 ctx.setName(getId());
493 if (trace != null) {
494 ctx.setTrace(trace);
495 }
496 if (delay != null) {
497 ctx.setDelay(delay);
498 }
499 if (errorHandlerRef != null) {
500 ErrorHandlerBuilder errorHandlerBuilder = (ErrorHandlerBuilder) getApplicationContext().getBean(errorHandlerRef, ErrorHandlerBuilder.class);
501 if (errorHandlerBuilder == null) {
502 throw new IllegalArgumentException("Could not find bean: " + errorHandlerRef);
503 }
504 ctx.setErrorHandlerBuilder(errorHandlerBuilder);
505 }
506
507 if (shouldStartContext != null) {
508 ctx.setShouldStartContext(shouldStartContext);
509 }
510
511 return ctx;
512 }
513
514 /**
515 * Strategy to install all available routes into the context
516 */
517 protected void installRoutes() throws Exception {
518 if (autowireRouteBuilders != null && autowireRouteBuilders) {
519 Map builders = getApplicationContext().getBeansOfType(RouteBuilder.class, true, true);
520 if (builders != null) {
521 for (Object builder : builders.values()) {
522 getContext().addRoutes((RouteBuilder) builder);
523 }
524 }
525 }
526 for (Routes routeBuilder : additionalBuilders) {
527 getContext().addRoutes(routeBuilder);
528 }
529 if (routeBuilder != null) {
530 getContext().addRoutes(routeBuilder);
531 }
532
533 // lets add route builders added from references
534 if (builderRefs != null) {
535 for (RouteBuilderDefinition builderRef : builderRefs) {
536 RouteBuilder builder = builderRef.createRouteBuilder(getContext());
537 getContext().addRoutes(builder);
538 }
539 }
540 }
541
542 /**
543 * Strategy method to try find {@link RouteBuilder} instances on the
544 * classpath
545 */
546 protected void findRouteBuilders() throws Exception, InstantiationException {
547 if (getPackages() != null && getPackages().length > 0) {
548 RouteBuilderFinder finder = new RouteBuilderFinder(getContext(), getPackages(), getContextClassLoaderOnStart(),
549 getBeanPostProcessor(), getContext().getPackageScanClassResolver());
550 finder.appendBuilders(getAdditionalBuilders());
551 }
552 }
553
554 public void setDataFormats(DataFormatsDefinition dataFormats) {
555 this.dataFormats = dataFormats;
556 }
557
558 public DataFormatsDefinition getDataFormats() {
559 return dataFormats;
560 }
561
562 public void setExceptionClauses(List<OnExceptionDefinition> exceptionClauses) {
563 this.exceptionClauses = exceptionClauses;
564 }
565
566 public List<OnExceptionDefinition> getExceptionClauses() {
567 return exceptionClauses;
568 }
569 }