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