001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.spring;
018    
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.Map;
022    
023    import javax.xml.bind.annotation.XmlAccessType;
024    import javax.xml.bind.annotation.XmlAccessorType;
025    import javax.xml.bind.annotation.XmlAttribute;
026    import javax.xml.bind.annotation.XmlElement;
027    import javax.xml.bind.annotation.XmlElementRef;
028    import javax.xml.bind.annotation.XmlElements;
029    import javax.xml.bind.annotation.XmlRootElement;
030    import javax.xml.bind.annotation.XmlTransient;
031    
032    import org.apache.camel.RuntimeCamelException;
033    import org.apache.camel.builder.ErrorHandlerBuilder;
034    import org.apache.camel.builder.RouteBuilder;
035    import org.apache.camel.impl.DefaultLifecycleStrategy;
036    import org.apache.camel.management.DefaultInstrumentationAgent;
037    import org.apache.camel.management.InstrumentationLifecycleStrategy;
038    import org.apache.camel.model.IdentifiedType;
039    import org.apache.camel.model.RouteBuilderRef;
040    import org.apache.camel.model.RouteContainer;
041    import org.apache.camel.model.RouteType;
042    import org.apache.camel.model.dataformat.DataFormatType;
043    import org.apache.camel.processor.interceptor.Debugger;
044    import org.apache.camel.processor.interceptor.Tracer;
045    import org.apache.camel.spi.LifecycleStrategy;
046    import org.apache.camel.spi.Registry;
047    import org.apache.commons.logging.Log;
048    import org.apache.commons.logging.LogFactory;
049    import org.springframework.beans.factory.DisposableBean;
050    import org.springframework.beans.factory.FactoryBean;
051    import org.springframework.beans.factory.InitializingBean;
052    import org.springframework.beans.factory.config.BeanPostProcessor;
053    import org.springframework.context.ApplicationContext;
054    import org.springframework.context.ApplicationContextAware;
055    import org.springframework.context.ApplicationEvent;
056    import org.springframework.context.ApplicationListener;
057    import org.springframework.context.event.ContextRefreshedEvent;
058    
059    /**
060     * A Spring {@link FactoryBean} to create and initialize a
061     * {@link SpringCamelContext} and install routes either explicitly configured in
062     * Spring XML or found by searching the classpath for Java classes which extend
063     * {@link RouteBuilder} using the nested {@link #setPackages(String[])}.
064     *
065     * @version $Revision: 674383 $
066     */
067    @XmlRootElement(name = "camelContext")
068    @XmlAccessorType(XmlAccessType.FIELD)
069    public class CamelContextFactoryBean extends IdentifiedType implements RouteContainer, FactoryBean, InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener {
070        private static final Log LOG = LogFactory.getLog(CamelContextFactoryBean.class);
071    
072        @XmlAttribute(required = false)
073        private Boolean useJmx = Boolean.TRUE;
074        @XmlAttribute(required = false)
075        private Boolean autowireRouteBuilders = Boolean.TRUE;
076        @XmlAttribute(required = false)
077        private Boolean trace;
078        @XmlAttribute(required = false)
079        private String errorHandlerRef;
080        @XmlElement(name = "package", required = false)
081        private String[] packages = {};
082        @XmlElement(name = "jmxAgent", type = CamelJMXAgentType.class, required = false)
083        private CamelJMXAgentType camelJMXAgent;
084        @XmlElements({
085            @XmlElement(name = "beanPostProcessor", type = CamelBeanPostProcessor.class, required = false),
086            @XmlElement(name = "template", type = CamelTemplateFactoryBean.class, required = false),
087            @XmlElement(name = "proxy", type = CamelProxyFactoryType.class, required = false),
088            @XmlElement(name = "export", type = CamelServiceExporterType.class, required = false)})
089        private List beans;
090        @XmlElement(name = "routeBuilderRef", required = false)
091        private List<RouteBuilderRef> builderRefs = new ArrayList<RouteBuilderRef>();
092        @XmlElement(name = "endpoint", required = false)
093        private List<EndpointFactoryBean> endpoints;
094        @XmlElementRef
095        private List<DataFormatType> dataFormats;
096        @XmlElement(name = "route", required = false)
097        private List<RouteType> routes = new ArrayList<RouteType>();
098        @XmlTransient
099        private SpringCamelContext context;
100        @XmlTransient
101        private RouteBuilder routeBuilder;
102        @XmlTransient
103        private List<RouteBuilder> additionalBuilders = new ArrayList<RouteBuilder>();
104        @XmlTransient
105        private ApplicationContext applicationContext;
106        @XmlTransient
107        private ClassLoader contextClassLoaderOnStart;
108        @XmlTransient
109        private BeanPostProcessor beanPostProcessor;
110    
111        public CamelContextFactoryBean() {
112            // Lets keep track of the class loader for when we actually do start things up
113            contextClassLoaderOnStart = Thread.currentThread().getContextClassLoader();
114        }
115    
116        public Object getObject() throws Exception {
117            return getContext();
118        }
119    
120        public Class getObjectType() {
121            return SpringCamelContext.class;
122        }
123    
124        public boolean isSingleton() {
125            return true;
126        }
127    
128        public void afterPropertiesSet() throws Exception {
129            // lets see if we can find a debugger to add
130            // TODO there should be a neater way to do this!
131            Debugger debugger = getBeanForType(Debugger.class);
132            if (debugger != null) {
133                getContext().addInterceptStrategy(debugger);
134            }
135            Tracer tracer = getBeanForType(Tracer.class);
136            if (tracer != null) {
137                getContext().addInterceptStrategy(tracer);
138            }
139    
140            // set the lifecycle strategy if defined
141            LifecycleStrategy lifecycleStrategy = getBeanForType(LifecycleStrategy.class);
142            if (lifecycleStrategy != null) {
143                getContext().setLifecycleStrategy(lifecycleStrategy);
144            }
145    
146            // set the strategy if defined
147            Registry registry = getBeanForType(Registry.class);
148            if (registry != null) {
149                getContext().setRegistry(registry);
150            }
151    
152            // Set the application context and camelContext for the beanPostProcessor
153            if (beanPostProcessor != null) {
154                if (beanPostProcessor instanceof ApplicationContextAware) {
155                    ((ApplicationContextAware)beanPostProcessor).setApplicationContext(applicationContext);
156                }
157                if (beanPostProcessor instanceof CamelBeanPostProcessor) {
158                    ((CamelBeanPostProcessor)beanPostProcessor).setCamelContext(getContext());
159                }
160            }
161    
162            // lets force any lazy creation
163            getContext().addRouteDefinitions(routes);
164    
165            if (!isJmxEnabled()
166                    || (camelJMXAgent != null && camelJMXAgent.isDisabled() != null && camelJMXAgent.isDisabled())) {
167                LOG.debug("JMXAgent disabled");
168                getContext().setLifecycleStrategy(new DefaultLifecycleStrategy());
169            } else if (camelJMXAgent != null) {
170                LOG.debug("JMXAgent enabled");
171    
172                if (lifecycleStrategy != null) {
173                    LOG.warn("lifecycleStrategy will be overriden by InstrumentationLifecycleStrategy");
174                }
175    
176                DefaultInstrumentationAgent agent = new DefaultInstrumentationAgent();
177                agent.setConnectorPort(camelJMXAgent.getConnectorPort());
178                agent.setCreateConnector(camelJMXAgent.isCreateConnector());
179                agent.setMBeanObjectDomainName(camelJMXAgent.getMbeanObjectDomainName());
180                agent.setMBeanServerDefaultDomain(camelJMXAgent.getMbeanServerDefaultDomain());
181                agent.setRegistryPort(camelJMXAgent.getRegistryPort());
182                agent.setServiceUrlPath(camelJMXAgent.getServiceUrlPath());
183                agent.setUsePlatformMBeanServer(camelJMXAgent.isUsePlatformMBeanServer());
184    
185                getContext().setLifecycleStrategy(new InstrumentationLifecycleStrategy(agent));
186            }
187    
188            if (LOG.isDebugEnabled()) {
189                LOG.debug("Found JAXB created routes: " + getRoutes());
190            }
191            findRouteBuiders();
192            installRoutes();
193        }
194    
195        private <T> T getBeanForType(Class<T> clazz) {
196            T bean = null;
197            String[] names = getApplicationContext().getBeanNamesForType(clazz, true, true);
198            if (names.length == 1) {
199                bean = (T) getApplicationContext().getBean(names[0], clazz);
200            }
201            if (bean == null) {
202                ApplicationContext parentContext = getApplicationContext().getParent();
203                if (parentContext != null) {
204                    names = parentContext.getBeanNamesForType(clazz, true, true);
205                    if (names.length == 1) {
206                        bean = (T) parentContext.getBean(names[0], clazz);
207                    }
208                }
209            }
210            return bean;
211    
212        }
213    
214        public void destroy() throws Exception {
215            getContext().stop();
216        }
217    
218        public void onApplicationEvent(ApplicationEvent event) {
219            if (LOG.isDebugEnabled()) {
220                LOG.debug("Publishing event: " + event);
221            }
222    
223            if (event instanceof ContextRefreshedEvent) {
224                // now lets start the CamelContext so that all its possible
225                // dependencies are initailized
226                try {
227                    LOG.debug("Starting the context now!");
228                    getContext().start();
229                } catch (Exception e) {
230                    throw new RuntimeCamelException(e);
231                }
232            }
233            /*
234             * if (context != null) { context.onApplicationEvent(event); }
235             */
236        }
237    
238        // Properties
239        // -------------------------------------------------------------------------
240        public SpringCamelContext getContext() throws Exception {
241            if (context == null) {
242                context = createContext();
243            }
244            return context;
245        }
246    
247        public void setContext(SpringCamelContext context) {
248            this.context = context;
249        }
250    
251        public List<RouteType> getRoutes() {
252            return routes;
253        }
254    
255        public void setRoutes(List<RouteType> routes) {
256            this.routes = routes;
257        }
258    
259        public RouteBuilder getRouteBuilder() {
260            return routeBuilder;
261        }
262    
263        /**
264         * Set a single {@link RouteBuilder} to be used to create the default routes
265         * on startup
266         */
267        public void setRouteBuilder(RouteBuilder routeBuilder) {
268            this.routeBuilder = routeBuilder;
269        }
270    
271        /**
272         * Set a collection of {@link RouteBuilder} instances to be used to create
273         * the default routes on startup
274         */
275        public void setRouteBuilders(RouteBuilder[] builders) {
276            for (RouteBuilder builder : builders) {
277                additionalBuilders.add(builder);
278            }
279        }
280    
281        public ApplicationContext getApplicationContext() {
282            if (applicationContext == null) {
283                throw new IllegalArgumentException("No applicationContext has been injected!");
284            }
285            return applicationContext;
286        }
287    
288        public void setApplicationContext(ApplicationContext applicationContext) {
289            this.applicationContext = applicationContext;
290        }
291    
292        public String[] getPackages() {
293            return packages;
294        }
295    
296        /**
297         * Sets the package names to be recursively searched for Java classes which
298         * extend {@link RouteBuilder} to be auto-wired up to the
299         * {@link SpringCamelContext} as a route. Note that classes are excluded if
300         * they are specifically configured in the spring.xml
301         *
302         * @param packages the package names which are recursively searched
303         */
304        public void setPackages(String[] packages) {
305            this.packages = packages;
306        }
307    
308        public void setBeanPostProcessor(BeanPostProcessor postProcessor) {
309            this.beanPostProcessor = postProcessor;
310        }
311    
312        public BeanPostProcessor getBeanPostProcessor() {
313            return beanPostProcessor;
314        }
315    
316        /**
317         * This method merely retrieves the value of the "useJmx" attribute and does
318         * not consider the "disabled" flag in jmxAgent element.  The useJmx
319         * attribute will be removed in 2.0.  Please the jmxAgent element instead.
320         *
321         * @deprecated Please the jmxAgent element instead. Will be removed in Camel 2.0.
322         */
323        public boolean isJmxEnabled() {
324            return useJmx.booleanValue();
325        }
326    
327        public Boolean getUseJmx() {
328            return useJmx;
329        }
330    
331        /**
332         * @deprecated Please the jmxAgent element instead. Will be removed in Camel 2.0.
333         */
334        public void setUseJmx(Boolean useJmx) {
335            this.useJmx = useJmx;
336        }
337    
338        public void setCamelJMXAgent(CamelJMXAgentType agent) {
339            camelJMXAgent = agent;
340        }
341    
342        public Boolean getTrace() {
343            return trace;
344        }
345    
346        public void setTrace(Boolean trace) {
347            this.trace = trace;
348        }
349    
350        public CamelJMXAgentType getCamelJMXAgent() {
351            return camelJMXAgent;
352        }
353    
354        public List<RouteBuilderRef> getBuilderRefs() {
355            return builderRefs;
356        }
357    
358        public void setBuilderRefs(List<RouteBuilderRef> builderRefs) {
359            this.builderRefs = builderRefs;
360        }
361    
362        /**
363         * If enabled this will force all {@link RouteBuilder} classes configured in the Spring
364         * {@link ApplicationContext} to be registered automatically with this CamelContext.
365         */
366        public void setAutowireRouteBuilders(Boolean autowireRouteBuilders) {
367            this.autowireRouteBuilders = autowireRouteBuilders;
368        }
369    
370        public String getErrorHandlerRef() {
371            return errorHandlerRef;
372        }
373    
374        /**
375         * Sets the name of the error handler object used to default the error handling strategy
376         *
377         * @param errorHandlerRef the Spring bean ref of the error handler
378         */
379        public void setErrorHandlerRef(String errorHandlerRef) {
380            this.errorHandlerRef = errorHandlerRef;
381        }
382    
383    
384        // Implementation methods
385        // -------------------------------------------------------------------------
386    
387        /**
388         * Create the context
389         */
390        protected SpringCamelContext createContext() {
391            SpringCamelContext ctx = new SpringCamelContext(getApplicationContext());
392            ctx.setName(getId());
393            if (trace != null) {
394                ctx.setTrace(trace);
395            }
396            if (errorHandlerRef != null) {
397                ErrorHandlerBuilder errorHandlerBuilder = (ErrorHandlerBuilder) getApplicationContext().getBean(errorHandlerRef, ErrorHandlerBuilder.class);
398                if (errorHandlerBuilder == null) {
399                    throw new IllegalArgumentException("Could not find bean: " + errorHandlerRef);
400                }
401                ctx.setErrorHandlerBuilder(errorHandlerBuilder);
402            }
403            return ctx;
404        }
405    
406        /**
407         * Strategy to install all available routes into the context
408         */
409        protected void installRoutes() throws Exception {
410            if (autowireRouteBuilders != null && autowireRouteBuilders.booleanValue()) {
411                Map builders = getApplicationContext().getBeansOfType(RouteBuilder.class, true, true);
412                if (builders != null) {
413                    for (Object builder : builders.values()) {
414                        getContext().addRoutes((RouteBuilder) builder);
415                    }
416                }
417            }
418            for (RouteBuilder routeBuilder : additionalBuilders) {
419                getContext().addRoutes(routeBuilder);
420            }
421            if (routeBuilder != null) {
422                getContext().addRoutes(routeBuilder);
423            }
424    
425            // lets add route builders added from references
426            if (builderRefs != null) {
427                for (RouteBuilderRef builderRef : builderRefs) {
428                    RouteBuilder builder = builderRef.createRouteBuilder(getContext());
429                    getContext().addRoutes(builder);
430                }
431            }
432        }
433    
434        /**
435         * Strategy method to try find {@link RouteBuilder} instances on the
436         * classpath
437         */
438        protected void findRouteBuiders() throws Exception, InstantiationException {
439            if (packages != null && packages.length > 0) {
440                RouteBuilderFinder finder = new RouteBuilderFinder(getContext(), packages, contextClassLoaderOnStart, getBeanPostProcessor());
441                finder.appendBuilders(additionalBuilders);
442            }
443        }
444    }