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.util.Map; 020 021import org.apache.camel.AsyncCallback; 022import org.apache.camel.AsyncProcessor; 023import org.apache.camel.AsyncProducerCallback; 024import org.apache.camel.CamelContext; 025import org.apache.camel.Endpoint; 026import org.apache.camel.Exchange; 027import org.apache.camel.ExchangePattern; 028import org.apache.camel.FailedToCreateProducerException; 029import org.apache.camel.Processor; 030import org.apache.camel.Producer; 031import org.apache.camel.ProducerCallback; 032import org.apache.camel.ServicePoolAware; 033import org.apache.camel.processor.UnitOfWorkProducer; 034import org.apache.camel.spi.ServicePool; 035import org.apache.camel.support.ServiceSupport; 036import org.apache.camel.util.AsyncProcessorConverterHelper; 037import org.apache.camel.util.CamelContextHelper; 038import org.apache.camel.util.EventHelper; 039import org.apache.camel.util.LRUCache; 040import org.apache.camel.util.ServiceHelper; 041import org.apache.camel.util.StopWatch; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045/** 046 * Cache containing created {@link Producer}. 047 * 048 * @version 049 */ 050public class ProducerCache extends ServiceSupport { 051 private static final Logger LOG = LoggerFactory.getLogger(ProducerCache.class); 052 053 private final CamelContext camelContext; 054 private final ServicePool<Endpoint, Producer> pool; 055 private final Map<String, Producer> producers; 056 private final Object source; 057 private boolean eventNotifierEnabled = true; 058 private boolean stopServicePool; 059 060 public ProducerCache(Object source, CamelContext camelContext) { 061 this(source, camelContext, CamelContextHelper.getMaximumCachePoolSize(camelContext)); 062 } 063 064 public ProducerCache(Object source, CamelContext camelContext, int cacheSize) { 065 this(source, camelContext, null, createLRUCache(cacheSize)); 066 } 067 068 public ProducerCache(Object source, CamelContext camelContext, Map<String, Producer> cache) { 069 this(source, camelContext, null, cache); 070 } 071 072 public ProducerCache(Object source, CamelContext camelContext, ServicePool<Endpoint, Producer> producerServicePool, Map<String, Producer> cache) { 073 this.source = source; 074 this.camelContext = camelContext; 075 if (producerServicePool == null) { 076 // use shared producer pool which lifecycle is managed by CamelContext 077 this.pool = camelContext.getProducerServicePool(); 078 this.stopServicePool = false; 079 } else { 080 this.pool = producerServicePool; 081 this.stopServicePool = true; 082 } 083 this.producers = cache; 084 } 085 086 public boolean isEventNotifierEnabled() { 087 return eventNotifierEnabled; 088 } 089 090 public void setEventNotifierEnabled(boolean eventNotifierEnabled) { 091 this.eventNotifierEnabled = eventNotifierEnabled; 092 } 093 094 /** 095 * Creates the {@link LRUCache} to be used. 096 * <p/> 097 * This implementation returns a {@link LRUCache} instance. 098 099 * @param cacheSize the cache size 100 * @return the cache 101 */ 102 protected static LRUCache<String, Producer> createLRUCache(int cacheSize) { 103 // Use a regular cache as we want to ensure that the lifecycle of the producers 104 // being cache is properly handled, such as they are stopped when being evicted 105 // or when this cache is stopped. This is needed as some producers requires to 106 // be stopped so they can shutdown internal resources that otherwise may cause leaks 107 return new LRUCache<String, Producer>(cacheSize); 108 } 109 110 public CamelContext getCamelContext() { 111 return camelContext; 112 } 113 114 /** 115 * Gets the source which uses this cache 116 * 117 * @return the source 118 */ 119 public Object getSource() { 120 return source; 121 } 122 123 /** 124 * Acquires a pooled producer which you <b>must</b> release back again after usage using the 125 * {@link #releaseProducer(org.apache.camel.Endpoint, org.apache.camel.Producer)} method. 126 * 127 * @param endpoint the endpoint 128 * @return the producer 129 */ 130 public Producer acquireProducer(Endpoint endpoint) { 131 return doGetProducer(endpoint, true); 132 } 133 134 /** 135 * Releases an acquired producer back after usage. 136 * 137 * @param endpoint the endpoint 138 * @param producer the producer to release 139 * @throws Exception can be thrown if error stopping producer if that was needed. 140 */ 141 public void releaseProducer(Endpoint endpoint, Producer producer) throws Exception { 142 if (producer instanceof ServicePoolAware) { 143 // release back to the pool 144 pool.release(endpoint, producer); 145 } else if (!producer.isSingleton()) { 146 // stop and shutdown non-singleton producers as we should not leak resources 147 ServiceHelper.stopAndShutdownService(producer); 148 } 149 } 150 151 /** 152 * Starts the {@link Producer} to be used for sending to the given endpoint 153 * <p/> 154 * This can be used to early start the {@link Producer} to ensure it can be created, 155 * such as when Camel is started. This allows to fail fast in case the {@link Producer} 156 * could not be started. 157 * 158 * @param endpoint the endpoint to send the exchange to 159 * @throws Exception is thrown if failed to create or start the {@link Producer} 160 */ 161 public void startProducer(Endpoint endpoint) throws Exception { 162 Producer producer = acquireProducer(endpoint); 163 releaseProducer(endpoint, producer); 164 } 165 166 /** 167 * Sends the exchange to the given endpoint. 168 * <p> 169 * This method will <b>not</b> throw an exception. If processing of the given 170 * Exchange failed then the exception is stored on the provided Exchange 171 * 172 * @param endpoint the endpoint to send the exchange to 173 * @param exchange the exchange to send 174 */ 175 public void send(Endpoint endpoint, Exchange exchange) { 176 sendExchange(endpoint, null, null, exchange); 177 } 178 179 /** 180 * Sends an exchange to an endpoint using a supplied 181 * {@link Processor} to populate the exchange 182 * <p> 183 * This method will <b>not</b> throw an exception. If processing of the given 184 * Exchange failed then the exception is stored on the return Exchange 185 * 186 * @param endpoint the endpoint to send the exchange to 187 * @param processor the transformer used to populate the new exchange 188 * @throws org.apache.camel.CamelExecutionException is thrown if sending failed 189 * @return the exchange 190 */ 191 public Exchange send(Endpoint endpoint, Processor processor) { 192 return sendExchange(endpoint, null, processor, null); 193 } 194 195 /** 196 * Sends an exchange to an endpoint using a supplied 197 * {@link Processor} to populate the exchange 198 * <p> 199 * This method will <b>not</b> throw an exception. If processing of the given 200 * Exchange failed then the exception is stored on the return Exchange 201 * 202 * @param endpoint the endpoint to send the exchange to 203 * @param pattern the message {@link ExchangePattern} such as 204 * {@link ExchangePattern#InOnly} or {@link ExchangePattern#InOut} 205 * @param processor the transformer used to populate the new exchange 206 * @return the exchange 207 */ 208 public Exchange send(Endpoint endpoint, ExchangePattern pattern, Processor processor) { 209 return sendExchange(endpoint, pattern, processor, null); 210 } 211 212 /** 213 * Sends an exchange to an endpoint using a supplied callback, using the synchronous processing. 214 * <p/> 215 * If an exception was thrown during processing, it would be set on the given Exchange 216 * 217 * @param endpoint the endpoint to send the exchange to 218 * @param exchange the exchange, can be <tt>null</tt> if so then create a new exchange from the producer 219 * @param pattern the exchange pattern, can be <tt>null</tt> 220 * @param callback the callback 221 * @return the response from the callback 222 * @see #doInAsyncProducer(org.apache.camel.Endpoint, org.apache.camel.Exchange, org.apache.camel.ExchangePattern, org.apache.camel.AsyncCallback, org.apache.camel.AsyncProducerCallback) 223 */ 224 public <T> T doInProducer(Endpoint endpoint, Exchange exchange, ExchangePattern pattern, ProducerCallback<T> callback) { 225 T answer = null; 226 227 // get the producer and we do not mind if its pooled as we can handle returning it back to the pool 228 Producer producer = doGetProducer(endpoint, true); 229 230 if (producer == null) { 231 if (isStopped()) { 232 LOG.warn("Ignoring exchange sent after processor is stopped: " + exchange); 233 return null; 234 } else { 235 throw new IllegalStateException("No producer, this processor has not been started: " + this); 236 } 237 } 238 239 try { 240 // invoke the callback 241 answer = callback.doInProducer(producer, exchange, pattern); 242 } catch (Throwable e) { 243 if (exchange != null) { 244 exchange.setException(e); 245 } 246 } finally { 247 if (producer instanceof ServicePoolAware) { 248 // release back to the pool 249 pool.release(endpoint, producer); 250 } else if (!producer.isSingleton()) { 251 // stop and shutdown non-singleton producers as we should not leak resources 252 try { 253 ServiceHelper.stopAndShutdownService(producer); 254 } catch (Exception e) { 255 // ignore and continue 256 LOG.warn("Error stopping/shutting down producer: " + producer, e); 257 } 258 } 259 } 260 261 return answer; 262 } 263 264 /** 265 * Sends an exchange to an endpoint using a supplied callback supporting the asynchronous routing engine. 266 * <p/> 267 * If an exception was thrown during processing, it would be set on the given Exchange 268 * 269 * @param endpoint the endpoint to send the exchange to 270 * @param exchange the exchange, can be <tt>null</tt> if so then create a new exchange from the producer 271 * @param pattern the exchange pattern, can be <tt>null</tt> 272 * @param callback the asynchronous callback 273 * @param producerCallback the producer template callback to be executed 274 * @return (doneSync) <tt>true</tt> to continue execute synchronously, <tt>false</tt> to continue being executed asynchronously 275 */ 276 public boolean doInAsyncProducer(final Endpoint endpoint, final Exchange exchange, final ExchangePattern pattern, 277 final AsyncCallback callback, final AsyncProducerCallback producerCallback) { 278 279 Producer target; 280 try { 281 // get the producer and we do not mind if its pooled as we can handle returning it back to the pool 282 target = doGetProducer(endpoint, true); 283 284 if (target == null) { 285 if (isStopped()) { 286 LOG.warn("Ignoring exchange sent after processor is stopped: " + exchange); 287 callback.done(true); 288 return true; 289 } else { 290 exchange.setException(new IllegalStateException("No producer, this processor has not been started: " + this)); 291 callback.done(true); 292 return true; 293 } 294 } 295 } catch (Throwable e) { 296 exchange.setException(e); 297 callback.done(true); 298 return true; 299 } 300 301 final Producer producer = target; 302 303 // record timing for sending the exchange using the producer 304 final StopWatch watch = eventNotifierEnabled && exchange != null ? new StopWatch() : null; 305 306 try { 307 if (eventNotifierEnabled && exchange != null) { 308 EventHelper.notifyExchangeSending(exchange.getContext(), exchange, endpoint); 309 } 310 // invoke the callback 311 AsyncProcessor asyncProcessor = AsyncProcessorConverterHelper.convert(producer); 312 return producerCallback.doInAsyncProducer(producer, asyncProcessor, exchange, pattern, new AsyncCallback() { 313 @Override 314 public void done(boolean doneSync) { 315 try { 316 if (eventNotifierEnabled && watch != null) { 317 long timeTaken = watch.stop(); 318 // emit event that the exchange was sent to the endpoint 319 EventHelper.notifyExchangeSent(exchange.getContext(), exchange, endpoint, timeTaken); 320 } 321 322 if (producer instanceof ServicePoolAware) { 323 // release back to the pool 324 pool.release(endpoint, producer); 325 } else if (!producer.isSingleton()) { 326 // stop and shutdown non-singleton producers as we should not leak resources 327 try { 328 ServiceHelper.stopAndShutdownService(producer); 329 } catch (Exception e) { 330 // ignore and continue 331 LOG.warn("Error stopping/shutting down producer: " + producer, e); 332 } 333 } 334 } finally { 335 callback.done(doneSync); 336 } 337 } 338 }); 339 } catch (Throwable e) { 340 // ensure exceptions is caught and set on the exchange 341 if (exchange != null) { 342 exchange.setException(e); 343 } 344 callback.done(true); 345 return true; 346 } 347 } 348 349 protected Exchange sendExchange(final Endpoint endpoint, ExchangePattern pattern, 350 final Processor processor, Exchange exchange) { 351 return doInProducer(endpoint, exchange, pattern, new ProducerCallback<Exchange>() { 352 public Exchange doInProducer(Producer producer, Exchange exchange, ExchangePattern pattern) { 353 if (exchange == null) { 354 exchange = pattern != null ? producer.createExchange(pattern) : producer.createExchange(); 355 } 356 357 if (processor != null) { 358 // lets populate using the processor callback 359 try { 360 processor.process(exchange); 361 } catch (Exception e) { 362 // populate failed so return 363 exchange.setException(e); 364 return exchange; 365 } 366 } 367 368 // now lets dispatch 369 LOG.debug(">>>> {} {}", endpoint, exchange); 370 371 // set property which endpoint we send to 372 exchange.setProperty(Exchange.TO_ENDPOINT, endpoint.getEndpointUri()); 373 374 // send the exchange using the processor 375 StopWatch watch = null; 376 try { 377 if (eventNotifierEnabled) { 378 watch = new StopWatch(); 379 EventHelper.notifyExchangeSending(exchange.getContext(), exchange, endpoint); 380 } 381 // ensure we run in an unit of work 382 Producer target = new UnitOfWorkProducer(producer); 383 target.process(exchange); 384 } catch (Throwable e) { 385 // ensure exceptions is caught and set on the exchange 386 exchange.setException(e); 387 } finally { 388 // emit event that the exchange was sent to the endpoint 389 if (eventNotifierEnabled && watch != null) { 390 long timeTaken = watch.stop(); 391 EventHelper.notifyExchangeSent(exchange.getContext(), exchange, endpoint, timeTaken); 392 } 393 } 394 return exchange; 395 } 396 }); 397 } 398 399 protected synchronized Producer doGetProducer(Endpoint endpoint, boolean pooled) { 400 String key = endpoint.getEndpointUri(); 401 Producer answer = producers.get(key); 402 if (pooled && answer == null) { 403 // try acquire from connection pool 404 answer = pool.acquire(endpoint); 405 } 406 407 if (answer == null) { 408 // create a new producer 409 try { 410 answer = endpoint.createProducer(); 411 // add as service which will also start the service 412 // (false => we and handling the lifecycle of the producer in this cache) 413 getCamelContext().addService(answer, false); 414 } catch (Exception e) { 415 throw new FailedToCreateProducerException(endpoint, e); 416 } 417 418 // add producer to cache or pool if applicable 419 if (pooled && answer instanceof ServicePoolAware) { 420 LOG.debug("Adding to producer service pool with key: {} for producer: {}", endpoint, answer); 421 answer = pool.addAndAcquire(endpoint, answer); 422 } else if (answer.isSingleton()) { 423 LOG.debug("Adding to producer cache with key: {} for producer: {}", endpoint, answer); 424 producers.put(key, answer); 425 } 426 } 427 428 return answer; 429 } 430 431 protected void doStart() throws Exception { 432 ServiceHelper.startServices(producers.values()); 433 ServiceHelper.startServices(pool); 434 } 435 436 protected void doStop() throws Exception { 437 // when stopping we intend to shutdown 438 if (stopServicePool) { 439 ServiceHelper.stopAndShutdownService(pool); 440 } 441 try { 442 ServiceHelper.stopAndShutdownServices(producers.values()); 443 } finally { 444 // ensure producers are removed, and also from JMX 445 for (Producer producer : producers.values()) { 446 getCamelContext().removeService(producer); 447 } 448 } 449 producers.clear(); 450 } 451 452 /** 453 * Returns the current size of the cache 454 * 455 * @return the current size 456 */ 457 public int size() { 458 int size = producers.size(); 459 size += pool.size(); 460 461 LOG.trace("size = {}", size); 462 return size; 463 } 464 465 /** 466 * Gets the maximum cache size (capacity). 467 * <p/> 468 * Will return <tt>-1</tt> if it cannot determine this if a custom cache was used. 469 * 470 * @return the capacity 471 */ 472 public int getCapacity() { 473 int capacity = -1; 474 if (producers instanceof LRUCache) { 475 LRUCache<String, Producer> cache = (LRUCache<String, Producer>)producers; 476 capacity = cache.getMaxCacheSize(); 477 } 478 return capacity; 479 } 480 481 /** 482 * Gets the cache hits statistic 483 * <p/> 484 * Will return <tt>-1</tt> if it cannot determine this if a custom cache was used. 485 * 486 * @return the hits 487 */ 488 public long getHits() { 489 long hits = -1; 490 if (producers instanceof LRUCache) { 491 LRUCache<String, Producer> cache = (LRUCache<String, Producer>)producers; 492 hits = cache.getHits(); 493 } 494 return hits; 495 } 496 497 /** 498 * Gets the cache misses statistic 499 * <p/> 500 * Will return <tt>-1</tt> if it cannot determine this if a custom cache was used. 501 * 502 * @return the misses 503 */ 504 public long getMisses() { 505 long misses = -1; 506 if (producers instanceof LRUCache) { 507 LRUCache<String, Producer> cache = (LRUCache<String, Producer>)producers; 508 misses = cache.getMisses(); 509 } 510 return misses; 511 } 512 513 /** 514 * Gets the cache evicted statistic 515 * <p/> 516 * Will return <tt>-1</tt> if it cannot determine this if a custom cache was used. 517 * 518 * @return the evicted 519 */ 520 public long getEvicted() { 521 long evicted = -1; 522 if (producers instanceof LRUCache) { 523 LRUCache<String, Producer> cache = (LRUCache<String, Producer>)producers; 524 evicted = cache.getEvicted(); 525 } 526 return evicted; 527 } 528 529 /** 530 * Resets the cache statistics 531 */ 532 public void resetCacheStatistics() { 533 if (producers instanceof LRUCache) { 534 LRUCache<String, Producer> cache = (LRUCache<String, Producer>)producers; 535 cache.resetStatistics(); 536 } 537 } 538 539 /** 540 * Purges this cache 541 */ 542 public synchronized void purge() { 543 producers.clear(); 544 pool.purge(); 545 } 546 547 @Override 548 public String toString() { 549 return "ProducerCache for source: " + source + ", capacity: " + getCapacity(); 550 } 551}