You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/02/15 14:08:23 UTC

[2/2] cayenne git commit: CAY-2228 Deprecate multiple cache groups in caching and query API - deprecate multiple cache groups in old API - remove multiple cache groups in 4.0 API - check and alert for multiple cache groups settings in Modeler - chang

CAY-2228 Deprecate multiple cache groups in caching and query API
 - deprecate multiple cache groups in old API
 - remove multiple cache groups in 4.0 API
 - check and alert for multiple cache groups settings in Modeler
 - change getOrginatingQuery() to getOriginatingQuery() in QueryMetadata


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

Branch: refs/heads/master
Commit: dd1745ca858acc9772c5a4190fbe75e01ef5f6f2
Parents: 8a570a9
Author: Nikita Timofeev <st...@gmail.com>
Authored: Wed Feb 15 17:04:04 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Wed Feb 15 17:04:04 2017 +0300

----------------------------------------------------------------------
 .../apache/cayenne/jcache/JCacheQueryCache.java |  16 +-
 .../cayenne/lifecycle/id/StringIdQuery.java     |  10 ++
 .../project/validation/BaseQueryValidator.java  |   9 +
 .../project/validation/EJBQLQueryValidator.java |   9 +-
 .../validation/ProcedureQueryValidator.java     |   1 +
 .../validation/SQLTemplateValidator.java        |   1 +
 .../validation/SelectQueryValidator.java        |   5 +-
 .../access/ClientServerChannelQueryAction.java  |   2 +-
 .../apache/cayenne/access/DataDomainQuery.java  |  17 ++
 .../access/ObjectsFromDataRowsQuery.java        |  10 ++
 .../apache/cayenne/cache/EhCacheQueryCache.java |  25 +--
 .../org/apache/cayenne/cache/MapQueryCache.java |  15 +-
 .../org/apache/cayenne/cache/OSQueryCache.java  |   8 +-
 .../org/apache/cayenne/cache/QueryCache.java    |   4 +-
 .../org/apache/cayenne/query/AbstractQuery.java |   3 +-
 .../apache/cayenne/query/BaseQueryMetadata.java |  74 ++++++--
 .../org/apache/cayenne/query/BatchQuery.java    |   1 +
 .../apache/cayenne/query/CacheableQuery.java    | 180 +++++++++++++++++++
 .../org/apache/cayenne/query/ColumnSelect.java  |   2 +-
 .../cayenne/query/DefaultQueryMetadata.java     |  21 +++
 .../org/apache/cayenne/query/EJBQLQuery.java    |  69 ++-----
 .../cayenne/query/EJBQLQueryMetadata.java       |  97 +++++-----
 .../org/apache/cayenne/query/FluentSelect.java  |  68 ++++---
 .../org/apache/cayenne/query/IndirectQuery.java |   1 +
 .../org/apache/cayenne/query/NamedQuery.java    |  25 +--
 .../apache/cayenne/query/ProcedureQuery.java    |  64 +------
 .../org/apache/cayenne/query/QueryChain.java    |   5 +-
 .../org/apache/cayenne/query/QueryMetadata.java |  24 ++-
 .../cayenne/query/QueryMetadataProxy.java       |  13 +-
 .../cayenne/query/QueryMetadataWrapper.java     |  30 +++-
 .../org/apache/cayenne/query/RefreshQuery.java  |  11 +-
 .../org/apache/cayenne/query/SQLSelect.java     |  93 ++++++----
 .../org/apache/cayenne/query/SQLTemplate.java   |  61 +------
 .../org/apache/cayenne/query/SelectById.java    |  68 ++++---
 .../org/apache/cayenne/query/SelectQuery.java   |  65 +------
 .../apache/cayenne/remote/IncrementalQuery.java |   2 +-
 .../cayenne/remote/IncrementalSelectQuery.java  |  31 +++-
 .../org/apache/cayenne/remote/RangeQuery.java   |  10 ++
 .../apache/cayenne/query/MockAbstractQuery.java |   5 +
 .../apache/cayenne/query/MockQueryMetadata.java |  10 ++
 .../apache/cayenne/query/ObjectSelectTest.java  |  31 ++--
 .../cayenne/query/ObjectSelect_CompileIT.java   |   9 +-
 .../org/apache/cayenne/query/SQLSelectTest.java |  44 ++---
 .../cayenne/query/SelectQueryCacheKeyIT.java    |  68 ++++---
 .../editor/EjbqlQueryPropertiesPanel.java       |   2 +-
 .../editor/ObjectQueryPropertiesPanel.java      |   4 +-
 .../modeler/editor/ProcedureQueryView.java      |   3 +-
 .../modeler/editor/RawQueryPropertiesPanel.java |   4 +-
 .../modeler/editor/SelectPropertiesPanel.java   |  75 +++-----
 49 files changed, 772 insertions(+), 633 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/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 cc8c91f..88a98ca 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
