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