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.ContextClosedEvent;
041import org.springframework.context.event.ContextRefreshedEvent;
042import org.springframework.context.event.ContextStoppedEvent;
043import org.springframework.context.support.ClassPathXmlApplicationContext;
044
045import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
046
047/**
048 * A Spring aware implementation of {@link org.apache.camel.CamelContext} which
049 * will automatically register itself with Springs lifecycle methods plus allows
050 * spring to be used to customize a any <a
051 * href="http://camel.apache.org/type-converter.html">Type Converters</a>
052 * as well as supporting accessing components and beans via the Spring
053 * {@link ApplicationContext}
054 *
055 * @version 
056 */
057public class SpringCamelContext extends DefaultCamelContext implements InitializingBean, DisposableBean,
058        ApplicationContextAware {
059
060    private static final Logger LOG = LoggerFactory.getLogger(SpringCamelContext.class);
061    private static final ThreadLocal<Boolean> NO_START = new ThreadLocal<Boolean>();
062    private ApplicationContext applicationContext;
063    private EventComponent eventComponent;
064    private boolean shutdownEager = true;
065
066    public SpringCamelContext() {
067    }
068
069    public SpringCamelContext(ApplicationContext applicationContext) {
070        setApplicationContext(applicationContext);
071        setModelJAXBContextFactory(new SpringModelJAXBContextFactory());
072    }
073
074    public static void setNoStart(boolean b) {
075        if (b) {
076            NO_START.set(b);
077        } else {
078            NO_START.remove();
079        }
080    }
081
082    /**
083     * @deprecated its better to create and boot Spring the standard Spring way and to get hold of CamelContext
084     * using the Spring API.
085     */
086    @Deprecated
087    public static SpringCamelContext springCamelContext(ApplicationContext applicationContext) throws Exception {
088        return springCamelContext(applicationContext, true);
089    }
090
091    /**
092     * @deprecated its better to create and boot Spring the standard Spring way and to get hold of CamelContext
093     * using the Spring API.
094     */
095    @Deprecated
096    public static SpringCamelContext springCamelContext(ApplicationContext applicationContext, boolean maybeStart) throws Exception {
097        if (applicationContext != null) {
098            // lets try and look up a configured camel context in the context
099            String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class);
100            if (names.length == 1) {
101                return applicationContext.getBean(names[0], SpringCamelContext.class);
102            }
103        }
104        SpringCamelContext answer = new SpringCamelContext();
105        answer.setApplicationContext(applicationContext);
106        if (maybeStart) {
107            answer.afterPropertiesSet();
108        }
109        return answer;
110    }
111
112    /**
113     * @deprecated its better to create and boot Spring the standard Spring way and to get hold of CamelContext
114     * using the Spring API.
115     */
116    @Deprecated
117    public static SpringCamelContext springCamelContext(String configLocations) throws Exception {
118        return springCamelContext(new ClassPathXmlApplicationContext(configLocations));
119    }
120
121    public void afterPropertiesSet() throws Exception {
122        maybeStart();
123    }
124
125    public void destroy() throws Exception {
126        stop();
127    }
128
129    public void onApplicationEvent(ApplicationEvent event) {
130        LOG.debug("onApplicationEvent: {}", event);
131
132        if (event instanceof ContextRefreshedEvent) {
133            // now lets start the CamelContext so that all its possible
134            // dependencies are initialized
135            try {
136                maybeStart();
137            } catch (Exception e) {
138                throw wrapRuntimeCamelException(e);
139            }
140        } else if (event instanceof ContextClosedEvent) {
141            // ContextClosedEvent is emitted when Spring is about to be shutdown
142            if (isShutdownEager()) {
143                try {
144                    maybeStop();
145                } catch (Exception e) {
146                    throw wrapRuntimeCamelException(e);
147                }
148            }
149        } else if (event instanceof ContextStoppedEvent) {
150            // ContextStoppedEvent is emitted when Spring is end of shutdown
151            try {
152                maybeStop();
153            } catch (Exception e) {
154                throw wrapRuntimeCamelException(e);
155            }
156        }
157
158        if (eventComponent != null) {
159            eventComponent.onApplicationEvent(event);
160        }
161    }
162
163    // Properties
164    // -----------------------------------------------------------------------
165
166    public ApplicationContext getApplicationContext() {
167        return applicationContext;
168    }
169
170    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
171        this.applicationContext = applicationContext;
172        ClassLoader cl;
173
174        // set the application context classloader
175        if (applicationContext != null && applicationContext.getClassLoader() != null) {
176            cl = applicationContext.getClassLoader();
177        } else {
178            LOG.warn("Cannot find the class loader from application context, using the thread context class loader instead");
179            cl = Thread.currentThread().getContextClassLoader();
180        }
181        LOG.debug("Set the application context classloader to: {}", cl);
182        this.setApplicationContextClassLoader(cl);
183
184        if (applicationContext instanceof ConfigurableApplicationContext) {
185            // only add if not already added
186            if (hasComponent("spring-event") == null) {
187                eventComponent = new EventComponent(applicationContext);
188                addComponent("spring-event", eventComponent);
189            }
190        }
191    }
192
193    @Deprecated
194    public EventEndpoint getEventEndpoint() {
195        return null;
196    }
197
198    @Deprecated
199    public void setEventEndpoint(EventEndpoint eventEndpoint) {
200        // noop
201    }
202
203    /**
204     * Whether to shutdown this {@link org.apache.camel.spring.SpringCamelContext} eager (first)
205     * when Spring {@link org.springframework.context.ApplicationContext} is being stopped.
206     * <p/>
207     * <b>Important:</b> This option is default <tt>true</tt> which ensures we shutdown Camel
208     * before other beans. Setting this to <tt>false</tt> restores old behavior in earlier
209     * Camel releases, which can be used for special cases to behave as before.
210     *
211     * @return <tt>true</tt> to shutdown eager (first), <tt>false</tt> to shutdown last
212     */
213    public boolean isShutdownEager() {
214        return shutdownEager;
215    }
216
217    /**
218     * @see #isShutdownEager()
219     */
220    public void setShutdownEager(boolean shutdownEager) {
221        this.shutdownEager = shutdownEager;
222    }
223
224    // Implementation methods
225    // -----------------------------------------------------------------------
226
227    @Override
228    protected Injector createInjector() {
229        if (applicationContext instanceof ConfigurableApplicationContext) {
230            return new SpringInjector((ConfigurableApplicationContext)applicationContext);
231        } else {
232            LOG.warn("Cannot use SpringInjector as applicationContext is not a ConfigurableApplicationContext as its: "
233                      + applicationContext);
234            return super.createInjector();
235        }
236    }
237
238    @Override
239    protected ManagementMBeanAssembler createManagementMBeanAssembler() {
240        // use a spring mbean assembler
241        return new SpringManagementMBeanAssembler(this);
242    }
243
244    protected EventEndpoint createEventEndpoint() {
245        return getEndpoint("spring-event:default", EventEndpoint.class);
246    }
247
248    protected Endpoint convertBeanToEndpoint(String uri, Object bean) {
249        // We will use the type convert to build the endpoint first
250        Endpoint endpoint = getTypeConverter().convertTo(Endpoint.class, bean);
251        if (endpoint != null) {
252            endpoint.setCamelContext(this);
253            return endpoint;
254        }
255
256        return new ProcessorEndpoint(uri, this, new BeanProcessor(bean, this));
257    }
258
259    @Override
260    protected Registry createRegistry() {
261        return new ApplicationContextRegistry(getApplicationContext());
262    }
263
264    private void maybeStart() throws Exception {
265        // for example from unit testing we want to start Camel later and not when Spring framework
266        // publish a ContextRefreshedEvent
267
268        if (NO_START.get() == null) {
269            if (!isStarted() && !isStarting()) {
270                start();
271            } else {
272                // ignore as Camel is already started
273                LOG.trace("Ignoring maybeStart() as Apache Camel is already started");
274            }
275        } else {
276            LOG.trace("Ignoring maybeStart() as NO_START is false");
277        }
278    }
279
280    private void maybeStop() throws Exception {
281        if (!isStopping() && !isStopped()) {
282            stop();
283        } else {
284            // ignore as Camel is already stopped
285            LOG.trace("Ignoring maybeStop() as Apache Camel is already stopped");
286        }
287    }
288
289    @Override
290    public String toString() {
291        StringBuilder sb = new StringBuilder();
292        sb.append("SpringCamelContext(").append(getName()).append(")");
293        if (applicationContext != null) {
294            sb.append(" with spring id ").append(applicationContext.getId());
295        }
296        return sb.toString();
297    }
298
299}