cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject [1/2] cayenne git commit: CAY-2210 Query cache: incorrect cache key for queries with custom value objects
Date Fri, 31 Mar 2017 14:02:08 GMT
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


Mime
View raw message