code: @SuppressWarnings({ "unchecked", "rawtypes" })
      public static <K, V> V getValue(ConcurrentMap<K, CacheEntry<V>> map, K key, long timeoutMillis, Callable<V> updater, Callable<? extends CacheEntry> cacheEntryFactory, boolean returnExpiredWhileUpdating, Object cacheRequestObject) {
          CacheEntry<V> cacheEntry = map.get(key);
          if(cacheEntry==null) {
              try {
                  cacheEntry = cacheEntryFactory.call();
              }
              catch (Exception e) {
                  throw new UpdateException(e);
              }
              CacheEntry<V> previousEntry = map.putIfAbsent(key, cacheEntry);
              if(previousEntry != null) {
                  cacheEntry = previousEntry;
              }
          }
          try {
              return cacheEntry.getValue(timeoutMillis, updater, returnExpiredWhileUpdating, cacheRequestObject);
          }
          catch (UpdateException e) {
              e.rethrowRuntimeException();
              // make compiler happy
              return null;
          }
      }
docs: Gets a value from cache. If the key doesn't exist, it will create the value using the updater callback
  Prevents cache storms with a lock
  
  The key is always added to the cache. Null return values will also be cached.
  You can use this together with ConcurrentLinkedHashMap to create a bounded LRU cache
  
  @param map the cache map
  @param key the key to look up
  @param timeoutMillis cache entry timeout
  @param updater callback to create/update value
  @param cacheEntryClass CacheEntry implementation class to use
  @param returnExpiredWhileUpdating when true, return expired value while updating new value
  @param cacheRequestObject context object that gets passed to hasExpired, shouldUpdate and updateValue methods, not used in default implementation
  @return