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.Method;
020import java.util.Set;
021import javax.xml.bind.annotation.XmlTransient;
022
023import org.apache.camel.CamelContext;
024import org.apache.camel.CamelContextAware;
025import org.apache.camel.Consume;
026import org.apache.camel.Consumer;
027import org.apache.camel.ConsumerTemplate;
028import org.apache.camel.Endpoint;
029import org.apache.camel.FluentProducerTemplate;
030import org.apache.camel.IsSingleton;
031import org.apache.camel.NoSuchBeanException;
032import org.apache.camel.PollingConsumer;
033import org.apache.camel.Processor;
034import org.apache.camel.Produce;
035import org.apache.camel.Producer;
036import org.apache.camel.ProducerTemplate;
037import org.apache.camel.ProxyInstantiationException;
038import org.apache.camel.Service;
039import org.apache.camel.builder.DefaultFluentProducerTemplate;
040import org.apache.camel.component.bean.BeanInfo;
041import org.apache.camel.component.bean.BeanProcessor;
042import org.apache.camel.component.bean.ProxyHelper;
043import org.apache.camel.processor.CamelInternalProcessor;
044import org.apache.camel.processor.DeferServiceFactory;
045import org.apache.camel.processor.UnitOfWorkProducer;
046import org.apache.camel.util.CamelContextHelper;
047import org.apache.camel.util.IntrospectionSupport;
048import org.apache.camel.util.ObjectHelper;
049import org.apache.camel.util.ServiceHelper;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053/**
054 * A helper class for Camel based injector or post processing hooks which can be
055 * reused by both the <a href="http://camel.apache.org/spring.html">Spring</a>,
056 * <a href="http://camel.apache.org/guice.html">Guice</a> and
057 * <a href="http://camel.apache.org/blueprint.html">Blueprint</a> support.
058 *
059 * @version
060 */
061public class CamelPostProcessorHelper implements CamelContextAware {
062
063    private static final Logger LOG = LoggerFactory.getLogger(CamelPostProcessorHelper.class);
064
065    @XmlTransient
066    private CamelContext camelContext;
067
068    public CamelPostProcessorHelper() {
069    }
070
071    public CamelPostProcessorHelper(CamelContext camelContext) {
072        this.setCamelContext(camelContext);
073    }
074
075    public CamelContext getCamelContext() {
076        return camelContext;
077    }
078
079    public void setCamelContext(CamelContext camelContext) {
080        this.camelContext = camelContext;
081    }
082
083    /**
084     * Does the given context match this camel context
085     */
086    public boolean matchContext(String context) {
087        if (ObjectHelper.isNotEmpty(context)) {
088            if (!getCamelContext().getName().equals(context)) {
089                return false;
090            }
091        }
092        return true;
093    }
094
095    public void consumerInjection(Method method, Object bean, String beanName) {
096        Consume consume = method.getAnnotation(Consume.class);
097        if (consume != null && matchContext(consume.context())) {
098            LOG.debug("Creating a consumer for: " + consume);
099            subscribeMethod(method, bean, beanName, consume.uri(), consume.ref(), consume.property());
100        }
101    }
102
103    public void subscribeMethod(Method method, Object bean, String beanName, String endpointUri, String endpointName, String endpointProperty) {
104        // lets bind this method to a listener
105        String injectionPointName = method.getName();
106        Endpoint endpoint = getEndpointInjection(bean, endpointUri, endpointName, endpointProperty, injectionPointName, true);
107        if (endpoint != null) {
108            try {
109                Processor processor = createConsumerProcessor(bean, method, endpoint);
110                Consumer consumer = endpoint.createConsumer(processor);
111                LOG.debug("Created processor: {} for consumer: {}", processor, consumer);
112                startService(consumer, endpoint.getCamelContext(), bean, beanName);
113            } catch (Exception e) {
114                throw ObjectHelper.wrapRuntimeCamelException(e);
115            }
116        }
117    }
118
119    /**
120     * Stats the given service
121     */
122    protected void startService(Service service, CamelContext camelContext, Object bean, String beanName) throws Exception {
123        // defer starting the service until CamelContext has started all its initial services
124        if (camelContext != null) {
125            camelContext.deferStartService(service, true);
126        } else {
127            // mo CamelContext then start service manually
128            ServiceHelper.startService(service);
129        }
130
131        boolean singleton = isSingleton(bean, beanName);
132        if (!singleton) {
133            LOG.debug("Service is not singleton so you must remember to stop it manually {}", service);
134        }
135    }
136
137    /**
138     * Create a processor which invokes the given method when an incoming
139     * message exchange is received
140     */
141    protected Processor createConsumerProcessor(final Object pojo, final Method method, final Endpoint endpoint) {
142        BeanInfo info = new BeanInfo(getCamelContext(), method);
143        BeanProcessor answer = new BeanProcessor(pojo, info);
144        // must ensure the consumer is being executed in an unit of work so synchronization callbacks etc is invoked
145        CamelInternalProcessor internal = new CamelInternalProcessor(answer);
146        internal.addAdvice(new CamelInternalProcessor.UnitOfWorkProcessorAdvice(null));
147        return internal;
148    }
149
150    public Endpoint getEndpointInjection(Object bean, String uri, String name, String propertyName,
151            String injectionPointName, boolean mandatory) {
152        if (ObjectHelper.isEmpty(uri) && ObjectHelper.isEmpty(name)) {
153            // if no uri or ref, then fallback and try the endpoint property
154            return doGetEndpointInjection(bean, propertyName, injectionPointName);
155        } else {
156            return doGetEndpointInjection(uri, name, injectionPointName, mandatory);
157        }
158    }
159
160    private Endpoint doGetEndpointInjection(String uri, String name, String injectionPointName, boolean mandatory) {
161        return CamelContextHelper.getEndpointInjection(getCamelContext(), uri, name, injectionPointName, mandatory);
162    }
163
164    /**
165     * Gets the injection endpoint from a bean property.
166     *
167     * @param bean the bean
168     * @param propertyName the property name on the bean
169     */
170    private Endpoint doGetEndpointInjection(Object bean, String propertyName, String injectionPointName) {
171        // fall back and use the method name if no explicit property name was given
172        if (ObjectHelper.isEmpty(propertyName)) {
173            propertyName = injectionPointName;
174        }
175
176        // we have a property name, try to lookup a getter method on the bean with that name using this strategy
177        // 1. first the getter with the name as given
178        // 2. then the getter with Endpoint as postfix
179        // 3. then if start with on then try step 1 and 2 again, but omit the on prefix
180        try {
181            Object value = IntrospectionSupport.getOrElseProperty(bean, propertyName, null);
182            if (value == null) {
183                // try endpoint as postfix
184                value = IntrospectionSupport.getOrElseProperty(bean, propertyName + "Endpoint", null);
185            }
186            if (value == null && propertyName.startsWith("on")) {
187                // retry but without the on as prefix
188                propertyName = propertyName.substring(2);
189                return doGetEndpointInjection(bean, propertyName, injectionPointName);
190            }
191            if (value == null) {
192                return null;
193            } else if (value instanceof Endpoint) {
194                return (Endpoint) value;
195            } else {
196                String uriOrRef = getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, value);
197                return getCamelContext().getEndpoint(uriOrRef);
198            }
199        } catch (Exception e) {
200            throw new IllegalArgumentException("Error getting property " + propertyName + " from bean " + bean + " due " + e.getMessage(), e);
201        }
202    }
203
204    /**
205     * Creates the object to be injected for an
206     * {@link org.apache.camel.EndpointInject} or
207     * {@link org.apache.camel.Produce} injection point
208     */
209    public Object getInjectionValue(Class<?> type, String endpointUri, String endpointRef, String endpointProperty,
210            String injectionPointName, Object bean, String beanName) {
211        return getInjectionValue(type, endpointUri, endpointRef, endpointProperty, injectionPointName, bean, beanName, true);
212    }
213    
214    /**
215     * Creates the object to be injected for an
216     * {@link org.apache.camel.EndpointInject} or
217     * {@link org.apache.camel.Produce} injection point
218     */
219    public Object getInjectionValue(Class<?> type, String endpointUri, String endpointRef, String endpointProperty,
220            String injectionPointName, Object bean, String beanName, boolean binding) {
221        if (type.isAssignableFrom(ProducerTemplate.class)) {
222            return createInjectionProducerTemplate(endpointUri, endpointRef, endpointProperty, injectionPointName, bean);
223        } else if (type.isAssignableFrom(FluentProducerTemplate.class)) {
224            return createInjectionFluentProducerTemplate(endpointUri, endpointRef, endpointProperty, injectionPointName, bean);
225        } else if (type.isAssignableFrom(ConsumerTemplate.class)) {
226            return createInjectionConsumerTemplate(endpointUri, endpointRef, endpointProperty, injectionPointName);
227        } else {
228            Endpoint endpoint = getEndpointInjection(bean, endpointUri, endpointRef, endpointProperty, injectionPointName, true);
229            if (endpoint != null) {
230                if (type.isInstance(endpoint)) {
231                    return endpoint;
232                } else if (type.isAssignableFrom(Producer.class)) {
233                    return createInjectionProducer(endpoint, bean, beanName);
234                } else if (type.isAssignableFrom(PollingConsumer.class)) {
235                    return createInjectionPollingConsumer(endpoint, bean, beanName);
236                } else if (type.isInterface()) {
237                    // lets create a proxy
238                    try {
239                        return ProxyHelper.createProxy(endpoint, binding, type);
240                    } catch (Exception e) {
241                        throw createProxyInstantiationRuntimeException(type, endpoint, e);
242                    }
243                } else {
244                    throw new IllegalArgumentException("Invalid type: " + type.getName()
245                            + " which cannot be injected via @EndpointInject/@Produce for: " + endpoint);
246                }
247            }
248            return null;
249        }
250    }
251
252    public Object getInjectionPropertyValue(Class<?> type, String propertyName, String propertyDefaultValue,
253            String injectionPointName, Object bean, String beanName) {
254        try {
255            // enforce a properties component to be created if none existed
256            CamelContextHelper.lookupPropertiesComponent(getCamelContext(), true);
257
258            String key;
259            String prefix = getCamelContext().getPropertyPrefixToken();
260            String suffix = getCamelContext().getPropertySuffixToken();
261            if (!propertyName.contains(prefix)) {
262                // must enclose the property name with prefix/suffix to have it resolved
263                key = prefix + propertyName + suffix;
264            } else {
265                // key has already prefix/suffix so use it as-is as it may be a compound key
266                key = propertyName;
267            }
268            String value = getCamelContext().resolvePropertyPlaceholders(key);
269            if (value != null) {
270                return getCamelContext().getTypeConverter().mandatoryConvertTo(type, value);
271            } else {
272                return null;
273            }
274        } catch (Exception e) {
275            if (ObjectHelper.isNotEmpty(propertyDefaultValue)) {
276                try {
277                    return getCamelContext().getTypeConverter().mandatoryConvertTo(type, propertyDefaultValue);
278                } catch (Exception e2) {
279                    throw ObjectHelper.wrapRuntimeCamelException(e2);
280                }
281            }
282            throw ObjectHelper.wrapRuntimeCamelException(e);
283        }
284    }
285
286    public Object getInjectionBeanValue(Class<?> type, String name) {
287        if (ObjectHelper.isEmpty(name)) {
288            Set<?> found = getCamelContext().getRegistry().findByType(type);
289            if (found == null || found.isEmpty()) {
290                throw new NoSuchBeanException(name, type.getName());
291            } else if (found.size() > 1) {
292                throw new NoSuchBeanException("Found " + found.size() + " beans of type: " + type + ". Only one bean expected.");
293            } else {
294                // we found only one
295                return found.iterator().next();
296            }
297        } else {
298            return CamelContextHelper.mandatoryLookup(getCamelContext(), name, type);
299        }
300    }
301
302    /**
303     * Factory method to create a {@link org.apache.camel.ProducerTemplate} to
304     * be injected into a POJO
305     */
306    protected ProducerTemplate createInjectionProducerTemplate(String endpointUri, String endpointRef, String endpointProperty,
307            String injectionPointName, Object bean) {
308        // endpoint is optional for this injection point
309        Endpoint endpoint = getEndpointInjection(bean, endpointUri, endpointRef, endpointProperty, injectionPointName, false);
310        CamelContext context = endpoint != null ? endpoint.getCamelContext() : getCamelContext();
311        ProducerTemplate answer = new DefaultProducerTemplate(context, endpoint);
312        // start the template so its ready to use
313        try {
314            // no need to defer the template as it can adjust to the endpoint at runtime
315            startService(answer, context, bean, null);
316        } catch (Exception e) {
317            throw ObjectHelper.wrapRuntimeCamelException(e);
318        }
319        return answer;
320    }
321
322    /**
323     * Factory method to create a
324     * {@link org.apache.camel.FluentProducerTemplate} to be injected into a
325     * POJO
326     */
327    protected FluentProducerTemplate createInjectionFluentProducerTemplate(String endpointUri, String endpointRef, String endpointProperty,
328            String injectionPointName, Object bean) {
329        // endpoint is optional for this injection point
330        Endpoint endpoint = getEndpointInjection(bean, endpointUri, endpointRef, endpointProperty, injectionPointName, false);
331        CamelContext context = endpoint != null ? endpoint.getCamelContext() : getCamelContext();
332        FluentProducerTemplate answer = new DefaultFluentProducerTemplate(context);
333        answer.setDefaultEndpoint(endpoint);
334        // start the template so its ready to use
335        try {
336            // no need to defer the template as it can adjust to the endpoint at runtime
337            startService(answer, context, bean, null);
338        } catch (Exception e) {
339            throw ObjectHelper.wrapRuntimeCamelException(e);
340        }
341        return answer;
342    }
343
344    /**
345     * Factory method to create a {@link org.apache.camel.ConsumerTemplate} to
346     * be injected into a POJO
347     */
348    protected ConsumerTemplate createInjectionConsumerTemplate(String endpointUri, String endpointRef, String endpointProperty,
349            String injectionPointName) {
350        ConsumerTemplate answer = new DefaultConsumerTemplate(getCamelContext());
351        // start the template so its ready to use
352        try {
353            startService(answer, null, null, null);
354        } catch (Exception e) {
355            throw ObjectHelper.wrapRuntimeCamelException(e);
356        }
357        return answer;
358    }
359
360    /**
361     * Factory method to create a started
362     * {@link org.apache.camel.PollingConsumer} to be injected into a POJO
363     */
364    protected PollingConsumer createInjectionPollingConsumer(Endpoint endpoint, Object bean, String beanName) {
365        try {
366            PollingConsumer consumer = endpoint.createPollingConsumer();
367            startService(consumer, endpoint.getCamelContext(), bean, beanName);
368            return consumer;
369        } catch (Exception e) {
370            throw ObjectHelper.wrapRuntimeCamelException(e);
371        }
372    }
373
374    /**
375     * A Factory method to create a started {@link org.apache.camel.Producer} to
376     * be injected into a POJO
377     */
378    protected Producer createInjectionProducer(Endpoint endpoint, Object bean, String beanName) {
379        try {
380            Producer producer = DeferServiceFactory.createProducer(endpoint);
381            return new UnitOfWorkProducer(producer);
382        } catch (Exception e) {
383            throw ObjectHelper.wrapRuntimeCamelException(e);
384        }
385    }
386
387    protected RuntimeException createProxyInstantiationRuntimeException(Class<?> type, Endpoint endpoint, Exception e) {
388        return new ProxyInstantiationException(type, endpoint, e);
389    }
390
391    /**
392     * Implementations can override this method to determine if the bean is
393     * singleton.
394     *
395     * @param bean the bean
396     * @return <tt>true</tt> if its singleton scoped, for prototype scoped
397     * <tt>false</tt> is returned.
398     */
399    protected boolean isSingleton(Object bean, String beanName) {
400        if (bean instanceof IsSingleton) {
401            IsSingleton singleton = (IsSingleton) bean;
402            return singleton.isSingleton();
403        }
404        return true;
405    }
406}