Repository: cayenne
Updated Branches:
  refs/heads/querycache [created] 0a62b2be5


Added getQueryCache to the ObjectContext interface since this is already 
implemented by BaseContext anyway and makes accessing the cache much easier.

Revised signature for QueryCache.remove(String) to be remove(QueryMetadata) to 
increase understandability. It was never clear how to use this method before.

Added QueryCache.clearLocalCache method. This is now called automatically at 
the end of the request-response loop in StatelessContextRequestHandler and by 
BaseContext.finalize. This will prevent memory leaking from locally cached data 
in cases where the cache is not configured to expire entries based on time.

Added QueryCache.debugListCacheKeys method to list all keys (prefixed by cache 
group) in the cache for debugging purposes.


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/be513314
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/be513314
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/be513314

Branch: refs/heads/querycache
Commit: be51331499f8dbdb9c897db90602f491a711715f
Parents: 15b7f69
Author: John Huss <johnth...@gmail.com>
Authored: Tue May 1 17:02:56 2018 -0500
Committer: John Huss <johnth...@apache.org>
Committed: Wed May 2 10:27:37 2018 -0500

----------------------------------------------------------------------
 .../apache/cayenne/jcache/JCacheQueryCache.java | 68 ++++++++++++++++----
 .../java/org/apache/cayenne/BaseContext.java    | 10 +++
 .../java/org/apache/cayenne/ObjectContext.java  |  3 +
 .../cayenne/access/DataContextQueryAction.java  |  6 +-
 .../cayenne/access/DataDomainQueryAction.java   |  2 +-
 .../org/apache/cayenne/cache/MapQueryCache.java | 44 +++++++++++--
 .../apache/cayenne/cache/NestedQueryCache.java  | 21 ++++--
 .../org/apache/cayenne/cache/QueryCache.java    | 21 ++++--
 .../apache/cayenne/cache/MockQueryCache.java    | 16 ++++-
 .../web/StatelessContextRequestHandler.java     |  6 ++
 10 files changed, 164 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java
----------------------------------------------------------------------
diff --git 
a/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java 
b/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java
index a6d0e17..c7dcfe3 100644
--- 
a/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java
+++ 
b/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java
@@ -19,21 +19,26 @@
 
 package org.apache.cayenne.jcache;
 
-import org.apache.cayenne.cache.QueryCache;
-import org.apache.cayenne.cache.QueryCacheEntryFactory;
-import org.apache.cayenne.di.BeforeScopeEnd;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.query.QueryMetadata;
-
-import javax.cache.Cache;
-import javax.cache.CacheException;
-import javax.cache.CacheManager;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import javax.cache.Cache;
+import javax.cache.Cache.Entry;
+import javax.cache.CacheException;
+import javax.cache.CacheManager;
+
+import org.apache.cayenne.cache.QueryCache;
+import org.apache.cayenne.cache.QueryCacheEntryFactory;
+import org.apache.cayenne.di.BeforeScopeEnd;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.query.QueryMetadata;
+
 /**
  * @since 4.0
  */
@@ -75,14 +80,16 @@ public class JCacheQueryCache implements QueryCache {
     }
 
     @Override
