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    }