/**
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
 * If a copy of the MPL was not distributed with this file, You can obtain one at
 * http://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is also subject to the terms of the Health-Related Additional
 * Disclaimer of Warranty and Limitation of Liability available at
 * http://www.carewebframework.org/licensing/disclaimer.
 */
package org.carewebframework.common;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Abstract class for managing globally cached data. Subclasses must implement the fetch logic for
 * retrieving the data object into the cache.
 * 
 * @param <KEY> The class of the indexing key.
 * @param <VALUE> The class of the cached item.
 */
public abstract class AbstractCache<KEY, VALUE> implements Iterable<VALUE> {
    
    private final Map<KEY, VALUE> map = new ConcurrentHashMap<>();
    
    /**
     * Logic to retrieve the data item from its primary store based on the provided key. The
     * returned item will be stored in the cache for future retrieval. Note that it is acceptable to
     * return a null item from this call.
     * 
     * @param key The key.
     * @return The fetched value.
     */
    protected abstract VALUE fetch(KEY key);
    
    /**
     * Get value for specified key. If not in cache, will call subclass's fetch method and load into
     * cache.
     * 
     * @param key The key.
     * @return The associated value.
     */
    public VALUE get(KEY key) {
        return isCached(key) ? map.get(key) : internalGet(key);
    }
    
    /**
     * Returns true if the item associated with the specified key is in the cache.
     * 
     * @param key The key.
     * @return True if associated item has been cached.
     */
    public boolean isCached(KEY key) {
        return map.containsKey(key);
    }
    
    /**
     * Internal, thread-safe method for loading result into cache.
     * 
     * @param key The key.
     * @return The associated value.
     */
    private VALUE internalGet(KEY key) {
        VALUE value = null;
        
        synchronized (map) {
            value = map.get(key);
            
            if (value == null) {
                map.put(key, value = fetch(key));
            }
        }
        
        return value;
    }
    
    /**
     * Refresh the cache. Any existing entries in the cache will be re-fetched after it is cleared.
     */
    public void refresh() {
        synchronized (map) {
            Set<KEY> contents = new HashSet<>(map.keySet());
            map.clear();
            
            for (KEY key : contents) {
                internalGet(key);
            }
        }
    }
    
    /**
     * Iterate over value set.
     */
    @Override
    public Iterator<VALUE> iterator() {
        return map.values().iterator();
    }
    
    /**
     * Return number of entries.
     * 
     * @return Number of entries.
     */
    public int size() {
        return map.size();
    }
    
}

@@ -599,16 +599,65 @@
 HashMap;
+%0Aimport java.util.concurrent.locks.ReentrantLock;
 %0A%0A/**%0A *
@@ -980,24 +980,840 @@
 LUE%3E %7B%0A    %0A
+    private static class CachedObject%3CVALUE%3E %7B%0A        %0A        private VALUE object;%0A        %0A        private ReentrantLock lock = new ReentrantLock();%0A        %0A        private CachedObject() %7B%0A            lock.lock();%0A        %7D%0A        %0A        public void setObject(VALUE object) %7B%0A            this.object = object;%0A            ReentrantLock lock = this.lock;%0A            this.lock = null;%0A            lock.unlock();%0A        %7D%0A        %0A        public VALUE getObject() %7B%0A            lock();%0A            unlock();%0A            return object;%0A        %7D%0A        %0A        private void lock() %7B%0A            if (lock != null) %7B%0A                lock.lock();%0A            %7D%0A        %7D%0A        %0A        private void unlock() %7B%0A            if (lock != null) %7B%0A                lock.unlock();%0A            %7D%0A        %7D%0A    %7D%0A    %0A
     private 
@@ -1827,22 +1827,36 @@
 ap%3CKEY, 
+CachedObject%3C
 VALUE%3E
+%3E
  map = n
@@ -2535,16 +2535,23 @@
 get(key)
+.object
  : inter
@@ -3087,38 +3087,255 @@
     
-%0A        synchronized (map) %7B%0A
+CachedObject%3CVALUE%3E cachedObject = null;%0A        %0A        synchronized (map) %7B%0A            if (!map.containsKey(key)) %7B%0A                cachedObject = new CachedObject%3CVALUE%3E();%0A                map.put(key, cachedObject);%0A            %7D else %7B%0A    
     
@@ -3358,24 +3358,36 @@
 map.get(key)
+.getObject()
 ;%0A          
@@ -3392,32 +3392,55 @@
     
-%0A
+%7D%0A  
      
+ %7D%0A        %0A
        
+ 
 if (
-value =
+cachedObject !
 = nu
@@ -3461,43 +3461,145 @@
     
-    map.put(key, value = fetch(key)
+try %7B%0A                cachedObject.setObject(fetch(key));%0A            %7D catch (Throwable e) %7B%0A                cachedObject.setObject(null
 );%0A 
@@ -4163,31 +4163,554 @@
 urn 
-map.values().iterator()
+new Iterator%3CVALUE%3E() %7B%0A            %0A            Iterator%3CCachedObject%3CVALUE%3E%3E iterator = map.values().iterator();%0A            %0A            @Override%0A            public boolean hasNext() %7B%0A                return iterator.hasNext();%0A            %7D%0A            %0A            @Override%0A            public VALUE next() %7B%0A                return iterator.next().getObject();%0A            %7D%0A            %0A            @Override%0A            public void remove() %7B%0A                throw new UnsupportedOperationException();%0A            %7D%0A            %0A        %7D
 ;%0A  

Improve AbstractCache performance by not keeping the whole cache locked while fetching a new entry.