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