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    }