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}