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

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


The following commit(s) were added to refs/heads/master by this push:
     new bc19298  CAY-2728 Add ExtendedType to generate user-friendly 
exceptions for internally used values
bc19298 is described below

commit bc1929807b5db5f08258344db182e897b1c3d5b4
Author: Nikita Timofeev <stari...@gmail.com>
AuthorDate: Tue Jan 11 17:58:56 2022 +0300

    CAY-2728 Add ExtendedType to generate user-friendly exceptions for 
internally used values
---
 RELEASE-NOTES.txt                                  |   2 +
 .../cayenne/access/flush/IdGenerationMarker.java   |   8 +-
 .../access/flush/ObjectIdValueSupplier.java        |   8 +-
 .../types/InternalUnsupportedTypeFactory.java      |  82 +++++++++++++
 .../cayenne/configuration/server/ServerModule.java |   5 +-
 .../org/apache/cayenne/CircularDependencyIT.java   |  58 +++++++++
 .../apache/cayenne/testdo/relationships/E1.java}   |  34 +-----
 .../apache/cayenne/testdo/relationships/E2.java}   |  34 +-----
 .../cayenne/testdo/relationships/auto/_E1.java     | 130 +++++++++++++++++++++
 .../cayenne/testdo/relationships/auto/_E2.java     | 130 +++++++++++++++++++++
 .../cayenne/unit/di/server/ServerCaseModule.java   |   4 +-
 .../src/test/resources/relationships.map.xml       |  32 +++++
 12 files changed, 464 insertions(+), 63 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index c63b79c..2591d7c 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -13,6 +13,8 @@ Date:
 ----------------------------------
 Changes/New Features:
 
+CAY-2728 Add ExtendedType to generate user-friendly exceptions for internally 
used values
+
 Bug Fixes:
 
 CAY-2697 Read-only cgen template creates mutator methods for to-many 
relationships
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
index 1519556..2a10744 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
@@ -22,13 +22,14 @@ package org.apache.cayenne.access.flush;
 import java.io.Serializable;
 
 import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.access.types.InternalUnsupportedTypeFactory;
 
 /**
  * Special value that denotes generated id attribute
  *
  * @since 4.2
  */
