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