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