OPENJPA-2767: Incomplete ValueMapDiscriminatorStrategy cache and MetaDataRepository race condition

Signed-off-by: Will Dazey <dazeydev.3@gmail.com>
diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ValueMapDiscriminatorStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ValueMapDiscriminatorStrategy.java
index cedfa85..5c44c27 100644
--- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ValueMapDiscriminatorStrategy.java
+++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ValueMapDiscriminatorStrategy.java
@@ -24,6 +24,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.TreeSet;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.apache.openjpa.jdbc.kernel.JDBCStore;
 import org.apache.openjpa.jdbc.meta.ClassMapping;
@@ -43,7 +45,6 @@
 public class ValueMapDiscriminatorStrategy
     extends InValueDiscriminatorStrategy {
 
-    
     private static final long serialVersionUID = 1L;
 
     public static final String ALIAS = "value-map";
@@ -51,7 +52,10 @@
     private static final Localizer _loc = Localizer.forPackage
         (ValueMapDiscriminatorStrategy.class);
 
-    private Map _vals = null;
+    private Map<String, Class<?>> _vals;
+    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true);
+    private final Lock _readLock = rwl.readLock();
+    private final Lock _writeLock = rwl.writeLock();
 
     @Override
     public String getAlias() {
@@ -67,8 +71,8 @@
         // if the user wants the type to be null, we need a jdbc-type
         // on the column or an existing column to figure out the java type
         DiscriminatorMappingInfo info = disc.getMappingInfo();
-        List cols = info.getColumns();
-        Column col = (cols.isEmpty()) ? null : (Column) cols.get(0);
+        List<Column> cols = info.getColumns();
+        Column col = (cols.isEmpty()) ? null : cols.get(0);
         if (col != null) {
             if (col.getJavaType() != JavaTypes.OBJECT)
                 return col.getJavaType();
@@ -88,29 +92,67 @@
     @Override
     protected Class getClass(Object val, JDBCStore store)
         throws ClassNotFoundException {
-        if (_vals == null) {
-            ClassMapping cls = disc.getClassMapping();
-            ClassMapping[] subs = cls.getJoinablePCSubclassMappings();
-            Map map = new HashMap((int) ((subs.length + 1) * 1.33 + 1));
-            mapDiscriminatorValue(cls, map);
-            for (int i = 0; i < subs.length; i++)
-                mapDiscriminatorValue(subs[i], map);
-            _vals = map;
+
+        if(_vals == null) {
+            _writeLock.lock();
+            try {
+                if(_vals == null) {
+                    _vals = constructCache(disc);
+                }
+            } finally {
+                _writeLock.unlock();
+            }
         }
 
-        String str = (val == null) ? null : val.toString();
-        Class cls = (Class) _vals.get(str);
-        if (cls != null)
-            return cls;
-        throw new ClassNotFoundException(_loc.get("unknown-discrim-value",
-            new Object[]{ str, disc.getClassMapping().getDescribedType().
-            getName(), new TreeSet(_vals.keySet()) }).getMessage());
+        String className = (val == null) ? null : val.toString();
+        _readLock.lock();
+        try {
+            Class<?> clz = _vals.get(className);
+            if (clz != null)
+                return clz;
+        } finally {
+            _readLock.unlock();
+        }
+
+        _writeLock.lock();
+        try {
+            Class<?> clz = _vals.get(className);
+            if (clz != null)
+                return clz;
+
+            //Rebuild the cache to check for updates
+            _vals = constructCache(disc);
+
+            //Try get again
+            clz = _vals.get(className);
+            if (clz != null)
+                return clz;
+            throw new ClassNotFoundException(_loc.get("unknown-discrim-value",
+                    new Object[]{ className, disc.getClassMapping().getDescribedType().
+                    getName(), new TreeSet<String>(_vals.keySet()) }).getMessage());
+        } finally {
+            _writeLock.unlock();
+        }
+    }
+
+    /**
+     * Build a class cache map from the discriminator
+     */
+    private static Map<String, Class<?>> constructCache(Discriminator disc) {
+        //Build the cache map
+        ClassMapping cls = disc.getClassMapping();
+        ClassMapping[] subs = cls.getJoinablePCSubclassMappings();
+        Map<String, Class<?>> map = new HashMap<String, Class<?>>((int) ((subs.length + 1) * 1.33 + 1));
+        mapDiscriminatorValue(cls, map);
+        for (int i = 0; i < subs.length; i++)
+            mapDiscriminatorValue(subs[i], map);
+        return map;
     }
 
     /**
      * Map the stringified version of the discriminator value of the given type.
      */
-    private static void mapDiscriminatorValue(ClassMapping cls, Map map) {
+    private static void mapDiscriminatorValue(ClassMapping cls, Map<String, Class<?>> map) {
         // possible that some types will never be persisted and therefore
         // can have no discriminator value
         Object val = cls.getDiscriminator().getValue();
@@ -118,7 +160,7 @@
             return;
 
         String str = (val == Discriminator.NULL) ? null : val.toString();
-        Class exist = (Class) map.get(str);
+        Class<?> exist = map.get(str);
         if (exist != null)
             throw new MetaDataException(_loc.get("dup-discrim-value",
                 str, exist, cls));
diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/MetaDataRepository.java b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/MetaDataRepository.java
index 7bc33b9..0525e22 100644
--- a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/MetaDataRepository.java
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/MetaDataRepository.java
@@ -126,7 +126,8 @@
     private Map<Class<?>, Class<?>> _metamodel = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>>());
 
     // map of classes to lists of their subclasses
-    private Map<Class<?>, List<Class<?>>> _subs = Collections.synchronizedMap(new HashMap<Class<?>, List<Class<?>>>());
+    private Map<Class<?>, Collection<Class<?>>> _subs =
+            Collections.synchronizedMap(new HashMap<Class<?>, Collection<Class<?>>>());
 
     // xml mapping
     protected final XMLMetaData[] EMPTY_XMLMETAS;
@@ -1625,19 +1626,20 @@
     }
 
     /**
-     * Updates our datastructures with the latest registered classes.
+     * Updates our data structures with the latest registered classes.
+     * 
+     * This method is synchronized to make sure that all data structures are fully updated
+     *  before other threads attempt to call this method
      */
-    Class<?>[] processRegisteredClasses(ClassLoader envLoader) {
-        if (_registered.isEmpty())
+    synchronized Class<?>[] processRegisteredClasses(ClassLoader envLoader) {
+        if (_registered.isEmpty()) {
             return EMPTY_CLASSES;
+        }
 
         // copy into new collection to avoid concurrent mod errors on reentrant
         // registrations
-        Class<?>[] reg;
-        synchronized (_registered) {
-            reg = _registered.toArray(new Class[_registered.size()]);
-            _registered.clear();
-        }
+        Class<?>[] reg = _registered.toArray(new Class[_registered.size()]);
+        _registered.clear();
 
         Collection<String> pcNames = getPersistentTypeNames(false, envLoader);
         Collection<Class<?>> failed = null;
@@ -1652,8 +1654,8 @@
             if (_filterRegisteredClasses) {
                 Log log = (_conf == null) ? null : _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
                 ClassLoader loadCL = (envLoader != null) ?
-                        envLoader :
-                        AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction());
+                    envLoader :
+                    AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction());
 
                 try {
                     Class<?> classFromAppClassLoader = Class.forName(reg[i].getName(), true, loadCL);
@@ -1840,7 +1842,7 @@
     /**
      * Add the given value to the collection cached in the given map under the given key.
      */
-    private void addToCollection(Map map, Class<?> key, Class<?> value, boolean inheritance) {
+    private void addToCollection(Map<Class<?>, Collection<Class<?>>> map, Class<?> key, Class<?> value, boolean inheritance) {
         if (_locking) {
             synchronized (map) {
                 addToCollectionInternal(map, key, value, inheritance);
@@ -1850,8 +1852,8 @@
         }
     }
 
-    private void addToCollectionInternal(Map map, Class<?> key, Class<?> value, boolean inheritance) {
-        Collection coll = (Collection) map.get(key);
+    private void addToCollectionInternal(Map<Class<?>, Collection<Class<?>>> map, Class<?> key, Class<?> value, boolean inheritance) {
+        Collection<Class<?>> coll = map.get(key);
         if (coll == null) {
             if (inheritance) {
                 InheritanceComparator comp = new InheritanceComparator();
@@ -1927,8 +1929,8 @@
     @Override
     public void endConfiguration() {
         initializeMetaDataFactory();
-		if (_implGen == null)
-			_implGen = new InterfaceImplGenerator(this);
+        if (_implGen == null)
+            _implGen = new InterfaceImplGenerator(this);
         if (_preload == true) {
             _oids = new HashMap<>();
             _impls = new HashMap<>();