Provide proxy kv, add tests for JPA store
diff --git a/build.gradle b/build.gradle
index b539938..f840203 100644
--- a/build.gradle
+++ b/build.gradle
@@ -22,7 +22,10 @@
   repositories { jcenter() // jcenter
   }
 
-  dependencies { classpath "org.ajoberstar.grgit:grgit-core:3.1.1" }
+  dependencies {
+    classpath "org.ajoberstar.grgit:grgit-core:3.1.1"
+    classpath 'com.radcortez.gradle:openjpa-gradle-plugin:3.1.0'
+  }
 }
 
 plugins {
diff --git a/dependency-versions.gradle b/dependency-versions.gradle
index fa8d1cd..11b2eed 100644
--- a/dependency-versions.gradle
+++ b/dependency-versions.gradle
@@ -35,6 +35,7 @@
       entry 'antlr4-runtime'
     }
     dependency('org.apache.lucene:lucene-core:7.6.0')
+    dependency('org.apache.openjpa:openjpa:3.1.0')
     dependency('org.assertj:assertj-core:3.11.1')
     dependencySet(group: 'org.bouncycastle', version: '1.60') {
       entry 'bcpkix-jdk15on'
diff --git a/kv/build.gradle b/kv/build.gradle
index d05b5c2..c6f8aa1 100644
--- a/kv/build.gradle
+++ b/kv/build.gradle
@@ -12,7 +12,40 @@
  */
 description = 'Key value store implementations.'
 
+task openJPAEnhance {
+  description "Enhance JPA model classes using OpenJPA Enhancer"
+  dependsOn compileTestJava
+
+  doLast {
+    // define the entity classes
+    def entityFiles = sourceSets.test.output.asFileTree.filter { it.name == "Store.class" }
+
+    println "Enhancing with OpenJPA, the following files..."
+    entityFiles.getFiles().each { println it }
+
+    // define Ant task for Enhancer
+    ant.taskdef(
+      name : 'openjpac',
+      classpath : sourceSets.test.runtimeClasspath.asPath,
+      classname : 'org.apache.openjpa.ant.PCEnhancerTask'
+      )
+
+    // Run the OpenJPA Enhancer as an Ant task
+    //   - see OpenJPA 'PCEnhancerTask' for supported arguments
+    //   - this invocation of the enhancer adds support for a default-ctor
+    //   - as well as ensuring JPA property use is valid.
+    ant.openjpac(
+      classpath: sourceSets.test.runtimeClasspath.asPath,
+      addDefaultConstructor: true,
+      enforcePropertyRestrictions: true) {
+        entityFiles.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
+      }
+  }
+}
+test { dependsOn openJPAEnhance }
+
 dependencies {
+
   compile project(':bytes')
   compile project(':concurrent-coroutines')
   compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
@@ -41,6 +74,8 @@
   testCompile 'org.junit.jupiter:junit-jupiter-params'
   testCompile 'org.mapdb:mapdb'
   testCompile 'org.rocksdb:rocksdbjni'
+  testRuntime 'org.apache.openjpa:openjpa'
+
 
   testRuntime 'org.jetbrains.spek:spek-junit-platform-engine'
   testRuntime 'org.junit.jupiter:junit-jupiter-engine'
diff --git a/kv/src/main/kotlin/org/apache/tuweni/kv/EntityManagerKeyValueStore.kt b/kv/src/main/kotlin/org/apache/tuweni/kv/EntityManagerKeyValueStore.kt
index 8b653ad..712aec9 100644
--- a/kv/src/main/kotlin/org/apache/tuweni/kv/EntityManagerKeyValueStore.kt
+++ b/kv/src/main/kotlin/org/apache/tuweni/kv/EntityManagerKeyValueStore.kt
@@ -16,7 +16,6 @@
  */
 package org.apache.tuweni.kv
 
-import kotlin.jvm.Throws
 import kotlinx.coroutines.Dispatchers
 
 import java.io.IOException
@@ -27,7 +26,6 @@
 import kotlin.coroutines.CoroutineContext
 
 class EntityManagerKeyValueStore<K, V>
-        @Throws(IOException::class)
         constructor(
           private val entityManagerProvider: () -> EntityManager,
           private val entityClass: Class<V>,
@@ -44,12 +42,11 @@
      * @throws IOException If an I/O error occurs.
      */
     @JvmStatic
-    @Throws(IOException::class)
     fun <K, V> open(
       entityManagerProvider: Supplier<EntityManager>,
       entityClass: Class<V>,
       idAccessor: Function<V, K>
-    ) = EntityManagerKeyValueStore<K, V>(entityManagerProvider::get, entityClass, idAccessor::apply)
+    ) = EntityManagerKeyValueStore(entityManagerProvider::get, entityClass, idAccessor::apply)
   }
 
   override suspend fun get(key: K): V? {
@@ -63,6 +60,8 @@
     }
   }
 
