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 */
017package org.apache.camel.impl;
018
019import java.lang.reflect.Field;
020import java.lang.reflect.Method;
021
022import org.apache.camel.BeanInject;
023import org.apache.camel.CamelContext;
024import org.apache.camel.CamelContextAware;
025import org.apache.camel.EndpointInject;
026import org.apache.camel.Produce;
027import org.apache.camel.PropertyInject;
028import org.apache.camel.util.ObjectHelper;
029import org.apache.camel.util.ReflectionHelper;
030import org.slf4j.Logger;
031import 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 */
049public 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     * A strategy method to allow implementations to perform some custom JBI
162     * based injection of the POJO
163     *
164     * @param bean the bean to be injected
165     */
166    protected void injectFields(final Object bean, final String beanName) {
167        ReflectionHelper.doWithFields(bean.getClass(), new ReflectionHelper.FieldCallback() {
168            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
169                PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
170                if (propertyInject != null && getPostProcessorHelper().matchContext(propertyInject.context())) {
171                    injectFieldProperty(field, propertyInject.value(), propertyInject.defaultValue(), bean, beanName);
172                }
173
174                BeanInject beanInject = field.getAnnotation(BeanInject.class);
175                if (beanInject != null && getPostProcessorHelper().matchContext(beanInject.context())) {
176                    injectFieldBean(field, beanInject.value(), bean, beanName);
177                }
178
179                EndpointInject endpointInject = field.getAnnotation(EndpointInject.class);
180                if (endpointInject != null && getPostProcessorHelper().matchContext(endpointInject.context())) {
181                    injectField(field, endpointInject.uri(), endpointInject.ref(), endpointInject.property(), bean, beanName);
182                }
183
184                Produce produce = field.getAnnotation(Produce.class);
185                if (produce != null && getPostProcessorHelper().matchContext(produce.context())) {
186                    injectField(field, produce.uri(), produce.ref(), produce.property(), bean, beanName, produce.binding());
187                }
188            }
189        });
190    }
191
192    public void injectField(Field field, String endpointUri, String endpointRef, String endpointProperty,
193                               Object bean, String beanName) {
194        injectField(field, endpointUri, endpointRef, endpointProperty, bean, beanName, true);
195    }
196    
197    public void injectField(Field field, String endpointUri, String endpointRef, String endpointProperty,
198                               Object bean, String beanName, boolean binding) {
199        ReflectionHelper.setField(field, bean,
200                getPostProcessorHelper().getInjectionValue(field.getType(), endpointUri, endpointRef, endpointProperty,
201                        field.getName(), bean, beanName, binding));
202    }
203
204    public void injectFieldBean(Field field, String name, Object bean, String beanName) {
205        ReflectionHelper.setField(field, bean,
206                getPostProcessorHelper().getInjectionBeanValue(field.getType(), name));
207    }
208
209    public void injectFieldProperty(Field field, String propertyName, String propertyDefaultValue, Object bean, String beanName) {
210        ReflectionHelper.setField(field, bean,
211                getPostProcessorHelper().getInjectionPropertyValue(field.getType(), propertyName, propertyDefaultValue,
212                        field.getName(), bean, beanName));
213    }
214
215    protected void injectMethods(final Object bean, final String beanName) {
216        ReflectionHelper.doWithMethods(bean.getClass(), new ReflectionHelper.MethodCallback() {
217            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
218                setterInjection(method, bean, beanName);
219                getPostProcessorHelper().consumerInjection(method, bean, beanName);
220            }
221        });
222    }
223
224    protected void setterInjection(Method method, Object bean, String beanName) {
225        PropertyInject propertyInject = method.getAnnotation(PropertyInject.class);
226        if (propertyInject != null && getPostProcessorHelper().matchContext(propertyInject.context())) {
227            setterPropertyInjection(method, propertyInject.value(), propertyInject.defaultValue(), bean, beanName);
228        }
229
230        BeanInject beanInject = method.getAnnotation(BeanInject.class);
231        if (beanInject != null && getPostProcessorHelper().matchContext(beanInject.context())) {
232            setterBeanInjection(method, beanInject.value(), bean, beanName);
233        }
234
235        EndpointInject endpointInject = method.getAnnotation(EndpointInject.class);
236        if (endpointInject != null && getPostProcessorHelper().matchContext(endpointInject.context())) {
237            setterInjection(method, bean, beanName, endpointInject.uri(), endpointInject.ref(), endpointInject.property());
238        }
239
240        Produce produce = method.getAnnotation(Produce.class);
241        if (produce != null && getPostProcessorHelper().matchContext(produce.context())) {
242            setterInjection(method, bean, beanName, produce.uri(), produce.ref(), produce.property());
243        }
244    }
245
246    public void setterInjection(Method method, Object bean, String beanName, String endpointUri, String endpointRef, String endpointProperty) {
247        Class<?>[] parameterTypes = method.getParameterTypes();
248        if (parameterTypes != null) {
249            if (parameterTypes.length != 1) {
250                LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
251            } else {
252                String propertyName = ObjectHelper.getPropertyName(method);
253                Object value = getPostProcessorHelper().getInjectionValue(parameterTypes[0], endpointUri, endpointRef, endpointProperty,
254                        propertyName, bean, beanName);
255                ObjectHelper.invokeMethod(method, bean, value);
256            }
257        }
258    }
259
260    public void setterPropertyInjection(Method method, String propertyValue, String propertyDefaultValue,
261                                        Object bean, String beanName) {
262        Class<?>[] parameterTypes = method.getParameterTypes();
263        if (parameterTypes != null) {
264            if (parameterTypes.length != 1) {
265                LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
266            } else {
267                String propertyName = ObjectHelper.getPropertyName(method);
268                Object value = getPostProcessorHelper().getInjectionPropertyValue(parameterTypes[0], propertyValue, propertyDefaultValue, propertyName, bean, beanName);
269                ObjectHelper.invokeMethod(method, bean, value);
270            }
271        }
272    }
273
274    public void setterBeanInjection(Method method, String name, Object bean, String beanName) {
275        Class<?>[] parameterTypes = method.getParameterTypes();
276        if (parameterTypes != null) {
277            if (parameterTypes.length != 1) {
278                LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
279            } else {
280                Object value = getPostProcessorHelper().getInjectionBeanValue(parameterTypes[0], name);
281                ObjectHelper.invokeMethod(method, bean, value);
282            }
283        }
284    }
285
286}