CVE-2016-15018: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: A vulnerability was found in krail-jpa up to 0.9.1. It has been classified as critical. This affects an unknown part. The manipulation leads to sql injection. Upgrading to version 0.9.2 is able to address this issue. The identifier of the patch is c1e848665492e21ef6cc9be443205e36b9a1f6be. It is recommended to upgrade the affected component. The identifier VDB-218373 was assigned to this vulnerability.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 9.8
Published: 2023-01-15T19:15Z
Repository: https://github.com/KrailOrg/krail-jpa
Language: Java
Files changed: src/main/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao.java, src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternDao.java, src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternEntity.java, src/main/java/uk/q3c/krail/jpa/i18n/KeyValueEntity.java, src/main/java/uk/q3c/krail/jpa/i18n/PatternId.java, src/main/java/uk/q3c/krail/jpa/persist/BaseJpaDao.java, src/main/java/uk/q3c/krail/jpa/persist/BaseJpaKeyValueDao.java, src/main/java/uk/q3c/krail/jpa/persist/DefaultJpaInstanceConfiguration.java, src/main/java/uk/q3c/krail/jpa/persist/JpaKeyValueDao.java, src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionContainerProvider.java, src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao.java, src/main/java/uk/q3c/krail/jpa/user/opt/JpaOptionDao.java, src/main/java/uk/q3c/krail/jpa/user/opt/JpaOptionEntity.java, src/main/java/uk/q3c/krail/jpa/user/opt/OptionId.java, src/test/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao__IntegrationTest.groovy, src/test/java/uk/q3c/krail/jpa/i18n/JpaPatternEntity_Test.groovy, src/test/java/uk/q3c/krail/jpa/persist/TestJpaModule.java, src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDaoTest.groovy, src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao__DataTypeTest.groovy, src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao__IntegrationTest.groovy, src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDaoTest2.java, src/test/resources/META-INF/persistence.xml
Commit date: 2016-01-18 18:34:48 +0000
Commit message: Fix #18 SQLInjection vulnerability cleared

Pattern and Option DAOs re-written to a common key-value base class.  Using composite primary key in place of surrogate key.

