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.CamelContext;
022import org.apache.camel.Endpoint;
023import org.apache.camel.Exchange;
024import org.apache.camel.FailedToCreateConsumerException;
025import org.apache.camel.IsSingleton;
026import org.apache.camel.PollingConsumer;
027import org.apache.camel.RuntimeCamelException;
028import org.apache.camel.ServicePoolAware;
029import org.apache.camel.spi.EndpointUtilizationStatistics;
030import org.apache.camel.spi.ServicePool;
031import org.apache.camel.support.ServiceSupport;
032import org.apache.camel.util.CamelContextHelper;
033import org.apache.camel.util.LRUCache;
034import org.apache.camel.util.ServiceHelper;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * Cache containing created {@link org.apache.camel.Consumer}.
040 *
041 * @version 
042 */
043public class ConsumerCache extends ServiceSupport {
044    private static final Logger LOG = LoggerFactory.getLogger(ConsumerCache.class);
045
046    private final CamelContext camelContext;
047    private final ServicePool<Endpoint, PollingConsumer> pool;
048    private final Map<String, PollingConsumer> consumers;
049    private final Object source;
050
051    private EndpointUtilizationStatistics statistics;
052    private boolean extendedStatistics;
053    private int maxCacheSize;
054
055    public ConsumerCache(Object source, CamelContext camelContext) {
056        this(source, camelContext, CamelContextHelper.getMaximumCachePoolSize(camelContext));
057    }
058
059    public ConsumerCache(Object source, CamelContext camelContext, int cacheSize) {
060        this(source, camelContext, createLRUCache(cacheSize));
061    }
062    
063    public ConsumerCache(Object source, CamelContext camelContext, Map<String, PollingConsumer> cache) {
064        this(source, camelContext, cache, camelContext.getPollingConsumerServicePool());
065    }
066
067    public ConsumerCache(Object source, CamelContext camelContext, Map<String, PollingConsumer> cache, ServicePool<Endpoint, PollingConsumer> pool) {
068        this.camelContext = camelContext;
069        this.consumers = cache;
070        this.source = source;
071        this.pool = pool;
072        if (consumers instanceof LRUCache) {
073            maxCacheSize = ((LRUCache) consumers).getMaxCacheSize();
074        }
075
076        // only if JMX is enabled
077        if (camelContext.getManagementStrategy().getManagementAgent() != null) {
078            this.extendedStatistics = camelContext.getManagementStrategy().getManagementAgent().getStatisticsLevel().isExtended();
079        } else {
080            this.extendedStatistics = false;
081        }
082    }
083
084    public boolean isExtendedStatistics() {
085        return extendedStatistics;
086    }
087
088    /**
089     * Whether extended JMX statistics is enabled for {@link org.apache.camel.spi.EndpointUtilizationStatistics}
090     */
091    public void setExtendedStatistics(boolean extendedStatistics) {
092        this.extendedStatistics = extendedStatistics;
093    }
094
095    /**
096     * Creates the {@link LRUCache} to be used.
097     * <p/>
098     * This implementation returns a {@link LRUCache} instance.
099
100     * @param cacheSize the cache size
101     * @return the cache
102     */
103    protected static LRUCache<String, PollingConsumer> createLRUCache(int cacheSize) {
104        // Use a regular cache as we want to ensure that the lifecycle of the consumers
105        // being cache is properly handled, such as they are stopped when being evicted
106        // or when this cache is stopped. This is needed as some consumers requires to
107        // be stopped so they can shutdown internal resources that otherwise may cause leaks
108        return new LRUCache<String, PollingConsumer>(cacheSize);
109    }
110    
111    /**
112     * Acquires a pooled PollingConsumer which you <b>must</b> release back again after usage using the
113     * {@link #releasePollingConsumer(org.apache.camel.Endpoint, org.apache.camel.PollingConsumer)} method.
114     *
115     * @param endpoint the endpoint
116     * @return the PollingConsumer
117     */
118    public PollingConsumer acquirePollingConsumer(Endpoint endpoint) {
119        return doGetPollingConsumer(endpoint, true);
120    }
121
122    /**
123     * Releases an acquired producer back after usage.
124     *
125     * @param endpoint the endpoint
126     * @param pollingConsumer the pollingConsumer to release
127     */
128    public void releasePollingConsumer(Endpoint endpoint, PollingConsumer pollingConsumer) {
129        if (pollingConsumer instanceof ServicePoolAware) {
130            // release back to the pool
131            pool.release(endpoint, pollingConsumer);
132        } else {
133            boolean singleton = false;
134            if (pollingConsumer instanceof IsSingleton) {
135                singleton = ((IsSingleton) pollingConsumer).isSingleton();
136            }
137            String key = endpoint.getEndpointUri();
138            boolean cached = consumers.containsKey(key);
139            if (!singleton || !cached) {
140                try {
141                    // stop and shutdown non-singleton/non-cached consumers as we should not leak resources
142                    if (!singleton) {
143                        LOG.debug("Released PollingConsumer: {} is stopped as consumer is not singleton", endpoint);
144                    } else {
145                        LOG.debug("Released PollingConsumer: {} is stopped as consumer cache is full", endpoint);
146                    }
147                    ServiceHelper.stopAndShutdownService(pollingConsumer);
148                } catch (Throwable ex) {
149                    if (ex instanceof RuntimeCamelException) {
150                        throw (RuntimeCamelException)ex;
151                    } else {
152                        throw new RuntimeCamelException(ex);
153                    }
154                }
155            }
156        }
157    }
158
159    public PollingConsumer getConsumer(Endpoint endpoint) {
160        return doGetPollingConsumer(endpoint, true);
161    }
162    
163    protected synchronized PollingConsumer doGetPollingConsumer(Endpoint endpoint, boolean pooled) {
164        String key = endpoint.getEndpointUri();
165        PollingConsumer answer = consumers.get(key);
166        if (pooled && answer == null) {
167            pool.acquire(endpoint);
168        }  
169        
170        if (answer == null) {
171            try {
172                answer = endpoint.createPollingConsumer();
173                answer.start();
174            } catch (Throwable e) {
175                throw new FailedToCreateConsumerException(endpoint, e);
176            }
177            if (pooled && answer instanceof ServicePoolAware) {
178                LOG.debug("Adding to producer service pool with key: {} for producer: {}", endpoint, answer);
179                answer = pool.addAndAcquire(endpoint, answer);
180            } else {
181                boolean singleton = false;
182                if (answer instanceof IsSingleton) {
183                    singleton = ((IsSingleton) answer).isSingleton();
184                }
185                if (singleton) {
186                    LOG.debug("Adding to consumer cache with key: {} for consumer: {}", endpoint, answer);
187                    consumers.put(key, answer);
188                } else {
189                    LOG.debug("Consumer for endpoint: {} is not singleton and thus not added to consumer cache", key);
190                }
191            }
192        }
193
194        if (answer != null) {
195            // record statistics
196            if (extendedStatistics) {
197                statistics.onHit(key);
198            }
199        }
200
201        return answer;
202    }
203 
204    public Exchange receive(Endpoint endpoint) {
205        LOG.debug("<<<< {}", endpoint);
206        PollingConsumer consumer = null;
207        try {
208            consumer = acquirePollingConsumer(endpoint);
209            return consumer.receive();
210        } finally {
211            if (consumer != null) {
212                releasePollingConsumer(endpoint, consumer);
213            }
214        }
215    }
216
217    public Exchange receive(Endpoint endpoint, long timeout) {
218        LOG.debug("<<<< {}", endpoint);
219        PollingConsumer consumer = null;
220        try {
221            consumer = acquirePollingConsumer(endpoint);
222            return consumer.receive(timeout);
223        } finally {
224            if (consumer != null) {
225                releasePollingConsumer(endpoint, consumer);
226            }
227        }
228    }
229
230    public Exchange receiveNoWait(Endpoint endpoint) {
231        LOG.debug("<<<< {}", endpoint);
232        PollingConsumer consumer = null;
233        try {
234            consumer = doGetPollingConsumer(endpoint, true);
235            return consumer.receiveNoWait();
236        } finally {
237            if (consumer != null) {
238                releasePollingConsumer(endpoint, consumer);
239            }
240        }
241    }
242    
243    public CamelContext getCamelContext() {
244        return camelContext;
245    }
246
247    /**
248     * Gets the source which uses this cache
249     *
250     * @return the source
251     */
252    public Object getSource() {
253        return source;
254    }
255
256    /**
257     * Returns the current size of the cache
258     *
259     * @return the current size
260     */
261    public int size() {
262        int size = consumers.size();
263        LOG.trace("size = {}", size);
264        return size;
265    }
266
267    /**
268     * Gets the maximum cache size (capacity).
269     * <p/>
270     * Will return <tt>-1</tt> if it cannot determine this if a custom cache was used.
271     *
272     * @return the capacity
273     */
274    public int getCapacity() {
275        int capacity = -1;
276        if (consumers instanceof LRUCache) {
277            LRUCache<String, PollingConsumer> cache = (LRUCache<String, PollingConsumer>)consumers;
278            capacity = cache.getMaxCacheSize();
279        }
280        return capacity;
281    }
282
283    /**
284     * Gets the cache hits statistic
285     * <p/>
286     * Will return <tt>-1</tt> if it cannot determine this if a custom cache was used.
287     *
288     * @return the hits
289     */
290    public long getHits() {
291        long hits = -1;
292        if (consumers instanceof LRUCache) {
293            LRUCache<String, PollingConsumer> cache = (LRUCache<String, PollingConsumer>)consumers;
294            hits = cache.getHits();
295        }
296        return hits;
297    }
298
299    /**
300     * Gets the cache misses statistic
301     * <p/>
302     * Will return <tt>-1</tt> if it cannot determine this if a custom cache was used.
303     *
304     * @return the misses
305     */
306    public long getMisses() {
307        long misses = -1;
308        if (consumers instanceof LRUCache) {
309            LRUCache<String, PollingConsumer> cache = (LRUCache<String, PollingConsumer>)consumers;
310            misses = cache.getMisses();
311        }
312        return misses;
313    }
314
315    /**
316     * Gets the cache evicted statistic
317     * <p/>
318     * Will return <tt>-1</tt> if it cannot determine this if a custom cache was used.
319     *
320     * @return the evicted
321     */
322    public long getEvicted() {
323        long evicted = -1;
324        if (consumers instanceof LRUCache) {
325            LRUCache<String, PollingConsumer> cache = (LRUCache<String, PollingConsumer>)consumers;
326            evicted = cache.getEvicted();
327        }
328        return evicted;
329    }
330
331    /**
332     * Resets the cache statistics
333     */
334    public void resetCacheStatistics() {
335        if (consumers instanceof LRUCache) {
336            LRUCache<String, PollingConsumer> cache = (LRUCache<String, PollingConsumer>)consumers;
337            cache.resetStatistics();
338        }
339        if (statistics != null) {
340            statistics.clear();
341        }
342    }
343
344    /**
345     * Purges this cache
346     */
347    public synchronized void purge() {
348        consumers.clear();
349        if (statistics != null) {
350            statistics.clear();
351        }
352    }
353
354    /**
355     * Cleanup the cache (purging stale entries)
356     */
357    public void cleanUp() {
358        if (consumers instanceof LRUCache) {
359            LRUCache<String, PollingConsumer> cache = (LRUCache<String, PollingConsumer>)consumers;
360            cache.cleanUp();
361        }
362    }
363
364    public EndpointUtilizationStatistics getEndpointUtilizationStatistics() {
365        return statistics;
366    }
367
368    @Override
369    public String toString() {
370        return "ConsumerCache for source: " + source + ", capacity: " + getCapacity();
371    }
372
373    protected void doStart() throws Exception {
374        if (extendedStatistics) {
375            int max = maxCacheSize == 0 ? CamelContextHelper.getMaximumCachePoolSize(camelContext) : maxCacheSize;
376            statistics = new DefaultEndpointUtilizationStatistics(max);
377        }
378
379        ServiceHelper.startServices(consumers.values());
380    }
381
382    protected void doStop() throws Exception {
383        // when stopping we intend to shutdown
384        ServiceHelper.stopAndShutdownServices(statistics, pool);
385        try {
386            ServiceHelper.stopAndShutdownServices(consumers.values());
387        } finally {
388            // ensure consumers are removed, and also from JMX
389            for (PollingConsumer consumer : consumers.values()) {
390                getCamelContext().removeService(consumer);
391            }
392        }
393        consumers.clear();
394        if (statistics != null) {
395            statistics.clear();
396        }
397    }
398
399}