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    /**
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}