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 */
017 package org.apache.camel.spring;
018
019 import org.apache.camel.Endpoint;
020 import org.apache.camel.component.bean.BeanProcessor;
021 import org.apache.camel.component.event.EventComponent;
022 import org.apache.camel.component.event.EventEndpoint;
023 import org.apache.camel.impl.DefaultCamelContext;
024 import org.apache.camel.impl.ProcessorEndpoint;
025 import org.apache.camel.spi.Injector;
026 import org.apache.camel.spi.ManagementMBeanAssembler;
027 import org.apache.camel.spi.Registry;
028 import org.apache.camel.spring.spi.ApplicationContextRegistry;
029 import org.apache.camel.spring.spi.SpringInjector;
030 import org.apache.camel.spring.spi.SpringManagementMBeanAssembler;
031 import org.slf4j.Logger;
032 import org.slf4j.LoggerFactory;
033 import org.springframework.beans.BeansException;
034 import org.springframework.beans.factory.DisposableBean;
035 import org.springframework.beans.factory.InitializingBean;
036 import org.springframework.context.ApplicationContext;
037 import org.springframework.context.ApplicationContextAware;
038 import org.springframework.context.ApplicationEvent;
039 import org.springframework.context.ConfigurableApplicationContext;
040 import org.springframework.context.event.ContextClosedEvent;
041 import org.springframework.context.event.ContextRefreshedEvent;
042 import org.springframework.context.event.ContextStoppedEvent;
043 import org.springframework.context.support.ClassPathXmlApplicationContext;
044
045 import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
046
047 /**
048 * A Spring aware implementation of {@link org.apache.camel.CamelContext} which
049 * will automatically register itself with Springs lifecycle methods plus allows
050 * spring to be used to customize a any <a
051 * href="http://camel.apache.org/type-converter.html">Type Converters</a>
052 * as well as supporting accessing components and beans via the Spring
053 * {@link ApplicationContext}
054 *
055 * @version
056 */
057 public class SpringCamelContext extends DefaultCamelContext implements InitializingBean, DisposableBean,
058 ApplicationContextAware {
059
060 private static final transient Logger LOG = LoggerFactory.getLogger(SpringCamelContext.class);
061 private static final ThreadLocal<Boolean> NO_START = new ThreadLocal<Boolean>();
062 private ApplicationContext applicationContext;
063 private EventEndpoint eventEndpoint;
064 private boolean shutdownEager;
065
066 public SpringCamelContext() {
067 }
068
069 public SpringCamelContext(ApplicationContext applicationContext) {
070 setApplicationContext(applicationContext);
071 }
072
073 public static void setNoStart(boolean b) {
074 if (b) {
075 NO_START.set(b);
076 } else {
077 NO_START.remove();
078 }
079 }
080
081 public static SpringCamelContext springCamelContext(ApplicationContext applicationContext) throws Exception {
082 return springCamelContext(applicationContext, true);
083 }
084
085 public static SpringCamelContext springCamelContext(ApplicationContext applicationContext, boolean maybeStart) throws Exception {
086 if (applicationContext != null) {
087 // lets try and look up a configured camel context in the context
088 String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class);
089 if (names.length == 1) {
090 return applicationContext.getBean(names[0], SpringCamelContext.class);
091 }
092 }
093 SpringCamelContext answer = new SpringCamelContext();
094 answer.setApplicationContext(applicationContext);
095 if (maybeStart) {
096 answer.afterPropertiesSet();
097 }
098 return answer;
099 }
100
101 public static SpringCamelContext springCamelContext(String configLocations) throws Exception {
102 return springCamelContext(new ClassPathXmlApplicationContext(configLocations));
103 }
104
105 public void afterPropertiesSet() throws Exception {
106 maybeStart();
107 }
108
109 public void destroy() throws Exception {
110 stop();
111 }
112
113 public void onApplicationEvent(ApplicationEvent event) {
114 LOG.debug("onApplicationEvent: {}", event);
115
116 if (event instanceof ContextRefreshedEvent) {
117 // now lets start the CamelContext so that all its possible
118 // dependencies are initialized
119 try {
120 maybeStart();
121 } catch (Exception e) {
122 throw wrapRuntimeCamelException(e);
123 }
124 } else if (event instanceof ContextClosedEvent) {
125 // ContextClosedEvent is emitted when Spring is about to be shutdown
126 if (isShutdownEager()) {
127 try {
128 maybeStop();
129 } catch (Exception e) {
130 throw wrapRuntimeCamelException(e);
131 }
132 }
133 } else if (event instanceof ContextStoppedEvent) {
134 // ContextStoppedEvent is emitted when Spring is end of shutdown
135 try {
136 maybeStop();
137 } catch (Exception e) {
138 throw wrapRuntimeCamelException(e);
139 }
140 }
141
142 if (eventEndpoint != null) {
143 eventEndpoint.onApplicationEvent(event);
144 } else {
145 LOG.info("No spring-event endpoint enabled to handle event: {}", event);
146 }
147 }
148
149 // Properties
150 // -----------------------------------------------------------------------
151
152 public ApplicationContext getApplicationContext() {
153 return applicationContext;
154 }
155
156 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
157 this.applicationContext = applicationContext;
158 ClassLoader cl;
159
160 // set the application context classloader
161 if (applicationContext != null && applicationContext.getClassLoader() != null) {
162 cl = applicationContext.getClassLoader();
163 } else {
164 LOG.warn("Cannot find the class loader from application context, using the thread context class loader instead");
165 cl = Thread.currentThread().getContextClassLoader();
166 }
167 LOG.debug("Set the application context classloader to: {}", cl);
168 this.setApplicationContextClassLoader(cl);
169
170 if (applicationContext instanceof ConfigurableApplicationContext) {
171 // only add if not already added
172 if (hasComponent("spring-event") == null) {
173 addComponent("spring-event", new EventComponent(applicationContext));
174 }
175 }
176 }
177
178 public EventEndpoint getEventEndpoint() {
179 return eventEndpoint;
180 }
181
182 public void setEventEndpoint(EventEndpoint eventEndpoint) {
183 this.eventEndpoint = eventEndpoint;
184 }
185
186 /**
187 * Whether to shutdown this {@link org.apache.camel.spring.SpringCamelContext} eager (first)
188 * when Spring {@link org.springframework.context.ApplicationContext} is being stopped.
189 * <p/>
190 * <b>Important:</b> This option is default <tt>false</tt> to be backwards compatible
191 * with current behavior. Setting this to <tt>true</tt> ensures we shutdown
192 * Camel before any beans, which is also the default behavior in Camel 2.13 onwards.
193 *
194 * @return <tt>true</tt> to shutdown eager (first), <tt>false</tt> to shutdown last
195 */
196 public boolean isShutdownEager() {
197 return shutdownEager;
198 }
199
200 /**
201 * @see #isShutdownEager()
202 */
203 public void setShutdownEager(boolean shutdownEager) {
204 this.shutdownEager = shutdownEager;
205 }
206
207 // Implementation methods
208 // -----------------------------------------------------------------------
209
210 @Override
211 protected void doStart() throws Exception {
212 super.doStart();
213 if (eventEndpoint == null) {
214 eventEndpoint = createEventEndpoint();
215 }
216 }
217
218 @Override
219 protected Injector createInjector() {
220 if (applicationContext instanceof ConfigurableApplicationContext) {
221 return new SpringInjector((ConfigurableApplicationContext)applicationContext);
222 } else {
223 LOG.warn("Cannot use SpringInjector as applicationContext is not a ConfigurableApplicationContext as its: "
224 + applicationContext);
225 return super.createInjector();
226 }
227 }
228
229 @Override
230 protected ManagementMBeanAssembler createManagementMBeanAssembler() {
231 // use a spring mbean assembler
232 return new SpringManagementMBeanAssembler();
233 }
234
235 protected EventEndpoint createEventEndpoint() {
236 return getEndpoint("spring-event:default", EventEndpoint.class);
237 }
238
239 protected Endpoint convertBeanToEndpoint(String uri, Object bean) {
240 // We will use the type convert to build the endpoint first
241 Endpoint endpoint = getTypeConverter().convertTo(Endpoint.class, bean);
242 if (endpoint != null) {
243 endpoint.setCamelContext(this);
244 return endpoint;
245 }
246
247 return new ProcessorEndpoint(uri, this, new BeanProcessor(bean, this));
248 }
249
250 @Override
251 protected Registry createRegistry() {
252 return new ApplicationContextRegistry(getApplicationContext());
253 }
254
255 private void maybeStart() throws Exception {
256 // for example from unit testing we want to start Camel later and not when Spring framework
257 // publish a ContextRefreshedEvent
258
259 if (NO_START.get() == null) {
260 if (!isStarted() && !isStarting()) {
261 start();
262 } else {
263 // ignore as Camel is already started
264 LOG.trace("Ignoring maybeStart() as Apache Camel is already started");
265 }
266 } else {
267 LOG.trace("Ignoring maybeStart() as NO_START is false");
268 }
269 }
270
271 private void maybeStop() throws Exception {
272 if (!isStopping() && !isStopped()) {
273 stop();
274 } else {
275 // ignore as Camel is already stopped
276 LOG.trace("Ignoring maybeStop() as Apache Camel is already stopped");
277 }
278 }
279
280 @Override
281 public String toString() {
282 StringBuilder sb = new StringBuilder();
283 sb.append("SpringCamelContext(").append(getName()).append(")");
284 if (applicationContext != null) {
285 sb.append(" with spring id ").append(applicationContext.getId());
286 }
287 return sb.toString();
288 }
289
290 }