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