-class IdGenerationMarker implements Serializable {
+class IdGenerationMarker implements Serializable, 
InternalUnsupportedTypeFactory.Marker {
     private static final long serialVersionUID = -5339942931435878094L;
 
     private final int id;
@@ -49,4 +50,9 @@ class IdGenerationMarker implements Serializable {
     public int hashCode() {
         return id;
     }
+
+    @Override
+    public String errorMessage() {
+        return "PK is not generated. Check your PK generation strategy or 
presence of the mutually dependent entities.";
+    }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/flush/ObjectIdValueSupplier.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/flush/ObjectIdValueSupplier.java
index da27b99..eac62ef 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/flush/ObjectIdValueSupplier.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/flush/ObjectIdValueSupplier.java
@@ -23,13 +23,14 @@ import java.util.Objects;
 import java.util.function.Supplier;
 
 import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.access.types.InternalUnsupportedTypeFactory;
 
 /**
  * Deferred value extracted from ObjectId
  *
  * @since 4.2
  */
-class ObjectIdValueSupplier implements Supplier<Object> {
+class ObjectIdValueSupplier implements Supplier<Object>, 
InternalUnsupportedTypeFactory.Marker {
 
     private final ObjectId id;
     private final String attribute;
@@ -79,4 +80,9 @@ class ObjectIdValueSupplier implements Supplier<Object> {
     public String toString() {
         return "{id=" + id + ", attr=" + attribute + '}';
     }
+
+    @Override
+    public String errorMessage() {
+        return "Value supplier is not resolved before usage.";
+    }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/types/InternalUnsupportedTypeFactory.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/types/InternalUnsupportedTypeFactory.java
new file mode 100644
index 0000000..461a98a
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/types/InternalUnsupportedTypeFactory.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
+ *
+ *    https://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;
+
+import org.apache.cayenne.CayenneRuntimeException;
+
+/**
+ * Extended type factory that produces types for Cayenne internal value types 
that could potentially make it to the DB.
+ * ExtendedTypes that produced by this factory just trying to throw 
user-friendly exception instead of unknown type one.
+ *
+ * @since 4.2
+ */
+public class InternalUnsupportedTypeFactory implements ExtendedTypeFactory {
+
+    @Override
+    public ExtendedType<?> getType(Class<?> objectClass) {
+        if(Marker.class.isAssignableFrom(objectClass)) {
+            return new ExtendedType<Marker>() {
+                @Override
+                public String getClassName() {
+                    return objectClass.getName();
+                }
+
+                @Override
+                public void setJdbcObject(PreparedStatement statement, Marker 
value, int pos, int type, int scale) {
+                    throw new CayenneRuntimeException(value.errorMessage());
+                }
+
+                @Override
+                public Marker materializeObject(ResultSet rs, int index, int 
type) {
+                    // this normally shouldn't happen
+                    throw new CayenneRuntimeException("Trying to materialize 
internal Cayenne value. Check your mapping or report an issue.");
+                }
+
+                @Override
+                public Marker materializeObject(CallableStatement rs, int 
index, int type) {
+                    // this normally shouldn't happen
+                    throw new CayenneRuntimeException("Trying to materialize 
internal Cayenne value. Check your mapping or report an issue.");
+                }
+
+                @Override
+                public String toString(Marker value) {
+                    return "Internal marker of type " + 
objectClass.getSimpleName();
+                }
+            };
+        }
+        return null;
+    }
+
+    /**
+     * Marker interface, that should be used by any internal value types, that 
could potentially get to the SQL
+     */
+    public interface Marker {
+        /**
+         *  Error message in case this object made it to the DB
+         */
+        default String errorMessage() {
+            return "Trying to use internal type in the query.";
+        }
+    }
+}
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 1d12d8f..edd6a15 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
@@ -61,7 +61,7 @@ import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.FloatType;
 import org.apache.cayenne.access.types.GeoJsonType;
 import org.apache.cayenne.access.types.IntegerType;
-import org.apache.cayenne.access.types.JsonType;
+import org.apache.cayenne.access.types.InternalUnsupportedTypeFactory;
 import org.apache.cayenne.access.types.LocalDateTimeValueType;
 import org.apache.cayenne.access.types.LocalDateValueType;
 import org.apache.cayenne.access.types.LocalTimeValueType;
@@ -434,7 +434,8 @@ public class ServerModule implements Module {
                 .add(GeoJsonType.class)
                 .add(WktType.class);
         contributeUserTypes(binder);
-        contributeTypeFactories(binder);
+        contributeTypeFactories(binder)
+                .add(new InternalUnsupportedTypeFactory());
 
         // Custom ValueObjects types contribution
         contributeValueObjectTypes(binder)
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/CircularDependencyIT.java 
b/cayenne-server/src/test/java/org/apache/cayenne/CircularDependencyIT.java
new file mode 100644
index 0000000..6cfc68f
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/CircularDependencyIT.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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;
+
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.testdo.relationships.E1;
+import org.apache.cayenne.testdo.relationships.E2;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+@UseServerRuntime(CayenneProjects.RELATIONSHIPS_PROJECT)
+public class CircularDependencyIT extends ServerCase {
+
+    @Inject
+    private ObjectContext context;
+    
+    @Test()
+    public void testCycle() {
+        E1 e1 = context.newObject(E1.class);
+        E2 e2 = context.newObject(E2.class);
+
+        e1.setText("e1 #" + 1);
+        e2.setText("e2 #" + 2);
+
+        e1.setE2(e2);
+        e2.setE1(e1);
+
+        try {
+            context.commitChanges();
+            fail("Exception should be thrown here");
+        } catch (CayenneRuntimeException ex) {
+            assertTrue("Unexpected exception message: " + ex.getMessage(),
+                    ex.getMessage().contains("PK is not generated"));
+        }
+
+    }
+}
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
 b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/E1.java
similarity index 53%
copy from 
cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
copy to 
cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/E1.java
index 1519556..60069b0 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/E1.java
@@ -7,7 +7,7 @@
  *  "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
+ *    https://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
@@ -17,36 +17,12 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.flush;
+package org.apache.cayenne.testdo.relationships;
 
-import java.io.Serializable;
+import org.apache.cayenne.testdo.relationships.auto._E1;
 
-import org.apache.cayenne.ObjectId;
+public class E1 extends _E1 {
 
-/**
- * Special value that denotes generated id attribute
- *
- * @since 4.2
- */
-class IdGenerationMarker implements Serializable {
-    private static final long serialVersionUID = -5339942931435878094L;
-
-    private final int id;
-
-    IdGenerationMarker(ObjectId id) {
-        this.id = id.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        IdGenerationMarker that = (IdGenerationMarker) o;
-        return id == that.id;
-    }
+    private static final long serialVersionUID = 1L;
 
-    @Override
-    public int hashCode() {
-        return id;
-    }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
 b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/E2.java
similarity index 53%
copy from 
cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
copy to 
cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/E2.java
index 1519556..bf1065a 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/flush/IdGenerationMarker.java
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/E2.java
@@ -7,7 +7,7 @@
  *  "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
+ *    https://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
@@ -17,36 +17,12 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.flush;
+package org.apache.cayenne.testdo.relationships;
 
-import java.io.Serializable;
+import org.apache.cayenne.testdo.relationships.auto._E2;
 
-import org.apache.cayenne.ObjectId;
+public class E2 extends _E2 {
 
-/**
- * Special value that denotes generated id attribute
- *
- * @since 4.2
- */
-class IdGenerationMarker implements Serializable {
-    private static final long serialVersionUID = -5339942931435878094L;
-
-    private final int id;
-
-    IdGenerationMarker(ObjectId id) {
-        this.id = id.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        IdGenerationMarker that = (IdGenerationMarker) o;
-        return id == that.id;
-    }
+    private static final long serialVersionUID = 1L;
 
-    @Override
-    public int hashCode() {
-        return id;
-    }
 }
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/auto/_E1.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/auto/_E1.java
new file mode 100644
index 0000000..26f81c1
--- /dev/null
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/auto/_E1.java
@@ -0,0 +1,130 @@
+package org.apache.cayenne.testdo.relationships.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.StringProperty;
+import org.apache.cayenne.testdo.relationships.E2;
+
+/**
+ * Class _E1 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _E1 extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final String ID_PK_COLUMN = "id";
+
+    public static final StringProperty<String> TEXT = 
PropertyFactory.createString("text", String.class);
+    public static final EntityProperty<E2> E2 = 
PropertyFactory.createEntity("e2", E2.class);
+    public static final ListProperty<E2> E2S = 
PropertyFactory.createList("e2s", E2.class);
+
+    protected String text;
+
+    protected Object e2;
+    protected Object e2s;
+
+    public void setText(String text) {
+        beforePropertyWrite("text", this.text, text);
+        this.text = text;
+    }
+
+    public String getText() {
+        beforePropertyRead("text");
+        return this.text;
+    }
+
+    public void setE2(E2 e2) {
+        setToOneTarget("e2", e2, true);
+    }
+
+    public E2 getE2() {
+        return (E2)readProperty("e2");
+    }
+
+    public void addToE2s(E2 obj) {
+        addToManyTarget("e2s", obj, true);
+    }
+
+    public void removeFromE2s(E2 obj) {
+        removeToManyTarget("e2s", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<E2> getE2s() {
+        return (List<E2>)readProperty("e2s");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "text":
+                return this.text;
+            case "e2":
+                return this.e2;
+            case "e2s":
+                return this.e2s;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "text":
+                this.text = (String)val;
+                break;
+            case "e2":
+                this.e2 = val;
+                break;
+            case "e2s":
+                this.e2s = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.text);
+        out.writeObject(this.e2);
+        out.writeObject(this.e2s);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        super.readState(in);
+        this.text = (String)in.readObject();
+        this.e2 = in.readObject();
+        this.e2s = in.readObject();
+    }
+
+}
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/auto/_E2.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/auto/_E2.java
new file mode 100644
index 0000000..1a5e35d
--- /dev/null
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships/auto/_E2.java
@@ -0,0 +1,130 @@
+package org.apache.cayenne.testdo.relationships.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.StringProperty;
+import org.apache.cayenne.testdo.relationships.E1;
+
+/**
+ * Class _E2 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _E2 extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final String ID_PK_COLUMN = "id";
+
+    public static final StringProperty<String> TEXT = 
PropertyFactory.createString("text", String.class);
+    public static final EntityProperty<E1> E1 = 
PropertyFactory.createEntity("e1", E1.class);
+    public static final ListProperty<E1> E1S = 
PropertyFactory.createList("e1s", E1.class);
+
+    protected String text;
+
+    protected Object e1;
+    protected Object e1s;
+
+    public void setText(String text) {
+        beforePropertyWrite("text", this.text, text);
+        this.text = text;
+    }
+
+    public String getText() {
+        beforePropertyRead("text");
+        return this.text;
+    }
+
+    public void setE1(E1 e1) {
+        setToOneTarget("e1", e1, true);
+    }
+
+    public E1 getE1() {
+        return (E1)readProperty("e1");
+    }
+
+    public void addToE1s(E1 obj) {
+        addToManyTarget("e1s", obj, true);
+    }
+
+    public void removeFromE1s(E1 obj) {
+        removeToManyTarget("e1s", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<E1> getE1s() {
+        return (List<E1>)readProperty("e1s");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "text":
+                return this.text;
+            case "e1":
+                return this.e1;
+            case "e1s":
+                return this.e1s;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "text":
+                this.text = (String)val;
+                break;
+            case "e1":
+                this.e1 = val;
+                break;
+            case "e1s":
+                this.e1s = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.text);
+        out.writeObject(this.e1);
+        out.writeObject(this.e1s);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        super.readState(in);
+        this.text = (String)in.readObject();
+        this.e1 = in.readObject();
+        this.e1s = in.readObject();
+    }
+
+}
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 27a8e5a..08e3801 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
@@ -42,6 +42,7 @@ import org.apache.cayenne.access.types.DoubleType;
 import org.apache.cayenne.access.types.DurationType;
 import org.apache.cayenne.access.types.FloatType;
 import org.apache.cayenne.access.types.IntegerType;
+import org.apache.cayenne.access.types.InternalUnsupportedTypeFactory;
 import org.apache.cayenne.access.types.LocalDateTimeValueType;
 import org.apache.cayenne.access.types.LocalDateValueType;
 import org.apache.cayenne.access.types.LocalTimeValueType;
@@ -223,7 +224,8 @@ public class ServerCaseModule implements Module {
                 .add(new CalendarType<>(Calendar.class))
                 .add(new DurationType());
         ServerModule.contributeUserTypes(binder);
-        ServerModule.contributeTypeFactories(binder);
+        ServerModule.contributeTypeFactories(binder)
+                .add(new InternalUnsupportedTypeFactory());
         ServerModule.contributeValueObjectTypes(binder)
                 .add(BigIntegerValueType.class)
                 .add(BigDecimalValueType.class)
diff --git a/cayenne-server/src/test/resources/relationships.map.xml 
b/cayenne-server/src/test/resources/relationships.map.xml
index b487db9..7f4b4f5 100644
--- a/cayenne-server/src/test/resources/relationships.map.xml
+++ b/cayenne-server/src/test/resources/relationships.map.xml
@@ -22,6 +22,22 @@
                <db-attribute name="NAME" type="VARCHAR" length="100"/>
                <db-attribute name="RELATIONSHIP_HELPER_ID" type="INTEGER" 
isPrimaryKey="true" isMandatory="true"/>
        </db-entity>
+       <db-entity name="CYCLE_E1">
+               <db-attribute name="e2_id" type="INTEGER"/>
+               <db-attribute name="id" type="INTEGER" isPrimaryKey="true" 
isGenerated="true" isMandatory="true"/>
+               <db-attribute name="text" type="VARCHAR" length="128"/>
+       </db-entity>
+       <db-entity name="CYCLE_E2">
+               <db-attribute name="e1_id" type="INTEGER"/>
+               <db-attribute name="id" type="INTEGER" isPrimaryKey="true" 
isGenerated="true" isMandatory="true"/>
+               <db-attribute name="text" type="VARCHAR" length="128"/>
+       </db-entity>
+       <obj-entity name="CYCLE_E1" 
className="org.apache.cayenne.testdo.relationships.E1" dbEntityName="CYCLE_E1">
+               <obj-attribute name="text" type="java.lang.String" 
db-attribute-path="text"/>
+       </obj-entity>
+       <obj-entity name="CYCLE_E2" 
className="org.apache.cayenne.testdo.relationships.E2" dbEntityName="CYCLE_E2">
+               <obj-attribute name="text" type="java.lang.String" 
db-attribute-path="text"/>
+       </obj-entity>
        <obj-entity name="FkOfDifferentType" 
className="org.apache.cayenne.testdo.relationships.FkOfDifferentType" 
dbEntityName="FK_OF_DIFFERENT_TYPE"/>
        <obj-entity name="MeaningfulFK" 
className="org.apache.cayenne.testdo.relationships.MeaningfulFK" 
dbEntityName="MEANINGFUL_FK">
                <obj-attribute name="relationshipHelperID" type="int" 
db-attribute-path="RELATIONSHIP_HELPER_ID"/>
@@ -56,6 +72,22 @@
        <db-relationship name="reflexiveAndToOneArray" 
source="RELATIONSHIP_HELPER" target="REFLEXIVE_AND_TO_ONE" toMany="true">
                <db-attribute-pair source="RELATIONSHIP_HELPER_ID" 
target="RELATIONSHIP_HELPER_ID"/>
        </db-relationship>
+       <db-relationship name="e2" source="CYCLE_E1" target="CYCLE_E2">
+               <db-attribute-pair source="e2_id" target="id"/>
+       </db-relationship>
+       <db-relationship name="e2s" source="CYCLE_E1" target="CYCLE_E2" 
toMany="true">
+               <db-attribute-pair source="id" target="e1_id"/>
+       </db-relationship>
+       <db-relationship name="e1" source="CYCLE_E2" target="CYCLE_E1">
+               <db-attribute-pair source="e1_id" target="id"/>
+       </db-relationship>
+       <db-relationship name="e1s" source="CYCLE_E2" target="CYCLE_E1" 
toMany="true">
+               <db-attribute-pair source="id" target="e2_id"/>
+       </db-relationship>
+       <obj-relationship name="e2" source="CYCLE_E1" target="CYCLE_E2" 
deleteRule="Nullify" db-relationship-path="e2"/>
+       <obj-relationship name="e2s" source="CYCLE_E1" target="CYCLE_E2" 
deleteRule="Deny" db-relationship-path="e2s"/>
+       <obj-relationship name="e1" source="CYCLE_E2" target="CYCLE_E1" 
deleteRule="Nullify" db-relationship-path="e1"/>
+       <obj-relationship name="e1s" source="CYCLE_E2" target="CYCLE_E1" 
deleteRule="Deny" db-relationship-path="e1s"/>
        <obj-relationship name="relationshipHelper" source="FkOfDifferentType" 
target="RelationshipHelper" db-relationship-path="relationshipHelper"/>
        <obj-relationship name="toRelationshipHelper" source="MeaningfulFK" 
target="RelationshipHelper" db-relationship-path="toRelationshipHelper"/>
        <obj-relationship name="children" source="ReflexiveAndToOne" 
target="ReflexiveAndToOne" db-relationship-path="children"/>

Reply via email to