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 }