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