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.CamelException;
032 import org.apache.camel.Routes;
033 import org.apache.camel.builder.ErrorHandlerBuilder;
034 import org.apache.camel.builder.RouteBuilder;
035 import org.apache.camel.impl.DefaultLifecycleStrategy;
036 import org.apache.camel.management.DefaultInstrumentationAgent;
037 import org.apache.camel.management.InstrumentationLifecycleStrategy;
038 import org.apache.camel.model.ExceptionType;
039 import org.apache.camel.model.IdentifiedType;
040 import org.apache.camel.model.InterceptType;
041 import org.apache.camel.model.PolicyRef;
042 import org.apache.camel.model.ProceedType;
043 import org.apache.camel.model.ProcessorType;
044 import org.apache.camel.model.RouteBuilderRef;
045 import org.apache.camel.model.RouteContainer;
046 import org.apache.camel.model.RouteType;
047 import org.apache.camel.model.config.PropertiesType;
048 import org.apache.camel.model.dataformat.DataFormatsType;
049 import org.apache.camel.processor.interceptor.Debugger;
050 import org.apache.camel.processor.interceptor.Delayer;
051 import org.apache.camel.processor.interceptor.TraceFormatter;
052 import org.apache.camel.processor.interceptor.Tracer;
053 import org.apache.camel.spi.LifecycleStrategy;
054 import org.apache.camel.spi.Registry;
055 import org.apache.camel.util.ProcessorTypeHelper;
056 import org.apache.camel.util.ResolverUtil;
057 import org.apache.commons.logging.Log;
058 import org.apache.commons.logging.LogFactory;
059 import org.springframework.beans.factory.DisposableBean;
060 import org.springframework.beans.factory.FactoryBean;
061 import org.springframework.beans.factory.InitializingBean;
062 import org.springframework.beans.factory.config.BeanPostProcessor;
063 import org.springframework.context.ApplicationContext;
064 import org.springframework.context.ApplicationContextAware;
065 import org.springframework.context.ApplicationEvent;
066 import org.springframework.context.ApplicationListener;
067 import org.springframework.context.event.ContextRefreshedEvent;
068
069 import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
070
071
072 /**
073 * A Spring {@link FactoryBean} to create and initialize a
074 * {@link SpringCamelContext} and install routes either explicitly configured in
075 * Spring XML or found by searching the classpath for Java classes which extend
076 * {@link RouteBuilder} using the nested {@link #setPackages(String[])}.
077 *
078 * @version $Revision: 13162 $
079 */
080 @XmlRootElement(name = "camelContext")
081 @XmlAccessorType(XmlAccessType.FIELD)
082 public class CamelContextFactoryBean extends IdentifiedType implements RouteContainer, FactoryBean, InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener {
083 private static final Log LOG = LogFactory.getLog(CamelContextFactoryBean.class);
084
085 @XmlAttribute(required = false)
086 @Deprecated
087 private Boolean useJmx = Boolean.TRUE;
088 @XmlAttribute(required = false)
089 private Boolean autowireRouteBuilders = Boolean.TRUE;
090 @XmlAttribute(required = false)
091 private Boolean trace;
092 @XmlAttribute(required = false)
093 private Long delay;
094 @XmlAttribute(required = false)
095 private String errorHandlerRef;
096 @XmlAttribute(required = false)
097 private Boolean shouldStartContext = Boolean.TRUE;
098 @XmlElement(name = "properties", required = false)
099 private PropertiesType properties;
100 @XmlElement(name = "package", required = false)
101 private String[] packages = {};
102 @XmlElement(name = "jmxAgent", type = CamelJMXAgentType.class, required = false)
103 private CamelJMXAgentType camelJMXAgent;
104 @XmlElements({
105 @XmlElement(name = "beanPostProcessor", type = CamelBeanPostProcessor.class, required = false),
106 @XmlElement(name = "template", type = CamelTemplateFactoryBean.class, required = false),
107 @XmlElement(name = "proxy", type = CamelProxyFactoryType.class, required = false),
108 @XmlElement(name = "export", type = CamelServiceExporterType.class, required = false)})
109 private List beans;
110 @XmlElement(name = "routeBuilderRef", required = false)
111 private List<RouteBuilderRef> builderRefs = new ArrayList<RouteBuilderRef>();
112 @XmlElement(name = "endpoint", required = false)
113 private List<EndpointFactoryBean> endpoints;
114 @XmlElement(name = "dataFormats", required = false)
115 private DataFormatsType dataFormats;
116 @XmlElement(name = "intercept", required = false)
117 private List<InterceptType> intercepts = new ArrayList<InterceptType>();
118 @XmlElement(name = "route", required = false)
119 private List<RouteType> routes = new ArrayList<RouteType>();
120 @XmlTransient
121 private SpringCamelContext context;
122 @XmlTransient
123 private RouteBuilder routeBuilder;
124 @XmlTransient
125 private List<Routes> additionalBuilders = new ArrayList<Routes>();
126 @XmlTransient
127 private ApplicationContext applicationContext;
128 @XmlTransient
129 private ClassLoader contextClassLoaderOnStart;
130 @XmlTransient
131 private BeanPostProcessor beanPostProcessor;
132
133 public CamelContextFactoryBean() {
134 // Lets keep track of the class loader for when we actually do start things up
135 contextClassLoaderOnStart = Thread.currentThread().getContextClassLoader();
136 }
137
138 public Object getObject() throws Exception {
139 return getContext();
140 }
141
142 public Class getObjectType() {
143 return SpringCamelContext.class;
144 }
145
146 public boolean isSingleton() {
147 return true;
148 }
149
150 public ClassLoader getContextClassLoaderOnStart() {
151 return contextClassLoaderOnStart;
152 }
153
154 public List<Routes> getAdditionalBuilders() {
155 return additionalBuilders;
156 }
157
158 public void afterPropertiesSet() throws Exception {
159 // TODO there should be a neater way to do this!
160 if (properties != null) {
161 getContext().setProperties(properties.asMap());
162 }
163 Debugger debugger = getBeanForType(Debugger.class);
164 if (debugger != null) {
165 getContext().addInterceptStrategy(debugger);
166 }
167
168 Tracer tracer = getBeanForType(Tracer.class);
169 if (tracer != null) {
170 // use formatter if there is a TraceFormatter bean defined
171 TraceFormatter formatter = getBeanForType(TraceFormatter.class);
172 if (formatter != null) {
173 tracer.setFormatter(formatter);
174 }
175 getContext().addInterceptStrategy(tracer);
176 }
177
178 Delayer delayer = getBeanForType(Delayer.class);
179 if (delayer != null) {
180 getContext().addInterceptStrategy(delayer);
181 }
182
183 // set the lifecycle strategy if defined
184 LifecycleStrategy lifecycleStrategy = getBeanForType(LifecycleStrategy.class);
185 if (lifecycleStrategy != null) {
186 getContext().setLifecycleStrategy(lifecycleStrategy);
187 }
188
189 // set the strategy if defined
190 Registry registry = getBeanForType(Registry.class);
191 if (registry != null) {
192 getContext().setRegistry(registry);
193 }
194
195 // Set the application context and camelContext for the beanPostProcessor
196 if (beanPostProcessor != null) {
197 if (beanPostProcessor instanceof ApplicationContextAware) {
198 ((ApplicationContextAware)beanPostProcessor).setApplicationContext(applicationContext);
199 }
200 if (beanPostProcessor instanceof CamelBeanPostProcessor) {
201 ((CamelBeanPostProcessor)beanPostProcessor).setCamelContext(getContext());
202 }
203 }
204
205 // do special preparation for some concepts such as interceptors and policies
206 for (RouteType route : routes) {
207 initInterceptors(route);
208 initPolicies(route);
209 }
210
211 if (dataFormats != null) {
212 getContext().setDataFormats(dataFormats.asMap());
213 }
214
215 // lets force any lazy creation
216 getContext().addRouteDefinitions(routes);
217
218 if (!isJmxEnabled() || (camelJMXAgent != null && camelJMXAgent.isDisabled())) {
219 LOG.debug("JMXAgent disabled");
220 getContext().setLifecycleStrategy(new DefaultLifecycleStrategy());
221 } else if (camelJMXAgent != null) {
222 LOG.debug("JMXAgent enabled");
223
224 if (lifecycleStrategy != null) {
225 LOG.warn("lifecycleStrategy will be overriden by InstrumentationLifecycleStrategy");
226 }
227
228 DefaultInstrumentationAgent agent = new DefaultInstrumentationAgent();
229 agent.setConnectorPort(camelJMXAgent.getConnectorPort());
230 agent.setCreateConnector(camelJMXAgent.isCreateConnector());
231 agent.setMBeanObjectDomainName(camelJMXAgent.getMbeanObjectDomainName());
232 agent.setMBeanServerDefaultDomain(camelJMXAgent.getMbeanServerDefaultDomain());
233 agent.setRegistryPort(camelJMXAgent.getRegistryPort());
234 agent.setServiceUrlPath(camelJMXAgent.getServiceUrlPath());
235 agent.setUsePlatformMBeanServer(camelJMXAgent.isUsePlatformMBeanServer());
236
237 getContext().setLifecycleStrategy(new InstrumentationLifecycleStrategy(agent));
238 }
239
240 if (LOG.isDebugEnabled()) {
241 LOG.debug("Found JAXB created routes: " + getRoutes());
242 }
243 findRouteBuilders();
244 installRoutes();
245 }
246
247 private void initInterceptors(RouteType route) {
248 // setup the intercepts correctly as the JAXB have not properly setup our routes
249 for (InterceptType intercept : intercepts) {
250 List<ProcessorType<?>> outputs = new ArrayList<ProcessorType<?>>();
251 List<ProcessorType<?>> exceptionHandlers = new ArrayList<ProcessorType<?>>();
252 for (ProcessorType output : route.getOutputs()) {
253 if (output instanceof ExceptionType) {
254 exceptionHandlers.add(output);
255 } else {
256 outputs.add(output);
257 }
258 }
259
260 // clearing the outputs
261 route.clearOutput();
262
263 // add exception handlers as top children
264 route.getOutputs().addAll(exceptionHandlers);
265
266 // add the interceptor but we must do some pre configuration beforehand
267 intercept.afterPropertiesSet();
268 InterceptType proxy = intercept.createProxy();
269 route.addOutput(proxy);
270 route.pushBlock(proxy.getProceed());
271
272 // if there is a proceed in the interceptor proxy then we should add
273 // the current outputs to out route so we will proceed and continue to route to them
274 ProceedType proceed = ProcessorTypeHelper.findFirstTypeInOutputs(proxy.getOutputs(), ProceedType.class);
275 if (proceed != null) {
276 proceed.getOutputs().addAll(outputs);
277 }
278 }
279 }
280
281 private void initPolicies(RouteType route) {
282 // setup the policies as JAXB yet again have not created a correct model for us
283 List<ProcessorType<?>> types = route.getOutputs();
284 PolicyRef policy = null;
285 for (ProcessorType type : types) {
286 if (type instanceof PolicyRef) {
287 policy = (PolicyRef) type;
288 } else if (policy != null) {
289 // the outputs should be moved to the policy
290 policy.addOutput(type);
291 }
292 }
293 // did we find a policy if so add replace it as the only output on the route
294 if (policy != null) {
295 route.clearOutput();
296 route.addOutput(policy);
297 }
298 }
299
300 private <T> T getBeanForType(Class<T> clazz) {
301 T bean = null;
302 String[] names = getApplicationContext().getBeanNamesForType(clazz, true, true);
303 if (names.length == 1) {
304 bean = (T) getApplicationContext().getBean(names[0], clazz);
305 }
306 if (bean == null) {
307 ApplicationContext parentContext = getApplicationContext().getParent();
308 if (parentContext != null) {
309 names = parentContext.getBeanNamesForType(clazz, true, true);
310 if (names.length == 1) {
311 bean = (T) parentContext.getBean(names[0], clazz);
312 }
313 }
314 }
315 return bean;
316
317 }
318
319 public void destroy() throws Exception {
320 getContext().stop();
321 }
322
323 public void onApplicationEvent(ApplicationEvent event) {
324 if (context != null) {
325 // let the spring camel context handle the events
326 context.onApplicationEvent(event);
327 } else {
328 if (LOG.isDebugEnabled()) {
329 LOG.debug("Publishing spring-event: " + event);
330 }
331
332 if (event instanceof ContextRefreshedEvent) {
333 // now lets start the CamelContext so that all its possible
334 // dependencies are initialized
335 try {
336 LOG.debug("Starting the context now!");
337 getContext().start();
338 } catch (Exception e) {
339 throw wrapRuntimeCamelException(e);
340 }
341 }
342 }
343 /*
344 * if (context != null) { context.onApplicationEvent(event); }
345 */
346 }
347
348 // Properties
349 // -------------------------------------------------------------------------
350 public SpringCamelContext getContext() throws Exception {
351 if (context == null) {
352 context = createContext();
353 }
354 return context;
355 }
356
357 public void setContext(SpringCamelContext context) {
358 this.context = context;
359 }
360
361 public List<RouteType> getRoutes() {
362 return routes;
363 }
364
365 public void setRoutes(List<RouteType> routes) {
366 this.routes = routes;
367 }
368
369 public List<InterceptType> getIntercepts() {
370 return intercepts;
371 }
372
373 public void setIntercepts(List<InterceptType> intercepts) {
374 this.intercepts = intercepts;
375 }
376
377 public RouteBuilder getRouteBuilder() {
378 return routeBuilder;
379 }
380
381 /**
382 * Set a single {@link RouteBuilder} to be used to create the default routes
383 * on startup
384 */
385 public void setRouteBuilder(RouteBuilder routeBuilder) {
386 this.routeBuilder = routeBuilder;
387 }
388
389 /**
390 * Set a collection of {@link RouteBuilder} instances to be used to create
391 * the default routes on startup
392 */
393 public void setRouteBuilders(RouteBuilder[] builders) {
394 for (RouteBuilder builder : builders) {
395 additionalBuilders.add(builder);
396 }
397 }
398
399 public ApplicationContext getApplicationContext() {
400 if (applicationContext == null) {
401 throw new IllegalArgumentException("No applicationContext has been injected!");
402 }
403 return applicationContext;
404 }
405
406 public void setApplicationContext(ApplicationContext applicationContext) {
407 this.applicationContext = applicationContext;
408 }
409
410 public PropertiesType getProperties() {
411 return properties;
412 }
413
414 public void setProperties(PropertiesType properties) {
415 this.properties = properties;
416 }
417
418 public String[] getPackages() {
419 return packages;
420 }
421
422 /**
423 * Sets the package names to be recursively searched for Java classes which
424 * extend {@link RouteBuilder} to be auto-wired up to the
425 * {@link SpringCamelContext} as a route. Note that classes are excluded if
426 * they are specifically configured in the spring.xml
427 *
428 * @param packages the package names which are recursively searched
429 */
430 public void setPackages(String[] packages) {
431 this.packages = packages;
432 }
433
434 public void setBeanPostProcessor(BeanPostProcessor postProcessor) {
435 this.beanPostProcessor = postProcessor;
436 }
437
438 public BeanPostProcessor getBeanPostProcessor() {
439 return beanPostProcessor;
440 }
441
442 /**
443 * This method merely retrieves the value of the "useJmx" attribute and does
444 * not consider the "disabled" flag in jmxAgent element. The useJmx
445 * attribute will be removed in 2.0. Please the jmxAgent element instead.
446 *
447 * @deprecated Please the jmxAgent element instead. Will be removed in Camel 2.0.
448 */
449 public boolean isJmxEnabled() {
450 return useJmx.booleanValue();
451 }
452
453 /**
454 * @deprecated Please the jmxAgent element instead. Will be removed in Camel 2.0.
455 */
456 public Boolean getUseJmx() {
457 return useJmx;
458 }
459
460 /**
461 * @deprecated Please the jmxAgent element instead. Will be removed in Camel 2.0.
462 */
463 public void setUseJmx(Boolean useJmx) {
464 this.useJmx = useJmx;
465 }
466
467 public void setCamelJMXAgent(CamelJMXAgentType agent) {
468 camelJMXAgent = agent;
469 }
470
471 public Boolean getTrace() {
472 return trace;
473 }
474
475 public void setTrace(Boolean trace) {
476 this.trace = trace;
477 }
478
479 public Long getDelay() {
480 return delay;
481 }
482
483 public void setDelay(Long delay) {
484 this.delay = delay;
485 }
486
487 public CamelJMXAgentType getCamelJMXAgent() {
488 return camelJMXAgent;
489 }
490
491 public List<RouteBuilderRef> getBuilderRefs() {
492 return builderRefs;
493 }
494
495 public void setBuilderRefs(List<RouteBuilderRef> builderRefs) {
496 this.builderRefs = builderRefs;
497 }
498
499 /**
500 * If enabled this will force all {@link RouteBuilder} classes configured in the Spring
501 * {@link ApplicationContext} to be registered automatically with this CamelContext.
502 */
503 public void setAutowireRouteBuilders(Boolean autowireRouteBuilders) {
504 this.autowireRouteBuilders = autowireRouteBuilders;
505 }
506
507 public String getErrorHandlerRef() {
508 return errorHandlerRef;
509 }
510
511 /**
512 * Sets the name of the error handler object used to default the error handling strategy
513 *
514 * @param errorHandlerRef the Spring bean ref of the error handler
515 */
516 public void setErrorHandlerRef(String errorHandlerRef) {
517 this.errorHandlerRef = errorHandlerRef;
518 }
519
520 public Boolean getShouldStartContext() {
521 return shouldStartContext;
522 }
523
524 public void setShouldStartContext(Boolean shouldStartContext) {
525 this.shouldStartContext = shouldStartContext;
526 }
527
528 // Implementation methods
529 // -------------------------------------------------------------------------
530
531 /**
532 * Create the context
533 */
534 protected SpringCamelContext createContext() {
535 SpringCamelContext ctx = new SpringCamelContext(getApplicationContext());
536 ctx.setName(getId());
537 if (trace != null) {
538 ctx.setTrace(trace);
539 }
540 if (delay != null) {
541 ctx.setDelay(delay);
542 }
543 if (errorHandlerRef != null) {
544 ErrorHandlerBuilder errorHandlerBuilder = (ErrorHandlerBuilder) getApplicationContext().getBean(errorHandlerRef, ErrorHandlerBuilder.class);
545 if (errorHandlerBuilder == null) {
546 throw new IllegalArgumentException("Could not find bean: " + errorHandlerRef);
547 }
548 ctx.setErrorHandlerBuilder(errorHandlerBuilder);
549 }
550
551 if (shouldStartContext != null) {
552 ctx.setShouldStartContext(shouldStartContext);
553 }
554
555 return ctx;
556 }
557
558 /**
559 * Strategy to install all available routes into the context
560 */
561 protected void installRoutes() throws Exception {
562 if (autowireRouteBuilders != null && autowireRouteBuilders.booleanValue()) {
563 Map builders = getApplicationContext().getBeansOfType(RouteBuilder.class, true, true);
564 if (builders != null) {
565 for (Object builder : builders.values()) {
566 if (beanPostProcessor != null) {
567 // Inject the annotated resource
568 beanPostProcessor.postProcessBeforeInitialization(builder, builder.toString());
569 }
570 getContext().addRoutes((RouteBuilder) builder);
571 }
572 }
573 }
574 for (Routes routeBuilder : additionalBuilders) {
575 getContext().addRoutes(routeBuilder);
576 }
577 if (routeBuilder != null) {
578 if (beanPostProcessor != null) {
579 // Inject the annotated resource
580 beanPostProcessor.postProcessBeforeInitialization(routeBuilder, routeBuilder.toString());
581 }
582 getContext().addRoutes(routeBuilder);
583 }
584
585 // lets add route builders added from references
586 if (builderRefs != null) {
587 for (RouteBuilderRef builderRef : builderRefs) {
588 RouteBuilder builder = builderRef.createRouteBuilder(getContext());
589 if (builder != null) {
590 if (beanPostProcessor != null) {
591 // Inject the annotated resource
592 beanPostProcessor.postProcessBeforeInitialization(builder, builder.toString());
593 }
594 getContext().addRoutes(builder);
595 } else {
596 // support to get the route here
597 Routes routes = builderRef.createRoutes(getContext());
598 if (routes != null) {
599 getContext().addRoutes(routes);
600 } else {
601 // Throw the exception that we can't find any build here
602 throw new CamelException("Cannot find any routes with this RouteBuilder reference: " + builderRef);
603 }
604 }
605 }
606 }
607 }
608
609 /**
610 * Strategy method to try find {@link RouteBuilder} instances on the
611 * classpath
612 */
613 protected void findRouteBuilders() throws Exception, InstantiationException {
614 if (getPackages() != null && getPackages().length > 0) {
615 RouteBuilderFinder finder = new RouteBuilderFinder(getContext(), getPackages(), getContextClassLoaderOnStart(), getBeanPostProcessor(), createResolverUtil());
616 finder.appendBuilders(getAdditionalBuilders());
617 }
618 }
619
620 /**
621 * The factory method for create the ResolverUtil
622 * @return a new instance of ResolverUtil
623 */
624 protected ResolverUtil createResolverUtil() {
625 return new ResolverUtil();
626 }
627
628 public void setDataFormats(DataFormatsType dataFormats) {
629 this.dataFormats = dataFormats;
630 }
631
632 public DataFormatsType getDataFormats() {
633 return dataFormats;
634 }
635 }