You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by ab...@apache.org on 2019/06/07 14:50:17 UTC

[cayenne] 02/02: CAY-2582 Double insert of manyToMany relationship mapped to Set

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

abulatski pushed a commit to branch STABLE-4.0
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 60b16868953607ad9e4eb67f5843ac93aec44376
Author: Arseni Bulatski <an...@gmail.com>
AuthorDate: Fri Jun 7 17:48:36 2019 +0300

    CAY-2582 Double insert of manyToMany relationship mapped to Set
---
 RELEASE-NOTES.txt                                  |  1 +
 .../java/org/apache/cayenne/access/ToManySet.java  | 72 +++++++++++++++++++++
 .../org/apache/cayenne/access/ToManySetFault.java  |  3 +-
 .../apache/cayenne/util/PersistentObjectSet.java   | 12 ++--
 .../java/org/apache/cayenne/ManyToManyJoinIT.java  | 66 +++++++++++++++++--
 .../relationships_many_to_many_join/Author.java    | 18 ++++++
 .../relationships_many_to_many_join/Song.java      | 18 ++++++
 .../auto/_Author.java                              | 67 ++-----------------
 .../auto/_Song.java                                | 75 ++--------------------
 .../cayenne-relationships-many-to-many-join.xml    |  5 +-
 .../relationships-many-to-many-join.map.xml        | 10 +--
 11 files changed, 194 insertions(+), 153 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index ece7e32..40100e7 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -22,6 +22,7 @@ Bug Fixes:
 CAY-2550 Modeler: ObjAttribute inspector modifies wrong columns in attribute table
 CAY-2559 Modeler: Warning dialog shows wrong information after changing target entity in dbRelationship
 CAY-2573 DI field injection is triggered when creating sql Driver
+CAY-2582 Double insert of manyToMany relationship mapped to Set
 CAY-2584 Crypto: can't use ColumnSelect with encrypted columns
 
 ----------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySet.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySet.java
new file mode 100644
index 0000000..6dcfbcc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySet.java
@@ -0,0 +1,72 @@
+/*****************************************************************
+ *   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;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.util.PersistentObjectSet;
+
+public class ToManySet extends PersistentObjectSet implements Serializable {
+
+    protected ToManySet(Persistent relationshipOwner, String relationshipName) {
+        super(relationshipOwner, relationshipName);
+    }
+
+    @Override
+    protected boolean shouldAddToRemovedFromUnresolvedSet(Object object) {
+        // No point in adding a new or transient object -- these will never be fetched
+        // from the database.
+        if (object instanceof Persistent) {
+            Persistent dataObject = (Persistent) object;
+            return (dataObject.getPersistenceState() != PersistenceState.TRANSIENT)
+                    && (dataObject.getPersistenceState() != PersistenceState.NEW);
+        }
+        return true;
+    }
+
+    @Override
+    protected void postprocessAdd(Collection<?> collection) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessRemove(Collection<?> collection) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessAdd(Object addedObject) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessRemove(Object removedObject) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void updateReverse(List resolved) {
+        // no need for this operation for DataObjects...
+    }
+
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySetFault.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySetFault.java
index 44e2e10..4114b0d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySetFault.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySetFault.java
@@ -20,7 +20,6 @@ package org.apache.cayenne.access;
 
 import org.apache.cayenne.Fault;
 import org.apache.cayenne.Persistent;
-import org.apache.cayenne.util.PersistentObjectSet;
 
 /**
  * @since 3.0
@@ -29,7 +28,7 @@ public class ToManySetFault extends Fault {
 
     @Override
     public Object resolveFault(Persistent sourceObject, String relationshipName) {
-        return new PersistentObjectSet(sourceObject, relationshipName);
+        return new ToManySet(sourceObject, relationshipName);
     }
 
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectSet.java b/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectSet.java
index 0275e5a..c3e4242 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectSet.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectSet.java
@@ -110,6 +110,10 @@ public class PersistentObjectSet extends RelationshipFault
         this.objectSet = objectSet;
     }
 
+    protected boolean shouldAddToRemovedFromUnresolvedSet(Object object) {
+        return true;
+    }
+
     // ====================================================
     // Standard Set Methods.
     // ====================================================
@@ -323,19 +327,19 @@ public class PersistentObjectSet extends RelationshipFault
         return true;
     }
 
-    void postprocessAdd(Collection<?> collection) {
+    protected void postprocessAdd(Collection<?> collection) {
         for (Object next : collection) {
             postprocessAdd(next);
         }
     }
 
-    void postprocessRemove(Collection<?> collection) {
+    protected void postprocessRemove(Collection<?> collection) {
         for (Object next : collection) {
             postprocessRemove(next);
         }
     }
 
-    void postprocessAdd(Object addedObject) {
+    protected void postprocessAdd(Object addedObject) {
 
         // notify ObjectContext
         if (relationshipOwner.getObjectContext() != null) {
@@ -351,7 +355,7 @@ public class PersistentObjectSet extends RelationshipFault
         }
     }
 
-    void postprocessRemove(Object removedObject) {
+    protected void postprocessRemove(Object removedObject) {
 
         // notify ObjectContext
         if (relationshipOwner.getObjectContext() != null) {
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java b/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
index 0949b5e..b8b25e8 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
@@ -18,9 +18,13 @@
  ****************************************************************/
 package org.apache.cayenne;
 
