geronimo-scm mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rmannibu...@apache.org
Subject [geronimo-jcache-simple] 01/01: initial import
Date Tue, 20 Mar 2018 13:53:34 GMT
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 <rmannibucau@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.

Mime
View raw message