Diff: diff --git a/src/main/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao.java b/src/main/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao.java
new file mode 100644
index 0000000..8afe71c
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao.java
@@ -0,0 +1,57 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.i18n;
+
+import com.google.inject.Inject;
+import org.apache.onami.persist.EntityManagerProvider;
+import org.apache.onami.persist.PersistenceUnitModule;
+import uk.q3c.krail.i18n.PatternCacheKey;
+import uk.q3c.krail.jpa.persist.BaseJpaKeyValueDao;
+
+import javax.annotation.Nonnull;
+import java.util.Optional;
+
+/**
+ * The default implementation of {@link JpaPatternDao}.  The {@code entityManagerProvider} and {@code dao} are bound by {@link PersistenceUnitModule} to the
+ * annotation which is used in the injection of this class. (For example, if an instance of this class is annotated with @Jpa1, then the constructor parameters
+ * will also be bound with @Jpa1)
+ * <p>
+ * Created by David Sowerby on 15/04/15.
+ */
+public class DefaultJpaPatternDao extends BaseJpaKeyValueDao<PatternId, PatternCacheKey, JpaPatternEntity> implements JpaPatternDao {
+
+
+    @Inject
+    protected DefaultJpaPatternDao(EntityManagerProvider entityManagerProvider) {
+        super(entityManagerProvider, JpaPatternEntity.class);
+    }
+
+
+    @Override
+    protected JpaPatternEntity newEntity(PatternCacheKey cacheKey, String value) {
+        return new JpaPatternEntity(cacheKey, value);
+    }
+
+    @Override
+    protected PatternId newId(PatternCacheKey cacheKey) {
+        return new PatternId(cacheKey);
+    }
+
+
+    @Nonnull
+    @Override
+    public Optional<String> getValue(@Nonnull PatternCacheKey cacheKey) {
+        return getValueAsString(cacheKey);
+    }
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao_LongInt.java b/src/main/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao_LongInt.java
deleted file mode 100644
index b5f76f7..0000000
--- a/src/main/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao_LongInt.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (c) 2015. David Sowerby
- *
- * Licensed 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 uk.q3c.krail.jpa.i18n;
-
-import com.google.inject.Inject;
-import org.apache.onami.persist.EntityManagerProvider;
-import org.apache.onami.persist.PersistenceUnitModule;
-import org.apache.onami.persist.Transactional;
-import uk.q3c.krail.core.data.Select;
-import uk.q3c.krail.i18n.PatternCacheKey;
-import uk.q3c.krail.jpa.persist.DefaultJpaDao_LongInt;
-
-import javax.annotation.Nonnull;
-import javax.persistence.TypedQuery;
-import java.util.List;
-import java.util.Optional;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * The default implementation of {@link JpaPatternDao}.  The {@code entityManagerProvider} and {@code dao} are bound by {@link PersistenceUnitModule} to the
- * annotation which is used in the injection of this class. (For example, if an instance of this class is annotated with @Jpa1, then the constructor parameters
- * will also be bound with @Jpa1)
- * <p>
- * Created by David Sowerby on 15/04/15.
- */
-public class DefaultJpaPatternDao_LongInt extends DefaultJpaDao_LongInt implements JpaPatternDao {
-
-    @Inject
-    protected DefaultJpaPatternDao_LongInt(EntityManagerProvider entityManagerProvider) {
-        super(entityManagerProvider);
-    }
-
-    @Transactional
-    @Override
-    public void write(@Nonnull PatternCacheKey cacheKey, @Nonnull String value) {
-        checkNotNull(cacheKey);
-        checkNotNull(value);
-        Optional<PatternEntity_LongInt> existingValue = find(cacheKey);
-        if (existingValue.isPresent()) {
-            existingValue.get()
-                         .setValue(value);
-            save(existingValue.get());
-        } else {
-            save(keyToEntity(cacheKey, value));
-        }
-    }
-
-    @Nonnull
-    protected PatternEntity_LongInt keyToEntity(@Nonnull PatternCacheKey key, @Nonnull String value) {
-        return new PatternEntity_LongInt(key, value);
-    }
-
-    @Transactional
-    @Override
-    @Nonnull
-    public Optional<PatternEntity_LongInt> find(@Nonnull PatternCacheKey cacheKey) {
-        checkNotNull(cacheKey);
-        Select select = selectEntity(cacheKey);
-
-        TypedQuery<PatternEntity_LongInt> query = getEntityManager().createQuery(select.toString(), PatternEntity_LongInt.class);
-        List<PatternEntity_LongInt> results = query.getResultList();
-        if (results.isEmpty()) {
-            return Optional.empty();
-        } else {
-            return Optional.of(results.get(0));
-        }
-    }
-
-    protected Select selectEntity(PatternCacheKey cacheKey) {
-        //ensure the same conversion as writing
-        PatternEntity_LongInt searchKey = new PatternEntity_LongInt(cacheKey, "x");
-        return new Select().from(entityName(PatternEntity_LongInt.class))
-                           .where("i18nkey", searchKey.getI18nkey())
-                           .and("locale", searchKey.getLocale());
-    }
-
-    @Transactional
-    @Nonnull
-    @Override
-    public Optional<String> deleteValue(@Nonnull PatternCacheKey cacheKey) {
-        checkNotNull(cacheKey);
-
-
-        TypedQuery<PatternEntity_LongInt> query = getEntityManager().createQuery(selectFromKey(cacheKey), PatternEntity_LongInt.class);
-        List<PatternEntity_LongInt> results = query.getResultList();
-        if (results.isEmpty()) {
-            return Optional.empty();
-        } else {
-            final Optional<PatternEntity_LongInt> patternEntity = deleteById(PatternEntity_LongInt.class, results.get(0)
-                                                                                                 .getId());
-            if (patternEntity.isPresent()) {
-                return Optional.of(patternEntity.get()
-                                                .getValue());
-            } else {
-                return Optional.empty();
-            }
-        }
-    }
-
-    /**
-     * Returns a Select String from the {@code cacheKey}.  Uses {@link PatternEntity_LongInt} to ensure conversion from key to entity is consistent for the esearch
-     * compared to the original write
-     *
-     * @param cacheKey
-     *         the key being selected
-     *
-     * @return a Select String from the {@code cacheKey}
-     */
-    protected String selectFromKey(@Nonnull PatternCacheKey cacheKey) {
-        PatternEntity_LongInt searchPattern = keyToEntity(cacheKey, "x");
-        return new Select().from(PatternEntity_LongInt.class)
-                           .where("i18nkey", searchPattern.getI18nkey())
-                           .and("locale", searchPattern.getLocale())
-                           .toString();
-    }
-
-    @Nonnull
-    @Override
-    public Optional<String> getValue(@Nonnull PatternCacheKey cacheKey) {
-        checkNotNull(cacheKey);
-        Optional<PatternEntity_LongInt> entity = find(cacheKey);
-        if (entity.isPresent()) {
-            return Optional.of(entity.get()
-                                     .getValue());
-        } else {
-            return Optional.empty();
-        }
-    }
-
-    /**
-     * returns the number of entries
-     *
-     * @return the number of entries
-     */
-    @Override
-    @Transactional
-    public long count() {
-        return super.count(PatternEntity_LongInt.class);
-    }
-
-
-}
diff --git a/src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternDao.java b/src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternDao.java
index d98ffe6..f109156 100644
--- a/src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternDao.java
+++ b/src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternDao.java
@@ -1,27 +1,27 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.i18n;
 
 import uk.q3c.krail.i18n.PatternCacheKey;
 import uk.q3c.krail.i18n.PatternDao;
 
-import java.util.Optional;
-
 /**
  * JPA specific interface for {@link PatternDao} to enable binding alternatives
  * <p>
  * Created by David Sowerby on 15/04/15.
  */
 public interface JpaPatternDao extends PatternDao {
 
-    Optional<PatternEntity_LongInt> find(PatternCacheKey cacheKey);
+    JpaPatternEntity find(PatternCacheKey cacheKey);
 }
diff --git a/src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternEntity.java b/src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternEntity.java
new file mode 100644
index 0000000..2f6ab53
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/i18n/JpaPatternEntity.java
@@ -0,0 +1,70 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.i18n;
+
+import uk.q3c.krail.i18n.PatternCacheKey;
+
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Version;
+
+/**
+ * An entity to represent an I18N key, Locale and value combination
+ * <p>
+ * Created by David Sowerby on 15/04/15.
+ */
+
+@Entity
+public class JpaPatternEntity implements KeyValueEntity<PatternId, Integer> {
+
+    @EmbeddedId
+    private PatternId id;
+    private String value;
+    @Version
+    private Integer version;
+
+    protected JpaPatternEntity() {
+    }
+
+    public JpaPatternEntity(PatternCacheKey cacheKey, String value) {
+        id = new PatternId(cacheKey);
+        this.value = value;
+    }
+
+    public String getI18nkey() {
+        return id.getI18nkey();
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getLocale() {
+        return id.getLocale();
+    }
+
+    @Override
+    public PatternId getId() {
+        return id;
+    }
+
+    @Override
+    public Integer getVersion() {
+        return version;
+    }
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/i18n/KeyValueEntity.java b/src/main/java/uk/q3c/krail/jpa/i18n/KeyValueEntity.java
new file mode 100644
index 0000000..c21463a
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/i18n/KeyValueEntity.java
@@ -0,0 +1,26 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.i18n;
+
+import uk.q3c.krail.core.data.KrailEntity;
+
+/**
+ * Created by David Sowerby on 18 Jan 2016
+ */
+public interface KeyValueEntity<ID, VER> extends KrailEntity<ID, VER> {
+
+    String getValue();
+
+    void setValue(String value);
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/i18n/PatternEntity_LongInt.java b/src/main/java/uk/q3c/krail/jpa/i18n/PatternEntity_LongInt.java
deleted file mode 100644
index 3e05d9c..0000000
--- a/src/main/java/uk/q3c/krail/jpa/i18n/PatternEntity_LongInt.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2015. David Sowerby
- *
- * Licensed 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 uk.q3c.krail.jpa.i18n;
-
-import uk.q3c.krail.core.data.EnumConverter;
-import uk.q3c.krail.i18n.PatternCacheKey;
-import uk.q3c.krail.jpa.persist.EntityBase_LongInt;
-
-import javax.persistence.Entity;
-
-/**
- * An entity to represent an I18N key, Locale and value combination
- * <p>
- * Created by David Sowerby on 15/04/15.
- */
-
-@Entity
-public class PatternEntity_LongInt extends EntityBase_LongInt {
-
-    private String i18nkey;
-    private String locale;
-    private String value;
-
-    public PatternEntity_LongInt() {
-    }
-
-    public PatternEntity_LongInt(PatternCacheKey cacheKey, String value) {
-        final Enum<?> enumKey = cacheKey.getKey();
-        this.i18nkey = new EnumConverter().convertToString(enumKey);
-        this.locale = cacheKey.getRequestedLocale()
-                              .toLanguageTag();
-        this.value = value;
-    }
-
-    public String getI18nkey() {
-        return i18nkey;
-    }
-
-    public String getValue() {
-        return value;
-    }
-
-    public void setValue(String value) {
-        this.value = value;
-    }
-
-    public String getLocale() {
-        return locale;
-    }
-}
diff --git a/src/main/java/uk/q3c/krail/jpa/i18n/PatternId.java b/src/main/java/uk/q3c/krail/jpa/i18n/PatternId.java
new file mode 100644
index 0000000..faed1cc
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/i18n/PatternId.java
@@ -0,0 +1,69 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.i18n;
+
+import uk.q3c.krail.core.data.EnumConverter;
+import uk.q3c.krail.i18n.PatternCacheKey;
+
+import javax.annotation.Nonnull;
+import javax.persistence.Embeddable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Created by David Sowerby on 17 Jan 2016
+ */
+@Embeddable
+public class PatternId {
+    private String i18nkey;
+    private String locale;
+
+    protected PatternId() {
+
+    }
+
+    public PatternId(@Nonnull PatternCacheKey cacheKey) {
+        checkNotNull(cacheKey);
+        final Enum<?> enumKey = cacheKey.getKey();
+        this.i18nkey = new EnumConverter().convertToString(enumKey);
+        this.locale = cacheKey.getRequestedLocale()
+                              .toLanguageTag();
+    }
+
+    public String getI18nkey() {
+        return i18nkey;
+    }
+
+    public String getLocale() {
+        return locale;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PatternId that = (PatternId) o;
+
+        if (!i18nkey.equals(that.i18nkey)) return false;
+        return locale.equals(that.locale);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = i18nkey.hashCode();
+        return 31 * result + locale.hashCode();
+    }
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/persist/BaseJpaDao.java b/src/main/java/uk/q3c/krail/jpa/persist/BaseJpaDao.java
index 6e8b9a2..5253485 100644
--- a/src/main/java/uk/q3c/krail/jpa/persist/BaseJpaDao.java
+++ b/src/main/java/uk/q3c/krail/jpa/persist/BaseJpaDao.java
@@ -1,18 +1,21 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.persist;
 
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.apache.onami.persist.EntityManagerProvider;
 import org.apache.onami.persist.Transactional;
 import org.eclipse.persistence.config.PersistenceUnitProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -136,10 +139,12 @@ public abstract class BaseJpaDao<ID, VER> implements JpaDao<ID, VER> {
     }
 
     /**
      * {@inheritDoc}
      */
+    @SuppressFBWarnings("SQL_INJECTION_JPA")
+    // The only parameter is entityName(), which is limited to either the simple class name of the entity, or its annotation
     @Nonnull
     @Override
     public <E extends KrailEntity<ID, VER>> List<E> findAll(@Nonnull Class<E> entityClass) {
         checkNotNull(entityClass);
         EntityManager entityManager = entityManagerProvider.get();
@@ -151,11 +156,11 @@ public abstract class BaseJpaDao<ID, VER> implements JpaDao<ID, VER> {
     /**
      * {@inheritDoc}
      */
     @Override
     @Nonnull
-    public <E extends KrailEntity<ID, VER>> String entityName(@Nonnull Class<E> entityClass) {
+    public final <E extends KrailEntity<ID, VER>> String entityName(@Nonnull Class<E> entityClass) {
         checkNotNull(entityClass);
 
         // Get the @Entity annotation to check for name change
         Entity t = entityClass.getAnnotation(Entity.class);
 
@@ -184,12 +189,15 @@ public abstract class BaseJpaDao<ID, VER> implements JpaDao<ID, VER> {
         return entity.getId();
     }
 
 
     /**
-     * {@inheritDoc}
+     *
      */
+
+    @SuppressFBWarnings("SQL_INJECTION_JPA")
+    // The only parameter is entityName(), which is limited to either the simple class name of the entity, or its annotation
     @Transactional
     @Override
     public <E extends KrailEntity<ID, VER>> long count(@Nonnull Class<E> entityClass) {
         checkNotNull(entityClass);
         EntityManager entityManager = entityManagerProvider.get();
diff --git a/src/main/java/uk/q3c/krail/jpa/persist/BaseJpaKeyValueDao.java b/src/main/java/uk/q3c/krail/jpa/persist/BaseJpaKeyValueDao.java
new file mode 100644
index 0000000..6dc00eb
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/persist/BaseJpaKeyValueDao.java
@@ -0,0 +1,100 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.persist;
+
+import org.apache.onami.persist.EntityManagerProvider;
+import org.apache.onami.persist.Transactional;
+import uk.q3c.krail.jpa.i18n.KeyValueEntity;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A common base class for Pattern and Option DAOs, which both require a key-value type of implementation
+ * <p>
+ * Created by David Sowerby on 18 Jan 2016
+ */
+public abstract class BaseJpaKeyValueDao<ID, K, E extends KeyValueEntity<ID, Integer>> extends BaseJpaDao<ID, Integer> implements JpaKeyValueDao<ID, Integer, K,
+        E> {
+
+    private Class<E> entityClass;
+
+    public BaseJpaKeyValueDao(EntityManagerProvider entityManagerProvider, Class<E> entityClass) {
+        super(entityManagerProvider);
+        this.entityClass = entityClass;
+    }
+
+    @Transactional
+    @Override
+    public E write(@Nonnull K cacheKey, @Nonnull String value) {
+        checkNotNull(cacheKey);
+        checkNotNull(value);
+        E existingEntity = find(cacheKey);
+        if (existingEntity != null) {
+            existingEntity.setValue(value);
+            return save(existingEntity);
+        } else {
+            return save(newEntity(cacheKey, value));
+        }
+    }
+
+    protected abstract E newEntity(K cacheKey, String value);
+
+    protected abstract ID newId(K cacheKey);
+
+    @Transactional
+    @Override
+    @Nullable
+    public E find(@Nonnull K cacheKey) {
+        checkNotNull(cacheKey);
+        return getEntityManager().find(entityClass, newId(cacheKey));
+    }
+
+    @Transactional
+    @Nonnull
+    @Override
+    public Optional<String> deleteValue(@Nonnull K cacheKey) {
+        checkNotNull(cacheKey);
+        Optional<E> deletedItem = deleteById(entityClass, newId(cacheKey));
+        if (deletedItem.isPresent()) {
+            return Optional.of(deletedItem.get()
+                                          .getValue());
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    @Nonnull
+    @Override
+    public Optional<String> getValueAsString(@Nonnull K cacheKey) {
+        checkNotNull(cacheKey);
+        E entity = find(cacheKey);
+        return (entity == null) ? Optional.empty() : Optional.of(entity.getValue());
+    }
+
+    /**
+     * returns the number of entries
+     *
+     * @return the number of entries
+     */
+    @Override
+    @Transactional
+    public long count() {
+        return super.count(entityClass);
+    }
+
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/persist/DefaultJpaInstanceConfiguration.java b/src/main/java/uk/q3c/krail/jpa/persist/DefaultJpaInstanceConfiguration.java
index 485f6e6..0590842 100644
--- a/src/main/java/uk/q3c/krail/jpa/persist/DefaultJpaInstanceConfiguration.java
+++ b/src/main/java/uk/q3c/krail/jpa/persist/DefaultJpaInstanceConfiguration.java
@@ -17,12 +17,12 @@ import org.apache.onami.persist.BindingPair;
 import org.eclipse.persistence.config.PersistenceUnitProperties;
 import org.eclipse.persistence.jpa.PersistenceProvider;
 import uk.q3c.krail.core.user.opt.OptionDao;
 import uk.q3c.krail.i18n.I18NKey;
 import uk.q3c.krail.i18n.PatternDao;
-import uk.q3c.krail.jpa.i18n.DefaultJpaPatternDao_LongInt;
-import uk.q3c.krail.jpa.user.opt.DefaultOptionJpaDao_LongInt;
+import uk.q3c.krail.jpa.i18n.DefaultJpaPatternDao;
+import uk.q3c.krail.jpa.user.opt.DefaultJpaOptionDao;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Properties;
@@ -196,22 +196,22 @@ public class DefaultJpaInstanceConfiguration extends HashMap<String, Object> imp
     /**
      * {@inheritDoc}
      */
     @Override
     public DefaultJpaInstanceConfiguration provideOptionDao() {
-        bind(OptionDao.class, DefaultOptionJpaDao_LongInt.class);
+        bind(OptionDao.class, DefaultJpaOptionDao.class);
         provideOptionDao = true;
         return this;
     }
 
 
     /**
      * {@inheritDoc}
      */
     @Override
     public DefaultJpaInstanceConfiguration providePatternDao() {
-        bind(PatternDao.class, DefaultJpaPatternDao_LongInt.class);
+        bind(PatternDao.class, DefaultJpaPatternDao.class);
         providePatternDao = true;
         return this;
     }
 
     @Override
diff --git a/src/main/java/uk/q3c/krail/jpa/persist/JpaKeyValueDao.java b/src/main/java/uk/q3c/krail/jpa/persist/JpaKeyValueDao.java
new file mode 100644
index 0000000..1273955
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/persist/JpaKeyValueDao.java
@@ -0,0 +1,43 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.persist;
+
+import org.apache.onami.persist.Transactional;
+import uk.q3c.krail.jpa.i18n.KeyValueEntity;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Optional;
+
+/**
+ * Created by David Sowerby on 18 Jan 2016
+ */
+public interface JpaKeyValueDao<ID, VER, K, E extends KeyValueEntity> extends JpaDao<ID, VER> {
+    @Transactional
+    E write(@Nonnull K cacheKey, @Nonnull String value);
+
+    @Transactional
+    @Nullable
+    E find(@Nonnull K cacheKey);
+
+    @Transactional
+    @Nonnull
+    Optional<String> deleteValue(@Nonnull K cacheKey);
+
+    @Nonnull
+    Optional<String> getValueAsString(@Nonnull K cacheKey);
+
+    @Transactional
+    long count();
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionContainerProvider.java b/src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionContainerProvider.java
index 5cee546..7742770 100644
--- a/src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionContainerProvider.java
+++ b/src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionContainerProvider.java
@@ -1,14 +1,16 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.user.opt;
 
 import com.google.inject.Inject;
@@ -36,8 +38,8 @@ public class DefaultJpaOptionContainerProvider implements JpaOptionContainerProv
         return get(ContainerType.CACHED);
     }
 
     @Override
     public Container get(ContainerType containerType) {
-        return jpaContainerProvider.get(OptionEntity_LongInt.class, containerType);
+        return jpaContainerProvider.get(JpaOptionEntity.class, containerType);
     }
 }
diff --git a/src/main/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt.java b/src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao.java
similarity index 58%
rename from src/main/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt.java
rename to src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao.java
index c4f9eb7..2737dc3 100644
--- a/src/main/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt.java
+++ b/src/main/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao.java
@@ -11,140 +11,78 @@
  *
  */
 
 package uk.q3c.krail.jpa.user.opt;
 
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.apache.onami.persist.EntityManagerProvider;
 import org.apache.onami.persist.PersistenceUnitModule;
 import org.apache.onami.persist.Transactional;
 import uk.q3c.krail.core.data.OptionStringConverter;
 import uk.q3c.krail.core.data.Select;
 import uk.q3c.krail.core.user.opt.Option;
 import uk.q3c.krail.core.user.opt.OptionDao;
-import uk.q3c.krail.core.user.opt.OptionException;
 import uk.q3c.krail.core.user.opt.cache.DefaultOptionCacheLoader;
 import uk.q3c.krail.core.user.opt.cache.OptionCache;
 import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;
 import uk.q3c.krail.core.user.profile.RankOption;
-import uk.q3c.krail.jpa.persist.DefaultJpaDao_LongInt;
+import uk.q3c.krail.jpa.persist.BaseJpaKeyValueDao;
 
 import javax.annotation.Nonnull;
-import javax.persistence.EntityManager;
-import javax.persistence.Query;
-import javax.persistence.TypedQuery;
 import java.util.List;
 import java.util.Optional;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static uk.q3c.krail.core.data.Select.Compare.EQ;
 import static uk.q3c.krail.core.user.profile.RankOption.SPECIFIC_RANK;
 
 /**
- * Converts {@link OptionCacheKey} to {@link OptionEntity_LongInt} for persistence.
+ * Converts {@link OptionCacheKey} to {@link JpaOptionEntity} for persistence.
  *
  * Injected automatically with the correct {@link EntityManagerProvider} (where correct == annotated the same as this instance).  This is done by the {@link
  * PersistenceUnitModule}
  * <br>
  * <b>NOTE:</b> All values to and from {@link Option} are natively typed.  All values to and from {@link OptionCache}, {@link DefaultOptionCacheLoader} and
  * {@link OptionDao} are wrapped in Optional.
  * <br>
  * Created by David Sowerby on 13/04/15.
  */
-public class DefaultOptionJpaDao_LongInt extends DefaultJpaDao_LongInt implements OptionJpaDao_LongInt {
+public class DefaultJpaOptionDao extends BaseJpaKeyValueDao<OptionId, OptionCacheKey, JpaOptionEntity> implements JpaOptionDao {
 
 
     private OptionStringConverter optionStringConverter;
 
     @Inject
-    protected DefaultOptionJpaDao_LongInt(EntityManagerProvider entityManagerProvider, OptionStringConverter optionStringConverter) {
-        super(entityManagerProvider);
+    protected DefaultJpaOptionDao(EntityManagerProvider entityManagerProvider, OptionStringConverter optionStringConverter) {
+        super(entityManagerProvider, JpaOptionEntity.class);
         this.optionStringConverter = optionStringConverter;
     }
 
-    @Override
-    @Transactional
-    @Nonnull
-    public <V> Object write(@Nonnull OptionCacheKey cacheKey, @Nonnull Optional<V> value) {
-        checkRankOption(cacheKey, SPECIFIC_RANK);
-        Preconditions.checkArgument(value.isPresent(), "Value must be non-empty");
-        EntityManager entityManager = getEntityManager();
-        // is there an existing entity (bearing in mind that the id field is not the same as the key field
-        String stringValue = optionStringConverter.convertValueToString(value.get());
 
 
-        Optional<OptionEntity_LongInt> existingEntity = find(cacheKey);
-        if (existingEntity.isPresent()) {
-            OptionEntity_LongInt existing = existingEntity.get();
-            existing
-                          .setValue(stringValue);
-            entityManager.persist(existingEntity.get());
-            return existing;
-        } else {
-            //noinspection ConstantConditions
-            final OptionEntity_LongInt entity = new OptionEntity_LongInt(cacheKey, stringValue);
-            entityManager.persist(entity);
-            return entity;
-        }
-
-    }
-
-    @Transactional
-    @Nonnull
-    public Optional<OptionEntity_LongInt> find(@Nonnull OptionCacheKey cacheKey) {
-        checkRankOption(cacheKey, SPECIFIC_RANK);
-
-        Select select = selectSingleRank(cacheKey);
-
-        TypedQuery<OptionEntity_LongInt> query = getEntityManager().createQuery(select.statement(), OptionEntity_LongInt.class);
-        List<OptionEntity_LongInt> results = query.getResultList();
-        if (results.isEmpty()) {
-            return Optional.empty();
-        } else {
-            if (results.size() > 1) {
-                throw new OptionException("Multiple values for one cache key found, cacheKey =  " + cacheKey);
-            }
-            return Optional.of(results.get(0));
-        }
-    }
-
     protected Select selectSingleRank(@Nonnull OptionCacheKey cacheKey) {
-        return new Select().from(entityName(OptionEntity_LongInt.class))
+        return new Select().from(entityName(JpaOptionEntity.class))
                            .where("userHierarchyName", EQ, cacheKey.getHierarchy()
                                                                    .persistenceName())
                            .and("rankName", EQ, cacheKey.getRequestedRankName())
                            .and("optionKey", EQ, cacheKey.getOptionKey()
                                                          .compositeKey());
     }
 
-    @Transactional
-    @Nonnull
-    @Override
-    public Optional<?> deleteValue(@Nonnull OptionCacheKey cacheKey) {
-        checkRankOption(cacheKey, SPECIFIC_RANK);
-        final Optional<OptionEntity_LongInt> entity = find(cacheKey);
-        if (entity.isPresent()) {
-            String entityValue = entity.get()
-                                       .getValue();
-            delete(entity.get());
-            return Optional.of(entityValue);
-        } else {
-            return Optional.empty();
-        }
-    }
-
     @Nonnull
     @Override
     public Optional<?> getHighestRankedValue(@Nonnull final OptionCacheKey cacheKey) {
         checkRankOption(cacheKey, RankOption.HIGHEST_RANK);
         final ImmutableList<String> ranks = cacheKey.getHierarchy()
                                                     .ranksForCurrentUser();
 
         return findFirstRankedValue(cacheKey, ranks);
     }
 
+
     /**
      * Returns the first value found from the ordered {@code ranks}
      *
      * @param cacheKey
      *         the key to identify candidates (the rank name is replaced by a member of ranks)
@@ -163,24 +101,24 @@ public class DefaultOptionJpaDao_LongInt extends DefaultJpaDao_LongInt implement
             }
         }
         return value;
     }
 
+    @Override
+    public <V> Object write(@Nonnull OptionCacheKey cacheKey, @Nonnull Optional<V> value) {
+        checkRankOption(cacheKey, SPECIFIC_RANK);
+        checkArgument(value.isPresent(), "Value must be present");
+        String stringValue = optionStringConverter.convertValueToString(value.get());
+        return write(cacheKey, stringValue);
+    }
+
     @Nonnull
     @Override
     public Optional<?> getValue(@Nonnull OptionCacheKey cacheKey) {
         checkRankOption(cacheKey, SPECIFIC_RANK);
-        final Optional<OptionEntity_LongInt> optionalEntity = find(cacheKey);
-        if (optionalEntity.isPresent()) {
-
-            String value = optionalEntity.get()
-                                         .getValue();
-
-            return Optional.of(optionStringConverter.convertStringToValue(cacheKey, value));
-        } else {
-            return Optional.empty();
-        }
+        Optional<String> v = getValueAsString(cacheKey);
+        return (v.isPresent()) ? Optional.of(optionStringConverter.convertStringToValue(cacheKey, v.get())) : Optional.empty();
     }
 
 
     @Nonnull
     @Override
@@ -194,22 +132,34 @@ public class DefaultOptionJpaDao_LongInt extends DefaultJpaDao_LongInt implement
 
 
     /**
      * {@inheritDoc}
      */
+    @SuppressFBWarnings("SQL_INJECTION_JPA")//entityName() is final and can only return SimpleClassName of annotation
+    @SuppressWarnings("JpaQlInspection")
     @Transactional
     @Override
     public int clear() {
-        final Query query = getEntityManager().createQuery("DELETE FROM " + entityName(OptionEntity_LongInt.class));
-        return query.executeUpdate();
+        return getEntityManager().createQuery("DELETE FROM " + entityName(JpaOptionEntity.class))
+                                 .executeUpdate();
+    }
+
+    @Override
+    protected JpaOptionEntity newEntity(OptionCacheKey cacheKey, String value) {
+        return new JpaOptionEntity(cacheKey, value);
+    }
+
+    @Override
+    protected OptionId newId(OptionCacheKey cacheKey) {
+        return new OptionId(cacheKey);
     }
 
     /**
      * {@inheritDoc}
      */
     @Override
     public long count() {
-        return super.count(OptionEntity_LongInt.class);
+        return super.count(JpaOptionEntity.class);
     }
 
 
 }
diff --git a/src/main/java/uk/q3c/krail/jpa/user/opt/JpaOptionDao.java b/src/main/java/uk/q3c/krail/jpa/user/opt/JpaOptionDao.java
new file mode 100644
index 0000000..d9a0710
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/user/opt/JpaOptionDao.java
@@ -0,0 +1,31 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.user.opt;
+
+import org.apache.onami.persist.Transactional;
+import uk.q3c.krail.core.user.opt.OptionDao;
+import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Created by David Sowerby on 13/04/15.
+ */
+public interface JpaOptionDao extends OptionDao {
+
+    @Transactional
+    @Nullable
+    JpaOptionEntity find(@Nonnull OptionCacheKey cacheKey);
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/user/opt/JpaOptionEntity.java b/src/main/java/uk/q3c/krail/jpa/user/opt/JpaOptionEntity.java
new file mode 100644
index 0000000..fa4bea7
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/user/opt/JpaOptionEntity.java
@@ -0,0 +1,89 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.user.opt;
+
+import uk.q3c.krail.core.user.opt.Option;
+import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;
+import uk.q3c.krail.jpa.i18n.KeyValueEntity;
+
+import javax.annotation.Nonnull;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Version;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An entity to hold data for an {@link Option}.  The value is held as a String to enable the use of a single column for it.  Context is part of the overall
+ * {#optionKey} to ensure uniqueness, but is also held separately as it is a useful way to group the options for manual setting up, and there may also be an
+ * opportunity to pre-load options by Context.
+ * <p>
+ * Created by David Sowerby on 13/04/15.
+ */
+@Entity
+public class JpaOptionEntity implements KeyValueEntity<OptionId, Integer> {
+
+    @EmbeddedId
+    private OptionId id;
+    private String value;
+    @Version
+    private Integer version;
+
+    protected JpaOptionEntity() {
+    }
+
+    public JpaOptionEntity(@Nonnull OptionCacheKey optionCacheKey, @Nonnull String value) {
+        this.id = new OptionId(optionCacheKey);
+        this.value = value;
+
+    }
+
+    @Override
+    public OptionId getId() {
+        return id;
+    }
+
+    @Override
+    public Integer getVersion() {
+        return version;
+    }
+
+    public String getContext() {
+        return id.getContext();
+    }
+
+    public String getUserHierarchyName() {
+        return id.getUserHierarchyName();
+    }
+
+    public String getRankName() {
+        return id.getRankName();
+    }
+
+    public String getOptionKey() {
+        return id.getOptionKey();
+    }
+
+    @Nonnull
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(@Nonnull String value) {
+        checkNotNull(value);
+        this.value = value;
+    }
+
+
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/user/opt/OptionEntity_LongInt.java b/src/main/java/uk/q3c/krail/jpa/user/opt/OptionEntity_LongInt.java
deleted file mode 100644
index 1e337ad..0000000
--- a/src/main/java/uk/q3c/krail/jpa/user/opt/OptionEntity_LongInt.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (c) 2015. David Sowerby
- *
- * Licensed 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 uk.q3c.krail.jpa.user.opt;
-
-import uk.q3c.krail.core.user.opt.Option;
-import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;
-import uk.q3c.krail.jpa.persist.EntityBase_LongInt;
-
-import javax.annotation.Nonnull;
-import javax.persistence.Entity;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * An entity to hold data for an {@link Option}.  The value is held as a String to enable the use of a single column for it.  Context is part of the overall
- * {#optionKey} to ensure uniqueness, but is also held separately as it is a useful way to group the options for manual setting up, and there may also be an
- * opportunity to pre-load options by Context.
- * <p>
- * Created by David Sowerby on 13/04/15.
- */
-@Entity
-public class OptionEntity_LongInt extends EntityBase_LongInt {
-
-    private String context;
-    private String optionKey;
-    private String rankName;
-    private String userHierarchyName;
-    private String value;
-
-    public OptionEntity_LongInt() {
-    }
-
-    public OptionEntity_LongInt(@Nonnull OptionCacheKey optionCacheKey, @Nonnull String value) {
-        userHierarchyName = optionCacheKey.getHierarchy()
-                                          .persistenceName();
-        rankName = optionCacheKey.getRequestedRankName();
-        optionKey = optionCacheKey.getOptionKey()
-                                  .compositeKey();
-        context = optionCacheKey.getOptionKey()
-                                .getContext()
-                                .getName();
-        this.value = value;
-
-    }
-
-    public String getContext() {
-        return context;
-    }
-
-    public String getUserHierarchyName() {
-        return userHierarchyName;
-    }
-
-    public void setUserHierarchyName(String userHierarchyName) {
-        this.userHierarchyName = userHierarchyName;
-    }
-
-    public String getRankName() {
-        return rankName;
-    }
-
-    public void setRankName(String rankName) {
-        this.rankName = rankName;
-    }
-
-    public String getOptionKey() {
-        return optionKey;
-    }
-
-    public void setOptionKey(String optionKey) {
-        this.optionKey = optionKey;
-    }
-
-    @Nonnull
-    public String getValue() {
-        return value;
-    }
-
-    public void setValue(@Nonnull String value) {
-        checkNotNull(value);
-        this.value = value;
-    }
-
-
-}
diff --git a/src/main/java/uk/q3c/krail/jpa/user/opt/OptionId.java b/src/main/java/uk/q3c/krail/jpa/user/opt/OptionId.java
new file mode 100644
index 0000000..f0e5d43
--- /dev/null
+++ b/src/main/java/uk/q3c/krail/jpa/user/opt/OptionId.java
@@ -0,0 +1,84 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.user.opt;
+
+import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;
+
+import javax.persistence.Embeddable;
+
+/**
+ * Created by David Sowerby on 17 Jan 2016
+ */
+@Embeddable
+public class OptionId {
+
+
+    private String context;
+    private String optionKey;
+    private String rankName;
+    private String userHierarchyName;
+
+    protected OptionId() {
+
+    }
+
+    public OptionId(OptionCacheKey optionCacheKey) {
+        userHierarchyName = optionCacheKey.getHierarchy()
+                                          .persistenceName();
+        rankName = optionCacheKey.getRequestedRankName();
+        optionKey = optionCacheKey.getOptionKey()
+                                  .compositeKey();
+        context = optionCacheKey.getOptionKey()
+                                .getContext()
+                                .getName();
+    }
+
+    public String getContext() {
+        return context;
+    }
+
+    public String getOptionKey() {
+        return optionKey;
+    }
+
+    public String getRankName() {
+        return rankName;
+    }
+
+    public String getUserHierarchyName() {
+        return userHierarchyName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        OptionId optionId = (OptionId) o;
+
+        if (context != null ? !context.equals(optionId.context) : optionId.context != null) return false;
+        if (optionKey != null ? !optionKey.equals(optionId.optionKey) : optionId.optionKey != null) return false;
+        if (rankName != null ? !rankName.equals(optionId.rankName) : optionId.rankName != null) return false;
+        return userHierarchyName != null ? userHierarchyName.equals(optionId.userHierarchyName) : optionId.userHierarchyName == null;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = context != null ? context.hashCode() : 0;
+        result = 31 * result + (optionKey != null ? optionKey.hashCode() : 0);
+        result = 31 * result + (rankName != null ? rankName.hashCode() : 0);
+        return 31 * result + (userHierarchyName != null ? userHierarchyName.hashCode() : 0);
+    }
+}
diff --git a/src/main/java/uk/q3c/krail/jpa/user/opt/OptionJpaDao_LongInt.java b/src/main/java/uk/q3c/krail/jpa/user/opt/OptionJpaDao_LongInt.java
deleted file mode 100644
index 9f0b410..0000000
--- a/src/main/java/uk/q3c/krail/jpa/user/opt/OptionJpaDao_LongInt.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (c) 2015. David Sowerby
- *
- * Licensed 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 uk.q3c.krail.jpa.user.opt;
-
-import uk.q3c.krail.core.user.opt.OptionDao;
-
-/**
- * Created by David Sowerby on 13/04/15.
- */
-public interface OptionJpaDao_LongInt extends OptionDao {
-
-}
diff --git a/src/test/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao_LongInt_IntegrationTest.groovy b/src/test/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao__IntegrationTest.groovy
similarity index 61%
rename from src/test/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao_LongInt_IntegrationTest.groovy
rename to src/test/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao__IntegrationTest.groovy
index 8177888..5f8527d 100644
--- a/src/test/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao_LongInt_IntegrationTest.groovy
+++ b/src/test/java/uk/q3c/krail/jpa/i18n/DefaultJpaPatternDao__IntegrationTest.groovy
@@ -1,18 +1,19 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.i18n
-
 import com.google.inject.Inject
 import org.apache.onami.persist.PersistenceService
 import org.apache.onami.persist.UnitOfWork
 import spock.guice.UseModules
 import spock.lang.Specification
@@ -21,11 +22,11 @@ import uk.q3c.krail.i18n.LabelKey
 import uk.q3c.krail.i18n.PatternCacheKey
 import uk.q3c.krail.i18n.PatternDao
 import uk.q3c.krail.jpa.persist.Jpa1
 
 @UseModules([TestPatternJpaModule, DataModule])
-class DefaultJpaPatternDao_LongInt_IntegrationTest extends Specification {
+class DefaultJpaPatternDao__IntegrationTest extends Specification {
 
     @Inject
     @Jpa1
     PersistenceService persistenceService;
 
@@ -51,14 +52,14 @@ class DefaultJpaPatternDao_LongInt_IntegrationTest extends Specification {
 
 
     def "setting up"() {
         expect:
         persistenceService != null
-        persistenceService.isRunning() == true
+        persistenceService.isRunning()
 
         unitOfWork != null
-        unitOfWork.isActive() == true
+        unitOfWork.isActive()
 
         dao != null
 
 
     }
@@ -67,32 +68,31 @@ class DefaultJpaPatternDao_LongInt_IntegrationTest extends Specification {
     def "write a single value"() {
         given:
         PatternCacheKey cacheKey = new PatternCacheKey(LabelKey.Yes, Locale.UK);
         when:
         dao.write(cacheKey, "4");
-        Optional<PatternEntity_LongInt> actual = (Optional<PatternEntity_LongInt>) dao.find(cacheKey)
+        JpaPatternEntity actual = dao.find(cacheKey)
         then:
-        actual.isPresent()
+        actual != null
         dao.count() == 1
-        actual.get().getValue() == "4"
+        actual.getValue() == "4"
     }
 
 
     def "write with the same cache key multiple times and ensure there is only the latest value present in persistence"() {
         given:
         PatternCacheKey cacheKey = new PatternCacheKey(LabelKey.Yes, Locale.UK);
         when:
         dao.write(cacheKey, "4");
         dao.write(cacheKey, "5");
         dao.write(cacheKey, "6");
-        Optional<PatternEntity_LongInt> actual = (Optional<PatternEntity_LongInt>) dao.find(cacheKey)
+        JpaPatternEntity actual = dao.find(cacheKey)
 
         then:
-        actual.isPresent()
+        actual != null
         dao.count() == 1
-        actual.isPresent()
-        actual.get().getValue() == "6"
+        actual.getValue() == "6"
 
     }
 
     def "write multiple keys and delete one"() {
         given:
@@ -110,18 +110,18 @@ class DefaultJpaPatternDao_LongInt_IntegrationTest extends Specification {
         dao.count() == 3
 
         when:
 
         dao.deleteValue(cacheKey1)
-        Optional<PatternEntity_LongInt> result2 = dao.find(cacheKey2)
-        Optional<PatternEntity_LongInt> result3 = dao.find(cacheKey3)
+        JpaPatternEntity result2 = dao.find(cacheKey2)
+        JpaPatternEntity result3 = dao.find(cacheKey3)
 
         then:
 
         dao.count() == 2
-        result2.get().getValue() == "4"
-        result3.get().getValue() == "5"
+        result2.getValue() == "4"
+        result3.getValue() == "5"
 
 
     }
 
 
diff --git a/src/test/java/uk/q3c/krail/jpa/i18n/JpaPatternEntity_Test.groovy b/src/test/java/uk/q3c/krail/jpa/i18n/JpaPatternEntity_Test.groovy
new file mode 100644
index 0000000..058501b
--- /dev/null
+++ b/src/test/java/uk/q3c/krail/jpa/i18n/JpaPatternEntity_Test.groovy
@@ -0,0 +1,42 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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 uk.q3c.krail.jpa.i18n
+
+import spock.lang.Specification
+import uk.q3c.krail.i18n.LabelKey
+import uk.q3c.krail.i18n.PatternCacheKey
+
+/**
+ * Created by David Sowerby on 13/07/15.
+ */
+class JpaPatternEntity_Test extends Specification {
+
+
+    JpaPatternEntity entity;
+
+    def "create decodes the I18NKey"() {
+        given:
+
+        PatternCacheKey cacheKey1 = new PatternCacheKey(LabelKey.Yes, Locale.UK)
+
+        when:
+
+        entity = new JpaPatternEntity(cacheKey1, "x")
+
+
+        then:
+
+        entity.getI18nkey().equals("uk.q3c.krail.i18n.LabelKey.Yes")
+    }
+}
diff --git a/src/test/java/uk/q3c/krail/jpa/i18n/PatternEntity_LongIntTest.groovy b/src/test/java/uk/q3c/krail/jpa/i18n/PatternEntity_LongIntTest.groovy
deleted file mode 100644
index 882737e..0000000
--- a/src/test/java/uk/q3c/krail/jpa/i18n/PatternEntity_LongIntTest.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2015. David Sowerby
- *
- * Licensed 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 uk.q3c.krail.jpa.i18n
-
-import spock.lang.Specification
-import uk.q3c.krail.i18n.LabelKey
-import uk.q3c.krail.i18n.PatternCacheKey
-
-/**
- * Created by David Sowerby on 13/07/15.
- */
-class PatternEntity_LongIntTest extends Specification {
-
-
-    PatternEntity_LongInt entity;
-
-    def "create decodes the I18NKey"() {
-        given:
-
-        PatternCacheKey cacheKey1 = new PatternCacheKey(LabelKey.Yes, Locale.UK)
-
-        when:
-
-        entity = new PatternEntity_LongInt(cacheKey1, "x")
-
-
-        then:
-
-        entity.getI18nkey().equals("uk.q3c.krail.i18n.LabelKey.Yes")
-    }
-}
diff --git a/src/test/java/uk/q3c/krail/jpa/persist/TestJpaModule.java b/src/test/java/uk/q3c/krail/jpa/persist/TestJpaModule.java
index ee6cf8c..ecc88c8 100644
--- a/src/test/java/uk/q3c/krail/jpa/persist/TestJpaModule.java
+++ b/src/test/java/uk/q3c/krail/jpa/persist/TestJpaModule.java
@@ -1,24 +1,26 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.persist;
 
 import org.apache.onami.persist.PersistenceUnitModuleConfiguration;
 import org.junit.rules.TemporaryFolder;
-import uk.q3c.krail.jpa.i18n.DefaultJpaPatternDao_LongInt;
+import uk.q3c.krail.jpa.i18n.DefaultJpaPatternDao;
 import uk.q3c.krail.jpa.i18n.JpaPatternDao;
-import uk.q3c.krail.jpa.user.opt.DefaultOptionJpaDao_LongInt;
-import uk.q3c.krail.jpa.user.opt.OptionJpaDao_LongInt;
+import uk.q3c.krail.jpa.user.opt.DefaultJpaOptionDao;
+import uk.q3c.krail.jpa.user.opt.JpaOptionDao;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.persistence.EntityManagerFactory;
 import java.io.File;
@@ -80,13 +82,13 @@ public class TestJpaModule extends JpaModule {
               .url(dbFolder.getAbsolutePath())
               .user("test")
               .password("test")
               .ddlGeneration(DefaultJpaInstanceConfiguration.Ddl.DROP_AND_CREATE)
               .
-                      bind(OptionJpaDao_LongInt.class, DefaultOptionJpaDao_LongInt.class)
+                      bind(JpaOptionDao.class, DefaultJpaOptionDao.class)
               .bind(JpaDao_LongInt.class, DefaultJpaDao_LongInt.class)
-              .bind(JpaPatternDao.class, DefaultJpaPatternDao_LongInt.class);
+              .bind(JpaPatternDao.class, DefaultJpaPatternDao.class);
 
         return config;
     }
 
     private DefaultJpaInstanceConfiguration hsqlConfig() {
diff --git a/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongIntTest.groovy b/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDaoTest.groovy
similarity index 69%
rename from src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongIntTest.groovy
rename to src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDaoTest.groovy
index bf66cdd..0af3c01 100644
--- a/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongIntTest.groovy
+++ b/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDaoTest.groovy
@@ -1,14 +1,16 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.user.opt
 
 import com.vaadin.data.util.converter.ConverterFactory
@@ -27,15 +29,15 @@ import uk.q3c.krail.core.user.profile.UserHierarchy
 import javax.persistence.EntityManager
 import javax.persistence.TypedQuery
 
 /**
  *
- * Test for {@link uk.q3c.krail.jpa.user.opt.DefaultOptionJpaDao_LongInt}
+ * Test for {@link DefaultJpaOptionDao}
  *
  * Created by David Sowerby on 10/07/15.
  */
-class DefaultOptionJpaDao_LongIntTest extends Specification {
+class DefaultJpaOptionDaoTest extends Specification {
 
 
     ConverterFactory converterFactory = new DefaultConverterFactory()
 
     OptionStringConverter stringPersistenceConverter = new DefaultOptionStringConverter()
@@ -44,27 +46,27 @@ class DefaultOptionJpaDao_LongIntTest extends Specification {
 
     EntityManager entityManager = Mock()
 
     UserHierarchy userHierarchy = Mock()
 
-    TypedQuery<OptionEntity_LongInt> query = Mock()
+    TypedQuery<JpaOptionEntity> query = Mock()
 
-    List<OptionEntity_LongInt> emptyResultList;
+    List<JpaOptionEntity> emptyResultList;
 
     OptionKey optionKey1 = Mock()
 
-    DefaultOptionJpaDao_LongInt dao
+    DefaultJpaOptionDao dao
 
 
     def setup() {
         optionKey1.compositeKey() >> "a_composite_key"
         optionKey1.getContext() >> OptionContext.class
         entityManagerProvider.get() >> entityManager
         emptyResultList = new ArrayList<>()
         userHierarchy.persistenceName() >> "simple"
         userHierarchy.rankName(_) >> "ds"
-        dao = new DefaultOptionJpaDao_LongInt(entityManagerProvider, stringPersistenceConverter)
+        dao = new DefaultJpaOptionDao(entityManagerProvider, stringPersistenceConverter)
     }
 
     def "Write should throw an OptionKeyException if OptionKey rank is not specific"() {
 
         given:
@@ -93,35 +95,35 @@ class DefaultOptionJpaDao_LongIntTest extends Specification {
         Object entity = dao.write(cacheKey, Optional.of(3))
 
         then:
         1 * entityManager.persist(_)
         entity != null
-        entity instanceof OptionEntity_LongInt
-        OptionEntity_LongInt rEntity = (OptionEntity_LongInt) entity
+        entity instanceof JpaOptionEntity
+        JpaOptionEntity rEntity = (JpaOptionEntity) entity
         rEntity.getValue() == "3"
 
     }
 
     def "Write the second time should change the value of the existing OptionEntity and persist it"() {
         given:
-        List<OptionEntity_LongInt> resultList = new ArrayList<>();
+        List<JpaOptionEntity> resultList = new ArrayList<>();
         OptionCacheKey cacheKey = new OptionCacheKey(userHierarchy, RankOption.SPECIFIC_RANK, optionKey1)
         emptyResultList = new ArrayList<>()
         query.getResultList() >> emptyResultList
         query.getResultList() >> resultList
         entityManager.createQuery(_, _) >> query
 
 
         when:
         Object entity = dao.write(cacheKey, Optional.of(3))
-        resultList.add((OptionEntity_LongInt) entity)
+        resultList.add((JpaOptionEntity) entity)
         entity = dao.write(cacheKey, Optional.of(5))
 
         then:
         2 * entityManager.persist(_)
         entity != null
-        entity instanceof OptionEntity_LongInt
-        OptionEntity_LongInt rEntity = (OptionEntity_LongInt) entity
+        entity instanceof JpaOptionEntity
+        JpaOptionEntity rEntity = (JpaOptionEntity) entity
         rEntity.getValue() == "5"
 
     }
 }
diff --git a/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt_DataTypeTest.groovy b/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao__DataTypeTest.groovy
similarity index 90%
rename from src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt_DataTypeTest.groovy
rename to src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao__DataTypeTest.groovy
index 249002c..195cfe4 100644
--- a/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt_DataTypeTest.groovy
+++ b/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao__DataTypeTest.groovy
@@ -1,14 +1,16 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.user.opt
 
 import com.google.common.collect.Lists
@@ -36,11 +38,11 @@ import static uk.q3c.krail.core.navigate.sitemap.comparator.DefaultUserSitemapSo
 
 /**
  * Round-trip tests for different data types used by Option
  */
 @UseModules([TestOptionJpaModule, DataModule])
-class DefaultOptionJpaDao_LongInt_DataTypeTest extends Specification {
+class DefaultJpaOptionDao__DataTypeTest extends Specification {
 
 
     @Inject
     @Jpa1
     PersistenceService persistenceService;
diff --git a/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt_IntegrationTest.groovy b/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao__IntegrationTest.groovy
similarity index 68%
rename from src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt_IntegrationTest.groovy
rename to src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao__IntegrationTest.groovy
index a6cce03..09b6a78 100644
--- a/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDao_LongInt_IntegrationTest.groovy
+++ b/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultJpaOptionDao__IntegrationTest.groovy
@@ -1,14 +1,16 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.user.opt
 import com.google.inject.Inject
 import org.apache.onami.persist.PersistenceService
@@ -24,11 +26,11 @@ import uk.q3c.krail.core.user.profile.UserHierarchy
 import uk.q3c.krail.core.view.component.LocaleContainer
 import uk.q3c.krail.i18n.LabelKey
 import uk.q3c.krail.jpa.persist.Jpa1
 
 @UseModules([TestOptionJpaModule, DataModule])
-class DefaultOptionJpaDao_LongInt_IntegrationTest extends Specification {
+class DefaultJpaOptionDao__IntegrationTest extends Specification {
 
     @Inject
     @Jpa1
     PersistenceService persistenceService;
 
@@ -78,11 +80,11 @@ class DefaultOptionJpaDao_LongInt_IntegrationTest extends Specification {
 
     def "write a single value"() {
         given:
         OptionCacheKey cacheKey = new OptionCacheKey(userHierarchy, RankOption.SPECIFIC_RANK, optionKey);
         when:
-        OptionEntity_LongInt actual = (OptionEntity_LongInt) dao.write(cacheKey, Optional.of("4"));
+        JpaOptionEntity actual = (JpaOptionEntity) dao.write(cacheKey, Optional.of("4"));
         then:
         actual != null
         dao.count() == 1
     }
 
@@ -90,20 +92,20 @@ class DefaultOptionJpaDao_LongInt_IntegrationTest extends Specification {
     def "write with the same cache key multiple times and ensure there is only the latest value present in persistence"() {
         given:
         OptionCacheKey cacheKey = new OptionCacheKey(userHierarchy, RankOption.SPECIFIC_RANK, optionKey);
 
         when:
-        OptionEntity_LongInt actual = (OptionEntity_LongInt) dao.write(cacheKey, Optional.of("4"));
-        actual = (OptionEntity_LongInt) dao.write(cacheKey, Optional.of("5"));
-        actual = (OptionEntity_LongInt) dao.write(cacheKey, Optional.of("6"));
-        Optional<OptionEntity_LongInt> result = dao.find(cacheKey)
+        JpaOptionEntity actual = (JpaOptionEntity) dao.write(cacheKey, Optional.of("4"));
+        actual = (JpaOptionEntity) dao.write(cacheKey, Optional.of("5"));
+        actual = (JpaOptionEntity) dao.write(cacheKey, Optional.of("6"));
+        JpaOptionEntity result = dao.find(cacheKey)
 
         then:
         actual != null
         dao.count() == 1
-        result.isPresent()
-        result.get().getValue() == "6"
+        result != null
+        result.getValue() == "6"
 
     }
 
     def "write multiple keys and delete one"() {
         given:
@@ -121,18 +123,18 @@ class DefaultOptionJpaDao_LongInt_IntegrationTest extends Specification {
         dao.count() == 3
 
         when:
 
         dao.deleteValue(cacheKey)
-        Optional<OptionEntity_LongInt> result1 = dao.find(cacheKey1)
-        Optional<OptionEntity_LongInt> result2 = dao.find(cacheKey2)
+        JpaOptionEntity result1 = dao.find(cacheKey1)
+        JpaOptionEntity result2 = dao.find(cacheKey2)
 
         then:
 
         dao.count() == 2
-        result1.get().getValue() == "4"
-        result2.get().getValue() == "5"
+        result1.getValue() == "4"
+        result2.getValue() == "5"
 
     }
 
 
 }
\ No newline at end of file
diff --git a/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDaoLongIntTest.java b/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDaoTest2.java
similarity index 91%
rename from src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDaoLongIntTest.java
rename to src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDaoTest2.java
index 7ca5192..e24aa1b 100644
--- a/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDaoLongIntTest.java
+++ b/src/test/java/uk/q3c/krail/jpa/user/opt/DefaultOptionJpaDaoTest2.java
@@ -1,14 +1,16 @@
 /*
- * Copyright (c) 2015. David Sowerby
  *
- * Licensed 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
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed 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.
  *
- * 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 uk.q3c.krail.jpa.user.opt;
 
 import com.google.common.collect.ImmutableList;
@@ -33,14 +35,14 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.when;
 import static uk.q3c.krail.core.user.profile.RankOption.*;
 
 @RunWith(MycilaJunitRunner.class)
 @GuiceContext({})
-public class DefaultOptionJpaDaoLongIntTest extends JpaDaoTestBase {
+public class DefaultOptionJpaDaoTest2 extends JpaDaoTestBase {
 
 
-    OptionJpaDao_LongInt dao;
+    JpaOptionDao dao;
 
     @Mock
     UserHierarchy hierarchy1;
 
     ImmutableList<String> rankNames1 = ImmutableList.of("fred", "accounts", "finance");
@@ -50,11 +52,11 @@ public class DefaultOptionJpaDaoLongIntTest extends JpaDaoTestBase {
     private OptionKey<Integer> optionKey1;
 
     @Before
     public void setup() {
         super.setUp();
-        final Key<OptionJpaDao_LongInt> daoKey = Key.get(OptionJpaDao_LongInt.class, Jpa1.class);
+        final Key<JpaOptionDao> daoKey = Key.get(JpaOptionDao.class, Jpa1.class);
         dao = injector.getInstance(daoKey);
         when(hierarchy1.persistenceName()).thenReturn("p1");
         when(hierarchy1.ranksForCurrentUser()).thenReturn(rankNames1);
         when(hierarchy1.rankName(0)).thenReturn(rankNames1.get(0));
         when(hierarchy1.rankName(1)).thenReturn(rankNames1.get(1));
diff --git a/src/test/resources/META-INF/persistence.xml b/src/test/resources/META-INF/persistence.xml
index ef2828a..a984782 100644
--- a/src/test/resources/META-INF/persistence.xml
+++ b/src/test/resources/META-INF/persistence.xml
@@ -1,15 +1,17 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!--
-  ~ Copyright (c) 2015. David Sowerby
-  ~
-  ~ Licensed 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.
+  ~ /*
+  ~  * Copyright (c) 2016. David Sowerby
+  ~  *
+  ~  * Licensed 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 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
              version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
@@ -20,12 +22,12 @@
 
         <class>org.apache.onami.persist.test.TestEntity</class>
         <class>uk.q3c.krail.jpa.persist.Widget</class>
         <class>uk.q3c.krail.jpa.persist.Widget2</class>
         <class>uk.q3c.krail.jpa.persist.StandardTestEntity</class>
-        <class>uk.q3c.krail.jpa.user.opt.OptionEntity_LongInt</class>
-        <class>uk.q3c.krail.jpa.i18n.PatternEntity_LongInt</class>
+        <class>uk.q3c.krail.jpa.user.opt.JpaOptionEntity</class>
+        <class>uk.q3c.krail.jpa.i18n.JpaPatternEntity</class>
 
         <exclude-unlisted-classes>false</exclude-unlisted-classes>
 
         <properties>
             <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:mem:persistence"/>
@@ -42,12 +44,12 @@
 
         <class>org.apache.onami.persist.test.TestEntity</class>
         <class>uk.q3c.krail.jpa.persist.Widget</class>
         <class>uk.q3c.krail.jpa.persist.Widget2</class>
         <class>uk.q3c.krail.jpa.persist.StandardTestEntity</class>
-        <class>uk.q3c.krail.jpa.user.opt.OptionEntity_LongInt</class>
-        <class>uk.q3c.krail.jpa.i18n.PatternEntity_LongInt</class>
+        <class>uk.q3c.krail.jpa.user.opt.JpaOptionEntity</class>
+        <class>uk.q3c.krail.jpa.i18n.JpaPatternEntity</class>
 
         <exclude-unlisted-classes>true</exclude-unlisted-classes>
 
         <properties>
             <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:mem:first"/>
@@ -64,12 +66,12 @@
 
         <class>org.apache.onami.persist.test.TestEntity</class>
         <class>uk.q3c.krail.jpa.persist.Widget</class>
         <class>uk.q3c.krail.jpa.persist.Widget2</class>
         <class>uk.q3c.krail.jpa.persist.StandardTestEntity</class>
-        <class>uk.q3c.krail.jpa.user.opt.OptionEntity_LongInt</class>
-        <class>uk.q3c.krail.jpa.i18n.PatternEntity_LongInt</class>
+        <class>uk.q3c.krail.jpa.user.opt.JpaOptionEntity</class>
+        <class>uk.q3c.krail.jpa.i18n.JpaPatternEntity</class>
 
         <exclude-unlisted-classes>true</exclude-unlisted-classes>
 
         <properties>
             <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:mem:second"/>
@@ -85,12 +87,12 @@
     <persistence-unit name="derbyDb">
         <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
         <class>uk.q3c.krail.jpa.persist.Todo</class>
         <class>uk.q3c.krail.jpa.persist.Widget</class>
         <class>uk.q3c.krail.jpa.persist.Widget2</class>
-        <class>uk.q3c.krail.jpa.user.opt.OptionEntity_LongInt</class>
-        <class>uk.q3c.krail.jpa.i18n.PatternEntity_LongInt</class>
+        <class>uk.q3c.krail.jpa.user.opt.JpaOptionEntity</class>
+        <class>uk.q3c.krail.jpa.i18n.JpaPatternEntity</class>
         <exclude-unlisted-classes>false</exclude-unlisted-classes>
         <properties>
         </properties>
 
     </persistence-unit>

Vulnerable code: /*
 * Copyright (c) 2015. David Sowerby
 *
 * Licensed 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 uk.q3c.krail.jpa.i18n;

import com.google.inject.Inject;
import org.apache.onami.persist.EntityManagerProvider;
import org.apache.onami.persist.PersistenceUnitModule;
import org.apache.onami.persist.Transactional;
import uk.q3c.krail.core.data.Select;
import uk.q3c.krail.i18n.PatternCacheKey;
import uk.q3c.krail.jpa.persist.DefaultJpaDao_LongInt;

import javax.annotation.Nonnull;
import javax.persistence.TypedQuery;
import java.util.List;
import java.util.Optional;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * The default implementation of {@link JpaPatternDao}.  The {@code entityManagerProvider} and {@code dao} are bound by {@link PersistenceUnitModule} to the
 * annotation which is used in the injection of this class. (For example, if an instance of this class is annotated with @Jpa1, then the constructor parameters
 * will also be bound with @Jpa1)
 * <p>
 * Created by David Sowerby on 15/04/15.
 */
public class DefaultJpaPatternDao_LongInt extends DefaultJpaDao_LongInt implements JpaPatternDao {

    @Inject
    protected DefaultJpaPatternDao_LongInt(EntityManagerProvider entityManagerProvider) {
        super(entityManagerProvider);
    }

    @Transactional
    @Override
    public void write(@Nonnull PatternCacheKey cacheKey, @Nonnull String value) {
        checkNotNull(cacheKey);
        checkNotNull(value);
        Optional<PatternEntity_LongInt> existingValue = find(cacheKey);
        if (existingValue.isPresent()) {
            existingValue.get()
                         .setValue(value);
            save(existingValue.get());
        } else {
            save(keyToEntity(cacheKey, value));
        }
    }

    @Nonnull
    protected PatternEntity_LongInt keyToEntity(@Nonnull PatternCacheKey key, @Nonnull String value) {
        return new PatternEntity_LongInt(key, value);
    }

    @Transactional
    @Override
    @Nonnull
    public Optional<PatternEntity_LongInt> find(@Nonnull PatternCacheKey cacheKey) {
        checkNotNull(cacheKey);
        Select select = selectEntity(cacheKey);

        TypedQuery<PatternEntity_LongInt> query = getEntityManager().createQuery(select.toString(), PatternEntity_LongInt.class);
        List<PatternEntity_LongInt> results = query.getResultList();
        if (results.isEmpty()) {
            return Optional.empty();
        } else {
            return Optional.of(results.get(0));
        }
    }

    protected Select selectEntity(PatternCacheKey cacheKey) {
        //ensure the same conversion as writing
        PatternEntity_LongInt searchKey = new PatternEntity_LongInt(cacheKey, "x");
        return new Select().from(entityName(PatternEntity_LongInt.class))
                           .where("i18nkey", searchKey.getI18nkey())
                           .and("locale", searchKey.getLocale());
    }

    @Transactional
    @Nonnull
    @Override
    public Optional<String> deleteValue(@Nonnull PatternCacheKey cacheKey) {
        checkNotNull(cacheKey);


        TypedQuery<PatternEntity_LongInt> query = getEntityManager().createQuery(selectFromKey(cacheKey), PatternEntity_LongInt.class);
        List<PatternEntity_LongInt> results = query.getResultList();
        if (results.isEmpty()) {
            return Optional.empty();
        } else {
            final Optional<PatternEntity_LongInt> patternEntity = deleteById(PatternEntity_LongInt.class, results.get(0)
                                                                                                 .getId());
            if (patternEntity.isPresent()) {
                return Optional.of(patternEntity.get()
                                                .getValue());
            } else {
                return Optional.empty();
            }
        }
    }

    /**
     * Returns a Select String from the {@code cacheKey}.  Uses {@link PatternEntity_LongInt} to ensure conversion from key to entity is consistent for the esearch
     * compared to the original write
     *
     * @param cacheKey
     *         the key being selected
     *
     * @return a Select String from the {@code cacheKey}
     */
    protected String selectFromKey(@Nonnull PatternCacheKey cacheKey) {
        PatternEntity_LongInt searchPattern = keyToEntity(cacheKey, "x");
        return new Select().from(PatternEntity_LongInt.class)
                           .where("i18nkey", searchPattern.getI18nkey())
                           .and("locale", searchPattern.getLocale())
                           .toString();
    }

    @Nonnull
    @Override
    public Optional<String> getValue(@Nonnull PatternCacheKey cacheKey) {
        checkNotNull(cacheKey);
        Optional<PatternEntity_LongInt> entity = find(cacheKey);
        if (entity.isPresent()) {
            return Optional.of(entity.get()
                                     .getValue());
        } else {
            return Optional.empty();
        }
    }

    /**
     * returns the number of entries
     *
     * @return the number of entries
     */
    @Override
    @Transactional
    public long count() {
        return super.count(PatternEntity_LongInt.class);
    }


}
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.
import java.util.Optional;

    Optional<PatternEntity_LongInt> find(PatternCacheKey cacheKey);
/*
 * Copyright (c) 2015. David Sowerby
 *
 * Licensed 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 uk.q3c.krail.jpa.i18n;

import uk.q3c.krail.core.data.EnumConverter;
import uk.q3c.krail.i18n.PatternCacheKey;
import uk.q3c.krail.jpa.persist.EntityBase_LongInt;

import javax.persistence.Entity;

/**
 * An entity to represent an I18N key, Locale and value combination
 * <p>
 * Created by David Sowerby on 15/04/15.
 */

@Entity
public class PatternEntity_LongInt extends EntityBase_LongInt {

    private String i18nkey;
    private String locale;
    private String value;

    public PatternEntity_LongInt() {
    }

    public PatternEntity_LongInt(PatternCacheKey cacheKey, String value) {
        final Enum<?> enumKey = cacheKey.getKey();
        this.i18nkey = new EnumConverter().convertToString(enumKey);
        this.locale = cacheKey.getRequestedLocale()
                              .toLanguageTag();
        this.value = value;
    }

    public String getI18nkey() {
        return i18nkey;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getLocale() {
        return locale;
    }
}
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.
    public <E extends KrailEntity<ID, VER>> String entityName(@Nonnull Class<E> entityClass) {
     * {@inheritDoc}
import uk.q3c.krail.jpa.i18n.DefaultJpaPatternDao_LongInt;
import uk.q3c.krail.jpa.user.opt.DefaultOptionJpaDao_LongInt;
        bind(OptionDao.class, DefaultOptionJpaDao_LongInt.class);
        bind(PatternDao.class, DefaultJpaPatternDao_LongInt.class);
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.
        return jpaContainerProvider.get(OptionEntity_LongInt.class, containerType);
import com.google.common.base.Preconditions;
import uk.q3c.krail.core.user.opt.OptionException;
import uk.q3c.krail.jpa.persist.DefaultJpaDao_LongInt;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
 * Converts {@link OptionCacheKey} to {@link OptionEntity_LongInt} for persistence.
public class DefaultOptionJpaDao_LongInt extends DefaultJpaDao_LongInt implements OptionJpaDao_LongInt {
    protected DefaultOptionJpaDao_LongInt(EntityManagerProvider entityManagerProvider, OptionStringConverter optionStringConverter) {
        super(entityManagerProvider);
    @Override
    @Transactional
    @Nonnull
    public <V> Object write(@Nonnull OptionCacheKey cacheKey, @Nonnull Optional<V> value) {
        checkRankOption(cacheKey, SPECIFIC_RANK);
        Preconditions.checkArgument(value.isPresent(), "Value must be non-empty");
        EntityManager entityManager = getEntityManager();
        // is there an existing entity (bearing in mind that the id field is not the same as the key field
        String stringValue = optionStringConverter.convertValueToString(value.get());
        Optional<OptionEntity_LongInt> existingEntity = find(cacheKey);
        if (existingEntity.isPresent()) {
            OptionEntity_LongInt existing = existingEntity.get();
            existing
                          .setValue(stringValue);
            entityManager.persist(existingEntity.get());
            return existing;
        } else {
            //noinspection ConstantConditions
            final OptionEntity_LongInt entity = new OptionEntity_LongInt(cacheKey, stringValue);
            entityManager.persist(entity);
            return entity;
        }

    }

    @Transactional
    @Nonnull
    public Optional<OptionEntity_LongInt> find(@Nonnull OptionCacheKey cacheKey) {
        checkRankOption(cacheKey, SPECIFIC_RANK);

        Select select = selectSingleRank(cacheKey);

        TypedQuery<OptionEntity_LongInt> query = getEntityManager().createQuery(select.statement(), OptionEntity_LongInt.class);
        List<OptionEntity_LongInt> results = query.getResultList();
        if (results.isEmpty()) {
            return Optional.empty();
        } else {
            if (results.size() > 1) {
                throw new OptionException("Multiple values for one cache key found, cacheKey =  " + cacheKey);
            }
            return Optional.of(results.get(0));
        }
    }

        return new Select().from(entityName(OptionEntity_LongInt.class))
    @Transactional
    @Nonnull
    @Override
    public Optional<?> deleteValue(@Nonnull OptionCacheKey cacheKey) {
        checkRankOption(cacheKey, SPECIFIC_RANK);
        final Optional<OptionEntity_LongInt> entity = find(cacheKey);
        if (entity.isPresent()) {
            String entityValue = entity.get()
                                       .getValue();
            delete(entity.get());
            return Optional.of(entityValue);
        } else {
            return Optional.empty();
        }
    }

        final Optional<OptionEntity_LongInt> optionalEntity = find(cacheKey);
        if (optionalEntity.isPresent()) {

            String value = optionalEntity.get()
                                         .getValue();

            return Optional.of(optionStringConverter.convertStringToValue(cacheKey, value));
        } else {
            return Optional.empty();
        }
        final Query query = getEntityManager().createQuery("DELETE FROM " + entityName(OptionEntity_LongInt.class));
        return query.executeUpdate();
        return super.count(OptionEntity_LongInt.class);
/*
 * Copyright (c) 2015. David Sowerby
 *
 * Licensed 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 uk.q3c.krail.jpa.user.opt;

import uk.q3c.krail.core.user.opt.Option;
import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;
import uk.q3c.krail.jpa.persist.EntityBase_LongInt;

import javax.annotation.Nonnull;
import javax.persistence.Entity;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * An entity to hold data for an {@link Option}.  The value is held as a String to enable the use of a single column for it.  Context is part of the overall
 * {#optionKey} to ensure uniqueness, but is also held separately as it is a useful way to group the options for manual setting up, and there may also be an
 * opportunity to pre-load options by Context.
 * <p>
 * Created by David Sowerby on 13/04/15.
 */
@Entity
public class OptionEntity_LongInt extends EntityBase_LongInt {

    private String context;
    private String optionKey;
    private String rankName;
    private String userHierarchyName;
    private String value;

    public OptionEntity_LongInt() {
    }

    public OptionEntity_LongInt(@Nonnull OptionCacheKey optionCacheKey, @Nonnull String value) {
        userHierarchyName = optionCacheKey.getHierarchy()
                                          .persistenceName();
        rankName = optionCacheKey.getRequestedRankName();
        optionKey = optionCacheKey.getOptionKey()
                                  .compositeKey();
        context = optionCacheKey.getOptionKey()
                                .getContext()
                                .getName();
        this.value = value;

    }

    public String getContext() {
        return context;
    }

    public String getUserHierarchyName() {
        return userHierarchyName;
    }

    public void setUserHierarchyName(String userHierarchyName) {
        this.userHierarchyName = userHierarchyName;
    }

    public String getRankName() {
        return rankName;
    }

    public void setRankName(String rankName) {
        this.rankName = rankName;
    }

    public String getOptionKey() {
        return optionKey;
    }

    public void setOptionKey(String optionKey) {
        this.optionKey = optionKey;
    }

    @Nonnull
    public String getValue() {
        return value;
    }

    public void setValue(@Nonnull String value) {
        checkNotNull(value);
        this.value = value;
    }


}
/*
 * Copyright (c) 2015. David Sowerby
 *
 * Licensed 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 uk.q3c.krail.jpa.user.opt;

import uk.q3c.krail.core.user.opt.OptionDao;

/**
 * Created by David Sowerby on 13/04/15.
 */
public interface OptionJpaDao_LongInt extends OptionDao {

}
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.

class DefaultJpaPatternDao_LongInt_IntegrationTest extends Specification {
        persistenceService.isRunning() == true
        unitOfWork.isActive() == true
        Optional<PatternEntity_LongInt> actual = (Optional<PatternEntity_LongInt>) dao.find(cacheKey)
        actual.isPresent()
        actual.get().getValue() == "4"
        Optional<PatternEntity_LongInt> actual = (Optional<PatternEntity_LongInt>) dao.find(cacheKey)
        actual.isPresent()
        actual.isPresent()
        actual.get().getValue() == "6"
        Optional<PatternEntity_LongInt> result2 = dao.find(cacheKey2)
        Optional<PatternEntity_LongInt> result3 = dao.find(cacheKey3)
        result2.get().getValue() == "4"
        result3.get().getValue() == "5"
/*
 * Copyright (c) 2015. David Sowerby
 *
 * Licensed 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 uk.q3c.krail.jpa.i18n

import spock.lang.Specification
import uk.q3c.krail.i18n.LabelKey
import uk.q3c.krail.i18n.PatternCacheKey

/**
 * Created by David Sowerby on 13/07/15.
 */
class PatternEntity_LongIntTest extends Specification {


    PatternEntity_LongInt entity;

    def "create decodes the I18NKey"() {
        given:

        PatternCacheKey cacheKey1 = new PatternCacheKey(LabelKey.Yes, Locale.UK)

        when:

        entity = new PatternEntity_LongInt(cacheKey1, "x")


        then:

        entity.getI18nkey().equals("uk.q3c.krail.i18n.LabelKey.Yes")
    }
}
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.
import uk.q3c.krail.jpa.i18n.DefaultJpaPatternDao_LongInt;
import uk.q3c.krail.jpa.user.opt.DefaultOptionJpaDao_LongInt;
import uk.q3c.krail.jpa.user.opt.OptionJpaDao_LongInt;
                      bind(OptionJpaDao_LongInt.class, DefaultOptionJpaDao_LongInt.class)
              .bind(JpaPatternDao.class, DefaultJpaPatternDao_LongInt.class);
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.
 * Test for {@link uk.q3c.krail.jpa.user.opt.DefaultOptionJpaDao_LongInt}
class DefaultOptionJpaDao_LongIntTest extends Specification {
    TypedQuery<OptionEntity_LongInt> query = Mock()
    List<OptionEntity_LongInt> emptyResultList;
    DefaultOptionJpaDao_LongInt dao
        dao = new DefaultOptionJpaDao_LongInt(entityManagerProvider, stringPersistenceConverter)
        entity instanceof OptionEntity_LongInt
        OptionEntity_LongInt rEntity = (OptionEntity_LongInt) entity
        List<OptionEntity_LongInt> resultList = new ArrayList<>();
        resultList.add((OptionEntity_LongInt) entity)
        entity instanceof OptionEntity_LongInt
        OptionEntity_LongInt rEntity = (OptionEntity_LongInt) entity
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.
class DefaultOptionJpaDao_LongInt_DataTypeTest extends Specification {
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.
class DefaultOptionJpaDao_LongInt_IntegrationTest extends Specification {
        OptionEntity_LongInt actual = (OptionEntity_LongInt) dao.write(cacheKey, Optional.of("4"));
        OptionEntity_LongInt actual = (OptionEntity_LongInt) dao.write(cacheKey, Optional.of("4"));
        actual = (OptionEntity_LongInt) dao.write(cacheKey, Optional.of("5"));
        actual = (OptionEntity_LongInt) dao.write(cacheKey, Optional.of("6"));
        Optional<OptionEntity_LongInt> result = dao.find(cacheKey)
        result.isPresent()
        result.get().getValue() == "6"
        Optional<OptionEntity_LongInt> result1 = dao.find(cacheKey1)
        Optional<OptionEntity_LongInt> result2 = dao.find(cacheKey2)
        result1.get().getValue() == "4"
        result2.get().getValue() == "5"
 * Copyright (c) 2015. David Sowerby
 * Licensed 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.
public class DefaultOptionJpaDaoLongIntTest extends JpaDaoTestBase {
    OptionJpaDao_LongInt dao;
        final Key<OptionJpaDao_LongInt> daoKey = Key.get(OptionJpaDao_LongInt.class, Jpa1.class);
  ~ Copyright (c) 2015. David Sowerby
  ~
  ~ Licensed 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.
        <class>uk.q3c.krail.jpa.user.opt.OptionEntity_LongInt</class>
        <class>uk.q3c.krail.jpa.i18n.PatternEntity_LongInt</class>
        <class>uk.q3c.krail.jpa.user.opt.OptionEntity_LongInt</class>
        <class>uk.q3c.krail.jpa.i18n.PatternEntity_LongInt</class>
        <class>uk.q3c.krail.jpa.user.opt.OptionEntity_LongInt</class>
        <class>uk.q3c.krail.jpa.i18n.PatternEntity_LongInt</class>
        <class>uk.q3c.krail.jpa.user.opt.OptionEntity_LongInt</class>
        <class>uk.q3c.krail.jpa.i18n.PatternEntity_LongInt</class>
Fixed code: /*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.i18n;

import com.google.inject.Inject;
import org.apache.onami.persist.EntityManagerProvider;
import org.apache.onami.persist.PersistenceUnitModule;
import uk.q3c.krail.i18n.PatternCacheKey;
import uk.q3c.krail.jpa.persist.BaseJpaKeyValueDao;

import javax.annotation.Nonnull;
import java.util.Optional;

/**
 * The default implementation of {@link JpaPatternDao}.  The {@code entityManagerProvider} and {@code dao} are bound by {@link PersistenceUnitModule} to the
 * annotation which is used in the injection of this class. (For example, if an instance of this class is annotated with @Jpa1, then the constructor parameters
 * will also be bound with @Jpa1)
 * <p>
 * Created by David Sowerby on 15/04/15.
 */
public class DefaultJpaPatternDao extends BaseJpaKeyValueDao<PatternId, PatternCacheKey, JpaPatternEntity> implements JpaPatternDao {


    @Inject
    protected DefaultJpaPatternDao(EntityManagerProvider entityManagerProvider) {
        super(entityManagerProvider, JpaPatternEntity.class);
    }


    @Override
    protected JpaPatternEntity newEntity(PatternCacheKey cacheKey, String value) {
        return new JpaPatternEntity(cacheKey, value);
    }

    @Override
    protected PatternId newId(PatternCacheKey cacheKey) {
        return new PatternId(cacheKey);
    }


    @Nonnull
    @Override
    public Optional<String> getValue(@Nonnull PatternCacheKey cacheKey) {
        return getValueAsString(cacheKey);
    }
}
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
    JpaPatternEntity find(PatternCacheKey cacheKey);
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.i18n;

import uk.q3c.krail.i18n.PatternCacheKey;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Version;

/**
 * An entity to represent an I18N key, Locale and value combination
 * <p>
 * Created by David Sowerby on 15/04/15.
 */

@Entity
public class JpaPatternEntity implements KeyValueEntity<PatternId, Integer> {

    @EmbeddedId
    private PatternId id;
    private String value;
    @Version
    private Integer version;

    protected JpaPatternEntity() {
    }

    public JpaPatternEntity(PatternCacheKey cacheKey, String value) {
        id = new PatternId(cacheKey);
        this.value = value;
    }

    public String getI18nkey() {
        return id.getI18nkey();
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getLocale() {
        return id.getLocale();
    }

    @Override
    public PatternId getId() {
        return id;
    }

    @Override
    public Integer getVersion() {
        return version;
    }
}
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.i18n;

import uk.q3c.krail.core.data.KrailEntity;

/**
 * Created by David Sowerby on 18 Jan 2016
 */
public interface KeyValueEntity<ID, VER> extends KrailEntity<ID, VER> {

    String getValue();

    void setValue(String value);
}
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.i18n;

import uk.q3c.krail.core.data.EnumConverter;
import uk.q3c.krail.i18n.PatternCacheKey;

import javax.annotation.Nonnull;
import javax.persistence.Embeddable;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Created by David Sowerby on 17 Jan 2016
 */
@Embeddable
public class PatternId {
    private String i18nkey;
    private String locale;

    protected PatternId() {

    }

    public PatternId(@Nonnull PatternCacheKey cacheKey) {
        checkNotNull(cacheKey);
        final Enum<?> enumKey = cacheKey.getKey();
        this.i18nkey = new EnumConverter().convertToString(enumKey);
        this.locale = cacheKey.getRequestedLocale()
                              .toLanguageTag();
    }

    public String getI18nkey() {
        return i18nkey;
    }

    public String getLocale() {
        return locale;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        PatternId that = (PatternId) o;

        if (!i18nkey.equals(that.i18nkey)) return false;
        return locale.equals(that.locale);

    }

    @Override
    public int hashCode() {
        int result = i18nkey.hashCode();
        return 31 * result + locale.hashCode();
    }
}
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    @SuppressFBWarnings("SQL_INJECTION_JPA")
    // The only parameter is entityName(), which is limited to either the simple class name of the entity, or its annotation
    public final <E extends KrailEntity<ID, VER>> String entityName(@Nonnull Class<E> entityClass) {
     *

    @SuppressFBWarnings("SQL_INJECTION_JPA")
    // The only parameter is entityName(), which is limited to either the simple class name of the entity, or its annotation
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.persist;

import org.apache.onami.persist.EntityManagerProvider;
import org.apache.onami.persist.Transactional;
import uk.q3c.krail.jpa.i18n.KeyValueEntity;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Optional;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * A common base class for Pattern and Option DAOs, which both require a key-value type of implementation
 * <p>
 * Created by David Sowerby on 18 Jan 2016
 */
public abstract class BaseJpaKeyValueDao<ID, K, E extends KeyValueEntity<ID, Integer>> extends BaseJpaDao<ID, Integer> implements JpaKeyValueDao<ID, Integer, K,
        E> {

    private Class<E> entityClass;

    public BaseJpaKeyValueDao(EntityManagerProvider entityManagerProvider, Class<E> entityClass) {
        super(entityManagerProvider);
        this.entityClass = entityClass;
    }

    @Transactional
    @Override
    public E write(@Nonnull K cacheKey, @Nonnull String value) {
        checkNotNull(cacheKey);
        checkNotNull(value);
        E existingEntity = find(cacheKey);
        if (existingEntity != null) {
            existingEntity.setValue(value);
            return save(existingEntity);
        } else {
            return save(newEntity(cacheKey, value));
        }
    }

    protected abstract E newEntity(K cacheKey, String value);

    protected abstract ID newId(K cacheKey);

    @Transactional
    @Override
    @Nullable
    public E find(@Nonnull K cacheKey) {
        checkNotNull(cacheKey);
        return getEntityManager().find(entityClass, newId(cacheKey));
    }

    @Transactional
    @Nonnull
    @Override
    public Optional<String> deleteValue(@Nonnull K cacheKey) {
        checkNotNull(cacheKey);
        Optional<E> deletedItem = deleteById(entityClass, newId(cacheKey));
        if (deletedItem.isPresent()) {
            return Optional.of(deletedItem.get()
                                          .getValue());
        } else {
            return Optional.empty();
        }
    }

    @Nonnull
    @Override
    public Optional<String> getValueAsString(@Nonnull K cacheKey) {
        checkNotNull(cacheKey);
        E entity = find(cacheKey);
        return (entity == null) ? Optional.empty() : Optional.of(entity.getValue());
    }

    /**
     * returns the number of entries
     *
     * @return the number of entries
     */
    @Override
    @Transactional
    public long count() {
        return super.count(entityClass);
    }

}
import uk.q3c.krail.jpa.i18n.DefaultJpaPatternDao;
import uk.q3c.krail.jpa.user.opt.DefaultJpaOptionDao;
        bind(OptionDao.class, DefaultJpaOptionDao.class);
        bind(PatternDao.class, DefaultJpaPatternDao.class);
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.persist;

import org.apache.onami.persist.Transactional;
import uk.q3c.krail.jpa.i18n.KeyValueEntity;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Optional;

/**
 * Created by David Sowerby on 18 Jan 2016
 */
public interface JpaKeyValueDao<ID, VER, K, E extends KeyValueEntity> extends JpaDao<ID, VER> {
    @Transactional
    E write(@Nonnull K cacheKey, @Nonnull String value);

    @Transactional
    @Nullable
    E find(@Nonnull K cacheKey);

    @Transactional
    @Nonnull
    Optional<String> deleteValue(@Nonnull K cacheKey);

    @Nonnull
    Optional<String> getValueAsString(@Nonnull K cacheKey);

    @Transactional
    long count();
}
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
        return jpaContainerProvider.get(JpaOptionEntity.class, containerType);
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import uk.q3c.krail.jpa.persist.BaseJpaKeyValueDao;
import static com.google.common.base.Preconditions.checkArgument;
 * Converts {@link OptionCacheKey} to {@link JpaOptionEntity} for persistence.
public class DefaultJpaOptionDao extends BaseJpaKeyValueDao<OptionId, OptionCacheKey, JpaOptionEntity> implements JpaOptionDao {
    protected DefaultJpaOptionDao(EntityManagerProvider entityManagerProvider, OptionStringConverter optionStringConverter) {
        super(entityManagerProvider, JpaOptionEntity.class);
        return new Select().from(entityName(JpaOptionEntity.class))

    @Override
    public <V> Object write(@Nonnull OptionCacheKey cacheKey, @Nonnull Optional<V> value) {
        checkRankOption(cacheKey, SPECIFIC_RANK);
        checkArgument(value.isPresent(), "Value must be present");
        String stringValue = optionStringConverter.convertValueToString(value.get());
        return write(cacheKey, stringValue);
    }

        Optional<String> v = getValueAsString(cacheKey);
        return (v.isPresent()) ? Optional.of(optionStringConverter.convertStringToValue(cacheKey, v.get())) : Optional.empty();
    @SuppressFBWarnings("SQL_INJECTION_JPA")//entityName() is final and can only return SimpleClassName of annotation
    @SuppressWarnings("JpaQlInspection")
        return getEntityManager().createQuery("DELETE FROM " + entityName(JpaOptionEntity.class))
                                 .executeUpdate();
    }

    @Override
    protected JpaOptionEntity newEntity(OptionCacheKey cacheKey, String value) {
        return new JpaOptionEntity(cacheKey, value);
    }

    @Override
    protected OptionId newId(OptionCacheKey cacheKey) {
        return new OptionId(cacheKey);
        return super.count(JpaOptionEntity.class);
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.user.opt;

import org.apache.onami.persist.Transactional;
import uk.q3c.krail.core.user.opt.OptionDao;
import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Created by David Sowerby on 13/04/15.
 */
public interface JpaOptionDao extends OptionDao {

    @Transactional
    @Nullable
    JpaOptionEntity find(@Nonnull OptionCacheKey cacheKey);
}
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.user.opt;

import uk.q3c.krail.core.user.opt.Option;
import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;
import uk.q3c.krail.jpa.i18n.KeyValueEntity;

import javax.annotation.Nonnull;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Version;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * An entity to hold data for an {@link Option}.  The value is held as a String to enable the use of a single column for it.  Context is part of the overall
 * {#optionKey} to ensure uniqueness, but is also held separately as it is a useful way to group the options for manual setting up, and there may also be an
 * opportunity to pre-load options by Context.
 * <p>
 * Created by David Sowerby on 13/04/15.
 */
@Entity
public class JpaOptionEntity implements KeyValueEntity<OptionId, Integer> {

    @EmbeddedId
    private OptionId id;
    private String value;
    @Version
    private Integer version;

    protected JpaOptionEntity() {
    }

    public JpaOptionEntity(@Nonnull OptionCacheKey optionCacheKey, @Nonnull String value) {
        this.id = new OptionId(optionCacheKey);
        this.value = value;

    }

    @Override
    public OptionId getId() {
        return id;
    }

    @Override
    public Integer getVersion() {
        return version;
    }

    public String getContext() {
        return id.getContext();
    }

    public String getUserHierarchyName() {
        return id.getUserHierarchyName();
    }

    public String getRankName() {
        return id.getRankName();
    }

    public String getOptionKey() {
        return id.getOptionKey();
    }

    @Nonnull
    public String getValue() {
        return value;
    }

    public void setValue(@Nonnull String value) {
        checkNotNull(value);
        this.value = value;
    }


}
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.user.opt;

import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;

import javax.persistence.Embeddable;

/**
 * Created by David Sowerby on 17 Jan 2016
 */
@Embeddable
public class OptionId {


    private String context;
    private String optionKey;
    private String rankName;
    private String userHierarchyName;

    protected OptionId() {

    }

    public OptionId(OptionCacheKey optionCacheKey) {
        userHierarchyName = optionCacheKey.getHierarchy()
                                          .persistenceName();
        rankName = optionCacheKey.getRequestedRankName();
        optionKey = optionCacheKey.getOptionKey()
                                  .compositeKey();
        context = optionCacheKey.getOptionKey()
                                .getContext()
                                .getName();
    }

    public String getContext() {
        return context;
    }

    public String getOptionKey() {
        return optionKey;
    }

    public String getRankName() {
        return rankName;
    }

    public String getUserHierarchyName() {
        return userHierarchyName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        OptionId optionId = (OptionId) o;

        if (context != null ? !context.equals(optionId.context) : optionId.context != null) return false;
        if (optionKey != null ? !optionKey.equals(optionId.optionKey) : optionId.optionKey != null) return false;
        if (rankName != null ? !rankName.equals(optionId.rankName) : optionId.rankName != null) return false;
        return userHierarchyName != null ? userHierarchyName.equals(optionId.userHierarchyName) : optionId.userHierarchyName == null;

    }

    @Override
    public int hashCode() {
        int result = context != null ? context.hashCode() : 0;
        result = 31 * result + (optionKey != null ? optionKey.hashCode() : 0);
        result = 31 * result + (rankName != null ? rankName.hashCode() : 0);
        return 31 * result + (userHierarchyName != null ? userHierarchyName.hashCode() : 0);
    }
}
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
class DefaultJpaPatternDao__IntegrationTest extends Specification {
        persistenceService.isRunning()
        unitOfWork.isActive()
        JpaPatternEntity actual = dao.find(cacheKey)
        actual != null
        actual.getValue() == "4"
        JpaPatternEntity actual = dao.find(cacheKey)
        actual != null
        actual.getValue() == "6"
        JpaPatternEntity result2 = dao.find(cacheKey2)
        JpaPatternEntity result3 = dao.find(cacheKey3)
        result2.getValue() == "4"
        result3.getValue() == "5"
/*
 *
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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 uk.q3c.krail.jpa.i18n

import spock.lang.Specification
import uk.q3c.krail.i18n.LabelKey
import uk.q3c.krail.i18n.PatternCacheKey

/**
 * Created by David Sowerby on 13/07/15.
 */
class JpaPatternEntity_Test extends Specification {


    JpaPatternEntity entity;

    def "create decodes the I18NKey"() {
        given:

        PatternCacheKey cacheKey1 = new PatternCacheKey(LabelKey.Yes, Locale.UK)

        when:

        entity = new JpaPatternEntity(cacheKey1, "x")


        then:

        entity.getI18nkey().equals("uk.q3c.krail.i18n.LabelKey.Yes")
    }
}
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
import uk.q3c.krail.jpa.i18n.DefaultJpaPatternDao;
import uk.q3c.krail.jpa.user.opt.DefaultJpaOptionDao;
import uk.q3c.krail.jpa.user.opt.JpaOptionDao;
                      bind(JpaOptionDao.class, DefaultJpaOptionDao.class)
              .bind(JpaPatternDao.class, DefaultJpaPatternDao.class);
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
 * Test for {@link DefaultJpaOptionDao}
class DefaultJpaOptionDaoTest extends Specification {
    TypedQuery<JpaOptionEntity> query = Mock()
    List<JpaOptionEntity> emptyResultList;
    DefaultJpaOptionDao dao
        dao = new DefaultJpaOptionDao(entityManagerProvider, stringPersistenceConverter)
        entity instanceof JpaOptionEntity
        JpaOptionEntity rEntity = (JpaOptionEntity) entity
        List<JpaOptionEntity> resultList = new ArrayList<>();
        resultList.add((JpaOptionEntity) entity)
        entity instanceof JpaOptionEntity
        JpaOptionEntity rEntity = (JpaOptionEntity) entity
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
class DefaultJpaOptionDao__DataTypeTest extends Specification {
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
class DefaultJpaOptionDao__IntegrationTest extends Specification {
        JpaOptionEntity actual = (JpaOptionEntity) dao.write(cacheKey, Optional.of("4"));
        JpaOptionEntity actual = (JpaOptionEntity) dao.write(cacheKey, Optional.of("4"));
        actual = (JpaOptionEntity) dao.write(cacheKey, Optional.of("5"));
        actual = (JpaOptionEntity) dao.write(cacheKey, Optional.of("6"));
        JpaOptionEntity result = dao.find(cacheKey)
        result != null
        result.getValue() == "6"
        JpaOptionEntity result1 = dao.find(cacheKey1)
        JpaOptionEntity result2 = dao.find(cacheKey2)
        result1.getValue() == "4"
        result2.getValue() == "5"
 *  * Copyright (c) 2016. David Sowerby
 *  *
 *  * Licensed 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.
public class DefaultOptionJpaDaoTest2 extends JpaDaoTestBase {
    JpaOptionDao dao;
        final Key<JpaOptionDao> daoKey = Key.get(JpaOptionDao.class, Jpa1.class);
  ~ /*
  ~  * Copyright (c) 2016. David Sowerby
  ~  *
  ~  * Licensed 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.
  ~  */
        <class>uk.q3c.krail.jpa.user.opt.JpaOptionEntity</class>
        <class>uk.q3c.krail.jpa.i18n.JpaPatternEntity</class>
        <class>uk.q3c.krail.jpa.user.opt.JpaOptionEntity</class>
        <class>uk.q3c.krail.jpa.i18n.JpaPatternEntity</class>
        <class>uk.q3c.krail.jpa.user.opt.JpaOptionEntity</class>
        <class>uk.q3c.krail.jpa.i18n.JpaPatternEntity</class>
        <class>uk.q3c.krail.jpa.user.opt.JpaOptionEntity</class>
        <class>uk.q3c.krail.jpa.i18n.JpaPatternEntity</class>