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    }