CAY-2259 QueryCache: support for referencing type-safe caches
+ move cayenne-cache-invalidation module out of lifecycle module


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

Branch: refs/heads/master
Commit: d71bfe1b2ca47dfed034a5f51058429c338bf835
Parents: 67225b3
Author: Nikita Timofeev <stari...@gmail.com>
Authored: Mon Apr 3 15:49:18 2017 +0300
Committer: Nikita Timofeev <stari...@gmail.com>
Committed: Mon Apr 3 15:49:18 2017 +0300

----------------------------------------------------------------------
 assembly/pom.xml                                |   6 +
 .../resources/assemblies/assembly-generic.xml   |   1 +
 .../main/resources/assemblies/assembly-mac.xml  |   1 +
 .../resources/assemblies/assembly-windows.xml   |   1 +
 cayenne-cache-invalidation/pom.xml              |  92 +++++++++++
 .../cayenne/lifecycle/cache/CacheGroup.java     |  60 +++++++
 .../lifecycle/cache/CacheGroupDescriptor.java   |  60 +++++++
 .../cayenne/lifecycle/cache/CacheGroups.java    |  56 +++++++
 .../lifecycle/cache/CacheGroupsHandler.java     |  79 +++++++++
 .../cache/CacheInvalidationFilter.java          | 159 +++++++++++++++++++
 .../cache/CacheInvalidationModule.java          |  46 ++++++
 .../cache/CacheInvalidationModuleBuilder.java   |  84 ++++++++++
 .../cache/CacheInvalidationModuleProvider.java  |  50 ++++++
 .../lifecycle/cache/InvalidationFunction.java   |  36 +++++
 .../lifecycle/cache/InvalidationHandler.java    |  35 ++++
 .../org.apache.cayenne.di.spi.ModuleProvider    |  20 +++
 .../lifecycle/cache/CacheGroupsHandlerTest.java |  68 ++++++++
 .../CacheInvalidationCacheGroupsHandlerIT.java  | 153 ++++++++++++++++++
 .../cache/CacheInvalidationCustomHandlerIT.java |  98 ++++++++++++
 ...enneCacheInvalidationModuleProviderTest.java |  31 ++++
 .../org/apache/cayenne/lifecycle/db/E1.java     |  11 ++
 .../org/apache/cayenne/lifecycle/db/E2.java     |  21 +++
 .../apache/cayenne/lifecycle/db/auto/_E1.java   |  18 +++
 .../apache/cayenne/lifecycle/db/auto/_E2.java   |  18 +++
 .../lifecycle/unit/CacheInvalidationCase.java   |  75 +++++++++
 .../src/test/resources/cayenne-lifecycle.xml    |  17 ++
 .../src/test/resources/lifecycle-map.map.xml    |  17 ++
 .../cayenne/lifecycle/cache/CacheGroups.java    |  46 ------
 .../lifecycle/cache/CacheGroupsHandler.java     |  59 -------
 .../cache/CacheInvalidationFilter.java          | 154 ------------------
 .../cache/CacheInvalidationModuleBuilder.java   |  96 -----------
 .../lifecycle/cache/InvalidationFunction.java   |  36 -----
 .../lifecycle/cache/InvalidationHandler.java    |  35 ----
 .../lifecycle/cache/CacheInvalidationIT.java    |  76 ---------
 .../lifecycle/unit/CacheInvalidationCase.java   |  83 ----------
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   4 +-
 docs/doc/src/main/resources/UPGRADE.txt         |   7 +
 pom.xml                                         |   3 +-
 38 files changed, 1324 insertions(+), 588 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/assembly/pom.xml
----------------------------------------------------------------------
diff --git a/assembly/pom.xml b/assembly/pom.xml
index b35ee1a..24a13f8 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -103,6 +103,12 @@
                </dependency>
 
                <dependency>
+                       <groupId>org.apache.cayenne</groupId>
+                       <artifactId>cayenne-cache-invalidation</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+
+               <dependency>
                        <groupId>org.apache.cayenne.modeler</groupId>
                        <artifactId>cayenne-modeler</artifactId>
                        <version>${project.version}</version>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/assembly/src/main/resources/assemblies/assembly-generic.xml
----------------------------------------------------------------------
diff --git a/assembly/src/main/resources/assemblies/assembly-generic.xml 
b/assembly/src/main/resources/assemblies/assembly-generic.xml
index e688faa..7c19a8c 100644
--- a/assembly/src/main/resources/assemblies/assembly-generic.xml
+++ b/assembly/src/main/resources/assemblies/assembly-generic.xml
@@ -90,6 +90,7 @@
                                
<include>org.apache.cayenne:cayenne-dbcp2</include>
                                
<include>org.apache.cayenne:cayenne-java8</include>
                                
<include>org.apache.cayenne:cayenne-jcache</include>
+                               
<include>org.apache.cayenne:cayenne-cache-invalidation</include>
                                
<include>org.apache.cayenne:cayenne-jgroups</include>
                                
<include>org.apache.cayenne:cayenne-jms</include>
                                
<include>org.apache.cayenne:cayenne-xmpp</include>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/assembly/src/main/resources/assemblies/assembly-mac.xml
----------------------------------------------------------------------
diff --git a/assembly/src/main/resources/assemblies/assembly-mac.xml 
b/assembly/src/main/resources/assemblies/assembly-mac.xml
index 45efd5d..85400b4 100644
--- a/assembly/src/main/resources/assemblies/assembly-mac.xml
+++ b/assembly/src/main/resources/assemblies/assembly-mac.xml
@@ -90,6 +90,7 @@
                                
<include>org.apache.cayenne:cayenne-dbcp2</include>
                                
<include>org.apache.cayenne:cayenne-java8</include>
                                
<include>org.apache.cayenne:cayenne-jcache</include>
+                               
<include>org.apache.cayenne:cayenne-cache-invalidation</include>
                                
<include>org.apache.cayenne:cayenne-jgroups</include>
                                
<include>org.apache.cayenne:cayenne-jms</include>
                                
