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