You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/10/20 12:15:27 UTC

[isis] branch master updated: ISIS-2409: registers type converters for DN to handle offset time types

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

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new c6fc602  ISIS-2409: registers type converters for DN to handle offset time types
c6fc602 is described below

commit c6fc602e40baa9992c6aaa5f8d3a8477f333fd22
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Oct 20 14:15:12 2020 +0200

    ISIS-2409: registers type converters for DN to handle offset time types
    
    also adds a monkey patch for DN's TypeManagerImpl
    in support of priority based type registration
    (while documented, was never implemented)
    
    See also
    https://github.com/datanucleus/datanucleus-core/pull/360
---
 .../dom/types/javatime/JavaTimeTypesMenu.java      |   13 +-
 .../applib/IsisBookmarkConverter.java              |    2 -
 .../applib/IsisLocalResourcePathConverter.java     |    1 -
 .../typeconverters/applib/IsisMarkupConverter.java |    4 +-
 .../applib/IsisPasswordConverter.java              |    4 +-
 ...dConverter.java => IsoOffsetTimeConverter.java} |   23 +-
 ...nverter.java => IsoZonedDateTimeConverter.java} |   23 +-
 .../datanucleus/store/types/TypeManagerImpl.java   | 1533 ++++++++++++++++++++
 .../datanucleus-5/src/main/resources/plugin.xml    |   46 +-
 .../wicket/model/isis/WicketViewerSettings.java    |    8 -
 .../services/WicketViewerSettingsDefault.java      |   30 +-
 11 files changed, 1618 insertions(+), 69 deletions(-)

diff --git a/examples/demo/domain/src/main/java/demoapp/dom/types/javatime/JavaTimeTypesMenu.java b/examples/demo/domain/src/main/java/demoapp/dom/types/javatime/JavaTimeTypesMenu.java
index 9cd33f4..88a0237 100644
--- a/examples/demo/domain/src/main/java/demoapp/dom/types/javatime/JavaTimeTypesMenu.java
+++ b/examples/demo/domain/src/main/java/demoapp/dom/types/javatime/JavaTimeTypesMenu.java
@@ -25,9 +25,6 @@ import org.apache.isis.applib.annotation.DomainObjectLayout;
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.SemanticsOf;
-import org.apache.isis.applib.annotation.Where;
-
-import lombok.extern.log4j.Log4j2;
 
 import demoapp.dom.types.javatime.javatimelocaldate.JavaTimeLocalDates;
 import demoapp.dom.types.javatime.javatimelocaldatetime.JavaTimeLocalDateTimes;
@@ -37,7 +34,7 @@ import demoapp.dom.types.javatime.javatimezoneddatetime.JavaTimeZonedDateTimes;
 
 @DomainService(nature=NatureOfService.VIEW, objectType = "demo.JavaTimeTypesMenu")
 @DomainObjectLayout(named="JavaTimeTypes")