<include>org.apache.cayenne:cayenne-xmpp</include>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/assembly/src/main/resources/assemblies/assembly-windows.xml
----------------------------------------------------------------------
diff --git a/assembly/src/main/resources/assemblies/assembly-windows.xml 
b/assembly/src/main/resources/assemblies/assembly-windows.xml
index 25f1099..0e229d1 100644
--- a/assembly/src/main/resources/assemblies/assembly-windows.xml
+++ b/assembly/src/main/resources/assemblies/assembly-windows.xml
@@ -90,6 +90,7 @@
                                
<include>org.apache.cayenne:cayenne-dbcp2</include>
                                
<include>org.apache.cayenne:cayenne-java8</include>
                                
<include>org.apache.cayenne:cayenne-jcache</include>
+                               
<include>org.apache.cayenne:cayenne-cache-invalidation</include>
                                
<include>org.apache.cayenne:cayenne-jgroups</include>
                                
<include>org.apache.cayenne:cayenne-jms</include>
                                
<include>org.apache.cayenne:cayenne-xmpp</include>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-cache-invalidation/pom.xml 
b/cayenne-cache-invalidation/pom.xml
new file mode 100644
index 0000000..a59a576
--- /dev/null
+++ b/cayenne-cache-invalidation/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <parent>
+        <artifactId>cayenne-parent</artifactId>
+        <groupId>org.apache.cayenne</groupId>
+        <version>4.0.M6-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>cayenne-cache-invalidation</artifactId>
+    <name>cayenne-cache-invalidation: Cayenne Cache Invalidation</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <!-- Compile dependencies -->
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hsqldb</groupId>
+            <artifactId>hsqldb</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cayenne.build-tools</groupId>
+            <artifactId>cayenne-test-utilities</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.7</source>
+                    <target>1.7</target>
+                </configuration>
+            </plugin>
+            <!-- This ensures LICENSE and NOTICE inclusion in all jars -->
+            <plugin>
+                <artifactId>maven-remote-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>process</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroup.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroup.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroup.java
new file mode 100644
index 0000000..be8bda5
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroup.java
@@ -0,0 +1,60 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for defining cache group in case different cache types are 
targeted.
+ *
+ * @see CacheGroups
+ * @see CacheGroupsHandler
+ *
+ * @since 4.0
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface CacheGroup {
+
+    /**
+     * @return cache group name
+     */
+    String value();
+
+
+    /**
+     * Defines key type of the cache.
+     * Could be used for managing  external caches that is strictly typed 
(e.g. JCache).
+     */
+    Class<?> keyType() default Void.class;
+
+    /**
+     * Defines value type of the cache.
+     * Could be used for managing external caches that is strictly typed (e.g. 
JCache).
+     */
+    Class<?> valueType() default Void.class;
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupDescriptor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupDescriptor.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupDescriptor.java
new file mode 100644
index 0000000..bc38393
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupDescriptor.java
@@ -0,0 +1,60 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+/**
+ * Immutable object describing cache group.
+ * Used as a result in {@link InvalidationFunction}.
+ *
+ * @see CacheGroupDescriptor#CacheGroupDescriptor(String)
+ * @see CacheGroupDescriptor#CacheGroupDescriptor(String, Class, Class)
+ *
+ * @since 4.0
+ */
+public class CacheGroupDescriptor {
+
+    private final String cacheGroupName;
+
+    private final Class<?> keyType;
+
+    private final Class<?> valueType;
+
+    public CacheGroupDescriptor(String cacheGroupName) {
+        this(cacheGroupName, Void.class, Void.class);
+    }
+
+    public CacheGroupDescriptor(String cacheGroupName, Class<?> keyType, 
Class<?> valueType) {
+        this.cacheGroupName = cacheGroupName;
+        this.keyType = keyType;
+        this.valueType = valueType;
+    }
+
+    public String getCacheGroupName() {
+        return cacheGroupName;
+    }
+
+    public Class<?> getKeyType() {
+        return keyType;
+    }
+
+    public Class<?> getValueType() {
+        return valueType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroups.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroups.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroups.java
new file mode 100644
index 0000000..1e5449b6
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroups.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * A built-in annotation that provides declarative cache management for 
persistent
+ * objects.
+ * </p>
+ *
+ * @see InvalidationHandler
+ * @see CacheGroup
+ *
+ * @since 3.1
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface CacheGroups {
+
+    /**
+     * Defines one or more cache group names associated with the tagged entity.
+     */
+    String[] value() default {};
+
+    /**
+     * Defines one or more typed cache groups associated with the tagged 
entity.
+     *
+     * @since 4.0
+     */
+    CacheGroup[] groups() default {};
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandler.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandler.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandler.java
new file mode 100644
index 0000000..63979fd
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandler.java
@@ -0,0 +1,79 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.cayenne.Persistent;
+
+import static java.util.Arrays.asList;
+
+/**
+ * @since 4.0
+ */
+public class CacheGroupsHandler implements InvalidationHandler {
+
+    /**
+     * Return invalidation function that returns values
+     * of {@link CacheGroups} and {@link CacheGroup} annotations for the given 
type.
+     */
+    @Override
+    public InvalidationFunction canHandle(Class<? extends Persistent> type) {
+
+        CacheGroup multipleCacheGroups = type.getAnnotation(CacheGroup.class);
+        CacheGroups cacheGroups = type.getAnnotation(CacheGroups.class);
+        if (cacheGroups == null && multipleCacheGroups == null) {
+            return null;
+        }
+
+        final Collection<CacheGroupDescriptor> groupsList = new ArrayList<>();
+        extractCacheGroups(cacheGroups, groupsList);
+        extractCacheGroups(multipleCacheGroups, groupsList);
+
+        return new InvalidationFunction() {
+            @Override
+            public Collection<CacheGroupDescriptor> apply(Persistent 
persistent) {
+                return groupsList;
+            }
+        };
+    }
+
+    private void extractCacheGroups(CacheGroup cacheGroup, 
Collection<CacheGroupDescriptor> groupsList) {
+        if(cacheGroup == null) {
+            return;
+        }
+        groupsList.add(new CacheGroupDescriptor(cacheGroup.value(), 
cacheGroup.keyType(), cacheGroup.valueType()));
+    }
+
+    private void extractCacheGroups(CacheGroups cacheGroups, 
Collection<CacheGroupDescriptor> groupsList) {
+        if(cacheGroups == null) {
+            return;
+        }
+
+        for(String name : cacheGroups.value()) {
+            groupsList.add(new CacheGroupDescriptor(name));
+        }
+
+        for(CacheGroup group : cacheGroups.groups()) {
+            extractCacheGroups(group, groupsList);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationFilter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationFilter.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationFilter.java
new file mode 100644
index 0000000..8f1e3bd
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationFilter.java
@@ -0,0 +1,159 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.DataChannelFilter;
+import org.apache.cayenne.DataChannelFilterChain;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.QueryResponse;
+import org.apache.cayenne.annotation.PrePersist;
+import org.apache.cayenne.annotation.PreRemove;
+import org.apache.cayenne.annotation.PreUpdate;
+import org.apache.cayenne.cache.QueryCache;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.di.Provider;
+import org.apache.cayenne.graph.GraphDiff;
+import org.apache.cayenne.query.Query;
+
+/**
+ * <p>
+ * A {@link DataChannelFilter} that invalidates cache groups.
+ * Use custom rules for invalidation provided via DI.
+ * </p>
+ * <p>
+ * Default rule is based on entities' {@link CacheGroups} annotation.
+ * </p>
+ * <p>
+ *     To add default filter: <pre>
+ *         ServerRuntime.builder("cayenne-project.xml")
+ *              .addModule(CacheInvalidationModuleBuilder.builder().build());
+ *     </pre>
+ * </p>
+ *
+ * @see CacheInvalidationModuleBuilder
+ * @see InvalidationHandler
+ *
+ * @since 3.1
+ * @since 4.0 enhanced to support custom handlers.
+ */
+public class CacheInvalidationFilter implements DataChannelFilter {
+
+    @Inject
+    private Provider<QueryCache> cacheProvider;
+
+    @Inject
+    private List<InvalidationHandler> handlers;
+
+    private final Map<Class<? extends Persistent>, InvalidationFunction> 
mappedHandlers;
+
+    private final InvalidationFunction skipHandler;
+
+    private final ThreadLocal<Set<CacheGroupDescriptor>> groups;
+
+    public CacheInvalidationFilter() {
+        mappedHandlers = new ConcurrentHashMap<>();
+        skipHandler = new InvalidationFunction() {
+            @Override
+            public Collection<CacheGroupDescriptor> apply(Persistent p) {
+                return Collections.emptyList();
+            }
+        };
+        groups = new ThreadLocal<>();
+    }
+
+    public void init(DataChannel channel) {
+        // noop
+    }
+
+    public QueryResponse onQuery(ObjectContext originatingContext, Query 
query, DataChannelFilterChain filterChain) {
+        return filterChain.onQuery(originatingContext, query);
+    }
+
+    public GraphDiff onSync(ObjectContext originatingContext, GraphDiff 
changes,
+                            int syncType, DataChannelFilterChain filterChain) {
+        try {
+            GraphDiff result = filterChain.onSync(originatingContext, changes, 
syncType);
+            // no exceptions, flush...
+            Collection<CacheGroupDescriptor> groupSet = groups.get();
+            if (groupSet != null && !groupSet.isEmpty()) {
+                QueryCache cache = cacheProvider.get();
+                for (CacheGroupDescriptor group : groupSet) {
+                    if(group.getKeyType() != Void.class) {
+                        cache.removeGroup(group.getCacheGroupName(), 
group.getKeyType(), group.getValueType());
+                    } else {
+                        cache.removeGroup(group.getCacheGroupName());
+                    }
+                }
+            }
+            return result;
+        } finally {
+            groups.set(null);
+        }
+    }
+
+    /**
+     * A callback method that records cache group to flush at the end of the 
commit.
+     */
+    @PrePersist
+    @PreRemove
+    @PreUpdate
+    protected void preCommit(Object object) {
+        // TODO: for some reason we can't use Persistent as the argument 
type... (is it fixed in Cayenne 4.0.M4?)
+        Persistent p = (Persistent) object;
+
+        InvalidationFunction invalidationFunction = 
mappedHandlers.get(p.getClass());
+        if(invalidationFunction == null) {
+            invalidationFunction = skipHandler;
+            for (InvalidationHandler handler : handlers) {
+                InvalidationFunction function = 
handler.canHandle(p.getClass());
+                if (function != null) {
+                    invalidationFunction = function;
+                    break;
+                }
+            }
+            mappedHandlers.put(p.getClass(), invalidationFunction);
+        }
+
+        Collection<CacheGroupDescriptor> objectGroups = 
invalidationFunction.apply(p);
+        if (!objectGroups.isEmpty()) {
+            getOrCreateTxGroups().addAll(objectGroups);
+        }
+    }
+
+    protected Set<CacheGroupDescriptor> getOrCreateTxGroups() {
+        Set<CacheGroupDescriptor> txGroups = groups.get();
+        if (txGroups == null) {
+            txGroups = new HashSet<>();
+            groups.set(txGroups);
+        }
+
+        return txGroups;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModule.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModule.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModule.java
new file mode 100644
index 0000000..8cb86ad
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModule.java
@@ -0,0 +1,46 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import org.apache.cayenne.configuration.server.ServerModule;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.ListBuilder;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.tx.TransactionFilter;
+
+/**
+ * This module is autoloaded, all extensions should be done via {@link 
CacheInvalidationModuleBuilder}.
+ * @since 4.0
+ */
+public class CacheInvalidationModule implements Module {
+
+    static ListBuilder<InvalidationHandler> 
contributeInvalidationHandler(Binder binder) {
+        return binder.bindList(InvalidationHandler.class);
+    }
+
+    @Override
+    public void configure(Binder binder) {
+        contributeInvalidationHandler(binder);
+
+        // want the filter to be INSIDE transaction
+        ServerModule.contributeDomainFilters(binder)
+                .insertBefore(CacheInvalidationFilter.class, 
TransactionFilter.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleBuilder.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleBuilder.java
new file mode 100644
index 0000000..56049f1
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleBuilder.java
@@ -0,0 +1,84 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.ListBuilder;
+import org.apache.cayenne.di.Module;
+
+/**
+ * @since 4.0
+ */
+public class CacheInvalidationModuleBuilder {
+
+    private Collection<Class<? extends InvalidationHandler>> handlerTypes;
+
+    private Collection<InvalidationHandler> handlerInstances;
+
+    private boolean noCacheGroupsHandler;
+
+    public static CacheInvalidationModuleBuilder builder() {
+        return new CacheInvalidationModuleBuilder();
+    }
+
+    CacheInvalidationModuleBuilder() {
+        this.handlerTypes = new HashSet<>();
+        this.handlerInstances = new HashSet<>();
+    }
+
+    /**
+     * Disable {@link CacheGroupsHandler} based on {@link CacheGroups} 
annotation.
+     */
+    public CacheInvalidationModuleBuilder noCacheGroupsHandler() {
+        noCacheGroupsHandler = true;
+        return this;
+    }
+
+    public CacheInvalidationModuleBuilder invalidationHandler(Class<? extends 
InvalidationHandler> handlerType) {
+        handlerTypes.add(handlerType);
+        return this;
+    }
+
+    public CacheInvalidationModuleBuilder 
invalidationHandler(InvalidationHandler handlerInstance) {
+        handlerInstances.add(handlerInstance);
+        return this;
+    }
+
+    public Module build() {
+        return new Module() {
+            @Override
+            public void configure(Binder binder) {
+                ListBuilder<InvalidationHandler> handlers = 
CacheInvalidationModule.contributeInvalidationHandler(binder);
+
+                if(!noCacheGroupsHandler) {
+                    handlers.add(CacheGroupsHandler.class);
+                }
+                handlers.addAll(handlerInstances);
+
+                for(Class<? extends InvalidationHandler> handlerType : 
handlerTypes) {
+                    handlers.add(handlerType);
+                }
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleProvider.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleProvider.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleProvider.java
new file mode 100644
index 0000000..3696686
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleProvider.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.cayenne.configuration.server.ServerModule;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.ModuleProvider;
+
+/**
+ * @since 4.0
+ */
+public class CacheInvalidationModuleProvider implements ModuleProvider {
+
+    @Override
+    public Module module() {
+        return new CacheInvalidationModule();
+    }
+
+    @Override
+    public Class<? extends Module> moduleType() {
+        return CacheInvalidationModule.class;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Collection<Class<? extends Module>> overrides() {
+        Collection modules = Collections.singletonList(ServerModule.class);
+        return modules;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationFunction.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationFunction.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationFunction.java
new file mode 100644
index 0000000..6f6d6d5
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationFunction.java
@@ -0,0 +1,36 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.util.Collection;
+
+import org.apache.cayenne.Persistent;
+
+/**
+ * @since 4.0
+ */
+public interface InvalidationFunction {
+
+    /**
+     * @return collection of cache groups to invalidate for given object
+     */
+    Collection<CacheGroupDescriptor> apply(Persistent persistent);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationHandler.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationHandler.java
 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationHandler.java
new file mode 100644
index 0000000..a667ead
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationHandler.java
@@ -0,0 +1,35 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import org.apache.cayenne.Persistent;
+
+/**
+ * A pluggable handler to invalidate cache groups on changes in certain 
objects.
+ * @since 4.0
+ */
+public interface InvalidationHandler {
+
+    /**
+     * @return invalidation function or null if there is nothing to invalidate
+     */
+    InvalidationFunction canHandle(Class<? extends Persistent> type);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
 
b/cayenne-cache-invalidation/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
new file mode 100644
index 0000000..4fc646c
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
@@ -0,0 +1,20 @@
+##################################################################
+#   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.
+##################################################################
+
+org.apache.cayenne.lifecycle.cache.CacheInvalidationModuleProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandlerTest.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandlerTest.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandlerTest.java
new file mode 100644
index 0000000..1afa4f5
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandlerTest.java
@@ -0,0 +1,68 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.cayenne.lifecycle.db.E1;
+import org.apache.cayenne.lifecycle.db.E2;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.0
+ */
+public class CacheGroupsHandlerTest {
+
+    @Test
+    public void canHandleE1() throws Exception {
+        CacheGroupsHandler handler = new  CacheGroupsHandler();
+        InvalidationFunction function = handler.canHandle(E1.class);
+        Collection<CacheGroupDescriptor> result = function.apply(null);
+
+        assertEquals(2, result.size());
+
+        String[] names = {"g1", "g2"};
+        Collection<String> extractedNames = new ArrayList<>();
+        for(CacheGroupDescriptor descriptor : result) {
+            extractedNames.add(descriptor.getCacheGroupName());
+        }
+        assertArrayEquals(names, extractedNames.toArray());
+    }
+
+    @Test
+    public void canHandleE2() throws Exception {
+        CacheGroupsHandler handler = new  CacheGroupsHandler();
+        InvalidationFunction function = handler.canHandle(E2.class);
+        Collection<CacheGroupDescriptor> result = function.apply(null);
+
+        assertEquals(6, result.size());
+
+        String[] names = {"g1", "g2", "g3", "g4", "g5", "g6"};
+        Collection<String> extractedNames = new ArrayList<>();
+        for(CacheGroupDescriptor descriptor : result) {
+            extractedNames.add(descriptor.getCacheGroupName());
+        }
+        assertArrayEquals(names, extractedNames.toArray());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationCacheGroupsHandlerIT.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationCacheGroupsHandlerIT.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationCacheGroupsHandlerIT.java
new file mode 100644
index 0000000..2b090ea
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationCacheGroupsHandlerIT.java
@@ -0,0 +1,153 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.cache.MapQueryCache;
+import org.apache.cayenne.cache.QueryCache;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.lifecycle.db.E1;
+import org.apache.cayenne.lifecycle.db.E2;
+import org.apache.cayenne.lifecycle.unit.CacheInvalidationCase;
+import org.apache.cayenne.query.ObjectSelect;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.0
+ */
+public class CacheInvalidationCacheGroupsHandlerIT extends 
CacheInvalidationCase {
+
+    private AtomicInteger removeGroupUntypedCounter;
+    private AtomicInteger removeGroupTypedCounter;
+
+    @Before
+    public void resetCounters() {
+        removeGroupUntypedCounter = new AtomicInteger(0);
+        removeGroupTypedCounter = new AtomicInteger(0);
+    }
+
+    @Override
+    protected Module buildInvalidationModule() {
+        return CacheInvalidationModuleBuilder.builder().build();
+    }
+
+    @Override
+    protected Module buildCustomModule() {
+        // Proxy query cache that will count methods calls
+        final QueryCache cache = new MapQueryCache() {
+            @Override
+            public void removeGroup(String groupKey) {
+                removeGroupUntypedCounter.incrementAndGet();
+                super.removeGroup(groupKey);
+            }
+
+            @Override
+            public void removeGroup(String groupKey, Class<?> keyType, 
Class<?> valueType) {
+                removeGroupTypedCounter.incrementAndGet();
+                super.removeGroup(groupKey, keyType, valueType);
+            }
+        };
+
+        return new Module() {
+            @Override
+            public void configure(Binder binder) {
+                binder.bind(QueryCache.class).toInstance(cache);
+            }
+        };
+    }
+
+    @Test
+    public void invalidateE1() throws Exception {
+        ObjectContext context = runtime.newContext();
+
+        ObjectSelect<E1> g0 = ObjectSelect.query(E1.class).localCache();
+        ObjectSelect<E1> g1 = ObjectSelect.query(E1.class).localCache("g1");
+        ObjectSelect<E1> g2 = ObjectSelect.query(E1.class).localCache("g2");
+
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(0, g1.selectCount(context));
+        assertEquals(0, g2.selectCount(context));
+
+        e1.insert(1).insert(2);
+
+        // inserted via SQL... query results are still cached...
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(0, g1.selectCount(context));
+        assertEquals(0, g2.selectCount(context));
+
+
+        context.newObject(E1.class);
+        context.commitChanges();
+
+        assertEquals(2, removeGroupUntypedCounter.get());
+        assertEquals(0, removeGroupTypedCounter.get());
+        // inserted via Cayenne... "g1" and "g2" should get auto refreshed...
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(3, g1.selectCount(context));
+        assertEquals(3, g2.selectCount(context));
+    }
+
+    @Test
+    public void invalidateE2() throws Exception {
+        ObjectContext context = runtime.newContext();
+
+        ObjectSelect<E2> g0 = ObjectSelect.query(E2.class).localCache();
+        ObjectSelect<E2> g1 = ObjectSelect.query(E2.class).localCache("g1");
+        ObjectSelect<E2> g2 = ObjectSelect.query(E2.class).localCache("g2");
+        ObjectSelect<E2> g3 = ObjectSelect.query(E2.class).localCache("g3");
+        ObjectSelect<E2> g5 = ObjectSelect.query(E2.class).localCache("g5");
+
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(0, g1.selectCount(context));
+        assertEquals(0, g2.selectCount(context));
+        assertEquals(0, g3.selectCount(context));
+        assertEquals(0, g5.selectCount(context));
+
+        e2.insert(1).insert(2);
+
+        // inserted via SQL... query results are still cached...
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(0, g1.selectCount(context));
+        assertEquals(0, g2.selectCount(context));
+        assertEquals(0, g3.selectCount(context));
+        assertEquals(0, g5.selectCount(context));
+
+
+        context.newObject(E2.class);
+        context.commitChanges();
+
+        // Typed remove will actually call untyped version, thus 4 + 2
+        assertEquals(4 + 2, removeGroupUntypedCounter.get());
+        assertEquals(2, removeGroupTypedCounter.get());
+
+        // inserted via Cayenne... "g1" and "g2" should get auto refreshed...
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(3, g1.selectCount(context));
+        assertEquals(3, g2.selectCount(context));
+        assertEquals(3, g3.selectCount(context));
+        assertEquals(3, g5.selectCount(context));
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationCustomHandlerIT.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationCustomHandlerIT.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationCustomHandlerIT.java
new file mode 100644
index 0000000..56ae915
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationCustomHandlerIT.java
@@ -0,0 +1,98 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.lifecycle.db.E1;
+import org.apache.cayenne.lifecycle.unit.CacheInvalidationCase;
+import org.apache.cayenne.query.ObjectSelect;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.0
+ */
+public class CacheInvalidationCustomHandlerIT extends CacheInvalidationCase {
+
+    @Override
+    protected Module buildInvalidationModule() {
+        return CacheInvalidationModuleBuilder.builder()
+                .noCacheGroupsHandler()
+                .invalidationHandler(G1InvalidationHandler.class)
+                .build();
+    }
+
+    @Test
+    public void testInvalidate() throws Exception {
+        ObjectContext context = runtime.newContext();
+
+        // no explicit cache group must still work - it lands inside default 
cache called 'cayenne.default.cache'
+        ObjectSelect<E1> g0 = ObjectSelect.query(E1.class).localCache();
+        ObjectSelect<E1> g1 = ObjectSelect.query(E1.class).localCache("g1");
+        ObjectSelect<E1> g2 = ObjectSelect.query(E1.class).localCache("g2");
+
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(0, g1.selectCount(context));
+        assertEquals(0, g2.selectCount(context));
+
+        e1.insert(1).insert(2);
+
+        // inserted via SQL... query results are still cached...
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(0, g1.selectCount(context));
+        assertEquals(0, g2.selectCount(context));
+
+
+        E1 e1 = context.newObject(E1.class);
+        context.commitChanges();
+
+        // inserted via Cayenne... "g1" should get auto refreshed...
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(3, g1.selectCount(context));
+        assertEquals(0, g2.selectCount(context));
+
+        context.deleteObject(e1);
+        context.commitChanges();
+
+        // deleted via Cayenne... "g1" should get auto refreshed
+        assertEquals(0, g0.selectCount(context));
+        assertEquals(2, g1.selectCount(context));
+        assertEquals(0, g2.selectCount(context));
+    }
+
+    public static class G1InvalidationHandler implements InvalidationHandler {
+        @Override
+        public InvalidationFunction canHandle(Class<? extends Persistent> 
type) {
+            return new InvalidationFunction() {
+                @Override
+                public Collection<CacheGroupDescriptor> apply(Persistent 
persistent) {
+                    return Collections.singleton(new 
CacheGroupDescriptor("g1"));
+                }
+            };
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CayenneCacheInvalidationModuleProviderTest.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CayenneCacheInvalidationModuleProviderTest.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CayenneCacheInvalidationModuleProviderTest.java
new file mode 100644
index 0000000..4928fd1
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/cache/CayenneCacheInvalidationModuleProviderTest.java
@@ -0,0 +1,31 @@
+/*****************************************************************
+ *   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.lifecycle.cache;
+
+import org.apache.cayenne.unit.util.ModuleProviderChecker;
+import org.junit.Test;
+
+public class CayenneCacheInvalidationModuleProviderTest {
+
+    @Test
+    public void testAutoLoadable() {
+        
ModuleProviderChecker.testProviderPresent(CacheInvalidationModuleProvider.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/E1.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/E1.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/E1.java
new file mode 100644
index 0000000..a6ae37d
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/E1.java
@@ -0,0 +1,11 @@
+package org.apache.cayenne.lifecycle.db;
+
+import org.apache.cayenne.lifecycle.cache.CacheGroups;
+import org.apache.cayenne.lifecycle.db.auto._E1;
+
+@CacheGroups({"g1", "g2"})
+public class E1 extends _E1 {
+
+    private static final long serialVersionUID = 1L; 
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/E2.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/E2.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/E2.java
new file mode 100644
index 0000000..44a8f4f
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/E2.java
@@ -0,0 +1,21 @@
+package org.apache.cayenne.lifecycle.db;
+
+import org.apache.cayenne.lifecycle.cache.CacheGroup;
+import org.apache.cayenne.lifecycle.cache.CacheGroups;
+import org.apache.cayenne.lifecycle.db.auto._E2;
+
+
+@CacheGroups(
+        value = {"g1", "g2"},
+        groups = {
+            @CacheGroup("g3"),
+            @CacheGroup(value = "g4", keyType = String.class, valueType = 
Object.class),
+            @CacheGroup(value = "g5", keyType = Integer.class, valueType = 
Object.class),
+        }
+)
+@CacheGroup("g6")
+public class E2 extends _E2 {
+
+    private static final long serialVersionUID = 1L; 
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/auto/_E1.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/auto/_E1.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/auto/_E1.java
new file mode 100644
index 0000000..ce307f3
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/auto/_E1.java
@@ -0,0 +1,18 @@
+package org.apache.cayenne.lifecycle.db.auto;
+
+import org.apache.cayenne.CayenneDataObject;
+
+/**
+ * Class _E1 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _E1 extends CayenneDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ID_PK_COLUMN = "ID";
+
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/auto/_E2.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/auto/_E2.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/auto/_E2.java
new file mode 100644
index 0000000..e333492
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/db/auto/_E2.java
@@ -0,0 +1,18 @@
+package org.apache.cayenne.lifecycle.db.auto;
+
+import org.apache.cayenne.CayenneDataObject;
+
+/**
+ * Class _E2 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _E2 extends CayenneDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ID_PK_COLUMN = "ID";
+
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/unit/CacheInvalidationCase.java
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/unit/CacheInvalidationCase.java
 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/unit/CacheInvalidationCase.java
new file mode 100644
index 0000000..bfe6272
--- /dev/null
+++ 
b/cayenne-cache-invalidation/src/test/java/org/apache/cayenne/lifecycle/unit/CacheInvalidationCase.java
@@ -0,0 +1,75 @@
+/*****************************************************************
+ *   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.lifecycle.unit;
+
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.configuration.server.ServerRuntimeBuilder;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.junit.After;
+import org.junit.Before;
+
+public abstract class CacheInvalidationCase {
+
+       protected ServerRuntime runtime;
+
+       protected TableHelper e1;
+
+       protected TableHelper e2;
+
+       @Before
+       public void startCayenne() throws Exception {
+               this.runtime = configureCayenne().build();
+
+               DBHelper dbHelper = new DBHelper(runtime.getDataSource());
+
+               this.e1 = new TableHelper(dbHelper, "E1").setColumns("ID");
+               this.e1.deleteAll();
+
+               this.e2 = new TableHelper(dbHelper, "E2").setColumns("ID");
+               this.e2.deleteAll();
+       }
+
+       protected abstract Module buildInvalidationModule();
+
+       protected Module buildCustomModule() {
+               return new Module() {
+                       @Override
+                       public void configure(Binder binder) {
+                       }
+               };
+       }
+
+       protected ServerRuntimeBuilder configureCayenne() {
+               return ServerRuntime.builder()
+                               .addModule(buildInvalidationModule())
+                               .addModule(buildCustomModule())
+                               .addConfig("cayenne-lifecycle.xml");
+       }
+
+       @After
+       public void shutdownCayenne() {
+               if (runtime != null) {
+                       runtime.shutdown();
+               }
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/resources/cayenne-lifecycle.xml
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/resources/cayenne-lifecycle.xml 
b/cayenne-cache-invalidation/src/test/resources/cayenne-lifecycle.xml
new file mode 100644
index 0000000..5b9a83e
--- /dev/null
+++ b/cayenne-cache-invalidation/src/test/resources/cayenne-lifecycle.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<domain project-version="9">
+       <map name="lifecycle-map"/>
+
+       <node name="lifecycle-db"
+                
factory="org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory"
+                
schema-update-strategy="org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy"
+               >
+               <map-ref name="lifecycle-map"/>
+               <data-source>
+                       <driver value="org.hsqldb.jdbcDriver"/>
+                       <url value="jdbc:hsqldb:mem:lifecycle"/>
+                       <connectionPool min="1" max="1"/>
+                       <login userName="sa"/>
+               </data-source>
+       </node>
+</domain>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-cache-invalidation/src/test/resources/lifecycle-map.map.xml
----------------------------------------------------------------------
diff --git 
a/cayenne-cache-invalidation/src/test/resources/lifecycle-map.map.xml 
b/cayenne-cache-invalidation/src/test/resources/lifecycle-map.map.xml
new file mode 100644
index 0000000..80d44d6
--- /dev/null
+++ b/cayenne-cache-invalidation/src/test/resources/lifecycle-map.map.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data-map xmlns="http://cayenne.apache.org/schema/9/modelMap";
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+        xsi:schemaLocation="http://cayenne.apache.org/schema/9/modelMap 
http://cayenne.apache.org/schema/9/modelMap.xsd";
+        project-version="9">
+       <property name="defaultPackage" 
value="org.apache.cayenne.lifecycle.db"/>
+       <db-entity name="E1">
+               <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" 
isMandatory="true"/>
+       </db-entity>
+       <db-entity name="E2">
+               <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" 
isMandatory="true"/>
+       </db-entity>
+       <obj-entity name="E1" className="org.apache.cayenne.lifecycle.db.E1" 
dbEntityName="E1">
+       </obj-entity>
+       <obj-entity name="E2" className="org.apache.cayenne.lifecycle.db.E2" 
dbEntityName="E2">
+       </obj-entity>
+</data-map>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroups.java
----------------------------------------------------------------------
diff --git 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroups.java
 
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroups.java
deleted file mode 100644
index 2020575..0000000
--- 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroups.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*****************************************************************
- *   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.lifecycle.cache;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * A built-in annotation that provides declarative cache management for 
persistent
- * objects.
- * 
- * @since 3.1
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@Inherited
-public @interface CacheGroups {
-
-    /**
-     * Defines one or more cache group names associated with the tagged 
entity. If
-     * omitted, it is assumed that cache group names are determined 
dynamically based on
-     * the object type or state in whatever listener is to process them.
-     */
-    String[] value() default {};
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandler.java
----------------------------------------------------------------------
diff --git 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandler.java
 
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandler.java
deleted file mode 100644
index 330a30c..0000000
--- 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheGroupsHandler.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*****************************************************************
- *   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.lifecycle.cache;
-
-import java.util.Collection;
-
-import org.apache.cayenne.Persistent;
-
-import static java.util.Arrays.asList;
-
-/**
- * @since 4.0
- */
-public class CacheGroupsHandler implements InvalidationHandler {
-
-    /**
-     * Return invalidation function that returns values
-     * of {@link CacheGroups} annotations for the given type.
-     */
-    @Override
-    public InvalidationFunction canHandle(Class<? extends Persistent> type) {
-
-        CacheGroups a = type.getAnnotation(CacheGroups.class);
-        if (a == null) {
-            return null;
-        }
-
-        String[] groups = a.value();
-        if (groups.length == 0) {
-            return null;
-        }
-
-        final Collection<String> groupsList = asList(groups);
-        return new InvalidationFunction() {
-            @Override
-            public Collection<String> apply(Persistent persistent) {
-                return groupsList;
-            }
-        };
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationFilter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationFilter.java
 
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationFilter.java
deleted file mode 100644
index f18a668..0000000
--- 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationFilter.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*****************************************************************
- *   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.lifecycle.cache;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.DataChannelFilter;
-import org.apache.cayenne.DataChannelFilterChain;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.QueryResponse;
-import org.apache.cayenne.annotation.PrePersist;
-import org.apache.cayenne.annotation.PreRemove;
-import org.apache.cayenne.annotation.PreUpdate;
-import org.apache.cayenne.cache.QueryCache;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.di.Provider;
-import org.apache.cayenne.graph.GraphDiff;
-import org.apache.cayenne.query.Query;
-
-/**
- * <p>
- * A {@link DataChannelFilter} that invalidates cache groups.
- * Use custom rules for invalidation provided via DI.
- * </p>
- * <p>
- * Default rule is based on entities' {@link CacheGroups} annotation.
- * </p>
- * <p>
- *     To add default filter: <pre>
- *         ServerRuntime.builder("cayenne-project.xml")
- *              .addModule(CacheInvalidationModuleBuilder.builder().build());
- *     </pre>
- * </p>
- *
- * @since 3.1
- * @see InvalidationHandler
- * @see CacheInvalidationModuleBuilder
- */
-public class CacheInvalidationFilter implements DataChannelFilter {
-
-    @Inject
-    private Provider<QueryCache> cacheProvider;
-
-    @Inject(CacheInvalidationModuleBuilder.INVALIDATION_HANDLERS_LIST)
-    private List<InvalidationHandler> handlers;
-
-    private final Map<Class<? extends Persistent>, InvalidationFunction> 
mappedHandlers;
-
-    private final InvalidationFunction skipHandler;
-
-    private final ThreadLocal<Set<String>> groups;
-
-    public CacheInvalidationFilter() {
-        mappedHandlers = new ConcurrentHashMap<>();
-        skipHandler = new InvalidationFunction() {
-            @Override
-            public Collection<String> apply(Persistent p) {
-                return Collections.emptyList();
-            }
-        };
-        groups = new ThreadLocal<>();
-    }
-
-    public void init(DataChannel channel) {
-        // noop
-    }
-
-    public QueryResponse onQuery(ObjectContext originatingContext, Query 
query, DataChannelFilterChain filterChain) {
-        return filterChain.onQuery(originatingContext, query);
-    }
-
-    public GraphDiff onSync(ObjectContext originatingContext, GraphDiff 
changes,
-                            int syncType, DataChannelFilterChain filterChain) {
-        try {
-            GraphDiff result = filterChain.onSync(originatingContext, changes, 
syncType);
-            // no exceptions, flush...
-            Collection<String> groupSet = groups.get();
-            if (groupSet != null && !groupSet.isEmpty()) {
-                QueryCache cache = cacheProvider.get();
-                for (String group : groupSet) {
-                    cache.removeGroup(group);
-                }
-            }
-            return result;
-        } finally {
-            groups.set(null);
-        }
-    }
-
-    /**
-     * A callback method that records cache group to flush at the end of the 
commit.
-     */
-    @PrePersist
-    @PreRemove
-    @PreUpdate
-    protected void preCommit(Object object) {
-        // TODO: for some reason we can't use Persistent as the argument 
type... (is it fixed in Cayenne 4.0.M4?)
-        Persistent p = (Persistent) object;
-
-        InvalidationFunction invalidationFunction = 
mappedHandlers.get(p.getClass());
-        if(invalidationFunction == null) {
-            invalidationFunction = skipHandler;
-            for (InvalidationHandler handler : handlers) {
-                InvalidationFunction function = 
handler.canHandle(p.getClass());
-                if (function != null) {
-                    invalidationFunction = function;
-                    break;
-                }
-            }
-            mappedHandlers.put(p.getClass(), invalidationFunction);
-        }
-
-        Collection<String> objectGroups = invalidationFunction.apply(p);
-        if (!objectGroups.isEmpty()) {
-            getOrCreateTxGroups().addAll(objectGroups);
-        }
-    }
-
-
-    protected Set<String> getOrCreateTxGroups() {
-        Set<String> txGroups = groups.get();
-        if (txGroups == null) {
-            txGroups = new HashSet<>();
-            groups.set(txGroups);
-        }
-
-        return txGroups;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleBuilder.java
 
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleBuilder.java
deleted file mode 100644
index 7b3b6bb..0000000
--- 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/CacheInvalidationModuleBuilder.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*****************************************************************
- *   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.lifecycle.cache;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-import org.apache.cayenne.configuration.server.ServerModule;
-import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.ListBuilder;
-import org.apache.cayenne.di.Module;
-import org.apache.cayenne.tx.TransactionFilter;
-
-/**
- * @since 4.0
- */
-public class CacheInvalidationModuleBuilder {
-
-    public static final String INVALIDATION_HANDLERS_LIST = 
"cayenne.querycache.invalidation_handlers";
-
-    private Collection<Class<? extends InvalidationHandler>> handlerTypes;
-
-    private Collection<InvalidationHandler> handlerInstances;
-
-    private boolean noCacheGroupsHandler;
-
-    public static CacheInvalidationModuleBuilder builder() {
-        return new CacheInvalidationModuleBuilder();
-    }
-
-    private static ListBuilder<InvalidationHandler> 
contributeInvalidationHandler(Binder binder) {
-        return binder.bindList(InvalidationHandler.class, 
INVALIDATION_HANDLERS_LIST);
-    }
-
-    CacheInvalidationModuleBuilder() {
-        this.handlerTypes = new HashSet<>();
-        this.handlerInstances = new HashSet<>();
-    }
-
-    /**
-     * Disable {@link CacheGroupsHandler} based on {@link CacheGroups} 
annotation.
-     */
-    public CacheInvalidationModuleBuilder noCacheGroupsHandler() {
-        noCacheGroupsHandler = true;
-        return this;
-    }
-
-    public CacheInvalidationModuleBuilder invalidationHandler(Class<? extends 
InvalidationHandler> handlerType) {
-        handlerTypes.add(handlerType);
-        return this;
-    }
-
-    public CacheInvalidationModuleBuilder 
invalidationHandler(InvalidationHandler handlerInstance) {
-        handlerInstances.add(handlerInstance);
-        return this;
-    }
-
-    public Module build() {
-        return new Module() {
-            @Override
-            public void configure(Binder binder) {
-                ListBuilder<InvalidationHandler> handlers = 
contributeInvalidationHandler(binder);
-
-                if(!noCacheGroupsHandler) {
-                    handlers.add(CacheGroupsHandler.class);
-                }
-                handlers.addAll(handlerInstances);
-
-                for(Class<? extends InvalidationHandler> handlerType : 
handlerTypes) {
-                    handlers.add(handlerType);
-                }
-
-                // want the filter to be INSIDE transaction
-                ServerModule.contributeDomainFilters(binder)
-                        .insertBefore(CacheInvalidationFilter.class, 
TransactionFilter.class);
-            }
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationFunction.java
----------------------------------------------------------------------
diff --git 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationFunction.java
 
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationFunction.java
deleted file mode 100644
index 8ae16db..0000000
--- 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationFunction.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*****************************************************************
- *   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.lifecycle.cache;
-
-import java.util.Collection;
-
-import org.apache.cayenne.Persistent;
-
-/**
- * @since 4.0
- */
-public interface InvalidationFunction {
-
-    /**
-     * @return collection of cache groups to invalidate for given object
-     */
-    Collection<String> apply(Persistent persistent);
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d71bfe1b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationHandler.java
----------------------------------------------------------------------
diff --git 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationHandler.java
 
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationHandler.java
deleted file mode 100644
index a667ead..0000000
--- 
a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/cache/InvalidationHandler.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*****************************************************************
- *   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.lifecycle.cache;
-
-import org.apache.cayenne.Persistent;
-
-/**
- * A pluggable handler to invalidate cache groups on changes in certain 
objects.
- * @since 4.0
- */
-public interface InvalidationHandler {
-
-    /**
-     * @return invalidation function or null if there is nothing to invalidate
-     */
-    InvalidationFunction canHandle(Class<? extends Persistent> type);
-
-}

Reply via email to