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