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.impl;
018    
019    import java.lang.reflect.Field;
020    import java.lang.reflect.Method;
021    
022    import org.apache.camel.BeanInject;
023    import org.apache.camel.CamelContext;
024    import org.apache.camel.CamelContextAware;
025    import org.apache.camel.EndpointInject;
026    import org.apache.camel.Produce;
027    import org.apache.camel.PropertyInject;
028    import org.apache.camel.util.ObjectHelper;
029    import org.apache.camel.util.ReflectionHelper;
030    import org.slf4j.Logger;
031    import org.slf4j.LoggerFactory;
032    
033    /**
034     * A bean post processor which implements the <a href="http://camel.apache.org/bean-integration.html">Bean Integration</a>
035     * features in Camel. Features such as the <a href="http://camel.apache.org/bean-injection.html">Bean Injection</a> of objects like
036     * {@link org.apache.camel.Endpoint} and
037     * {@link org.apache.camel.ProducerTemplate} together with support for
038     * <a href="http://camel.apache.org/pojo-consuming.html">POJO Consuming</a> via the
039     * {@link org.apache.camel.Consume} annotation along with
040     * <a href="http://camel.apache.org/pojo-producing.html">POJO Producing</a> via the
041     * {@link org.apache.camel.Produce} annotation along with other annotations such as
042     * {@link org.apache.camel.DynamicRouter} for creating <a href="http://camel.apache.org/dynamicrouter-annotation.html">a Dynamic router via annotations</a>.
043     * {@link org.apache.camel.RecipientList} for creating <a href="http://camel.apache.org/recipientlist-annotation.html">a Recipient List router via annotations</a>.
044     * {@link org.apache.camel.RoutingSlip} for creating <a href="http://camel.apache.org/routingslip-annotation.html">a Routing Slip router via annotations</a>.
045     * <p/>
046     * Components such as <tt>camel-spring</tt>, and <tt>camel-blueprint</tt> can leverage this post processor to hook in Camel
047     * bean post processing into their bean processing framework.
048     */
049    public class DefaultCamelBeanPostProcessor {
050    
051        protected static final Logger LOG = LoggerFactory.getLogger(DefaultCamelBeanPostProcessor.class);
052        protected CamelPostProcessorHelper camelPostProcessorHelper;
053        protected CamelContext camelContext;
054    
055        public DefaultCamelBeanPostProcessor() {
056        }
057    
058        public DefaultCamelBeanPostProcessor(CamelContext camelContext) {
059            this.camelContext = camelContext;
060        }
061    
062        /**
063         * Apply this post processor to the given new bean instance <i>before</i> any bean
064         * initialization callbacks (like <code>afterPropertiesSet</code>
065         * or a custom init-method). The bean will already be populated with property values.
066         * The returned bean instance may be a wrapper around the original.
067         * 
068         * @param bean the new bean instance
069         * @param beanName the name of the bean
070         * @return the bean instance to use, either the original or a wrapped one; if
071         * <code>null</code>, no subsequent BeanPostProcessors will be invoked
072         * @throws Exception is thrown if error post processing bean
073         */
074        public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
075            LOG.trace("Camel bean processing before initialization for bean: {}", beanName);
076    
077            // some beans cannot be post processed at this given time, so we gotta check beforehand
078            if (!canPostProcessBean(bean, beanName)) {
079                return bean;
080            }
081    
082            injectFields(bean, beanName);
083            injectMethods(bean, beanName);
084    
085            if (bean instanceof CamelContextAware && canSetCamelContext(bean, beanName)) {
086                CamelContextAware contextAware = (CamelContextAware)bean;
087                CamelContext context = getOrLookupCamelContext();
088                if (context == null) {
089                    LOG.warn("No CamelContext defined yet so cannot inject into bean: " + beanName);
090                } else {
091                    contextAware.setCamelContext(context);
092                }
093            }
094    
095            return bean;
096        }
097    
098        /**
099         * Apply this post processor to the given new bean instance <i>after</i> any bean
100         * initialization callbacks (like <code>afterPropertiesSet</code>
101         * or a custom init-method). The bean will already be populated with property values.
102         * The returned bean instance may be a wrapper around the original.
103         * 
104         * @param bean the new bean instance
105         * @param beanName the name of the bean
106         * @return the bean instance to use, either the original or a wrapped one; if
107         * <code>null</code>, no subsequent BeanPostProcessors will be invoked
108         * @throws Exception is thrown if error post processing bean
109         */
110        public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
111            LOG.trace("Camel bean processing after initialization for bean: {}", beanName);
112    
113            // some beans cannot be post processed at this given time, so we gotta check beforehand
114            if (!canPostProcessBean(bean, beanName)) {
115                return bean;
116            }
117    
118            if (bean instanceof DefaultEndpoint) {
119                DefaultEndpoint defaultEndpoint = (DefaultEndpoint) bean;
120                defaultEndpoint.setEndpointUriIfNotSpecified(beanName);
121            }
122    
123            return bean;
124        }
125    
126        /**
127         * Strategy to get the {@link CamelContext} to use.
128         */
129        public CamelContext getOrLookupCamelContext() {
130            return camelContext;
131        }
132    
133        /**
134         * Strategy to get the {@link CamelPostProcessorHelper}
135         */
136        public CamelPostProcessorHelper getPostProcessorHelper() {
137            if (camelPostProcessorHelper == null) {
138                camelPostProcessorHelper = new CamelPostProcessorHelper(getOrLookupCamelContext());
139            }
140            return camelPostProcessorHelper;
141        }
142    
143        protected boolean canPostProcessBean(Object bean, String beanName) {
144            return bean != null;
145        }
146    
147        protected boolean canSetCamelContext(Object bean, String beanName) {
148            if (bean instanceof CamelContextAware) {
149                CamelContextAware camelContextAware = (CamelContextAware) bean;
150                CamelContext context = camelContextAware.getCamelContext();
151                if (context != null) {
152                    LOG.trace("CamelContext already set on bean with id [{}]. Will keep existing CamelContext on bean.", beanName);
153                    return false;
154                }
155            }
156    
157            return true;
158        }
159    
160    
161        /**
162         * A strategy method to allow implementations to perform some custom JBI
163         * based injection of the POJO
164         *
165         * @param bean the bean to be injected
166         */
167        protected void injectFields(final Object bean, final String beanName) {
168            ReflectionHelper.doWithFields(bean.getClass(), new ReflectionHelper.FieldCallback() {
169                public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
170                    PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
171                    if (propertyInject != null && getPostProcessorHelper().matchContext(propertyInject.context())) {
172                        injectFieldProperty(field, propertyInject.value(), propertyInject.defaultValue(), bean, beanName);
173                    }
174    
175                    BeanInject beanInject = field.getAnnotation(BeanInject.class);
176                    if (beanInject != null && getPostProcessorHelper().matchContext(beanInject.context())) {
177                        injectFieldBean(field, beanInject.value(), bean, beanName);
178                    }
179    
180                    EndpointInject endpointInject = field.getAnnotation(EndpointInject.class);
181                    if (endpointInject != null && getPostProcessorHelper().matchContext(endpointInject.context())) {
182                        injectField(field, endpointInject.uri(), endpointInject.ref(), endpointInject.property(), bean, beanName);
183                    }
184    
185                    Produce produce = field.getAnnotation(Produce.class);
186                    if (produce != null && getPostProcessorHelper().matchContext(produce.context())) {
187                        injectField(field, produce.uri(), produce.ref(), produce.property(), bean, beanName);
188                    }
189                }
190            });
191        }
192    
193        public void injectField(Field field, String endpointUri, String endpointRef, String endpointProperty,
194                                   Object bean, String beanName) {
195            ReflectionHelper.setField(field, bean,
196                    getPostProcessorHelper().getInjectionValue(field.getType(), endpointUri, endpointRef, endpointProperty,
197                            field.getName(), bean, beanName));
198        }
199    
200        public void injectFieldBean(Field field, String name, Object bean, String beanName) {
201            ReflectionHelper.setField(field, bean,
202                    getPostProcessorHelper().getInjectionBeanValue(field.getType(), name));
203        }
204    
205        public void injectFieldProperty(Field field, String propertyName, String propertyDefaultValue, Object bean, String beanName) {
206            ReflectionHelper.setField(field, bean,
207                    getPostProcessorHelper().getInjectionPropertyValue(field.getType(), propertyName, propertyDefaultValue,
208                            field.getName(), bean, beanName));
209        }
210    
211        protected void injectMethods(final Object bean, final String beanName) {
212            ReflectionHelper.doWithMethods(bean.getClass(), new ReflectionHelper.MethodCallback() {
213                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
214                    setterInjection(method, bean, beanName);
215                    getPostProcessorHelper().consumerInjection(method, bean, beanName);
216                }
217            });
218        }
219    
220        protected void setterInjection(Method method, Object bean, String beanName) {
221            PropertyInject propertyInject = method.getAnnotation(PropertyInject.class);
222            if (propertyInject != null && getPostProcessorHelper().matchContext(propertyInject.context())) {
223                setterPropertyInjection(method, propertyInject.value(), propertyInject.defaultValue(), bean, beanName);
224            }
225    
226            BeanInject beanInject = method.getAnnotation(BeanInject.class);
227            if (beanInject != null && getPostProcessorHelper().matchContext(beanInject.context())) {
228                setterBeanInjection(method, beanInject.value(), bean, beanName);
229            }
230    
231            EndpointInject endpointInject = method.getAnnotation(EndpointInject.class);
232            if (endpointInject != null && getPostProcessorHelper().matchContext(endpointInject.context())) {
233                setterInjection(method, bean, beanName, endpointInject.uri(), endpointInject.ref(), endpointInject.property());
234            }
235    
236            Produce produce = method.getAnnotation(Produce.class);
237            if (produce != null && getPostProcessorHelper().matchContext(produce.context())) {
238                setterInjection(method, bean, beanName, produce.uri(), produce.ref(), produce.property());
239            }
240        }
241    
242        public void setterInjection(Method method, Object bean, String beanName, String endpointUri, String endpointRef, String endpointProperty) {
243            Class<?>[] parameterTypes = method.getParameterTypes();
244            if (parameterTypes != null) {
245                if (parameterTypes.length != 1) {
246                    LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
247                } else {
248                    String propertyName = ObjectHelper.getPropertyName(method);
249                    Object value = getPostProcessorHelper().getInjectionValue(parameterTypes[0], endpointUri, endpointRef, endpointProperty,
250                            propertyName, bean, beanName);
251                    ObjectHelper.invokeMethod(method, bean, value);
252                }
253            }
254        }
255    
256        public void setterPropertyInjection(Method method, String propertyValue, String propertyDefaultValue,
257                                            Object bean, String beanName) {
258            Class<?>[] parameterTypes = method.getParameterTypes();
259            if (parameterTypes != null) {
260                if (parameterTypes.length != 1) {
261                    LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
262                } else {
263                    String propertyName = ObjectHelper.getPropertyName(method);
264                    Object value = getPostProcessorHelper().getInjectionPropertyValue(parameterTypes[0], propertyValue, propertyDefaultValue, propertyName, bean, beanName);
265                    ObjectHelper.invokeMethod(method, bean, value);
266                }
267            }
268        }
269    
270        public void setterBeanInjection(Method method, String name, Object bean, String beanName) {
271            Class<?>[] parameterTypes = method.getParameterTypes();
272            if (parameterTypes != null) {
273                if (parameterTypes.length != 1) {
274                    LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
275                } else {
276                    Object value = getPostProcessorHelper().getInjectionBeanValue(parameterTypes[0], name);
277                    ObjectHelper.invokeMethod(method, bean, value);
278                }
279            }
280        }
281    
282    }