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}