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