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