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.management.MBeanServer;
024    import javax.xml.bind.annotation.XmlAccessType;
025    import javax.xml.bind.annotation.XmlAccessorType;
026    import javax.xml.bind.annotation.XmlAttribute;
027    import javax.xml.bind.annotation.XmlElement;
028    import javax.xml.bind.annotation.XmlElementRef;
029    import javax.xml.bind.annotation.XmlElements;
030    import javax.xml.bind.annotation.XmlRootElement;
031    import javax.xml.bind.annotation.XmlTransient;
032    
033    import org.apache.camel.RuntimeCamelException;
034    import org.apache.camel.builder.RouteBuilder;
035    import org.apache.camel.model.IdentifiedType;
036    import org.apache.camel.model.RouteBuilderRef;
037    import org.apache.camel.model.RouteContainer;
038    import org.apache.camel.model.RouteType;
039    import org.apache.camel.model.dataformat.DataFormatType;
040    import org.apache.camel.processor.interceptor.Debugger;
041    import org.apache.camel.spi.InstrumentationAgent;
042    import org.apache.camel.spi.LifecycleStrategy;
043    import org.apache.camel.spi.Registry;
044    import org.apache.commons.logging.Log;
045    import org.apache.commons.logging.LogFactory;
046    import org.springframework.beans.factory.DisposableBean;
047    import org.springframework.beans.factory.FactoryBean;
048    import org.springframework.beans.factory.InitializingBean;
049    import org.springframework.beans.factory.config.BeanPostProcessor;
050    import org.springframework.context.ApplicationContext;
051    import org.springframework.context.ApplicationContextAware;
052    import org.springframework.context.ApplicationEvent;
053    import org.springframework.context.ApplicationListener;
054    import org.springframework.context.event.ContextRefreshedEvent;
055    
056    /**
057     * A Spring {@link FactoryBean} to create and initialize a
058     * {@link SpringCamelContext} and install routes either explicitly configured in
059     * Spring XML or found by searching the classpath for Java classes which extend
060     * {@link RouteBuilder} using the nested {@link #setPackages(String[])}.
061     *
062     * @version $Revision: 42292 $
063     */
064    @XmlRootElement(name = "camelContext")
065    @XmlAccessorType(XmlAccessType.FIELD)
066    public class CamelContextFactoryBean extends IdentifiedType implements RouteContainer, FactoryBean, InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener {
067        private static final Log LOG = LogFactory.getLog(CamelContextFactoryBean.class);
068        @XmlAttribute(required = false)
069        private Boolean useJmx;
070        @XmlAttribute(required = false)
071        private String mbeanServer;
072        @XmlAttribute(required = false)
073        private Boolean autowireRouteBuilders = Boolean.TRUE;
074        @XmlElement(name = "package", required = false)
075        private String[] packages = {};
076        @XmlElement(name = "jmxAgent", type = CamelJMXAgentType.class, required = false)
077        private CamelJMXAgentType camelJMXAgent;
078        @XmlElements({
079            @XmlElement(name = "beanPostProcessor", type = CamelBeanPostProcessor.class, required = false),
080            @XmlElement(name = "template", type = CamelTemplateFactoryBean.class, required = false),
081            @XmlElement(name = "proxy", type = CamelProxyFactoryType.class, required = false),
082            @XmlElement(name = "export", type = CamelServiceExporterType.class, required = false)})
083        private List beans;
084        @XmlElement(name = "routeBuilderRef", required = false)
085        private List<RouteBuilderRef> builderRefs = new ArrayList<RouteBuilderRef>();
086        @XmlElement(name = "endpoint", required = false)
087        private List<EndpointFactoryBean> endpoints;
088        @XmlElementRef
089        private List<DataFormatType> dataFormats;
090        @XmlElement(name = "route", required = false)
091        private List<RouteType> routes = new ArrayList<RouteType>();
092        @XmlTransient
093        private SpringCamelContext context;
094        @XmlTransient
095        private RouteBuilder routeBuilder;
096        @XmlTransient
097        private List<RouteBuilder> additionalBuilders = new ArrayList<RouteBuilder>();
098        @XmlTransient
099        private ApplicationContext applicationContext;
100        @XmlTransient
101        private ClassLoader contextClassLoaderOnStart;
102        @XmlTransient
103        private InstrumentationAgent instrumentationAgent;
104        @XmlTransient
105        private BeanPostProcessor beanPostProcessor;
106    
107        public CamelContextFactoryBean() {
108            // Lets keep track of the class loader for when we actually do start things up
109            contextClassLoaderOnStart = Thread.currentThread().getContextClassLoader();
110        }
111    
112        public Object getObject() throws Exception {
113            return getContext();
114        }
115    
116        public Class getObjectType() {
117            return SpringCamelContext.class;
118        }
119    
120        public boolean isSingleton() {
121            return true;
122        }
123    
124        public void afterPropertiesSet() throws Exception {
125            // lets see if we can find a debugger to add
126            // TODO there should be a neater way to do this!
127            Debugger debugger = getBeanForType(Debugger.class);       
128            if (debugger != null) {
129                getContext().addInterceptStrategy(debugger);
130            }
131    
132            // set the lifecycle strategy if defined
133            LifecycleStrategy lifecycleStrategy = getBeanForType(LifecycleStrategy.class);
134            if (lifecycleStrategy != null) {
135                getContext().setLifecycleStrategy(lifecycleStrategy);
136            }
137    
138            // set the strategy if defined
139            Registry registry = getBeanForType(Registry.class);
140            if (registry != null) {
141                getContext().setRegistry(registry);
142            }
143    
144            // Set the application context and camelContext for the beanPostProcessor
145            if (beanPostProcessor != null) {
146                if (beanPostProcessor instanceof ApplicationContextAware) {
147                    ((ApplicationContextAware)beanPostProcessor).setApplicationContext(applicationContext);
148                }
149                if (beanPostProcessor instanceof CamelBeanPostProcessor) {
150                    ((CamelBeanPostProcessor)beanPostProcessor).setCamelContext(getContext());
151                }
152            }
153    
154            // lets force any lazy creation
155            getContext().addRouteDefinitions(routes);
156    
157            // set the instrumentation agent here        
158            if (instrumentationAgent == null && isJmxEnabled()) {            
159                if (lifecycleStrategy != null) {
160                    LOG.warn("lifecycleStrategy will be overriden by InstrumentationLifecycleStrategy");
161                }
162                
163                SpringInstrumentationAgent agent = new SpringInstrumentationAgent();
164                if (camelJMXAgent != null) {
165                    agent.enableJmx(camelJMXAgent.getJmxDomainName(), camelJMXAgent.getConnectorPath(), camelJMXAgent.getConnectorPort());
166                    if (camelJMXAgent.isCreateConnector() != null) {
167                        agent.setCreateConnector(camelJMXAgent.isCreateConnector());
168                    }
169                    if (camelJMXAgent.isUsePlatformMBeanServer() != null) {
170                        agent.setUsePlatformMBeanServer(camelJMXAgent.isUsePlatformMBeanServer());
171                    }
172                } else {
173                    agent.enableJmx();
174                }
175                agent.setCamelContext(getContext());
176                String name = getMbeanServer();
177                if (name != null) {
178                    MBeanServer mbeanServer = (MBeanServer) getApplicationContext().getBean(name, MBeanServer.class);
179                    agent.setMBeanServer(mbeanServer);
180                }
181                instrumentationAgent = agent;
182                instrumentationAgent.start();
183            } 
184            
185            if (LOG.isDebugEnabled()) {
186                LOG.debug("Found JAXB created routes: " + getRoutes());
187            }
188    
189            findRouteBuiders();
190            installRoutes();
191        }
192    
193        private <T> T getBeanForType(Class<T> clazz) {
194            T bean = null;
195            String[] names = getApplicationContext().getBeanNamesForType(clazz, true, true);
196            if (names.length == 1) {
197                bean = (T) getApplicationContext().getBean(names[0], clazz);
198            }
199            if (bean == null) {
200                ApplicationContext parentContext = getApplicationContext().getParent();
201                if (parentContext != null) {
202                    names = parentContext.getBeanNamesForType(clazz, true, true);
203                    if (names.length == 1) {
204                        bean = (T) parentContext.getBean(names[0], clazz);
205                    }
206                }
207            }
208            return bean;
209    
210        }
211    
212        public void destroy() throws Exception {
213            getContext().stop();
214        }
215    
216        public void onApplicationEvent(ApplicationEvent event) {
217            if (LOG.isDebugEnabled()) {
218                LOG.debug("Publishing event: " + event);
219            }
220    
221            if (event instanceof ContextRefreshedEvent) {
222                // now lets start the CamelContext so that all its possible
223                // dependencies are initailized
224                try {
225                    LOG.debug("Starting the context now!");
226                    getContext().start();
227                } catch (Exception e) {
228                    throw new RuntimeCamelException(e);
229                }
230            }
231            /*
232             * if (context != null) { context.onApplicationEvent(event); }
233             */
234        }
235    
236        // Properties
237        // -------------------------------------------------------------------------
238        public SpringCamelContext getContext() throws Exception {
239            if (context == null) {
240                context = createContext();
241            }
242            return context;
243        }
244    
245        public void setContext(SpringCamelContext context) {
246            this.context = context;
247        }
248    
249        public List<RouteType> getRoutes() {
250            return routes;
251        }
252    
253        public void setRoutes(List<RouteType> routes) {
254            this.routes = routes;
255        }
256    
257        public RouteBuilder getRouteBuilder() {
258            return routeBuilder;
259        }
260    
261        /**
262         * Set a single {@link RouteBuilder} to be used to create the default routes
263         * on startup
264         */
265        public void setRouteBuilder(RouteBuilder routeBuilder) {
266            this.routeBuilder = routeBuilder;
267        }
268    
269        /**
270         * Set a collection of {@link RouteBuilder} instances to be used to create
271         * the default routes on startup
272         */
273        public void setRouteBuilders(RouteBuilder[] builders) {
274            for (RouteBuilder builder : builders) {
275                additionalBuilders.add(builder);
276            }
277        }
278    
279        public ApplicationContext getApplicationContext() {
280            if (applicationContext == null) {
281                throw new IllegalArgumentException("No applicationContext has been injected!");
282            }
283            return applicationContext;
284        }
285    
286        public void setApplicationContext(ApplicationContext applicationContext) {
287            this.applicationContext = applicationContext;
288        }
289    
290        public String[] getPackages() {
291            return packages;
292        }
293    
294        /**
295         * Sets the package names to be recursively searched for Java classes which
296         * extend {@link RouteBuilder} to be auto-wired up to the
297         * {@link SpringCamelContext} as a route. Note that classes are excluded if
298         * they are specifically configured in the spring.xml
299         *
300         * @param packages the package names which are recursively searched
301         */
302        public void setPackages(String[] packages) {
303            this.packages = packages;
304        }
305    
306        public String getMbeanServer() {
307            return mbeanServer;
308        }
309    
310        public void setMbeanServer(String mbeanServer) {
311            this.mbeanServer = mbeanServer;
312        }
313    
314        public void setBeanPostProcessor(BeanPostProcessor postProcessor) {
315            this.beanPostProcessor = postProcessor;
316        }
317    
318        public BeanPostProcessor getBeanPostProcessor() {
319            return beanPostProcessor;
320        }
321    
322        public boolean isJmxEnabled() {
323            return useJmx != null && useJmx.booleanValue();
324        }
325    
326        public Boolean getUseJmx() {
327            return useJmx;
328        }
329    
330        public void setUseJmx(Boolean useJmx) {
331            this.useJmx = useJmx;
332        }
333    
334        public void setCamelJMXAgent(CamelJMXAgentType agent) {
335            camelJMXAgent = agent;
336        }
337    
338        public CamelJMXAgentType getCamelJMXAgent() {
339            return camelJMXAgent;
340        }
341    
342        public List<RouteBuilderRef> getBuilderRefs() {
343            return builderRefs;
344        }
345    
346        public void setBuilderRefs(List<RouteBuilderRef> builderRefs) {
347            this.builderRefs = builderRefs;
348        }
349    
350        /**
351         * If enabled this will force all {@link RouteBuilder} classes configured in the Spring
352         * {@link ApplicationContext} to be registered automatically with this CamelContext.
353         */
354        public void setAutowireRouteBuilders(Boolean autowireRouteBuilders) {
355            this.autowireRouteBuilders = autowireRouteBuilders;
356        }
357    
358        // Implementation methods
359        // -------------------------------------------------------------------------
360    
361        /**
362         * Create the context
363         */
364        protected SpringCamelContext createContext() {
365            SpringCamelContext ctx = new SpringCamelContext(getApplicationContext());
366            ctx.setName(getId());
367            return ctx;
368        }
369    
370        /**
371         * Strategy to install all available routes into the context
372         */
373        protected void installRoutes() throws Exception {
374            if (autowireRouteBuilders != null && autowireRouteBuilders.booleanValue()) {
375                Map builders = getApplicationContext().getBeansOfType(RouteBuilder.class, true, true);
376                if (builders != null) {
377                    for (Object builder : builders.values()) {
378                        getContext().addRoutes((RouteBuilder) builder);
379                    }
380                }
381            }
382            for (RouteBuilder routeBuilder : additionalBuilders) {
383                getContext().addRoutes(routeBuilder);
384            }
385            if (routeBuilder != null) {
386                getContext().addRoutes(routeBuilder);
387            }
388    
389            // lets add route builders added from references
390            if (builderRefs != null) {
391                for (RouteBuilderRef builderRef : builderRefs) {
392                    RouteBuilder builder = builderRef.createRouteBuilder(getContext());
393                    getContext().addRoutes(builder);
394                }
395            }
396        }
397    
398        /**
399         * Strategy method to try find {@link RouteBuilder} instances on the
400         * classpath
401         */
402        protected void findRouteBuiders() throws Exception, InstantiationException {
403            if (packages != null && packages.length > 0) {
404                RouteBuilderFinder finder = new RouteBuilderFinder(getContext(), packages, contextClassLoaderOnStart, getBeanPostProcessor());
405                finder.appendBuilders(additionalBuilders);
406            }
407        }
408    }