-@Log4j2
+//@Log4j2
 public class JavaTimeTypesMenu {
 
     @Action(semantics = SemanticsOf.SAFE)
@@ -59,16 +56,16 @@ public class JavaTimeTypesMenu {
     }
 
     @Action(
-            semantics = SemanticsOf.SAFE,
-            hidden = Where.EVERYWHERE   // TODO: ISIS-2409 - NPE
+            semantics = SemanticsOf.SAFE
+            //,hidden = Where.EVERYWHERE   // TODO: ISIS-2409 - NPE
     )
     @ActionLayout(cssClassFa="fa-clock")
     public JavaTimeOffsetTimes offsetTimes(){
         return new JavaTimeOffsetTimes();
     }
 
-    @Action(semantics = SemanticsOf.SAFE,
-            hidden = Where.EVERYWHERE   // TODO: ISIS-2410 - does not render/parse correctly
+    @Action(semantics = SemanticsOf.SAFE
+            //,hidden = Where.EVERYWHERE   // TODO: ISIS-2410 - does not render/parse correctly
     )
     @ActionLayout(cssClassFa="fa-clock")
     public JavaTimeZonedDateTimes zonedDateTimes(){
diff --git a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisBookmarkConverter.java b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisBookmarkConverter.java
index 6064f2f..5f2a4d2 100644
--- a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisBookmarkConverter.java
+++ b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisBookmarkConverter.java
@@ -21,8 +21,6 @@ package org.apache.isis.persistence.jdo.datanucleus5.datanucleus.typeconverters.
 import org.datanucleus.store.types.converters.TypeConverter;
 
 import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.util.schema.ChangesDtoUtils;
-import org.apache.isis.schema.chg.v2.ChangesDto;
 
 public class IsisBookmarkConverter implements TypeConverter<Bookmark, String>{
 
diff --git a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisLocalResourcePathConverter.java b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisLocalResourcePathConverter.java
index 57d593a..97fdb35 100644
--- a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisLocalResourcePathConverter.java
+++ b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisLocalResourcePathConverter.java
@@ -21,7 +21,6 @@ package org.apache.isis.persistence.jdo.datanucleus5.datanucleus.typeconverters.
 import org.datanucleus.store.types.converters.TypeConverter;
 
 import org.apache.isis.applib.value.LocalResourcePath;
-import org.apache.isis.applib.value.Markup;
 
 public class IsisLocalResourcePathConverter implements TypeConverter<LocalResourcePath, String>{
 
diff --git a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisMarkupConverter.java b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisMarkupConverter.java
index ba910ca..01f56e4 100644
--- a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisMarkupConverter.java
+++ b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisMarkupConverter.java
@@ -30,14 +30,14 @@ public class IsisMarkupConverter implements TypeConverter<Markup, String>{
     public String toDatastoreType(final Markup memberValue) {
         return memberValue != null
                 ? memberValue.asHtml()
-                        : null;
+                : null;
     }
 
     @Override
     public Markup toMemberType(final String datastoreValue) {
         return datastoreValue != null
                 ? new Markup(datastoreValue)
-                        : null;
+                : null;
     }
 
 }
diff --git a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java
index ee7dc8c..cb9cba8 100644
--- a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java
+++ b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java
@@ -30,14 +30,14 @@ public class IsisPasswordConverter implements TypeConverter<Password, String>{
     public String toDatastoreType(final Password memberValue) {
         return memberValue != null
                 ? memberValue.getPassword()
-                        : null;
+                : null;
     }
 
     @Override
     public Password toMemberType(final String datastoreValue) {
         return datastoreValue != null
                 ? new Password(datastoreValue)
-                        : null;
+                : null;
     }
 
 }
diff --git a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsoOffsetTimeConverter.java
similarity index 67%
copy from persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java
copy to persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsoOffsetTimeConverter.java
index ee7dc8c..9c7a807 100644
--- a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java
+++ b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsoOffsetTimeConverter.java
@@ -18,26 +18,27 @@
  */
 package org.apache.isis.persistence.jdo.datanucleus5.datanucleus.typeconverters.applib;
 
-import org.datanucleus.store.types.converters.TypeConverter;
+import java.time.OffsetTime;
+import java.time.format.DateTimeFormatter;
 
-import org.apache.isis.applib.value.Password;
+import org.datanucleus.store.types.converters.TypeConverter;
 
-public class IsisPasswordConverter implements TypeConverter<Password, String>{
+public class IsoOffsetTimeConverter implements TypeConverter<OffsetTime, String>{
 
     private static final long serialVersionUID = 1L;
-
+    
     @Override
-    public String toDatastoreType(final Password memberValue) {
-        return memberValue != null
-                ? memberValue.getPassword()
-                        : null;
+    public String toDatastoreType(final OffsetTime offsetTime) {
+        return offsetTime != null
+                ? offsetTime.format(DateTimeFormatter.ISO_OFFSET_TIME)
+                : null;
     }
 
     @Override
-    public Password toMemberType(final String datastoreValue) {
+    public OffsetTime toMemberType(final String datastoreValue) {
         return datastoreValue != null
-                ? new Password(datastoreValue)
-                        : null;
+                ? OffsetTime.parse(datastoreValue, DateTimeFormatter.ISO_OFFSET_TIME)
+                : null;
     }
 
 }
diff --git a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsoZonedDateTimeConverter.java
similarity index 66%
copy from persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java
copy to persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsoZonedDateTimeConverter.java
index ee7dc8c..eed47a7 100644
--- a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsisPasswordConverter.java
+++ b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/datanucleus/typeconverters/applib/IsoZonedDateTimeConverter.java
@@ -18,26 +18,27 @@
  */
 package org.apache.isis.persistence.jdo.datanucleus5.datanucleus.typeconverters.applib;
 
-import org.datanucleus.store.types.converters.TypeConverter;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 
-import org.apache.isis.applib.value.Password;
+import org.datanucleus.store.types.converters.TypeConverter;
 
-public class IsisPasswordConverter implements TypeConverter<Password, String>{
+public class IsoZonedDateTimeConverter implements TypeConverter<ZonedDateTime, String>{
 
     private static final long serialVersionUID = 1L;
-
+    
     @Override
-    public String toDatastoreType(final Password memberValue) {
-        return memberValue != null
-                ? memberValue.getPassword()
-                        : null;
+    public String toDatastoreType(final ZonedDateTime offsetTime) {
+        return offsetTime != null
+                ? offsetTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)
+                : null;
     }
 
     @Override
-    public Password toMemberType(final String datastoreValue) {
+    public ZonedDateTime toMemberType(final String datastoreValue) {
         return datastoreValue != null
-                ? new Password(datastoreValue)
-                        : null;
+                ? ZonedDateTime.parse(datastoreValue, DateTimeFormatter.ISO_ZONED_DATE_TIME)
+                : null;
     }
 
 }
diff --git a/persistence/jdo/datanucleus-5/src/main/java/org/datanucleus/store/types/TypeManagerImpl.java b/persistence/jdo/datanucleus-5/src/main/java/org/datanucleus/store/types/TypeManagerImpl.java
new file mode 100644
index 0000000..a8a4783
--- /dev/null
+++ b/persistence/jdo/datanucleus-5/src/main/java/org/datanucleus/store/types/TypeManagerImpl.java
@@ -0,0 +1,1533 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.datanucleus.store.types;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.MonthDay;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.Period;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Currency;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.PriorityQueue;
+import java.util.Properties;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.Stack;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.datanucleus.ClassLoaderResolver;
+import org.datanucleus.NucleusContext;
+import org.datanucleus.PropertyNames;
+import org.datanucleus.exceptions.ClassNotResolvedException;
+import org.datanucleus.exceptions.NucleusException;
+import org.datanucleus.exceptions.NucleusUserException;
+import org.datanucleus.identity.IdentityUtils;
+import org.datanucleus.metadata.AbstractMemberMetaData;
+import org.datanucleus.plugin.ConfigurationElement;
+import org.datanucleus.plugin.PluginManager;
+import org.datanucleus.state.ObjectProvider;
+import org.datanucleus.store.StoreManager;
+import org.datanucleus.store.types.containers.ArrayHandler;
+import org.datanucleus.store.types.containers.ArrayListHandler;
+import org.datanucleus.store.types.containers.HashMapHandler;
+import org.datanucleus.store.types.containers.HashSetHandler;
+import org.datanucleus.store.types.containers.HashtableHandler;
+import org.datanucleus.store.types.containers.JDKCollectionHandler;
+import org.datanucleus.store.types.containers.LinkedHashMapHandler;
+import org.datanucleus.store.types.containers.LinkedHashSetHandler;
+import org.datanucleus.store.types.containers.LinkedListHandler;
+import org.datanucleus.store.types.containers.OptionalHandler;
+import org.datanucleus.store.types.containers.PriorityQueueHandler;
+import org.datanucleus.store.types.containers.PropertiesHandler;
+import org.datanucleus.store.types.containers.StackHandler;
+import org.datanucleus.store.types.containers.TreeMapHandler;
+import org.datanucleus.store.types.containers.TreeSetHandler;
+import org.datanucleus.store.types.containers.VectorHandler;
+import org.datanucleus.store.types.converters.ClassStringConverter;
+import org.datanucleus.store.types.converters.TypeConverter;
+import org.datanucleus.util.ClassUtils;
+import org.datanucleus.util.Localiser;
+import org.datanucleus.util.NucleusLogger;
+import org.datanucleus.util.StringUtils;
+
+/**
+ * TODO remove once fixed upstream!
+ * <p>
+ * Monkey patched from DN original in support of priority based type registration.
+ * While documented, was never implemented.
+ * <p>
+ * See also
+ * https://github.com/datanucleus/datanucleus-core/pull/360
+ */
+public class TypeManagerImpl implements TypeManager, Serializable
+{
+    private static final long serialVersionUID = 8217508318434539002L;
+
+    protected NucleusContext nucCtx;
+
+    protected transient ClassLoaderResolver clr;
+
+    /** Map of java types, keyed by the class name. */
+    protected Map<String, JavaType> javaTypes = new ConcurrentHashMap<>();
+    
+    /** Map of java type priorities, keyed by the class name. */
+    protected Map<String, Integer> javaTypePriorities = new ConcurrentHashMap<>();
+
+    /** Map of ContainerHandlers, keyed by the container type class name. */
+    protected Map<Class, ? super ContainerHandler> containerHandlersByClass = new ConcurrentHashMap<>();
+
+    /** Map of TypeConverter keyed by their symbolic name. */
+    protected Map<String, TypeConverter> typeConverterByName = new ConcurrentHashMap<>();
+
+    /** Map of TypeConverter keyed by type name that we should default to for this type (user-defined). */
+    protected Map<String, TypeConverter> autoApplyConvertersByType = null;
+
+    /** Map of (Map of TypeConverter keyed by the datastore type), keyed by the member type. */
+    protected Map<Class, Map<Class, TypeConverter>> typeConverterMap = new ConcurrentHashMap<>();
+
+    /** Cache of TypeConverter datastore type, keyed by the converter. */
+    protected Map<TypeConverter, Class> typeConverterDatastoreTypeByConverter = new ConcurrentHashMap<>();
+
+    /** Cache of TypeConverter member type, keyed by the converter. */
+    protected Map<TypeConverter, Class> typeConverterMemberTypeByConverter = new ConcurrentHashMap<>();
+
+    /**
+     * Constructor, loading support for type mappings using the plugin mechanism.
+     * @param nucCtx NucleusContext
+     */
+    public TypeManagerImpl(NucleusContext nucCtx)
+    {
+        this.nucCtx = nucCtx;
+        this.clr = nucCtx.getClassLoaderResolver(null);
+        loadJavaTypes(nucCtx.getPluginManager());
+
+        // Load converters since if a type is otherwise not persistable but a converter is found, then the type becomes persistable, hence needs enhancing
+        loadTypeConverters(nucCtx.getPluginManager());
+    }
+
+    public void close()
+    {
+        containerHandlersByClass = null;
+        javaTypes = null;
+
+        typeConverterByName.clear();
+        typeConverterMap.clear();
+        typeConverterMemberTypeByConverter.clear();
+        typeConverterDatastoreTypeByConverter.clear();
+
+        autoApplyConvertersByType = null;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getSupportedSecondClassTypes()
+     */
+    @Override
+    public Set<String> getSupportedSecondClassTypes()
+    {
+        return new HashSet(javaTypes.keySet());
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#isSupportedSecondClassType(java.lang.String)
+     */
+    @Override
+    public boolean isSupportedSecondClassType(String className)
+    {
+        if (className == null)
+        {
+            return false;
+        }
+
+        JavaType type = javaTypes.get(className);
+        if (type == null)
+        {
+            try
+            {
+                Class cls = clr.classForName(className);
+                type = findJavaTypeForClass(cls);
+                return type != null;
+            }
+            catch (Exception e)
+            {
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#filterOutSupportedSecondClassNames(java.lang.String[])
+     */
+    @Override
+    public String[] filterOutSupportedSecondClassNames(String[] inputClassNames)
+    {
+        // Filter out any "simple" type classes
+        int filteredClasses = 0;
+        for (int i = 0; i < inputClassNames.length; ++i)
+        {
+            if (isSupportedSecondClassType(inputClassNames[i]))
+            {
+                inputClassNames[i] = null;
+                ++filteredClasses;
+            }
+        }
+        if (filteredClasses == 0)
+        {
+            return inputClassNames;
+        }
+        String[] restClasses = new String[inputClassNames.length - filteredClasses];
+        int m = 0;
+        for (int i = 0; i < inputClassNames.length; ++i)
+        {
+            if (inputClassNames[i] != null)
+            {
+                restClasses[m++] = inputClassNames[i];
+            }
+        }
+        return restClasses;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#isDefaultPersistent(java.lang.Class)
+     */
+    @Override
+    public boolean isDefaultPersistent(Class c)
+    {
+        if (c == null)
+        {
+            return false;
+        }
+
+        JavaType type = javaTypes.get(c.getName());
+        if (type != null)
+        {
+            return true;
+        }
+
+        // Try to find a class that this class extends that is supported
+        type = findJavaTypeForClass(c);
+        if (type != null)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#isDefaultFetchGroup(java.lang.Class)
+     */
+    @Override
+    public boolean isDefaultFetchGroup(Class c)
+    {
+        if (c == null)
+        {
+            return false;
+        }
+
+        if (nucCtx.getApiAdapter().isPersistable(c))
+        {
+            // 1-1/N-1 (persistable field), so return what the API default is
+            return nucCtx.getApiAdapter().getDefaultDFGForPersistableField();
+        }
+
+        JavaType type = javaTypes.get(c.getName());
+        if (type != null)
+        {
+            // Field type defined in plugins, so return the setting
+            return type.dfg;
+        }
+
+        // Try to find a class that this class extends that is supported
+        type = findJavaTypeForClass(c);
+        if (type != null)
+        {
+            return type.dfg;
+        }
+
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#isDefaultFetchGroupForCollection(java.lang.Class, java.lang.Class)
+     */
+    @Override
+    public boolean isDefaultFetchGroupForCollection(Class c, Class genericType)
+    {
+        if (c != null && genericType == null)
+        {
+            return isDefaultFetchGroup(c);
+        }
+        else if (c == null)
+        {
+            return false;
+        }
+
+        String name = c.getName() + "<" + genericType.getName() + ">";
+        JavaType type = javaTypes.get(name);
+        if (type != null)
+        {
+            return type.dfg;
+        }
+
+        // Try to find a class that this class extends that is supported
+        type = findJavaTypeForCollectionClass(c, genericType);
+        if (type != null)
+        {
+            return type.dfg;
+        }
+
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#isDefaultEmbeddedType(java.lang.Class)
+     */
+    @Override
+    public boolean isDefaultEmbeddedType(Class c)
+    {
+        if (c == null)
+        {
+            return false;
+        }
+
+        JavaType type = javaTypes.get(c.getName());
+        if (type != null)
+        {
+            return type.embedded;
+        }
+
+        // Try to find a class that this class extends that is supported
+        type = findJavaTypeForClass(c);
+        if (type != null)
+        {
+            return type.embedded;
+        }
+
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#isSecondClassMutableType(java.lang.String)
+     */
+    @Override
+    public boolean isSecondClassMutableType(String className)
+    {
+        return getWrapperTypeForType(className) != null;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getWrapperTypeForType(java.lang.String)
+     */
+    @Override
+    public Class getWrapperTypeForType(String className)
+    {
+        if (className == null)
+        {
+            return null;
+        }
+
+        JavaType type = javaTypes.get(className);
+        return type == null ? null : type.wrapperType;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getWrappedTypeBackedForType(java.lang.String)
+     */
+    @Override
+    public Class getWrappedTypeBackedForType(String className)
+    {
+        if (className == null)
+        {
+            return null;
+        }
+
+        JavaType type = javaTypes.get(className);
+        return type == null ? null : type.wrapperTypeBacked;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#isSecondClassWrapper(java.lang.String)
+     */
+    @Override
+    public boolean isSecondClassWrapper(String className)
+    {
+        if (className == null)
+        {
+            return false;
+        }
+
+        // Check java types with wrappers
+        Iterator iter = javaTypes.values().iterator();
+        while (iter.hasNext())
+        {
+            JavaType type = (JavaType)iter.next();
+            if (type.wrapperType != null && type.wrapperType.getName().equals(className))
+            {
+                return true;
+            }
+            if (type.wrapperTypeBacked != null && type.wrapperTypeBacked.getName().equals(className))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#createSCOInstance(org.datanucleus.state.ObjectProvider, org.datanucleus.metadata.AbstractMemberMetaData, java.lang.Class, java.lang.Object, boolean)
+     */
+    @Override
+    public SCO createSCOInstance(ObjectProvider ownerOP, AbstractMemberMetaData mmd, Class instantiatedType, Object value, boolean replaceField)
+    {
+        if (value != null && value instanceof SCO)
+        {
+            // Passed in value is a wrapper type already, so just return it!
+            if (replaceField)
+            {
+                // Replace the field with this value
+                ownerOP.replaceField(mmd.getAbsoluteFieldNumber(), value);
+            }
+            return (SCO) value;
+        }
+
+        // Create new wrapper of the required type
+        Class requiredType = value != null ? value.getClass() : instantiatedType; // Default to instantiated type
+        if ("declared".equalsIgnoreCase(nucCtx.getConfiguration().getStringProperty(PropertyNames.PROPERTY_TYPE_WRAPPER_BASIS)))
+        {
+            // Use declared type of the field to define the wrapper type
+            requiredType = mmd.getType();
+        }
+        SCO sco = createSCOInstance(ownerOP, mmd, requiredType);
+
+        if (replaceField)
+        {
+            // Replace the field in the owner with the wrapper before initialising it
+            ownerOP.replaceField(mmd.getAbsoluteFieldNumber(), sco);
+        }
+
+        // Initialise the SCO for use
+        if (value != null)
+        {
+            // Apply the existing value
+            sco.initialise(value);
+        }
+        else
+        {
+            // Just create it empty and load from the datastore
+            sco.initialise();
+        }
+
+        return sco;
+    }
+
+    /**
+     * Method to create a new SCO wrapper for the specified field replacing the old value with the new value. 
+     * If the member value is a SCO already will just return the (new) value.
+     * @param ownerOP The ObjectProvider of the owner object
+     * @param memberNumber The member number in the owner
+     * @param newValue The value to initialise the wrapper with (if any) for this member
+     * @param oldValue The previous value that we are replacing with this value
+     * @param replaceFieldIfChanged Whether to replace the member in the object if wrapping the value
+     * @return The wrapper (or original value if not wrappable)
+     */
+    public Object wrapAndReplaceSCOField(ObjectProvider ownerOP, int memberNumber, Object newValue, Object oldValue, boolean replaceFieldIfChanged)
+    {
+        if (newValue == null || !ownerOP.getClassMetaData().getSCOMutableMemberFlags()[memberNumber])
+        {
+            // We don't wrap null objects currently
+            return newValue;
+        }
+
+        if (!(newValue instanceof SCO) || ownerOP.getObject() != ((SCO)newValue).getOwner())
+        {
+            // Not a SCO wrapper, or is a SCO wrapper but not owned by this object
+            AbstractMemberMetaData mmd = ownerOP.getClassMetaData().getMetaDataForManagedMemberAtAbsolutePosition(memberNumber);
+            if (replaceFieldIfChanged)
+            {
+                if (NucleusLogger.PERSISTENCE.isDebugEnabled())
+                {
+                    NucleusLogger.PERSISTENCE.debug(Localiser.msg("026029", StringUtils.toJVMIDString(ownerOP.getObject()), 
+                        ownerOP.getExecutionContext() != null ? IdentityUtils.getPersistableIdentityForId(ownerOP.getInternalObjectId()) : ownerOP.getInternalObjectId(), mmd.getName()));
+                }
+            }
+
+            if (newValue instanceof SCO)
+            {
+                // Passed in value is a wrapper type already, so just return it!
+                if (replaceFieldIfChanged)
+                {
+                    // Replace the field with this value
+                    ownerOP.replaceField(mmd.getAbsoluteFieldNumber(), newValue);
+                }
+                return newValue;
+            }
+
+            // Create new wrapper of the required type
+            Class requiredType = newValue.getClass();
+            SCO sco = createSCOInstance(ownerOP, mmd, requiredType);
+
+            if (replaceFieldIfChanged)
+            {
+                // Replace the field in the owner with the wrapper before initialising it
+                ownerOP.replaceField(mmd.getAbsoluteFieldNumber(), sco);
+            }
+
+            // Initialise the SCO for use, providing new and old values so the wrapper has the ability to do something intelligent
+            sco.initialise(newValue, oldValue);
+
+            return sco;
+        }
+        return newValue;
+    }
+
+    /**
+     * Method to create a new SCO wrapper for member type.
+     * Will find a wrapper suitable for the instantiated type (if provided), otherwise suitable for the member metadata type.
+     * @param ownerOP ObjectProvider for the owning object
+     * @param mmd The MetaData for the related member.
+     * @param instantiatedType Type to instantiate the member as (if known), otherwise falls back to the type from metadata
+     * @return The wrapper object of the required type
+     * @throws NucleusUserException if an error occurred when creating the SCO instance
+     */
+    private SCO createSCOInstance(ObjectProvider ownerOP, AbstractMemberMetaData mmd, Class instantiatedType)
+    {
+        String typeName = (instantiatedType != null) ? instantiatedType.getName() : mmd.getTypeName();
+
+        // Find the SCO wrapper type most suitable
+        StoreManager storeMgr = ownerOP.getExecutionContext().getStoreManager();
+        boolean backedWrapper = storeMgr.useBackedSCOWrapperForMember(mmd, ownerOP.getExecutionContext());
+        Class wrapperType = null;
+        if (mmd.isSerialized())
+        {
+            // If we have all elements serialised into a column then cannot have backing stores
+            backedWrapper = false;
+        }
+
+        if (backedWrapper)
+        {
+            wrapperType = getBackedWrapperTypeForType(mmd.getType(), instantiatedType, typeName);
+        }
+        else
+        {
+            wrapperType = getSimpleWrapperTypeForType(mmd.getType(), instantiatedType, typeName);
+        }
+        if (wrapperType == null)
+        {
+            throw new NucleusUserException(Localiser.msg("023011", mmd.getTypeName(), typeName, mmd.getFullFieldName()));
+        }
+
+        // Create the SCO wrapper
+        try
+        {
+            return (SCO) ClassUtils.newInstance(wrapperType, new Class[]{ObjectProvider.class, AbstractMemberMetaData.class}, new Object[]{ownerOP, mmd});
+        }
+        catch (UnsupportedOperationException uoe)
+        {
+            // Can't create backing store? so try simple wrapper
+            if (backedWrapper)
+            {
+                NucleusLogger.PERSISTENCE.warn("Creation of backed wrapper for " + mmd.getFullFieldName() + " unsupported, so trying simple wrapper");
+                wrapperType = getSimpleWrapperTypeForType(mmd.getType(), instantiatedType, typeName);
+                return (SCO) ClassUtils.newInstance(wrapperType, new Class[]{ObjectProvider.class, AbstractMemberMetaData.class}, new Object[]{ownerOP, mmd});
+            }
+
+            throw uoe;
+        }
+    }
+
+    /**
+     * Convenience method to return the backed wrapper type for the field definition. Wrapper is null if no backed wrapper is defined for the type.
+     * @param declaredType Declared type of the field
+     * @param instantiatedType Instantiated type of the field
+     * @param typeName Type name to try first
+     * @return The wrapper type
+     */
+    private Class getBackedWrapperTypeForType(Class declaredType, Class instantiatedType, String typeName)
+    {
+        Class wrapperType = getWrappedTypeBackedForType(typeName);
+        if (wrapperType == null)
+        {
+            // typeName not supported directly (no SCO wrapper for the precise type)
+            if (instantiatedType != null)
+            {
+                // Try the instantiated type
+                wrapperType = getWrappedTypeBackedForType(instantiatedType.getName());
+            }
+            if (wrapperType == null)
+            {
+                // Try the declared type
+                wrapperType = getWrappedTypeBackedForType(declaredType.getName());
+            }
+        }
+        return wrapperType;
+    }
+
+    /**
+     * Convenience method to return the simple wrapper type for the field definition. Wrapper is null if no simple wrapper is defined for the type.
+     * @param declaredType Declared type of the field
+     * @param instantiatedType Instantiated type of the field
+     * @param typeName Type name to try first
+     * @return The wrapper type
+     */
+    private Class getSimpleWrapperTypeForType(Class declaredType, Class instantiatedType, String typeName)
+    {
+        Class wrapperType = getWrapperTypeForType(typeName);
+        if (wrapperType == null)
+        {
+            // typeName not supported directly (no SCO wrapper for the precise type)
+            if (instantiatedType != null)
+            {
+                // Try the instantiated type
+                wrapperType = getWrapperTypeForType(instantiatedType.getName());
+            }
+            if (wrapperType == null)
+            {
+                // Try the declared type
+                wrapperType = getWrapperTypeForType(declaredType.getName());
+            }
+        }
+        return wrapperType;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getTypeForSecondClassWrapper(java.lang.String)
+     */
+    @Override
+    public Class getTypeForSecondClassWrapper(String className)
+    {
+        Iterator iter = javaTypes.values().iterator();
+        while (iter.hasNext())
+        {
+            JavaType type = (JavaType)iter.next();
+            if (type.wrapperType != null && type.wrapperType.getName().equals(className))
+            {
+                return type.cls;
+            }
+            if (type.wrapperTypeBacked != null && type.wrapperTypeBacked.getName().equals(className))
+            {
+                return type.cls;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public ContainerAdapter getContainerAdapter(Object container)
+    {
+        ContainerHandler containerHandler = getContainerHandler(container.getClass());
+        return containerHandler == null ? null : containerHandler.getAdapter(container);
+    }
+    
+    @Override
+    public <H extends ContainerHandler> H getContainerHandler(Class containerClass)
+    {
+        H containerHandler = (H) containerHandlersByClass.get(containerClass);
+        if (containerHandler == null)
+        {
+            // Try to find the container handler using the registered type
+            JavaType type = findJavaTypeForClass(containerClass);
+            if (type != null && type.containerHandlerType != null)
+            {
+                Class[] parameterTypes = null;
+                Object[] parameters = null;
+                
+                Class[] classParameterTypes = new Class[]{Class.class};
+
+                // Allow ContainerHandlers that receive the container type on the constructor. e.g. ArrayHandler
+                if (ClassUtils.getConstructorWithArguments(type.containerHandlerType, classParameterTypes) != null )
+                {
+                    parameterTypes = classParameterTypes;
+                    parameters = new Object[] {containerClass};
+                }
+                
+                containerHandler = (H) ClassUtils.newInstance(type.containerHandlerType, parameterTypes, parameters);
+                
+                containerHandlersByClass.put(containerClass, containerHandler);
+            }
+        }
+
+        return containerHandler;
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getTypeConverterForName(java.lang.String)
+     */
+    @Override
+    public TypeConverter getTypeConverterForName(String converterName)
+    {
+        return converterName == null ? null : typeConverterByName.get(converterName);
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#registerConverter(java.lang.String, org.datanucleus.store.types.converters.TypeConverter, java.lang.Class, java.lang.Class, boolean, java.lang.String)
+     */
+    @Override
+    public void registerConverter(String name, TypeConverter converter, Class memberType, Class dbType, boolean autoApply, String autoApplyType)
+    {
+        // Add to lookup name -> converter
+        if (name != null)
+        {
+            typeConverterByName.put(name, converter);
+        }
+
+        // Add to lookup converter -> memberType
+        typeConverterDatastoreTypeByConverter.put(converter, dbType);
+
+        // Add to lookup converter -> dbType
+        typeConverterMemberTypeByConverter.put(converter, memberType);
+
+        // Add to lookup by memberType and dbType
+        Map<Class, TypeConverter> convertersForMember = typeConverterMap.get(memberType);
+        if (convertersForMember == null)
+        {
+            convertersForMember = new ConcurrentHashMap<Class, TypeConverter>();
+            typeConverterMap.put(memberType, convertersForMember);
+        }
+        // TODO We could already have a converter registered for this datastore type, so this replaces it!
+        convertersForMember.put(dbType, converter);
+
+        if (converter instanceof ClassStringConverter)
+        {
+            // ClassStringConverter is a special case that needs the CLR injecting. TODO Find a general way for converters to use this
+            ((ClassStringConverter)converter).setClassLoaderResolver(clr);
+        }
+
+        if (autoApply)
+        {
+            // Register converter to auto-apply
+            if (autoApplyConvertersByType == null)
+            {
+                autoApplyConvertersByType = new ConcurrentHashMap<String, TypeConverter>();
+            }
+            autoApplyConvertersByType.put(autoApplyType, converter);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getAutoApplyTypeConverterForType(java.lang.Class)
+     */
+    @Override
+    public TypeConverter getAutoApplyTypeConverterForType(Class memberType)
+    {
+        return autoApplyConvertersByType == null ? null : autoApplyConvertersByType.get(memberType.getName());
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#setDefaultTypeConverterForType(java.lang.Class, java.lang.String)
+     */
+    @Override
+    public void setDefaultTypeConverterForType(Class memberType, String converterName)
+    {
+        JavaType javaType = javaTypes.get(memberType.getName());
+        if (javaType == null)
+        {
+            return;
+        }
+
+        String typeConverterName = javaType.typeConverterName;
+        if (typeConverterName == null || !typeConverterName.equals(converterName))
+        {
+            javaType.typeConverterName = converterName;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getDefaultTypeConverterForType(java.lang.Class)
+     */
+    @Override
+    public TypeConverter getDefaultTypeConverterForType(Class memberType)
+    {
+        JavaType javaType = javaTypes.get(memberType.getName());
+        if (javaType == null)
+        {
+            return null;
+        }
+        String typeConverterName = javaType.typeConverterName;
+        if (typeConverterName == null)
+        {
+            return null;
+        }
+        return getTypeConverterForName(typeConverterName);
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getTypeConverterForType(java.lang.Class, java.lang.Class)
+     */
+    @Override
+    public TypeConverter getTypeConverterForType(Class memberType, Class datastoreType)
+    {
+        if (memberType == null)
+        {
+            return null;
+        }
+
+        Map<Class, TypeConverter> convertersForMember = typeConverterMap.get(memberType);
+        if (convertersForMember == null)
+        {
+            return null;
+        }
+        return convertersForMember.get(datastoreType);
+    }
+
+    /* (non-Javadoc)
+     * @see org.datanucleus.store.types.TypeManager#getTypeConvertersForType(java.lang.Class)
+     */
+    @Override
+    public Collection<TypeConverter> getTypeConvertersForType(Class memberType)
+    {
+        if (memberType == null)
+        {
+            return null;
+        }
+
+        Map<Class, TypeConverter> convertersForMember = typeConverterMap.get(memberType);
+        if (convertersForMember == null)
+        {
+            return null;
+        }
+        return convertersForMember.values();
+    }
+
+    /**
+     * Method to return the datastore type for the specified TypeConverter.
+     * @param conv The converter
+     * @param memberType The member type
+     * @return The datastore type
+     */
+    public Class getDatastoreTypeForTypeConverter(TypeConverter conv, Class memberType)
+    {
+        return typeConverterDatastoreTypeByConverter.get(conv);
+    }
+
+    /**
+     * Method to return the member type for the specified TypeConverter.
+     * @param conv The converter
+     * @param datastoreType The datastore type for this converter
+     * @return The member type
+     */
+    public Class getMemberTypeForTypeConverter(TypeConverter conv, Class datastoreType)
+    {
+        return typeConverterMemberTypeByConverter.get(conv);
+    }
+
+    /**
+     * Convenience method to return the JavaType for the specified class. If this class has a defined
+     * JavaType then returns it. If not then tries to find a superclass that is castable to the specified type.
+     * @param cls The class required
+     * @return The JavaType
+     */
+    protected JavaType findJavaTypeForClass(Class cls)
+    {
+        if (cls == null)
+        {
+            return null;
+        }
+        JavaType type = javaTypes.get(cls.getName());
+        if (type != null)
+        {
+            return type;
+        }
+
+        // Not supported so try to find one that is supported that this class derives from
+        Collection supportedTypes = new HashSet(javaTypes.values());
+        Iterator iter = supportedTypes.iterator();
+        while (iter.hasNext())
+        {
+            type = (JavaType)iter.next();
+            if (type.cls == cls && type.genericType == null)
+            {
+                return type;
+            }
+            if (!type.cls.getName().equals("java.lang.Object") && !type.cls.getName().equals("java.io.Serializable"))
+            {
+                Class componentCls = cls.isArray() ? cls.getComponentType() : null;
+                if (componentCls != null)
+                {
+                    // Array type
+                    if (type.cls.isArray() && type.cls.getComponentType().isAssignableFrom(componentCls))
+                    {
+                        javaTypes.put(cls.getName(), type); // Register this subtype for reference
+                        if (NucleusLogger.PERSISTENCE.isDebugEnabled())
+                        {
+                            NucleusLogger.PERSISTENCE.debug(Localiser.msg("016001", cls.getName(), type.cls.getName()));
+                        }
+                        return type;
+                    }
+                }
+                else
+                {
+                    // Basic type
+                    if (type.cls.isAssignableFrom(cls) && type.genericType == null)
+                    {
+                        if (type.wrapperType == null || type.wrapperType.isAssignableFrom(cls)) // Dont do this when we have a wrapper type to consider since would lead to ClassCastException
+                        {
+                            javaTypes.put(cls.getName(), type); // Register this subtype for reference
+                            if (NucleusLogger.PERSISTENCE.isDebugEnabled())
+                            {
+                                NucleusLogger.PERSISTENCE.debug(Localiser.msg("016001", cls.getName(), type.cls.getName()));
+                            }
+                            return type;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Not supported
+        return null;
+    }
+
+    /**
+     * Convenience method to return the JavaType for the specified class. If this class has a defined
+     * JavaType then returns it. If not then tries to find a superclass that is castable to the specified
+     * type.
+     * @param cls The class required
+     * @param genericType Any generic type specified for the element
+     * @return The JavaType
+     */
+    protected JavaType findJavaTypeForCollectionClass(Class cls, Class genericType)
+    {
+        if (cls == null)
+        {
+            return null;
+        }
+        else if (genericType == null)
+        {
+            return findJavaTypeForClass(cls);
+        }
+
+        String typeName = cls.getName() + "<" + genericType.getName() + ">";
+        JavaType type = javaTypes.get(typeName);
+        if (type != null)
+        {
+            return type;
+        }
+
+        // Not supported so try to find one that is supported that this class derives from
+        Collection supportedTypes = new HashSet(javaTypes.values());
+        Iterator iter = supportedTypes.iterator();
+        while (iter.hasNext())
+        {
+            type = (JavaType)iter.next();
+            if (type.cls.isAssignableFrom(cls))
+            {
+                if (type.genericType != null && type.genericType.isAssignableFrom(genericType))
+                {
+                    javaTypes.put(typeName, type); // Register this subtype for reference
+                    return type;
+                }
+            }
+        }
+
+        // Fallback to just matching the collection type and forget the generic detail
+        return findJavaTypeForClass(cls);
+    }
+
+    static class JavaType implements Serializable
+    {
+        private static final long serialVersionUID = -811442140006259453L;
+        final Class cls;
+        final Class genericType;
+        final boolean embedded;
+        final boolean dfg;
+        final Class wrapperType;
+        final Class wrapperTypeBacked;
+        String typeConverterName;
+        final Class containerHandlerType;
+
+        public JavaType(Class cls, Class genericType, boolean embedded, boolean dfg, Class wrapperType, Class wrapperTypeBacked, Class containerHandlerType, String typeConverterName)
+        {
+            this.cls = cls;
+            this.genericType = genericType;
+            this.embedded = embedded;
+            this.dfg = dfg;
+            this.wrapperType = wrapperType;
+            this.wrapperTypeBacked = wrapperTypeBacked != null ? wrapperTypeBacked : wrapperType;
+            this.containerHandlerType = containerHandlerType;
+            this.typeConverterName = typeConverterName;
+        }
+    }
+
+    /**
+     * Method to load the java types that we support out of the box.
+     * This includes all built-in types, as well as all types registered via the plugin mechanism.
+     * @param mgr the PluginManager
+     */
+    private void loadJavaTypes(PluginManager mgr)
+    {
+        if (NucleusLogger.PERSISTENCE.isDebugEnabled())
+        {
+            NucleusLogger.PERSISTENCE.debug(Localiser.msg("016003"));
+        }
+
+        // Load up built-in types
+        addJavaType(boolean.class, null, true, true, null, null, null, null);
+        addJavaType(byte.class, null, true, true, null, null, null, null);
+        addJavaType(char.class, null, true, true, null, null, null, null);
+        addJavaType(double.class, null, true, true, null, null, null, null);
+        addJavaType(float.class, null, true, true, null, null, null, null);
+        addJavaType(int.class, null, true, true, null, null, null, null);
+        addJavaType(long.class, null, true, true, null, null, null, null);
+        addJavaType(short.class, null, true, true, null, null, null, null);
+
+        addJavaType(Boolean.class, null, true, true, null, null, null, null);
+        addJavaType(Byte.class, null, true, true, null, null, null, null);
+        addJavaType(Character.class, null, true, true, null, null, null, null);
+        addJavaType(Double.class, null, true, true, null, null, null, null);
+        addJavaType(Float.class, null, true, true, null, null, null, null);
+        addJavaType(Integer.class, null, true, true, null, null, null, null);
+        addJavaType(Long.class, null, true, true, null, null, null, null);
+        addJavaType(Short.class, null, true, true, null, null, null, null);
+
+        addJavaType(Number.class, null, true, true, null, null, null, null);
+        addJavaType(String.class, null, true, true, null, null, null, null);
+        addJavaType(Enum.class, null, true, true, null, null, null, null);
+        addJavaType(StringBuffer.class, null, true, true, null, null, null, "dn.stringbuffer-string");
+        addJavaType(StringBuilder.class, null, true, true, null, null, null, "dn.stringbuilder-string");
+        addJavaType(Class.class, null, true, true, null, null, null, "dn.class-string");
+
+        if (ClassUtils.isClassPresent("java.awt.Color", clr)) // Not present in some JDKs
+        {
+            // java.awt
+            addJavaType(java.awt.image.BufferedImage.class, null, true, false, null, null, null, "dn.bufferedimage-bytearray");
+            addJavaType(java.awt.Color.class, null, true, true, null, null, null, "dn.color-string");
+        }
+
+        addJavaType(java.math.BigDecimal.class, null, true, true, null, null, null, null);
+        addJavaType(java.math.BigInteger.class, null, true, true, null, null, null, null);
+
+        // java.net
+        addJavaType(java.net.URL.class, null, true, true, null, null, null, "dn.url-string");
+        addJavaType(java.net.URI.class, null, true, true, null, null, null, "dn.uri-string");
+
+        // date/time/java.time
+        addJavaType(java.sql.Date.class, null, true, true, org.datanucleus.store.types.wrappers.SqlDate.class, null, null, null);
+        addJavaType(java.sql.Time.class, null, true, true, org.datanucleus.store.types.wrappers.SqlTime.class, null, null, null);
+        addJavaType(java.sql.Timestamp.class, null, true, true, org.datanucleus.store.types.wrappers.SqlTimestamp.class, null, null, null);
+        addJavaType(java.util.Date.class, null, true, true, org.datanucleus.store.types.wrappers.Date.class, null, null, null);
+        addJavaType(Calendar.class, null, true, true, org.datanucleus.store.types.wrappers.GregorianCalendar.class, null, null, "dn.calendar-string");
+        addJavaType(GregorianCalendar.class, null, true, true, org.datanucleus.store.types.wrappers.GregorianCalendar.class, null, null, "dn.calendar-string");
+
+        addJavaType(java.time.LocalDate.class, null, true, true, null, null, null, "dn.localdate-sqldate");
+        addJavaType(java.time.LocalDateTime.class, null, true, true, null, null, null, "dn.localdatetime-timestamp");
+        addJavaType(java.time.LocalTime.class, null, true, true, null, null, null, "dn.localtime-sqltime");
+        addJavaType(java.time.OffsetTime.class, null, true, true, null, null, null, "dn.offsettime-sqltime");
+        addJavaType(java.time.OffsetDateTime.class, null, true, true, null, null, null, "dn.offsetdatetime-timestamp");
+        addJavaType(java.time.Duration.class, null, true, true, null, null, null, "dn.duration-long");
+        addJavaType(java.time.Instant.class, null, true, true, null, null, null, "dn.instant-timestamp");
+        addJavaType(java.time.Period.class, null, true, true, null, null, null, "dn.period-string");
+        addJavaType(java.time.Year.class, null, true, true, null, null, null, "dn.year-integer");
+        addJavaType(java.time.YearMonth.class, null, true, true, null, null, null, "dn.yearmonth-string");
+        addJavaType(java.time.MonthDay.class, null, true, true, null, null, null, "dn.monthday-string");
+        addJavaType(java.time.ZoneId.class, null, true, true, null, null, null, "dn.zoneid-string");
+        addJavaType(java.time.ZoneOffset.class, null, true, true, null, null, null, "dn.zoneoffset-string");
+        addJavaType(java.time.ZonedDateTime.class, null, true, true, null, null, null, "dn.zoneddatetime-timestamp");
+
+        // java.util
+        addJavaType(Locale.class, null, true, true, null, null, null, "dn.locale-string");
+        addJavaType(Currency.class, null, true, true, null, null, null, "dn.currency-string");
+        addJavaType(UUID.class, null, true, true, null, null, null, "dn.uuid-string");
+        addJavaType(TimeZone.class, null, true, true, null, null, null, "dn.timezone-string");
+
+        addJavaType(ArrayList.class, null, false, false, org.datanucleus.store.types.wrappers.ArrayList.class, 
+            org.datanucleus.store.types.wrappers.backed.ArrayList.class, ArrayListHandler.class, null);
+
+        String arrayListInnerType = "java.util.Arrays$ArrayList";
+        Class arrayListInnerTypeCls = clr.classForName(arrayListInnerType);
+        addJavaType(arrayListInnerTypeCls, null, false, false, org.datanucleus.store.types.wrappers.List.class, org.datanucleus.store.types.wrappers.backed.List.class,
+            org.datanucleus.store.types.containers.ArrayListHandler.class, null);
+
+        addJavaType(BitSet.class, null, true, true, org.datanucleus.store.types.wrappers.BitSet.class, null, null, "dn.bitset-string");
+        addJavaType(Collection.class, null, false, false, org.datanucleus.store.types.wrappers.Collection.class, org.datanucleus.store.types.wrappers.backed.Collection.class, 
+            JDKCollectionHandler.class, null);
+        addJavaType(HashMap.class, null, false, false, org.datanucleus.store.types.wrappers.HashMap.class, org.datanucleus.store.types.wrappers.backed.HashMap.class,
+            HashMapHandler.class, null);
+        addJavaType(HashSet.class, null, false, false, org.datanucleus.store.types.wrappers.HashSet.class, org.datanucleus.store.types.wrappers.backed.HashSet.class,
+            HashSetHandler.class, null);
+        addJavaType(Hashtable.class, null, false, false, org.datanucleus.store.types.wrappers.Hashtable.class, org.datanucleus.store.types.wrappers.backed.Hashtable.class,
+            HashtableHandler.class, null);
+        addJavaType(LinkedHashMap.class, null, false, false, org.datanucleus.store.types.wrappers.LinkedHashMap.class, org.datanucleus.store.types.wrappers.backed.LinkedHashMap.class,
+            LinkedHashMapHandler.class, null);
+        addJavaType(LinkedHashSet.class, null, false, false, org.datanucleus.store.types.wrappers.LinkedHashSet.class, org.datanucleus.store.types.wrappers.backed.LinkedHashSet.class,
+            LinkedHashSetHandler.class, null);
+        addJavaType(LinkedList.class, null, false, false, org.datanucleus.store.types.wrappers.LinkedList.class, org.datanucleus.store.types.wrappers.backed.LinkedList.class,
+            LinkedListHandler.class, null);
+        addJavaType(List.class, null, false, false, org.datanucleus.store.types.wrappers.List.class, org.datanucleus.store.types.wrappers.backed.List.class,
+            ArrayListHandler.class, null);
+        addJavaType(Map.class, null, false, false, org.datanucleus.store.types.wrappers.Map.class, org.datanucleus.store.types.wrappers.backed.Map.class,
+            HashMapHandler.class, null);
+        addJavaType(PriorityQueue.class, null, false, false, org.datanucleus.store.types.wrappers.PriorityQueue.class, org.datanucleus.store.types.wrappers.backed.PriorityQueue.class,
+            PriorityQueueHandler.class, null);
+        addJavaType(Properties.class, null, false, false, org.datanucleus.store.types.wrappers.Properties.class, org.datanucleus.store.types.wrappers.backed.Properties.class,
+            PropertiesHandler.class, null);
+        addJavaType(Queue.class, null, false, false, org.datanucleus.store.types.wrappers.Queue.class, org.datanucleus.store.types.wrappers.backed.Queue.class,
+            PriorityQueueHandler.class, null);
+        addJavaType(Set.class, null, false, false, org.datanucleus.store.types.wrappers.Set.class, org.datanucleus.store.types.wrappers.backed.Set.class,
+            HashSetHandler.class, null);
+        addJavaType(SortedMap.class, null, false, false, org.datanucleus.store.types.wrappers.SortedMap.class, org.datanucleus.store.types.wrappers.backed.SortedMap.class,
+            TreeMapHandler.class, null);
+        addJavaType(SortedSet.class, null, false, false, org.datanucleus.store.types.wrappers.SortedSet.class, org.datanucleus.store.types.wrappers.backed.SortedSet.class,
+            TreeSetHandler.class, null);
+        addJavaType(Stack.class, null, false, false, org.datanucleus.store.types.wrappers.Stack.class, org.datanucleus.store.types.wrappers.backed.Stack.class,
+            StackHandler.class, null);
+        addJavaType(TreeMap.class, null, false, false, org.datanucleus.store.types.wrappers.TreeMap.class, org.datanucleus.store.types.wrappers.backed.TreeMap.class,
+            TreeMapHandler.class, null);
+        addJavaType(TreeSet.class, null, false, false, org.datanucleus.store.types.wrappers.TreeSet.class, org.datanucleus.store.types.wrappers.backed.TreeSet.class,
+            TreeSetHandler.class, null);
+        addJavaType(Vector.class, null, false, false, org.datanucleus.store.types.wrappers.Vector.class, org.datanucleus.store.types.wrappers.backed.Vector.class,
+            VectorHandler.class, null);
+        addJavaType(Optional.class, null, false, false, null, null, OptionalHandler.class, null);
+
+        // arrays
+        addJavaType(boolean[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(byte[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(char[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(double[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(float[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(int[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(long[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(short[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Boolean[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Byte[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Character[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Double[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Float[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Integer[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Long[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Short[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Number[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(String[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(BigDecimal[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(BigInteger[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(java.util.Date[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(java.util.Locale[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Enum[].class, null, true, false, null, null, ArrayHandler.class, null);
+        addJavaType(Object[].class, null, true, false, null, null, ArrayHandler.class, null);
+
+        // Add on any plugin mechanism types
+        ConfigurationElement[] elems = mgr.getConfigurationElementsForExtension("org.datanucleus.java_type", null, null);
+        if (elems != null)
+        {
+            for (int i=0; i<elems.length; i++)
+            {
+                String javaName = elems[i].getAttribute("name").trim();
+                String genericTypeName = elems[i].getAttribute("generic-type");
+                String embeddedString = elems[i].getAttribute("embedded");
+                String dfgString = elems[i].getAttribute("dfg");
+                String priorityString = elems[i].getAttribute("priority");
+                String wrapperType = elems[i].getAttribute("wrapper-type");
+                String wrapperTypeBacked = elems[i].getAttribute("wrapper-type-backed");
+                String typeConverterName = elems[i].getAttribute("converter-name");
+                String containerHandlerType = elems[i].getAttribute("container-handler");
+
+                boolean embedded = false;
+                if (embeddedString != null && embeddedString.equalsIgnoreCase("true"))
+                {
+                    embedded = true;
+                }
+                boolean dfg = false;
+                if (dfgString != null && dfgString.equalsIgnoreCase("true"))
+                {
+                    dfg = true;
+                }
+                int priority = 0;
+                if (priorityString != null && !StringUtils.isWhitespace(priorityString))
+                {
+                    try {
+                        priority = Integer.parseInt(priorityString.trim());
+                    } catch (Exception e) {
+                        // if cannot parse, silently fallback
+                        priority = 0;
+                    }
+                }
+                if (!StringUtils.isWhitespace(wrapperType))
+                {
+                    wrapperType = wrapperType.trim();
+                }
+                else
+                {
+                    wrapperType = null;
+                }
+                if (!StringUtils.isWhitespace(wrapperTypeBacked))
+                {
+                    wrapperTypeBacked = wrapperTypeBacked.trim();
+                }
+                else
+                {
+                    wrapperTypeBacked = null;
+                }
+                if (!StringUtils.isWhitespace(containerHandlerType))
+                {
+                    containerHandlerType = containerHandlerType.trim();
+                }
+                else
+                {
+                    containerHandlerType = null;
+                }
+
+                try
+                {
+                    Class cls = clr.classForName(javaName);
+                    Class genericType = null;
+                    String javaTypeName = cls.getName();
+                    if (!StringUtils.isWhitespace(genericTypeName))
+                    {
+                        genericType = clr.classForName(genericTypeName);
+                        javaTypeName += "<" + genericTypeName + ">";
+                    }
+
+                    
+                    boolean doRegister = !javaTypes.containsKey(javaTypeName);
+                    if(!doRegister) {
+                        // check whether new priority wins over already registered priority
+                        final int priorityToBeat = 
+                                Optional.ofNullable(javaTypePriorities.get(javaTypeName)).orElse(0);
+                        doRegister = priority > priorityToBeat;
+                    }
+                    
+                    if (doRegister)
+                    {
+                        // Only add first entry for a java type (ordered by the "priority" flag)
+
+                        Class wrapperClass = loadClass(mgr, elems, i, wrapperType, "016005");
+                        Class wrapperClassBacked = loadClass(mgr, elems, i, wrapperTypeBacked, "016005");
+                        Class containerHandlerClass = loadClass(mgr, elems, i, containerHandlerType, "016009");
+
+                        String typeName = cls.getName();
+                        if (genericType != null)
+                        {
+                            // "Collection<String>"
+                            typeName += "<" + genericType.getName() + ">";
+                        }
+                        javaTypes.put(typeName, new JavaType(cls, genericType, embedded, dfg, wrapperClass, wrapperClassBacked, containerHandlerClass, typeConverterName));
+
+                        // keep track of registered priority values, 
+                        // as an optimization, save heap usage, don't collect priority==0 
+                        if(priority>0) {
+                            javaTypePriorities.put(javaTypeName, priority);
+                        }
+                    }
+                }
+                catch (ClassNotResolvedException cnre)
+                {
+                    NucleusLogger.PERSISTENCE.debug("Not enabling java type support for " + javaName + " : java type not present in CLASSPATH");
+                }
+                catch (Exception e)
+                {
+                    NucleusLogger.PERSISTENCE.debug("Not enabling java type support for " + javaName + " : " + e.getMessage());
+                }
+            }
+        }
+        if (NucleusLogger.PERSISTENCE.isDebugEnabled())
+        {
+            List<String> typesList = new ArrayList<String>(javaTypes.keySet());
+            Collections.sort(typesList, ALPHABETICAL_ORDER_STRING);
+            NucleusLogger.PERSISTENCE.debug(Localiser.msg("016006", StringUtils.collectionToString(typesList)));
+        }
+    }
+
+    private void addJavaType(Class cls, Class genericType, boolean embedded, boolean dfg, Class wrapperType, Class wrapperTypeBacked, Class containerHandlerType, String typeConverterName)
+    {
+        String typeName = cls.getName();
+        if (genericType != null)
+        {
+            // "Collection<String>"
+            typeName += "<" + genericType.getName() + ">";
+        }
+        javaTypes.put(typeName, new JavaType(cls, genericType, embedded, dfg, wrapperType, wrapperTypeBacked, containerHandlerType, typeConverterName));
+    }
+
+    private Class loadClass(PluginManager mgr, ConfigurationElement[] elems, int i, String className, String messageKey)
+    {
+        Class result = null;
+        if (className != null)
+        {
+            try
+            {
+                result = mgr.loadClass(elems[i].getExtension().getPlugin().getSymbolicName(), className);
+            }
+            catch (NucleusException jpe)
+            {
+                // Impossible to load the class implementation from this plugin
+                NucleusLogger.PERSISTENCE.error(Localiser.msg(messageKey, className));
+                throw new NucleusException(Localiser.msg(messageKey, className));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Method to load the java type that are currently registered in the PluginManager.
+     * @param mgr the PluginManager
+     */
+    private void loadTypeConverters(PluginManager mgr)
+    {
+        if (NucleusLogger.PERSISTENCE.isDebugEnabled())
+        {
+            NucleusLogger.PERSISTENCE.debug(Localiser.msg("016007"));
+        }
+
+        // Load up built-in converters
+        registerConverter("dn.boolean-yn", new org.datanucleus.store.types.converters.BooleanYNConverter(), Boolean.class, Character.class, false, null);
+        registerConverter("dn.boolean-integer", new org.datanucleus.store.types.converters.BooleanIntegerConverter(), Boolean.class, Integer.class, false, null);
+        registerConverter("dn.character-string", new org.datanucleus.store.types.converters.CharacterStringConverter(), Character.class, String.class, false, null);
+        registerConverter("dn.bigdecimal-string", new org.datanucleus.store.types.converters.BigDecimalStringConverter(), BigDecimal.class, String.class, false, null);
+        registerConverter("dn.bigdecimal-double", new org.datanucleus.store.types.converters.BigDecimalDoubleConverter(), BigDecimal.class, Double.class, false, null);
+        registerConverter("dn.biginteger-string", new org.datanucleus.store.types.converters.BigIntegerStringConverter(), BigInteger.class, String.class, false, null);
+        registerConverter("dn.biginteger-long", new org.datanucleus.store.types.converters.BigIntegerLongConverter(), BigInteger.class, Long.class, false, null);
+        registerConverter("dn.bitset-string", new org.datanucleus.store.types.converters.BitSetStringConverter(), BitSet.class, String.class, false, null);
+
+        if (ClassUtils.isClassPresent("java.awt.Color", clr)) // Not present in some JDKs
+        {
+            // java.awt
+            registerConverter("dn.color-string", new org.datanucleus.store.types.converters.ColorStringConverter(), java.awt.Color.class, String.class, false, null);
+            registerConverter("dn.color-components", new org.datanucleus.store.types.converters.ColorComponentsConverter(), java.awt.Color.class, int[].class, false, null);
+            registerConverter("dn.bufferedimage-bytearray", new org.datanucleus.store.types.converters.BufferedImageByteArrayConverter(), java.awt.image.BufferedImage.class, byte[].class, false, null);
+            registerConverter("dn.bufferedimage-bytebuffer", new org.datanucleus.store.types.converters.BufferedImageByteBufferConverter(), java.awt.image.BufferedImage.class, ByteBuffer.class, false, null);
+        }
+
+        registerConverter("dn.class-string", new org.datanucleus.store.types.converters.ClassStringConverter(), Class.class, String.class, false, null);
+        registerConverter("dn.integer-string", new org.datanucleus.store.types.converters.IntegerStringConverter(), Integer.class, String.class, false, null);
+        registerConverter("dn.long-string", new org.datanucleus.store.types.converters.LongStringConverter(), Long.class, String.class, false, null);
+        registerConverter("dn.currency-string", new org.datanucleus.store.types.converters.CurrencyStringConverter(), Currency.class, String.class, false, null);
+        registerConverter("dn.locale-string", new org.datanucleus.store.types.converters.LocaleStringConverter(), Locale.class, String.class, false, null);
+        registerConverter("dn.stringbuffer-string", new org.datanucleus.store.types.converters.StringBufferStringConverter(), StringBuffer.class, String.class, false, null);
+        registerConverter("dn.stringbuilder-string", new org.datanucleus.store.types.converters.StringBuilderStringConverter(), StringBuilder.class, String.class, false, null);
+        registerConverter("dn.timezone-string", new org.datanucleus.store.types.converters.TimeZoneStringConverter(), TimeZone.class, String.class, false, null);
+        registerConverter("dn.uri-string", new org.datanucleus.store.types.converters.URIStringConverter(), java.net.URI.class, String.class, false, null);
+        registerConverter("dn.url-string", new org.datanucleus.store.types.converters.URLStringConverter(), java.net.URL.class, String.class, false, null);
+        registerConverter("dn.uuid-string", new org.datanucleus.store.types.converters.UUIDStringConverter(), UUID.class, String.class, false, null);
+
+        // Date/Time
+        registerConverter("dn.date-long", new org.datanucleus.store.types.converters.DateLongConverter(), java.util.Date.class, Long.class, false, null);
+        registerConverter("dn.date-string", new org.datanucleus.store.types.converters.DateStringConverter(), java.util.Date.class, String.class, false, null);
+
+        registerConverter("dn.sqldate-long", new org.datanucleus.store.types.converters.SqlDateLongConverter(), java.sql.Date.class, Long.class, false, null);
+        registerConverter("dn.sqldate-string", new org.datanucleus.store.types.converters.SqlDateStringConverter(), java.sql.Date.class, String.class, false, null);
+        registerConverter("dn.sqldate-date", new org.datanucleus.store.types.converters.SqlDateStringConverter(), java.sql.Date.class, java.util.Date.class, false, null);
+        registerConverter("dn.sqltime-long", new org.datanucleus.store.types.converters.SqlTimeStringConverter(), java.sql.Time.class, Long.class, false, null);
+        registerConverter("dn.sqltime-string", new org.datanucleus.store.types.converters.SqlTimeStringConverter(), java.sql.Time.class, String.class, false, null);
+        registerConverter("dn.sqltime-date", new org.datanucleus.store.types.converters.SqlTimeStringConverter(), java.sql.Time.class, java.util.Date.class, false, null);
+        registerConverter("dn.sqltimestamp-long", new org.datanucleus.store.types.converters.SqlTimestampStringConverter(), java.sql.Timestamp.class, Long.class, false, null);
+        registerConverter("dn.sqltimestamp-date", new org.datanucleus.store.types.converters.SqlTimestampStringConverter(), java.sql.Timestamp.class, java.util.Date.class, false, null);
+        registerConverter("dn.sqltimestamp-string", new org.datanucleus.store.types.converters.SqlTimestampStringConverter(), java.sql.Timestamp.class, String.class, false, null);
+
+        registerConverter("dn.calendar-string", new org.datanucleus.store.types.converters.CalendarStringConverter(), Calendar.class, String.class, false, null);
+        registerConverter("dn.calendar-date", new org.datanucleus.store.types.converters.CalendarDateConverter(), Calendar.class, java.util.Date.class, false, null);
+        registerConverter("dn.calendar-timestamp", new org.datanucleus.store.types.converters.CalendarTimestampConverter(), Calendar.class, java.sql.Timestamp.class, false, null);
+        registerConverter("dn.calendar-components", new org.datanucleus.store.types.converters.CalendarComponentsConverter(), Calendar.class, Object[].class, false, null);
+
+        // Serializable
+        registerConverter("dn.serializable-string", new org.datanucleus.store.types.converters.SerializableStringConverter(), java.io.Serializable.class, String.class, false, null);
+        registerConverter("dn.serializable-bytearray", new org.datanucleus.store.types.converters.SerializableByteArrayConverter(), java.io.Serializable.class, byte[].class, false, null);
+        registerConverter("dn.serializable-bytebuffer", new org.datanucleus.store.types.converters.SerializableByteBufferConverter(), java.io.Serializable.class, ByteBuffer.class, false, null);
+
+        // Arrays
+        registerConverter("dn.bytearray-bytebuffer", new org.datanucleus.store.types.converters.ByteArrayByteBufferConverter(), byte[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.booleanarray-bytebuffer", new org.datanucleus.store.types.converters.BooleanArrayByteBufferConverter(), boolean[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.chararray-bytebuffer", new org.datanucleus.store.types.converters.CharArrayByteBufferConverter(), char[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.doublearray-bytebuffer", new org.datanucleus.store.types.converters.DoubleArrayByteBufferConverter(), double[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.floatarray-bytebuffer", new org.datanucleus.store.types.converters.FloatArrayByteBufferConverter(), float[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.intarray-bytebuffer", new org.datanucleus.store.types.converters.IntArrayByteBufferConverter(), int[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.longarray-bytebuffer", new org.datanucleus.store.types.converters.LongArrayByteBufferConverter(), long[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.shortarray-bytebuffer", new org.datanucleus.store.types.converters.ShortArrayByteBufferConverter(), short[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.bigintegerarray-bytebuffer", new org.datanucleus.store.types.converters.BigIntegerArrayByteBufferConverter(), BigInteger[].class, ByteBuffer.class, false, null);
+        registerConverter("dn.bigdecimalarray-bytebuffer", new org.datanucleus.store.types.converters.BigDecimalArrayByteBufferConverter(), BigDecimal[].class, ByteBuffer.class, false, null);
+
+        // java.time
+        registerConverter("dn.localdate-string", new org.datanucleus.store.types.converters.LocalDateStringConverter(), LocalDate.class, String.class, false, null);
+        registerConverter("dn.localdate-sqldate", new org.datanucleus.store.types.converters.LocalDateSqlDateConverter(), LocalDate.class, java.sql.Date.class, false, null);
+        registerConverter("dn.localdate-date", new org.datanucleus.store.types.converters.LocalDateDateConverter(), LocalDate.class, java.util.Date.class, false, null);
+
+        registerConverter("dn.localtime-string", new org.datanucleus.store.types.converters.LocalTimeStringConverter(), LocalTime.class, String.class, false, null);
+        registerConverter("dn.localtime-sqltime", new org.datanucleus.store.types.converters.LocalTimeSqlTimeConverter(), LocalTime.class, java.sql.Time.class, false, null);
+        registerConverter("dn.localtime-date", new org.datanucleus.store.types.converters.LocalTimeDateConverter(), LocalTime.class, java.util.Date.class, false, null);
+        registerConverter("dn.localtime-long", new org.datanucleus.store.types.converters.LocalTimeLongConverter(), LocalTime.class, Long.class, false, null);
+
+        registerConverter("dn.localdatetime-string", new org.datanucleus.store.types.converters.LocalDateTimeStringConverter(), LocalDateTime.class, String.class, false, null);
+        registerConverter("dn.localdatetime-timestamp", new org.datanucleus.store.types.converters.LocalDateTimeTimestampConverter(), LocalDateTime.class, java.sql.Timestamp.class, false, null);
+        registerConverter("dn.localdatetime-date", new org.datanucleus.store.types.converters.LocalDateTimeDateConverter(), LocalDateTime.class, java.util.Date.class, false, null);
+
+        registerConverter("dn.offsettime-string", new org.datanucleus.store.types.converters.OffsetTimeStringConverter(), OffsetTime.class, String.class, false, null);
+        registerConverter("dn.offsettime-long", new org.datanucleus.store.types.converters.OffsetTimeLongConverter(), OffsetTime.class, Long.class, false, null);
+        registerConverter("dn.offsettime-sqltime", new org.datanucleus.store.types.converters.OffsetTimeSqlTimeConverter(), OffsetTime.class, java.sql.Time.class, false, null);
+
+        registerConverter("dn.offsetdatetime-string", new org.datanucleus.store.types.converters.OffsetDateTimeStringConverter(), OffsetDateTime.class, String.class, false, null);
+        registerConverter("dn.offsetdatetime-timestamp", new org.datanucleus.store.types.converters.OffsetDateTimeTimestampConverter(), OffsetDateTime.class, java.sql.Timestamp.class, false, null);
+        registerConverter("dn.offsetdatetime-date", new org.datanucleus.store.types.converters.OffsetDateTimeDateConverter(), OffsetDateTime.class, java.util.Date.class, false, null);
+
+        registerConverter("dn.duration-string", new org.datanucleus.store.types.converters.DurationStringConverter(), Duration.class, String.class, false, null);
+        registerConverter("dn.duration-long", new org.datanucleus.store.types.converters.DurationLongConverter(), Duration.class, Long.class, false, null);
+        registerConverter("dn.duration-double", new org.datanucleus.store.types.converters.DurationDoubleConverter(), Duration.class, Double.class, false, null);
+
+        registerConverter("dn.period-string", new org.datanucleus.store.types.converters.PeriodStringConverter(), Period.class, String.class, false, null);
+        registerConverter("dn.period-components", new org.datanucleus.store.types.converters.PeriodComponentsConverter(), Period.class, int[].class, false, null);
+
+        registerConverter("dn.instant-timestamp", new org.datanucleus.store.types.converters.InstantTimestampConverter(), Instant.class, java.sql.Timestamp.class, false, null);
+        registerConverter("dn.instant-date", new org.datanucleus.store.types.converters.InstantDateConverter(), Instant.class, java.util.Date.class, false, null);
+        registerConverter("dn.instant-string", new org.datanucleus.store.types.converters.InstantStringConverter(), Instant.class, String.class, false, null);
+        registerConverter("dn.instant-long", new org.datanucleus.store.types.converters.InstantLongConverter(), Instant.class, Long.class, false, null);
+
+        registerConverter("dn.year-string", new org.datanucleus.store.types.converters.YearStringConverter(), Year.class, String.class, false, null);
+        registerConverter("dn.year-integer", new org.datanucleus.store.types.converters.YearIntegerConverter(), Year.class, Integer.class, false, null);
+
+        registerConverter("dn.yearmonth-string", new org.datanucleus.store.types.converters.YearMonthStringConverter(), YearMonth.class, String.class, false, null);
+        registerConverter("dn.yearmonth-components", new org.datanucleus.store.types.converters.YearMonthComponentsConverter(), YearMonth.class, int[].class, false, null);
+        registerConverter("dn.yearmonth-sqldate", new org.datanucleus.store.types.converters.YearMonthSqlDateConverter(), YearMonth.class, java.sql.Date.class, false, null);
+        registerConverter("dn.yearmonth-date", new org.datanucleus.store.types.converters.YearMonthDateConverter(), YearMonth.class, java.util.Date.class, false, null);
+
+        registerConverter("dn.monthday-string", new org.datanucleus.store.types.converters.MonthDayStringConverter(), MonthDay.class, String.class, false, null);
+        registerConverter("dn.monthday-components", new org.datanucleus.store.types.converters.MonthDayComponentsConverter(), MonthDay.class, int[].class, false, null);
+        registerConverter("dn.monthday-sqldate", new org.datanucleus.store.types.converters.MonthDaySqlDateConverter(), MonthDay.class, java.sql.Date.class, false, null);
+        registerConverter("dn.monthday-date", new org.datanucleus.store.types.converters.MonthDayDateConverter(), MonthDay.class, java.util.Date.class, false, null);
+
+        registerConverter("dn.zoneid-string", new org.datanucleus.store.types.converters.ZoneIdStringConverter(), ZoneId.class, String.class, false, null);
+        registerConverter("dn.zoneoffset-string", new org.datanucleus.store.types.converters.ZoneOffsetStringConverter(), ZoneOffset.class, String.class, false, null);
+        registerConverter("dn.zoneddatetime-string", new org.datanucleus.store.types.converters.ZonedDateTimeStringConverter(), ZonedDateTime.class, String.class, false, null);
+        registerConverter("dn.zoneddatetime-timestamp", new org.datanucleus.store.types.converters.ZonedDateTimeTimestampConverter(), ZonedDateTime.class, java.sql.Timestamp.class, false, null);
+
+        // Add on any plugin mechanism types
+        ConfigurationElement[] elems = mgr.getConfigurationElementsForExtension("org.datanucleus.type_converter", null, null);
+        if (elems != null)
+        {
+            for (int i=0; i<elems.length; i++)
+            {
+                String name = elems[i].getAttribute("name").trim();
+                String memberTypeName = elems[i].getAttribute("member-type").trim();
+                String datastoreTypeName = elems[i].getAttribute("datastore-type").trim();
+                String converterClsName = elems[i].getAttribute("converter-class").trim();
+                Class memberType = null;
+                try
+                {
+                    // Use plugin manager to instantiate the converter in case its in separate plugin
+                    TypeConverter conv = (TypeConverter) mgr.createExecutableExtension("org.datanucleus.type_converter", "name", name, "converter-class", null, null);
+                    memberType = clr.classForName(memberTypeName);
+                    Class datastoreType = clr.classForName(datastoreTypeName);
+                    registerConverter(name, conv, memberType, datastoreType, false, null);
+                }
+                catch (Exception e)
+                {
+                    if (NucleusLogger.PERSISTENCE.isDebugEnabled())
+                    {
+                        if (memberType != null)
+                        {
+                            NucleusLogger.PERSISTENCE.debug("TypeConverter for " + memberTypeName + "<->" +
+                                datastoreTypeName + " using " + converterClsName + " not instantiable (missing dependencies?) so ignoring");
+                        }
+                        else
+                        {
+                            NucleusLogger.PERSISTENCE.debug("TypeConverter for " + memberTypeName + "<->" +
+                                datastoreTypeName + " ignored since java type not present in CLASSPATH");
+                        }
+                    }
+                }
+            }
+        }
+
+        if (NucleusLogger.PERSISTENCE.isDebugEnabled())
+        {
+            NucleusLogger.PERSISTENCE.debug(Localiser.msg("016008"));
+            if (typeConverterMap != null)
+            {
+                List<Class> typesList = new ArrayList<Class>(typeConverterMap.keySet());
+                Collections.sort(typesList, ALPHABETICAL_ORDER);
+                for (Class javaType : typesList)
+                {
+                    Set<Class> datastoreTypes = typeConverterMap.get(javaType).keySet();
+                    StringBuilder str = new StringBuilder();
+                    for (Class datastoreCls : datastoreTypes)
+                    {
+                        if (str.length() > 0)
+                        {
+                            str.append(',');
+                        }
+                        str.append(StringUtils.getNameOfClass(datastoreCls));
+                    }
+                    NucleusLogger.PERSISTENCE.debug("TypeConverter(s) available for " + StringUtils.getNameOfClass(javaType) + " to : " + str.toString());
+                }
+            }
+        }
+    }
+
+    private static Comparator<Class> ALPHABETICAL_ORDER = new Comparator<Class>() 
+    {
+        public int compare(Class cls1, Class cls2) 
+        {
+            int res = String.CASE_INSENSITIVE_ORDER.compare(cls1.getName(), cls2.getName());
+            if (res == 0) 
+            {
+                res = cls1.getName().compareTo(cls2.getName());
+            }
+            return res;
+        }
+    };
+
+    private static Comparator<String> ALPHABETICAL_ORDER_STRING = new Comparator<String>() 
+    {
+        public int compare(String cls1, String cls2) 
+        {
+            int res = String.CASE_INSENSITIVE_ORDER.compare(cls1, cls2);
+            if (res == 0) 
+            {
+                res = cls1.compareTo(cls2);
+            }
+            return res;
+        }
+    };
+}
\ No newline at end of file
diff --git a/persistence/jdo/datanucleus-5/src/main/resources/plugin.xml b/persistence/jdo/datanucleus-5/src/main/resources/plugin.xml
index bb5b2e9..34bc0eb 100644
--- a/persistence/jdo/datanucleus-5/src/main/resources/plugin.xml
+++ b/persistence/jdo/datanucleus-5/src/main/resources/plugin.xml
@@ -33,11 +33,6 @@
             java-type="org.apache.isis.applib.value.Clob"
             mapping-class="org.apache.isis.persistence.jdo.datanucleus5.datanucleus.valuetypes.applib.IsisClobMapping"/>
         
-        <!-- TODO[2249] deprecated
-        <mapping
-            java-type="org.apache.isis.applib.value.Money"
-            mapping-class="org.apache.isis.persistence.jdo.datanucleus5.datanucleus.valuetypes.IsisMoneyMapping"/>
-            -->
     </extension>
 
 
@@ -64,6 +59,21 @@
     <!--
     http://www.datanucleus.org/extensions/java_types.html
     -->
+    
+    <extension point="org.datanucleus.java_type">
+        <java-type
+                name="java.time.ZonedDateTime"
+                dfg="true" priority="10"
+                converter-name="iso.zoneddatetime-string" />
+    </extension>
+    
+    <extension point="org.datanucleus.java_type">
+        <java-type
+                name="java.time.OffsetTime"
+                dfg="true" priority="10"
+                converter-name="iso.offsettime-string" />
+    </extension>
+    
     <extension point="org.datanucleus.java_type">
         <java-type
                 name="org.apache.isis.applib.value.Password"
@@ -123,6 +133,32 @@
     <!--
     http://www.datanucleus.org/extensions/type_converter.html
     -->
+    
+    <extension point="org.datanucleus.type_converter">
+        <type-converter
+                name="iso.zoneddatetime-string"
+                member-type="java.time.ZonedDateTime"
+                datastore-type="java.lang.String"
+                converter-class="org.apache.isis.persistence.jdo.datanucleus5.datanucleus.typeconverters.applib.IsoZonedDateTimeConverter"/>
+    </extension>
+    
+    <extension point="org.datanucleus.type_converter">
+        <type-converter
+                name="iso.offsettime-string"
+                member-type="java.time.OffsetTime"
+                datastore-type="java.lang.String"
+                converter-class="org.apache.isis.persistence.jdo.datanucleus5.datanucleus.typeconverters.applib.IsoOffsetTimeConverter"/>
+    </extension>
+    
+    <extension point="org.datanucleus.type_converter">
+        <type-converter
+                name="isis.password-string"
+                member-type="org.apache.isis.applib.value.Password"
+                datastore-type="java.lang.String"
+                converter-class="org.apache.isis.persistence.jdo.datanucleus5.datanucleus.typeconverters.applib.IsisPasswordConverter"/>
+    </extension>
+    
+    
     <extension point="org.datanucleus.type_converter">
         <type-converter
                 name="isis.password-string"
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/isis/WicketViewerSettings.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/isis/WicketViewerSettings.java
index 6038027..e27cc82 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/isis/WicketViewerSettings.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/isis/WicketViewerSettings.java
@@ -50,14 +50,6 @@ public interface WicketViewerSettings extends Serializable {
     String getDateTimePattern();
 
     /**
-     * As per {@link IsisConfiguration.Viewer.Wicket#setTimestampPattern(String)}
-     *
-     * @deprecated - seemingly unused
-     */
-    @Deprecated
-    String getTimestampPattern();
-
-    /**
      * As per {@link IsisConfiguration.Viewer.Wicket#setReplaceDisabledTagWithReadonlyTag(boolean)}
      */
     boolean isReplaceDisabledTagWithReadonlyTag();
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/services/WicketViewerSettingsDefault.java b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/services/WicketViewerSettingsDefault.java
index 919435c..bc9f53c 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/services/WicketViewerSettingsDefault.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/services/WicketViewerSettingsDefault.java
@@ -30,10 +30,9 @@ import org.springframework.stereotype.Service;
 import org.apache.isis.applib.annotation.OrderPrecedence;
 import org.apache.isis.applib.annotation.PromptStyle;
 import org.apache.isis.core.config.IsisConfiguration;
+import org.apache.isis.viewer.wicket.model.common.CommonContextUtils;
 import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
 
-import lombok.Getter;
-
 @Service
 @Named("isisWicketViewer.WicketViewerSettingsDefault")
 @Order(OrderPrecedence.MIDPOINT)
@@ -43,7 +42,7 @@ public class WicketViewerSettingsDefault implements WicketViewerSettings {
 
     private static final long serialVersionUID = 1L;
 
-    @Inject @Getter private transient IsisConfiguration configuration;
+    @Inject private transient IsisConfiguration configuration;
 
     @Override
     public int getMaxTitleLengthInStandaloneTables() {
@@ -55,13 +54,6 @@ public class WicketViewerSettingsDefault implements WicketViewerSettings {
         return getConfiguration().getViewer().getWicket().getMaxTitleLengthInParentedTables();
     }
 
-    /**
-     * Fallback for either {@link #getMaxTitleLengthInParentedTables()} and {@link #getMaxTitleLengthInParentedTables()}
-     */
-    private int getMaxTitleLengthInTables() {
-        return getConfiguration().getViewer().getWicket().getMaxTitleLengthInTables();
-    }
-
     @Override
     public String getDatePattern() {
         return getConfiguration().getViewer().getWicket().getDatePattern();
@@ -72,15 +64,6 @@ public class WicketViewerSettingsDefault implements WicketViewerSettings {
         return getConfiguration().getViewer().getWicket().getDateTimePattern();
     }
 
-    /**
-     * @deprecated - seemingly unused.
-     */
-    @Deprecated
-    @Override
-    public String getTimestampPattern() {
-        return getConfiguration().getViewer().getWicket().getTimestampPattern();
-    }
-
     @Override
     public boolean isReplaceDisabledTagWithReadonlyTag() {
         return getConfiguration().getViewer().getWicket().isReplaceDisabledTagWithReadonlyTag();
@@ -115,4 +98,13 @@ public class WicketViewerSettingsDefault implements WicketViewerSettings {
     public boolean isRedirectEvenIfSameObject() {
         return getConfiguration().getViewer().getWicket().isRedirectEvenIfSameObject();
     }
+
+    // -- HELPER
+    
+    private IsisConfiguration getConfiguration() {
+        if(configuration==null) {
+            configuration = CommonContextUtils.getCommonContext().getConfiguration();
+        }
+        return configuration;
+    }
 }