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