+  suspend fun put(value: V) = put(idAccessor(value), value)
+
   override suspend fun put(key: K, value: V) {
     val em = entityManagerProvider()
     em.transaction.begin()
@@ -76,8 +75,11 @@
 
   override suspend fun keys(): Iterable<K> {
     val em = entityManagerProvider()
-    val query = em.createQuery(em.criteriaBuilder.createQuery(entityClass))
-    val resultStream: Stream<V> = query.resultStream
+    val query = em.criteriaBuilder.createQuery(entityClass)
+    val root = query.from(entityClass)
+    val all = query.select(root)
+    val finalAll = em.createQuery(all)
+    val resultStream: Stream<V> = finalAll.resultStream
     return Iterable { resultStream.map(idAccessor).iterator() }
   }
 
diff --git a/kv/src/main/kotlin/org/apache/tuweni/kv/ProxyKeyValueStore.kt b/kv/src/main/kotlin/org/apache/tuweni/kv/ProxyKeyValueStore.kt
new file mode 100644
index 0000000..6d68683
--- /dev/null
+++ b/kv/src/main/kotlin/org/apache/tuweni/kv/ProxyKeyValueStore.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.tuweni.kv
+
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * A store used as a proxy for another store.
+ *
+ * For example, we may want to store rich objects and transform them to a lower-level form,
+ * or reuse the same store across multiple usages.
+ */
+class ProxyKeyValueStore<K, V, E, R>(
+  private val store: KeyValueStore<E, R>,
+  private val unproxyKey: (E) -> K,
+  private val proxyKey: (K) -> E,
+  private val unproxyValue: (R?) -> V?,
+  private val proxyValue: (V) -> R,
+  override val coroutineContext: CoroutineContext = store.coroutineContext
+) : KeyValueStore<K, V> {
+
+  override suspend fun get(key: K): V? = unproxyValue(store.get(proxyKey(key)))
+
+  override suspend fun put(key: K, value: V) = store.put(proxyKey(key), proxyValue(value))
+
+  override suspend fun keys(): Iterable<K> = store.keys().map(unproxyKey)
+
+  override fun close() = store.close()
+}
diff --git a/kv/src/test/java/org/apache/tuweni/kv/Store.java b/kv/src/test/java/org/apache/tuweni/kv/Store.java
new file mode 100644
index 0000000..069db93
--- /dev/null
+++ b/kv/src/test/java/org/apache/tuweni/kv/Store.java
@@ -0,0 +1,64 @@
+/*
+ * 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.tuweni.kv;
+
+import java.util.Objects;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity(name = "STORE")
+public class Store {
+
+  public Store() {}
+
+  public Store(String key, String value) {
+    this.key = key;
+    this.value = value;
+  }
+
+  @Id
+  private String key;
+
+  private String value;
+
+  public String getKey() {
+    return key;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public void setKey(String key) {
+    this.key = key;
+  }
+
+  public void setValue(String value) {
+    this.value = value;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o)
+      return true;
+    if (!(o instanceof Store))
+      return false;
+    Store store = (Store) o;
+    return Objects.equals(key, store.key) && Objects.equals(value, store.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(key, value);
+  }
+}
diff --git a/kv/src/test/kotlin/org/apache/tuweni/kv/KeyValueStoreSpec.kt b/kv/src/test/kotlin/org/apache/tuweni/kv/KeyValueStoreSpec.kt
index 94f4ffb..a69277b 100644
--- a/kv/src/test/kotlin/org/apache/tuweni/kv/KeyValueStoreSpec.kt
+++ b/kv/src/test/kotlin/org/apache/tuweni/kv/KeyValueStoreSpec.kt
@@ -21,6 +21,7 @@
 import com.winterbe.expekt.should
 import kotlinx.coroutines.runBlocking
 import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.io.Base64
 import org.apache.tuweni.kv.Vars.bar
 import org.apache.tuweni.kv.Vars.foo
 import org.apache.tuweni.kv.Vars.foobar
@@ -35,6 +36,7 @@
 import java.nio.file.Paths
 import java.sql.DriverManager
 import java.util.concurrent.RejectedExecutionException
+import javax.persistence.Persistence
 
 object Vars {
   val foo = Bytes.wrap("foo".toByteArray())!!
@@ -380,3 +382,100 @@
     }
   }
 })
+
+object EntityManagerKeyValueStoreSpec : Spek({
+  val entityManagerFactory = Persistence.createEntityManagerFactory("h2")
+  val kv = EntityManagerKeyValueStore(entityManagerFactory::createEntityManager, Store::class.java, { it.key })
+  afterGroup {
+    kv.close()
+  }
+  describe("a JPA entity manager-backed key value store") {
+
+    it("should allow to retrieve values") {
+      runBlocking {
+        kv.put("foo", Store("foo", "bar"))
+        kv.get("foo").should.equal(Store("foo", "bar"))
+      }
+    }
+
+    it("should allow to update values") {
+      runBlocking {
+        kv.put("foo", Store("foo", "bar"))
+        kv.put("foo", Store("foo", "foobar"))
+        kv.get("foo").should.equal(Store("foo", "foobar"))
+      }
+    }
+
+    it("should allow to update values without keys") {
+      runBlocking {
+        kv.put(Store("foo2", "bar2"))
+        kv.put(Store("foo2", "foobar2"))
+        kv.get("foo2").should.equal(Store("foo2", "foobar2"))
+      }
+    }
+
+    it("should return null when no value is present") {
+      runBlocking {
+        kv.get("foobar").should.be.`null`
+      }
+    }
+
+    it("should iterate over keys") {
+      runBlocking {
+        kv.put("foo", Store("foo", "bar"))
+        kv.put("bar", Store("bar", "bar"))
+        val keys = kv.keys().map { it }
+        keys.should.contain("bar")
+        keys.should.contain("foo")
+      }
+    }
+  }
+})
+
+object ProxyKeyValueStoreSpec : Spek({
+  val kv = MapKeyValueStore<String, String>()
+  val proxy = ProxyKeyValueStore(
+    kv,
+    Base64::decode,
+    Base64::encode,
+    { if (it == null) { null } else { Base64.decode(it) } },
+    Base64::encode
+  )
+  afterGroup {
+    proxy.close()
+  }
+  describe("a proixy key value store") {
+
+    it("should allow to retrieve values") {
+      runBlocking {
+        proxy.put(foo, bar)
+        proxy.get(foo).should.equal(bar)
+        kv.get(Base64.encode(foo)).should.equal(Base64.encode(bar))
+      }
+    }
+
+    it("should allow to update values") {
+      runBlocking {
+        proxy.put(foo, bar)
+        proxy.put(foo, foobar)
+        proxy.get(foo).should.equal(foobar)
+      }
+    }
+
+    it("should return null when no value is present") {
+      runBlocking {
+        proxy.get(foobar).should.be.`null`
+      }
+    }
+
+    it("should iterate over keys") {
+      runBlocking {
+        proxy.put(foo, bar)
+        proxy.put(bar, foo)
+        val keys = proxy.keys().map { it }
+        keys.should.contain(bar)
+        keys.should.contain(foo)
+      }
+    }
+  }
+})
diff --git a/kv/src/test/resources/META-INF/persistence.xml b/kv/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..e77da5a
--- /dev/null
+++ b/kv/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+<persistence version="2.1"
+             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
+
+    <!-- h2 -->
+
+    <persistence-unit name="h2" transaction-type="RESOURCE_LOCAL">
+        <class>org.apache.tuweni.kv.Store</class>
+        <properties>
+            <property name="openjpa.RuntimeUnenhancedClasses" value="supported" />
+            <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
+            <property name="openjpa.InitializeEagerly" value="true"/>
+            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
+            <property name="javax.persistence.jdbc.url"    value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" />
+            <property name="javax.persistence.jdbc.user" value="sa" />
+            <property name="javax.persistence.jdbc.password" value="" />
+            <property name="show_sql" value="true"/>
+        </properties>
+    </persistence-unit>
+
+</persistence>
\ No newline at end of file