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.ModelJAXBContextFactory;
028import org.apache.camel.spi.Registry;
029import org.apache.camel.spring.spi.ApplicationContextRegistry;
030import org.apache.camel.spring.spi.SpringInjector;
031import org.apache.camel.spring.spi.SpringManagementMBeanAssembler;
032import org.apache.camel.util.StopWatch;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035import org.springframework.beans.BeansException;
036import org.springframework.context.ApplicationContext;
037import org.springframework.context.ApplicationContextAware;
038import org.springframework.context.ApplicationEvent;
039import org.springframework.context.ApplicationListener;
040import org.springframework.context.ConfigurableApplicationContext;
041import org.springframework.context.Lifecycle;
042import org.springframework.context.Phased;
043import org.springframework.context.event.ContextRefreshedEvent;
044import org.springframework.context.support.ClassPathXmlApplicationContext;
045import org.springframework.core.Ordered;
046
047import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
048
049/**
050 * A Spring aware implementation of {@link org.apache.camel.CamelContext} which
051 * will automatically register itself with Springs lifecycle methods plus allows
052 * spring to be used to customize a any <a
053 * href="http://camel.apache.org/type-converter.html">Type Converters</a>
054 * as well as supporting accessing components and beans via the Spring
055 * {@link ApplicationContext}
056 *
057 * @version 
058 */
059public class SpringCamelContext extends DefaultCamelContext implements Lifecycle, ApplicationContextAware, Phased,
060        ApplicationListener<ApplicationEvent>, Ordered {
061
062    private static final Logger LOG = LoggerFactory.getLogger(SpringCamelContext.class);
063    private static final ThreadLocal<Boolean> NO_START = new ThreadLocal<>();
064    private ApplicationContext applicationContext;
065    private EventComponent eventComponent;
066    private boolean shutdownEager = true;
067
068    public SpringCamelContext() {
069    }
070
071    public SpringCamelContext(ApplicationContext applicationContext) {
072        setApplicationContext(applicationContext);
073    }
074
075    public static void setNoStart(boolean b) {
076        if (b) {
077            NO_START.set(true);
078        } else {
079            NO_START.set(null);
080        }
081    }
082
083    /**
084     * @deprecated its better to create and boot Spring the standard Spring way and to get hold of CamelContext
085     * using the Spring API.
086     */
087    @Deprecated
088    public static SpringCamelContext springCamelContext(ApplicationContext applicationContext) throws Exception {
089        return springCamelContext(applicationContext, true);
090    }
091
092    /**
093     * @deprecated its better to create and boot Spring the standard Spring way and to get hold of CamelContext
094     * using the Spring API.
095     */
096    @Deprecated
097    public static SpringCamelContext springCamelContext(ApplicationContext applicationContext, boolean maybeStart) throws Exception {
098        if (applicationContext != null) {
099            // lets try and look up a configured camel context in the context
100            String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class);
101            if (names.length == 1) {
102                return applicationContext.getBean(names[0], SpringCamelContext.class);
103            }
104        }
105        SpringCamelContext answer = new SpringCamelContext();
106        answer.setApplicationContext(applicationContext);
107        if (maybeStart) {
108            answer.start();
109        }
110        return answer;
111    }
112
113    /**
114     * @deprecated its better to create and boot Spring the standard Spring way and to get hold of CamelContext
115     * using the Spring API.
116     */
117    @Deprecated
118    public static SpringCamelContext springCamelContext(String configLocations) throws Exception {
119        return springCamelContext(new ClassPathXmlApplicationContext(configLocations));
120    }
121
122    @Override
123    public void start() {
124        // for example from unit testing we want to start Camel later (manually)
125        if (Boolean.TRUE.equals(NO_START.get())) {
126            LOG.trace("Ignoring start() as NO_START is false");
127            return;
128        }
129
130        if (!isStarted() && !isStarting()) {
131            try {
132                StopWatch watch = new StopWatch();
133                super.start();
134                LOG.debug("start() took {} millis", watch.stop());
135            } catch (Exception e) {
136                throw wrapRuntimeCamelException(e);
137            }
138        } else {
139            // ignore as Camel is already started
140            LOG.trace("Ignoring start() as Camel is already started");
141        }
142    }
143
144    @Override
145    public void stop() {
146        if (!isStopping() && !isStopped()) {
147            try {
148                super.stop();
149            } catch (Exception e) {
150                throw wrapRuntimeCamelException(e);
151            }
152        } else {
153            // ignore as Camel is already stopped
154            LOG.trace("Ignoring stop() as Camel is already stopped");
155        }
156    }
157
158    @Override
159    public void onApplicationEvent(ApplicationEvent event) {
160        LOG.debug("onApplicationEvent: {}", event);
161
162        if (event instanceof ContextRefreshedEvent && ((ContextRefreshedEvent) event).getApplicationContext() == this.applicationContext) {
163            // nominally we would prefer to use Lifecycle interface that
164            // would invoke start() method, but in order to do that 
165            // SpringCamelContext needs to implement SmartLifecycle
166            // (look at DefaultLifecycleProcessor::startBeans), but it
167            // cannot implement it as it already implements
168            // RuntimeConfiguration, and both SmartLifecycle and
169            // RuntimeConfiguration declare isAutoStartup method but
170            // with boolean and Boolean return types, and covariant
171            // methods with primitive types are not allowed by the JLS
172            // so we need to listen for ContextRefreshedEvent and start
173            // on its reception
174            start();
175        }
176
177        if (eventComponent != null) {
178            eventComponent.onApplicationEvent(event);
179        }
180    }
181
182    @Override
183    public int getOrder() {
184        // SpringCamelContext implements Ordered so that it's the last
185        // in ApplicationListener to receive events, this is important
186        // for startup as we want all resources to be ready and all
187        // routes added to the context (see
188        // org.apache.camel.spring.boot.RoutesCollector)
189        // and we need to be after CamelContextFactoryBean
190        return LOWEST_PRECEDENCE;
191    }
192
193    // Properties
194    // -----------------------------------------------------------------------
195
196    public ApplicationContext getApplicationContext() {
197        return applicationContext;
198    }
199
200    @Override
201    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
202        this.applicationContext = applicationContext;
203        ClassLoader cl;
204
205        // set the application context classloader
206        if (applicationContext != null && applicationContext.getClassLoader() != null) {
207            cl = applicationContext.getClassLoader();
208        } else {
209            LOG.warn("Cannot find the class loader from application context, using the thread context class loader instead");
210            cl = Thread.currentThread().getContextClassLoader();
211        }
212        LOG.debug("Set the application context classloader to: {}", cl);
213        this.setApplicationContextClassLoader(cl);
214
215        if (applicationContext instanceof ConfigurableApplicationContext) {
216            // only add if not already added
217            if (hasComponent("spring-event") == null) {
218                eventComponent = new EventComponent(applicationContext);
219                addComponent("spring-event", eventComponent);
220            }
221        }
222    }
223
224    @Deprecated
225    public EventEndpoint getEventEndpoint() {
226        return null;
227    }
228
229    @Deprecated
230    public void setEventEndpoint(EventEndpoint eventEndpoint) {
231        // noop
232    }
233
234    /**
235     * Whether to shutdown this {@link org.apache.camel.spring.SpringCamelContext} eager (first)
236     * when Spring {@link org.springframework.context.ApplicationContext} is being stopped.
237     * <p/>
238     * <b>Important:</b> This option is default <tt>true</tt> which ensures we shutdown Camel
239     * before other beans. Setting this to <tt>false</tt> restores old behavior in earlier
240     * Camel releases, which can be used for special cases to behave as before.
241     *
242     * @return <tt>true</tt> to shutdown eager (first), <tt>false</tt> to shutdown last
243     */
244    public boolean isShutdownEager() {
245        return shutdownEager;
246    }
247
248    /**
249     * @see #isShutdownEager()
250     */
251    public void setShutdownEager(boolean shutdownEager) {
252        this.shutdownEager = shutdownEager;
253    }
254
255    // Implementation methods
256    // -----------------------------------------------------------------------
257
258    @Override
259    protected Injector createInjector() {
260        if (applicationContext instanceof ConfigurableApplicationContext) {
261            return new SpringInjector((ConfigurableApplicationContext)applicationContext);
262        } else {
263            LOG.warn("Cannot use SpringInjector as applicationContext is not a ConfigurableApplicationContext as its: "
264                      + applicationContext);
265            return super.createInjector();
266        }
267    }
268
269    @Override
270    protected ManagementMBeanAssembler createManagementMBeanAssembler() {
271        // use a spring mbean assembler
272        return new SpringManagementMBeanAssembler(this);
273    }
274
275    protected EventEndpoint createEventEndpoint() {
276        return getEndpoint("spring-event:default", EventEndpoint.class);
277    }
278
279    @Override
280    protected Endpoint convertBeanToEndpoint(String uri, Object bean) {
281        // We will use the type convert to build the endpoint first
282        Endpoint endpoint = getTypeConverter().convertTo(Endpoint.class, bean);
283        if (endpoint != null) {
284            endpoint.setCamelContext(this);
285            return endpoint;
286        }
287
288        return new ProcessorEndpoint(uri, this, new BeanProcessor(bean, this));
289    }
290
291    @Override
292    protected Registry createRegistry() {
293        return new ApplicationContextRegistry(getApplicationContext());
294    }
295
296    @Override
297    protected ModelJAXBContextFactory createModelJAXBContextFactory() {
298        return new SpringModelJAXBContextFactory();
299    }
300
301    @Override
302    public String toString() {
303        StringBuilder sb = new StringBuilder();
304        sb.append("SpringCamelContext(").append(getName()).append(")");
305        if (applicationContext != null) {
306            sb.append(" with spring id ").append(applicationContext.getId());
307        }
308        return sb.toString();
309    }
310
311    @Override
312    public int getPhase() {
313        // the context is started by invoking start method which
314        // happens either on ContextRefreshedEvent or explicitly
315        // invoking the method, for instance CamelContextFactoryBean
316        // is using that to start the context, _so_ here we want to
317        // have maximum priority as the getPhase() will be used only
318        // for stopping, in order to be used for starting we would
319        // need to implement SmartLifecycle which we cannot
320        // (explained in comment in the onApplicationEvent method)
321        // we use LOWEST_PRECEDENCE here as this is taken into account
322        // only when stopping and then in reversed order
323        return LOWEST_PRECEDENCE;
324    }
325
326    @Override
327    public boolean isRunning() {
328        return !isStopping() && !isStopped();
329    }
330
331}