@@ -147,19 +147,9 @@ public class JCacheQueryCache implements QueryCache {
 
     protected String cacheName(QueryMetadata metadata) {
 
-        String[] cacheGroups = metadata.getCacheGroups();
-
-        if (cacheGroups != null && cacheGroups.length > 0) {
-
-            if (cacheGroups.length > 1) {
-                if (LOGGER.isWarnEnabled()) {
-                    List<String> ignored = Arrays.asList(cacheGroups).subList(1, cacheGroups.length);
-                    LOGGER.warn("multiple cache groups per key '" + metadata.getCacheKey() + "', using the first one: "
-                            + cacheGroups[0] + ". Ignoring others: " + ignored);
-                }
-            }
-
-            return cacheGroups[0];
+        String cacheGroup = metadata.getCacheGroup();
+        if (cacheGroup != null) {
+            return cacheGroup;
         }
 
         // no explicit cache groups

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java
index a066a4c..a5a37e4 100644
--- a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java
+++ b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java
@@ -160,10 +160,15 @@ public class StringIdQuery implements Query {
                 return false;
             }
 
+            @Deprecated
             public Query getOrginatingQuery() {
                 return null;
             }
 
+            public Query getOriginatingQuery() {
+                return null;
+            }
+
             public QueryCacheStrategy getCacheStrategy() {
                 return QueryCacheStrategy.getDefaultStrategy();
             }
@@ -188,10 +193,15 @@ public class StringIdQuery implements Query {
                 return null;
             }
 
+            @Deprecated
             public String[] getCacheGroups() {
                 return null;
             }
 
+            public String getCacheGroup() {
+                return null;
+            }
+
             public boolean isFetchingDataRows() {
                 // overriding this... Can't fetch objects until DataDomainQueryAction
                 // starts converting multiple ResultSets to object... Same as QueryChain

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java
index 2c73270..5b6ac20 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.project.validation;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.validation.ValidationResult;
 
@@ -30,6 +31,14 @@ import org.apache.cayenne.validation.ValidationResult;
  */
 class BaseQueryValidator extends ConfigurationNodeValidator {
 
+    void validateCacheGroup(QueryDescriptor query, ValidationResult validationResult) {
+        String cacheGroup = query.getProperty(QueryMetadata.CACHE_GROUPS_PROPERTY);
+        if(cacheGroup != null && cacheGroup.contains(",")) {
+            addFailure(validationResult, query, "Invalid cache group \"%s\", " +
+                    "multiple groups are deprecated", cacheGroup);
+        }
+    }
+
     void validateName(QueryDescriptor query, ValidationResult validationResult) {
         final String name = query.getName();
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-project/src/main/java/org/apache/cayenne/project/validation/EJBQLQueryValidator.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/EJBQLQueryValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/EJBQLQueryValidator.java
index ae250e4..80ae7be 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/EJBQLQueryValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/EJBQLQueryValidator.java
@@ -22,14 +22,15 @@ import org.apache.cayenne.project.validation.EJBQLStatementValidator.PositionExc
 import org.apache.cayenne.map.EJBQLQueryDescriptor;
 import org.apache.cayenne.validation.ValidationResult;
 
-class EJBQLQueryValidator extends ConfigurationNodeValidator {
+class EJBQLQueryValidator extends BaseQueryValidator {
 
     void validate(EJBQLQueryDescriptor query, ValidationResult validationResult) {
-
         PositionException message = new EJBQLStatementValidator().validateEJBQL(query);
         if (message != null) {
-            addFailure(validationResult, query, "Error in EJBQL query '%s' syntax", query
-                    .getName());
+            addFailure(validationResult, query, "Error in EJBQL query '%s' syntax", query.getName());
         }
+
+        validateName(query, validationResult);
+        validateCacheGroup(query, validationResult);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureQueryValidator.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureQueryValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureQueryValidator.java
index 4cac694..4a0ceef 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureQueryValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureQueryValidator.java
@@ -28,6 +28,7 @@ class ProcedureQueryValidator extends BaseQueryValidator {
     void validate(ProcedureQueryDescriptor query, ValidationResult validationResult) {
         validateName(query, validationResult);
         validateRoot(query, validationResult);
+        validateCacheGroup(query, validationResult);
     }
 
     void validateRoot(ProcedureQueryDescriptor query, ValidationResult validationResult) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SQLTemplateValidator.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SQLTemplateValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SQLTemplateValidator.java
index 80e1cbe..03bde9d 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SQLTemplateValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SQLTemplateValidator.java
@@ -32,6 +32,7 @@ class SQLTemplateValidator extends BaseQueryValidator {
         validateName(query, validationResult);
         validateRoot(query, validationResult);
         validateDefaultSQL(query, validationResult);
+        validateCacheGroup(query, validationResult);
     }
 
     void validateDefaultSQL(SQLTemplateDescriptor query, ValidationResult validationResult) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SelectQueryValidator.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SelectQueryValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SelectQueryValidator.java
index f9c0843..908fae7 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SelectQueryValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/SelectQueryValidator.java
@@ -36,6 +36,8 @@ class SelectQueryValidator extends BaseQueryValidator {
 
         validateName(query, validationResult);
 
+        validateCacheGroup(query, validationResult);
+
         // Resolve root to Entity for further validation
         Entity root = validateRoot(query, validationResult);
 
@@ -48,8 +50,7 @@ class SelectQueryValidator extends BaseQueryValidator {
             }
 
             if (query.getPrefetches() != null) {
-                for (String prefetchPath : query
-                        .getPrefetches()) {
+                for (String prefetchPath : query.getPrefetches()) {
                     validatePrefetch(root, prefetchPath, validationResult);
                 }
             }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/access/ClientServerChannelQueryAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ClientServerChannelQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ClientServerChannelQueryAction.java
index 04c55dc..0fbd203 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ClientServerChannelQueryAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ClientServerChannelQueryAction.java
@@ -83,7 +83,7 @@ class ClientServerChannelQueryAction {
             if (cachedList == null) {
 
                 // attempt to refetch... respawn the action...
-                Query originatingQuery = serverMetadata.getOrginatingQuery();
+                Query originatingQuery = serverMetadata.getOriginatingQuery();
                 if (originatingQuery != null) {
                     ClientServerChannelQueryAction subaction = new ClientServerChannelQueryAction(
                             channel,

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java
index e341cdf..5077359 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java
@@ -63,10 +63,19 @@ class DataDomainQuery implements Query, QueryMetadata {
         return false;
     }
 
+    @Deprecated
     public Query getOrginatingQuery() {
         return null;
     }
 
+    /**
+     * @since 4.0
+     */
+    public Query getOriginatingQuery() {
+        return null;
+    }
+
+    @Deprecated
     public String getName() {
         return null;
     }
@@ -74,10 +83,18 @@ class DataDomainQuery implements Query, QueryMetadata {
     public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
     }
 
+    @Deprecated
     public String[] getCacheGroups() {
         return null;
     }
 
+    /**
+     * @since 4.0
+     */
+    public String getCacheGroup() {
+        return null;
+    }
+
     public String getCacheKey() {
         return null;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java
index 5a94fcd..40277bd 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java
@@ -100,10 +100,15 @@ class ObjectsFromDataRowsQuery implements Query, QueryMetadata {
         return null;
     }
 
+    @Deprecated
     public String[] getCacheGroups() {
         return null;
     }
 
+    public String getCacheGroup() {
+        return null;
+    }
+
     public boolean isFetchingDataRows() {
         return false;
     }
@@ -124,10 +129,15 @@ class ObjectsFromDataRowsQuery implements Query, QueryMetadata {
         return 0;
     }
 
+    @Deprecated
     public Query getOrginatingQuery() {
         return null;
     }
 
+    public Query getOriginatingQuery() {
+        return null;
+    }
+
     public PrefetchTreeNode getPrefetchTree() {
         return null;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/cache/EhCacheQueryCache.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/cache/EhCacheQueryCache.java b/cayenne-server/src/main/java/org/apache/cayenne/cache/EhCacheQueryCache.java
index 4dfa1dd..732c7ec 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/cache/EhCacheQueryCache.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/cache/EhCacheQueryCache.java
@@ -19,7 +19,6 @@
 package org.apache.cayenne.cache;
 
 import java.util.List;
-import java.util.Objects;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.di.BeforeScopeEnd;
@@ -36,9 +35,9 @@ public class EhCacheQueryCache implements QueryCache {
     /**
      * Default cache group name.
      */
-    static final String DEFAULT_CACHE_NAME = "cayenne.default.cachegroup";
+    private static final String DEFAULT_CACHE_NAME = "cayenne.default.cachegroup";
 
-    private static Log logger = LogFactory.getLog(EhCacheQueryCache.class);
+    private static final Log logger = LogFactory.getLog(EhCacheQueryCache.class);
 
     protected CacheManager cacheManager;
 
@@ -72,7 +71,7 @@ public class EhCacheQueryCache implements QueryCache {
             return null;
         }
 
-        String cacheName = cacheName(key, metadata.getCacheGroups());
+        String cacheName = cacheName(metadata);
         Ehcache cache = cacheManager.getCache(cacheName);
 
         if (cache == null) {
@@ -93,7 +92,7 @@ public class EhCacheQueryCache implements QueryCache {
             return null;
         }
 
-        String cacheName = cacheName(key, metadata.getCacheGroups());
+        String cacheName = cacheName(metadata);
 
         // create empty cache for cache group here, as we have a factory to
         // create an object, and should never ever return null from this
@@ -136,15 +135,9 @@ public class EhCacheQueryCache implements QueryCache {
     /**
      * @since 4.0
      */
-	protected String cacheName(String key, String... cacheGroups) {
-		if (cacheGroups != null && cacheGroups.length > 0) {
-
-			if (cacheGroups.length > 1) {
-				logger.warn("multiple cache groups per key '" + key + "', ignoring all but the first one: "
-						+ cacheGroups[0]);
-			}
-
-			return Objects.requireNonNull(cacheGroups[0], "Null cache group");
+	protected String cacheName(QueryMetadata metadata) {
+		if (metadata.getCacheGroup() != null) {
+			return metadata.getCacheGroup();
 		}
 
 		return DEFAULT_CACHE_NAME;
@@ -154,7 +147,7 @@ public class EhCacheQueryCache implements QueryCache {
     public void put(QueryMetadata metadata, List results) {
         String key = metadata.getCacheKey();
         if (key != null) {
-            String cacheName = cacheName(key, metadata.getCacheGroups());
+            String cacheName = cacheName(metadata);
             Ehcache cache = cacheManager.addCacheIfAbsent(cacheName);
             cache.put(new Element(key, results));
         }
@@ -191,7 +184,7 @@ public class EhCacheQueryCache implements QueryCache {
      * Returns default cache group.
      * 
      * @deprecated since 4.0 - this method is no longer in use. If you are
-     *             overriding it, override {@link #cacheName(String, String...)}
+     *             overriding it, override {@link #cacheName(QueryMetadata)}
      *             instead.
      */
     @Deprecated

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/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 de74b13..6950051 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
@@ -92,7 +92,7 @@ public class MapQueryCache implements QueryCache, Serializable {
 
             CacheEntry entry = new CacheEntry();
             entry.list = results;
-            entry.cacheGroups = metadata.getCacheGroups();
+            entry.cacheGroup = metadata.getCacheGroup();
 
             synchronized (this) {
                 map.put(key, entry);
@@ -114,13 +114,10 @@ public class MapQueryCache implements QueryCache, Serializable {
                 Iterator<CacheEntry> it = map.values().iterator();
                 while (it.hasNext()) {
                     CacheEntry entry = it.next();
-                    if (entry.cacheGroups != null) {
-                        for (int i = 0; i < entry.cacheGroups.length; i++) {
-
-                            if (groupKey.equals(entry.cacheGroups[i])) {
-                                it.remove();
-                                break;
-                            }
+                    if (entry.cacheGroup != null) {
+                        if (groupKey.equals(entry.cacheGroup)) {
+                            it.remove();
+                            break;
                         }
                     }
                 }
@@ -140,6 +137,6 @@ public class MapQueryCache implements QueryCache, Serializable {
 
     final static class CacheEntry implements Serializable {
         List<?> list;
-        String[] cacheGroups;
+        String cacheGroup;
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/cache/OSQueryCache.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/cache/OSQueryCache.java b/cayenne-server/src/main/java/org/apache/cayenne/cache/OSQueryCache.java
index 50910a9..e0aa060 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/cache/OSQueryCache.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/cache/OSQueryCache.java
@@ -319,9 +319,9 @@ public class OSQueryCache implements QueryCache {
         RefreshSpecification refresh = null;
 
         if (refreshSpecifications != null) {
-            String[] groups = metadata.getCacheGroups();
-            if (groups != null && groups.length > 0) {
-                refresh = refreshSpecifications.get(groups[0]);
+            String group = metadata.getCacheGroup();
+            if (group != null) {
+                refresh = refreshSpecifications.get(group);
             }
         }
 
@@ -332,7 +332,7 @@ public class OSQueryCache implements QueryCache {
     public void put(QueryMetadata metadata, List results) {
         String key = metadata.getCacheKey();
         if (key != null) {
-            osCache.putInCache(key, results, metadata.getCacheGroups());
+            osCache.putInCache(key, results, new String[]{metadata.getCacheGroup()});
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/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 790887e..89f1040 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
@@ -23,13 +23,15 @@ import java.util.List;
 import org.apache.cayenne.query.QueryMetadata;
 
 /**
+ * <p>
  * An interface that defines generic QueryCache.
+ * </p>
  * <p>
  * Note that depending on implementation, {@link #remove(String)},
  * {@link #removeGroup(String)} and {@link #clear()} methods may mark the matching
  * existing entries as expired instead of actually removing them. So it may appear that
  * the size of the cache, as reported by {@link #size()} method, is unchanged.
- * 
+ * </p>
  * @since 3.0
  */
 public interface QueryCache {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/AbstractQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/AbstractQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/AbstractQuery.java
index c3413d0..7cab054 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/AbstractQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/AbstractQuery.java
@@ -30,7 +30,7 @@ import org.apache.cayenne.util.ToStringBuilder;
 /**
  * A common superclass of Cayenne queries.
  */
-public abstract class AbstractQuery implements Query {
+public abstract class AbstractQuery extends CacheableQuery {
 
     /**
      * The root object this query. May be an entity name, Java class, ObjEntity or
@@ -59,6 +59,7 @@ public abstract class AbstractQuery implements Query {
      * 
      * @since 1.1
      */
+    @Deprecated
     public String getName() {
         return name;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
index 9c13e44..8c523f5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
@@ -56,7 +56,11 @@ class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable
 
 	PrefetchTreeNode prefetchTree;
 	String cacheKey;
-	String[] cacheGroups;
+
+	/**
+	 * @since 4.0 cacheGroups array replaced with single cache group
+	 */
+	String cacheGroup;
 
 	transient List<Object> resultSetMapping;
 	transient DbEntity dbEntity;
@@ -80,7 +84,7 @@ class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable
 		this.pageSize = info.getPageSize();
 		this.cacheStrategy = info.getCacheStrategy();
 		this.cacheKey = info.getCacheKey();
-		this.cacheGroups = info.getCacheGroups();
+		this.cacheGroup = info.getCacheGroup();
 		this.resultSetMapping = info.getResultSetMapping();
 
 		setPrefetchTree(info.getPrefetchTree());
@@ -189,15 +193,18 @@ class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable
 		this.cacheStrategy = (cacheStrategy != null) ? QueryCacheStrategy.safeValueOf(cacheStrategy.toString())
 				: QueryCacheStrategy.getDefaultStrategy();
 
-		this.cacheGroups = null;
-		if (cacheGroups instanceof String[]) {
-			this.cacheGroups = (String[]) cacheGroups;
-		} else if (cacheGroups instanceof String) {
-			StringTokenizer toks = new StringTokenizer(cacheGroups.toString(), ",");
-			this.cacheGroups = new String[toks.countTokens()];
-			for (int i = 0; i < this.cacheGroups.length; i++) {
-				this.cacheGroups[i] = toks.nextToken();
+		this.cacheGroup = null;
+		if(cacheGroups instanceof String) {
+			if(((String) cacheGroups).contains(",")) {
+				StringTokenizer toks = new StringTokenizer(cacheGroups.toString(), ",");
+				if(toks.countTokens() > 0) {
+					this.cacheGroup = toks.nextToken();
+				}
+			} else {
+				this.cacheGroup = (String) cacheGroups;
 			}
+		} else if (cacheGroups instanceof String[]) {
+			this.cacheGroup = ((String[]) cacheGroups)[0];
 		}
 	}
 
@@ -231,12 +238,8 @@ class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable
 			prefetchTree.encodeAsXML(encoder);
 		}
 
-		if (cacheGroups != null && cacheGroups.length > 0) {
-			StringBuilder buffer = new StringBuilder(cacheGroups[0]);
-			for (int i = 1; i < cacheGroups.length; i++) {
-				buffer.append(',').append(cacheGroups[i]);
-			}
-			encoder.printProperty(QueryMetadata.CACHE_GROUPS_PROPERTY, buffer.toString());
+		if (cacheGroup != null) {
+			encoder.printProperty(QueryMetadata.CACHE_GROUPS_PROPERTY, cacheGroup);
 		}
 	}
 
@@ -346,16 +349,40 @@ class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable
 
 	/**
 	 * @since 3.0
+	 * @deprecated since 4.0, use {@link BaseQueryMetadata#getCacheGroup()}
 	 */
+	@Deprecated
 	public String[] getCacheGroups() {
-		return cacheGroups;
+		if(cacheGroup == null) {
+			return null;
+		}
+		return new String[]{cacheGroup};
 	}
 
 	/**
 	 * @since 3.0
+	 * @deprecated since 4.0, use {@link BaseQueryMetadata#setCacheGroup(String)}
 	 */
+	@Deprecated
 	void setCacheGroups(String... groups) {
-		this.cacheGroups = groups;
+		if(groups.length > 0) {
+			this.cacheGroup = groups[0];
+		}
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public String getCacheGroup() {
+		return cacheGroup;
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public void setCacheGroup(String group) {
+		this.cacheGroup = group;
 	}
 
 	public boolean isFetchingDataRows() {
@@ -370,11 +397,22 @@ class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable
 		return pageSize;
 	}
 
+	/**
+	 * @deprecated since 4.0, use {@link BaseQueryMetadata#getOriginatingQuery()}
+	 */
+	@Deprecated
 	public Query getOrginatingQuery() {
 		return null;
 	}
 
 	/**
+	 * @since 4.0
+	 */
+	public Query getOriginatingQuery() {
+		return null;
+	}
+
+	/**
 	 * @since 3.0
 	 */
 	public int getFetchOffset() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/BatchQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/BatchQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/BatchQuery.java
index 9ba1af5..7042039 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/BatchQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/BatchQuery.java
@@ -72,6 +72,7 @@ public abstract class BatchQuery implements Query {
     }
 
     @Override
+    @Deprecated
     public String getName() {
         return name;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/CacheableQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/CacheableQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/CacheableQuery.java
new file mode 100644
index 0000000..b10e1bc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/CacheableQuery.java
@@ -0,0 +1,180 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.query;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Shared functionality for cacheable queries.
+ *
+ * @since 4.0
+ */
+public abstract class CacheableQuery implements Query {
+
+    protected static final Log logger = LogFactory.getLog(SelectQuery.class);
+
+    abstract protected BaseQueryMetadata getBaseMetaData();
+
+    /**
+     * @since 3.0
+     */
+    public QueryCacheStrategy getCacheStrategy() {
+        return getBaseMetaData().getCacheStrategy();
+    }
+
+    /**
+     * @since 3.0
+     */
+    public void setCacheStrategy(QueryCacheStrategy strategy) {
+        getBaseMetaData().setCacheStrategy(strategy);
+    }
+
+    /**
+     * @since 3.0
+     * @deprecated since 4.0, use {@link CacheableQuery#getCacheGroup()}
+     */
+    @Deprecated
+    public String[] getCacheGroups() {
+        return getBaseMetaData().getCacheGroups();
+    }
+
+    /**
+     * @since 4.0
+     */
+    public String getCacheGroup() {
+        return getBaseMetaData().getCacheGroup();
+    }
+
+    /**
+     * @since 3.0
+     * @deprecated since 4.0, use {@link CacheableQuery#setCacheGroup(String)}
+     */
+    @Deprecated
+    public void setCacheGroups(String... cacheGroups) {
+        getBaseMetaData().setCacheGroups(cacheGroups);
+        if(cacheGroups.length > 1) {
+            logger.warn("Multiple cache groups usage have been deprecated, only first one will be used.");
+        }
+    }
+
+    /**
+     * @since 4.0
+     */
+    public void setCacheGroup(String cacheGroup) {
+        getBaseMetaData().setCacheGroup(cacheGroup);
+    }
+
+
+    /**
+     * Instructs Cayenne to look for query results in the "local" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+     * </pre>
+     *
+     * @since 4.0
+     */
+    public void useLocalCache() {
+        setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "local" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+     * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
+     * </pre>
+     *
+     * @since 4.0
+     */
+    public void useLocalCache(String cacheGroup) {
+        setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+        setCacheGroup(cacheGroup);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "local" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+     * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
+     * </pre>
+     * @deprecated since 4.0, use {@link CacheableQuery#useLocalCache(String)}
+     * @since 4.0
+     */
+    @Deprecated
+    public void useLocalCache(String... cacheGroups) {
+        setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+        setCacheGroups(cacheGroups);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "shared" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+     * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
+     * </pre>
+     * @deprecated since 4.0, use {@link CacheableQuery#useSharedCache(String)}
+     * @since 4.0
+     */
+    @Deprecated
+    public void useSharedCache(String... cacheGroups) {
+        setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+        setCacheGroups(cacheGroups);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "shared" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+     * </pre>
+     *
+     * @since 4.0
+     */
+    public void useSharedCache() {
+        setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "shared" cache when
+     * running the query. This is a short-hand notation for:
+     *
+     * <pre>
+     * query.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+     * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
+     * </pre>
+     *
+     * @since 4.0
+     */
+    public void useSharedCache(String cacheGroup) {
+        setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+        setCacheGroup(cacheGroup);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
index 85c774b..69494b8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
@@ -84,7 +84,7 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
         this.pageSize = select.pageSize;
         this.statementFetchSize = select.statementFetchSize;
         this.cacheStrategy = select.cacheStrategy;
-        this.cacheGroups = select.cacheGroups;
+        this.cacheGroup = select.cacheGroup;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java
index f399023..10dff3c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java
@@ -75,12 +75,21 @@ class DefaultQueryMetadata implements QueryMetadata {
 
     /**
      * @since 3.0
+     * @deprecated use {@link DefaultQueryMetadata#getOriginatingQuery()}
      */
+    @Deprecated
     public Query getOrginatingQuery() {
         return null;
     }
 
     /**
+     * @since 4.0
+     */
+    public Query getOriginatingQuery() {
+        return null;
+    }
+
+    /**
      * @since 3.0
      */
     public QueryCacheStrategy getCacheStrategy() {
@@ -107,10 +116,22 @@ class DefaultQueryMetadata implements QueryMetadata {
         return null;
     }
 
+    /**
+     * @deprecated since 4.0, use {@link DefaultQueryMetadata#getCacheGroup()}
+     */
+    @Deprecated
     public String[] getCacheGroups() {
         return null;
     }
 
+    /**
+     * @since 4.0
+     */
+    @Override
+    public String getCacheGroup() {
+        return null;
+    }
+
     public boolean isFetchingDataRows() {
         return QueryMetadata.FETCHING_DATA_ROWS_DEFAULT;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
index 6e5d708..e778096 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
@@ -36,7 +36,7 @@ import java.util.Map;
  * 
  * @since 3.0
  */
-public class EJBQLQuery implements Query, XMLSerializable {
+public class EJBQLQuery extends CacheableQuery implements XMLSerializable {
 
     protected String name;
     protected DataMap dataMap;
@@ -92,52 +92,9 @@ public class EJBQLQuery implements Query, XMLSerializable {
         metadata.setFetchingDataRows(flag);
     }
 
-    public String[] getCacheGroups() {
-        return metadata.getCacheGroups();
-    }
-
-    public QueryCacheStrategy getCacheStrategy() {
-        return metadata.getCacheStrategy();
-    }
-
-    public void setCacheGroups(String... cacheGroups) {
-        this.metadata.setCacheGroups(cacheGroups);
-    }
-
-    public void setCacheStrategy(QueryCacheStrategy strategy) {
-        metadata.setCacheStrategy(strategy);
-    }
-    
-    /**
-     * Instructs Cayenne to look for query results in the "local" cache when
-     * running the query. This is a short-hand notation for:
-     * 
-     * <pre>
-     * query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-     * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
-     * </pre>
-     * 
-     * @since 4.0
-     */
-    public void useLocalCache(String... cacheGroups) {
-        setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-        setCacheGroups(cacheGroups);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "shared" cache when
-     * running the query. This is a short-hand notation for:
-     * 
-     * <pre>
-     * query.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-     * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
-     * </pre>
-     * 
-     * @since 4.0
-     */
-    public void useSharedCache(String... cacheGroups) {
-        setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-        setCacheGroups(cacheGroups);
+    @Override
+    protected BaseQueryMetadata getBaseMetaData() {
+        return metadata;
     }
 
     public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
@@ -176,6 +133,7 @@ public class EJBQLQuery implements Query, XMLSerializable {
         return expression;
     }
 
+    @Deprecated
     public String getName() {
         return name;
     }
@@ -196,8 +154,9 @@ public class EJBQLQuery implements Query, XMLSerializable {
     }
 
     public Map<Integer, Object> getPositionalParameters() {
-        return positionalParameters != null ? Collections
-                .unmodifiableMap(positionalParameters) : Collections.<Integer, Object>emptyMap();
+        return positionalParameters != null
+                ? Collections.unmodifiableMap(positionalParameters)
+                : Collections.<Integer, Object>emptyMap();
     }
 
     /**
@@ -221,24 +180,20 @@ public class EJBQLQuery implements Query, XMLSerializable {
     }
 
     /**
-     * Sets a positional query parameter value. Note that parameter indexes are starting
-     * from 1.
+     * Sets a positional query parameter value. Note that parameter indexes are starting from 1.
      */
     public void setParameter(int position, Object object) {
 
         if (position < 1) {
-            throw new IllegalArgumentException("Parameter position must be >= 1: "
-                    + position);
+            throw new IllegalArgumentException("Parameter position must be >= 1: " + position);
         }
 
-        // TODO: andrus, 6/12/2007 - validate against available query parameters - JPA
-        // spec requires it.
-
+        // TODO: andrus, 6/12/2007 - validate against available query parameters - JPA spec requires it.
         if (positionalParameters == null) {
             positionalParameters = new HashMap<>();
         }
 
-        positionalParameters.put(Integer.valueOf(position), object);
+        positionalParameters.put(position, object);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java
index bbc5756..620f8e7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java
@@ -37,70 +37,63 @@ class EJBQLQueryMetadata extends BaseQueryMetadata {
     boolean resolve(EntityResolver resolver, EJBQLQuery query) {
         EJBQLCompiledExpression expression = query.getExpression(resolver);
         setPrefetchTree(expression.getPrefetchTree());
-        resultSetMapping = expression.getResult() != null ? expression
-                .getResult()
-                .getResolvedComponents(resolver) : null;
+        resultSetMapping = expression.getResult() != null
+                ? expression.getResult().getResolvedComponents(resolver)
+                : null;
 
         ObjEntity root = expression.getRootDescriptor().getEntity();
 
-        if (super.resolve(root, resolver, null)) {
-
-            if (QueryCacheStrategy.NO_CACHE == getCacheStrategy()) {
-
-            }
-            else {
-
-                // create a unique key based on entity, EJBQL, and parameters
-                StringBuilder key = new StringBuilder();
-
-                if (query.getEjbqlStatement() != null) {
-                    key.append('/').append(query.getEjbqlStatement());
-                }
+        if (!super.resolve(root, resolver, null)) {
+            return false;
+        }
 
-                if (query.getFetchLimit() > 0) {
-                    key.append('/').append(query.getFetchLimit());
-                }
+        if (QueryCacheStrategy.NO_CACHE == getCacheStrategy()) {
+            return true;
+        }
 
-                Map<String, Object> namedParameters = query.getNamedParameters();
-                if (!namedParameters.isEmpty()) {
+        // create a unique key based on entity, EJBQL, and parameters
+        StringBuilder key = new StringBuilder();
 
-                    List<String> keys = new ArrayList<>(namedParameters.keySet());
-                    Collections.sort(keys);
-                    for (String parameterKey : keys) {
-                        key.append('/').append(parameterKey).append('=').append(
-                                namedParameters.get(parameterKey));
-                    }
-                }
+        if (query.getEjbqlStatement() != null) {
+            key.append('/').append(query.getEjbqlStatement());
+        }
 
-                Map<Integer, Object> positionalParameters = query
-                        .getPositionalParameters();
-                if (!positionalParameters.isEmpty()) {
+        if (query.getFetchLimit() > 0) {
+            key.append('/').append(query.getFetchLimit());
+        }
 
-                    List<Integer> keys = new ArrayList<>(positionalParameters
-                            .keySet());
-                    Collections.sort(keys);
-                    for (Integer parameterKey : keys) {
-                        key.append('/').append(parameterKey).append('=').append(
-                                positionalParameters.get(parameterKey));
-                    }
-                }
-                
-                if (query.getFetchOffset() > 0 || query.getFetchLimit() > 0) {
-                    key.append('/');
-                    if (query.getFetchOffset() > 0) {
-                        key.append('o').append(query.getFetchOffset());
-                    }
-                    if (query.getFetchLimit() > 0) {
-                        key.append('l').append(query.getFetchLimit());
-                    }
-                }
+        Map<String, Object> namedParameters = query.getNamedParameters();
+        if (!namedParameters.isEmpty()) {
+            List<String> keys = new ArrayList<>(namedParameters.keySet());
+            Collections.sort(keys);
+            for (String parameterKey : keys) {
+                key.append('/').append(parameterKey).append('=').append(
+                        namedParameters.get(parameterKey));
+            }
+        }
 
-                this.cacheKey = key.toString();
+        Map<Integer, Object> positionalParameters = query.getPositionalParameters();
+        if (!positionalParameters.isEmpty()) {
+            List<Integer> keys = new ArrayList<>(positionalParameters.keySet());
+            Collections.sort(keys);
+            for (Integer parameterKey : keys) {
+                key.append('/').append(parameterKey).append('=').append(
+                        positionalParameters.get(parameterKey));
             }
+        }
 
-            return true;
+        if (query.getFetchOffset() > 0 || query.getFetchLimit() > 0) {
+            key.append('/');
+            if (query.getFetchOffset() > 0) {
+                key.append('o').append(query.getFetchOffset());
+            }
+            if (query.getFetchLimit() > 0) {
+                key.append('l').append(query.getFetchLimit());
+            }
         }
 
-        return false;
+        this.cacheKey = key.toString();
+
+        return true;
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java
index 363f8b1..7f3303a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java
@@ -55,7 +55,7 @@ public abstract class FluentSelect<T, S extends FluentSelect<T, S>> extends Indi
     protected int pageSize;
     protected int statementFetchSize;
     protected QueryCacheStrategy cacheStrategy;
-    protected String[] cacheGroups;
+    protected String cacheGroup;
 
     protected FluentSelect() {
     }
@@ -97,7 +97,7 @@ public abstract class FluentSelect<T, S extends FluentSelect<T, S>> extends Indi
         replacement.addOrderings(orderings);
         replacement.setPrefetchTree(prefetches);
         replacement.setCacheStrategy(cacheStrategy);
-        replacement.setCacheGroups(cacheGroups);
+        replacement.setCacheGroup(cacheGroup);
         replacement.setFetchLimit(limit);
         replacement.setFetchOffset(offset);
         replacement.setPageSize(pageSize);
@@ -411,30 +411,42 @@ public abstract class FluentSelect<T, S extends FluentSelect<T, S>> extends Indi
         return (S)this;
     }
 
-    public S cacheStrategy(QueryCacheStrategy strategy, String... cacheGroups) {
+    @SuppressWarnings("unchecked")
+    public S cacheStrategy(QueryCacheStrategy strategy) {
         if (this.cacheStrategy != strategy) {
             this.cacheStrategy = strategy;
             this.replacementQuery = null;
         }
 
-        return cacheGroups(cacheGroups);
+        if(this.cacheGroup != null) {
+            this.cacheGroup = null;
+            this.replacementQuery = null;
+        }
+
+        return (S)this;
+    }
+
+    public S cacheStrategy(QueryCacheStrategy strategy, String cacheGroup) {
+        return cacheStrategy(strategy).cacheGroup(cacheGroup);
     }
 
     @SuppressWarnings("unchecked")
-    public S cacheGroups(String... cacheGroups) {
-        this.cacheGroups = cacheGroups != null && cacheGroups.length > 0 ? cacheGroups : null;
+    public S cacheGroup(String cacheGroup) {
+        this.cacheGroup = cacheGroup;
         this.replacementQuery = null;
         return (S)this;
     }
 
-    public S cacheGroups(Collection<String> cacheGroups) {
-
-        if (cacheGroups == null) {
-            return cacheGroups((String) null);
-        }
-
-        String[] array = new String[cacheGroups.size()];
-        return cacheGroups(cacheGroups.toArray(array));
+    /**
+     * Instructs Cayenne to look for query results in the "local" cache when
+     * running the query. This is a short-hand notation for:
+     * <p>
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
+     * </pre>
+     */
+    public S localCache(String cacheGroup) {
+        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
     }
 
     /**
@@ -442,11 +454,23 @@ public abstract class FluentSelect<T, S extends FluentSelect<T, S>> extends Indi
      * running the query. This is a short-hand notation for:
      * <p>
      * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+     * </pre>
+     */
+    public S localCache() {
+        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "shared" cache when
+     * running the query. This is a short-hand notation for:
+     * <p>
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
      * </pre>
      */
-    public S localCache(String... cacheGroups) {
-        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+    public S sharedCache(String cacheGroup) {
+        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
     }
 
     /**
@@ -454,15 +478,15 @@ public abstract class FluentSelect<T, S extends FluentSelect<T, S>> extends Indi
      * running the query. This is a short-hand notation for:
      * <p>
      * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
      * </pre>
      */
-    public S sharedCache(String... cacheGroups) {
-        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+    public S sharedCache() {
+        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
     }
 
-    public String[] getCacheGroups() {
-        return cacheGroups;
+    public String getCacheGroup() {
+        return cacheGroup;
     }
 
     public QueryCacheStrategy getCacheStrategy() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/IndirectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/IndirectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/IndirectQuery.java
index 8bcf52b..fa7a305 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/IndirectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/IndirectQuery.java
@@ -67,6 +67,7 @@ public abstract class IndirectQuery implements Query {
 	}
 
 	@Override
+	@Deprecated
 	public String getName() {
 		return name;
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/NamedQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/NamedQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/NamedQuery.java
index 5ebdf18..3ae8cca 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/NamedQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/NamedQuery.java
@@ -82,24 +82,16 @@ public class NamedQuery extends IndirectQuery {
     @Override
     public QueryMetadata getMetaData(EntityResolver resolver) {
 
-        QueryMetadata base = overrideMetadata != null ? overrideMetadata : super
-                .getMetaData(resolver);
-
+        QueryMetadata base = overrideMetadata != null ? overrideMetadata : super.getMetaData(resolver);
         QueryMetadataWrapper wrapper = new QueryMetadataWrapper(base);
 
         // override cache policy, forcing refresh if needed
         if (forceNoCache) {
             QueryCacheStrategy strategy = base.getCacheStrategy();
-
             if (QueryCacheStrategy.LOCAL_CACHE == strategy) {
-                wrapper.override(
-                        QueryMetadata.CACHE_STRATEGY_PROPERTY,
-                        QueryCacheStrategy.LOCAL_CACHE_REFRESH);
-            }
-            else if (QueryCacheStrategy.SHARED_CACHE == strategy) {
-                wrapper.override(
-                        QueryMetadata.CACHE_STRATEGY_PROPERTY,
-                        QueryCacheStrategy.SHARED_CACHE_REFRESH);
+                wrapper.override(QueryMetadata.CACHE_STRATEGY_PROPERTY, QueryCacheStrategy.LOCAL_CACHE_REFRESH);
+            } else if (QueryCacheStrategy.SHARED_CACHE == strategy) {
+                wrapper.override(QueryMetadata.CACHE_STRATEGY_PROPERTY, QueryCacheStrategy.SHARED_CACHE_REFRESH);
             }
         }
 
@@ -131,11 +123,8 @@ public class NamedQuery extends IndirectQuery {
         if (query instanceof ParameterizedQuery) {
             query = ((ParameterizedQuery) query).createQuery(normalizedParameters());
         } else if (query instanceof EJBQLQuery) {
-            
-            Iterator it = normalizedParameters().entrySet().iterator();
-            while (it.hasNext()) {
-                Map.Entry pairs = (Map.Entry)it.next();
-                ((EJBQLQuery)query).setParameter((String) pairs.getKey(), pairs.getValue());
+            for (Map.Entry<String, ?> pairs : normalizedParameters().entrySet()) {
+                ((EJBQLQuery) query).setParameter(pairs.getKey(), pairs.getValue());
             }
         }
 
@@ -149,7 +138,7 @@ public class NamedQuery extends IndirectQuery {
      */
     Map<String, ?> normalizedParameters() {
         if (parameters == null || parameters.isEmpty()) {
-            return Collections.EMPTY_MAP;
+            return Collections.emptyMap();
         }
 
         Map<String, Object> substitutes = new HashMap<>(parameters);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
index 36f1f80..1a4738f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
@@ -53,8 +53,7 @@ import java.util.Map;
  * {@link org.apache.cayenne.access.DataContext#performGenericQuery(Query)}.
  * </p>
  */
-public class ProcedureQuery extends AbstractQuery implements ParameterizedQuery,
-        XMLSerializable {
+public class ProcedureQuery extends AbstractQuery implements ParameterizedQuery, XMLSerializable {
 
     public static final String COLUMN_NAME_CAPITALIZATION_PROPERTY = "cayenne.ProcedureQuery.columnNameCapitalization";
 
@@ -297,64 +296,9 @@ public class ProcedureQuery extends AbstractQuery implements ParameterizedQuery,
         return query;
     }
 
-    /**
-     * @since 3.0
-     */
-    public QueryCacheStrategy getCacheStrategy() {
-        return metaData.getCacheStrategy();
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setCacheStrategy(QueryCacheStrategy strategy) {
-        metaData.setCacheStrategy(strategy);
-    }
-
-    /**
-     * @since 3.0
-     */
-    public String[] getCacheGroups() {
-        return metaData.getCacheGroups();
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setCacheGroups(String... cacheGroups) {
-        this.metaData.setCacheGroups(cacheGroups);
-    }
-    
-    /**
-     * Instructs Cayenne to look for query results in the "local" cache when
-     * running the query. This is a short-hand notation for:
-     * 
-     * <pre>
-     * query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-     * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
-     * </pre>
-     * 
-     * @since 4.0
-     */
-    public void useLocalCache(String... cacheGroups) {
-        setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-        setCacheGroups(cacheGroups);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "shared" cache when
-     * running the query. This is a short-hand notation for:
-     * 
-     * <pre>
-     * query.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-     * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
-     * </pre>
-     * 
-     * @since 4.0
-     */
-    public void useSharedCache(String... cacheGroups) {
-        setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-        setCacheGroups(cacheGroups);
+    @Override
+    protected BaseQueryMetadata getBaseMetaData() {
+        return metaData;
     }
 
     public int getFetchLimit() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/QueryChain.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryChain.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryChain.java
index 5e00431..f6082ec 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryChain.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryChain.java
@@ -84,7 +84,7 @@ public class QueryChain implements Query {
      * the chain and was removed.
      */
     public boolean removeQuery(Query query) {
-        return (chain != null) ? chain.remove(query) : false;
+        return (chain != null) && chain.remove(query);
     }
 
     public boolean isEmpty() {
@@ -138,8 +138,7 @@ public class QueryChain implements Query {
      * Returns default metadata.
      */
     public QueryMetadata getMetaData(EntityResolver resolver) {
-        QueryMetadataWrapper wrapper = new QueryMetadataWrapper(
-                DefaultQueryMetadata.defaultMetadata);
+        QueryMetadataWrapper wrapper = new QueryMetadataWrapper(DefaultQueryMetadata.defaultMetadata);
         wrapper.override(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY, Boolean.TRUE);
         return wrapper;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
index a1dc464..15a3057 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
@@ -149,10 +149,21 @@ public interface QueryMetadata {
      * with some cache providers.
      * 
      * @since 3.0
+     * @deprecated since 4.0 only single cache group supported, use {@link QueryMetadata#getCacheGroup()} instead
+     * @see QueryMetadata#getCacheGroup()
      */
+    @Deprecated
     String[] getCacheGroups();
 
     /**
+     * Returns an optional cache "group".
+     * Cache groups allow to invalidate query caches in bulk on different events.
+     *
+     * @since 4.0
+     */
+    String getCacheGroup();
+
+    /**
      * Returns <code>true</code> if this query should produce a list of data rows as
      * opposed to DataObjects, <code>false</code> for DataObjects. This is a hint to
      * QueryEngine executing this query.
@@ -194,16 +205,23 @@ public interface QueryMetadata {
     int getFetchLimit();
 
     /**
+     * @since 3.0
+     * @deprecated since 4.0, use {@link QueryMetadata#getOriginatingQuery()}
+     */
+    @Deprecated
+    Query getOrginatingQuery();
+
+    /**
      * Returns a query that originated this query. Originating query is a query whose
      * result is needed to obtain the result of the query owning this metadata. Most often
      * than not the returned value is null. One example of non-null originating query is a
      * query for a range of objects in a previously fetched paginated list. The query that
      * fetched the original paginated list is an "originated" query. It may be used to
      * restore a list that got lost due to a cache overflow, etc.
-     * 
-     * @since 3.0
+     *
+     * @since 4.0
      */
-    Query getOrginatingQuery();
+    Query getOriginatingQuery();
 
     /**
      * Returns a root node of prefetch tree used by this query, or null of no prefetches

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java
index 2d8070c..53ec1bf 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java
@@ -38,11 +38,16 @@ public class QueryMetadataProxy implements QueryMetadata {
         this.mdDelegate = mdDelegate;
     }
 
+    @Deprecated
     @Override
     public String[] getCacheGroups() {
         return mdDelegate.getCacheGroups();
     }
 
+    public String getCacheGroup() {
+        return mdDelegate.getCacheGroup();
+    }
+
     @Override
     public String getCacheKey() {
         return mdDelegate.getCacheKey();
@@ -83,9 +88,15 @@ public class QueryMetadataProxy implements QueryMetadata {
         return mdDelegate.getObjEntity();
     }
 
+    @Deprecated
     @Override
     public Query getOrginatingQuery() {
-        return mdDelegate.getOrginatingQuery();
+        return mdDelegate.getOriginatingQuery();
+    }
+
+    @Override
+    public Query getOriginatingQuery() {
+        return mdDelegate.getOriginatingQuery();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java
index aab9d55..f14223c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java
@@ -55,8 +55,9 @@ class QueryMetadataWrapper extends QueryMetadataProxy {
     }
 
     public String getCacheKey() {
-        return (overrideExists(CACHE_KEY_PROPERTY)) ? (String) overrides
-                .get(CACHE_KEY_PROPERTY) : super.getCacheKey();
+        return (overrideExists(CACHE_KEY_PROPERTY))
+                ? (String) overrides.get(CACHE_KEY_PROPERTY)
+                : super.getCacheKey();
     }
 
     /**
@@ -64,24 +65,43 @@ class QueryMetadataWrapper extends QueryMetadataProxy {
      */
     public QueryCacheStrategy getCacheStrategy() {
         return (overrideExists(QueryMetadata.CACHE_STRATEGY_PROPERTY))
-                ? (QueryCacheStrategy) overrides
-                        .get(QueryMetadata.CACHE_STRATEGY_PROPERTY)
+                ? (QueryCacheStrategy) overrides.get(QueryMetadata.CACHE_STRATEGY_PROPERTY)
                 : super.getCacheStrategy();
     }
 
+    /**
+     * @deprecated since 4.0, use {@link QueryMetadataWrapper#getCacheKey()}
+     */
+    @Deprecated
     public String[] getCacheGroups() {
         return (overrideExists(QueryMetadata.CACHE_GROUPS_PROPERTY))
                 ? (String[]) overrides.get(QueryMetadata.CACHE_GROUPS_PROPERTY)
                 : super.getCacheGroups();
     }
 
+    /**
+     * @since 4.0
+     */
+    public String getCacheGroup() {
+        if(overrideExists(QueryMetadata.CACHE_GROUPS_PROPERTY)) {
+            String[] cacheGroups = (String[]) overrides.get(QueryMetadata.CACHE_GROUPS_PROPERTY);
+            if(cacheGroups == null || cacheGroups.length == 0) {
+                return null;
+            } else {
+                return cacheGroups[0];
+            }
+        }
+
+        return super.getCacheGroup();
+    }
+
     public boolean isFetchingDataRows() {
         if (!overrideExists(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY)) {
             return super.isFetchingDataRows();
         }
 
         Boolean b = (Boolean) overrides.get(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY);
-        return b != null && b.booleanValue();
+        return b != null && b;
     }
 
     public boolean isRefreshingObjects() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/RefreshQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/RefreshQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/RefreshQuery.java
index 6c35fa6..c2935ae 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/RefreshQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/RefreshQuery.java
@@ -127,14 +127,9 @@ public class RefreshQuery implements Query {
 
                 QueryMetadataWrapper wrappedMd = new QueryMetadataWrapper(md);
                 if (QueryCacheStrategy.LOCAL_CACHE == md.getCacheStrategy()) {
-                    wrappedMd.override(
-                            QueryMetadata.CACHE_STRATEGY_PROPERTY,
-                            QueryCacheStrategy.LOCAL_CACHE_REFRESH);
-                }
-                else if (QueryCacheStrategy.SHARED_CACHE == md.getCacheStrategy()) {
-                    wrappedMd.override(
-                            QueryMetadata.CACHE_STRATEGY_PROPERTY,
-                            QueryCacheStrategy.SHARED_CACHE_REFRESH);
+                    wrappedMd.override(QueryMetadata.CACHE_STRATEGY_PROPERTY, QueryCacheStrategy.LOCAL_CACHE_REFRESH);
+                } else if (QueryCacheStrategy.SHARED_CACHE == md.getCacheStrategy()) {
+                    wrappedMd.override(QueryMetadata.CACHE_STRATEGY_PROPERTY, QueryCacheStrategy.SHARED_CACHE_REFRESH);
                 }
 
                 return wrappedMd;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java
index 55d653d..d9de26e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java
@@ -48,8 +48,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	 * Creates a query that selects DataRows and uses default routing.
 	 */
 	public static SQLSelect<DataRow> dataRowQuery(String sql) {
-		SQLSelect<DataRow> query = new SQLSelect<DataRow>(sql);
-		return query;
+		return new SQLSelect<>(sql);
 	}
 
 	/**
@@ -57,7 +56,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	 * provided DataMap name.
 	 */
 	public static SQLSelect<DataRow> dataRowQuery(String dataMapName, String sql) {
-		SQLSelect<DataRow> query = new SQLSelect<DataRow>(sql);
+		SQLSelect<DataRow> query = new SQLSelect<>(sql);
 		query.dataMapName = dataMapName;
 		return query;
 	}
@@ -66,14 +65,14 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	 * Creates a query that selects DataObjects.
 	 */
 	public static <T> SQLSelect<T> query(Class<T> type, String sql) {
-		return new SQLSelect<T>(type, sql);
+		return new SQLSelect<>(type, sql);
 	}
 
 	/**
 	 * Creates a query that selects scalar values and uses default routing.
 	 */
 	public static <T> SQLSelect<T> scalarQuery(Class<T> type, String sql) {
-		SQLSelect<T> query = new SQLSelect<T>(sql);
+		SQLSelect<T> query = new SQLSelect<>(sql);
 		query.scalarType = type;
 		return query;
 	}
@@ -83,7 +82,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	 * provided DataMap name.
 	 */
 	public static <T> SQLSelect<T> scalarQuery(Class<T> type, String dataMapName, String sql) {
-		SQLSelect<T> query = new SQLSelect<T>(sql);
+		SQLSelect<T> query = new SQLSelect<>(sql);
 		query.dataMapName = dataMapName;
 		query.scalarType = type;
 		return query;
@@ -94,7 +93,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	protected String dataMapName;
 	protected StringBuilder sqlBuffer;
 	protected QueryCacheStrategy cacheStrategy;
-	protected String[] cacheGroups;
+	protected String cacheGroup;
 	protected Map<String, Object> params;
 	protected List<Object> positionalParams;
 	protected CapsStrategy columnNameCaps;
@@ -132,7 +131,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 
 	@Override
 	public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
-		context.iterate((Select<T>) this, callback);
+		context.iterate(this, callback);
 	}
 
 	@Override
@@ -178,8 +177,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 		if (this.params == null) {
 			this.params = new HashMap<>(parameters);
 		} else {
-			Map bareMap = parameters;
-			this.params.putAll(bareMap);
+			this.params.putAll(parameters);
 		}
 
 		this.replacementQuery = null;
@@ -253,7 +251,6 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 			if (map == null) {
 				throw new CayenneRuntimeException("Invalid dataMapName '%s'", dataMapName);
 			}
-
 			root = map;
 		} else {
 			// will route via default node. TODO: allow explicit node name?
@@ -264,7 +261,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 		template.setFetchingDataRows(isFetchingDataRows());
 		template.setRoot(root);
 		template.setDefaultTemplate(getSql());
-		template.setCacheGroups(cacheGroups);
+		template.setCacheGroup(cacheGroup);
 		template.setCacheStrategy(cacheStrategy);
 
 		if (positionalParams != null) {
@@ -293,11 +290,23 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	 * running the query. This is a short-hand notation for:
 	 * 
 	 * <pre>
-	 * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+	 * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+	 * </pre>
+	 */
+	public SQLSelect<T> localCache() {
+		return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+	}
+
+	/**
+	 * Instructs Cayenne to look for query results in the "local" cache when
+	 * running the query. This is a short-hand notation for:
+	 *
+	 * <pre>
+	 * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
 	 * </pre>
 	 */
-	public SQLSelect<T> localCache(String... cacheGroups) {
-		return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+	public SQLSelect<T> localCache(String cacheGroup) {
+		return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
 	}
 
 	/**
@@ -305,46 +314,56 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	 * running the query. This is a short-hand notation for:
 	 * 
 	 * <pre>
-	 * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+	 * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+	 * </pre>
+	 */
+	public SQLSelect<T> sharedCache() {
+		return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+	}
+
+	/**
+	 * Instructs Cayenne to look for query results in the "shared" cache when
+	 * running the query. This is a short-hand notation for:
+	 *
+	 * <pre>
+	 * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
 	 * </pre>
 	 */
-	public SQLSelect<T> sharedCache(String... cacheGroups) {
-		return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+	public SQLSelect<T> sharedCache(String cacheGroup) {
+		return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
 	}
 
 	public QueryCacheStrategy getCacheStrategy() {
 		return cacheStrategy;
 	}
 
-	public SQLSelect<T> cacheStrategy(QueryCacheStrategy strategy, String... cacheGroups) {
-		if (this.cacheStrategy != strategy) {
-			this.cacheStrategy = strategy;
-			this.replacementQuery = null;
+	public SQLSelect<T> cacheStrategy(QueryCacheStrategy strategy) {
+		if(cacheStrategy != strategy) {
+			cacheStrategy = strategy;
+			replacementQuery = null;
+		}
+		if(cacheGroup != null) {
+			cacheGroup = null;
+			replacementQuery = null;
 		}
 
-		return cacheGroups(cacheGroups);
+		return this;
+	}
+
+	public SQLSelect<T> cacheStrategy(QueryCacheStrategy strategy, String cacheGroup) {
+		return cacheStrategy(strategy).cacheGroup(cacheGroup);
 	}
 
-	public String[] getCacheGroups() {
-		return cacheGroups;
+	public String getCacheGroup() {
+		return cacheGroup;
 	}
 
-	public SQLSelect<T> cacheGroups(String... cacheGroups) {
-		this.cacheGroups = cacheGroups != null && cacheGroups.length > 0 ? cacheGroups : null;
+	public SQLSelect<T> cacheGroup(String cacheGroup) {
+		this.cacheGroup = cacheGroup;
 		this.replacementQuery = null;
 		return this;
 	}
 
-	public SQLSelect<T> cacheGroups(Collection<String> cacheGroups) {
-
-		if (cacheGroups == null) {
-			return cacheGroups((String) null);
-		}
-
-		String[] array = new String[cacheGroups.size()];
-		return cacheGroups(cacheGroups.toArray(array));
-	}
-
 	/**
 	 * Returns a column name capitalization policy applied to selecting queries.
 	 * This is used to simplify mapping of the queries like "SELECT * FROM ...",

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java
index c2a6d76..153ed9a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java
@@ -434,64 +434,9 @@ public class SQLTemplate extends AbstractQuery implements ParameterizedQuery, XM
 		return query;
 	}
 
-	/**
-	 * @since 3.0
-	 */
-	public QueryCacheStrategy getCacheStrategy() {
-		return metaData.getCacheStrategy();
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public void setCacheStrategy(QueryCacheStrategy strategy) {
-		metaData.setCacheStrategy(strategy);
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public String[] getCacheGroups() {
-		return metaData.getCacheGroups();
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public void setCacheGroups(String... cacheGroups) {
-		this.metaData.setCacheGroups(cacheGroups);
-	}
-
-	/**
-	 * Instructs Cayenne to look for query results in the "local" cache when
-	 * running the query. This is a short-hand notation for:
-	 * 
-	 * <pre>
-	 * query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-	 * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
-	 * </pre>
-	 * 
-	 * @since 4.0
-	 */
-	public void useLocalCache(String... cacheGroups) {
-		setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-		setCacheGroups(cacheGroups);
-	}
-
-	/**
-	 * Instructs Cayenne to look for query results in the "shared" cache when
-	 * running the query. This is a short-hand notation for:
-	 * 
-	 * <pre>
-	 * query.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-	 * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
-	 * </pre>
-	 * 
-	 * @since 4.0
-	 */
-	public void useSharedCache(String... cacheGroups) {
-		setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-		setCacheGroups(cacheGroups);
+	@Override
+	protected BaseQueryMetadata getBaseMetaData() {
+		return metaData;
 	}
 
 	public int getFetchLimit() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
index 3c151b1..ea9bfdc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
@@ -56,7 +56,7 @@ public class SelectById<T> extends IndirectQuery implements Select<T> {
 
 	boolean fetchingDataRows;
 	QueryCacheStrategy cacheStrategy;
-	String[] cacheGroups;
+	String cacheGroup;
 	PrefetchTreeNode prefetches;
 
 	public static <T> SelectById<T> query(Class<T> entityType, Object id) {
@@ -164,82 +164,74 @@ public class SelectById<T> extends IndirectQuery implements Select<T> {
 	 * running the query. This is a short-hand notation for:
 	 *
 	 * <pre>
-	 * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+	 * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
 	 * </pre>
-	 *
-	 * @since 4.0.M3
 	 */
-	public SelectById<T> localCache(String... cacheGroups) {
-		return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+	public SelectById<T> localCache(String cacheGroup) {
+		return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
 	}
 
 	/**
-	 * Instructs Cayenne to look for query results in the "shared" cache when
+	 * Instructs Cayenne to look for query results in the "local" cache when
 	 * running the query. This is a short-hand notation for:
 	 *
 	 * <pre>
-	 * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+	 * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
 	 * </pre>
-	 *
-	 * @since 4.0.M3
 	 */
-	public SelectById<T> sharedCache(String... cacheGroups) {
-		return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+	public SelectById<T> localCache() {
+		return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
 	}
 
 	/**
-	 * Instructs Cayenne to look for query results in the "local" cache when
+	 * Instructs Cayenne to look for query results in the "shared" cache when
 	 * running the query. This is a short-hand notation for:
 	 *
-	 * @deprecated since 4.0.M3 use {@link #localCache(String...)}
+	 * <pre>
+	 * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
+	 * </pre>
 	 */
-	@Deprecated
-	public SelectById<T> useLocalCache(String... cacheGroups) {
-		return localCache(cacheGroups);
+	public SelectById<T> sharedCache(String cacheGroup) {
+		return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
 	}
 
 	/**
 	 * Instructs Cayenne to look for query results in the "shared" cache when
 	 * running the query. This is a short-hand notation for:
 	 *
-	 * @deprecated since 4.0.M3 use {@link #sharedCache(String...)}
+	 * <pre>
+	 * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+	 * </pre>
 	 */
-	@Deprecated
-	public SelectById<T> useSharedCache(String... cacheGroups) {
-		return sharedCache(cacheGroups);
+	public SelectById<T> sharedCache() {
+		return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
 	}
 
 	public QueryCacheStrategy getCacheStrategy() {
 		return cacheStrategy;
 	}
 
-	public SelectById<T> cacheStrategy(QueryCacheStrategy strategy, String... cacheGroups) {
+	public SelectById<T> cacheStrategy(QueryCacheStrategy strategy) {
 		if (this.cacheStrategy != strategy) {
 			this.cacheStrategy = strategy;
 			this.replacementQuery = null;
 		}
 
-		return cacheGroups(cacheGroups);
+		return this;
 	}
 
-	public String[] getCacheGroups() {
-		return cacheGroups;
+	public SelectById<T> cacheStrategy(QueryCacheStrategy strategy, String cacheGroup) {
+		return cacheStrategy(strategy).cacheGroup(cacheGroup);
 	}
 
-	public SelectById<T> cacheGroups(String... cacheGroups) {
-		this.cacheGroups = cacheGroups != null && cacheGroups.length > 0 ? cacheGroups : null;
-		this.replacementQuery = null;
-		return this;
+	public String getCacheGroup() {
+		return cacheGroup;
 	}
 
-	public SelectById<T> cacheGroups(Collection<String> cacheGroups) {
-
-		if (cacheGroups == null) {
-			return cacheGroups((String) null);
-		}
-
-		String[] array = new String[cacheGroups.size()];
-		return cacheGroups(cacheGroups.toArray(array));
+	public SelectById<T> cacheGroup(String cacheGroup) {
+		this.cacheGroup = cacheGroup;
+		this.replacementQuery = null;
+		return this;
 	}
 
 	public boolean isFetchingDataRows() {
@@ -304,7 +296,7 @@ public class SelectById<T> extends IndirectQuery implements Select<T> {
 		// note on caching... this hits query cache instead of object cache...
 		// until we merge the two this may result in not using the cache
 		// optimally - object cache may have an object, but query cache will not
-		query.setCacheGroups(cacheGroups);
+		query.setCacheGroup(cacheGroup);
 		query.setCacheStrategy(cacheStrategy);
 		query.setPrefetchTree(prefetches);
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
index ceb86dc..97153cc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
@@ -699,66 +699,6 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 	}
 
 	/**
-	 * @since 3.0
-	 */
-	public QueryCacheStrategy getCacheStrategy() {
-		return metaData.getCacheStrategy();
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public void setCacheStrategy(QueryCacheStrategy strategy) {
-		metaData.setCacheStrategy(strategy);
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public String[] getCacheGroups() {
-		return metaData.getCacheGroups();
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public void setCacheGroups(String... cacheGroups) {
-		this.metaData.setCacheGroups(cacheGroups);
-	}
-
-	/**
-	 * Instructs Cayenne to look for query results in the "local" cache when
-	 * running the query. This is a short-hand notation for:
-	 * 
-	 * <pre>
-	 * query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-	 * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
-	 * </pre>
-	 * 
-	 * @since 4.0
-	 */
-	public void useLocalCache(String... cacheGroups) {
-		setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-		setCacheGroups(cacheGroups);
-	}
-
-	/**
-	 * Instructs Cayenne to look for query results in the "shared" cache when
-	 * running the query. This is a short-hand notation for:
-	 * 
-	 * <pre>
-	 * query.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-	 * query.setCacheGroups(&quot;group1&quot;, &quot;group2&quot;);
-	 * </pre>
-	 * 
-	 * @since 4.0
-	 */
-	public void useSharedCache(String... cacheGroups) {
-		setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-		setCacheGroups(cacheGroups);
-	}
-
-	/**
 	 * Returns the fetchOffset.
 	 * 
 	 * @since 3.0
@@ -950,4 +890,9 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 	public void orHavingQualifier(Expression e) {
 		havingQualifier = (havingQualifier != null) ? havingQualifier.orExp(e) : e;
 	}
+
+	@Override
+	protected BaseQueryMetadata getBaseMetaData() {
+		return metaData;
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/dd1745ca/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
index 57d6d77..4ff953c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
@@ -53,7 +53,7 @@ class IncrementalQuery implements Query {
         // overriding caching settings in the metadata will only affect
         // ClientServerChannel behavior
         return new QueryMetadataProxy(metadata) {
-            public Query getOrginatingQuery() {
+            public Query getOriginatingQuery() {
                 return null;
             }