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 java.lang.reflect.Field;
020 import java.lang.reflect.Method;
021
022 import javax.xml.bind.annotation.XmlAccessType;
023 import javax.xml.bind.annotation.XmlAccessorType;
024 import javax.xml.bind.annotation.XmlRootElement;
025 import javax.xml.bind.annotation.XmlTransient;
026
027 import org.apache.camel.CamelContextAware;
028 import org.apache.camel.Consume;
029 import org.apache.camel.Consumer;
030 import org.apache.camel.Endpoint;
031 import org.apache.camel.EndpointInject;
032 import org.apache.camel.MessageDriven;
033 import org.apache.camel.PollingConsumer;
034 import org.apache.camel.Processor;
035 import org.apache.camel.Produce;
036 import org.apache.camel.Producer;
037 import org.apache.camel.RuntimeCamelException;
038 import org.apache.camel.Service;
039 import org.apache.camel.component.bean.BeanProcessor;
040 import org.apache.camel.component.bean.ProxyHelper;
041 import org.apache.camel.impl.DefaultProducerTemplate;
042 import org.apache.camel.spring.util.ReflectionUtils;
043 import org.apache.camel.util.ObjectHelper;
044 import org.apache.commons.logging.Log;
045 import org.apache.commons.logging.LogFactory;
046 import org.springframework.beans.BeanInstantiationException;
047 import org.springframework.beans.BeansException;
048 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
049 import org.springframework.beans.factory.config.BeanPostProcessor;
050 import org.springframework.context.ApplicationContext;
051 import org.springframework.context.ApplicationContextAware;
052
053 import static org.apache.camel.util.ObjectHelper.isNotNullAndNonEmpty;
054 import static org.apache.camel.util.ObjectHelper.isNullOrBlank;
055
056 /**
057 * A post processor to perform injection of {@link Endpoint} and
058 * {@link Producer} instances together with binding methods annotated with
059 * {@link MessageDriven @MessageDriven} to a Camel consumer.
060 *
061 * @version $Revision: 46142 $
062 */
063 @XmlRootElement(name = "beanPostProcessor")
064 @XmlAccessorType(XmlAccessType.FIELD)
065 public class CamelBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
066 private static final transient Log LOG = LogFactory.getLog(CamelBeanPostProcessor.class);
067 @XmlTransient
068 private SpringCamelContext camelContext;
069 @XmlTransient
070 private ApplicationContext applicationContext;
071
072 public CamelBeanPostProcessor() {
073 }
074
075 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
076 injectFields(bean);
077 injectMethods(bean);
078 if (bean instanceof CamelContextAware) {
079 CamelContextAware contextAware = (CamelContextAware)bean;
080 if (camelContext == null) {
081 LOG.warn("No CamelContext defined yet so cannot inject into: " + bean);
082 } else {
083 contextAware.setCamelContext(camelContext);
084 }
085 }
086 return bean;
087 }
088
089 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
090 return bean;
091 }
092
093 // Properties
094 // -------------------------------------------------------------------------
095
096 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
097 this.applicationContext = applicationContext;
098 }
099
100 public SpringCamelContext getCamelContext() {
101 return camelContext;
102 }
103
104 public void setCamelContext(SpringCamelContext camelContext) {
105 this.camelContext = camelContext;
106 }
107
108 // Implementation methods
109 // -------------------------------------------------------------------------
110
111 /**
112 * A strategy method to allow implementations to perform some custom JBI
113 * based injection of the POJO
114 *
115 * @param bean the bean to be injected
116 */
117 protected void injectFields(final Object bean) {
118 ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
119 public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
120 EndpointInject annotation = field.getAnnotation(EndpointInject.class);
121 if (annotation != null) {
122 injectField(field, annotation.uri(), annotation.name(), bean);
123 }
124 Produce produce = field.getAnnotation(Produce.class);
125 if (produce != null) {
126 injectField(field, produce.uri(), produce.ref(), bean);
127 }
128 }
129 });
130 }
131
132 protected void injectField(Field field, String endpointUri, String endpointRef, Object bean) {
133 ReflectionUtils.setField(field, bean, getInjectionValue(field.getType(), endpointUri, endpointRef, field.getName()));
134 }
135
136 protected void injectMethods(final Object bean) {
137 ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
138 @SuppressWarnings("unchecked")
139 public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
140 setterInjection(method, bean);
141 consumerInjection(method, bean);
142 }
143 });
144 }
145
146 protected void setterInjection(Method method, Object bean) {
147 EndpointInject annoation = method.getAnnotation(EndpointInject.class);
148 if (annoation != null) {
149 setterInjection(method, bean, annoation.uri(), annoation.name());
150 }
151 Produce produce = method.getAnnotation(Produce.class);
152 if (produce != null) {
153 setterInjection(method, bean, produce.uri(), produce.ref());
154 }
155 }
156
157 protected void setterInjection(Method method, Object bean, String endpointUri, String endpointRef) {
158 Class<?>[] parameterTypes = method.getParameterTypes();
159 if (parameterTypes != null) {
160 if (parameterTypes.length != 1) {
161 LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
162 } else {
163 String propertyName = ObjectHelper.getPropertyName(method);
164 Object value = getInjectionValue(parameterTypes[0], endpointUri, endpointRef, propertyName);
165 ObjectHelper.invokeMethod(method, bean, value);
166 }
167 }
168 }
169
170 protected void consumerInjection(final Object bean) {
171 ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
172 @SuppressWarnings("unchecked")
173 public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
174 /*
175 * TODO support callbacks? if
176 * (method.getAnnotation(Callback.class) != null) { try {
177 * Expression e = ExpressionFactory.createExpression(
178 * method.getAnnotation(Callback.class).condition());
179 * JexlContext jc = JexlHelper.createContext();
180 * jc.getVars().put("this", obj); Object r = e.evaluate(jc); if
181 * (!(r instanceof Boolean)) { throw new
182 * RuntimeException("Expression did not returned a boolean value
183 * but: " + r); } Boolean oldVal =
184 * req.getCallbacks().get(method); Boolean newVal = (Boolean) r;
185 * if ((oldVal == null || !oldVal) && newVal) {
186 * req.getCallbacks().put(method, newVal); method.invoke(obj,
187 * new Object[0]); // TODO: handle return value and sent it as
188 * the answer } } catch (Exception e) { throw new
189 * RuntimeException("Unable to invoke callback", e); } }
190 */
191 }
192 });
193 }
194
195 protected void consumerInjection(Method method, Object bean) {
196 MessageDriven annotation = method.getAnnotation(MessageDriven.class);
197 if (annotation != null) {
198 LOG.info("Creating a consumer for: " + annotation);
199 subscribeMethod(method, bean, annotation.uri(), annotation.name());
200 }
201
202 Consume consume = method.getAnnotation(Consume.class);
203 if (consume != null) {
204 LOG.info("Creating a consumer for: " + consume);
205 subscribeMethod(method, bean, consume.uri(), consume.ref());
206 }
207 }
208
209 protected void subscribeMethod(Method method, Object bean, String endpointUri, String endpointName) {
210 // lets bind this method to a listener
211 String injectionPointName = method.getName();
212 Endpoint endpoint = getEndpointInjection(endpointUri, endpointName, injectionPointName);
213 if (endpoint != null) {
214 try {
215 Processor processor = createConsumerProcessor(bean, method, endpoint);
216 LOG.info("Created processor: " + processor);
217 Consumer consumer = endpoint.createConsumer(processor);
218 startService(consumer);
219 } catch (Exception e) {
220 LOG.warn(e);
221 throw new RuntimeCamelException(e);
222 }
223 }
224 }
225
226 protected void startService(Service service) throws Exception {
227 camelContext.addService(service);
228 }
229
230 /**
231 * Create a processor which invokes the given method when an incoming
232 * message exchange is received
233 */
234 protected Processor createConsumerProcessor(final Object pojo, final Method method, final Endpoint endpoint) {
235 BeanProcessor answer = new BeanProcessor(pojo, getCamelContext());
236 answer.setMethodObject(method);
237 return answer;
238 }
239
240
241 /**
242 * Creates the object to be injected for an {@link org.apache.camel.EndpointInject} or {@link Produce} injection point
243 */
244 protected Object getInjectionValue(Class<?> type, String endpointUri, String endpointRef, String injectionPointName) {
245 Endpoint endpoint = getEndpointInjection(endpointUri, endpointRef, injectionPointName);
246 if (endpoint != null) {
247 if (type.isInstance(endpoint)) {
248 return endpoint;
249 } else if (type.isAssignableFrom(Producer.class)) {
250 return createInjectionProducer(endpoint);
251 } else if (type.isAssignableFrom(DefaultProducerTemplate.class)) {
252 return new DefaultProducerTemplate(getCamelContext(), endpoint);
253 } else if (type.isAssignableFrom(PollingConsumer.class)) {
254 return createInjectionPollingConsumer(endpoint);
255 } else if (type.isInterface()) {
256 // lets create a proxy
257 try {
258 return ProxyHelper.createProxy(endpoint, type);
259 } catch (Exception e) {
260 throw new BeanInstantiationException(type, "Could not instantiate proxy of type " + type.getName() + " on endpoint " + endpoint, e);
261 }
262 } else {
263 throw new IllegalArgumentException("Invalid type: " + type.getName() + " which cannot be injected via @EndpointInject for " + endpoint);
264 }
265 }
266 return null;
267 }
268
269 /**
270 * Factory method to create a started {@link PollingConsumer} to be injected
271 * into a POJO
272 */
273 protected PollingConsumer createInjectionPollingConsumer(Endpoint endpoint) {
274 try {
275 PollingConsumer pollingConsumer = endpoint.createPollingConsumer();
276 startService(pollingConsumer);
277 return pollingConsumer;
278 } catch (Exception e) {
279 throw new RuntimeCamelException(e);
280 }
281 }
282
283 /**
284 * A Factory method to create a started {@link Producer} to be injected into
285 * a POJO
286 */
287 protected Producer createInjectionProducer(Endpoint endpoint) {
288 try {
289 Producer producer = endpoint.createProducer();
290 startService(producer);
291 return producer;
292 } catch (Exception e) {
293 throw new RuntimeCamelException(e);
294 }
295 }
296
297 protected Endpoint getEndpointInjection(String uri, String name, String injectionPointName) {
298 Endpoint endpoint = null;
299 if (isNotNullAndNonEmpty(uri)) {
300 endpoint = camelContext.getEndpoint(uri);
301 } else {
302 if (isNullOrBlank(name)) {
303 name = injectionPointName;
304 }
305 endpoint = (Endpoint) applicationContext.getBean(name);
306 if (endpoint == null) {
307 throw new NoSuchBeanDefinitionException(name);
308 }
309 }
310 return endpoint;
311 }
312
313 }