-import static org.junit.Assert.*;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.cayenne.access.DataDomain;
+import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.graph.GraphChangeHandler;
+import org.apache.cayenne.graph.GraphDiff;
 import org.apache.cayenne.testdo.relationships_many_to_many_join.Author;
 import org.apache.cayenne.testdo.relationships_many_to_many_join.Song;
 import org.apache.cayenne.unit.di.server.CayenneProjects;
@@ -28,24 +32,78 @@ 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.assertEquals;
+
 @UseServerRuntime(CayenneProjects.RELATIONSHIPS_MANY_TO_MANY_JOIN_PROJECT)
 public class ManyToManyJoinIT extends ServerCase {
 
     @Inject
+    private ServerRuntime serverRuntime;
+
+    @Inject
     private ObjectContext context;
 
     @Test
-    public void testManyToManyJoinWithFlattenedRelationship() throws Exception {
+    public void testManyToManyJoinWithFlattenedRelationship() {
     	Author author = context.newObject(Author.class);
     	author.setName("Bob Dylan");
-    	
+
         Song song = context.newObject(Song.class);
         song.setName("House of the Rising Sun");
 
         song.addToAuthors(author);
-        
+
         context.commitChanges();
         assertEquals(author, song.getAuthors().iterator().next());
     }
 
+    @Test
+    public void testManyToManyJoinWithFlattenedRelationshipEvents() {
+        DataDomain domain = serverRuntime.getDataDomain();
+        DataChannelFilter f1 = new MockDataChannelFilter() {
+            @Override
+            public GraphDiff onSync(
+                    ObjectContext originatingContext,
+                    GraphDiff changes,
+                    int syncType,
+                    DataChannelFilterChain filterChain) {
+
+                GraphDiff response = filterChain.onSync(
+                        originatingContext,
+                        changes,
+                        syncType);
+                final AtomicInteger count = new AtomicInteger(0);
+                changes.apply(new GraphChangeHandler() {
+                    @Override
+                    public void nodeIdChanged(Object nodeId, Object newId) {}
+
+                    @Override
+                    public void nodeCreated(Object nodeId) {}
+
+                    @Override
+                    public void nodeRemoved(Object nodeId) {}
+
+                    @Override
+                    public void nodePropertyChanged(Object nodeId, String property, Object oldValue, Object newValue) {}
+
+                    @Override
+                    public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+                        count.incrementAndGet();
+                    }
+
+                    @Override
+                    public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {}
+                });
+                assertEquals(1, count.get());
+                return response;
+            }
+        };
+        domain.addFilter(f1);
+        Author author = context.newObject(Author.class);
+        author.setName("Bob Dylan");
+        Song song = context.newObject(Song.class);
+        song.setName("House of the Rising Sun");
+        song.addToAuthors(author);
+        context.commitChanges();
+    }
 }
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
index a50741e..e516b70 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
@@ -1,3 +1,21 @@
+/*****************************************************************
+ *   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.testdo.relationships_many_to_many_join;
 
 import org.apache.cayenne.testdo.relationships_many_to_many_join.auto._Author;
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
index 8df7056..abf7b8a 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
@@ -1,3 +1,21 @@
+/*****************************************************************
+ *   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.testdo.relationships_many_to_many_join;
 
 import org.apache.cayenne.testdo.relationships_many_to_many_join.auto._Song;
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java
index 11554d9..999a0a6 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java
@@ -1,10 +1,6 @@
 package org.apache.cayenne.testdo.relationships_many_to_many_join.auto;
 
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-
-import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.CayenneDataObject;
 import org.apache.cayenne.exp.Property;
 
 /**
@@ -13,7 +9,7 @@ import org.apache.cayenne.exp.Property;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _Author extends BaseDataObject {
+public abstract class _Author extends CayenneDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -21,66 +17,11 @@ public abstract class _Author extends BaseDataObject {
 
     public static final Property<String> NAME = Property.create("name", String.class);
 
-    protected String name;
-
-
     public void setName(String name) {
-        beforePropertyWrite("name", this.name, name);
-        this.name = name;
+        writeProperty("name", name);
     }
-
     public String getName() {
-        beforePropertyRead("name");
-        return this.name;
-    }
-
-    @Override
-    public Object readPropertyDirectly(String propName) {
-        if(propName == null) {
-            throw new IllegalArgumentException();
-        }
-
-        switch(propName) {
-            case "name":
-                return this.name;
-            default:
-                return super.readPropertyDirectly(propName);
-        }
-    }
-
-    @Override
-    public void writePropertyDirectly(String propName, Object val) {
-        if(propName == null) {
-            throw new IllegalArgumentException();
-        }
-
-        switch (propName) {
-            case "name":
-                this.name = (String)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.name);
-    }
-
-    @Override
-    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
-        super.readState(in);
-        this.name = (String)in.readObject();
+        return (String)readProperty("name");
     }
 
 }
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
index 15160ca..c639c9f 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
@@ -1,11 +1,8 @@
 package org.apache.cayenne.testdo.relationships_many_to_many_join.auto;
 
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
 import java.util.Set;
 
-import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.CayenneDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.relationships_many_to_many_join.Author;
 
@@ -15,7 +12,7 @@ import org.apache.cayenne.testdo.relationships_many_to_many_join.Author;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _Song extends BaseDataObject {
+public abstract class _Song extends CayenneDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -24,87 +21,23 @@ public abstract class _Song extends BaseDataObject {
     public static final Property<String> NAME = Property.create("name", String.class);
     public static final Property<Set<Author>> AUTHORS = Property.create("authors", Set.class);
 
-    protected String name;
-
-    protected Object authors;
-
     public void setName(String name) {
-        beforePropertyWrite("name", this.name, name);
-        this.name = name;
+        writeProperty("name", name);
     }
-
     public String getName() {
-        beforePropertyRead("name");
-        return this.name;
+        return (String)readProperty("name");
     }
 
     public void addToAuthors(Author obj) {
         addToManyTarget("authors", obj, true);
     }
-
     public void removeFromAuthors(Author obj) {
         removeToManyTarget("authors", obj, true);
     }
-
     @SuppressWarnings("unchecked")
     public Set<Author> getAuthors() {
         return (Set<Author>)readProperty("authors");
     }
 
-    @Override
-    public Object readPropertyDirectly(String propName) {
-        if(propName == null) {
-            throw new IllegalArgumentException();
-        }
-
-        switch(propName) {
-            case "name":
-                return this.name;
-            case "authors":
-                return this.authors;
-            default:
-                return super.readPropertyDirectly(propName);
-        }
-    }
-
-    @Override
-    public void writePropertyDirectly(String propName, Object val) {
-        if(propName == null) {
-            throw new IllegalArgumentException();
-        }
-
-        switch (propName) {
-            case "name":
-                this.name = (String)val;
-                break;
-            case "authors":
-                this.authors = 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.name);
-        out.writeObject(this.authors);
-    }
-
-    @Override
-    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
-        super.readState(in);
-        this.name = (String)in.readObject();
-        this.authors = in.readObject();
-    }
 
 }
diff --git a/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml b/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml
index 0121356..d93fe1c 100644
--- a/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml
+++ b/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml
@@ -1,7 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<domain xmlns="http://cayenne.apache.org/schema/10/domain"
-	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/domain https://cayenne.apache.org/schema/10/domain.xsd"
-	 project-version="10">
+<domain project-version="9">
 	<map name="relationships-many-to-many-join"/>
 </domain>
diff --git a/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml b/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml
index 38ef803..07da692 100644
--- a/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml
+++ b/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
-<data-map xmlns="http://cayenne.apache.org/schema/10/modelMap"
+<data-map xmlns="http://cayenne.apache.org/schema/9/modelMap"
 	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap https://cayenne.apache.org/schema/10/modelMap.xsd"
-	 project-version="10">
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/9/modelMap https://cayenne.apache.org/schema/9/modelMap.xsd"
+	 project-version="9">
 	<property name="defaultPackage" value="org.apache.cayenne.testdo.relationships_many_to_many_join"/>
 	<db-entity name="X_AUTHOR">
 		<db-attribute name="AUTHOR_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
@@ -28,10 +28,10 @@
 	<db-relationship name="songAuthor" source="X_SONG" target="X_SONGAUTHOR" toDependentPK="true" toMany="true">
 		<db-attribute-pair source="SONG_ID" target="SONG_ID"/>
 	</db-relationship>
-	<db-relationship name="song" source="X_SONGAUTHOR" target="X_SONG">
+	<db-relationship name="song" source="X_SONGAUTHOR" target="X_SONG" toMany="false">
 		<db-attribute-pair source="SONG_ID" target="SONG_ID"/>
 	</db-relationship>
-	<db-relationship name="author" source="X_SONGAUTHOR" target="X_AUTHOR">
+	<db-relationship name="author" source="X_SONGAUTHOR" target="X_AUTHOR" toMany="false">
 		<db-attribute-pair source="AUTHOR_ID" target="AUTHOR_ID"/>
 	</db-relationship>
 	<obj-relationship name="authors" source="Song" target="Author" collection-type="java.util.Set" deleteRule="Cascade" db-relationship-path="songAuthor.author"/>