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