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