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: 763492 $
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 (LOG.isDebugEnabled()) {
325 LOG.debug("Publishing spring-event: " + event);
326 }
327
328 if (event instanceof ContextRefreshedEvent) {
329 // now lets start the CamelContext so that all its possible
330 // dependencies are initialized
331 try {
332 LOG.debug("Starting the context now!");
333 getContext().start();
334 } catch (Exception e) {
335 throw wrapRuntimeCamelException(e);
336 }
337 }
338 /*
339 * if (context != null) { context.onApplicationEvent(event); }
340 */
341 }
342
343 // Properties
344 // -------------------------------------------------------------------------
345 public SpringCamelContext getContext() throws Exception {
346 if (context == null) {
347 context = createContext();
348 }
349 return context;
350 }
351
352 public void setContext(SpringCamelContext context) {
353 this.context = context;
354 }
355
356 public List<RouteType> getRoutes() {
357 return routes;
358 }
359
360 public void setRoutes(List<RouteType> routes) {
361 this.routes = routes;
362 }
363
364 public List<InterceptType> getIntercepts() {
365 return intercepts;
366 }
367
368 public void setIntercepts(List<InterceptType> intercepts) {
369 this.intercepts = intercepts;
370 }
371
372 public RouteBuilder getRouteBuilder() {
373 return routeBuilder;
374 }
375
376 /**
377 * Set a single {@link RouteBuilder} to be used to create the default routes
378 * on startup
379 */
380 public void setRouteBuilder(RouteBuilder routeBuilder) {
381 this.routeBuilder = routeBuilder;
382 }
383
384 /**
385 * Set a collection of {@link RouteBuilder} instances to be used to create
386 * the default routes on startup
387 */
388 public void setRouteBuilders(RouteBuilder[] builders) {
389 for (RouteBuilder builder : builders) {
390 additionalBuilders.add(builder);
391 }
392 }
393
394 public ApplicationContext getApplicationContext() {
395 if (applicationContext == null) {
396 throw new IllegalArgumentException("No applicationContext has been injected!");
397 }
398 return applicationContext;
399 }
400
401 public void setApplicationContext(ApplicationContext applicationContext) {
402 this.applicationContext = applicationContext;
403 }
404
405 public PropertiesType getProperties() {
406 return properties;
407 }
408
409 public void setProperties(PropertiesType properties) {
410 this.properties = properties;
411 }
412
413 public String[] getPackages() {
414 return packages;
415 }
416
417 /**
418 * Sets the package names to be recursively searched for Java classes which
419 * extend {@link RouteBuilder} to be auto-wired up to the
420 * {@link SpringCamelContext} as a route. Note that classes are excluded if
421 * they are specifically configured in the spring.xml
422 *
423 * @param packages the package names which are recursively searched
424 */
425 public void setPackages(String[] packages) {
426 this.packages = packages;
427 }
428
429 public void setBeanPostProcessor(BeanPostProcessor postProcessor) {
430 this.beanPostProcessor = postProcessor;
431 }
432
433 public BeanPostProcessor getBeanPostProcessor() {
434 return beanPostProcessor;
435 }
436
437 /**
438 * This method merely retrieves the value of the "useJmx" attribute and does
439 * not consider the "disabled" flag in jmxAgent element. The useJmx
440 * attribute will be removed in 2.0. Please the jmxAgent element instead.
441 *
442 * @deprecated Please the jmxAgent element instead. Will be removed in Camel 2.0.
443 */
444 public boolean isJmxEnabled() {
445 return useJmx.booleanValue();
446 }
447
448 /**
449 * @deprecated Please the jmxAgent element instead. Will be removed in Camel 2.0.
450 */
451 public Boolean getUseJmx() {
452 return useJmx;
453 }
454
455 /**
456 * @deprecated Please the jmxAgent element instead. Will be removed in Camel 2.0.
457 */
458 public void setUseJmx(Boolean useJmx) {
459 this.useJmx = useJmx;
460 }
461
462 public void setCamelJMXAgent(CamelJMXAgentType agent) {
463 camelJMXAgent = agent;
464 }
465
466 public Boolean getTrace() {
467 return trace;
468 }
469
470 public void setTrace(Boolean trace) {
471 this.trace = trace;
472 }
473
474 public Long getDelay() {
475 return delay;
476 }
477
478 public void setDelay(Long delay) {
479 this.delay = delay;
480 }
481
482 public CamelJMXAgentType getCamelJMXAgent() {
483 return camelJMXAgent;
484 }
485
486 public List<RouteBuilderRef> getBuilderRefs() {
487 return builderRefs;
488 }
489
490 public void setBuilderRefs(List<RouteBuilderRef> builderRefs) {
491 this.builderRefs = builderRefs;
492 }
493
494 /**
495 * If enabled this will force all {@link RouteBuilder} classes configured in the Spring
496 * {@link ApplicationContext} to be registered automatically with this CamelContext.
497 */
498 public void setAutowireRouteBuilders(Boolean autowireRouteBuilders) {
499 this.autowireRouteBuilders = autowireRouteBuilders;
500 }
501
502 public String getErrorHandlerRef() {
503 return errorHandlerRef;
504 }
505
506 /**
507 * Sets the name of the error handler object used to default the error handling strategy
508 *
509 * @param errorHandlerRef the Spring bean ref of the error handler
510 */
511 public void setErrorHandlerRef(String errorHandlerRef) {
512 this.errorHandlerRef = errorHandlerRef;
513 }
514
515 public Boolean getShouldStartContext() {
516 return shouldStartContext;
517 }
518
519 public void setShouldStartContext(Boolean shouldStartContext) {
520 this.shouldStartContext = shouldStartContext;
521 }
522
523 // Implementation methods
524 // -------------------------------------------------------------------------
525
526 /**
527 * Create the context
528 */
529 protected SpringCamelContext createContext() {
530 SpringCamelContext ctx = new SpringCamelContext(getApplicationContext());
531 ctx.setName(getId());
532 if (trace != null) {
533 ctx.setTrace(trace);
534 }
535 if (delay != null) {
536 ctx.setDelay(delay);
537 }
538 if (errorHandlerRef != null) {
539 ErrorHandlerBuilder errorHandlerBuilder = (ErrorHandlerBuilder) getApplicationContext().getBean(errorHandlerRef, ErrorHandlerBuilder.class);
540 if (errorHandlerBuilder == null) {
541 throw new IllegalArgumentException("Could not find bean: " + errorHandlerRef);
542 }
543 ctx.setErrorHandlerBuilder(errorHandlerBuilder);
544 }
545
546 if (shouldStartContext != null) {
547 ctx.setShouldStartContext(shouldStartContext);
548 }
549
550 return ctx;
551 }
552
553 /**
554 * Strategy to install all available routes into the context
555 */
556 protected void installRoutes() throws Exception {
557 if (autowireRouteBuilders != null && autowireRouteBuilders.booleanValue()) {
558 Map builders = getApplicationContext().getBeansOfType(RouteBuilder.class, true, true);
559 if (builders != null) {
560 for (Object builder : builders.values()) {
561 if (beanPostProcessor != null) {
562 // Inject the annotated resource
563 beanPostProcessor.postProcessBeforeInitialization(builder, builder.toString());
564 }
565 getContext().addRoutes((RouteBuilder) builder);
566 }
567 }
568 }
569 for (Routes routeBuilder : additionalBuilders) {
570 getContext().addRoutes(routeBuilder);
571 }
572 if (routeBuilder != null) {
573 if (beanPostProcessor != null) {
574 // Inject the annotated resource
575 beanPostProcessor.postProcessBeforeInitialization(routeBuilder, routeBuilder.toString());
576 }
577 getContext().addRoutes(routeBuilder);
578 }
579
580 // lets add route builders added from references
581 if (builderRefs != null) {
582 for (RouteBuilderRef builderRef : builderRefs) {
583 RouteBuilder builder = builderRef.createRouteBuilder(getContext());
584 if (builder != null) {
585 if (beanPostProcessor != null) {
586 // Inject the annotated resource
587 beanPostProcessor.postProcessBeforeInitialization(builder, builder.toString());
588 }
589 getContext().addRoutes(builder);
590 } else {
591 // support to get the route here
592 Routes routes = builderRef.createRoutes(getContext());
593 if (routes != null) {
594 getContext().addRoutes(routes);
595 } else {
596 // Throw the exception that we can't find any build here
597 throw new CamelException("Can't find any routes info with this RouteBuilderDefinition " + builderRef);
598 }
599 }
600 }
601 }
602 }
603
604 /**
605 * Strategy method to try find {@link RouteBuilder} instances on the
606 * classpath
607 */
608 protected void findRouteBuilders() throws Exception, InstantiationException {
609 if (getPackages() != null && getPackages().length > 0) {
610 RouteBuilderFinder finder = new RouteBuilderFinder(getContext(), getPackages(), getContextClassLoaderOnStart(), getBeanPostProcessor(), createResolverUtil());
611 finder.appendBuilders(getAdditionalBuilders());
612 }
613 }
614
615 /**
616 * The factory method for create the ResolverUtil
617 * @return a new instance of ResolverUtil
618 */
619 protected ResolverUtil createResolverUtil() {
620 return new ResolverUtil();
621 }
622
623 public void setDataFormats(DataFormatsType dataFormats) {
624 this.dataFormats = dataFormats;
625 }
626
627 public DataFormatsType getDataFormats() {
628 return dataFormats;
629 }
630 }