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}