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