From d94ee81ed73e6980e962eda46f60b9442f44b9f3 Mon Sep 17 00:00:00 2001 From: gbrodman Date: Fri, 10 Dec 2021 14:14:54 -0500 Subject: [PATCH] Copy into PersistentSets in Domains if applicable (#1457) * Copy into PersistentSets in Domains if applicable This is similar to https://github.com/google/nomulus/pull/1456 It is possible that in some cases we could get an exception: Caused by: org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: [parent] The main cause of this, according to research (StackOverflow :P) is that when Hibernate is calling the setters for these sets of children it's losing the connection to the previously-managed child entity (which it needs, in order to know how to delete orphans). Thus, the solution is to maintain the same instance of the persistent set and just add/remove to/from it as necessary. This is complicated by the fact that sometimes the setter is given the persistent set (the one we want to keep) and sometimes (?) it isn't. --- .../registry/model/domain/DomainContent.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/google/registry/model/domain/DomainContent.java b/core/src/main/java/google/registry/model/domain/DomainContent.java index 05e39f521..187ee82cb 100644 --- a/core/src/main/java/google/registry/model/domain/DomainContent.java +++ b/core/src/main/java/google/registry/model/domain/DomainContent.java @@ -86,6 +86,7 @@ import javax.persistence.Embedded; import javax.persistence.MappedSuperclass; import javax.persistence.PostLoad; import javax.persistence.Transient; +import org.hibernate.collection.internal.PersistentSet; import org.joda.time.DateTime; import org.joda.time.Interval; @@ -508,16 +509,37 @@ public class DomainContent extends EppResource this.nsHosts = forceEmptyToNull(nsHosts); } + // Note: for the two methods below, how we wish to treat the Hibernate setters depends on the + // current state of the object and what's passed in. The key principle is that we wish to maintain + // the link between parent and child objects, meaning that we should keep around whichever of the + // two sets (the parameter vs the class variable and clear/populate that as appropriate. + // + // If the class variable is a PersistentSet and we overwrite it here, Hibernate will throw + // an exception "A collection with cascade=”all-delete-orphan” was no longer referenced by the + // owning entity instance". See https://stackoverflow.com/questions/5587482 for more details. + // Hibernate needs this in order to populate gracePeriods but no one else should ever use it @SuppressWarnings("UnusedMethod") private void setInternalGracePeriods(Set gracePeriods) { - this.gracePeriods = gracePeriods; + if (this.gracePeriods instanceof PersistentSet) { + Set nonNullGracePeriods = nullToEmpty(gracePeriods); + this.gracePeriods.retainAll(nonNullGracePeriods); + this.gracePeriods.addAll(nonNullGracePeriods); + } else { + this.gracePeriods = gracePeriods; + } } // Hibernate needs this in order to populate dsData but no one else should ever use it @SuppressWarnings("UnusedMethod") private void setInternalDelegationSignerData(Set dsData) { - this.dsData = dsData; + if (this.dsData instanceof PersistentSet) { + Set nonNullDsData = nullToEmpty(dsData); + this.dsData.retainAll(nonNullDsData); + this.dsData.addAll(nonNullDsData); + } else { + this.dsData = dsData; + } } public final String getCurrentSponsorRegistrarId() {