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