While working on group resource implementation, I came across a fascinating hibernate issue.
ca.uhn.fhir.rest.server.exceptions.InternalErrorException: Failed to call access method:
org.springframework.orm.hibernate4.HibernateSystemException: A collection with cascade="all-
delete-orphan" was no longer referenced by the owning entity instance:
org.openmrs.Cohort.memberships; nested exception is org.hibernate.HibernateException: A
collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance:
org.openmrs.Cohort.memberships
Hibernate gives enough info (stack trace) about the error to identify what’s going on. Hibernate is complaining of not being able to track the change for the child collection object cohortMembership while it is getting set in the parent object cohort. Hibernate requires that parent object owns a child collection object completely.
To fix this, we need to modify the following code;
public void setCohortMemberships(Collection<CohortMembership> members) {
this.memberships = members;
}
New code
public void setCohortMemberships(Collection<CohortMembership> members) {
this.memberships.addAll(members);
}
It should fix the issue, though I haven’t tried yet. So this requires modifying openmrs core. For now, I don’t want to do that. So is there any workaround or easy fix for this that does not require me modifying openmrs core?
So one quick thing to note is that the proposed code isn’t really equivalent to the previous code. To replace setCohortMemberships() with something like that we’d need:
public void setCohortMemberships(Collection<CohortMembership> members) {
this.memberships.clear();
this.memberships.addAll(members);
}
I bring this up because it helps me crystallise the problem which is that we need to modify the collection without changing the underlying collection identity. That leads me to think that instead of calling setCohortMemberships() (and maybe this shouldn’t be part of the Cohort public API), we should just use something like:
if (group.hasMember()) {
//Create new copy of existing cohortMemberships
Set<CohortMembership> memberships = new HashSet<>(existingCohort.getMemberships());
group.getMember().forEach(member -> memberships.add(groupMemberTranslator.toOpenmrsType(member)));
existingCohort.setMemberships(memberships);
}
I had this implementation, which I think there are similar
The implementation is similar, but it has the same problem. When Hibernate is managing a collection, the object has Hibernate-specific metadata. When you swap that out for another collection (which is what this.memberships = <new collection> does), we get the error. In other words, things will generally break if we call setCohortMemberships() on a Cohort that has been saved to the database.
So, using your code as an example, we probably need something closer to this:
I was convinced that this would work but ended up with this; @ibacher?
java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at java.util.AbstractCollection.addAll(AbstractCollection.java:344)