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.CamelContext;
028    import org.apache.camel.CamelContextAware;
029    import org.apache.camel.Endpoint;
030    import org.apache.camel.EndpointInject;
031    import org.apache.camel.Produce;
032    import org.apache.camel.impl.CamelPostProcessorHelper;
033    import org.apache.camel.impl.DefaultEndpoint;
034    import org.apache.camel.spring.util.ReflectionUtils;
035    import org.apache.camel.util.ObjectHelper;
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    import org.springframework.beans.BeanInstantiationException;
039    import org.springframework.beans.BeansException;
040    import org.springframework.beans.factory.config.BeanPostProcessor;
041    import org.springframework.context.ApplicationContext;
042    import org.springframework.context.ApplicationContextAware;
043    
044    /**
045     * A bean post processor which implements the <a href="http://activemq.apache.org/camel/bean-integration.html">Bean Integration</a>
046     * features in Camel such as the <a href="http://activemq.apache.org/camel/bean-injection.html">Bean Injection</a> of objects like
047     * {@link Endpoint} and
048     * {@link org.apache.camel.ProducerTemplate} together with support for
049     * <a href="http://activemq.apache.org/camel/pojo-consuming.html">POJO Consuming</a> via the 
050     * {@link org.apache.camel.Consume} and {@link org.apache.camel.MessageDriven} annotations along with
051     * <a href="http://activemq.apache.org/camel/pojo-producing.html">POJO Producing</a> via the
052     * {@link org.apache.camel.Produce} annotation along with other annotations such as
053     * {@link org.apache.camel.RecipientList} for creating <a href="http://activemq.apache.org/camel/recipientlist-annotation.html">a Recipient List router via annotations</a>.
054     * <p>
055     * If you use the &lt;camelContext&gt; element in your <a href="http://activemq.apache.org/camel/spring.html">Spring XML</a> 
056     * then one of these bean post processors is implicitly installed and configured for you. So you should never have to
057     * explicitly create or configure one of these instances.
058     *
059     * @version $Revision: 800394 $
060     */
061    @XmlRootElement(name = "beanPostProcessor")
062    @XmlAccessorType(XmlAccessType.FIELD)
063    public class CamelBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
064        private static final transient Log LOG = LogFactory.getLog(CamelBeanPostProcessor.class);
065        @XmlTransient
066        private SpringCamelContext camelContext;
067        @XmlTransient
068        private ApplicationContext applicationContext;
069        @XmlTransient
070        private CamelPostProcessorHelper postProcessor;
071        @XmlTransient
072        private String camelId;
073    
074        public CamelBeanPostProcessor() {
075        }
076    
077        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
078            if (LOG.isTraceEnabled()) {
079                LOG.trace("Camel bean processing before initialization for bean: " + beanName);
080            }
081    
082            // some beans cannot be post processed at this given time, so we gotta check beforehand
083            if (!canPostProcessBean(bean, beanName)) {
084                return bean;
085            }
086    
087            if (camelContext == null && applicationContext.containsBean(camelId)) {
088                setCamelContext((SpringCamelContext) applicationContext.getBean(camelId));
089            }
090            injectFields(bean);
091            injectMethods(bean);
092            if (bean instanceof CamelContextAware && canSetCamelContext(bean, beanName)) {
093                CamelContextAware contextAware = (CamelContextAware)bean;
094                if (camelContext == null) {
095                    LOG.warn("No CamelContext defined yet so cannot inject into: " + bean);
096                } else {
097                    contextAware.setCamelContext(camelContext);
098                }
099            }
100            return bean;
101        }
102    
103        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
104            if (LOG.isTraceEnabled()) {
105                LOG.trace("Camel bean processing after initialization for bean: " + beanName);
106            }
107    
108            // some beans cannot be post processed at this given time, so we gotta check beforehand
109            if (!canPostProcessBean(bean, beanName)) {
110                return bean;
111            }
112    
113            if (bean instanceof DefaultEndpoint) {
114                DefaultEndpoint defaultEndpoint = (DefaultEndpoint) bean;
115                defaultEndpoint.setEndpointUriIfNotSpecified(beanName);
116            }
117            return bean;
118        }
119    
120        // Properties
121        // -------------------------------------------------------------------------
122    
123        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
124            this.applicationContext = applicationContext;
125        }
126    
127        public SpringCamelContext getCamelContext() {
128            return camelContext;
129        }
130    
131        public void setCamelContext(SpringCamelContext camelContext) {
132            this.camelContext = camelContext;
133            postProcessor = new CamelPostProcessorHelper(camelContext) {
134                @Override
135                protected RuntimeException createProxyInstantiationRuntimeException(Class<?> type, Endpoint endpoint, Exception e) {
136                    return new BeanInstantiationException(type, "Could not instantiate proxy of type " + type.getName() + " on endpoint " + endpoint, e);
137                }
138            };
139        }
140    
141        public String getCamelId() {
142            return camelId;
143        }
144    
145        public void setCamelId(String camelId) {
146            this.camelId = camelId;
147        }
148    
149        // Implementation methods
150        // -------------------------------------------------------------------------
151    
152        /**
153         * Can we post process the given bean?
154         *
155         * @param bean the bean
156         * @param beanName the bean name
157         * @return true to process it
158         */
159        protected boolean canPostProcessBean(Object bean, String beanName) {
160            // the JMXAgent is a bit strange and causes Spring issues if we let it being
161            // post processed by this one. It does not need it anyway so we are good to go.
162            if (bean instanceof CamelJMXAgentType) {
163                return false;
164            }
165    
166            // all other beans can of course be processed
167            return true;
168        }
169        
170        
171        protected boolean canSetCamelContext(Object bean, String beanName) {
172            
173            try {
174                Method method = null;
175                try {
176                    method = bean.getClass().getMethod("getCamelContext", new Class[]{});
177                } catch (NoSuchMethodException ex) {
178                    method = bean.getClass().getMethod("getContext", new Class[]{});
179                }
180                CamelContext context = (CamelContext) method.invoke(bean, new Object[]{});
181                if (context != null) {
182                    if (LOG.isTraceEnabled()) {
183                        LOG.trace("The camel context of " + beanName + " is set, so we skip inject the camel context of it.");
184                    }
185                    return false;
186                }
187            } catch (Exception e) {
188                // can't check the status of camelContext , so return true by default
189            }
190            return true;
191        }
192    
193        /**
194         * A strategy method to allow implementations to perform some custom JBI
195         * based injection of the POJO
196         *
197         * @param bean the bean to be injected
198         */
199        protected void injectFields(final Object bean) {
200            ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
201                public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
202                    EndpointInject annotation = field.getAnnotation(EndpointInject.class);
203                    if (annotation != null) {
204                        injectField(field, annotation.uri(), annotation.name(), bean);
205                    }
206                    Produce produce = field.getAnnotation(Produce.class);
207                    if (produce != null) {
208                        injectField(field, produce.uri(), produce.ref(), bean);
209                    }
210                }
211            });
212        }
213    
214        protected void injectField(Field field, String endpointUri, String endpointRef, Object bean) {
215            ReflectionUtils.setField(field, bean, getPostProcessor().getInjectionValue(field.getType(), endpointUri, endpointRef, field.getName()));
216        }
217    
218        protected void injectMethods(final Object bean) {
219            ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
220                @SuppressWarnings("unchecked")
221                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
222                    setterInjection(method, bean);
223                    getPostProcessor().consumerInjection(method, bean);
224                }
225            });
226        }
227    
228        protected void setterInjection(Method method, Object bean) {
229            EndpointInject annoation = method.getAnnotation(EndpointInject.class);
230            if (annoation != null) {
231                setterInjection(method, bean, annoation.uri(), annoation.name());
232            }
233            Produce produce = method.getAnnotation(Produce.class);
234            if (produce != null) {
235                setterInjection(method, bean, produce.uri(), produce.ref());
236            }
237        }
238    
239        protected void setterInjection(Method method, Object bean, String endpointUri, String endpointRef) {
240            Class<?>[] parameterTypes = method.getParameterTypes();
241            if (parameterTypes != null) {
242                if (parameterTypes.length != 1) {
243                    LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
244                } else {
245                    String propertyName = ObjectHelper.getPropertyName(method);
246                    Object value = getPostProcessor().getInjectionValue(parameterTypes[0], endpointUri, endpointRef, propertyName);
247                    ObjectHelper.invokeMethod(method, bean, value);
248                }
249            }
250        }
251    
252    
253        protected void consumerInjection(final Object bean) {
254            org.springframework.util.ReflectionUtils.doWithMethods(bean.getClass(), new org.springframework.util.ReflectionUtils.MethodCallback() {
255                @SuppressWarnings("unchecked")
256                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
257                    /*
258                     * TODO support callbacks? if
259                     * (method.getAnnotation(Callback.class) != null) { try {
260                     * Expression e = ExpressionFactory.createExpression(
261                     * method.getAnnotation(Callback.class).condition());
262                     * JexlContext jc = JexlHelper.createContext();
263                     * jc.getVars().put("this", obj); Object r = e.evaluate(jc); if
264                     * (!(r instanceof Boolean)) { throw new
265                     * RuntimeException("Expression did not returned a boolean value
266                     * but: " + r); } Boolean oldVal =
267                     * req.getCallbacks().get(method); Boolean newVal = (Boolean) r;
268                     * if ((oldVal == null || !oldVal) && newVal) {
269                     * req.getCallbacks().put(method, newVal); method.invoke(obj,
270                     * new Object[0]); // TODO: handle return value and sent it as
271                     * the answer } } catch (Exception e) { throw new
272                     * RuntimeException("Unable to invoke callback", e); } }
273                     */
274                }
275            });
276        }
277    
278        public CamelPostProcessorHelper getPostProcessor() {
279            ObjectHelper.notNull(postProcessor, "postProcessor");
280            return postProcessor;
281        }
282    }