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