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