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