You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by rm...@apache.org on 2018/03/20 13:53:33 UTC

[geronimo-jcache-simple] branch master created (now e104016)

This is an automated email from the ASF dual-hosted git repository.

rmannibucau pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/geronimo-jcache-simple.git.


      at e104016  initial import

This branch includes the following new commits:

     new e104016  initial import

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


-- 
To stop receiving notification emails like this one, please contact
rmannibucau@apache.org.

[geronimo-jcache-simple] 01/01: initial import

Posted by rm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/geronimo-jcache-simple.git

commit e104016642f0eafbb58822ec79b82adea6c10239
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Tue Mar 20 14:53:23 2018 +0100

    initial import
---
 .gitignore                                         |   5 +
 README.adoc                                        |  12 +
 pom.xml                                            | 253 ++++++
 .../org/apache/geronimo/jcache/simple/Asserts.java |  32 +
 .../jcache/simple/ClassLoaderAwareCache.java       | 372 +++++++++
 .../simple/ConfigurableMBeanServerIdBuilder.java   | 153 ++++
 .../jcache/simple/ExceptionWrapperHandler.java     |  69 ++
 .../org/apache/geronimo/jcache/simple/JMXs.java    |  65 ++
 .../apache/geronimo/jcache/simple/NoLoader.java    |  48 ++
 .../apache/geronimo/jcache/simple/NoWriter.java    |  54 ++
 .../geronimo/jcache/simple/Serializations.java     | 127 +++
 .../apache/geronimo/jcache/simple/SimpleCache.java | 856 +++++++++++++++++++++
 .../geronimo/jcache/simple/SimpleCacheMXBean.java  |  92 +++
 .../jcache/simple/SimpleCacheStatisticsMXBean.java | 106 +++
 .../jcache/simple/SimpleConfiguration.java         | 172 +++++
 .../geronimo/jcache/simple/SimpleElement.java      |  42 +
 .../apache/geronimo/jcache/simple/SimpleEntry.java |  51 ++
 .../apache/geronimo/jcache/simple/SimpleEvent.java |  69 ++
 .../apache/geronimo/jcache/simple/SimpleKey.java   |  59 ++
 .../geronimo/jcache/simple/SimpleListener.java     | 126 +++
 .../geronimo/jcache/simple/SimpleManager.java      | 258 +++++++
 .../geronimo/jcache/simple/SimpleMutableEntry.java |  67 ++
 .../geronimo/jcache/simple/SimpleProvider.java     | 136 ++++
 .../jcache/simple/SimpleThreadFactory.java         |  47 ++
 .../apache/geronimo/jcache/simple/Statistics.java  | 139 ++++
 .../geronimo/jcache/simple/TempStateCacheView.java | 283 +++++++
 .../org/apache/geronimo/jcache/simple/Times.java   |  33 +
 .../jcache/simple/cdi/CDIJCacheHelper.java         | 584 ++++++++++++++
 .../simple/cdi/CacheInvocationContextImpl.java     |  91 +++
 .../simple/cdi/CacheInvocationParameterImpl.java   |  63 ++
 .../jcache/simple/cdi/CacheKeyGeneratorImpl.java   |  39 +
 .../simple/cdi/CacheKeyInvocationContextImpl.java  |  54 ++
 .../jcache/simple/cdi/CacheMethodDetailsImpl.java  |  68 ++
 .../jcache/simple/cdi/CachePutInterceptor.java     |  82 ++
 .../simple/cdi/CacheRemoveAllInterceptor.java      |  78 ++
 .../jcache/simple/cdi/CacheRemoveInterceptor.java  |  82 ++
 .../simple/cdi/CacheResolverFactoryImpl.java       |  75 ++
 .../jcache/simple/cdi/CacheResolverImpl.java       |  39 +
 .../jcache/simple/cdi/CacheResultInterceptor.java  |  94 +++
 .../jcache/simple/cdi/GeneratedCacheKeyImpl.java   |  53 ++
 .../cdi/MakeJCacheCDIInterceptorFriendly.java      | 227 ++++++
 .../services/javax.cache.spi.CachingProvider       |   1 +
 .../jcache/simple/tck/OWBBeanProvider.java         |  61 ++
 src/test/resources/ExcludeList                     |  19 +
 .../services/javax.cache.annotation.BeanProvider   |   1 +
 45 files changed, 5437 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..580c946
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.idea
+*.iml
+.classpath
+.settings
+target
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..9ec0965
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,12 @@
+= Simple JCache Implementation
+
+A light implementation simply backed by a ConcurrentHashMap.
+
+It is intended for reference data cache usages.
+
+There are three modules:
+
+- default ones embeds next two
+- cdi is only the CDI integration
+- standalone is only the API implementation without CDI support
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..67272b1
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,253 @@
+<?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">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache</groupId>
+    <artifactId>apache</artifactId>
+    <version>18</version>
+  </parent>
+
+  <groupId>org.apache.geronimo</groupId>
+  <artifactId>geronimo-jcache-simple</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <name>Geronimo :: Simple JCache Implementation</name>
+
+  <properties>
+
+    <implementation-groupId>${project.groupId}</implementation-groupId>
+    <implementation-artifactId>commons-jcs</implementation-artifactId>
+    <implementation-version>${project.version}</implementation-version>
+
+    <CacheManagerImpl>org.apache.geronimo.jcache.simple.SimpleManager</CacheManagerImpl>
+    <CacheImpl>org.apache.geronimo.jcache.simple.SimpleCache</CacheImpl>
+    <CacheEntryImpl>org.apache.geronimo.jcache.simple.SimpleEntry</CacheEntryImpl>
+    <CacheInvocationContextImpl>org.apache.geronimo.jcache.simple.cdi.CacheKeyInvocationContextImpl
+    </CacheInvocationContextImpl>
+
+    <javax.management.builder.initial>org.apache.geronimo.jcache.simple.ConfigurableMBeanServerIdBuilder
+    </javax.management.builder.initial>
+    <org.jsr107.tck.management.agentId>MBeanServerGeronimo</org.jsr107.tck.management.agentId>
+
+    <domain-lib-dir>${project.build.directory}/domainlib</domain-lib-dir>
+    <domain-jar>domain.jar</domain-jar>
+
+    <jcache.version>1.0.0</jcache.version>
+    <tck.version>1.1.0</tck.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jcache_1.0_spec</artifactId>
+      <version>1.0-alpha-1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jcdi_2.0_spec</artifactId>
+      <version>1.0.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-atinject_1.0_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-interceptor_1.2_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-annotation_1.3_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-library</artifactId>
+      <version>1.3</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.cache</groupId>
+      <artifactId>test-domain</artifactId>
+      <version>${jcache.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.cache</groupId>
+      <artifactId>app-domain</artifactId>
+      <version>${jcache.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.cache</groupId>
+      <artifactId>cache-tests</artifactId>
+      <version>${tck.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.cache</groupId>
+      <artifactId>cache-tests</artifactId>
+      <version>${tck.version}</version>
+      <classifier>tests</classifier>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.openwebbeans</groupId>
+      <artifactId>openwebbeans-impl</artifactId>
+      <version>2.0.2</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <testResources>
+      <testResource>
+        <directory>src/test/resources</directory>
+        <filtering>true</filtering>
+      </testResource>
+    </testResources>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <configuration>
+          <source>1.7</source>
+          <target>1.7</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>3.0.2</version>
+        <executions>
+          <execution> <!-- standalone + cdi -->
+            <id>default-jar</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+          <execution> <!-- cdi only -->
+            <id>cdi-jar</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <classifier>cdi</classifier>
+              <includes>
+                <include>org/apache/geronimo/jcache/simple/cdi/*</include>
+                <include>META-INF/services/javax.enterprise.inject.spi.Extension</include>
+              </includes>
+            </configuration>
+          </execution>
+          <execution> <!-- standalone only -->
+            <id>no-cdi-jar</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <classifier>standalone</classifier>
+              <excludes>
+                <exclude>org/apache/geronimo/jcache/simple/cdi/</exclude>
+                <exclude>META-INF/services/javax.enterprise.inject.spi.Extension</exclude>
+              </excludes>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>copy-cache-tests</id>
+            <phase>generate-test-resources</phase>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${project.build.testOutputDirectory}</outputDirectory>
+              <includeArtifactIds>cache-tests</includeArtifactIds>
+              <includeScope>test</includeScope>
+              <excludes>**/unwrap.properties</excludes>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-domain</id>
+            <phase>generate-test-resources</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>javax.cache</groupId>
+                  <artifactId>app-domain</artifactId>
+                  <version>${jcache.version}</version>
+                  <outputDirectory>${domain-lib-dir}</outputDirectory>
+                  <destFileName>${domain-jar}</destFileName>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.20.1</version>
+        <configuration>
+          <systemPropertyVariables>
+            <domainJar>${domain-lib-dir}/${domain-jar}</domainJar>
+            <javax.management.builder.initial>${javax.management.builder.initial}</javax.management.builder.initial>
+            <org.jsr107.tck.management.agentId>${org.jsr107.tck.management.agentId}</org.jsr107.tck.management.agentId>
+            <javax.cache.CacheManager>${CacheManagerImpl}</javax.cache.CacheManager>
+            <javax.cache.Cache>${CacheImpl}</javax.cache.Cache>
+            <javax.cache.Cache.Entry>${CacheEntryImpl}</javax.cache.Cache.Entry>
+            <javax.cache.annotation.CacheInvocationContext>${CacheInvocationContextImpl}
+            </javax.cache.annotation.CacheInvocationContext>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/Asserts.java b/src/main/java/org/apache/geronimo/jcache/simple/Asserts.java
new file mode 100644
index 0000000..a45de9e
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/Asserts.java
@@ -0,0 +1,32 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+public final class Asserts {
+
+    private Asserts() {
+        // no-op
+    }
+
+    static void assertNotNull(final Object value, final String msg) {
+        if (value == null) {
+            throw new NullPointerException(msg);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/ClassLoaderAwareCache.java b/src/main/java/org/apache/geronimo/jcache/simple/ClassLoaderAwareCache.java
new file mode 100644
index 0000000..0c398c8
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/ClassLoaderAwareCache.java
@@ -0,0 +1,372 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.integration.CompletionListener;
+import javax.cache.processor.EntryProcessor;
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.EntryProcessorResult;
+
+class ClassLoaderAwareCache<K, V> implements Cache<K, V> {
+
+    private final ClassLoader loader;
+
+    private final SimpleCache<K, V> delegate;
+
+    ClassLoaderAwareCache(final ClassLoader loader, final SimpleCache<K, V> delegate) {
+        this.loader = loader;
+        this.delegate = delegate;
+    }
+
+    private ClassLoader before(final Thread thread) {
+        final ClassLoader tccl = thread.getContextClassLoader();
+        thread.setContextClassLoader(loader);
+        return tccl;
+    }
+
+    public V get(final K key) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.get(key);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public Map<K, V> getAll(final Set<? extends K> keys) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.getAll(keys);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public boolean containsKey(final K key) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.containsKey(key);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public void loadAll(final Set<? extends K> keys, boolean replaceExistingValues, final CompletionListener completionListener) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.loadAll(keys, replaceExistingValues, completionListener);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public void put(final K key, final V value) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.put(key, value);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public V getAndPut(final K key, final V value) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.getAndPut(key, value);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public void putAll(final Map<? extends K, ? extends V> map) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.putAll(map);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public boolean putIfAbsent(final K key, final V value) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.putIfAbsent(key, value);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public boolean remove(final K key) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.remove(key);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public boolean remove(final K key, final V oldValue) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.remove(key, oldValue);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public V getAndRemove(final K key) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.getAndRemove(key);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public boolean replace(final K key, final V oldValue, final V newValue) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.replace(key, oldValue, newValue);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public boolean replace(final K key, final V value) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.replace(key, value);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public V getAndReplace(final K key, final V value) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.getAndReplace(key, value);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public void removeAll(final Set<? extends K> keys) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.removeAll(keys);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void removeAll() {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.removeAll();
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void clear() {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.clear();
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public <C extends Configuration<K, V>> C getConfiguration(final Class<C> clazz) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.getConfiguration(clazz);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments)
+            throws EntryProcessorException {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.invoke(key, entryProcessor, arguments);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public <T> Map<K, EntryProcessorResult<T>> invokeAll(final Set<? extends K> keys,
+            final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.invokeAll(keys, entryProcessor, arguments);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public String getName() {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.getName();
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public CacheManager getCacheManager() {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.getCacheManager();
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public void close() {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.close();
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean isClosed() {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.isClosed();
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.unwrap(clazz);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.registerCacheEntryListener(cacheEntryListenerConfiguration);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            delegate.deregisterCacheEntryListener(cacheEntryListenerConfiguration);
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public Iterator<Entry<K, V>> iterator() {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader loader = before(thread);
+        try {
+            return delegate.iterator();
+        } finally {
+            thread.setContextClassLoader(loader);
+        }
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (ClassLoaderAwareCache.class.isInstance(obj)) {
+            return delegate.equals(ClassLoaderAwareCache.class.cast(obj).delegate);
+        }
+        return super.equals(obj);
+    }
+
+    @Override
+    public int hashCode() {
+        return delegate.hashCode();
+    }
+
+    public static <K, V> Cache<K, V> wrap(final ClassLoader loader, final SimpleCache<K, V> delegate) {
+        ClassLoader dontWrapLoader = ClassLoaderAwareCache.class.getClassLoader();
+        while (dontWrapLoader != null) {
+            if (loader == dontWrapLoader) {
+                return delegate;
+            }
+            dontWrapLoader = dontWrapLoader.getParent();
+        }
+        return new ClassLoaderAwareCache<>(loader, delegate);
+    }
+
+    public static <K extends Serializable, V extends Serializable> SimpleCache<K, V> getDelegate(final Cache<?, ?> cache) {
+        if (SimpleCache.class.isInstance(cache)) {
+            return (SimpleCache<K, V>) cache;
+        }
+        return ((ClassLoaderAwareCache<K, V>) cache).delegate;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/ConfigurableMBeanServerIdBuilder.java b/src/main/java/org/apache/geronimo/jcache/simple/ConfigurableMBeanServerIdBuilder.java
new file mode 100644
index 0000000..71307d4
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/ConfigurableMBeanServerIdBuilder.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.geronimo.jcache.simple;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerBuilder;
+import javax.management.MBeanServerDelegate;
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+
+public class ConfigurableMBeanServerIdBuilder extends MBeanServerBuilder {
+
+    private static ConcurrentMap<Key, MBeanServer> JVM_SINGLETONS = new ConcurrentHashMap<>();
+
+    @Override
+    public MBeanServer newMBeanServer(final String defaultDomain, final MBeanServer outer, final MBeanServerDelegate delegate) {
+        final Key key = new Key(defaultDomain, outer);
+        MBeanServer server = JVM_SINGLETONS.get(key);
+        if (server == null) {
+            server = super.newMBeanServer(defaultDomain, outer, new ForceIdMBeanServerDelegate(delegate));
+            final MBeanServer existing = JVM_SINGLETONS.putIfAbsent(key, server);
+            if (existing != null) {
+                server = existing;
+            }
+        }
+        return server;
+    }
+
+    private static class Key {
+
+        private final String domain;
+
+        private final MBeanServer outer;
+
+        private Key(final String domain, final MBeanServer outer) {
+            this.domain = domain;
+            this.outer = outer;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            final Key key = Key.class.cast(o);
+            return !(domain != null ? !domain.equals(key.domain) : key.domain != null)
+                    && !(outer != null ? !outer.equals(key.outer) : key.outer != null);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = domain != null ? domain.hashCode() : 0;
+            result = 31 * result + (outer != null ? outer.hashCode() : 0);
+            return result;
+        }
+    }
+
+    private class ForceIdMBeanServerDelegate extends MBeanServerDelegate {
+
+        private final MBeanServerDelegate delegate;
+
+        public ForceIdMBeanServerDelegate(final MBeanServerDelegate delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public String getMBeanServerId() {
+            return System.getProperty("org.jsr107.tck.management.agentId", delegate.getMBeanServerId());
+        }
+
+        @Override
+        public String getSpecificationName() {
+            return delegate.getSpecificationName();
+        }
+
+        @Override
+        public String getSpecificationVersion() {
+            return delegate.getSpecificationVersion();
+        }
+
+        @Override
+        public String getSpecificationVendor() {
+            return delegate.getSpecificationVendor();
+        }
+
+        @Override
+        public String getImplementationName() {
+            return delegate.getImplementationName();
+        }
+
+        @Override
+        public String getImplementationVersion() {
+            return delegate.getImplementationVersion();
+        }
+
+        @Override
+        public String getImplementationVendor() {
+            return delegate.getImplementationVendor();
+        }
+
+        @Override
+        public MBeanNotificationInfo[] getNotificationInfo() {
+            return delegate.getNotificationInfo();
+        }
+
+        @Override
+        public void addNotificationListener(final NotificationListener listener, final NotificationFilter filter,
+                final Object handback) throws IllegalArgumentException {
+            delegate.addNotificationListener(listener, filter, handback);
+        }
+
+        @Override
+        public void removeNotificationListener(final NotificationListener listener, final NotificationFilter filter,
+                final Object handback) throws ListenerNotFoundException {
+            delegate.removeNotificationListener(listener, filter, handback);
+        }
+
+        @Override
+        public void removeNotificationListener(final NotificationListener listener) throws ListenerNotFoundException {
+            delegate.removeNotificationListener(listener);
+        }
+
+        @Override
+        public void sendNotification(final Notification notification) {
+            delegate.sendNotification(notification);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/ExceptionWrapperHandler.java b/src/main/java/org/apache/geronimo/jcache/simple/ExceptionWrapperHandler.java
new file mode 100644
index 0000000..f98bdd5
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/ExceptionWrapperHandler.java
@@ -0,0 +1,69 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class ExceptionWrapperHandler<T> implements InvocationHandler {
+
+    private final T delegate;
+
+    private final Constructor<? extends RuntimeException> wrapper;
+
+    public ExceptionWrapperHandler(final T delegate, final Class<? extends RuntimeException> exceptionType) {
+        this.delegate = delegate;
+        try {
+            this.wrapper = exceptionType.getConstructor(Throwable.class);
+        } catch (final NoSuchMethodException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+        if (AutoCloseable.class == method.getDeclaringClass() && !AutoCloseable.class.isInstance(delegate)) {
+            return null;
+        }
+        try {
+            return method.invoke(delegate, args);
+        } catch (final InvocationTargetException ite) {
+            final Throwable e = ite.getCause();
+            if (RuntimeException.class.isInstance(e)) {
+                final RuntimeException re;
+                try {
+                    re = wrapper.newInstance(e);
+                } catch (final Exception e1) {
+                    throw new IllegalArgumentException(e1);
+                }
+                throw re;
+            }
+            throw e;
+        }
+    }
+
+    public static <T> T newProxy(final ClassLoader loader, final T delegate,
+            final Class<? extends RuntimeException> exceptionType, final Class<T> apis) {
+        return (T) Proxy.newProxyInstance(loader, new Class<?>[] { apis, AutoCloseable.class },
+                new ExceptionWrapperHandler<>(delegate, exceptionType));
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/JMXs.java b/src/main/java/org/apache/geronimo/jcache/simple/JMXs.java
new file mode 100644
index 0000000..6356240
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/JMXs.java
@@ -0,0 +1,65 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+public class JMXs {
+
+    private static final MBeanServer SERVER = findMBeanServer();
+
+    private JMXs() {
+        // no-op
+    }
+
+    public static MBeanServer server() {
+        return SERVER;
+    }
+
+    public static void register(final ObjectName on, final Object bean) {
+        if (!SERVER.isRegistered(on)) {
+            try {
+                SERVER.registerMBean(bean, on);
+            } catch (final Exception e) {
+                throw new IllegalStateException(e.getMessage(), e);
+            }
+        }
+    }
+
+    public static void unregister(final ObjectName on) {
+        if (SERVER.isRegistered(on)) {
+            try {
+                SERVER.unregisterMBean(on);
+            } catch (final Exception e) {
+                // no-op
+            }
+        }
+    }
+
+    private static MBeanServer findMBeanServer() {
+        if (System.getProperty("javax.management.builder.initial") != null) {
+            return MBeanServerFactory.createMBeanServer();
+        }
+        return ManagementFactory.getPlatformMBeanServer();
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/NoLoader.java b/src/main/java/org/apache/geronimo/jcache/simple/NoLoader.java
new file mode 100644
index 0000000..3f87589
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/NoLoader.java
@@ -0,0 +1,48 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheLoaderException;
+
+public class NoLoader<K, V> implements CacheLoader<K, V> {
+
+    public static final NoLoader INSTANCE = new NoLoader();
+
+    private NoLoader() {
+        // no-op
+    }
+
+    @Override
+    public V load(K key) throws CacheLoaderException {
+        return null;
+    }
+
+    @Override
+    public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException {
+        final Map<K, V> entries = new HashMap<K, V>();
+        for (final K k : keys) {
+            entries.put(k, null);
+        }
+        return entries;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/NoWriter.java b/src/main/java/org/apache/geronimo/jcache/simple/NoWriter.java
new file mode 100644
index 0000000..3fb4052
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/NoWriter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.util.Collection;
+
+import javax.cache.Cache;
+import javax.cache.integration.CacheWriter;
+import javax.cache.integration.CacheWriterException;
+
+public class NoWriter<K, V> implements CacheWriter<K, V> {
+
+    public static final NoWriter INSTANCE = new NoWriter();
+
+    @Override
+    public void write(final Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException {
+        // no-op
+    }
+
+    @Override
+    public void delete(final Object key) throws CacheWriterException {
+        // no-op
+    }
+
+    @Override
+    public void writeAll(final Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException {
+        for (final Cache.Entry<? extends K, ? extends V> entry : entries) {
+            write(entry);
+        }
+    }
+
+    @Override
+    public void deleteAll(final Collection<?> keys) throws CacheWriterException {
+        for (final Object k : keys) {
+            delete(k);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/Serializations.java b/src/main/java/org/apache/geronimo/jcache/simple/Serializations.java
new file mode 100644
index 0000000..fecd387
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/Serializations.java
@@ -0,0 +1,127 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import static java.util.Arrays.asList;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.regex.Pattern;
+
+public class Serializations {
+
+    private final Collection<String> acceptedClasses;
+
+    public Serializations(final String acceptedClasses) {
+        this.acceptedClasses = acceptedClasses == null ? Collections.<String> emptySet()
+                : new HashSet<>(asList(acceptedClasses.split(",")));
+    }
+
+    public <K> K copy(final ClassLoader loader, final K key) {
+        try {
+            return deSerialize(serialize(key), loader);
+        } catch (final Exception e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private <T> byte[] serialize(final T obj) throws IOException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ObjectOutputStream oos = new ObjectOutputStream(baos);
+        try {
+            oos.writeObject(obj);
+        } finally {
+            oos.close();
+        }
+        return baos.toByteArray();
+    }
+
+    private <T> T deSerialize(final byte[] data, final ClassLoader loader) throws IOException, ClassNotFoundException {
+        final ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        final BufferedInputStream bis = new BufferedInputStream(bais);
+        final ObjectInputStream ois = new ObjectInputStreamClassLoaderAware(bis, loader, acceptedClasses);
+        try {
+            return (T) ois.readObject();
+        } finally {
+            ois.close();
+        }
+    }
+
+    private static class ObjectInputStreamClassLoaderAware extends ObjectInputStream {
+
+        private static final Pattern PRIMITIVE_ARRAY = Pattern.compile("^\\[+[BCDFIJSVZ]$");
+
+        private final ClassLoader classLoader;
+
+        private final Collection<String> accepted;
+
+        public ObjectInputStreamClassLoaderAware(final InputStream in, final ClassLoader classLoader,
+                final Collection<String> accepted) throws IOException {
+            super(in);
+            this.classLoader = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader();
+            this.accepted = accepted;
+        }
+
+        @Override
+        protected Class<?> resolveClass(final ObjectStreamClass desc) throws ClassNotFoundException {
+            if (isAccepted(desc.getName())) {
+                return Class.forName(desc.getName(), false, classLoader);
+            }
+            throw new SecurityException(desc.getName() + " not whitelisted");
+        }
+
+        @Override
+        protected Class resolveProxyClass(final String[] interfaces) throws ClassNotFoundException {
+            final Class[] cinterfaces = new Class[interfaces.length];
+            for (int i = 0; i < cinterfaces.length; i++) {
+                if (isAccepted(interfaces[i])) {
+                    cinterfaces[i] = Class.forName(interfaces[i], false, classLoader);
+                } else {
+                    throw new SecurityException(interfaces[i] + " not whitelisted");
+                }
+            }
+
+            try {
+                return Proxy.getProxyClass(classLoader, cinterfaces);
+            } catch (IllegalArgumentException e) {
+                throw new ClassNotFoundException(null, e);
+            }
+        }
+
+        private boolean isAccepted(final String name) {
+            if (PRIMITIVE_ARRAY.matcher(name).matches()) {
+                return false;
+            }
+            if (name.startsWith("[L") && name.endsWith(";")) {
+                return isAccepted(name.substring(2, name.length() - 1));
+            }
+            return !accepted.contains(name);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleCache.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleCache.java
new file mode 100644
index 0000000..b6d95bc
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleCache.java
@@ -0,0 +1,856 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import static org.apache.geronimo.jcache.simple.Asserts.assertNotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.cache.Cache;
+import javax.cache.CacheException;
+import javax.cache.CacheManager;
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.Factory;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.EventType;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.EternalExpiryPolicy;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheLoaderException;
+import javax.cache.integration.CacheWriter;
+import javax.cache.integration.CacheWriterException;
+import javax.cache.integration.CompletionListener;
+import javax.cache.processor.EntryProcessor;
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.EntryProcessorResult;
+import javax.management.ObjectName;
+
+public class SimpleCache<K, V> implements Cache<K, V> {
+
+    private final SimpleManager manager;
+
+    private final SimpleConfiguration<K, V> config;
+
+    private final CacheLoader<K, V> loader;
+
+    private final CacheWriter<? super K, ? super V> writer;
+
+    private final ExpiryPolicy expiryPolicy;
+
+    private final ObjectName cacheConfigObjectName;
+
+    private final ObjectName cacheStatsObjectName;
+
+    private final String name;
+
+    private final ConcurrentHashMap<SimpleKey<K>, SimpleElement<V>> delegate;
+
+    private final Map<CacheEntryListenerConfiguration<K, V>, SimpleListener<K, V>> listeners = new ConcurrentHashMap<>();
+
+    private final Statistics statistics = new Statistics();
+
+    private final ExecutorService pool;
+
+    private final Serializations serializations;
+
+    private volatile boolean closed = false;
+
+    public SimpleCache(final ClassLoader classLoader, final SimpleManager mgr, final String cacheName,
+            final SimpleConfiguration<K, V> configuration, final Properties properties) {
+        manager = mgr;
+
+        name = cacheName;
+
+        final int capacity = Integer.parseInt(property(properties, cacheName, "capacity", "1000"));
+        final float loadFactor = Float.parseFloat(property(properties, cacheName, "loadFactor", "0.75"));
+        final int concurrencyLevel = Integer.parseInt(property(properties, cacheName, "concurrencyLevel", "16"));
+        delegate = new ConcurrentHashMap<>(capacity, loadFactor, concurrencyLevel);
+        config = configuration;
+
+        ExecutorService executorService = rawProperty(properties, cacheName, "pool"); // lookup etc support
+        if (executorService == null) {
+            final int poolSize = Integer.parseInt(property(properties, cacheName, "pool.size", "3"));
+            final SimpleThreadFactory threadFactory = new SimpleThreadFactory("geronimo-simple-jcache-" + cacheName + "-");
+            executorService = poolSize > 0 ? Executors.newFixedThreadPool(poolSize, threadFactory)
+                    : Executors.newCachedThreadPool(threadFactory);
+        }
+        pool = executorService;
+
+        final long evictionPause = Long.parseLong(
+                properties.getProperty(cacheName + ".evictionPause", properties.getProperty("evictionPause", "30000")));
+        if (evictionPause > 0) {
+            final long maxDeleteByEvictionRun = Long.parseLong(property(properties, cacheName, "maxDeleteByEvictionRun", "100"));
+            pool.submit(new EvictionThread<>(this, evictionPause, maxDeleteByEvictionRun));
+        }
+
+        serializations = new Serializations(property(properties, cacheName, "serialization.whitelist", null));
+
+        final Factory<CacheLoader<K, V>> cacheLoaderFactory = configuration.getCacheLoaderFactory();
+        if (cacheLoaderFactory == null) {
+            loader = NoLoader.INSTANCE;
+        } else {
+            loader = ExceptionWrapperHandler.newProxy(classLoader, cacheLoaderFactory.create(), CacheLoaderException.class,
+                    CacheLoader.class);
+        }
+
+        final Factory<CacheWriter<? super K, ? super V>> cacheWriterFactory = configuration.getCacheWriterFactory();
+        if (cacheWriterFactory == null) {
+            writer = NoWriter.INSTANCE;
+        } else {
+            writer = ExceptionWrapperHandler.newProxy(classLoader, cacheWriterFactory.create(), CacheWriterException.class,
+                    CacheWriter.class);
+        }
+
+        final Factory<ExpiryPolicy> expiryPolicyFactory = configuration.getExpiryPolicyFactory();
+        if (expiryPolicyFactory == null) {
+            expiryPolicy = new EternalExpiryPolicy();
+        } else {
+            expiryPolicy = expiryPolicyFactory.create();
+        }
+
+        for (final CacheEntryListenerConfiguration<K, V> listener : config.getCacheEntryListenerConfigurations()) {
+            listeners.put(listener, new SimpleListener<>(listener));
+        }
+
+        statistics.setActive(config.isStatisticsEnabled());
+
+        final String mgrStr = manager.getURI().toString().replaceAll(",|:|=|\n", ".");
+        final String cacheStr = name.replaceAll(",|:|=|\n", ".");
+        try {
+            cacheConfigObjectName = new ObjectName(
+                    "javax.cache:type=CacheConfiguration," + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
+            cacheStatsObjectName = new ObjectName(
+                    "javax.cache:type=CacheStatistics," + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
+        } catch (final Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+        if (config.isManagementEnabled()) {
+            JMXs.register(cacheConfigObjectName, new SimpleCacheMXBean<K, V>(this));
+        }
+        if (config.isStatisticsEnabled()) {
+            JMXs.register(cacheStatsObjectName, new SimpleCacheStatisticsMXBean(statistics));
+        }
+    }
+
+    private void assertNotClosed() {
+        if (isClosed()) {
+            throw new IllegalStateException("cache closed");
+        }
+    }
+
+    @Override
+    public V get(final K key) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        final long getStart = Times.now(false);
+        return doGetControllingExpiry(getStart, key, true, false, false, true, loader);
+    }
+
+    private V doLoad(final K key, final boolean update, final boolean propagateLoadException, final CacheLoader<K, V> loader) {
+        V v = null;
+        try {
+            v = loader.load(key);
+        } catch (final CacheLoaderException e) {
+            if (propagateLoadException) {
+                throw e;
+            }
+        }
+        if (v != null) {
+            final Duration duration = update ? expiryPolicy.getExpiryForUpdate() : expiryPolicy.getExpiryForCreation();
+            if (isNotZero(duration)) {
+                delegate.put(new SimpleKey<>(key), new SimpleElement<>(v, duration));
+            }
+        }
+        return v;
+    }
+
+    private void touch(final SimpleKey<K> key, final SimpleElement<V> element) {
+        if (config.isStoreByValue()) {
+            delegate.put(new SimpleKey<>(serializations.copy(manager.getClassLoader(), key.getKey())), element);
+        }
+    }
+
+    @Override
+    public Map<K, V> getAll(final Set<? extends K> keys) {
+        assertNotClosed();
+        for (final K k : keys) {
+            assertNotNull(k, "key");
+        }
+
+        final Map<K, V> result = new HashMap<>();
+        for (final K key : keys) {
+            assertNotNull(key, "key");
+
+            final SimpleKey<K> simpleKey = new SimpleKey<>(key);
+            final SimpleElement<V> elt = delegate.get(simpleKey);
+            V val = elt != null ? elt.getElement() : null;
+            if (val == null && config.isReadThrough()) {
+                val = doLoad(key, false, false, loader);
+                if (val != null) {
+                    result.put(key, val);
+                }
+            } else if (elt != null) {
+                final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
+                if (isNotZero(expiryForAccess)) {
+                    touch(simpleKey, elt);
+                    result.put(key, val);
+                } else {
+                    expires(simpleKey);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean containsKey(final K key) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        return delegate.get(new SimpleKey<>(key)) != null;
+    }
+
+    @Override
+    public void put(final K key, final V rawValue) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(rawValue, "value");
+
+        final boolean storeByValue = config.isStoreByValue();
+        final SimpleKey<K> simpleKey = new SimpleKey<>(storeByValue ? serializations.copy(manager.getClassLoader(), key) : key);
+        final SimpleElement<V> oldElt = delegate.get(simpleKey);
+        final V old = oldElt != null ? oldElt.getElement() : null;
+        final V value = storeByValue ? serializations.copy(manager.getClassLoader(), rawValue) : rawValue;
+
+        final boolean created = old == null;
+        final Duration duration = created ? expiryPolicy.getExpiryForCreation() : expiryPolicy.getExpiryForUpdate();
+        if (isNotZero(duration)) {
+            final boolean statisticsEnabled = config.isStatisticsEnabled();
+            final long start = Times.now(false);
+
+            writer.write(new SimpleEntry<>(key, value));
+            delegate.put(simpleKey, new SimpleElement<>(value, duration));
+            if (!listeners.isEmpty()) {
+                for (final SimpleListener<K, V> listener : listeners.values()) {
+                    if (created) {
+                        listener.onCreated(Collections.<CacheEntryEvent<? extends K, ? extends V>> singletonList(
+                                new SimpleEvent<>(this, EventType.CREATED, null, key, value)));
+                    } else
+                        listener.onUpdated(Collections.<CacheEntryEvent<? extends K, ? extends V>> singletonList(
+                                new SimpleEvent<>(this, EventType.UPDATED, old, key, value)));
+                }
+            }
+
+            if (statisticsEnabled) {
+                statistics.increasePuts(1);
+                statistics.addPutTime(System.currentTimeMillis() - start);
+            }
+        } else {
+            if (!created) {
+                expires(simpleKey);
+            }
+        }
+    }
+
+    private void expires(final SimpleKey<K> cacheKey) {
+        final SimpleElement<V> elt = delegate.get(cacheKey);
+        delegate.remove(cacheKey);
+        for (final SimpleListener<K, V> listener : listeners.values()) {
+            listener.onExpired(Collections.<CacheEntryEvent<? extends K, ? extends V>> singletonList(
+                    new SimpleEvent<>(this, EventType.REMOVED, null, cacheKey.getKey(), elt.getElement())));
+        }
+    }
+
+    @Override
+    public V getAndPut(final K key, final V value) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(value, "value");
+        final long getStart = Times.now(false);
+        final V v = doGetControllingExpiry(getStart, key, false, false, true, false, loader);
+        put(key, value);
+        return v;
+    }
+
+    @Override
+    public void putAll(final Map<? extends K, ? extends V> map) {
+        assertNotClosed();
+        final TempStateCacheView<K, V> view = new TempStateCacheView<K, V>(this);
+        for (final Map.Entry<? extends K, ? extends V> e : map.entrySet()) {
+            view.put(e.getKey(), e.getValue());
+        }
+        view.merge();
+    }
+
+    @Override
+    public boolean putIfAbsent(final K key, final V value) {
+        final boolean statisticsEnabled = config.isStatisticsEnabled();
+        if (!containsKey(key)) {
+            if (statisticsEnabled) {
+                statistics.increaseMisses(1);
+            }
+            put(key, value);
+            return true;
+        } else {
+            if (statisticsEnabled) {
+                statistics.increaseHits(1);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean remove(final K key) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+
+        final boolean statisticsEnabled = config.isStatisticsEnabled();
+        final long start = Times.now(!statisticsEnabled);
+
+        writer.delete(key);
+        final SimpleKey<K> cacheKey = new SimpleKey<>(key);
+
+        final SimpleElement<V> v = delegate.remove(cacheKey);
+        if (v == null || v.isExpired()) {
+            return false;
+        }
+
+        final V value = v.getElement();
+        for (final SimpleListener<K, V> listener : listeners.values()) {
+            listener.onRemoved(Collections.<CacheEntryEvent<? extends K, ? extends V>> singletonList(
+                    new SimpleEvent<>(this, EventType.REMOVED, value, key, value)));
+        }
+        if (statisticsEnabled) {
+            statistics.increaseRemovals(1);
+            statistics.addRemoveTime(Times.now(false) - start);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean remove(final K key, final V oldValue) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(oldValue, "oldValue");
+        final long getStart = Times.now(false);
+        final V v = doGetControllingExpiry(getStart, key, false, false, false, false, loader);
+        if (oldValue.equals(v)) {
+            remove(key);
+            return true;
+        } else if (v != null) {
+            // weird but just for stats to be right
+            // (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods())
+            expiryPolicy.getExpiryForAccess();
+        }
+        return false;
+    }
+
+    @Override
+    public V getAndRemove(final K key) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        final long getStart = Times.now(false);
+        final V v = doGetControllingExpiry(getStart, key, false, false, true, false, loader);
+        remove(key);
+        return v;
+    }
+
+    private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad,
+            final boolean skipLoad, final boolean propagateLoadException, final CacheLoader<K, V> loader) {
+        final boolean statisticsEnabled = config.isStatisticsEnabled();
+        final SimpleKey<K> simpleKey = new SimpleKey<>(key);
+        final SimpleElement<V> elt = delegate.get(simpleKey);
+        V v = elt != null ? elt.getElement() : null;
+        if (v == null && (config.isReadThrough() || forceDoLoad)) {
+            if (!skipLoad) {
+                v = doLoad(key, false, propagateLoadException, loader);
+            }
+        } else if (statisticsEnabled) {
+            if (v != null) {
+                statistics.increaseHits(1);
+            } else {
+                statistics.increaseMisses(1);
+            }
+        }
+
+        if (updateAcess && elt != null) {
+            final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
+            if (!isNotZero(expiryForAccess)) {
+                expires(simpleKey);
+            }
+        }
+        if (statisticsEnabled && v != null) {
+            statistics.addGetTime(Times.now(false) - getStart);
+        }
+        return v;
+    }
+
+    @Override
+    public boolean replace(final K key, final V oldValue, final V newValue) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(oldValue, "oldValue");
+        assertNotNull(newValue, "newValue");
+        final V value = doGetControllingExpiry(Times.now(config.isStatisticsEnabled()), key, false, config.isReadThrough(), false,
+                true, loader);
+        if (value != null && value.equals(oldValue)) {
+            put(key, newValue);
+            return true;
+        } else if (value != null) {
+            expiryPolicy.getExpiryForAccess();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean replace(final K key, final V value) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(value, "value");
+        boolean statisticsEnabled = config.isStatisticsEnabled();
+        if (containsKey(key)) {
+            if (statisticsEnabled) {
+                statistics.increaseHits(1);
+            }
+            put(key, value);
+            return true;
+        } else if (statisticsEnabled) {
+            statistics.increaseMisses(1);
+        }
+        return false;
+    }
+
+    @Override
+    public V getAndReplace(final K key, final V value) {
+        assertNotClosed();
+        assertNotNull(key, "key");
+        assertNotNull(value, "value");
+
+        final boolean statisticsEnabled = config.isStatisticsEnabled();
+
+        final SimpleElement<V> elt = delegate.get(new SimpleKey<>(key));
+        if (elt != null) {
+            V oldValue = elt.getElement();
+            if (oldValue == null && config.isReadThrough()) {
+                oldValue = doLoad(key, false, false, loader);
+            } else if (statisticsEnabled) {
+                statistics.increaseHits(1);
+            }
+            put(key, value);
+            return oldValue;
+        } else if (statisticsEnabled) {
+            statistics.increaseMisses(1);
+        }
+        return null;
+    }
+
+    @Override
+    public void removeAll(final Set<? extends K> keys) {
+        assertNotClosed();
+        assertNotNull(keys, "keys");
+        for (final K k : keys) {
+            remove(k);
+        }
+    }
+
+    @Override
+    public void removeAll() {
+        assertNotClosed();
+        for (final SimpleKey<K> k : delegate.keySet()) {
+            remove(k.getKey());
+        }
+    }
+
+    @Override
+    public void clear() {
+        assertNotClosed();
+        delegate.clear();
+    }
+
+    @Override
+    public <C2 extends Configuration<K, V>> C2 getConfiguration(final Class<C2> clazz) {
+        assertNotClosed();
+        return clazz.cast(config);
+    }
+
+    @Override
+    public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues,
+            final CompletionListener completionListener) {
+        assertNotClosed();
+        assertNotNull(keys, "keys");
+        if (loader == null) { // quick exit path
+            if (completionListener != null) {
+                completionListener.onCompletion();
+            }
+            return;
+        }
+        for (final K k : keys) {
+            assertNotNull(k, "a key");
+        }
+        pool.submit(new Runnable() {
+
+            @Override
+            public void run() {
+                doLoadAll(keys, replaceExistingValues, completionListener);
+            }
+        });
+    }
+
+    private void doLoadAll(final Set<? extends K> keys, final boolean replaceExistingValues,
+            final CompletionListener completionListener) {
+        try {
+            final long now = Times.now(false);
+            final Map<K, V> kvMap = loader.loadAll(keys);
+            if (kvMap == null) {
+                return;
+            }
+            final CacheLoader<K, V> preloaded = new MapLoader<>(kvMap);
+            for (final K k : keys) {
+                if (replaceExistingValues) {
+                    doLoad(k, containsKey(k), completionListener != null, preloaded);
+                } else if (!containsKey(k)) {
+                    doGetControllingExpiry(now, k, true, true, false, completionListener != null, preloaded);
+                }
+            }
+        } catch (final RuntimeException e) {
+            if (completionListener != null) {
+                completionListener.onException(e);
+                return;
+            }
+        }
+        if (completionListener != null) {
+            completionListener.onCompletion();
+        }
+    }
+
+    @Override
+    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments)
+            throws EntryProcessorException {
+        final TempStateCacheView<K, V> view = new TempStateCacheView<K, V>(this);
+        final T t = doInvoke(view, key, entryProcessor, arguments);
+        view.merge();
+        return t;
+    }
+
+    private <T> T doInvoke(final TempStateCacheView<K, V> view, final K key, final EntryProcessor<K, V, T> entryProcessor,
+            final Object... arguments) {
+        assertNotClosed();
+        assertNotNull(entryProcessor, "entryProcessor");
+        assertNotNull(key, "key");
+        try {
+            if (config.isStatisticsEnabled()) {
+                if (containsKey(key)) {
+                    statistics.increaseHits(1);
+                } else {
+                    statistics.increaseMisses(1);
+                }
+            }
+            return entryProcessor.process(new SimpleMutableEntry<>(view, key), arguments);
+        } catch (final Exception ex) {
+            return throwEntryProcessorException(ex);
+        }
+    }
+
+    @Override
+    public <T> Map<K, EntryProcessorResult<T>> invokeAll(final Set<? extends K> keys,
+            final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) {
+        assertNotClosed();
+        assertNotNull(entryProcessor, "entryProcessor");
+        final Map<K, EntryProcessorResult<T>> results = new HashMap<>();
+        for (final K k : keys) {
+            try {
+                final T invoke = invoke(k, entryProcessor, arguments);
+                if (invoke != null) {
+                    results.put(k, new EntryProcessorResult<T>() {
+
+                        @Override
+                        public T get() throws EntryProcessorException {
+                            return invoke;
+                        }
+                    });
+                }
+            } catch (final Exception e) {
+                results.put(k, new EntryProcessorResult<T>() {
+
+                    @Override
+                    public T get() throws EntryProcessorException {
+                        return throwEntryProcessorException(e);
+                    }
+                });
+            }
+        }
+        return results;
+    }
+
+    @Override
+    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        assertNotClosed();
+        if (listeners.containsKey(cacheEntryListenerConfiguration)) {
+            throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered");
+        }
+        listeners.put(cacheEntryListenerConfiguration, new SimpleListener<>(cacheEntryListenerConfiguration));
+        config.addListener(cacheEntryListenerConfiguration);
+    }
+
+    @Override
+    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        assertNotClosed();
+        listeners.remove(cacheEntryListenerConfiguration);
+        config.removeListener(cacheEntryListenerConfiguration);
+    }
+
+    @Override
+    public Iterator<Entry<K, V>> iterator() {
+        assertNotClosed();
+        final Iterator<SimpleKey<K>> keys = new HashSet<>(delegate.keySet()).iterator();
+        return new Iterator<Entry<K, V>>() {
+
+            private K lastKey = null;
+
+            @Override
+            public boolean hasNext() {
+                return keys.hasNext();
+            }
+
+            @Override
+            public Entry<K, V> next() {
+                lastKey = keys.next().getKey();
+                return new SimpleEntry<>(lastKey, get(lastKey));
+            }
+
+            @Override
+            public void remove() {
+                if (isClosed() || lastKey == null) {
+                    throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()");
+                }
+                SimpleCache.this.remove(lastKey);
+            }
+        };
+    }
+
+    @Override
+    public String getName() {
+        assertNotClosed();
+        return name;
+    }
+
+    @Override
+    public CacheManager getCacheManager() {
+        assertNotClosed();
+        return manager;
+    }
+
+    @Override
+    public synchronized void close() {
+        if (isClosed()) {
+            return;
+        }
+
+        for (final Runnable task : pool.shutdownNow()) {
+            task.run();
+        }
+
+        // todo: better error handling (try/catch/log/suppressed)
+        manager.release(getName());
+        closed = true;
+        try {
+            close(loader);
+            close(writer);
+            close(expiryPolicy);
+        } catch (final Exception e) {
+            throw new CacheException(e);
+        }
+        for (final SimpleListener<K, V> listener : listeners.values()) {
+            try {
+                listener.close();
+            } catch (final Exception e) {
+                throw new CacheException(e);
+            }
+        }
+        listeners.clear();
+        JMXs.unregister(cacheConfigObjectName);
+        JMXs.unregister(cacheStatsObjectName);
+        delegate.clear();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return closed;
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz) {
+        assertNotClosed();
+        if (clazz.isInstance(this)) {
+            return clazz.cast(this);
+        }
+        if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class)) {
+            return clazz.cast(delegate);
+        }
+        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
+    }
+
+    public Statistics getStatistics() {
+        return statistics;
+    }
+
+    public void enableManagement() {
+        config.managementEnabled();
+        JMXs.register(cacheConfigObjectName, new SimpleCacheMXBean<K, V>(this));
+    }
+
+    public void disableManagement() {
+        config.managementDisabled();
+        JMXs.unregister(cacheConfigObjectName);
+    }
+
+    public void enableStatistics() {
+        config.statisticsEnabled();
+        statistics.setActive(true);
+        JMXs.register(cacheStatsObjectName, new SimpleCacheStatisticsMXBean(statistics));
+    }
+
+    public void disableStatistics() {
+        config.statisticsDisabled();
+        statistics.setActive(false);
+        JMXs.unregister(cacheStatsObjectName);
+    }
+
+    private static String property(final Properties properties, final String cacheName, final String name,
+            final String defaultValue) {
+        return properties.getProperty(cacheName + "." + name, properties.getProperty(name, defaultValue));
+    }
+
+    private static <T> T rawProperty(final Properties properties, final String cacheName, final String name) {
+        final Object value = properties.get(cacheName + "." + name);
+        if (value == null) {
+            return (T) properties.get(name);
+        }
+        return (T) value;
+    }
+
+    private static boolean isNotZero(final Duration duration) {
+        return duration == null || !duration.isZero();
+    }
+
+    private static <T> T throwEntryProcessorException(final Exception ex) {
+        if (EntryProcessorException.class.isInstance(ex)) {
+            throw EntryProcessorException.class.cast(ex);
+        }
+        throw new EntryProcessorException(ex);
+    }
+
+    private static void close(final Object potentiallyCloseable) throws Exception {
+        if (AutoCloseable.class.isInstance(potentiallyCloseable)) {
+            AutoCloseable.class.cast(potentiallyCloseable).close();
+        }
+    }
+
+    private static class EvictionThread<K> implements Runnable {
+
+        private final long pause;
+
+        private final long maxDelete;
+
+        private final SimpleCache<K, ?> cache;
+
+        private EvictionThread(final SimpleCache<K, ?> cache, final long evictionPause, final long maxDelete) {
+            this.cache = cache;
+            this.pause = evictionPause;
+            this.maxDelete = maxDelete;
+        }
+
+        @Override
+        public void run() {
+            while (!cache.isClosed()) {
+                try {
+                    Thread.sleep(pause * 10000);
+                } catch (final InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+
+                try {
+                    final List<SimpleKey<K>> keys = new ArrayList<>(new TreeSet<>(cache.delegate.keySet()));
+                    Collections.sort(keys, new Comparator<SimpleKey<K>>() {
+
+                        @Override
+                        public int compare(final SimpleKey<K> o1, final SimpleKey<K> o2) {
+                            final long l = o1.lastAccess() - o2.lastAccess();
+                            if (l == 0) {
+                                return keys.indexOf(o1) - keys.indexOf(o2);
+                            }
+                            return (int) l;
+                        }
+                    });
+
+                    int delete = 0;
+                    for (final SimpleKey<K> key : keys) {
+                        final SimpleElement<?> elt = cache.delegate.get(key);
+                        if (elt != null && elt.isExpired()) {
+                            cache.delegate.remove(key);
+                            cache.statistics.increaseEvictions(1);
+                            delete++;
+                            if (delete >= maxDelete) {
+                                break;
+                            }
+                        }
+                    }
+                } catch (final Exception e) {
+                    // no-op
+                }
+            }
+        }
+    }
+
+    private static class MapLoader<K, V> implements CacheLoader<K, V> {
+
+        private final Map<K, V> loaded;
+
+        private MapLoader(final Map<K, V> loaded) {
+            this.loaded = loaded;
+        }
+
+        @Override
+        public V load(final K key) throws CacheLoaderException {
+            return loaded.get(key);
+        }
+
+        @Override
+        public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheMXBean.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheMXBean.java
new file mode 100644
index 0000000..e40bebd
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheMXBean.java
@@ -0,0 +1,92 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import javax.cache.Cache;
+import javax.cache.configuration.CompleteConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.management.CacheMXBean;
+
+public class SimpleCacheMXBean<K, V> implements CacheMXBean {
+
+    private final Cache<K, V> delegate;
+
+    public SimpleCacheMXBean(final Cache<K, V> delegate) {
+        this.delegate = delegate;
+    }
+
+    private Configuration<K, V> config() {
+        return delegate.getConfiguration(Configuration.class);
+    }
+
+    private CompleteConfiguration<K, V> completeConfig() {
+        return delegate.getConfiguration(CompleteConfiguration.class);
+    }
+
+    @Override
+    public String getKeyType() {
+        return config().getKeyType().getName();
+    }
+
+    @Override
+    public String getValueType() {
+        return config().getValueType().getName();
+    }
+
+    @Override
+    public boolean isReadThrough() {
+        try {
+            return completeConfig().isReadThrough();
+        } catch (final Exception e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isWriteThrough() {
+        try {
+            return completeConfig().isWriteThrough();
+        } catch (final Exception e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isStoreByValue() {
+        return config().isStoreByValue();
+    }
+
+    @Override
+    public boolean isStatisticsEnabled() {
+        try {
+            return completeConfig().isStatisticsEnabled();
+        } catch (final Exception e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isManagementEnabled() {
+        try {
+            return completeConfig().isManagementEnabled();
+        } catch (final Exception e) {
+            return false;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheStatisticsMXBean.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheStatisticsMXBean.java
new file mode 100644
index 0000000..4360920
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleCacheStatisticsMXBean.java
@@ -0,0 +1,106 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import javax.cache.management.CacheStatisticsMXBean;
+
+public class SimpleCacheStatisticsMXBean implements CacheStatisticsMXBean {
+
+    private final Statistics statistics;
+
+    public SimpleCacheStatisticsMXBean(final Statistics stats) {
+        this.statistics = stats;
+    }
+
+    @Override
+    public void clear() {
+        statistics.reset();
+    }
+
+    @Override
+    public long getCacheHits() {
+        return statistics.getHits();
+    }
+
+    @Override
+    public float getCacheHitPercentage() {
+        final long hits = getCacheHits();
+        if (hits == 0) {
+            return 0;
+        }
+        return (float) hits / getCacheGets() * 100.0f;
+    }
+
+    @Override
+    public long getCacheMisses() {
+        return statistics.getMisses();
+    }
+
+    @Override
+    public float getCacheMissPercentage() {
+        final long misses = getCacheMisses();
+        if (misses == 0) {
+            return 0;
+        }
+        return (float) misses / getCacheGets() * 100.0f;
+    }
+
+    @Override
+    public long getCacheGets() {
+        return getCacheHits() + getCacheMisses();
+    }
+
+    @Override
+    public long getCachePuts() {
+        return statistics.getPuts();
+    }
+
+    @Override
+    public long getCacheRemovals() {
+        return statistics.getRemovals();
+    }
+
+    @Override
+    public long getCacheEvictions() {
+        return statistics.getEvictions();
+    }
+
+    @Override
+    public float getAverageGetTime() {
+        return averageTime(statistics.getTimeTakenForGets());
+    }
+
+    @Override
+    public float getAveragePutTime() {
+        return averageTime(statistics.getTimeTakenForPuts());
+    }
+
+    @Override
+    public float getAverageRemoveTime() {
+        return averageTime(statistics.getTimeTakenForRemovals());
+    }
+
+    private float averageTime(final long timeTaken) {
+        final long gets = getCacheGets();
+        if (timeTaken == 0 || gets == 0) {
+            return 0;
+        }
+        return timeTaken / gets;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleConfiguration.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleConfiguration.java
new file mode 100644
index 0000000..1f90ba2
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleConfiguration.java
@@ -0,0 +1,172 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import static java.util.Collections.unmodifiableSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.CompleteConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.configuration.Factory;
+import javax.cache.expiry.EternalExpiryPolicy;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheWriter;
+
+public class SimpleConfiguration<K, V> implements CompleteConfiguration<K, V> {
+
+    private final Class<K> keyType;
+
+    private final Class<V> valueType;
+
+    private final boolean storeByValue;
+
+    private final boolean readThrough;
+
+    private final boolean writeThrough;
+
+    private final Factory<CacheLoader<K, V>> cacheLoaderFactory;
+
+    private final Factory<CacheWriter<? super K, ? super V>> cacheWristerFactory;
+
+    private final Factory<ExpiryPolicy> expiryPolicyFactory;
+
+    private final Set<CacheEntryListenerConfiguration<K, V>> cacheEntryListenerConfigurations;
+
+    private volatile boolean statisticsEnabled;
+
+    private volatile boolean managementEnabled;
+
+    public SimpleConfiguration(final Configuration<K, V> configuration, final Class<K> keyType, final Class<V> valueType) {
+        this.keyType = keyType;
+        this.valueType = valueType;
+        if (configuration instanceof CompleteConfiguration) {
+            final CompleteConfiguration<K, V> cConfiguration = (CompleteConfiguration<K, V>) configuration;
+            storeByValue = configuration.isStoreByValue();
+            readThrough = cConfiguration.isReadThrough();
+            writeThrough = cConfiguration.isWriteThrough();
+            statisticsEnabled = cConfiguration.isStatisticsEnabled();
+            managementEnabled = cConfiguration.isManagementEnabled();
+            cacheLoaderFactory = cConfiguration.getCacheLoaderFactory();
+            cacheWristerFactory = cConfiguration.getCacheWriterFactory();
+            this.expiryPolicyFactory = cConfiguration.getExpiryPolicyFactory();
+            cacheEntryListenerConfigurations = new HashSet<>();
+
+            final Iterable<CacheEntryListenerConfiguration<K, V>> entryListenerConfigurations = cConfiguration
+                    .getCacheEntryListenerConfigurations();
+            if (entryListenerConfigurations != null) {
+                for (final CacheEntryListenerConfiguration<K, V> kvCacheEntryListenerConfiguration : entryListenerConfigurations) {
+                    cacheEntryListenerConfigurations.add(kvCacheEntryListenerConfiguration);
+                }
+            }
+        } else {
+            expiryPolicyFactory = EternalExpiryPolicy.factoryOf();
+            storeByValue = true;
+            readThrough = false;
+            writeThrough = false;
+            statisticsEnabled = false;
+            managementEnabled = false;
+            cacheLoaderFactory = null;
+            cacheWristerFactory = null;
+            cacheEntryListenerConfigurations = new HashSet<>();
+        }
+    }
+
+    @Override
+    public Class<K> getKeyType() {
+        return keyType == null ? (Class<K>) Object.class : keyType;
+    }
+
+    @Override
+    public Class<V> getValueType() {
+        return valueType == null ? (Class<V>) Object.class : valueType;
+    }
+
+    @Override
+    public boolean isStoreByValue() {
+        return storeByValue;
+    }
+
+    @Override
+    public boolean isReadThrough() {
+        return readThrough;
+    }
+
+    @Override
+    public boolean isWriteThrough() {
+        return writeThrough;
+    }
+
+    @Override
+    public boolean isStatisticsEnabled() {
+        return statisticsEnabled;
+    }
+
+    @Override
+    public boolean isManagementEnabled() {
+        return managementEnabled;
+    }
+
+    @Override
+    public Iterable<CacheEntryListenerConfiguration<K, V>> getCacheEntryListenerConfigurations() {
+        return unmodifiableSet(cacheEntryListenerConfigurations);
+    }
+
+    @Override
+    public Factory<CacheLoader<K, V>> getCacheLoaderFactory() {
+        return cacheLoaderFactory;
+    }
+
+    @Override
+    public Factory<CacheWriter<? super K, ? super V>> getCacheWriterFactory() {
+        return cacheWristerFactory;
+    }
+
+    @Override
+    public Factory<ExpiryPolicy> getExpiryPolicyFactory() {
+        return expiryPolicyFactory;
+    }
+
+    public synchronized void addListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        cacheEntryListenerConfigurations.add(cacheEntryListenerConfiguration);
+    }
+
+    public synchronized void removeListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        cacheEntryListenerConfigurations.remove(cacheEntryListenerConfiguration);
+    }
+
+    public void statisticsEnabled() {
+        statisticsEnabled = true;
+    }
+
+    public void managementEnabled() {
+        managementEnabled = true;
+    }
+
+    public void statisticsDisabled() {
+        statisticsEnabled = false;
+    }
+
+    public void managementDisabled() {
+        managementEnabled = false;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleElement.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleElement.java
new file mode 100644
index 0000000..e1314dc
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleElement.java
@@ -0,0 +1,42 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import javax.cache.expiry.Duration;
+
+public class SimpleElement<V> {
+
+    private final V element;
+
+    private final long end;
+
+    public SimpleElement(final V element, final Duration duration) {
+        this.element = element;
+        this.end = duration == null || duration.isEternal() ? Long.MAX_VALUE
+                        : ((System.nanoTime() + duration.getTimeUnit().toNanos(duration.getDurationAmount())) / 1000);
+    }
+
+    public V getElement() {
+        return element;
+    }
+
+    public boolean isExpired() {
+        return end != -1 && (end == 0 || Times.now(false) > end);
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleEntry.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleEntry.java
new file mode 100644
index 0000000..9dd4cc6
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleEntry.java
@@ -0,0 +1,51 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import javax.cache.Cache;
+
+public class SimpleEntry<K, V> implements Cache.Entry<K, V> {
+
+    private final K key;
+
+    private final V value;
+
+    public SimpleEntry(final K key, final V value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    @Override
+    public K getKey() {
+        return key;
+    }
+
+    @Override
+    public V getValue() {
+        return value;
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz) {
+        if (clazz.isInstance(this)) {
+            return clazz.cast(this);
+        }
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleEvent.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleEvent.java
new file mode 100644
index 0000000..7a7ec34
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleEvent.java
@@ -0,0 +1,69 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import javax.cache.Cache;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.EventType;
+
+public class SimpleEvent<K, V> extends CacheEntryEvent<K, V> {
+
+    private static final long serialVersionUID = 4761272981003897488L;
+
+    private final V old;
+
+    private final K key;
+
+    private final V value;
+
+    public SimpleEvent(final Cache<K, V> source, final EventType eventType, final V old, final K key, final V value) {
+        super(source, eventType);
+        this.old = old;
+        this.key = key;
+        this.value = value;
+    }
+
+    @Override
+    public V getOldValue() {
+        return old;
+    }
+
+    @Override
+    public boolean isOldValueAvailable() {
+        return old != null;
+    }
+
+    @Override
+    public K getKey() {
+        return key;
+    }
+
+    @Override
+    public V getValue() {
+        return value;
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz) {
+        if (clazz.isInstance(this)) {
+            return clazz.cast(this);
+        }
+        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleKey.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleKey.java
new file mode 100644
index 0000000..1480908
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleKey.java
@@ -0,0 +1,59 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.io.Serializable;
+
+public class SimpleKey<K> implements Serializable {
+
+    private final K key;
+
+    private volatile long lastAccess = 0;
+
+    public SimpleKey(final K key) {
+        this.key = key;
+    }
+
+    public void access(final long time) {
+        lastAccess = time;
+    }
+
+    public long lastAccess() {
+        return lastAccess;
+    }
+
+    public K getKey() {
+        return key;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o)
+            return true;
+        // if (o == null || getClass() != o.getClass()) return false; // not needed normally
+        final SimpleKey k = SimpleKey.class.cast(o);
+        return key.equals(k.key);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return key.hashCode();
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleListener.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleListener.java
new file mode 100644
index 0000000..82e6dc3
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleListener.java
@@ -0,0 +1,126 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.Factory;
+import javax.cache.event.CacheEntryCreatedListener;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.CacheEntryEventFilter;
+import javax.cache.event.CacheEntryExpiredListener;
+import javax.cache.event.CacheEntryListener;
+import javax.cache.event.CacheEntryListenerException;
+import javax.cache.event.CacheEntryRemovedListener;
+import javax.cache.event.CacheEntryUpdatedListener;
+
+public class SimpleListener<K, V> implements AutoCloseable {
+
+    private final CacheEntryEventFilter<? super K, ? super V> filter;
+
+    private final CacheEntryListener<? super K, ? super V> delegate;
+
+    private final boolean remove;
+
+    private final boolean expire;
+
+    private final boolean update;
+
+    private final boolean create;
+
+    public SimpleListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        final Factory<CacheEntryEventFilter<? super K, ? super V>> filterFactory = cacheEntryListenerConfiguration
+                .getCacheEntryEventFilterFactory();
+        if (filterFactory == null) {
+            this.filter = NoFilter.INSTANCE;
+        } else {
+            final CacheEntryEventFilter<? super K, ? super V> filter = filterFactory.create();
+            this.filter = (CacheEntryEventFilter<? super K, ? super V>) (filter == null ? NoFilter.INSTANCE : filter);
+        }
+
+        delegate = cacheEntryListenerConfiguration.getCacheEntryListenerFactory().create();
+        remove = CacheEntryRemovedListener.class.isInstance(delegate);
+        expire = CacheEntryExpiredListener.class.isInstance(delegate);
+        update = CacheEntryUpdatedListener.class.isInstance(delegate);
+        create = CacheEntryCreatedListener.class.isInstance(delegate);
+    }
+
+    public void onRemoved(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException {
+        if (remove) {
+            CacheEntryRemovedListener.class.cast(delegate).onRemoved(filter(events));
+        }
+    }
+
+    public void onExpired(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException {
+        if (expire) {
+            CacheEntryExpiredListener.class.cast(delegate).onExpired(filter(events));
+        }
+    }
+
+    public void onUpdated(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException {
+        if (update) {
+            CacheEntryUpdatedListener.class.cast(delegate).onUpdated(filter(events));
+        }
+    }
+
+    public void onCreated(final List<CacheEntryEvent<? extends K, ? extends V>> events) throws CacheEntryListenerException {
+        if (create) {
+            CacheEntryCreatedListener.class.cast(delegate).onCreated(filter(events));
+        }
+    }
+
+    private Iterable<CacheEntryEvent<? extends K, ? extends V>> filter(
+            final List<CacheEntryEvent<? extends K, ? extends V>> events) {
+        if (filter == NoFilter.INSTANCE) {
+            return events;
+        }
+
+        final List<CacheEntryEvent<? extends K, ? extends V>> filtered = new ArrayList<CacheEntryEvent<? extends K, ? extends V>>(
+                events.size());
+        for (final CacheEntryEvent<? extends K, ? extends V> event : events) {
+            if (filter.evaluate(event)) {
+                filtered.add(event);
+            }
+        }
+        return filtered;
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (AutoCloseable.class.isInstance(delegate)) {
+            AutoCloseable.class.cast(delegate).close();
+        }
+    }
+
+    public static class NoFilter implements CacheEntryEventFilter<Object, Object> {
+
+        public static final CacheEntryEventFilter<Object, Object> INSTANCE = new NoFilter();
+
+        private NoFilter() {
+            // no-op
+        }
+
+        @Override
+        public boolean evaluate(final CacheEntryEvent<?, ?> event) throws CacheEntryListenerException {
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleManager.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleManager.java
new file mode 100644
index 0000000..e4d4d66
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleManager.java
@@ -0,0 +1,258 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import static java.util.Collections.unmodifiableSet;
+import static org.apache.geronimo.jcache.simple.Asserts.assertNotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.cache.Cache;
+import javax.cache.CacheException;
+import javax.cache.CacheManager;
+import javax.cache.configuration.Configuration;
+import javax.cache.spi.CachingProvider;
+
+public class SimpleManager implements CacheManager {
+
+    private final CachingProvider provider;
+
+    private final URI uri;
+
+    private final ClassLoader loader;
+
+    private final Properties properties;
+
+    private final ConcurrentMap<String, Cache<?, ?>> caches = new ConcurrentHashMap<>();
+
+    private final Properties configProperties;
+
+    private volatile boolean closed = false;
+
+    SimpleManager(final CachingProvider provider, final URI uri, final ClassLoader loader, final Properties properties) {
+        this.provider = provider;
+        this.uri = uri;
+        this.loader = loader;
+        this.properties = readConfig(uri, loader, properties);
+        this.configProperties = properties;
+    }
+
+    private Properties readConfig(final URI uri, final ClassLoader loader, final Properties properties) {
+        final Properties props = new Properties();
+        try {
+            if (SimpleProvider.DEFAULT_URI.toString().equals(uri.toString()) || uri.getScheme().equals("geronimo")) {
+
+                final Enumeration<URL> resources = loader.getResources(uri.getPath());
+                do {
+                    addProperties(resources.nextElement(), props);
+                } while (resources.hasMoreElements());
+            } else {
+                props.load(uri.toURL().openStream());
+            }
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+        if (properties != null) {
+            props.putAll(properties);
+        }
+        return props;
+    }
+
+    private void addProperties(final URL url, final Properties aggregator) {
+        try (final InputStream inputStream = url.openStream()) {
+            aggregator.load(inputStream);
+        } catch (final IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private void assertNotClosed() {
+        if (isClosed()) {
+            throw new IllegalStateException("cache manager closed");
+        }
+    }
+
+    @Override
+    public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(final String cacheName, final C configuration)
+            throws IllegalArgumentException {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        assertNotNull(configuration, "configuration");
+        final Class<K> keyType = configuration.getKeyType();
+        final Class<V> valueType = configuration.getValueType();
+        if (!caches.containsKey(cacheName)) {
+            final Cache<K, V> cache = ClassLoaderAwareCache.wrap(loader, new SimpleCache<K, V>(loader, this, cacheName,
+                    new SimpleConfiguration<>(configuration, keyType, valueType), properties));
+            caches.putIfAbsent(cacheName, cache);
+        } else {
+            throw new CacheException("cache " + cacheName + " already exists");
+        }
+        return (Cache<K, V>) getCache(cacheName, keyType, valueType);
+    }
+
+    @Override
+    public void destroyCache(final String cacheName) {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        final Cache<?, ?> cache = caches.remove(cacheName);
+        if (cache != null && !cache.isClosed()) {
+            cache.clear();
+            cache.close();
+        }
+    }
+
+    @Override
+    public void enableManagement(final String cacheName, final boolean enabled) {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        final SimpleCache<?, ?> cache = getJCSCache(cacheName);
+        if (cache != null) {
+            if (enabled) {
+                cache.enableManagement();
+            } else {
+                cache.disableManagement();
+            }
+        }
+    }
+
+    private SimpleCache<?, ?> getJCSCache(final String cacheName) {
+        return SimpleCache.class.cast(ClassLoaderAwareCache.getDelegate(caches.get(cacheName)));
+    }
+
+    @Override
+    public void enableStatistics(final String cacheName, final boolean enabled) {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        final SimpleCache<?, ?> cache = getJCSCache(cacheName);
+        if (cache != null) {
+            if (enabled) {
+                cache.enableStatistics();
+            } else {
+                cache.disableStatistics();
+            }
+        }
+    }
+
+    @Override
+    public synchronized void close() {
+        if (isClosed()) {
+            return;
+        }
+
+        assertNotClosed();
+        for (final Cache<?, ?> c : caches.values()) {
+            c.close();
+        }
+        caches.clear();
+        closed = true;
+        if (SimpleProvider.class.isInstance(provider)) {
+            SimpleProvider.class.cast(provider).remove(this);
+        } // else throw?
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz) {
+        if (clazz.isInstance(this)) {
+            return clazz.cast(this);
+        }
+        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
+    }
+
+    @Override
+    public boolean isClosed() {
+        return closed;
+    }
+
+    @Override
+    public <K, V> Cache<K, V> getCache(final String cacheName) {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        return (Cache<K, V>) doGetCache(cacheName, null, null);
+    }
+
+    @Override
+    public Iterable<String> getCacheNames() {
+        assertNotClosed();
+        return unmodifiableSet(new HashSet<>(caches.keySet()));
+    }
+
+    @Override
+    public <K, V> Cache<K, V> getCache(final String cacheName, final Class<K> keyType, final Class<V> valueType) {
+        assertNotClosed();
+        assertNotNull(cacheName, "cacheName");
+        assertNotNull(keyType, "keyType");
+        assertNotNull(valueType, "valueType");
+        try {
+            return doGetCache(cacheName, keyType, valueType);
+        } catch (final IllegalArgumentException iae) {
+            throw new ClassCastException(iae.getMessage());
+        }
+    }
+
+    private <K, V> Cache<K, V> doGetCache(final String cacheName, final Class<K> keyType, final Class<V> valueType) {
+        final Cache<K, V> cache = (Cache<K, V>) caches.get(cacheName);
+        if (keyType == null && valueType == null) {
+            return cache;
+        }
+        if (cache == null) {
+            return null;
+        }
+
+        final Configuration<K, V> config = cache.getConfiguration(Configuration.class);
+        if ((keyType != null && !config.getKeyType().isAssignableFrom(keyType))
+                || (valueType != null && !config.getValueType().isAssignableFrom(valueType))) {
+            throw new IllegalArgumentException(
+                    "this cache is <" + config.getKeyType().getName() + ", " + config.getValueType().getName() + "> "
+                            + " and not <" + keyType.getName() + ", " + valueType.getName() + ">");
+        }
+        return cache;
+    }
+
+    @Override
+    public CachingProvider getCachingProvider() {
+        return provider;
+    }
+
+    @Override
+    public URI getURI() {
+        return uri;
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        return loader;
+    }
+
+    @Override
+    public Properties getProperties() {
+        return configProperties;
+    }
+
+    public void release(final String name) {
+        caches.remove(name);
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleMutableEntry.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleMutableEntry.java
new file mode 100644
index 0000000..3eab186
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleMutableEntry.java
@@ -0,0 +1,67 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import javax.cache.Cache;
+import javax.cache.processor.MutableEntry;
+
+public class SimpleMutableEntry<K, V> implements MutableEntry<K, V> {
+
+    private final Cache<K, V> cache;
+
+    private final K key;
+
+    public SimpleMutableEntry(final Cache<K, V> cache, final K key) {
+        this.cache = cache;
+        this.key = key;
+    }
+
+    @Override
+    public boolean exists() {
+        return cache.containsKey(key);
+    }
+
+    @Override
+    public void remove() {
+        cache.remove(key);
+    }
+
+    @Override
+    public K getKey() {
+        return key;
+    }
+
+    @Override
+    public V getValue() {
+        return cache.get(key);
+    }
+
+    @Override
+    public void setValue(final V value) {
+        cache.put(key, value);
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz) {
+        if (clazz.isInstance(this)) {
+            return clazz.cast(this);
+        }
+        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleProvider.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleProvider.java
new file mode 100644
index 0000000..92700f1
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleProvider.java
@@ -0,0 +1,136 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.cache.CacheManager;
+import javax.cache.configuration.OptionalFeature;
+import javax.cache.spi.CachingProvider;
+
+public class SimpleProvider implements CachingProvider {
+
+    static final URI DEFAULT_URI = URI.create("geronimo://simple-jcache.properties");
+
+    private final ConcurrentMap<ClassLoader, ConcurrentMap<URI, CacheManager>> cacheManagersByLoader = new ConcurrentHashMap<>();
+
+    @Override
+    public CacheManager getCacheManager(final URI inUri, final ClassLoader inClassLoader, final Properties properties) {
+        final URI uri = inUri != null ? inUri : getDefaultURI();
+        final ClassLoader classLoader = inClassLoader != null ? inClassLoader : getDefaultClassLoader();
+
+        ConcurrentMap<URI, CacheManager> managers = cacheManagersByLoader.get(classLoader);
+        if (managers == null) {
+            managers = new ConcurrentHashMap<>();
+            final ConcurrentMap<URI, CacheManager> existingManagers = cacheManagersByLoader.putIfAbsent(classLoader, managers);
+            if (existingManagers != null) {
+                managers = existingManagers;
+            }
+        }
+
+        CacheManager mgr = managers.get(uri);
+        if (mgr == null) {
+            mgr = new SimpleManager(this, uri, classLoader, properties);
+            final CacheManager existing = managers.putIfAbsent(uri, mgr);
+            if (existing != null) {
+                mgr = existing;
+            }
+        }
+
+        return mgr;
+    }
+
+    @Override
+    public URI getDefaultURI() {
+        return DEFAULT_URI;
+    }
+
+    @Override
+    public void close() {
+        for (final Map<URI, CacheManager> v : cacheManagersByLoader.values()) {
+            for (final CacheManager m : v.values()) {
+                m.close();
+            }
+            v.clear();
+        }
+        cacheManagersByLoader.clear();
+    }
+
+    @Override
+    public void close(final ClassLoader classLoader) {
+        final Map<URI, CacheManager> cacheManagers = cacheManagersByLoader.remove(classLoader);
+        if (cacheManagers != null) {
+            for (final CacheManager mgr : cacheManagers.values()) {
+                mgr.close();
+            }
+            cacheManagers.clear();
+        }
+    }
+
+    @Override
+    public void close(final URI uri, final ClassLoader classLoader) {
+        final Map<URI, CacheManager> cacheManagers = cacheManagersByLoader.remove(classLoader);
+        if (cacheManagers != null) {
+            final CacheManager mgr = cacheManagers.remove(uri);
+            if (mgr != null) {
+                mgr.close();
+            }
+        }
+    }
+
+    @Override
+    public CacheManager getCacheManager(final URI uri, final ClassLoader classLoader) {
+        return getCacheManager(uri, classLoader, getDefaultProperties());
+    }
+
+    @Override
+    public CacheManager getCacheManager() {
+        return getCacheManager(getDefaultURI(), getDefaultClassLoader());
+    }
+
+    @Override
+    public boolean isSupported(final OptionalFeature optionalFeature) {
+        return optionalFeature == OptionalFeature.STORE_BY_REFERENCE;
+    }
+
+    @Override
+    public ClassLoader getDefaultClassLoader() {
+        return SimpleProvider.class.getClassLoader();
+    }
+
+    @Override
+    public Properties getDefaultProperties() {
+        return new Properties();
+    }
+
+    void remove(final CacheManager mgr) {
+        final ClassLoader classLoader = mgr.getClassLoader();
+        final Map<URI, CacheManager> mgrs = cacheManagersByLoader.get(classLoader);
+        if (mgrs != null) {
+            mgrs.remove(mgr.getURI());
+            if (mgrs.isEmpty()) {
+                cacheManagersByLoader.remove(classLoader);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/SimpleThreadFactory.java b/src/main/java/org/apache/geronimo/jcache/simple/SimpleThreadFactory.java
new file mode 100644
index 0000000..a3c71b4
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/SimpleThreadFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class SimpleThreadFactory implements ThreadFactory {
+
+    private static final AtomicInteger POOL_IDX = new AtomicInteger();
+
+    private final AtomicInteger threadIdx = new AtomicInteger();
+
+    private final int poolIdx;
+
+    private final String format;
+
+    public SimpleThreadFactory(final String format) {
+        this.format = format;
+        this.poolIdx = POOL_IDX.incrementAndGet();
+    }
+
+    @Override
+    public Thread newThread(final Runnable r) {
+        final Thread thread = new Thread(r);
+        thread.setName(String.format(format, poolIdx, threadIdx.incrementAndGet()));
+        thread.setPriority(Thread.NORM_PRIORITY);
+        thread.setDaemon(false); // ensure to call close, that's it
+        return thread;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/Statistics.java b/src/main/java/org/apache/geronimo/jcache/simple/Statistics.java
new file mode 100644
index 0000000..3247e2c
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/Statistics.java
@@ -0,0 +1,139 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Statistics {
+
+    private final AtomicLong removals = new AtomicLong();
+    private final AtomicLong expiries = new AtomicLong();
+    private final AtomicLong puts = new AtomicLong();
+    private final AtomicLong hits = new AtomicLong();
+    private final AtomicLong misses = new AtomicLong();
+    private final AtomicLong evictions = new AtomicLong();
+    private final AtomicLong putTimeTaken = new AtomicLong();
+    private final AtomicLong getTimeTaken = new AtomicLong();
+    private final AtomicLong removeTimeTaken = new AtomicLong();
+    private volatile boolean active = true;
+
+    public long getHits() {
+        return hits.get();
+    }
+
+    public long getMisses() {
+        return misses.get();
+    }
+
+    public long getPuts() {
+        return puts.get();
+    }
+
+    public long getRemovals() {
+        return removals.get();
+    }
+
+    public long getEvictions() {
+        return evictions.get();
+    }
+
+    public long getTimeTakenForGets() {
+        return getTimeTaken.get();
+    }
+
+    public long getTimeTakenForPuts() {
+        return putTimeTaken.get();
+    }
+
+    public long getTimeTakenForRemovals() {
+        return removeTimeTaken.get();
+    }
+
+    public void increaseRemovals(final long number) {
+        increment(removals, number);
+    }
+
+    public void increaseExpiries(final long number) {
+        increment(expiries, number);
+    }
+
+    public void increasePuts(final long number) {
+        increment(puts, number);
+    }
+
+    public void increaseHits(final long number) {
+        increment(hits, number);
+    }
+
+    public void increaseMisses(final long number) {
+        increment(misses, number);
+    }
+
+    public void increaseEvictions(final long number) {
+        increment(evictions, number);
+    }
+
+    public void addGetTime(final long duration) {
+        increment(duration, getTimeTaken);
+    }
+
+    public void addPutTime(final long duration) {
+        increment(duration, putTimeTaken);
+    }
+
+    public void addRemoveTime(final long duration) {
+        increment(duration, removeTimeTaken);
+    }
+
+    private void increment(final AtomicLong counter, final long number) {
+        if (!active) {
+            return;
+        }
+        counter.addAndGet(number);
+    }
+
+    private void increment(final long duration, final AtomicLong counter) {
+        if (!active) {
+            return;
+        }
+
+        if (counter.get() < Long.MAX_VALUE - duration) {
+            counter.addAndGet(duration);
+        } else {
+            reset();
+            counter.set(duration);
+        }
+    }
+
+    public void reset() {
+        puts.set(0);
+        misses.set(0);
+        removals.set(0);
+        expiries.set(0);
+        hits.set(0);
+        evictions.set(0);
+        getTimeTaken.set(0);
+        putTimeTaken.set(0);
+        removeTimeTaken.set(0);
+    }
+
+    public void setActive(final boolean active) {
+        this.active = active;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/TempStateCacheView.java b/src/main/java/org/apache/geronimo/jcache/simple/TempStateCacheView.java
new file mode 100644
index 0000000..6e0684a
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/TempStateCacheView.java
@@ -0,0 +1,283 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+import static org.apache.geronimo.jcache.simple.Asserts.assertNotNull;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.CacheEntryListenerConfiguration;
+import javax.cache.configuration.CompleteConfiguration;
+import javax.cache.configuration.Configuration;
+import javax.cache.integration.CompletionListener;
+import javax.cache.processor.EntryProcessor;
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.EntryProcessorResult;
+
+// kind of transactional view for a Cache<K, V>, to use with EntryProcessor
+public class TempStateCacheView<K, V> implements Cache<K, V> {
+
+    private final SimpleCache<K, V> cache;
+
+    private final Map<K, V> put = new HashMap<K, V>();
+
+    private final Collection<K> remove = new LinkedList<K>();
+
+    private boolean removeAll = false;
+
+    private boolean clear = false;
+
+    public TempStateCacheView(final SimpleCache<K, V> entries) {
+        this.cache = entries;
+    }
+
+    public V get(final K key) {
+        if (ignoreKey(key)) {
+            return null;
+        }
+
+        final V v = put.get(key);
+        if (v != null) {
+            return v;
+        }
+
+        // for an EntryProcessor we already incremented stats - to enhance
+        // surely
+        if (cache.getConfiguration(CompleteConfiguration.class).isStatisticsEnabled()) {
+            final Statistics statistics = cache.getStatistics();
+            if (cache.containsKey(key)) {
+                statistics.increaseHits(-1);
+            } else {
+                statistics.increaseMisses(-1);
+            }
+        }
+        return cache.get(key);
+    }
+
+    private boolean ignoreKey(final K key) {
+        return removeAll || clear || remove.contains(key);
+    }
+
+    public Map<K, V> getAll(final Set<? extends K> keys) {
+        final Map<K, V> v = new HashMap<K, V>(keys.size());
+        final Set<K> missing = new HashSet<K>();
+        for (final K k : keys) {
+            final V value = put.get(k);
+            if (value != null) {
+                v.put(k, value);
+            } else if (!ignoreKey(k)) {
+                missing.add(k);
+            }
+        }
+        if (!missing.isEmpty()) {
+            v.putAll(cache.getAll(missing));
+        }
+        return v;
+    }
+
+    public boolean containsKey(final K key) {
+        return !ignoreKey(key) && (put.containsKey(key) || cache.containsKey(key));
+    }
+
+    public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues,
+            final CompletionListener completionListener) {
+        cache.loadAll(keys, replaceExistingValues, completionListener);
+    }
+
+    public void put(final K key, final V value) {
+        assertNotNull(key, "key");
+        assertNotNull(value, "value");
+        put.put(key, value);
+        remove.remove(key);
+    }
+
+    public V getAndPut(final K key, final V value) {
+        final V v = get(key);
+        put(key, value);
+        return v;
+    }
+
+    public void putAll(final Map<? extends K, ? extends V> map) {
+        put.putAll(map);
+        for (final K k : map.keySet()) {
+            remove.remove(k);
+        }
+    }
+
+    public boolean putIfAbsent(final K key, final V value) {
+        if (!put.containsKey(key)) {
+            put.put(key, value);
+            remove.remove(key);
+            return true;
+        }
+        return false;
+    }
+
+    public boolean remove(final K key) {
+        final boolean noop = put.containsKey(key);
+        put.remove(key);
+        if (!ignoreKey(key)) {
+            if (!noop) {
+                remove.add(key);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public boolean remove(final K key, final V oldValue) {
+        put.remove(key);
+        if (!ignoreKey(key) && oldValue.equals(cache.get(key))) {
+            remove.add(key);
+            return true;
+        }
+        return false;
+    }
+
+    public V getAndRemove(final K key) {
+        final V v = get(key);
+        remove.add(key);
+        put.remove(key);
+        return v;
+    }
+
+    public boolean replace(final K key, final V oldValue, final V newValue) {
+        if (oldValue.equals(get(key))) {
+            put(key, newValue);
+            return true;
+        }
+        return false;
+    }
+
+    public boolean replace(final K key, final V value) {
+        if (containsKey(key)) {
+            remove(key);
+            return true;
+        }
+        return false;
+    }
+
+    public V getAndReplace(final K key, final V value) {
+        if (containsKey(key)) {
+            final V oldValue = get(key);
+            put(key, value);
+            return oldValue;
+        }
+        return null;
+    }
+
+    public void removeAll(final Set<? extends K> keys) {
+        remove.addAll(keys);
+        for (final K k : keys) {
+            put.remove(k);
+        }
+    }
+
+    @Override
+    public void removeAll() {
+        removeAll = true;
+        put.clear();
+        remove.clear();
+    }
+
+    @Override
+    public void clear() {
+        clear = true;
+        put.clear();
+        remove.clear();
+    }
+
+    public <C extends Configuration<K, V>> C getConfiguration(final Class<C> clazz) {
+        return cache.getConfiguration(clazz);
+    }
+
+    public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments)
+            throws EntryProcessorException {
+        return cache.invoke(key, entryProcessor, arguments);
+    }
+
+    public <T> Map<K, EntryProcessorResult<T>> invokeAll(Set<? extends K> keys, final EntryProcessor<K, V, T> entryProcessor,
+            final Object... arguments) {
+        return cache.invokeAll(keys, entryProcessor, arguments);
+    }
+
+    @Override
+    public String getName() {
+        return cache.getName();
+    }
+
+    @Override
+    public CacheManager getCacheManager() {
+        return cache.getCacheManager();
+    }
+
+    @Override
+    public void close() {
+        cache.close();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return cache.isClosed();
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> clazz) {
+        return cache.unwrap(clazz);
+    }
+
+    public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        cache.registerCacheEntryListener(cacheEntryListenerConfiguration);
+    }
+
+    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
+        cache.deregisterCacheEntryListener(cacheEntryListenerConfiguration);
+    }
+
+    @Override
+    public Iterator<Entry<K, V>> iterator() {
+        return cache.iterator();
+    }
+
+    public void merge() {
+        if (removeAll) {
+            cache.removeAll();
+        }
+        if (clear) {
+            cache.clear();
+        }
+
+        for (final Map.Entry<K, V> entry : put.entrySet()) {
+            cache.put(entry.getKey(), entry.getValue());
+        }
+        put.clear();
+        for (final K entry : remove) {
+            cache.remove(entry);
+        }
+        remove.clear();
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/Times.java b/src/main/java/org/apache/geronimo/jcache/simple/Times.java
new file mode 100644
index 0000000..d9e2de3
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/Times.java
@@ -0,0 +1,33 @@
+/*
+ * 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.geronimo.jcache.simple;
+
+public class Times {
+
+    private Times() {
+        // no-op
+    }
+
+    public static long now(final boolean ignore) {
+        if (ignore) {
+            return -1;
+        }
+        return System.nanoTime() / 1000;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CDIJCacheHelper.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CDIJCacheHelper.java
new file mode 100644
index 0000000..2f2a69b
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CDIJCacheHelper.java
@@ -0,0 +1,584 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.logging.Logger;
+
+import javax.annotation.PreDestroy;
+import javax.cache.annotation.CacheDefaults;
+import javax.cache.annotation.CacheKey;
+import javax.cache.annotation.CacheKeyGenerator;
+import javax.cache.annotation.CachePut;
+import javax.cache.annotation.CacheRemove;
+import javax.cache.annotation.CacheRemoveAll;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.CacheResult;
+import javax.cache.annotation.CacheValue;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.interceptor.InvocationContext;
+
+@ApplicationScoped
+public class CDIJCacheHelper {
+
+    private static final Logger LOGGER = Logger.getLogger(CDIJCacheHelper.class.getName());
+
+    private static final boolean CLOSE_CACHE = !Boolean.getBoolean("org.apache.geronimo.jcache.simple.cdi.skip-close");
+
+    private final CacheKeyGeneratorImpl defaultCacheKeyGenerator = new CacheKeyGeneratorImpl();
+
+    private final Collection<CreationalContext<?>> toRelease = new ArrayList<CreationalContext<?>>();
+
+    private final ConcurrentMap<MethodKey, MethodMeta> methods = new ConcurrentHashMap<MethodKey, MethodMeta>();
+
+    private volatile CacheResolverFactoryImpl defaultCacheResolverFactory = null; // lazy to not create any cache if not needed
+
+    @Inject
+    private BeanManager beanManager;
+
+    @PreDestroy
+    private void release() {
+        if (CLOSE_CACHE && defaultCacheResolverFactory != null) {
+            defaultCacheResolverFactory.release();
+        }
+        for (final CreationalContext<?> cc : toRelease) {
+            try {
+                cc.release();
+            } catch (final RuntimeException re) {
+                LOGGER.warning(re.getMessage());
+            }
+        }
+    }
+
+    public MethodMeta findMeta(final InvocationContext ic) {
+        final Method mtd = ic.getMethod();
+        final Class<?> refType = findKeyType(ic.getTarget());
+        final MethodKey key = new MethodKey(refType, mtd);
+        MethodMeta methodMeta = methods.get(key);
+        if (methodMeta == null) {
+            synchronized (this) {
+                methodMeta = methods.get(key);
+                if (methodMeta == null) {
+                    methodMeta = createMeta(ic);
+                    methods.put(key, methodMeta);
+                }
+            }
+        }
+        return methodMeta;
+    }
+
+    private Class<?> findKeyType(final Object target) {
+        if (null == target) {
+            return null;
+        }
+        return target.getClass();
+    }
+
+    // it is unlikely we have all annotations but for now we have a single meta model
+    private MethodMeta createMeta(final InvocationContext ic) {
+        final CacheDefaults defaults = findDefaults(ic.getTarget() == null ? null : ic.getTarget().getClass(), ic.getMethod());
+
+        final Class<?>[] parameterTypes = ic.getMethod().getParameterTypes();
+        final Annotation[][] parameterAnnotations = ic.getMethod().getParameterAnnotations();
+        final List<Set<Annotation>> annotations = new ArrayList<Set<Annotation>>();
+        for (final Annotation[] parameterAnnotation : parameterAnnotations) {
+            final Set<Annotation> set = new HashSet<Annotation>(parameterAnnotation.length);
+            set.addAll(Arrays.asList(parameterAnnotation));
+            annotations.add(set);
+        }
+
+        final Set<Annotation> mtdAnnotations = new HashSet<Annotation>();
+        mtdAnnotations.addAll(Arrays.asList(ic.getMethod().getAnnotations()));
+
+        final CacheResult cacheResult = ic.getMethod().getAnnotation(CacheResult.class);
+        final String cacheResultCacheResultName = cacheResult == null ? null
+                : defaultName(ic.getMethod(), defaults, cacheResult.cacheName());
+        final CacheResolverFactory cacheResultCacheResolverFactory = cacheResult == null ? null
+                : cacheResolverFactoryFor(defaults, cacheResult.cacheResolverFactory());
+        final CacheKeyGenerator cacheResultCacheKeyGenerator = cacheResult == null ? null
+                : cacheKeyGeneratorFor(defaults, cacheResult.cacheKeyGenerator());
+
+        final CachePut cachePut = ic.getMethod().getAnnotation(CachePut.class);
+        final String cachePutCachePutName = cachePut == null ? null : defaultName(ic.getMethod(), defaults, cachePut.cacheName());
+        final CacheResolverFactory cachePutCacheResolverFactory = cachePut == null ? null
+                : cacheResolverFactoryFor(defaults, cachePut.cacheResolverFactory());
+        final CacheKeyGenerator cachePutCacheKeyGenerator = cachePut == null ? null
+                : cacheKeyGeneratorFor(defaults, cachePut.cacheKeyGenerator());
+
+        final CacheRemove cacheRemove = ic.getMethod().getAnnotation(CacheRemove.class);
+        final String cacheRemoveCacheRemoveName = cacheRemove == null ? null
+                : defaultName(ic.getMethod(), defaults, cacheRemove.cacheName());
+        final CacheResolverFactory cacheRemoveCacheResolverFactory = cacheRemove == null ? null
+                : cacheResolverFactoryFor(defaults, cacheRemove.cacheResolverFactory());
+        final CacheKeyGenerator cacheRemoveCacheKeyGenerator = cacheRemove == null ? null
+                : cacheKeyGeneratorFor(defaults, cacheRemove.cacheKeyGenerator());
+
+        final CacheRemoveAll cacheRemoveAll = ic.getMethod().getAnnotation(CacheRemoveAll.class);
+        final String cacheRemoveAllCacheRemoveAllName = cacheRemoveAll == null ? null
+                : defaultName(ic.getMethod(), defaults, cacheRemoveAll.cacheName());
+        final CacheResolverFactory cacheRemoveAllCacheResolverFactory = cacheRemoveAll == null ? null
+                : cacheResolverFactoryFor(defaults, cacheRemoveAll.cacheResolverFactory());
+
+        return new MethodMeta(parameterTypes, annotations, mtdAnnotations, keyParameterIndexes(ic.getMethod()),
+                getValueParameter(annotations), getKeyParameters(annotations), cacheResultCacheResultName,
+                cacheResultCacheResolverFactory, cacheResultCacheKeyGenerator, cacheResult, cachePutCachePutName,
+                cachePutCacheResolverFactory, cachePutCacheKeyGenerator, cachePut != null && cachePut.afterInvocation(), cachePut,
+                cacheRemoveCacheRemoveName, cacheRemoveCacheResolverFactory, cacheRemoveCacheKeyGenerator,
+                cacheRemove != null && cacheRemove.afterInvocation(), cacheRemove, cacheRemoveAllCacheRemoveAllName,
+                cacheRemoveAllCacheResolverFactory, cacheRemoveAll != null && cacheRemoveAll.afterInvocation(), cacheRemoveAll);
+    }
+
+    private Integer[] getKeyParameters(final List<Set<Annotation>> annotations) {
+        final Collection<Integer> list = new ArrayList<Integer>();
+        int idx = 0;
+        for (final Set<Annotation> set : annotations) {
+            for (final Annotation a : set) {
+                if (a.annotationType() == CacheKey.class) {
+                    list.add(idx);
+                }
+            }
+            idx++;
+        }
+        if (list.isEmpty()) {
+            for (int i = 0; i < annotations.size(); i++) {
+                list.add(i);
+            }
+        }
+        return list.toArray(new Integer[list.size()]);
+    }
+
+    private Integer getValueParameter(final List<Set<Annotation>> annotations) {
+        int idx = 0;
+        for (final Set<Annotation> set : annotations) {
+            for (final Annotation a : set) {
+                if (a.annotationType() == CacheValue.class) {
+                    return idx;
+                }
+            }
+        }
+        return -1;
+    }
+
+    private String defaultName(final Method method, final CacheDefaults defaults, final String cacheName) {
+        if (!cacheName.isEmpty()) {
+            return cacheName;
+        }
+        if (defaults != null) {
+            final String name = defaults.cacheName();
+            if (!name.isEmpty()) {
+                return name;
+            }
+        }
+
+        final StringBuilder name = new StringBuilder(method.getDeclaringClass().getName());
+        name.append(".");
+        name.append(method.getName());
+        name.append("(");
+        final Class<?>[] parameterTypes = method.getParameterTypes();
+        for (int pIdx = 0; pIdx < parameterTypes.length; pIdx++) {
+            name.append(parameterTypes[pIdx].getName());
+            if ((pIdx + 1) < parameterTypes.length) {
+                name.append(",");
+            }
+        }
+        name.append(")");
+        return name.toString();
+    }
+
+    private CacheDefaults findDefaults(final Class<?> targetType, final Method method) {
+        if (Proxy.isProxyClass(targetType)) // target doesnt hold annotations
+        {
+            final Class<?> api = method.getDeclaringClass();
+            for (final Class<?> type : targetType.getInterfaces()) {
+                if (!api.isAssignableFrom(type)) {
+                    continue;
+                }
+                return extractDefaults(type);
+            }
+        }
+        return extractDefaults(targetType);
+    }
+
+    private CacheDefaults extractDefaults(final Class<?> type) {
+        CacheDefaults annotation = null;
+        Class<?> clazz = type;
+        while (clazz != null && clazz != Object.class) {
+            annotation = clazz.getAnnotation(CacheDefaults.class);
+            if (annotation != null) {
+                break;
+            }
+            clazz = clazz.getSuperclass();
+        }
+        return annotation;
+    }
+
+    public boolean isIncluded(final Class<?> aClass, final Class<?>[] in, final Class<?>[] out) {
+        if (in.length == 0 && out.length == 0) {
+            return false;
+        }
+        for (final Class<?> potentialIn : in) {
+            if (potentialIn.isAssignableFrom(aClass)) {
+                for (final Class<?> potentialOut : out) {
+                    if (potentialOut.isAssignableFrom(aClass)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private CacheKeyGenerator cacheKeyGeneratorFor(final CacheDefaults defaults,
+            final Class<? extends CacheKeyGenerator> cacheKeyGenerator) {
+        if (!CacheKeyGenerator.class.equals(cacheKeyGenerator)) {
+            return instance(cacheKeyGenerator);
+        }
+        if (defaults != null) {
+            final Class<? extends CacheKeyGenerator> defaultCacheKeyGenerator = defaults.cacheKeyGenerator();
+            if (!CacheKeyGenerator.class.equals(defaultCacheKeyGenerator)) {
+                return instance(defaultCacheKeyGenerator);
+            }
+        }
+        return defaultCacheKeyGenerator;
+    }
+
+    private CacheResolverFactory cacheResolverFactoryFor(final CacheDefaults defaults,
+            final Class<? extends CacheResolverFactory> cacheResolverFactory) {
+        if (!CacheResolverFactory.class.equals(cacheResolverFactory)) {
+            return instance(cacheResolverFactory);
+        }
+        if (defaults != null) {
+            final Class<? extends CacheResolverFactory> defaultCacheResolverFactory = defaults.cacheResolverFactory();
+            if (!CacheResolverFactory.class.equals(defaultCacheResolverFactory)) {
+                return instance(defaultCacheResolverFactory);
+            }
+        }
+        return defaultCacheResolverFactory();
+    }
+
+    private <T> T instance(final Class<T> type) {
+        final Set<Bean<?>> beans = beanManager.getBeans(type);
+        if (beans.isEmpty()) {
+            if (CacheKeyGenerator.class == type) {
+                return (T) defaultCacheKeyGenerator;
+            }
+            if (CacheResolverFactory.class == type) {
+                return (T) defaultCacheResolverFactory();
+            }
+            return null;
+        }
+        final Bean<?> bean = beanManager.resolve(beans);
+        final CreationalContext<?> context = beanManager.createCreationalContext(bean);
+        final Class<? extends Annotation> scope = bean.getScope();
+        final boolean normalScope = beanManager.isNormalScope(scope);
+        try {
+            final Object reference = beanManager.getReference(bean, bean.getBeanClass(), context);
+            if (!normalScope) {
+                toRelease.add(context);
+            }
+            return (T) reference;
+        } finally {
+            if (normalScope) { // TODO: release at the right moment, @PreDestroy? question is: do we assume it is thread safe?
+                context.release();
+            }
+        }
+    }
+
+    private CacheResolverFactoryImpl defaultCacheResolverFactory() {
+        if (defaultCacheResolverFactory != null) {
+            return defaultCacheResolverFactory;
+        }
+        synchronized (this) {
+            if (defaultCacheResolverFactory != null) {
+                return defaultCacheResolverFactory;
+            }
+            defaultCacheResolverFactory = new CacheResolverFactoryImpl();
+        }
+        return defaultCacheResolverFactory;
+    }
+
+    private Integer[] keyParameterIndexes(final Method method) {
+        final List<Integer> keys = new LinkedList<Integer>();
+        final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+
+        // first check if keys are specified explicitely
+        for (int i = 0; i < method.getParameterTypes().length; i++) {
+            final Annotation[] annotations = parameterAnnotations[i];
+            for (final Annotation a : annotations) {
+                if (a.annotationType().equals(CacheKey.class)) {
+                    keys.add(i);
+                    break;
+                }
+            }
+        }
+
+        // if not then use all parameters but value ones
+        if (keys.isEmpty()) {
+            for (int i = 0; i < method.getParameterTypes().length; i++) {
+                final Annotation[] annotations = parameterAnnotations[i];
+                boolean value = false;
+                for (final Annotation a : annotations) {
+                    if (a.annotationType().equals(CacheValue.class)) {
+                        value = true;
+                        break;
+                    }
+                }
+                if (!value) {
+                    keys.add(i);
+                }
+            }
+        }
+        return keys.toArray(new Integer[keys.size()]);
+    }
+
+    private static final class MethodKey {
+
+        private final Class<?> base;
+
+        private final Method delegate;
+
+        private final int hash;
+
+        private MethodKey(final Class<?> base, final Method delegate) {
+            this.base = base; // we need a class to ensure inheritance don't fall in the same key
+            this.delegate = delegate;
+            this.hash = 31 * delegate.hashCode() + (base == null ? 0 : base.hashCode());
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            final MethodKey classKey = MethodKey.class.cast(o);
+            return delegate.equals(classKey.delegate)
+                    && ((base == null && classKey.base == null) || (base != null && base.equals(classKey.base)));
+        }
+
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+    }
+
+    // TODO: split it in 5?
+    public static class MethodMeta {
+
+        private final Class<?>[] parameterTypes;
+
+        private final List<Set<Annotation>> parameterAnnotations;
+
+        private final Set<Annotation> annotations;
+
+        private final Integer[] keysIndices;
+
+        private final Integer valueIndex;
+
+        private final Integer[] parameterIndices;
+
+        private final String cacheResultCacheName;
+
+        private final CacheResolverFactory cacheResultResolverFactory;
+
+        private final CacheKeyGenerator cacheResultKeyGenerator;
+
+        private final CacheResult cacheResult;
+
+        private final String cachePutCacheName;
+
+        private final CacheResolverFactory cachePutResolverFactory;
+
+        private final CacheKeyGenerator cachePutKeyGenerator;
+
+        private final boolean cachePutAfter;
+
+        private final CachePut cachePut;
+
+        private final String cacheRemoveCacheName;
+
+        private final CacheResolverFactory cacheRemoveResolverFactory;
+
+        private final CacheKeyGenerator cacheRemoveKeyGenerator;
+
+        private final boolean cacheRemoveAfter;
+
+        private final CacheRemove cacheRemove;
+
+        private final String cacheRemoveAllCacheName;
+
+        private final CacheResolverFactory cacheRemoveAllResolverFactory;
+
+        private final boolean cacheRemoveAllAfter;
+
+        private final CacheRemoveAll cacheRemoveAll;
+
+        public MethodMeta(Class<?>[] parameterTypes, List<Set<Annotation>> parameterAnnotations, Set<Annotation> annotations,
+                Integer[] keysIndices, Integer valueIndex, Integer[] parameterIndices, String cacheResultCacheName,
+                CacheResolverFactory cacheResultResolverFactory, CacheKeyGenerator cacheResultKeyGenerator,
+                CacheResult cacheResult, String cachePutCacheName, CacheResolverFactory cachePutResolverFactory,
+                CacheKeyGenerator cachePutKeyGenerator, boolean cachePutAfter, CachePut cachePut, String cacheRemoveCacheName,
+                CacheResolverFactory cacheRemoveResolverFactory, CacheKeyGenerator cacheRemoveKeyGenerator,
+                boolean cacheRemoveAfter, CacheRemove cacheRemove, String cacheRemoveAllCacheName,
+                CacheResolverFactory cacheRemoveAllResolverFactory, boolean cacheRemoveAllAfter, CacheRemoveAll cacheRemoveAll) {
+            this.parameterTypes = parameterTypes;
+            this.parameterAnnotations = parameterAnnotations;
+            this.annotations = annotations;
+            this.keysIndices = keysIndices;
+            this.valueIndex = valueIndex;
+            this.parameterIndices = parameterIndices;
+            this.cacheResultCacheName = cacheResultCacheName;
+            this.cacheResultResolverFactory = cacheResultResolverFactory;
+            this.cacheResultKeyGenerator = cacheResultKeyGenerator;
+            this.cacheResult = cacheResult;
+            this.cachePutCacheName = cachePutCacheName;
+            this.cachePutResolverFactory = cachePutResolverFactory;
+            this.cachePutKeyGenerator = cachePutKeyGenerator;
+            this.cachePutAfter = cachePutAfter;
+            this.cachePut = cachePut;
+            this.cacheRemoveCacheName = cacheRemoveCacheName;
+            this.cacheRemoveResolverFactory = cacheRemoveResolverFactory;
+            this.cacheRemoveKeyGenerator = cacheRemoveKeyGenerator;
+            this.cacheRemoveAfter = cacheRemoveAfter;
+            this.cacheRemove = cacheRemove;
+            this.cacheRemoveAllCacheName = cacheRemoveAllCacheName;
+            this.cacheRemoveAllResolverFactory = cacheRemoveAllResolverFactory;
+            this.cacheRemoveAllAfter = cacheRemoveAllAfter;
+            this.cacheRemoveAll = cacheRemoveAll;
+        }
+
+        public boolean isCacheRemoveAfter() {
+            return cacheRemoveAfter;
+        }
+
+        public boolean isCachePutAfter() {
+            return cachePutAfter;
+        }
+
+        public Class<?>[] getParameterTypes() {
+            return parameterTypes;
+        }
+
+        public List<Set<Annotation>> getParameterAnnotations() {
+            return parameterAnnotations;
+        }
+
+        public String getCacheResultCacheName() {
+            return cacheResultCacheName;
+        }
+
+        public CacheResolverFactory getCacheResultResolverFactory() {
+            return cacheResultResolverFactory;
+        }
+
+        public CacheKeyGenerator getCacheResultKeyGenerator() {
+            return cacheResultKeyGenerator;
+        }
+
+        public CacheResult getCacheResult() {
+            return cacheResult;
+        }
+
+        public Integer[] getParameterIndices() {
+            return parameterIndices;
+        }
+
+        public Set<Annotation> getAnnotations() {
+            return annotations;
+        }
+
+        public Integer[] getKeysIndices() {
+            return keysIndices;
+        }
+
+        public Integer getValuesIndex() {
+            return valueIndex;
+        }
+
+        public Integer getValueIndex() {
+            return valueIndex;
+        }
+
+        public String getCachePutCacheName() {
+            return cachePutCacheName;
+        }
+
+        public CacheResolverFactory getCachePutResolverFactory() {
+            return cachePutResolverFactory;
+        }
+
+        public CacheKeyGenerator getCachePutKeyGenerator() {
+            return cachePutKeyGenerator;
+        }
+
+        public CachePut getCachePut() {
+            return cachePut;
+        }
+
+        public String getCacheRemoveCacheName() {
+            return cacheRemoveCacheName;
+        }
+
+        public CacheResolverFactory getCacheRemoveResolverFactory() {
+            return cacheRemoveResolverFactory;
+        }
+
+        public CacheKeyGenerator getCacheRemoveKeyGenerator() {
+            return cacheRemoveKeyGenerator;
+        }
+
+        public CacheRemove getCacheRemove() {
+            return cacheRemove;
+        }
+
+        public String getCacheRemoveAllCacheName() {
+            return cacheRemoveAllCacheName;
+        }
+
+        public CacheResolverFactory getCacheRemoveAllResolverFactory() {
+            return cacheRemoveAllResolverFactory;
+        }
+
+        public boolean isCacheRemoveAllAfter() {
+            return cacheRemoveAllAfter;
+        }
+
+        public CacheRemoveAll getCacheRemoveAll() {
+            return cacheRemoveAll;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationContextImpl.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationContextImpl.java
new file mode 100644
index 0000000..bb48c4b
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationContextImpl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Set;
+
+import javax.cache.annotation.CacheInvocationContext;
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.interceptor.InvocationContext;
+
+public class CacheInvocationContextImpl<A extends Annotation> extends CacheMethodDetailsImpl<A>
+        implements CacheInvocationContext<A> {
+
+    private static final Object[] EMPTY_ARGS = new Object[0];
+
+    private CacheInvocationParameter[] parameters = null;
+
+    public CacheInvocationContextImpl(final InvocationContext delegate, final A cacheAnnotation, final String cacheName,
+            final CDIJCacheHelper.MethodMeta meta) {
+        super(delegate, cacheAnnotation, cacheName, meta);
+    }
+
+    @Override
+    public Object getTarget() {
+        return delegate.getTarget();
+    }
+
+    @Override
+    public CacheInvocationParameter[] getAllParameters() {
+        if (parameters == null) {
+            parameters = doGetAllParameters(null);
+        }
+        return parameters;
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> cls) {
+        if (cls.isAssignableFrom(getClass())) {
+            return cls.cast(this);
+        }
+        throw new IllegalArgumentException(cls.getName());
+    }
+
+    protected CacheInvocationParameter[] doGetAllParameters(final Integer[] indexes) {
+        final Object[] parameters = delegate.getParameters();
+        final Object[] args = parameters == null ? EMPTY_ARGS : parameters;
+        final Class<?>[] parameterTypes = meta.getParameterTypes();
+        final List<Set<Annotation>> parameterAnnotations = meta.getParameterAnnotations();
+
+        final CacheInvocationParameter[] parametersAsArray = new CacheInvocationParameter[indexes == null ? args.length
+                : indexes.length];
+        if (indexes == null) {
+            for (int i = 0; i < args.length; i++) {
+                parametersAsArray[i] = newCacheInvocationParameterImpl(parameterTypes[i], args[i], parameterAnnotations.get(i),
+                        i);
+            }
+        } else {
+            int outIdx = 0;
+            for (int idx = 0; idx < indexes.length; idx++) {
+                final int i = indexes[idx];
+                parametersAsArray[outIdx] = newCacheInvocationParameterImpl(parameterTypes[i], args[i],
+                        parameterAnnotations.get(i), i);
+                outIdx++;
+            }
+        }
+        return parametersAsArray;
+    }
+
+    private CacheInvocationParameterImpl newCacheInvocationParameterImpl(final Class<?> type, final Object arg,
+            final Set<Annotation> annotations, final int i) {
+        return new CacheInvocationParameterImpl(type, arg, annotations, i);
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationParameterImpl.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationParameterImpl.java
new file mode 100644
index 0000000..6311ab9
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheInvocationParameterImpl.java
@@ -0,0 +1,63 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.cache.annotation.CacheInvocationParameter;
+
+public class CacheInvocationParameterImpl implements CacheInvocationParameter {
+
+    private final Class<?> type;
+
+    private final Object value;
+
+    private final Set<Annotation> annotations;
+
+    private final int position;
+
+    public CacheInvocationParameterImpl(final Class<?> type, final Object value, final Set<Annotation> annotations,
+            final int position) {
+        this.type = type;
+        this.value = value;
+        this.annotations = annotations;
+        this.position = position;
+    }
+
+    @Override
+    public Class<?> getRawType() {
+        return type;
+    }
+
+    @Override
+    public Object getValue() {
+        return value;
+    }
+
+    @Override
+    public Set<Annotation> getAnnotations() {
+        return annotations;
+    }
+
+    @Override
+    public int getParameterPosition() {
+        return position;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyGeneratorImpl.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyGeneratorImpl.java
new file mode 100644
index 0000000..d39730d
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyGeneratorImpl.java
@@ -0,0 +1,39 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.lang.annotation.Annotation;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheKeyGenerator;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.GeneratedCacheKey;
+
+public class CacheKeyGeneratorImpl implements CacheKeyGenerator {
+
+    @Override
+    public GeneratedCacheKey generateCacheKey(final CacheKeyInvocationContext<? extends Annotation> cacheKeyInvocationContext) {
+        final CacheInvocationParameter[] keyParameters = cacheKeyInvocationContext.getKeyParameters();
+        final Object[] parameters = new Object[keyParameters.length];
+        for (int index = 0; index < keyParameters.length; index++) {
+            parameters[index] = keyParameters[index].getValue();
+        }
+        return new GeneratedCacheKeyImpl(parameters);
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyInvocationContextImpl.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyInvocationContextImpl.java
new file mode 100644
index 0000000..bedc65d
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheKeyInvocationContextImpl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.lang.annotation.Annotation;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.interceptor.InvocationContext;
+
+public class CacheKeyInvocationContextImpl<A extends Annotation> extends CacheInvocationContextImpl<A>
+        implements CacheKeyInvocationContext<A> {
+
+    private CacheInvocationParameter[] keyParams = null;
+
+    private CacheInvocationParameter valueParam = null;
+
+    public CacheKeyInvocationContextImpl(final InvocationContext delegate, final A annotation, final String name,
+            final CDIJCacheHelper.MethodMeta methodMeta) {
+        super(delegate, annotation, name, methodMeta);
+    }
+
+    @Override
+    public CacheInvocationParameter[] getKeyParameters() {
+        if (keyParams == null) {
+            keyParams = doGetAllParameters(meta.getKeysIndices());
+        }
+        return keyParams;
+    }
+
+    @Override
+    public CacheInvocationParameter getValueParameter() {
+        if (valueParam == null) {
+            valueParam = meta.getValueIndex() >= 0 ? doGetAllParameters(new Integer[] { meta.getValueIndex() })[0] : null;
+        }
+        return valueParam;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheMethodDetailsImpl.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheMethodDetailsImpl.java
new file mode 100644
index 0000000..9d80f6a
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheMethodDetailsImpl.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.geronimo.jcache.simple.cdi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+import javax.cache.annotation.CacheMethodDetails;
+import javax.interceptor.InvocationContext;
+
+public class CacheMethodDetailsImpl<A extends Annotation> implements CacheMethodDetails<A> {
+
+    protected final InvocationContext delegate;
+
+    protected final CDIJCacheHelper.MethodMeta meta;
+
+    private final Set<Annotation> annotations;
+
+    private final A cacheAnnotation;
+
+    private final String cacheName;
+
+    public CacheMethodDetailsImpl(final InvocationContext delegate, final A cacheAnnotation, final String cacheName,
+            final CDIJCacheHelper.MethodMeta meta) {
+        this.delegate = delegate;
+        this.annotations = meta.getAnnotations();
+        this.cacheAnnotation = cacheAnnotation;
+        this.cacheName = cacheName;
+        this.meta = meta;
+    }
+
+    @Override
+    public Method getMethod() {
+        return delegate.getMethod();
+    }
+
+    @Override
+    public Set<Annotation> getAnnotations() {
+        return annotations;
+    }
+
+    @Override
+    public A getCacheAnnotation() {
+        return cacheAnnotation;
+    }
+
+    @Override
+    public String getCacheName() {
+        return cacheName;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CachePutInterceptor.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CachePutInterceptor.java
new file mode 100644
index 0000000..100f120
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CachePutInterceptor.java
@@ -0,0 +1,82 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.io.Serializable;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.CachePut;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.GeneratedCacheKey;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+@CachePut
+@Interceptor
+@Priority(/* LIBRARY_BEFORE */1000)
+public class CachePutInterceptor implements Serializable {
+
+    @Inject
+    private CDIJCacheHelper helper;
+
+    @AroundInvoke
+    public Object cache(final InvocationContext ic) throws Throwable {
+        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
+
+        final String cacheName = methodMeta.getCachePutCacheName();
+
+        final CacheResolverFactory cacheResolverFactory = methodMeta.getCachePutResolverFactory();
+        final CacheKeyInvocationContext<CachePut> context = new CacheKeyInvocationContextImpl<CachePut>(ic,
+                methodMeta.getCachePut(), cacheName, methodMeta);
+        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
+        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
+
+        final GeneratedCacheKey cacheKey = methodMeta.getCachePutKeyGenerator().generateCacheKey(context);
+        final CachePut cachePut = methodMeta.getCachePut();
+        final boolean afterInvocation = methodMeta.isCachePutAfter();
+
+        if (!afterInvocation) {
+            cache.put(cacheKey, context.getValueParameter());
+        }
+
+        final Object result;
+        try {
+            result = ic.proceed();
+        } catch (final Throwable t) {
+            if (afterInvocation) {
+                if (helper.isIncluded(t.getClass(), cachePut.cacheFor(), cachePut.noCacheFor())) {
+                    cache.put(cacheKey, context.getValueParameter());
+                }
+            }
+
+            throw t;
+        }
+
+        if (afterInvocation) {
+            cache.put(cacheKey, context.getValueParameter());
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveAllInterceptor.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveAllInterceptor.java
new file mode 100644
index 0000000..1b4229b
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveAllInterceptor.java
@@ -0,0 +1,78 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.io.Serializable;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.CacheRemoveAll;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+@CacheRemoveAll
+@Interceptor
+@Priority(/* LIBRARY_BEFORE */1000)
+public class CacheRemoveAllInterceptor implements Serializable {
+
+    @Inject
+    private CDIJCacheHelper helper;
+
+    @AroundInvoke
+    public Object cache(final InvocationContext ic) throws Throwable {
+        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
+
+        final String cacheName = methodMeta.getCacheRemoveAllCacheName();
+
+        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheRemoveAllResolverFactory();
+        final CacheKeyInvocationContext<CacheRemoveAll> context = new CacheKeyInvocationContextImpl<CacheRemoveAll>(ic,
+                methodMeta.getCacheRemoveAll(), cacheName, methodMeta);
+        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
+        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
+
+        final boolean afterInvocation = methodMeta.isCachePutAfter();
+        if (!afterInvocation) {
+            cache.removeAll();
+        }
+
+        final Object result;
+        try {
+            result = ic.proceed();
+        } catch (final Throwable t) {
+            if (afterInvocation) {
+                if (helper.isIncluded(t.getClass(), methodMeta.getCacheRemoveAll().evictFor(),
+                        methodMeta.getCacheRemoveAll().noEvictFor())) {
+                    cache.removeAll();
+                }
+            }
+            throw t;
+        }
+
+        if (afterInvocation) {
+            cache.removeAll();
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveInterceptor.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveInterceptor.java
new file mode 100644
index 0000000..48547db
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheRemoveInterceptor.java
@@ -0,0 +1,82 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.io.Serializable;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.CacheRemove;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.GeneratedCacheKey;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+@CacheRemove
+@Interceptor
+@Priority(/* LIBRARY_BEFORE */1000)
+public class CacheRemoveInterceptor implements Serializable {
+
+    @Inject
+    private CDIJCacheHelper helper;
+
+    @AroundInvoke
+    public Object cache(final InvocationContext ic) throws Throwable {
+        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
+
+        final String cacheName = methodMeta.getCacheRemoveCacheName();
+
+        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheRemoveResolverFactory();
+        final CacheKeyInvocationContext<CacheRemove> context = new CacheKeyInvocationContextImpl<CacheRemove>(ic,
+                methodMeta.getCacheRemove(), cacheName, methodMeta);
+        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
+        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
+
+        final GeneratedCacheKey cacheKey = methodMeta.getCacheRemoveKeyGenerator().generateCacheKey(context);
+        final CacheRemove cacheRemove = methodMeta.getCacheRemove();
+        final boolean afterInvocation = methodMeta.isCacheRemoveAfter();
+
+        if (!afterInvocation) {
+            cache.remove(cacheKey);
+        }
+
+        final Object result;
+        try {
+            result = ic.proceed();
+        } catch (final Throwable t) {
+            if (afterInvocation) {
+                if (helper.isIncluded(t.getClass(), cacheRemove.evictFor(), cacheRemove.noEvictFor())) {
+                    cache.remove(cacheKey);
+                }
+            }
+
+            throw t;
+        }
+
+        if (afterInvocation) {
+            cache.remove(cacheKey);
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverFactoryImpl.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverFactoryImpl.java
new file mode 100644
index 0000000..ac99cf1
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverFactoryImpl.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.geronimo.jcache.simple.cdi;
+
+import java.lang.annotation.Annotation;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.annotation.CacheMethodDetails;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.CacheResult;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.spi.CachingProvider;
+
+public class CacheResolverFactoryImpl implements CacheResolverFactory {
+
+    private final CacheManager cacheManager;
+
+    private final CachingProvider provider;
+
+    public CacheResolverFactoryImpl() {
+        provider = Caching.getCachingProvider();
+        cacheManager = provider.getCacheManager(provider.getDefaultURI(), provider.getDefaultClassLoader());
+    }
+
+    @Override
+    public CacheResolver getCacheResolver(CacheMethodDetails<? extends Annotation> cacheMethodDetails) {
+        return findCacheResolver(cacheMethodDetails.getCacheName());
+    }
+
+    @Override
+    public CacheResolver getExceptionCacheResolver(final CacheMethodDetails<CacheResult> cacheMethodDetails) {
+        final String exceptionCacheName = cacheMethodDetails.getCacheAnnotation().exceptionCacheName();
+        if (exceptionCacheName == null || exceptionCacheName.isEmpty()) {
+            throw new IllegalArgumentException("CacheResult.exceptionCacheName() not specified");
+        }
+        return findCacheResolver(exceptionCacheName);
+    }
+
+    private CacheResolver findCacheResolver(String exceptionCacheName) {
+        Cache<?, ?> cache = cacheManager.getCache(exceptionCacheName);
+        if (cache == null) {
+            cache = createCache(exceptionCacheName);
+        }
+        return new CacheResolverImpl(cache);
+    }
+
+    private Cache<?, ?> createCache(final String exceptionCacheName) {
+        cacheManager.createCache(exceptionCacheName, new MutableConfiguration<Object, Object>().setStoreByValue(false));
+        return cacheManager.getCache(exceptionCacheName);
+    }
+
+    public void release() {
+        cacheManager.close();
+        provider.close();
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverImpl.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverImpl.java
new file mode 100644
index 0000000..62d9d14
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResolverImpl.java
@@ -0,0 +1,39 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.lang.annotation.Annotation;
+
+import javax.cache.Cache;
+import javax.cache.annotation.CacheInvocationContext;
+import javax.cache.annotation.CacheResolver;
+
+public class CacheResolverImpl implements CacheResolver {
+
+    private final Cache<?, ?> delegate;
+
+    public CacheResolverImpl(final Cache<?, ?> cache) {
+        delegate = cache;
+    }
+
+    @Override
+    public <K, V> Cache<K, V> resolveCache(final CacheInvocationContext<? extends Annotation> cacheInvocationContext) {
+        return (Cache<K, V>) delegate;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResultInterceptor.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResultInterceptor.java
new file mode 100644
index 0000000..9b1e95e
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/CacheResultInterceptor.java
@@ -0,0 +1,94 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.io.Serializable;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.CacheResult;
+import javax.cache.annotation.GeneratedCacheKey;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+@CacheResult
+@Interceptor
+@Priority(/* LIBRARY_BEFORE */1000)
+public class CacheResultInterceptor implements Serializable {
+
+    @Inject
+    private CDIJCacheHelper helper;
+
+    @AroundInvoke
+    public Object cache(final InvocationContext ic) throws Throwable {
+        final CDIJCacheHelper.MethodMeta methodMeta = helper.findMeta(ic);
+
+        final String cacheName = methodMeta.getCacheResultCacheName();
+
+        final CacheResult cacheResult = methodMeta.getCacheResult();
+        final CacheKeyInvocationContext<CacheResult> context = new CacheKeyInvocationContextImpl<CacheResult>(ic, cacheResult,
+                cacheName, methodMeta);
+
+        final CacheResolverFactory cacheResolverFactory = methodMeta.getCacheResultResolverFactory();
+        final CacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(context);
+        final Cache<Object, Object> cache = cacheResolver.resolveCache(context);
+
+        final GeneratedCacheKey cacheKey = methodMeta.getCacheResultKeyGenerator().generateCacheKey(context);
+
+        Cache<Object, Object> exceptionCache = null; // lazily created
+
+        Object result;
+        if (!cacheResult.skipGet()) {
+            result = cache.get(cacheKey);
+            if (result != null) {
+                return result;
+            }
+
+            if (!cacheResult.exceptionCacheName().isEmpty()) {
+                exceptionCache = cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context);
+                final Object exception = exceptionCache.get(cacheKey);
+                if (exception != null) {
+                    throw Throwable.class.cast(exception);
+                }
+            }
+        }
+
+        try {
+            result = ic.proceed();
+            if (result != null) {
+                cache.put(cacheKey, result);
+            }
+
+            return result;
+        } catch (final Throwable t) {
+            if (helper.isIncluded(t.getClass(), cacheResult.cachedExceptions(), cacheResult.nonCachedExceptions())) {
+                if (exceptionCache == null) {
+                    exceptionCache = cacheResolverFactory.getExceptionCacheResolver(context).resolveCache(context);
+                }
+                exceptionCache.put(cacheKey, t);
+            }
+            throw t;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/GeneratedCacheKeyImpl.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/GeneratedCacheKeyImpl.java
new file mode 100644
index 0000000..c8a6cc7
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/GeneratedCacheKeyImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import java.util.Arrays;
+
+import javax.cache.annotation.GeneratedCacheKey;
+
+public class GeneratedCacheKeyImpl implements GeneratedCacheKey {
+
+    private final Object[] params;
+
+    private final int hash;
+
+    public GeneratedCacheKeyImpl(final Object[] parameters) {
+        this.params = parameters;
+        this.hash = Arrays.deepHashCode(parameters);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final GeneratedCacheKeyImpl that = GeneratedCacheKeyImpl.class.cast(o);
+        return Arrays.deepEquals(params, that.params);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return hash;
+    }
+}
diff --git a/src/main/java/org/apache/geronimo/jcache/simple/cdi/MakeJCacheCDIInterceptorFriendly.java b/src/main/java/org/apache/geronimo/jcache/simple/cdi/MakeJCacheCDIInterceptorFriendly.java
new file mode 100644
index 0000000..b88205e
--- /dev/null
+++ b/src/main/java/org/apache/geronimo/jcache/simple/cdi/MakeJCacheCDIInterceptorFriendly.java
@@ -0,0 +1,227 @@
+/*
+ * 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.geronimo.jcache.simple.cdi;
+
+import static java.util.Arrays.asList;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.cache.annotation.CachePut;
+import javax.cache.annotation.CacheRemove;
+import javax.cache.annotation.CacheRemoveAll;
+import javax.cache.annotation.CacheResult;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.inject.spi.PassivationCapable;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.enterprise.util.AnnotationLiteral;
+
+// TODO: observe annotated type (or maybe sthg else) to cache data and inject this extension (used as metadata cache)
+// to get class model and this way allow to add cache annotation on the fly - == avoid java pure reflection to get metadata
+public class MakeJCacheCDIInterceptorFriendly implements Extension {
+
+    private static final AtomicInteger id = new AtomicInteger();
+
+    private static final boolean USE_ID = !Boolean.getBoolean("org.apache.geronimo.jcache.simple.skip-id");
+    private static final boolean SKIP = !Boolean.getBoolean("org.apache.geronimo.jcache.simple.skip-cdi");
+
+    private boolean needHelper = true;
+
+    protected void discoverInterceptorBindings(final @Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent,
+            final BeanManager bm) {
+        if (SKIP) {
+            return;
+        }
+        // CDI 1.1 will just pick createAnnotatedType(X) as beans so we'll skip our HelperBean
+        // but CDI 1.0 needs our HelperBean + interceptors in beans.xml like:
+        /*
+         * <beans xmlns="http://java.sun.com/xml/ns/javaee"
+         * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         * xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+         * http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
+         * <interceptors>
+         * <class>org.apache.geronimo.jcache.simple.cdi.CacheResultInterceptor</class>
+         * <class>org.apache.geronimo.jcache.simple.cdi.CacheRemoveAllInterceptor</class>
+         * <class>org.apache.geronimo.jcache.simple.cdi.CacheRemoveInterceptor</class>
+         * <class>org.apache.geronimo.jcache.simple.cdi.CachePutInterceptor</class>
+         * </interceptors>
+         * </beans>
+         */
+        bm.createAnnotatedType(CDIJCacheHelper.class);
+        for (final Class<?> interceptor : asList(CachePutInterceptor.class, CacheRemoveInterceptor.class,
+                CacheRemoveAllInterceptor.class, CacheResultInterceptor.class)) {
+            beforeBeanDiscoveryEvent.addAnnotatedType(bm.createAnnotatedType(interceptor));
+        }
+        for (final Class<? extends Annotation> interceptor : asList(CachePut.class, CacheRemove.class, CacheRemoveAll.class,
+                CacheResult.class)) {
+            beforeBeanDiscoveryEvent.addInterceptorBinding(interceptor);
+        }
+    }
+
+    protected void addHelper(final @Observes AfterBeanDiscovery afterBeanDiscovery, final BeanManager bm) {
+        if (SKIP) {
+            return;
+        }
+        if (!needHelper) {
+            return;
+        }
+        /*
+         * CDI >= 1.1 only. Actually we shouldn't go here with CDI 1.1 since we defined the annotated type for the helper
+         * final AnnotatedType<CDIJCacheHelper> annotatedType = bm.createAnnotatedType(CDIJCacheHelper.class);
+         * final BeanAttributes<CDIJCacheHelper> beanAttributes = bm.createBeanAttributes(annotatedType);
+         * final InjectionTarget<CDIJCacheHelper> injectionTarget = bm.createInjectionTarget(annotatedType);
+         * final Bean<CDIJCacheHelper> bean = bm.createBean(beanAttributes, CDIJCacheHelper.class, new
+         * InjectionTargetFactory<CDIJCacheHelper>() {
+         * 
+         * @Override
+         * public InjectionTarget<CDIJCacheHelper> createInjectionTarget(Bean<CDIJCacheHelper> bean) {
+         * return injectionTarget;
+         * }
+         * });
+         */
+        final AnnotatedType<CDIJCacheHelper> annotatedType = bm.createAnnotatedType(CDIJCacheHelper.class);
+        final InjectionTarget<CDIJCacheHelper> injectionTarget = bm.createInjectionTarget(annotatedType);
+        final HelperBean bean = new HelperBean(annotatedType, injectionTarget, findIdSuffix());
+        afterBeanDiscovery.addBean(bean);
+    }
+
+    protected void vetoScannedCDIJCacheHelperQualifiers(final @Observes ProcessAnnotatedType<CDIJCacheHelper> pat) {
+        if (SKIP) {
+            return;
+        }
+        if (!needHelper) { // already seen, shouldn't really happen,just a protection
+            pat.veto();
+        }
+        needHelper = false;
+    }
+
+    // TODO: make it better for ear+cluster case with CDI 1.0
+    private String findIdSuffix() {
+        // big disadvantage is all deployments of a cluster needs to be in the exact same order but it works with ears
+        if (USE_ID) {
+            return "lib" + id.incrementAndGet();
+        }
+        return "default";
+    }
+
+    public static class HelperBean implements Bean<CDIJCacheHelper>, PassivationCapable {
+
+        private final AnnotatedType<CDIJCacheHelper> at;
+
+        private final InjectionTarget<CDIJCacheHelper> it;
+
+        private final HashSet<Annotation> qualifiers;
+
+        private final String id;
+
+        public HelperBean(final AnnotatedType<CDIJCacheHelper> annotatedType,
+                final InjectionTarget<CDIJCacheHelper> injectionTarget, final String id) {
+            this.at = annotatedType;
+            this.it = injectionTarget;
+            this.id = "JCS#CDIHelper#" + id;
+
+            this.qualifiers = new HashSet<Annotation>();
+            this.qualifiers.add(new AnnotationLiteral<Default>() {
+            });
+            this.qualifiers.add(new AnnotationLiteral<Any>() {
+            });
+        }
+
+        @Override
+        public Set<InjectionPoint> getInjectionPoints() {
+            return it.getInjectionPoints();
+        }
+
+        @Override
+        public Class<?> getBeanClass() {
+            return at.getJavaClass();
+        }
+
+        @Override
+        public boolean isNullable() {
+            return false;
+        }
+
+        @Override
+        public Set<Type> getTypes() {
+            return at.getTypeClosure();
+        }
+
+        @Override
+        public Set<Annotation> getQualifiers() {
+            return qualifiers;
+        }
+
+        @Override
+        public Class<? extends Annotation> getScope() {
+            return ApplicationScoped.class;
+        }
+
+        @Override
+        public String getName() {
+            return null;
+        }
+
+        @Override
+        public Set<Class<? extends Annotation>> getStereotypes() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean isAlternative() {
+            return false;
+        }
+
+        @Override
+        public CDIJCacheHelper create(final CreationalContext<CDIJCacheHelper> context) {
+            final CDIJCacheHelper produce = it.produce(context);
+            it.inject(produce, context);
+            it.postConstruct(produce);
+            return produce;
+        }
+
+        @Override
+        public void destroy(final CDIJCacheHelper instance, final CreationalContext<CDIJCacheHelper> context) {
+            it.preDestroy(instance);
+            it.dispose(instance);
+            context.release();
+        }
+
+        @Override
+        public String getId() {
+            return id;
+        }
+    }
+}
diff --git a/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider b/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider
new file mode 100644
index 0000000..d7652c5
--- /dev/null
+++ b/src/main/resources/META-INF/services/javax.cache.spi.CachingProvider
@@ -0,0 +1 @@
+org.apache.geronimo.jcache.simple.SimpleProvider
diff --git a/src/test/java/org/apache/geronimo/jcache/simple/tck/OWBBeanProvider.java b/src/test/java/org/apache/geronimo/jcache/simple/tck/OWBBeanProvider.java
new file mode 100644
index 0000000..ee7adad
--- /dev/null
+++ b/src/test/java/org/apache/geronimo/jcache/simple/tck/OWBBeanProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.geronimo.jcache.simple.tck;
+
+import java.util.Set;
+
+import javax.cache.annotation.BeanProvider;
+import javax.enterprise.inject.spi.Bean;
+
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.container.BeanManagerImpl;
+import org.apache.webbeans.spi.ContainerLifecycle;
+
+public class OWBBeanProvider implements BeanProvider {
+
+    private final BeanManagerImpl bm;
+
+    public OWBBeanProvider() {
+        final WebBeansContext webBeansContext = WebBeansContext.currentInstance();
+        final ContainerLifecycle lifecycle = webBeansContext.getService(ContainerLifecycle.class);
+        lifecycle.startApplication(null);
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+
+            @Override
+            public void run() {
+                lifecycle.stopApplication(null);
+            }
+        });
+        bm = webBeansContext.getBeanManagerImpl();
+    }
+
+    @Override
+    public <T> T getBeanByType(final Class<T> tClass) {
+        if (tClass == null) {
+            throw new IllegalArgumentException("no bean class specified");
+        }
+
+        final Set<Bean<?>> beans = bm.getBeans(tClass);
+        if (beans.isEmpty()) {
+            throw new IllegalStateException("no bean of type " + tClass.getName());
+        }
+        final Bean<?> bean = bm.resolve(beans);
+        return (T) bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
+    }
+}
diff --git a/src/test/resources/ExcludeList b/src/test/resources/ExcludeList
new file mode 100644
index 0000000..170a7fa
--- /dev/null
+++ b/src/test/resources/ExcludeList
@@ -0,0 +1,19 @@
+# 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.
+
+# for tck this test needs to be excluded
+org.jsr107.tck.CachingTest#dummyTest
diff --git a/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider b/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider
new file mode 100644
index 0000000..8825dc7
--- /dev/null
+++ b/src/test/resources/META-INF/services/javax.cache.annotation.BeanProvider
@@ -0,0 +1 @@
+org.apache.geronimo.jcache.simple.tck.OWBBeanProvider

-- 
To stop receiving notification emails like this one, please contact
rmannibucau@apache.org.