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