-    public void remove(String key) {
-        if (key != null) {
-            for (String cache : cacheManager.getCacheNames()) {
-                getCache(cache).remove(key);
+    public void remove(QueryMetadata metadata) {
+        if (metadata != null) {
+            Cache<String, List> cache = getCache(metadata.getCacheGroup());
+            if (cache != null) {
+               String key = metadata.getCacheKey();
+               cache.remove(key);
             }
         }
     }
-
+    
     @Override
     public void removeGroup(String groupKey) {
         Cache<String, List> cache = getCache(groupKey);
@@ -156,4 +163,37 @@ public class JCacheQueryCache implements QueryCache {
     public void shutdown() {
         cacheManager.close();
     }
+    
+    @Override
+    public void clearLocalCache(Optional<String> namespace) {
+       if (!namespace.isPresent()) {
+               return;
+       }
+
+       for (String name : cacheManager.getCacheNames()) {
+               Cache<String, List> cache = getCache(name);
+               Iterator<Entry<String, List>> it = cache.iterator();
+               while (it.hasNext()) {
+                       Entry<String, List> entry = (Entry<String, List>) 
it.next();
+                       if (entry.getKey().startsWith(namespace.get())) {
+                               it.remove();
+                       }
+               }
+       }
+    }
+
+       @Override
+       public List<String> debugListCacheKeys() {
+               List<String> result = new ArrayList<>();
+               for (String name : cacheManager.getCacheNames()) {
+            Cache<String, List> cache = getCache(name);
+            Iterator<Entry<String, List>> it = cache.iterator();
+            while (it.hasNext()) {
+               Entry<String, List> entry = (Entry<String, List>) it.next();
+                               result.add(name + "." + entry.getKey());
+                       }
+               }
+               return result;
+       }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java 
b/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java
index ac9cc7a..d38cce0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java
@@ -48,6 +48,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -120,6 +121,14 @@ public abstract class BaseContext implements ObjectContext 
{
                graphAction = new ObjectContextGraphAction(this);
        }
 
+       @Override
+       protected void finalize() throws Throwable {
+               if (queryCache != null) {
+                       queryCache.clearLocalCache(Optional.empty());
+               }
+               super.finalize();
+       }
+       
        /**
         * Checks whether this context is attached to Cayenne runtime stack and 
if
         * not, attempts to attach itself to the runtime using Injector returned
@@ -469,6 +478,7 @@ public abstract class BaseContext implements ObjectContext {
        @Override
        public abstract Collection<?> uncommittedObjects();
 
+       @Override
        public QueryCache getQueryCache() {
                attachToRuntimeIfNeeded();
                return queryCache;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java 
b/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java
index dc535f8..d4ee643 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne;
 
+import org.apache.cayenne.cache.QueryCache;
 import org.apache.cayenne.graph.GraphManager;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.query.Query;
@@ -261,6 +262,8 @@ public interface ObjectContext extends DataChannel, 
Serializable {
      */
     DataChannel getChannel();
 
+    QueryCache getQueryCache();
+    
     /**
      * Returns <code>true</code> if there are any modified, deleted or new
      * objects registered with this ObjectContext, <code>false</code> 
otherwise.

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
index 87a7b80..1f53c9c 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
@@ -31,6 +31,7 @@ import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.query.EntityResultSegment;
 import org.apache.cayenne.query.ObjectIdQuery;
 import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.query.RefreshQuery;
 import org.apache.cayenne.util.ListResponse;
 import org.apache.cayenne.util.ObjectContextQueryAction;
@@ -176,9 +177,8 @@ class DataContextQueryAction extends 
ObjectContextQueryAction {
             Query cachedQuery = refreshQuery.getQuery();
             if (cachedQuery != null) {
 
-                String cacheKey = cachedQuery
-                        .getMetaData(context.getEntityResolver())
-                        .getCacheKey();
+                QueryMetadata cacheKey = cachedQuery
+                        .getMetaData(context.getEntityResolver());
                 context.getQueryCache().remove(cacheKey);
 
                 this.response = context.performGenericQuery(cachedQuery);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
index a87de83..be7f180 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
@@ -344,7 +344,7 @@ class DataDomainQueryAction implements QueryRouter, 
OperationObserver {
             if (refreshQuery.getQuery() != null) {
                 Query cachedQuery = refreshQuery.getQuery();
 
-                String cacheKey = 
cachedQuery.getMetaData(context.getEntityResolver()).getCacheKey();
+                QueryMetadata cacheKey = 
cachedQuery.getMetaData(context.getEntityResolver());
                 context.getQueryCache().remove(cacheKey);
 
                 this.response = domain.onQuery(context, cachedQuery);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java 
b/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java
index e2fb64d..3f85328 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java
@@ -19,8 +19,12 @@
 package org.apache.cayenne.cache;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.cayenne.CayenneRuntimeException;
@@ -97,15 +101,15 @@ public class MapQueryCache implements QueryCache, 
Serializable {
         }
     }
 
-    public void remove(String key) {
+    public void remove(QueryMetadata metadata) {
+        String key = metadata.getCacheKey();
         if (key == null) {
             return;
         }
-
-        for(Map<String, List<?>> map : cacheGroups.values()) {
-            synchronized (map) {
-                map.remove(key);
-            }
+        
+        Map<String, List<?>> map = createIfAbsent(metadata);
+        synchronized (map) {
+            map.remove(key);
         }
     }
 
@@ -172,4 +176,32 @@ public class MapQueryCache implements QueryCache, 
Serializable {
         // no explicit cache group
         return DEFAULT_CACHE_NAME;
     }
+
+       @Override
+       public void clearLocalCache(Optional<String> namespace) {
+               if (!namespace.isPresent()) {
+               return;
+       }
+               
+        for (Entry<String, Map<String, List<?>>> cacheGroup : 
cacheGroups.entrySet()) {
+               Set<String> keys = cacheGroup.getValue().keySet();
+               for (String key : keys) {
+                       if (key.startsWith(namespace.get())) {
+                               keys.remove(key);
+                       }
+               }
+        }
+       }
+
+       @Override
+       public List<String> debugListCacheKeys() {
+               List<String> result = new ArrayList<>();
+        for (Entry<String, Map<String, List<?>>> cacheGroup : 
cacheGroups.entrySet()) {
+               for (String key : cacheGroup.getValue().keySet()) {
+                               result.add(cacheGroup.getKey() + "." + key);
+               }
+        }
+               return result;
+       }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java 
b/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java
index 7f50e5d..7dd3236 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java
@@ -18,11 +18,12 @@
  ****************************************************************/
 package org.apache.cayenne.cache;
 
+import java.util.List;
+import java.util.Optional;
+
 import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.query.QueryMetadataProxy;
 
-import java.util.List;
-
 /**
  * A {@link QueryCache} wrapper that introduces a key namespace on top of a
  * delegate shared cache. This way multiple cache users can share the same
@@ -97,8 +98,8 @@ public class NestedQueryCache implements QueryCache {
      * Removes an entry for key in the current namespace.
      */
     @Override
-    public void remove(String key) {
-        delegate.remove(qualifiedKey(key));
+    public void remove(QueryMetadata metadata) {
+        delegate.remove(qualifiedMetadata(metadata));
     }
 
     /**
@@ -126,4 +127,16 @@ public class NestedQueryCache implements QueryCache {
             }
         };
     }
+
+       @Override
+       public void clearLocalCache(Optional<String> namespace) {
+               // ignore passed in namespace
+               delegate.clearLocalCache(Optional.of(this.namespace));
+       }
+
+       @Override
+       public List<String> debugListCacheKeys() {
+               return delegate.debugListCacheKeys();
+       }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java 
b/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java
index de74ea6..8aa242a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.cache;
 import org.apache.cayenne.query.QueryMetadata;
 
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Defines API of a cache that stores query results.
@@ -29,6 +30,7 @@ import java.util.List;
  */
 public interface QueryCache {
 
+       
     /**
      * Returns a cached query result for the given QueryMetadata or null if 
the result is
      * not cached or is expired.
@@ -51,11 +53,13 @@ public interface QueryCache {
 
     @SuppressWarnings("rawtypes")
     void put(QueryMetadata metadata, List results);
-
+       
     /**
-     * Removes a single entry from cache.
+     * Removes a single query result from the cache that matches the given 
metadata.
+     * The metadata can be obtained like so: 
+     * <pre>query.getMetaData(context.getEntityResolver())</pre>
      */
-    void remove(String key);
+    void remove(QueryMetadata metadata);
 
     /**
      * Removes a group of entries identified by group key. Note that depending 
on
@@ -65,7 +69,7 @@ public interface QueryCache {
      * will not change after calling this method.
      */
     void removeGroup(String groupKey);
-
+    
     /**
      * Removes a group of entries identified by group key.
      * Can be used if cache provider supports strictly typed caches.
@@ -82,4 +86,13 @@ public interface QueryCache {
      */
     @Deprecated
     void clear();
+    
+    /**
+     * Clears all entries in the LOCAL cache.
+     * @param namespace this parameter is for internal use only - client 
should always pass Optional.empty()
+     */
+       void clearLocalCache(Optional<String> namespace);
+       
+       List<String> debugListCacheKeys();
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java 
b/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java
index 0705a60..7ed263c 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java
@@ -18,8 +18,12 @@
  ****************************************************************/
 package org.apache.cayenne.cache;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 
+import org.apache.cayenne.query.QueryCacheStrategy;
 import org.apache.cayenne.query.QueryMetadata;
 
 public class MockQueryCache implements QueryCache {
@@ -38,7 +42,7 @@ public class MockQueryCache implements QueryCache {
     public void put(QueryMetadata metadata, List results) {
     }
 
-    public void remove(String key) {
+    public void remove(QueryMetadata metadata) {
     }
 
     public void removeGroup(String groupKey) {
@@ -51,4 +55,14 @@ public class MockQueryCache implements QueryCache {
     public int size() {
         return 0;
     }
+    
+       @Override
+       public void clearLocalCache(Optional<String> namespace) {
+       }
+
+       @Override
+       public List<String> debugListCacheKeys() {
+               return Collections.emptyList();
+       }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java
----------------------------------------------------------------------
diff --git 
a/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java
 
b/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java
index 0e7651e..66e8768 100644
--- 
a/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java
+++ 
b/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.web;
 
+import java.util.Optional;
+
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 
@@ -67,6 +69,10 @@ public class StatelessContextRequestHandler implements 
RequestHandler {
 
     public void requestEnd(ServletRequest request, ServletResponse response) {
         CayenneRuntime.bindThreadInjector(null);
+        ObjectContext context = BaseContext.getThreadObjectContext();
+        if (context != null) {
+               
((BaseContext)context).getQueryCache().clearLocalCache(Optional.empty());
+        }
         BaseContext.bindThreadObjectContext(null);
     }
 

Reply via email to