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