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