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: 706059 $
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    }