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 */
017package org.apache.camel.spring;
018
019import org.apache.camel.Endpoint;
020import org.apache.camel.component.bean.BeanProcessor;
021import org.apache.camel.component.event.EventComponent;
022import org.apache.camel.component.event.EventEndpoint;
023import org.apache.camel.impl.DefaultCamelContext;
024import org.apache.camel.impl.ProcessorEndpoint;
025import org.apache.camel.spi.Injector;
026import org.apache.camel.spi.ManagementMBeanAssembler;
027import org.apache.camel.spi.Registry;
028import org.apache.camel.spring.spi.ApplicationContextRegistry;
029import org.apache.camel.spring.spi.SpringInjector;
030import org.apache.camel.spring.spi.SpringManagementMBeanAssembler;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033import org.springframework.beans.BeansException;
034import org.springframework.beans.factory.DisposableBean;
035import org.springframework.beans.factory.InitializingBean;
036import org.springframework.context.ApplicationContext;
037import org.springframework.context.ApplicationContextAware;
038import org.springframework.context.ApplicationEvent;
039import org.springframework.context.ConfigurableApplicationContext;
040import org.springframework.context.event.ContextRefreshedEvent;
041import org.springframework.context.event.ContextStoppedEvent;
042import org.springframework.context.support.ClassPathXmlApplicationContext;
043
044import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
045
046/**
047 * A Spring aware implementation of {@link org.apache.camel.CamelContext} which
048 * will automatically register itself with Springs lifecycle methods plus allows
049 * spring to be used to customize a any <a
050 * href="http://camel.apache.org/type-converter.html">Type Converters</a>
051 * as well as supporting accessing components and beans via the Spring
052 * {@link ApplicationContext}
053 *
054 * @version 
055 */
056public class SpringCamelContext extends DefaultCamelContext implements InitializingBean, DisposableBean,
057        ApplicationContextAware {
058
059    private static final Logger LOG = LoggerFactory.getLogger(SpringCamelContext.class);
060    private static final ThreadLocal<Boolean> NO_START = new ThreadLocal<Boolean>();
061    private ApplicationContext applicationContext;
062    private EventComponent eventComponent;
063
064    public SpringCamelContext() {
065    }
066
067    public SpringCamelContext(ApplicationContext applicationContext) {
068        setApplicationContext(applicationContext);
069    }
070
071    public static void setNoStart(boolean b) {
072        if (b) {
073            NO_START.set(b);
074        } else {
075            NO_START.remove();
076        }
077    }
078    
079    public static SpringCamelContext springCamelContext(ApplicationContext applicationContext) throws Exception {
080        return springCamelContext(applicationContext, true);
081    }
082    
083    public static SpringCamelContext springCamelContext(ApplicationContext applicationContext, boolean maybeStart) throws Exception {
084        if (applicationContext != null) {
085            // lets try and look up a configured camel context in the context
086            String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class);
087            if (names.length == 1) {
088                return applicationContext.getBean(names[0], SpringCamelContext.class);
089            }
090        }
091        SpringCamelContext answer = new SpringCamelContext();
092        answer.setApplicationContext(applicationContext);
093        if (maybeStart) {
094            answer.afterPropertiesSet();
095        }
096        return answer;
097    }
098
099    public static SpringCamelContext springCamelContext(String configLocations) throws Exception {
100        return springCamelContext(new ClassPathXmlApplicationContext(configLocations));
101    }
102
103    public void afterPropertiesSet() throws Exception {
104        maybeStart();
105    }
106
107    public void destroy() throws Exception {
108        stop();
109    }
110
111    public void onApplicationEvent(ApplicationEvent event) {
112        LOG.debug("onApplicationEvent: {}", event);
113
114        if (event instanceof ContextRefreshedEvent) {
115            // now lets start the CamelContext so that all its possible
116            // dependencies are initialized
117            try {
118                maybeStart();
119            } catch (Exception e) {
120                throw wrapRuntimeCamelException(e);
121            }
122        } else if (event instanceof ContextStoppedEvent) {
123            try {
124                maybeStop();
125            } catch (Exception e) {
126                throw wrapRuntimeCamelException(e);
127            }
128        }
129
130        if (eventComponent != null) {
131            eventComponent.onApplicationEvent(event);
132        }
133    }
134
135    // Properties
136    // -----------------------------------------------------------------------
137
138    public ApplicationContext getApplicationContext() {
139        return applicationContext;
140    }
141
142    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
143        this.applicationContext = applicationContext;
144        ClassLoader cl;
145
146        // set the application context classloader
147        if (applicationContext != null && applicationContext.getClassLoader() != null) {
148            cl = applicationContext.getClassLoader();
149        } else {
150            LOG.warn("Cannot find the class loader from application context, using the thread context class loader instead");
151            cl = Thread.currentThread().getContextClassLoader();
152        }
153        LOG.debug("Set the application context classloader to: {}", cl);
154        this.setApplicationContextClassLoader(cl);
155
156        if (applicationContext instanceof ConfigurableApplicationContext) {
157            // only add if not already added
158            if (hasComponent("spring-event") == null) {
159                eventComponent = new EventComponent(applicationContext);
160                addComponent("spring-event", eventComponent);
161            }
162        }
163    }
164
165    @Deprecated
166    public EventEndpoint getEventEndpoint() {
167        return null;
168    }
169
170    @Deprecated
171    public void setEventEndpoint(EventEndpoint eventEndpoint) {
172        // noop
173    }
174
175    // Implementation methods
176    // -----------------------------------------------------------------------
177
178    @Override
179    protected Injector createInjector() {
180        if (applicationContext instanceof ConfigurableApplicationContext) {
181            return new SpringInjector((ConfigurableApplicationContext)applicationContext);
182        } else {
183            LOG.warn("Cannot use SpringInjector as applicationContext is not a ConfigurableApplicationContext as its: "
184                      + applicationContext);
185            return super.createInjector();
186        }
187    }
188
189    @Override
190    protected ManagementMBeanAssembler createManagementMBeanAssembler() {
191        // use a spring mbean assembler
192        return new SpringManagementMBeanAssembler(this);
193    }
194
195    protected EventEndpoint createEventEndpoint() {
196        return getEndpoint("spring-event:default", EventEndpoint.class);
197    }
198
199    protected Endpoint convertBeanToEndpoint(String uri, Object bean) {
200        // We will use the type convert to build the endpoint first
201        Endpoint endpoint = getTypeConverter().convertTo(Endpoint.class, bean);
202        if (endpoint != null) {
203            endpoint.setCamelContext(this);
204            return endpoint;
205        }
206
207        return new ProcessorEndpoint(uri, this, new BeanProcessor(bean, this));
208    }
209
210    @Override
211    protected Registry createRegistry() {
212        return new ApplicationContextRegistry(getApplicationContext());
213    }
214
215    private void maybeStart() throws Exception {
216        // for example from unit testing we want to start Camel later and not when Spring framework
217        // publish a ContextRefreshedEvent
218
219        if (NO_START.get() == null) {
220            if (!isStarted() && !isStarting()) {
221                start();
222            } else {
223                // ignore as Camel is already started
224                LOG.trace("Ignoring maybeStart() as Apache Camel is already started");
225            }
226        } else {
227            LOG.trace("Ignoring maybeStart() as NO_START is false");
228        }
229    }
230
231    private void maybeStop() throws Exception {
232        if (!isStopping() && !isStopped()) {
233            stop();
234        } else {
235            // ignore as Camel is already stopped
236            LOG.trace("Ignoring maybeStop() as Apache Camel is already stopped");
237        }
238    }
239
240    @Override
241    public String toString() {
242        StringBuilder sb = new StringBuilder();
243        sb.append("SpringCamelContext(").append(getName()).append(")");
244        if (applicationContext != null) {
245            sb.append(" with spring id ").append(applicationContext.getId());
246        }
247        return sb.toString();
248    }
249
250}