You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/03/31 14:02:08 UTC

[1/2] cayenne git commit: CAY-2210 Query cache: incorrect cache key for queries with custom value objects

Repository: cayenne
Updated Branches:
  refs/heads/master 4911ad11d -> 5e9f0e0f6


http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
index 61ba17b..74b8109 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
@@ -35,13 +35,14 @@ import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
 import org.apache.cayenne.access.translator.select.DefaultSelectTranslatorFactory;
 import org.apache.cayenne.access.translator.select.SelectTranslatorFactory;
 import org.apache.cayenne.access.types.BigDecimalType;
-import org.apache.cayenne.access.types.BigIntegerType;
+import org.apache.cayenne.access.types.BigIntegerValueType;
 import org.apache.cayenne.access.types.BooleanType;
 import org.apache.cayenne.access.types.ByteArrayType;
 import org.apache.cayenne.access.types.ByteType;
 import org.apache.cayenne.access.types.CalendarType;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.DateType;
+import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry;
 import org.apache.cayenne.access.types.DoubleType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -51,8 +52,9 @@ import org.apache.cayenne.access.types.LongType;
 import org.apache.cayenne.access.types.ShortType;
 import org.apache.cayenne.access.types.TimeType;
 import org.apache.cayenne.access.types.TimestampType;
-import org.apache.cayenne.access.types.UUIDType;
+import org.apache.cayenne.access.types.UUIDValueType;
 import org.apache.cayenne.access.types.UtilDateType;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.access.types.VoidType;
 import org.apache.cayenne.ashwood.AshwoodEntitySorter;
 import org.apache.cayenne.cache.MapQueryCacheProvider;
@@ -101,6 +103,7 @@ import org.apache.cayenne.event.EventManager;
 import org.apache.cayenne.log.CommonsJdbcEventLogger;
 import org.apache.cayenne.log.JdbcEventLogger;
 import org.apache.cayenne.map.EntitySorter;
+import org.apache.cayenne.access.types.ValueObjectType;
 import org.apache.cayenne.resource.ClassLoaderResourceLocator;
 import org.apache.cayenne.resource.ResourceLocator;
 import org.apache.cayenne.tx.DefaultTransactionFactory;
@@ -245,6 +248,16 @@ public class ServerModule implements Module {
     }
 
     /**
+     *
+     * @param binder DI binder passed to module during injector startup
+     * @return ListBuilder for user-contributed ValueObjectTypes
+     * @since 4.0
+     */
+    public static ListBuilder<ValueObjectType> contributeValueObjectTypes(Binder binder) {
+        return binder.bindList(ValueObjectType.class);
+    }
+
+    /**
      * Creates a new {@link ServerModule}.
      *
      * @since 4.0
@@ -300,16 +313,24 @@ public class ServerModule implements Module {
         contributeDomainListeners(binder);
 
         // configure extended types
-        contributeDefaultTypes(binder).add(new VoidType()).add(new BigDecimalType())
-                .add(new BigIntegerType()).add(new BooleanType()).add(new ByteArrayType(false, true))
-                .add(new ByteType(false)).add(new CharType(false, true)).add(new DateType()).add(new DoubleType())
-                .add(new FloatType()).add(new IntegerType()).add(new LongType()).add(new ShortType(false))
-                .add(new TimeType()).add(new TimestampType()).add(new UtilDateType())
-                .add(new CalendarType<GregorianCalendar>(GregorianCalendar.class))
-                .add(new CalendarType<Calendar>(Calendar.class)).add(new UUIDType());
+        contributeDefaultTypes(binder)
+                .add(new VoidType())
+                .add(new BigDecimalType())
+                .add(new BooleanType()).add(new ByteType(false)).add(new CharType(false, true))
+                .add(new DoubleType()).add(new FloatType()).add(new IntegerType()).add(new LongType()).add(new ShortType(false))
+                .add(new ByteArrayType(false, true))
+                .add(new DateType()).add(new TimeType()).add(new TimestampType())
+                // should be converted from ExtendedType to ValueType
+                .add(new UtilDateType()).add(new CalendarType<>(GregorianCalendar.class)).add(new CalendarType<>(Calendar.class));
         contributeUserTypes(binder);
         contributeTypeFactories(binder);
 
+        // Custom ValueObjects types contribution
+        contributeValueObjectTypes(binder)
+                .add(BigIntegerValueType.class)
+                .add(UUIDValueType.class);
+        binder.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class);
+
         // configure explicit configurations
         ListBuilder<String> locationsListBuilder = contributeProjectLocations(binder);
         for (String location : configurationLocations) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
index 01e244e..b163a88 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -32,6 +32,8 @@ import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeFactory;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.di.Inject;
@@ -78,8 +80,7 @@ public class JdbcAdapter implements DbAdapter {
 
 	/**
 	 * @since 3.1
-	 * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the
-	 * DataNode.
+	 * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the DataNode.
 	 */
 	@Inject
 	protected BatchTranslatorFactory batchQueryBuilderFactory;
