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: 782046 $ 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 }