@@ -94,7 +95,8 @@ public class JdbcAdapter implements DbAdapter {
 	                   @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
 	                   @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
 	                   @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-	                   @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
+	                   @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+					   @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
 
 		// init defaults
 		this.setSupportsBatchUpdates(false);
@@ -108,7 +110,7 @@ public class JdbcAdapter implements DbAdapter {
 		this.ejbqlTranslatorFactory = createEJBQLTranslatorFactory();
 		this.typesHandler = TypesHandler.getHandler(findResource("/types.xml"));
 		this.extendedTypes = new ExtendedTypeMap();
-		initExtendedTypes(defaultExtendedTypes, userExtendedTypes, extendedTypeFactories);
+		initExtendedTypes(defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, valueObjectTypeRegistry);
 	}
 
 	/**
@@ -159,7 +161,7 @@ public class JdbcAdapter implements DbAdapter {
 	}
 
 	/**
-	 * Called from {@link #initExtendedTypes(List, List, List)} to load
+	 * Called from {@link #initExtendedTypes(List, List, List, ValueObjectTypeRegistry)} to load
 	 * adapter-specific types into the ExtendedTypeMap right after the default
 	 * types are loaded, but before the DI overrides are. This method has
 	 * specific implementations in JdbcAdapter subclasses.
@@ -172,7 +174,8 @@ public class JdbcAdapter implements DbAdapter {
 	 * @since 3.1
 	 */
 	protected void initExtendedTypes(List<ExtendedType> defaultExtendedTypes, List<ExtendedType> userExtendedTypes,
-	                                 List<ExtendedTypeFactory> extendedTypeFactories) {
+	                                 List<ExtendedTypeFactory> extendedTypeFactories,
+									 ValueObjectTypeRegistry valueObjectTypeRegistry) {
 		for (ExtendedType type : defaultExtendedTypes) {
 			extendedTypes.registerType(type);
 		}
@@ -186,6 +189,7 @@ public class JdbcAdapter implements DbAdapter {
 		for (ExtendedTypeFactory typeFactory : extendedTypeFactories) {
 			extendedTypes.addFactory(typeFactory);
 		}
+		extendedTypes.addFactory(new ValueObjectTypeFactory(extendedTypes, valueObjectTypeRegistry));
 	}
 
 	/**
@@ -285,11 +289,7 @@ public class JdbcAdapter implements DbAdapter {
 	 */
 	@Override
 	public Collection<String> dropTableStatements(DbEntity table) {
-
-		StringBuilder buf = new StringBuilder("DROP TABLE ");
-		buf.append(quotingStrategy.quotedFullyQualifiedName(table));
-
-		return Collections.singleton(buf.toString());
+		return Collections.singleton("DROP TABLE " + quotingStrategy.quotedFullyQualifiedName(table));
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
index d1415bf..7ac6dec 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
@@ -36,6 +36,7 @@ import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -67,8 +68,9 @@ public class DB2Adapter extends JdbcAdapter {
             @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
             @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
             @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+            @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
         setSupportsGeneratedKeys(true);
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
index bca04a1..d2bb67e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
@@ -31,6 +31,7 @@ import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.access.types.ShortType;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -72,13 +73,15 @@ public class DerbyAdapter extends JdbcAdapter {
             @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
             @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
             @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
+            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+            @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
         super(
                 runtimeProperties,
                 defaultExtendedTypes,
                 userExtendedTypes,
                 extendedTypeFactories,
-                resourceLocator);
+                resourceLocator,
+                valueObjectTypeRegistry);
         setSupportsGeneratedKeys(true);
         setSupportsBatchUpdates(true);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
index ae754d5..a878be2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
@@ -28,6 +28,7 @@ import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -58,13 +59,15 @@ public class FirebirdAdapter extends JdbcAdapter {
             @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
             @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
             @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
+            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+            @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
         super(
                 runtimeProperties,
                 defaultExtendedTypes,
                 userExtendedTypes,
                 extendedTypeFactories,
-                resourceLocator);
+                resourceLocator,
+                valueObjectTypeRegistry);
 	    setSupportsBatchUpdates(true);
     }
     

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
index 564e0ed..69b9876 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
@@ -26,6 +26,7 @@ import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -72,8 +73,9 @@ public class FrontBaseAdapter extends JdbcAdapter {
 			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
 			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
 			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+			@Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 		setSupportsBatchUpdates(true);
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
index 0d99869..6b47636 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.dba.h2;
 
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -50,8 +51,9 @@ public class H2Adapter extends JdbcAdapter {
             @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
             @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
             @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+            @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
         setSupportsGeneratedKeys(true);
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
index 314de60..64c59b3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
@@ -31,6 +31,7 @@ import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -72,8 +73,9 @@ public class HSQLDBAdapter extends JdbcAdapter {
 			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
 			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
 			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+		    @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 		setSupportsGeneratedKeys(true);
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBNoSchemaAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBNoSchemaAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBNoSchemaAdapter.java
index d963f27..e8b6641 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBNoSchemaAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBNoSchemaAdapter.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.dba.hsqldb;
 
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.di.Inject;
@@ -42,8 +43,9 @@ public class HSQLDBNoSchemaAdapter extends HSQLDBAdapter {
             @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
             @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
             @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+            @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
index 3fc8f07..2c76b2a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
@@ -29,6 +29,7 @@ import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -67,8 +68,9 @@ public class IngresAdapter extends JdbcAdapter {
 	                     @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
 	                     @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
 	                     @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-	                     @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+	                     @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+						 @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 		setSupportsUniqueConstraints(true);
 		setSupportsGeneratedKeys(true);
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index 1981c2a..6e9529a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -32,6 +32,7 @@ import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.DefaultQuotingStrategy;
@@ -88,11 +89,12 @@ public class MySQLAdapter extends JdbcAdapter {
 	protected String storageEngine;
 
 	public MySQLAdapter(@Inject RuntimeProperties runtimeProperties,
-			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+						@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+						@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+						@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+						@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+						@Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 
 		// init defaults
 		this.storageEngine = DEFAULT_STORAGE_ENGINE;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
index 04d54cc..1a30779 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
@@ -35,6 +35,7 @@ import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -68,8 +69,9 @@ public class OpenBaseAdapter extends JdbcAdapter {
                            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
                            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
                            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-                           @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+                           @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+                           @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 
         // init defaults
         this.setSupportsUniqueConstraints(false);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
index 13ef0f7..60f820e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
@@ -25,6 +25,7 @@ import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.di.Inject;
@@ -54,11 +55,12 @@ public class Oracle8Adapter extends OracleAdapter {
 	}
 
 	public Oracle8Adapter(@Inject RuntimeProperties runtimeProperties,
-			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+						  @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+						  @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+						  @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+						  @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+						  @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 	}
 
 	private static void initOracle8DriverInformation() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
index 00ff222..f6528c9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
@@ -31,6 +31,7 @@ import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.access.types.ShortType;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -158,11 +159,12 @@ public class OracleAdapter extends JdbcAdapter {
 	}
 
 	public OracleAdapter(@Inject RuntimeProperties runtimeProperties,
-			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+						 @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+						 @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+						 @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+						 @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+						 @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 
 		// enable batch updates by default
 		setSupportsBatchUpdates(true);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
index b43eb7e..bf3f6b9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
@@ -29,6 +29,7 @@ import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -69,11 +70,12 @@ public class PostgresAdapter extends JdbcAdapter {
 	public static final String BYTEA = "bytea";
 
 	public PostgresAdapter(@Inject RuntimeProperties runtimeProperties,
-			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+						   @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+						   @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+						   @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+						   @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+						   @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 		setSupportsBatchUpdates(true);
 		setSupportsGeneratedKeys(true);
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
index 1796700..2542282 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
@@ -24,6 +24,7 @@ import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -61,13 +62,15 @@ public class SQLiteAdapter extends JdbcAdapter {
             @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
             @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
             @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
+            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+            @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
         super(
                 runtimeProperties,
                 defaultExtendedTypes,
                 userExtendedTypes,
                 extendedTypeFactories,
-                resourceLocator);
+                resourceLocator,
+                valueObjectTypeRegistry);
         this.setSupportsUniqueConstraints(false);
         this.setSupportsGeneratedKeys(true);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
index 3faec45..1a32cf4 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
@@ -25,6 +25,7 @@ import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.sybase.SybaseAdapter;
@@ -80,11 +81,12 @@ public class SQLServerAdapter extends SybaseAdapter {
 	public static final String TRIM_FUNCTION = "RTRIM";
 
 	public SQLServerAdapter(@Inject RuntimeProperties runtimeProperties,
-			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+							@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+							@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+							@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+							@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+							@Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
 
 		// TODO: i wonder if Sybase supports generated keys...
 		// in this case we need to move this to the super.

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
index 28e948c..886308a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
@@ -36,6 +36,7 @@ import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.access.types.ShortType;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.DefaultQuotingStrategy;
@@ -54,11 +55,12 @@ import org.apache.cayenne.resource.ResourceLocator;
 public class SybaseAdapter extends JdbcAdapter {
 
     public SybaseAdapter(@Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+                         @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+                         @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+                         @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+                         @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator,
+                         @Inject ValueObjectTypeRegistry valueObjectTypeRegistry) {
+        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/map/EntityResolver.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/EntityResolver.java b/cayenne-server/src/main/java/org/apache/cayenne/map/EntityResolver.java
index e7f93d8..2458361 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/EntityResolver.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/EntityResolver.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.map;
 
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.reflect.ClassDescriptor;
 import org.apache.cayenne.reflect.ClassDescriptorMap;
@@ -69,6 +70,8 @@ public class EntityResolver implements MappingNamespace, Serializable {
     // callbacks are not serializable
     protected transient LifecycleCallbackRegistry callbackRegistry;
 
+    protected transient ValueObjectTypeRegistry valueObjectTypeRegistry;
+
     /**
      * Creates new empty EntityResolver.
      */
@@ -661,4 +664,12 @@ public class EntityResolver implements MappingNamespace, Serializable {
         in.defaultReadObject();
         refreshMappingCache();
     }
+
+    public ValueObjectTypeRegistry getValueObjectTypeRegistry() {
+        return valueObjectTypeRegistry;
+    }
+
+    public void setValueObjectTypeRegistry(ValueObjectTypeRegistry valueObjectTypeRegistry) {
+        this.valueObjectTypeRegistry = valueObjectTypeRegistry;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
index 85de9a8..1c5b04e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
@@ -18,7 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.query;
 
-import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -28,10 +27,17 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.types.ValueObjectType;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.TraversalHandler;
 import org.apache.cayenne.exp.parser.ASTDbPath;
+import org.apache.cayenne.exp.parser.ASTFunctionCall;
+import org.apache.cayenne.exp.parser.ASTScalar;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
@@ -67,14 +73,12 @@ class SelectQueryMetadata extends BaseQueryMetadata {
 		this.pathSplitAliases = new HashMap<>(info.getPathSplitAliases());
 	}
 
-	<T> boolean resolve(Object root, EntityResolver resolver, SelectQuery<T> query) {
+	boolean resolve(Object root, EntityResolver resolver, SelectQuery<?> query) {
 
 		if (super.resolve(root, resolver, null)) {
-
 			// generate unique cache key, but only if we are caching..
-
 			if (cacheStrategy != null && cacheStrategy != QueryCacheStrategy.NO_CACHE) {
-				this.cacheKey = makeCacheKey(query);
+				this.cacheKey = makeCacheKey(query, resolver);
 			}
 
 			resolveAutoAliases(query);
@@ -87,12 +91,14 @@ class SelectQueryMetadata extends BaseQueryMetadata {
 		return false;
 	}
 
-	private String makeCacheKey(SelectQuery<?> query) {
+	private String makeCacheKey(SelectQuery<?> query, EntityResolver resolver) {
 
-		// create a unique key based on entity, qualifier, ordering and
-		// fetch offset and limit
+		// create a unique key based on entity or columns, qualifier, ordering, fetch offset and limit
 
 		StringBuilder key = new StringBuilder();
+		// handler to create string out of expressions, created lazily
+		TraversalHandler traversalHandler = null;
+
 		ObjEntity entity = getObjEntity();
 		if (entity != null) {
 			key.append(entity.getName());
@@ -102,23 +108,19 @@ class SelectQueryMetadata extends BaseQueryMetadata {
 
 		if(query.getColumns() != null && !query.getColumns().isEmpty()) {
 			key.append("/");
+			traversalHandler = new ToCacheKeyTraversalHandler(resolver.getValueObjectTypeRegistry(), key);
 			for(Property<?> property : query.getColumns()) {
 				key.append("c:");
-				try {
-					property.getExpression().appendAsString(key);
-				} catch (IOException e) {
-					throw new CayenneRuntimeException("Unexpected IO Exception appending to StringBuilder", e);
-				}
+				property.getExpression().traverse(traversalHandler);
 			}
 		}
 
 		if (query.getQualifier() != null) {
 			key.append('/');
-			try {
-				query.getQualifier().appendAsString(key);
-			} catch (IOException e) {
-				throw new CayenneRuntimeException("Unexpected IO Exception appending to StringBuilder", e);
+			if(traversalHandler == null) {
+				traversalHandler = new ToCacheKeyTraversalHandler(resolver.getValueObjectTypeRegistry(), key);
 			}
+			query.getQualifier().traverse(traversalHandler);
 		}
 
 		if (!query.getOrderings().isEmpty()) {
@@ -145,10 +147,9 @@ class SelectQueryMetadata extends BaseQueryMetadata {
 		}
 
 		return key.toString();
-
 	}
 
-	private <T> void resolveAutoAliases(SelectQuery<T> query) {
+	private void resolveAutoAliases(SelectQuery<?> query) {
 		Expression qualifier = query.getQualifier();
 		if (qualifier != null) {
 			resolveAutoAliases(qualifier);
@@ -399,4 +400,79 @@ class SelectQueryMetadata extends BaseQueryMetadata {
 	public void setSuppressingDistinct(boolean suppressingDistinct) {
 		this.suppressingDistinct = suppressingDistinct;
 	}
+
+	/**
+	 * Expression traverse handler to create cache key string out of Expression.
+	 * {@link Expression#appendAsString(Appendable)} where previously used for that,
+	 * but it can't handle custom value objects properly (see CAY-2210).
+	 *
+	 * @see ValueObjectTypeRegistry
+	 *
+	 * @since 4.0
+	 */
+	static class ToCacheKeyTraversalHandler implements TraversalHandler {
+
+		private ValueObjectTypeRegistry registry;
+		private StringBuilder out;
+
+		ToCacheKeyTraversalHandler(ValueObjectTypeRegistry registry, StringBuilder out) {
+			this.registry = registry;
+			this.out = out;
+		}
+
+		@Override
+		public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
+			out.append(',');
+		}
+
+		@Override
+		public void startNode(Expression node, Expression parentNode) {
+			if(node.getType() == Expression.FUNCTION_CALL) {
+				out.append(((ASTFunctionCall)node).getFunctionName()).append('(');
+			} else {
+				out.append(node.getType()).append('(');
+			}
+		}
+
+		@Override
+		public void endNode(Expression node, Expression parentNode) {
+			out.append(')');
+		}
+
+		@Override
+		public void objectNode(Object leaf, Expression parentNode) {
+			if(leaf == null) {
+				out.append("null");
+				return;
+			}
+
+			if(leaf instanceof ASTScalar) {
+				leaf = ((ASTScalar) leaf).getValue();
+			} else if(leaf instanceof Object[]) {
+				for(Object value : (Object[])leaf) {
+					objectNode(value, parentNode);
+					out.append(',');
+				}
+				return;
+			}
+
+			if (leaf instanceof Persistent) {
+				ObjectId id = ((Persistent) leaf).getObjectId();
+				Object encode = (id != null) ? id : leaf;
+				out.append(encode);
+			} else if (leaf instanceof Enum<?>) {
+				Enum<?> e = (Enum<?>) leaf;
+				out.append("e:").append(leaf.getClass().getName()).append(':').append(e.ordinal());
+			} else {
+				ValueObjectType<Object, ?> valueObjectType;
+				if (registry == null || (valueObjectType = registry.getValueType(leaf.getClass())) == null) {
+					// Registry will be null in cayenne-client context.
+					// Maybe we shouldn't create cache key at all in that case...
+					out.append(leaf);
+				} else {
+					out.append(valueObjectType.toCacheKey(leaf));
+				}
+			}
+		}
+	};
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/test/java/org/apache/cayenne/access/types/DefaultValueObjectTypeRegistryTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/types/DefaultValueObjectTypeRegistryTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/types/DefaultValueObjectTypeRegistryTest.java
new file mode 100644
index 0000000..28c7467
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/types/DefaultValueObjectTypeRegistryTest.java
@@ -0,0 +1,82 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.types;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @since 4.0
+ */
+public class DefaultValueObjectTypeRegistryTest {
+
+    DefaultValueObjectTypeRegistry registry;
+    ValueObjectType valueObjectType1, valueObjectType2;
+
+    @Before
+    public void setUpRegistry() {
+        valueObjectType1 = mock(ValueObjectType.class);
+        when(valueObjectType1.getValueType()).thenReturn(Integer.class);
+        when(valueObjectType1.getTargetType()).thenReturn(Integer.class);
+
+        valueObjectType2 = mock(ValueObjectType.class);
+        when(valueObjectType2.getValueType()).thenReturn(Number.class);
+        when(valueObjectType2.getTargetType()).thenReturn(Integer.class);
+
+        List<ValueObjectType<?, ?>> list = new ArrayList<>();
+        list.add(valueObjectType1);
+        list.add(valueObjectType2);
+
+        registry = new DefaultValueObjectTypeRegistry(list);
+    }
+
+    @Test
+    public void testInitialState() {
+        assertEquals(2, registry.typeCache.size());
+        assertTrue(registry.typeCache.containsKey(Integer.class.getName()));
+        assertTrue(registry.typeCache.containsKey(Number.class.getName()));
+        assertFalse(registry.typeCache.containsKey(String.class.getName()));
+        assertFalse(registry.typeCache.containsKey(Float.class.getName()));
+    }
+
+    @Test
+    public void getValueType() throws Exception {
+        ValueObjectType<?,?> valueObjectType = registry.getValueType(Integer.class);
+        assertSame(valueObjectType1, valueObjectType);
+
+        valueObjectType = registry.getValueType(Float.class);
+        assertSame(valueObjectType2, valueObjectType);
+
+        valueObjectType = registry.getValueType(String.class);
+        assertNull(valueObjectType);
+
+        assertEquals(4, registry.typeCache.size());
+        assertTrue(registry.typeCache.containsKey(String.class.getName()));
+        assertTrue(registry.typeCache.containsKey(Float.class.getName()));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
index af236eb..8a30ae8 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
@@ -36,6 +36,8 @@ import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
 import org.apache.cayenne.access.translator.select.DefaultSelectTranslatorFactory;
 import org.apache.cayenne.access.translator.select.SelectTranslatorFactory;
+import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.annotation.PostLoad;
 import org.apache.cayenne.ashwood.AshwoodEntitySorter;
 import org.apache.cayenne.cache.QueryCache;
@@ -205,6 +207,9 @@ public class DataDomainProviderTest {
 
                 binder.bind(EventBridge.class).toProvider(NoopEventBridgeProvider.class);
                 binder.bind(DataRowStoreFactory.class).to(DefaultDataRowStoreFactory.class);
+
+				ServerModule.contributeValueObjectTypes(binder);
+				binder.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class);
 			}
 		};
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactoryTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactoryTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactoryTest.java
index d03e2e3..a4709c1 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactoryTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactoryTest.java
@@ -21,6 +21,8 @@ package org.apache.cayenne.configuration.server;
 import com.mockrunner.mock.jdbc.MockConnection;
 import com.mockrunner.mock.jdbc.MockDataSource;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
+import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.DefaultRuntimeProperties;
@@ -119,6 +121,9 @@ public class DefaultDbAdapterFactoryTest {
                 binder.bind(Key.get(ResourceLocator.class, Constants.SERVER_RESOURCE_LOCATOR)).to(ClassLoaderResourceLocator.class);
                 binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
                 binder.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class));
+
+                ServerModule.contributeValueObjectTypes(binder);
+                binder.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class);
             }
         };
 
@@ -156,6 +161,9 @@ public class DefaultDbAdapterFactoryTest {
                 binder.bind(Key.get(ResourceLocator.class, Constants.SERVER_RESOURCE_LOCATOR)).to(ClassLoaderResourceLocator.class);
                 binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
                 binder.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class));
+
+                ServerModule.contributeValueObjectTypes(binder);
+                binder.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class);
             }
         };
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java
index fb2ebcb..505aefb 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java
@@ -20,6 +20,7 @@ package org.apache.cayenne.dba;
 
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.derby.DerbyAdapter;
 import org.apache.cayenne.dba.oracle.OracleAdapter;
@@ -49,18 +50,19 @@ public class PerAdapterProviderTest {
 
         ResourceLocator locator = new ClassLoaderResourceLocator(new DefaultClassLoaderManager());
         RuntimeProperties runtimeProperties = mock(RuntimeProperties.class);
+        ValueObjectTypeRegistry valueObjectTypeRegistry = mock(ValueObjectTypeRegistry.class);
 
         this.oracleAdapter = new OracleAdapter(runtimeProperties,
                 Collections.<ExtendedType>emptyList(),
                 Collections.<ExtendedType>emptyList(),
                 Collections.<ExtendedTypeFactory>emptyList(),
-                locator);
+                locator, valueObjectTypeRegistry);
 
         this.derbyAdapter = new DerbyAdapter(runtimeProperties,
                 Collections.<ExtendedType>emptyList(),
                 Collections.<ExtendedType>emptyList(),
                 Collections.<ExtendedTypeFactory>emptyList(),
-                locator);
+                locator, valueObjectTypeRegistry);
 
         this.autoDerbyAdapter = new AutoAdapter(new Provider<DbAdapter>() {
             @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryCacheKeyIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryCacheKeyIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryCacheKeyIT.java
index 6f6a4d1..701ca7b 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryCacheKeyIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryCacheKeyIT.java
@@ -30,6 +30,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
@@ -170,7 +171,7 @@ public class SelectQueryCacheKeyIT extends ServerCase {
         assertNotNull(q1.getMetaData(resolver).getCacheKey());
         assertEquals(q1.getMetaData(resolver).getCacheKey(), q2.getMetaData(resolver).getCacheKey());
 
-        assertFalse(q1.getMetaData(resolver).getCacheKey().equals(q3.getMetaData(resolver).getCacheKey()));
+        assertNotEquals(q1.getMetaData(resolver).getCacheKey(), q3.getMetaData(resolver).getCacheKey());
     }
 
     @Test
@@ -191,7 +192,7 @@ public class SelectQueryCacheKeyIT extends ServerCase {
         assertNotNull(q1.getMetaData(resolver).getCacheKey());
         assertEquals(q1.getMetaData(resolver).getCacheKey(), q2.getMetaData(resolver).getCacheKey());
 
-        assertFalse(q1.getMetaData(resolver).getCacheKey().equals(q3.getMetaData(resolver).getCacheKey()));
+        assertNotEquals(q1.getMetaData(resolver).getCacheKey(), q3.getMetaData(resolver).getCacheKey());
     }
 
     @Test
@@ -215,7 +216,7 @@ public class SelectQueryCacheKeyIT extends ServerCase {
         assertNotNull(q1.getMetaData(resolver).getCacheKey());
         assertEquals(q1.getMetaData(resolver).getCacheKey(), q2.getMetaData(resolver).getCacheKey());
 
-        assertFalse(q1.getMetaData(resolver).getCacheKey().equals(q3.getMetaData(resolver).getCacheKey()));
-        assertFalse(q1.getMetaData(resolver).getCacheKey().equals(q4.getMetaData(resolver).getCacheKey()));
+        assertNotEquals(q1.getMetaData(resolver).getCacheKey(), q3.getMetaData(resolver).getCacheKey());
+        assertNotEquals(q1.getMetaData(resolver).getCacheKey(), q4.getMetaData(resolver).getCacheKey());
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java
new file mode 100644
index 0000000..efd03c4
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java
@@ -0,0 +1,262 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.query;
+
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.types.ValueObjectType;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.TraversalHandler;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * This class is testing converting Expressions to cache key part.
+ *
+ * @since 4.0
+ */
+public class SelectQueryMetadataCacheKeyTest {
+
+    private ValueObjectTypeRegistry registry;
+    private StringBuilder cacheKey;
+
+    @SuppressWarnings("unchecked")
+    @Before
+    public void createObjects() {
+        registry = mock(ValueObjectTypeRegistry.class);
+
+        // mock value type for Double class
+        ValueObjectType mockType = mock(ValueObjectType.class);
+        when(mockType.getValueType()).thenReturn(Double.class);
+        when(mockType.toCacheKey(any())).thenReturn("<value placeholder>");
+        when(registry.getValueType(eq(Double.class))).thenReturn(mockType);
+
+        // value type for TestValue class
+        ValueObjectType testType = new TestValueType();
+        when(registry.getValueType(eq(TestValue.class))).thenReturn(testType);
+    }
+
+    /**
+     * Simple expressions
+     */
+    @Test
+    public void cacheKeySimple() {
+        ExpressionFactory.exp("field = 1").traverse(newHandler());
+        String s1 = cacheKey.toString();
+
+        ExpressionFactory.exp("field = 1").traverse(newHandler());
+        String s2 = cacheKey.toString();
+
+        ExpressionFactory.exp("field = 2").traverse(newHandler());
+        String s3 = cacheKey.toString();
+
+        assertEquals(s1, s2);
+        assertNotEquals(s2, s3);
+    }
+
+    /**
+     * Expressions with list of simple values
+     */
+    @Test
+    public void cacheKeyWithList() {
+        ExpressionFactory.exp("field in (1,2,3)").traverse(newHandler());
+        String s1 = cacheKey.toString();
+
+        ExpressionFactory.exp("field in (1,2,3)").traverse(newHandler());
+        String s2 = cacheKey.toString();
+
+        ExpressionFactory.exp("field in (2,3,4)").traverse(newHandler());
+        String s3 = cacheKey.toString();
+
+        assertEquals(s1, s2);
+        assertNotEquals(s2, s3);
+    }
+
+    /**
+     * Simple test for custom value object, Double.class is marked as a custom value object.
+     */
+    @Test
+    public void cacheKeyWithValueObjectSimple() {
+        ExpressionFactory.exp("field = 1.0").traverse(newHandler());
+        String s1 = cacheKey.toString();
+
+        assertTrue(s1.contains("<value placeholder>"));
+    }
+
+    /**
+     * List of value objects, Double.class is marked as a custom value object.
+     */
+    @Test
+    public void cacheKeyWithValueObjectList() {
+        ExpressionFactory.exp("field in (1.0,2.0,3.0)").traverse(newHandler());
+        String s1 = cacheKey.toString();
+
+        assertTrue(s1.contains("<value placeholder>"));
+    }
+
+    @Test
+    public void cacheKeyWithEnumValue() {
+        ExpressionFactory.greaterOrEqualExp("testPath", TestEnum.VALUE_1).traverse(newHandler());
+        String s1 = cacheKey.toString();
+
+        ExpressionFactory.greaterOrEqualExp("testPath", TestEnum.VALUE_1).traverse(newHandler());
+        String s2 = cacheKey.toString();
+
+        ExpressionFactory.greaterOrEqualExp("testPath", TestEnum.VALUE_2).traverse(newHandler());
+        String s3 = cacheKey.toString();
+
+        assertEquals(s1, s2);
+        assertNotEquals(s2, s3);
+    }
+
+    @Test
+    public void cacheKeyWithValueObject() {
+        ExpressionFactory.greaterOrEqualExp("testPath", new TestValue(1)).traverse(newHandler());
+        String s1 = cacheKey.toString();
+
+        ExpressionFactory.greaterOrEqualExp("testPath", new TestValue(1)).traverse(newHandler());
+        String s2 = cacheKey.toString();
+
+        ExpressionFactory.greaterOrEqualExp("testPath", new TestValue(2)).traverse(newHandler());
+        String s3 = cacheKey.toString();
+
+        assertEquals(s1, s2);
+        assertNotEquals(s2, s3);
+    }
+
+    /**
+     * Persistent objects should be converted to their ObjectIds.
+     */
+    @Test
+    public void cacheKeyWithPersistentObject() {
+        Persistent persistent1 = mock(Persistent.class);
+        ObjectId objectId1 = mock(ObjectId.class);
+        when(objectId1.toString()).thenReturn("objId1");
+        when(persistent1.getObjectId()).thenReturn(objectId1);
+
+        Persistent persistent2 = mock(Persistent.class);
+        ObjectId objectId2 = mock(ObjectId.class);
+        when(objectId2.toString()).thenReturn("objId2");
+        when(persistent2.getObjectId()).thenReturn(objectId2);
+
+        ExpressionFactory.greaterOrEqualExp("testPath", persistent1).traverse(newHandler());
+        String s1 = cacheKey.toString();
+
+        ExpressionFactory.greaterOrEqualExp("testPath", persistent1).traverse(newHandler());
+        String s2 = cacheKey.toString();
+
+        ExpressionFactory.greaterOrEqualExp("testPath", persistent2).traverse(newHandler());
+        String s3 = cacheKey.toString();
+
+        assertTrue(s1.contains("objId1"));
+        assertTrue(s3.contains("objId2"));
+        assertEquals(s1, s2);
+        assertNotEquals(s2, s3);
+    }
+
+    @Test
+    public void cacheKeyWithFunctionCall() {
+        ExpressionFactory.exp("length(testPath)").traverse(newHandler());
+        String s1 = cacheKey.toString();
+
+        ExpressionFactory.exp("length(testPath)").traverse(newHandler());
+        String s2 = cacheKey.toString();
+
+        ExpressionFactory.exp("count(testPath)").traverse(newHandler());
+        String s3 = cacheKey.toString();
+
+        assertEquals(s1, s2);
+        assertNotEquals(s2, s3);
+
+        ExpressionFactory.exp("substring(path, testPath)").traverse(newHandler());
+        String s4 = cacheKey.toString();
+
+        ExpressionFactory.exp("substring(path2, testPath)").traverse(newHandler());
+        String s5 = cacheKey.toString();
+
+        assertNotEquals(s4, s5);
+
+        ExpressionFactory.exp("year(path)").traverse(newHandler());
+        String s6 = cacheKey.toString();
+
+        ExpressionFactory.exp("hour(path)").traverse(newHandler());
+        String s7 = cacheKey.toString();
+
+        assertNotEquals(s6, s7);
+    }
+
+    private TraversalHandler newHandler() {
+        return new SelectQueryMetadata.ToCacheKeyTraversalHandler(registry, cacheKey = new StringBuilder());
+    }
+
+    /* ************* Test types *************** */
+
+    /**
+     * Test enum
+     */
+    enum TestEnum { VALUE_1, VALUE_2 }
+
+    /**
+     * Test value object
+     */
+    static class TestValue {
+        int v = 0;
+        TestValue(int v) {
+            this.v = v;
+        }
+    }
+
+    /**
+     * Test value object descriptor, we need only toCacheKey() method
+     */
+    static class TestValueType implements ValueObjectType<TestValue, Integer> {
+        @Override
+        public Class<Integer> getTargetType() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Class<TestValue> getValueType() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public TestValue toJavaObject(Integer value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Integer fromJavaObject(TestValue object) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String toCacheKey(TestValue object) {
+            return Integer.toString(object.v);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java
index 553807f..7e1f2dd 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java
@@ -25,13 +25,14 @@ import org.apache.cayenne.access.DefaultObjectMapRetainStrategy;
 import org.apache.cayenne.access.ObjectMapRetainStrategy;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.access.types.BigDecimalType;
-import org.apache.cayenne.access.types.BigIntegerType;
+import org.apache.cayenne.access.types.BigIntegerValueType;
 import org.apache.cayenne.access.types.BooleanType;
 import org.apache.cayenne.access.types.ByteArrayType;
 import org.apache.cayenne.access.types.ByteType;
 import org.apache.cayenne.access.types.CalendarType;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.DateType;
+import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry;
 import org.apache.cayenne.access.types.DoubleType;
 import org.apache.cayenne.access.types.FloatType;
 import org.apache.cayenne.access.types.IntegerType;
@@ -39,8 +40,9 @@ import org.apache.cayenne.access.types.LongType;
 import org.apache.cayenne.access.types.ShortType;
 import org.apache.cayenne.access.types.TimeType;
 import org.apache.cayenne.access.types.TimestampType;
-import org.apache.cayenne.access.types.UUIDType;
+import org.apache.cayenne.access.types.UUIDValueType;
 import org.apache.cayenne.access.types.UtilDateType;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.access.types.VoidType;
 import org.apache.cayenne.configuration.ConfigurationNameMapper;
 import org.apache.cayenne.configuration.Constants;
@@ -124,44 +126,28 @@ public class ServerCaseModule implements Module {
         // unit test injector. ServerRuntime injector contents are customized
         // inside ServerRuntimeProvider.
 
-        binder.bindMap(String.class, UnitDbAdapterProvider.TEST_ADAPTERS_MAP).put(
-                FirebirdAdapter.class.getName(),
-                FirebirdUnitDbAdapter.class.getName()).put(
-                OracleAdapter.class.getName(),
-                OracleUnitDbAdapter.class.getName()).put(
-                DerbyAdapter.class.getName(),
-                DerbyUnitDbAdapter.class.getName()).put(
-                Oracle8Adapter.class.getName(),
-                OracleUnitDbAdapter.class.getName()).put(
-                SybaseAdapter.class.getName(),
-                SybaseUnitDbAdapter.class.getName()).put(
-                MySQLAdapter.class.getName(),
-                MySQLUnitDbAdapter.class.getName()).put(
-                PostgresAdapter.class.getName(),
-                PostgresUnitDbAdapter.class.getName()).put(
-                OpenBaseAdapter.class.getName(),
-                OpenBaseUnitDbAdapter.class.getName()).put(
-                SQLServerAdapter.class.getName(),
-                SQLServerUnitDbAdapter.class.getName()).put(
-                DB2Adapter.class.getName(),
-                DB2UnitDbAdapter.class.getName()).put(
-                HSQLDBAdapter.class.getName(),
-                HSQLDBUnitDbAdapter.class.getName()).put(
-                H2Adapter.class.getName(),
-                H2UnitDbAdapter.class.getName()).put(
-                FrontBaseAdapter.class.getName(),
-                FrontBaseUnitDbAdapter.class.getName()).put(
-                IngresAdapter.class.getName(),
-                IngresUnitDbAdapter.class.getName()).put(
-                SQLiteAdapter.class.getName(),
-                SQLiteUnitDbAdapter.class.getName());
+        binder.bindMap(String.class, UnitDbAdapterProvider.TEST_ADAPTERS_MAP)
+                .put(FirebirdAdapter.class.getName(), FirebirdUnitDbAdapter.class.getName())
+                .put(OracleAdapter.class.getName(), OracleUnitDbAdapter.class.getName())
+                .put(DerbyAdapter.class.getName(), DerbyUnitDbAdapter.class.getName())
+                .put(Oracle8Adapter.class.getName(), OracleUnitDbAdapter.class.getName())
+                .put(SybaseAdapter.class.getName(), SybaseUnitDbAdapter.class.getName())
+                .put(MySQLAdapter.class.getName(), MySQLUnitDbAdapter.class.getName())
+                .put(PostgresAdapter.class.getName(), PostgresUnitDbAdapter.class.getName())
+                .put(OpenBaseAdapter.class.getName(), OpenBaseUnitDbAdapter.class.getName())
+                .put(SQLServerAdapter.class.getName(), SQLServerUnitDbAdapter.class.getName())
+                .put(DB2Adapter.class.getName(), DB2UnitDbAdapter.class.getName())
+                .put(HSQLDBAdapter.class.getName(), HSQLDBUnitDbAdapter.class.getName())
+                .put(H2Adapter.class.getName(), H2UnitDbAdapter.class.getName())
+                .put(FrontBaseAdapter.class.getName(), FrontBaseUnitDbAdapter.class.getName())
+                .put(IngresAdapter.class.getName(), IngresUnitDbAdapter.class.getName())
+                .put(SQLiteAdapter.class.getName(), SQLiteUnitDbAdapter.class.getName());
         ServerModule.contributeProperties(binder);
         
         // configure extended types
         ServerModule.contributeDefaultTypes(binder)
                 .add(new VoidType())
                 .add(new BigDecimalType())
-                .add(new BigIntegerType())
                 .add(new BooleanType())
                 .add(new ByteArrayType(false, true))
                 .add(new ByteType(false))
@@ -175,24 +161,24 @@ public class ServerCaseModule implements Module {
                 .add(new TimeType())
                 .add(new TimestampType())
                 .add(new UtilDateType())
-                .add(new CalendarType<GregorianCalendar>(GregorianCalendar.class))
-                .add(new CalendarType<Calendar>(Calendar.class))
-                .add(new UUIDType());
+                .add(new CalendarType<>(GregorianCalendar.class))
+                .add(new CalendarType<>(Calendar.class));
         ServerModule.contributeUserTypes(binder);
         ServerModule.contributeTypeFactories(binder);
+        ServerModule.contributeValueObjectTypes(binder)
+                .add(BigIntegerValueType.class)
+                .add(UUIDValueType.class);
+        binder.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class);
 
         binder.bind(SchemaBuilder.class).to(SchemaBuilder.class);
         binder.bind(JdbcEventLogger.class).to(CommonsJdbcEventLogger.class);
         binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
-        binder.bind(ObjectMapRetainStrategy.class).to(
-                DefaultObjectMapRetainStrategy.class);
+        binder.bind(ObjectMapRetainStrategy.class).to(DefaultObjectMapRetainStrategy.class);
 
         // singleton objects
-        binder.bind(UnitTestLifecycleManager.class).toInstance(
-                new ServerCaseLifecycleManager(testScope));
+        binder.bind(UnitTestLifecycleManager.class).toInstance(new ServerCaseLifecycleManager(testScope));
 
-        binder.bind(DataSourceInfo.class).toProvider(
-                ServerCaseDataSourceInfoProvider.class);
+        binder.bind(DataSourceInfo.class).toProvider(ServerCaseDataSourceInfoProvider.class);
         binder.bind(DataSourceFactory.class).to(ServerCaseSharedDataSourceFactory.class);
         binder.bind(DbAdapter.class).toProvider(ServerCaseDbAdapterProvider.class);
         binder.bind(JdbcAdapter.class).toProvider(ServerCaseDbAdapterProvider.class);
@@ -201,14 +187,10 @@ public class ServerCaseModule implements Module {
         // this factory is a hack that allows to inject to DbAdapters loaded outside of
         // server runtime... BatchQueryBuilderFactory is hardcoded and whatever is placed
         // in the ServerModule is ignored
-        binder.bind(BatchTranslatorFactory.class).toProvider(
-                ServerCaseBatchQueryBuilderFactoryProvider.class);
-        binder.bind(DataChannelInterceptor.class).to(
-                ServerCaseDataChannelInterceptor.class);
-        binder.bind(SQLTemplateCustomizer.class).toProvider(
-                SQLTemplateCustomizerProvider.class);
-        binder.bind(ServerCaseDataSourceFactory.class).to(
-                ServerCaseDataSourceFactory.class);
+        binder.bind(BatchTranslatorFactory.class).toProvider(ServerCaseBatchQueryBuilderFactoryProvider.class);
+        binder.bind(DataChannelInterceptor.class).to(ServerCaseDataChannelInterceptor.class);
+        binder.bind(SQLTemplateCustomizer.class).toProvider(SQLTemplateCustomizerProvider.class);
+        binder.bind(ServerCaseDataSourceFactory.class).to(ServerCaseDataSourceFactory.class);
         binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
         binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
         binder.bind(ResourceLocator.class).to(ClassLoaderResourceLocator.class);
@@ -218,26 +200,13 @@ public class ServerCaseModule implements Module {
         binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
 
         // test-scoped objects
-        binder.bind(EntityResolver.class).toProvider(
-                ServerCaseEntityResolverProvider.class).in(testScope);
-        binder.bind(DataNode.class).toProvider(ServerCaseDataNodeProvider.class).in(
-                testScope);
-        binder.bind(ServerCaseProperties.class).to(ServerCaseProperties.class).in(
-                testScope);
-        binder.bind(ServerRuntime.class).toProvider(ServerRuntimeProvider.class).in(
-                testScope);
-        binder
-                .bind(ObjectContext.class)
-                .toProvider(ServerCaseObjectContextProvider.class)
-                .withoutScope();
-        binder
-                .bind(DataContext.class)
-                .toProvider(ServerCaseDataContextProvider.class)
-                .withoutScope();
-
-        binder.bind(DBHelper.class).toProvider(FlavoredDBHelperProvider.class).in(
-                testScope);
-        binder.bind(DBCleaner.class).toProvider(DBCleanerProvider.class).in(
-                testScope);
+        binder.bind(EntityResolver.class).toProvider(ServerCaseEntityResolverProvider.class).in(testScope);
+        binder.bind(DataNode.class).toProvider(ServerCaseDataNodeProvider.class).in(testScope);
+        binder.bind(ServerCaseProperties.class).to(ServerCaseProperties.class).in(testScope);
+        binder.bind(ServerRuntime.class).toProvider(ServerRuntimeProvider.class).in(testScope);
+        binder.bind(ObjectContext.class).toProvider(ServerCaseObjectContextProvider.class).withoutScope();
+        binder.bind(DataContext.class).toProvider(ServerCaseDataContextProvider.class).withoutScope();
+        binder.bind(DBHelper.class).toProvider(FlavoredDBHelperProvider.class).in(testScope);
+        binder.bind(DBCleaner.class).toProvider(DBCleanerProvider.class).in(testScope);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index b07f2d0..20f6164 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -15,6 +15,7 @@ Changes/New Features:
 
 CAY-1873 Move DataDomain cache configuration from the Modeler and into DI
 CAY-2109 cayenne-crypto: add value authentication (HMAC)
+CAY-2210 Query cache: incorrect cache key for queries with custom value objects
 CAY-2255 ObjectSelect improvement: columns as full entities
 CAY-2258 DI: type-safe binding of List and Map
 CAY-2266 Move EventBridge implementations into autoloadable modules


[2/2] cayenne git commit: CAY-2210 Query cache: incorrect cache key for queries with custom value objects

Posted by nt...@apache.org.
CAY-2210 Query cache: incorrect cache key for queries with custom value objects


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/5e9f0e0f
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/5e9f0e0f
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/5e9f0e0f

Branch: refs/heads/master
Commit: 5e9f0e0f6dd23f34fb755dc5eef966652f1d3ded
Parents: 4911ad1
Author: Nikita Timofeev <st...@gmail.com>
Authored: Fri Mar 31 17:01:56 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Fri Mar 31 17:01:56 2017 +0300

----------------------------------------------------------------------
 .../rop/client/ClientChannelProvider.java       |   5 +-
 .../apache/cayenne/remote/ClientChannel.java    |  11 +-
 .../reverse/configuration/ToolsModule.java      |   4 +
 .../java/org/apache/cayenne/di/spi/DIUtil.java  |  20 +-
 .../org/apache/cayenne/java8/Java8Module.java   |  14 +-
 .../java8/access/types/LocalDateTimeType.java   |  62 -----
 .../access/types/LocalDateTimeValueType.java    |  56 ++++
 .../java8/access/types/LocalDateType.java       |  62 -----
 .../java8/access/types/LocalDateValueType.java  |  56 ++++
 .../java8/access/types/LocalTimeType.java       |  63 -----
 .../java8/access/types/LocalTimeValueType.java  |  56 ++++
 .../cayenne/access/types/BigDecimalType.java    |   6 +-
 .../cayenne/access/types/BigIntegerType.java    |  88 -------
 .../access/types/BigIntegerValueType.java       |  53 ++++
 .../cayenne/access/types/BooleanType.java       |   9 +-
 .../access/types/ByteOrCharArrayFactory.java    |  14 +-
 .../apache/cayenne/access/types/ByteType.java   |   9 +-
 .../apache/cayenne/access/types/DateType.java   |   6 +-
 .../types/DefaultValueObjectTypeRegistry.java   | 104 ++++++++
 .../apache/cayenne/access/types/DoubleType.java |   6 +-
 .../apache/cayenne/access/types/EnumType.java   |  25 +-
 .../cayenne/access/types/EnumTypeFactory.java   |   9 +-
 .../cayenne/access/types/ExtendedEnumType.java  |  42 ++-
 .../cayenne/access/types/ExtendedType.java      |   2 +-
 .../cayenne/access/types/ExtendedTypeMap.java   |   7 +-
 .../apache/cayenne/access/types/FloatType.java  |   6 +-
 .../cayenne/access/types/IntegerType.java       |   6 +-
 .../apache/cayenne/access/types/LongType.java   |   6 +-
 .../apache/cayenne/access/types/ObjectType.java |   6 +-
 .../access/types/SerializableTypeFactory.java   |  17 +-
 .../apache/cayenne/access/types/ShortType.java  |  10 +-
 .../access/types/SubclassTypeFactory.java       |   3 +-
 .../apache/cayenne/access/types/TimeType.java   |   6 +-
 .../cayenne/access/types/TimestampType.java     |   6 +-
 .../apache/cayenne/access/types/UUIDType.java   |  95 -------
 .../cayenne/access/types/UUIDValueType.java     |  59 +++++
 .../cayenne/access/types/UtilDateType.java      |  27 +-
 .../cayenne/access/types/ValueObjectType.java   |  63 +++++
 .../access/types/ValueObjectTypeFactory.java    | 101 +++++++
 .../access/types/ValueObjectTypeRegistry.java   |  39 +++
 .../server/DataDomainProvider.java              |   5 +
 .../configuration/server/ServerModule.java      |  39 ++-
 .../org/apache/cayenne/dba/JdbcAdapter.java     |  22 +-
 .../org/apache/cayenne/dba/db2/DB2Adapter.java  |   6 +-
 .../apache/cayenne/dba/derby/DerbyAdapter.java  |   7 +-
 .../cayenne/dba/firebird/FirebirdAdapter.java   |   7 +-
 .../cayenne/dba/frontbase/FrontBaseAdapter.java |   6 +-
 .../org/apache/cayenne/dba/h2/H2Adapter.java    |   6 +-
 .../cayenne/dba/hsqldb/HSQLDBAdapter.java       |   6 +-
 .../dba/hsqldb/HSQLDBNoSchemaAdapter.java       |   6 +-
 .../cayenne/dba/ingres/IngresAdapter.java       |   6 +-
 .../apache/cayenne/dba/mysql/MySQLAdapter.java  |  12 +-
 .../cayenne/dba/openbase/OpenBaseAdapter.java   |   6 +-
 .../cayenne/dba/oracle/Oracle8Adapter.java      |  12 +-
 .../cayenne/dba/oracle/OracleAdapter.java       |  12 +-
 .../cayenne/dba/postgres/PostgresAdapter.java   |  12 +-
 .../cayenne/dba/sqlite/SQLiteAdapter.java       |   7 +-
 .../cayenne/dba/sqlserver/SQLServerAdapter.java |  12 +-
 .../cayenne/dba/sybase/SybaseAdapter.java       |  12 +-
 .../org/apache/cayenne/map/EntityResolver.java  |  11 +
 .../cayenne/query/SelectQueryMetadata.java      | 114 ++++++--
 .../DefaultValueObjectTypeRegistryTest.java     |  82 ++++++
 .../server/DataDomainProviderTest.java          |   5 +
 .../server/DefaultDbAdapterFactoryTest.java     |   8 +
 .../cayenne/dba/PerAdapterProviderTest.java     |   6 +-
 .../cayenne/query/SelectQueryCacheKeyIT.java    |   9 +-
 .../query/SelectQueryMetadataCacheKeyTest.java  | 262 +++++++++++++++++++
 .../unit/di/server/ServerCaseModule.java        | 113 +++-----
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 69 files changed, 1346 insertions(+), 705 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientChannelProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientChannelProvider.java b/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientChannelProvider.java
index 7936a1e..e5cf674 100644
--- a/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientChannelProvider.java
+++ b/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientChannelProvider.java
@@ -20,7 +20,6 @@ package org.apache.cayenne.configuration.rop.client;
 
 import org.apache.cayenne.ConfigurationException;
 import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.di.Provider;
@@ -41,9 +40,7 @@ public class ClientChannelProvider implements Provider<DataChannel> {
 
     public DataChannel get() throws ConfigurationException {
 
-        boolean channelEvents = properties.getBoolean(
-                ClientConstants.ROP_CHANNEL_EVENTS_PROPERTY,
-                false);
+        boolean channelEvents = properties.getBoolean(ClientConstants.ROP_CHANNEL_EVENTS_PROPERTY, false);
 
         return new ClientChannel(connection, channelEvents, eventManager, channelEvents);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-client/src/main/java/org/apache/cayenne/remote/ClientChannel.java
----------------------------------------------------------------------
diff --git a/cayenne-client/src/main/java/org/apache/cayenne/remote/ClientChannel.java b/cayenne-client/src/main/java/org/apache/cayenne/remote/ClientChannel.java
index 8353bc4..11a14ce 100644
--- a/cayenne-client/src/main/java/org/apache/cayenne/remote/ClientChannel.java
+++ b/cayenne-client/src/main/java/org/apache/cayenne/remote/ClientChannel.java
@@ -19,7 +19,13 @@
 
 package org.apache.cayenne.remote;
 
-import org.apache.cayenne.*;
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.DataChannelSyncCallbackAction;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.QueryResponse;
 import org.apache.cayenne.event.EventBridge;
 import org.apache.cayenne.event.EventManager;
 import org.apache.cayenne.event.EventSubject;
@@ -72,8 +78,7 @@ public class ClientChannel implements DataChannel {
         } else {
             try {
                 setupRemoteChannelListener();
-            }
-            catch (CayenneRuntimeException e) {}
+            } catch (CayenneRuntimeException ignored) {}
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java
index bc5762e..8fcafa7 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java
@@ -21,6 +21,8 @@ package org.apache.cayenne.dbsync.reverse.configuration;
 
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
+import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.DefaultRuntimeProperties;
 import org.apache.cayenne.configuration.RuntimeProperties;
@@ -84,6 +86,8 @@ public class ToolsModule implements Module {
         ServerModule.contributeDefaultTypes(binder);
         ServerModule.contributeUserTypes(binder);
         ServerModule.contributeTypeFactories(binder);
+        ServerModule.contributeValueObjectTypes(binder);
+        binder.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class);
 
         binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
         binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java
index 5363ba0..416eb61 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java
@@ -39,7 +39,7 @@ class DIUtil {
             Type[] parameters = parameterizedType.getActualTypeArguments();
 
             if (parameters.length == 1) {
-                return (Class<?>) parameters[0];
+                return typeToClass(parameters[0]);
             }
         }
 
@@ -55,13 +55,7 @@ class DIUtil {
             arr = new Class[parameters.length];
             int i=0;
             for(Type next : parameters) {
-                if(next instanceof Class) {
-                    arr[i++] = (Class<?>) next;
-                } else if(next instanceof ParameterizedType){
-                    arr[i++] = (Class<?>) ((ParameterizedType)next).getRawType();
-                } else {
-                    arr[i++] = Object.class;
-                }
+                arr[i++] = typeToClass(next);
             }
         }
 
@@ -86,4 +80,14 @@ class DIUtil {
 
         return Key.get(type, bindingName);
     }
+
+    static Class<?> typeToClass(Type type) {
+        if(type instanceof Class) {
+            return  (Class<?>) type;
+        } else if(type instanceof ParameterizedType){
+            return  (Class<?>) ((ParameterizedType)type).getRawType();
+        } else {
+            return Object.class;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java
index 2de3749..434595d 100644
--- a/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java
+++ b/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java
@@ -21,9 +21,9 @@ package org.apache.cayenne.java8;
 import org.apache.cayenne.configuration.server.ServerModule;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.Module;
-import org.apache.cayenne.java8.access.types.LocalDateTimeType;
-import org.apache.cayenne.java8.access.types.LocalDateType;
-import org.apache.cayenne.java8.access.types.LocalTimeType;
+import org.apache.cayenne.java8.access.types.LocalDateTimeValueType;
+import org.apache.cayenne.java8.access.types.LocalDateValueType;
+import org.apache.cayenne.java8.access.types.LocalTimeValueType;
 
 /**
  * @since 4.0
@@ -32,9 +32,9 @@ public class Java8Module implements Module {
 
     @Override
     public void configure(Binder binder) {
-        ServerModule.contributeDefaultTypes(binder)
-                .add(new LocalDateType())
-                .add(new LocalTimeType())
-                .add(new LocalDateTimeType());
+        ServerModule.contributeValueObjectTypes(binder)
+                .add(LocalDateValueType.class)
+                .add(LocalTimeValueType.class)
+                .add(LocalDateTimeValueType.class);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateTimeType.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateTimeType.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateTimeType.java
deleted file mode 100644
index 6242574..0000000
--- a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateTimeType.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.java8.access.types;
-
-import org.apache.cayenne.access.types.ExtendedType;
-
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.Timestamp;
-import java.time.LocalDateTime;
-
-public class LocalDateTimeType implements ExtendedType<LocalDateTime> {
-
-    @Override
-    public String getClassName() {
-        return LocalDateTime.class.getName();
-    }
-
-    @Override
-    public void setJdbcObject(PreparedStatement statement, LocalDateTime value, int pos, int type, int scale) throws Exception {
-        statement.setTimestamp(pos, Timestamp.valueOf(value));
-    }
-
-    @Override
-    public LocalDateTime materializeObject(ResultSet rs, int index, int type) throws Exception {
-        Timestamp timestamp = rs.getTimestamp(index);
-        return timestamp != null ? timestamp.toLocalDateTime() : null;
-    }
-
-    @Override
-    public LocalDateTime materializeObject(CallableStatement rs, int index, int type) throws Exception {
-        Timestamp timestamp = rs.getTimestamp(index);
-        return timestamp != null ? timestamp.toLocalDateTime() : null;
-    }
-
-    @Override
-    public String toString(LocalDateTime value) {
-        if (value == null) {
-            return "NULL";
-        }
-
-        return '\'' + value.toString() + '\'';
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateTimeValueType.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateTimeValueType.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateTimeValueType.java
new file mode 100644
index 0000000..a877b7a
--- /dev/null
+++ b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateTimeValueType.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.java8.access.types;
+
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+import org.apache.cayenne.access.types.ValueObjectType;
+
+/**
+ * @since 4.0
+ */
+public class LocalDateTimeValueType implements ValueObjectType<LocalDateTime, Timestamp> {
+
+    @Override
+    public Class<Timestamp> getTargetType() {
+        return Timestamp.class;
+    }
+
+    @Override
+    public Class<LocalDateTime> getValueType() {
+        return LocalDateTime.class;
+    }
+
+    @Override
+    public LocalDateTime toJavaObject(Timestamp value) {
+        return value.toLocalDateTime();
+    }
+
+    @Override
+    public Timestamp fromJavaObject(LocalDateTime object) {
+        return Timestamp.valueOf(object);
+    }
+
+    @Override
+    public String toCacheKey(LocalDateTime object) {
+        return object.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateType.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateType.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateType.java
deleted file mode 100644
index 0d0b431..0000000
--- a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateType.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.java8.access.types;
-
-import org.apache.cayenne.access.types.ExtendedType;
-
-import java.sql.CallableStatement;
-import java.sql.Date;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.time.LocalDate;
-
-public class LocalDateType implements ExtendedType<LocalDate> {
-
-    @Override
-    public String getClassName() {
-        return LocalDate.class.getName();
-    }
-
-    @Override
-    public void setJdbcObject(PreparedStatement statement, LocalDate value, int pos, int type, int scale) throws Exception {
-        statement.setDate(pos, Date.valueOf(value));
-    }
-
-    @Override
-    public LocalDate materializeObject(ResultSet rs, int index, int type) throws Exception {
-        Date date = rs.getDate(index);
-        return date != null ? date.toLocalDate() : null;
-    }
-
-    @Override
-    public LocalDate materializeObject(CallableStatement rs, int index, int type) throws Exception {
-        Date date = rs.getDate(index);
-        return date != null ? date.toLocalDate() : null;
-    }
-
-    @Override
-    public String toString(LocalDate value) {
-        if (value == null) {
-            return "NULL";
-        }
-
-        return '\'' + value.toString() + '\'';
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateValueType.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateValueType.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateValueType.java
new file mode 100644
index 0000000..03e259c
--- /dev/null
+++ b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalDateValueType.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.java8.access.types;
+
+import java.sql.Date;
+import java.time.LocalDate;
+
+import org.apache.cayenne.access.types.ValueObjectType;
+
+/**
+ * @since 4.0
+ */
+public class LocalDateValueType implements ValueObjectType<LocalDate, Date> {
+
+    @Override
+    public Class<Date> getTargetType() {
+        return Date.class;
+    }
+
+    @Override
+    public Class<LocalDate> getValueType() {
+        return LocalDate.class;
+    }
+
+    @Override
+    public LocalDate toJavaObject(Date value) {
+        return value.toLocalDate();
+    }
+
+    @Override
+    public Date fromJavaObject(LocalDate object) {
+        return Date.valueOf(object);
+    }
+
+    @Override
+    public String toCacheKey(LocalDate object) {
+        return object.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalTimeType.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalTimeType.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalTimeType.java
deleted file mode 100644
index 1b0afb4..0000000
--- a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalTimeType.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.java8.access.types;
-
-import org.apache.cayenne.access.types.ExtendedType;
-
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.Time;
-import java.time.LocalTime;
-
-public class LocalTimeType implements ExtendedType<LocalTime> {
-
-    @Override
-    public String getClassName() {
-        return LocalTime.class.getName();
-    }
-
-    @Override
-    public void setJdbcObject(PreparedStatement statement, LocalTime value, int pos, int type, int scale) throws Exception {
-        statement.setTime(pos, Time.valueOf(value));
-    }
-
-    @Override
-    public LocalTime materializeObject(ResultSet rs, int index, int type) throws Exception {
-        Time time = rs.getTime(index);
-        return time != null ? time.toLocalTime() : null;
-    }
-
-    @Override
-    public LocalTime materializeObject(CallableStatement rs, int index, int type) throws Exception {
-        Time time = rs.getTime(index);
-        return time != null ? time.toLocalTime() : null;
-    }
-
-    @Override
-    public String toString(LocalTime value) {
-        if (value == null) {
-            return "NULL";
-        }
-
-        return '\'' + value.toString() + '\'';
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalTimeValueType.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalTimeValueType.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalTimeValueType.java
new file mode 100644
index 0000000..7843a3d
--- /dev/null
+++ b/cayenne-java8/src/main/java/org/apache/cayenne/java8/access/types/LocalTimeValueType.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.java8.access.types;
+
+import java.sql.Time;
+import java.time.LocalTime;
+
+import org.apache.cayenne.access.types.ValueObjectType;
+
+/**
+ * @since 4.0
+ */
+public class LocalTimeValueType implements ValueObjectType<LocalTime, Time> {
+
+    @Override
+    public Class<Time> getTargetType() {
+        return Time.class;
+    }
+
+    @Override
+    public Class<LocalTime> getValueType() {
+        return LocalTime.class;
+    }
+
+    @Override
+    public LocalTime toJavaObject(Time value) {
+        return value.toLocalTime();
+    }
+
+    @Override
+    public Time fromJavaObject(LocalTime object) {
+        return Time.valueOf(object);
+    }
+
+    @Override
+    public String toCacheKey(LocalTime object) {
+        return object.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigDecimalType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigDecimalType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigDecimalType.java
index 626e794..5423442 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigDecimalType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigDecimalType.java
@@ -39,8 +39,7 @@ public class BigDecimalType implements ExtendedType<BigDecimal> {
     }
 
     @Override
-    public BigDecimal materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public BigDecimal materializeObject(CallableStatement rs, int index, int type) throws Exception {
         return rs.getBigDecimal(index);
     }
 
@@ -54,8 +53,7 @@ public class BigDecimalType implements ExtendedType<BigDecimal> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setBigDecimal(pos, value);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigIntegerType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigIntegerType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigIntegerType.java
deleted file mode 100644
index f6c10b1..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigIntegerType.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.types;
-
-import org.apache.cayenne.dba.TypesMapping;
-
-import java.math.BigInteger;
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-
-/**
- * @since 3.0
- */
-public class BigIntegerType implements ExtendedType<BigInteger> {
-
-    @Override
-    public String getClassName() {
-        return BigInteger.class.getName();
-    }
-
-    @Override
-    public BigInteger materializeObject(ResultSet rs, int index, int type) throws Exception {
-        Object object = rs.getObject(index);
-        if (object == null) {
-            return null;
-        }
-
-        return new BigInteger(object.toString());
-    }
-
-    @Override
-    public BigInteger materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
-        Object object = rs.getObject(index);
-        if (object == null) {
-            return null;
-        }
-
-        return new BigInteger(object.toString());
-    }
-
-    @Override
-    public void setJdbcObject(
-            PreparedStatement statement,
-            BigInteger value,
-            int pos,
-            int type,
-            int precision) throws Exception {
-
-        if (value == null) {
-            statement.setNull(pos, type);
-        }
-        else if (TypesMapping.isNumeric(type)) {
-            statement.setLong(pos, ((BigInteger) value).longValue());
-        }
-        else {
-            throw new IllegalArgumentException(
-                    "Can't map BigInteger to a non-numeric type: "
-                            + TypesMapping.getSqlNameByType(type));
-        }
-    }
-
-    @Override
-    public String toString(BigInteger value) {
-        if (value == null) {
-            return "NULL";
-        }
-
-        return value.toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigIntegerValueType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigIntegerValueType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigIntegerValueType.java
new file mode 100644
index 0000000..890a4b2
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/BigIntegerValueType.java
@@ -0,0 +1,53 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.types;
+
+import java.math.BigInteger;
+
+/**
+ * @since 4.0
+ */
+public class BigIntegerValueType implements ValueObjectType<BigInteger, Long> {
+
+    @Override
+    public Class<Long> getTargetType() {
+        return Long.class;
+    }
+
+    @Override
+    public Class<BigInteger> getValueType() {
+        return BigInteger.class;
+    }
+
+    @Override
+    public BigInteger toJavaObject(Long value) {
+        return new BigInteger(value.toString());
+    }
+
+    @Override
+    public Long fromJavaObject(BigInteger object) {
+        return object.longValue();
+    }
+
+    @Override
+    public String toCacheKey(BigInteger object) {
+        return object.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/BooleanType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/BooleanType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/BooleanType.java
index 8de103d..d205240 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/BooleanType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/BooleanType.java
@@ -49,12 +49,10 @@ public class BooleanType implements ExtendedType<Boolean> {
 
         if (val == null) {
             st.setNull(pos, type);
-        }
-        else if (type == Types.BIT || type == Types.BOOLEAN) {
+        } else if (type == Types.BIT || type == Types.BOOLEAN) {
             boolean flag = Boolean.TRUE.equals(val);
             st.setBoolean(pos, flag);
-        }
-        else {
+        } else {
             st.setObject(pos, val, type);
         }
     }
@@ -66,8 +64,7 @@ public class BooleanType implements ExtendedType<Boolean> {
     }
 
     @Override
-    public Boolean materializeObject(CallableStatement st, int index, int type)
-            throws Exception {
+    public Boolean materializeObject(CallableStatement st, int index, int type) throws Exception {
         boolean b = st.getBoolean(index);
         return (st.wasNull()) ? null : b ? Boolean.TRUE : Boolean.FALSE;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteOrCharArrayFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteOrCharArrayFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteOrCharArrayFactory.java
index 2b1b83f..15ae4fb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteOrCharArrayFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteOrCharArrayFactory.java
@@ -36,31 +36,25 @@ class ByteOrCharArrayFactory implements ExtendedTypeFactory {
         this.map = map;
     }
 
+    @SuppressWarnings("unchecked")
     public ExtendedType getType(Class<?> objectClass) {
 
         if (objectClass.isArray()) {
-
             Class<?> elementType = objectClass.getComponentType();
-
             if (Character.class.isAssignableFrom(elementType)) {
                 // can't use "getRegisteredType" as it causes infinite recursion
                 ExtendedType<String> stringType = map.getExplictlyRegisteredType("java.lang.String");
                 return new CharacterArrayType(stringType);
-            }
-            else if (Character.TYPE.isAssignableFrom(elementType)) {
-
+            } else if (Character.TYPE.isAssignableFrom(elementType)) {
                 // can't use "getRegisteredType" as it causes infinite recursion
                 ExtendedType<String> stringType = map.getExplictlyRegisteredType("java.lang.String");
                 return new CharArrayType(stringType);
-            }
-            else if (Byte.class.isAssignableFrom(elementType)) {
+            } else if (Byte.class.isAssignableFrom(elementType)) {
                 // can't use "getRegisteredType" as it causes infinite recursion
                 ExtendedType<byte[]> bytesType = map.getExplictlyRegisteredType("byte[]");
                 return new ByteWrapperArrayType(bytesType);
             }
-        }
-        else if (Character.class.isAssignableFrom(objectClass)) {
-
+        } else if (Character.class.isAssignableFrom(objectClass)) {
             // can't use "getRegisteredType" as it causes infinite recursion
             ExtendedType<String> stringType = map.getExplictlyRegisteredType("java.lang.String");
             return new CharacterType(stringType);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteType.java
index b363bbf..fab946c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ByteType.java
@@ -53,8 +53,7 @@ public class ByteType implements ExtendedType<Byte> {
     }
 
     @Override
-    public Byte materializeObject(CallableStatement st, int index, int type)
-            throws Exception {
+    public Byte materializeObject(CallableStatement st, int index, int type) throws Exception {
         byte b = st.getByte(index);
         return (st.wasNull()) ? null : b;
     }
@@ -70,12 +69,10 @@ public class ByteType implements ExtendedType<Byte> {
         if (value == null) {
             statement.setNull(pos, type);
         } else {
-
-            Byte b = value;
             if (widenBytes) {
-                statement.setInt(pos, b.intValue());
+                statement.setInt(pos, value.intValue());
             } else {
-                statement.setByte(pos, b.byteValue());
+                statement.setByte(pos, value);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/DateType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/DateType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/DateType.java
index 654e1fe..1c732e9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/DateType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/DateType.java
@@ -39,8 +39,7 @@ public class DateType implements ExtendedType<Date> {
     }
 
     @Override
-    public Date materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public Date materializeObject(CallableStatement rs, int index, int type) throws Exception {
         return rs.getDate(index);
     }
 
@@ -54,8 +53,7 @@ public class DateType implements ExtendedType<Date> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setDate(pos, value);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/DefaultValueObjectTypeRegistry.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/DefaultValueObjectTypeRegistry.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/DefaultValueObjectTypeRegistry.java
new file mode 100644
index 0000000..3dda771
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/DefaultValueObjectTypeRegistry.java
@@ -0,0 +1,104 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.types;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.cayenne.di.Inject;
+
+/**
+ * Default implementation of {@link ValueObjectTypeRegistry}
+ * @since 4.0
+ */
+public class DefaultValueObjectTypeRegistry implements ValueObjectTypeRegistry {
+
+    final Map<String, ValueObjectType<?,?>> typeCache;
+
+    public DefaultValueObjectTypeRegistry(@Inject List<ValueObjectType<?, ?>> valueObjectTypeList) {
+        typeCache = new ConcurrentHashMap<>();
+        buildTypeCache(valueObjectTypeList);
+    }
+
+    private void buildTypeCache(List<ValueObjectType<?, ?>> valueObjectTypeList) {
+        for(ValueObjectType<?, ?> valueObjectType : valueObjectTypeList) {
+            typeCache.put(valueObjectType.getValueType().getName(), valueObjectType);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> ValueObjectType<T, ?> getValueType(Class<? extends T> valueClass) {
+        ValueObjectType type = typeCache.get(valueClass.getName());
+        if(type == null) {
+            type = findBySuperclasses(valueClass);
+        } else if(type == NULL_DUMMY) {
+            return null;
+        }
+        return type;
+    }
+
+    protected ValueObjectType<?, ?> findBySuperclasses(final Class<?> baseClass) {
+        ValueObjectType<?,?> type = null;
+        Class<?> searchClass = baseClass.getSuperclass();
+
+        while(searchClass != null && !searchClass.equals(Object.class)) {
+            type = typeCache.get(searchClass.getName());
+            if(type != null) {
+                // cache it for faster search later
+                typeCache.put(baseClass.getName(), type);
+                break;
+            }
+            searchClass = searchClass.getSuperclass();
+        }
+        // as well cache null result
+        if(type == null) {
+            typeCache.put(baseClass.getName(), NULL_DUMMY);
+        }
+        return type;
+    }
+
+    private static final ValueObjectType<?, ?> NULL_DUMMY = new ValueObjectType() {
+        @Override
+        public Class<?> getTargetType() {
+            return null;
+        }
+        @Override
+        public Class<?> getValueType() {
+            return null;
+        }
+        @Override
+        public Object toJavaObject(Object value) {
+            return null;
+        }
+        @Override
+        public Object fromJavaObject(Object object) {
+            return null;
+        }
+        @Override
+        public String toCacheKey(Object object) {
+            return null;
+        }
+    };
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/DoubleType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/DoubleType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/DoubleType.java
index cfdd91f..28c6fe5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/DoubleType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/DoubleType.java
@@ -39,8 +39,7 @@ public class DoubleType implements ExtendedType<Double> {
     }
 
     @Override
-    public Double materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public Double materializeObject(CallableStatement rs, int index, int type) throws Exception {
         double d = rs.getDouble(index);
         return rs.wasNull() ? null : d;
     }
@@ -55,8 +54,7 @@ public class DoubleType implements ExtendedType<Double> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setDouble(pos, value);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumType.java
index f8c4192..04182b2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumType.java
@@ -31,9 +31,6 @@ import java.sql.ResultSet;
  * An ExtendedType that handles an enum class. If Enum is mapped to a character column,
  * its name is used as persistent value; if it is mapped to a numeric column, its ordinal
  * (i.e. a position in enum class) is used.
- * <p>
- * <i>Requires Java 1.5 or newer</i>
- * </p>
  * 
  * @since 1.2
  */
@@ -54,11 +51,8 @@ public class EnumType<T extends Enum<T>> implements ExtendedType<T> {
         try {
             Method m = enumClass.getMethod("values");
             this.values = (T[]) m.invoke(null);
-        }
-        catch (Exception e) {
-            throw new IllegalArgumentException("Class "
-                    + enumClass.getName()
-                    + " is not an Enum", e);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Class " + enumClass.getName() + " is not an Enum", e);
         }
     }
 
@@ -78,12 +72,10 @@ public class EnumType<T extends Enum<T>> implements ExtendedType<T> {
         if (value != null) {
             if (TypesMapping.isNumeric(type)) {
                 statement.setInt(pos, value.ordinal());
-            }
-            else {
+            } else {
                 statement.setString(pos, value.name());
             }
-        }
-        else {
+        } else {
             statement.setNull(pos, type);
         }
     }
@@ -93,21 +85,18 @@ public class EnumType<T extends Enum<T>> implements ExtendedType<T> {
         if (TypesMapping.isNumeric(type)) {
             int i = rs.getInt(index);
             return (rs.wasNull() || index < 0) ? null : values[i];
-        }
-        else {
+        } else {
             String string = rs.getString(index);
             return string != null ? Enum.valueOf(enumClass, string) : null;
         }
     }
 
     @Override
-    public T materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public T materializeObject(CallableStatement rs, int index, int type) throws Exception {
         if (TypesMapping.isNumeric(type)) {
             int i = rs.getInt(index);
             return (rs.wasNull() || index < 0) ? null : values[i];
-        }
-        else {
+        } else {
             String string = rs.getString(index);
             return string != null ? Enum.valueOf(enumClass, string) : null;
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumTypeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumTypeFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumTypeFactory.java
index 7d28bdb..e1e77ec 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumTypeFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/EnumTypeFactory.java
@@ -22,7 +22,7 @@ package org.apache.cayenne.access.types;
 import org.apache.cayenne.ExtendedEnumeration;
 
 /**
- * ExtendedTypeFactory for handling JDK 1.5 Enums.
+ * ExtendedTypeFactory for handling Enum types.
  * 
  * @since 3.0
  */
@@ -30,12 +30,13 @@ public class EnumTypeFactory implements ExtendedTypeFactory {
 
     @SuppressWarnings("unchecked")
     public ExtendedType getType(Class<?> objectClass) {
-        if (ExtendedEnumeration.class.isAssignableFrom(objectClass))
+        if (ExtendedEnumeration.class.isAssignableFrom(objectClass)) {
             return new ExtendedEnumType(objectClass);
-        else if (objectClass.isEnum())
+        } else if (objectClass.isEnum()) {
             return new EnumType(objectClass);
-        else
+        } else {
             return null;
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedEnumType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedEnumType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedEnumType.java
index ed819fb..17151cb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedEnumType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedEnumType.java
@@ -56,18 +56,12 @@ public class ExtendedEnumType<T extends Enum<T>> implements ExtendedType<T> {
 
         try {
             Method m = enumerationClass.getMethod("values");
-
             values = (T[]) m.invoke(null);
-
-            for (int i = 0; i < values.length; i++)
-                register(values[i], ((ExtendedEnumeration) values[i])
-                        .getDatabaseValue());
-
-        }
-        catch (Exception e) {
-            throw new IllegalArgumentException("Class "
-                    + enumerationClass.getName()
-                    + " is not an Enum", e);
+            for (T value : values) {
+                register(value, ((ExtendedEnumeration) value).getDatabaseValue());
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Class " + enumerationClass.getName() + " is not an Enum", e);
         }
     }
 
@@ -81,21 +75,18 @@ public class ExtendedEnumType<T extends Enum<T>> implements ExtendedType<T> {
         if (TypesMapping.isNumeric(type)) {
             int i = rs.getInt(index);
             return (rs.wasNull() || index < 0) ? null : lookup(i);
-        }
-        else {
+        } else {
             String string = rs.getString(index);
             return string != null ? lookup(string) : null;
         }
     }
 
     @Override
-    public T materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public T materializeObject(CallableStatement rs, int index, int type) throws Exception {
         if (TypesMapping.isNumeric(type)) {
             int i = rs.getInt(index);
             return (rs.wasNull() || index < 0) ? null : lookup(i);
-        }
-        else {
+        } else {
             String string = rs.getString(index);
             return string != null ? lookup(string) : null;
         }
@@ -110,13 +101,12 @@ public class ExtendedEnumType<T extends Enum<T>> implements ExtendedType<T> {
             int precision) throws Exception {
         if (value instanceof ExtendedEnumeration) {
             ExtendedEnumeration e = (ExtendedEnumeration) value;
-
-            if (TypesMapping.isNumeric(type))
+            if (TypesMapping.isNumeric(type)) {
                 statement.setInt(pos, (Integer) e.getDatabaseValue());
-            else
+            } else {
                 statement.setString(pos, (String) e.getDatabaseValue());
-        }
-        else {
+            }
+        } else {
             statement.setNull(pos, type);
         }
     }
@@ -163,13 +153,15 @@ public class ExtendedEnumType<T extends Enum<T>> implements ExtendedType<T> {
         buffer.append(value.name()).append("=");
         if (value instanceof ExtendedEnumeration) {
             Object dbValue = ((ExtendedEnumeration) value).getDatabaseValue();
-            if (dbValue instanceof String)
+            if (dbValue instanceof String) {
                 buffer.append("'");
+            }
             buffer.append(value);
-            if (dbValue instanceof String)
+            if (dbValue instanceof String) {
                 buffer.append("'");
+            }
         } else {
-            buffer.append((value).ordinal());
+            buffer.append(value.ordinal());
             // FIXME -- this isn't quite right
         }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedType.java
index b30f674..8ace896 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedType.java
@@ -70,7 +70,7 @@ public interface ExtendedType<T> {
     /**
      * Converts value of the supported type to a human-readable String representation.
      *
-     * @param value a vlue to convert to String.
+     * @param value a value to convert to String.
      * @since 4.0
      */
     String toString(T value);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedTypeMap.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedTypeMap.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedTypeMap.java
index 387fe77..de620ec 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedTypeMap.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ExtendedTypeMap.java
@@ -55,9 +55,7 @@ public class ExtendedTypeMap {
 
 	Collection<ExtendedTypeFactory> extendedTypeFactories;
 
-	// standard type factories registered by Cayenne that are consulted after
-	// the user
-	// factories.
+	// standard type factories registered by Cayenne that are consulted after the user factories.
 	Collection<ExtendedTypeFactory> internalTypeFactories;
 
 	/**
@@ -87,8 +85,7 @@ public class ExtendedTypeMap {
 		internalTypeFactories.add(new ByteOrCharArrayFactory(this));
 
 		// note that Serializable type should be used as a last resort after all
-		// other
-		// alternatives are exhausted.
+		// other alternatives are exhausted.
 		internalTypeFactories.add(new SerializableTypeFactory(this));
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/FloatType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/FloatType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/FloatType.java
index 51713db..1dc98ad 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/FloatType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/FloatType.java
@@ -39,8 +39,7 @@ public class FloatType implements ExtendedType<Float> {
     }
 
     @Override
-    public Float materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public Float materializeObject(CallableStatement rs, int index, int type) throws Exception {
         float f = rs.getFloat(index);
         return rs.wasNull() ? null : f;
     }
@@ -55,8 +54,7 @@ public class FloatType implements ExtendedType<Float> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setFloat(pos, value);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/IntegerType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/IntegerType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/IntegerType.java
index 0d2ec62..8562b9a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/IntegerType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/IntegerType.java
@@ -39,8 +39,7 @@ public class IntegerType implements ExtendedType<Integer> {
     }
 
     @Override
-    public Integer materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public Integer materializeObject(CallableStatement rs, int index, int type) throws Exception {
         int value = rs.getInt(index);
         return (rs.wasNull()) ? null : value;
     }
@@ -55,8 +54,7 @@ public class IntegerType implements ExtendedType<Integer> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setInt(pos, value);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/LongType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/LongType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/LongType.java
index 287190e..dcfdf66 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/LongType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/LongType.java
@@ -39,8 +39,7 @@ public class LongType implements ExtendedType<Long> {
     }
 
     @Override
-    public Long materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public Long materializeObject(CallableStatement rs, int index, int type) throws Exception {
         long value = rs.getLong(index);
         return (rs.wasNull()) ? null : value;
     }
@@ -55,8 +54,7 @@ public class LongType implements ExtendedType<Long> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setLong(pos, value);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ObjectType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ObjectType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ObjectType.java
index 2473177..f4d7188 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ObjectType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ObjectType.java
@@ -35,8 +35,7 @@ public class ObjectType implements ExtendedType<Object> {
     }
 
     @Override
-    public Object materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public Object materializeObject(CallableStatement rs, int index, int type) throws Exception {
         return rs.getObject(index);
     }
 
@@ -54,8 +53,7 @@ public class ObjectType implements ExtendedType<Object> {
             int scale) throws Exception {
         if (scale != -1) {
             statement.setObject(pos, value, type, scale);
-        }
-        else {
+        } else {
             statement.setObject(pos, value, type);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/SerializableTypeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/SerializableTypeFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/SerializableTypeFactory.java
index e37eed5..5fe7d0d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/SerializableTypeFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/SerializableTypeFactory.java
@@ -52,13 +52,11 @@ class SerializableTypeFactory implements ExtendedTypeFactory {
 			logger.warn("SerializableType will be used for type conversion.");
 
 			// using a binary stream delegate instead of byte[] may actually
-			// speed up
-			// things in some dbs, but at least byte[] type works consistently
-			// across
-			// adapters...
+			// speed up things in some dbs, but at least byte[] type works consistently
+			// across adapters...
 
-			// note - can't use "getRegisteredType" as it causes infinite
-			// recursion
+			// note - can't use "getRegisteredType" as it causes infinite recursion
+			@SuppressWarnings("unchecked")
 			ExtendedType<byte[]> bytesType = map.getExplictlyRegisteredType("byte[]");
 			return new SerializableType(objectClass, bytesType);
 		}
@@ -86,7 +84,6 @@ class SerializableTypeFactory implements ExtendedTypeFactory {
 		@Override
 		byte[] fromJavaObject(Object object) {
 			ByteArrayOutputStream bytes = new ByteArrayOutputStream() {
-
 				// avoid unneeded array copy...
 				@Override
 				public synchronized byte[] toByteArray() {
@@ -94,7 +91,7 @@ class SerializableTypeFactory implements ExtendedTypeFactory {
 				}
 			};
 
-			try (ObjectOutputStream out = new ObjectOutputStream(bytes);) {
+			try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
 				out.writeObject(object);
 			} catch (Exception e) {
 				throw new CayenneRuntimeException("Error serializing object", e);
@@ -106,8 +103,8 @@ class SerializableTypeFactory implements ExtendedTypeFactory {
 		@Override
 		Object toJavaObject(byte[] bytes) {
 			try {
-				return bytes != null && bytes.length > 0 ? new ObjectInputStream(new ByteArrayInputStream(bytes))
-						.readObject() : null;
+				return bytes != null && bytes.length > 0
+						? new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject() : null;
 			} catch (Exception e) {
 				throw new CayenneRuntimeException("Error deserializing object", e);
 			}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ShortType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ShortType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ShortType.java
index 257f7cc..2295c52 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ShortType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ShortType.java
@@ -51,8 +51,7 @@ public class ShortType implements ExtendedType<Short> {
     }
 
     @Override
-    public Short materializeObject(CallableStatement st, int index, int type)
-            throws Exception {
+    public Short materializeObject(CallableStatement st, int index, int type) throws Exception {
         short s = st.getShort(index);
         return (st.wasNull()) ? null : s;
     }
@@ -67,13 +66,10 @@ public class ShortType implements ExtendedType<Short> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
-
+        } else {
             if (widenShorts) {
                 statement.setInt(pos, value.intValue());
-            }
-            else {
+            } else {
                 statement.setShort(pos, value);
             }
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/SubclassTypeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/SubclassTypeFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/SubclassTypeFactory.java
index d493259..6938759 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/SubclassTypeFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/SubclassTypeFactory.java
@@ -43,8 +43,7 @@ class SubclassTypeFactory implements ExtendedTypeFactory {
                     || javaClass.isPrimitive()) {
                 javaClass = null;
             }
-        }
-        catch (ClassNotFoundException e) {
+        } catch (ClassNotFoundException e) {
             // ignore.
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimeType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimeType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimeType.java
index 8966f82..78f1b78 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimeType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimeType.java
@@ -39,8 +39,7 @@ public class TimeType implements ExtendedType<Time> {
     }
 
     @Override
-    public Time materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
+    public Time materializeObject(CallableStatement rs, int index, int type) throws Exception {
         return rs.getTime(index);
     }
 
@@ -54,8 +53,7 @@ public class TimeType implements ExtendedType<Time> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setTime(pos, value);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimestampType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimestampType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimestampType.java
index d6b6aca..ab25fa9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimestampType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/TimestampType.java
@@ -39,8 +39,7 @@ public class TimestampType implements ExtendedType<Timestamp> {
     }
 
     @Override
-    public Timestamp materializeObject(CallableStatement cs, int index, int type)
-            throws Exception {
+    public Timestamp materializeObject(CallableStatement cs, int index, int type) throws Exception {
         return cs.getTimestamp(index);
     }
 
@@ -54,8 +53,7 @@ public class TimestampType implements ExtendedType<Timestamp> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setTimestamp(pos, value);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/UUIDType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/UUIDType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/UUIDType.java
deleted file mode 100644
index fd700ea..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/UUIDType.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.types;
-
-import org.apache.cayenne.CayenneRuntimeException;
-
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.util.UUID;
-
-/**
- * An ExtendedType to map Java UUIDs as persistent attributes.
- * 
- * @since 3.0
- */
-public class UUIDType implements ExtendedType<UUID> {
-
-    @Override
-    public String getClassName() {
-        return UUID.class.getName();
-    }
-
-    @Override
-    public UUID materializeObject(ResultSet rs, int index, int type) throws Exception {
-        String uuid = rs.getString(index);
-        if (uuid == null) {
-            return null;
-        }
-
-        try {
-            return UUID.fromString(uuid);
-        }
-        catch (IllegalArgumentException e) {
-            throw new CayenneRuntimeException("Invalid UUID value: " + uuid, e);
-        }
-    }
-
-    @Override
-    public UUID materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
-
-        String uuid = rs.getString(index);
-        if (uuid == null) {
-            return null;
-        }
-
-        try {
-            return UUID.fromString(uuid);
-        }
-        catch (IllegalArgumentException e) {
-            throw new CayenneRuntimeException("Invalid UUID value: " + uuid, e);
-        }
-    }
-
-    @Override
-    public void setJdbcObject(
-            PreparedStatement statement,
-            UUID value,
-            int pos,
-            int type,
-            int scale) throws Exception {
-
-        if (value == null) {
-            statement.setNull(pos, type);
-        } else {
-            statement.setObject(pos, value.toString(), type);
-        }
-    }
-
-    @Override
-    public String toString(UUID value) {
-        if (value == null) {
-            return "NULL";
-        }
-
-        return value.toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/UUIDValueType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/UUIDValueType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/UUIDValueType.java
new file mode 100644
index 0000000..6e63ac5
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/UUIDValueType.java
@@ -0,0 +1,59 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.types;
+
+import java.util.UUID;
+
+import org.apache.cayenne.CayenneRuntimeException;
+
+/**
+ * @since 4.0
+ */
+public class UUIDValueType implements ValueObjectType<UUID, String> {
+
+    @Override
+    public Class<String> getTargetType() {
+        return String.class;
+    }
+
+    @Override
+    public Class<UUID> getValueType() {
+        return UUID.class;
+    }
+
+    @Override
+    public UUID toJavaObject(String value) {
+        try {
+            return UUID.fromString(value);
+        } catch (IllegalArgumentException e) {
+            throw new CayenneRuntimeException("Invalid UUID value: " + value, e);
+        }
+    }
+
+    @Override
+    public String fromJavaObject(UUID object) {
+        return object.toString();
+    }
+
+    @Override
+    public String toCacheKey(UUID object) {
+        return object.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/UtilDateType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/UtilDateType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/UtilDateType.java
index 9fa5010..5e3fb82 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/types/UtilDateType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/UtilDateType.java
@@ -42,24 +42,22 @@ public class UtilDateType implements ExtendedType<Date> {
     }
 
     protected Object convertToJdbcObject(Date val, int type) throws Exception {
-        if (type == Types.DATE)
+        if (type == Types.DATE) {
             return new java.sql.Date(val.getTime());
-        else if (type == Types.TIME)
+        } else if (type == Types.TIME) {
             return new java.sql.Time(val.getTime());
-        else if (type == Types.TIMESTAMP)
+        } else if (type == Types.TIMESTAMP) {
             return new java.sql.Timestamp(val.getTime());
-        else
+        } else {
             throw new IllegalArgumentException(
-                    "Only DATE, TIME or TIMESTAMP can be mapped as '"
-                            + getClassName()
-                            + "', got "
-                            + TypesMapping.getSqlNameByType(type));
+                    "Only DATE, TIME or TIMESTAMP can be mapped as '" + getClassName()
+                            + "', got " + TypesMapping.getSqlNameByType(type));
+        }
     }
 
     @Override
     public Date materializeObject(ResultSet rs, int index, int type) throws Exception {
-        Date val = null;
-
+        Date val;
         switch (type) {
             case Types.TIMESTAMP:
                 val = rs.getTimestamp(index);
@@ -80,10 +78,8 @@ public class UtilDateType implements ExtendedType<Date> {
     }
 
     @Override
-    public Date materializeObject(CallableStatement cs, int index, int type)
-            throws Exception {
-        Date val = null;
-
+    public Date materializeObject(CallableStatement cs, int index, int type) throws Exception {
+        Date val;
         switch (type) {
             case Types.TIMESTAMP:
                 val = cs.getTimestamp(index);
@@ -113,8 +109,7 @@ public class UtilDateType implements ExtendedType<Date> {
 
         if (value == null) {
             statement.setNull(pos, type);
-        }
-        else {
+        } else {
             statement.setObject(pos, convertToJdbcObject(value, type), type);
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectType.java
new file mode 100644
index 0000000..f66799c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectType.java
@@ -0,0 +1,63 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.types;
+
+/**
+ * Descriptor and serialization helper for custom value objects that can be safely stored in the DB.
+ * Lightweight alternative for the {@link ExtendedType}.
+ *
+ * @param <V> type of user's custom object.
+ * @param <T> type that custom object will be serialized to/from
+ *            should be backed by appropriate {@link ExtendedType}.
+ *
+ * @since 4.0
+ */
+public interface ValueObjectType<V, T> {
+
+    /**
+     * @return base type used to serialize <b>V</b> objects to.
+     */
+    Class<T> getTargetType();
+
+    /**
+     * @return type of Objects described by this ValueObjectType.
+     */
+    Class<V> getValueType();
+
+    /**
+     * @param value of type T
+     * @return java object
+     */
+    V toJavaObject(T value);
+
+    /**
+     * @param object java object
+     * @return value of type T
+     */
+    T fromJavaObject(V object);
+
+    /**
+     * Returned value should be same for objects that is logically equal.
+     *
+     * @return String representation usable for cache.
+     */
+    String toCacheKey(V object);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectTypeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectTypeFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectTypeFactory.java
new file mode 100644
index 0000000..5ce4273
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectTypeFactory.java
@@ -0,0 +1,101 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.types;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+
+/**
+ * @since 4.0
+ */
+public class ValueObjectTypeFactory implements ExtendedTypeFactory {
+
+    ValueObjectTypeRegistry valueObjectTypeRegistry;
+
+    private ExtendedTypeMap map;
+
+    public ValueObjectTypeFactory(ExtendedTypeMap map, ValueObjectTypeRegistry valueObjectTypeRegistry) {
+        this.map = map;
+        this.valueObjectTypeRegistry = valueObjectTypeRegistry;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public ExtendedType<? extends ValueObjectType> getType(Class<?> objectClass) {
+
+        ValueObjectType<?, ?> valueObjectType = valueObjectTypeRegistry.getValueType(objectClass);
+        if(valueObjectType == null) {
+            return null;
+        }
+        ExtendedType<?> decorator = map.getExplictlyRegisteredType(valueObjectType.getTargetType().getName());
+
+        return new ExtendedTypeConverter(decorator, valueObjectType);
+    }
+
+    static class ExtendedTypeConverter<T, E> implements ExtendedType<T> {
+
+        private ExtendedType<E> extendedType;
+        private ValueObjectType<T, E> valueObjectType;
+
+        ExtendedTypeConverter(ExtendedType<E> extendedType, ValueObjectType<T, E> valueObjectType) {
+            this.extendedType = extendedType;
+            this.valueObjectType = valueObjectType;
+        }
+
+        @Override
+        public String getClassName() {
+            return valueObjectType.getValueType().getName();
+        }
+
+        protected T toJavaObject(E materializedValue) {
+            if(materializedValue == null) {
+                return null;
+            }
+            return valueObjectType.toJavaObject(materializedValue);
+        }
+
+        @Override
+        public T materializeObject(CallableStatement rs, int index, int type) throws Exception {
+            return toJavaObject(extendedType.materializeObject(rs, index, type));
+        }
+
+        @Override
+        public T materializeObject(ResultSet rs, int index, int type) throws Exception {
+            return toJavaObject(extendedType.materializeObject(rs, index, type));
+        }
+
+        @Override
+        public void setJdbcObject(PreparedStatement statement, T value, int pos, int type, int precision) throws Exception {
+            E dbValue = value == null ? null : valueObjectType.fromJavaObject(value);
+            extendedType.setJdbcObject(statement, dbValue, pos, type, precision);
+        }
+
+        @Override
+        public String toString(T value) {
+            if (value == null) {
+                return "NULL";
+            }
+
+            return valueObjectType.toCacheKey(value);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectTypeRegistry.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectTypeRegistry.java b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectTypeRegistry.java
new file mode 100644
index 0000000..1af249f
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/types/ValueObjectTypeRegistry.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.types;
+
+/**
+ * <p>Registry of user-defined descriptors of custom value objects' classes.</p>
+ * <p>Used to convert this objects into values that can be used directly with JDBC.</p>
+ * <p>Part of replacement for the {@link ExtendedType ExtendedTypes API} for the end-user.</p>
+ *
+ * @see ValueObjectType
+ * @since 4.0
+ */
+public interface ValueObjectTypeRegistry {
+
+    /**
+     * Lookup descriptor in this registry.
+     *
+     * @param valueClass class of the custom value object
+     * @return {@link ValueObjectType} descriptor
+     */
+    <T> ValueObjectType<T,?> getValueType(Class<? extends T> valueClass);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/5e9f0e0f/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java
index 67ad5d0..f282939 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java
@@ -24,6 +24,7 @@ import org.apache.cayenne.DataChannelFilter;
 import org.apache.cayenne.access.DataDomain;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.DataRowStoreFactory;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.cache.NestedQueryCache;
 import org.apache.cayenne.cache.QueryCache;
 import org.apache.cayenne.configuration.ConfigurationTree;
@@ -88,6 +89,9 @@ public class DataDomainProvider implements Provider<DataDomain> {
 	@Inject
 	protected DataNodeFactory dataNodeFactory;
 
+	@Inject
+	protected ValueObjectTypeRegistry valueObjectTypeRegistry;
+
 	@Override
 	public DataDomain get() throws ConfigurationException {
 
@@ -127,6 +131,7 @@ public class DataDomainProvider implements Provider<DataDomain> {
 
 		dataDomain.getEntityResolver().applyDBLayerDefaults();
 		dataDomain.getEntityResolver().applyObjectLayerDefaults();
+		dataDomain.getEntityResolver().setValueObjectTypeRegistry(valueObjectTypeRegistry);
 
 		for (DataNodeDescriptor nodeDescriptor : descriptor.getNodeDescriptors()) {
 			addDataNode(dataDomain, nodeDescriptor);