From aa27ccc54562eab303dd8a3741801f38865eef0e Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 26 Feb 2026 08:29:41 +0100 Subject: [PATCH 1/3] Fix LazyInitializationException in Bundle.getBitstreams during CLI reindexing Bundle.getBitstreams() creates a defensive copy (new ArrayList) which triggers lazy loading of the bitstreams collection. When the Hibernate session is already closed (e.g. during CLI database migration reindexing), this causes a LazyInitializationException. Wrap the call in try-catch to gracefully skip bitstream uncaching when the session is unavailable. --- .../org/dspace/core/HibernateDBConnection.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index 7dae4a9d9812..984e0abb9385 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -25,6 +25,7 @@ import org.dspace.storage.rdbms.DatabaseConfigVO; import org.hibernate.FlushMode; import org.hibernate.Hibernate; +import org.hibernate.LazyInitializationException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; @@ -301,10 +302,17 @@ public void uncacheEntity(E entity) throws SQLExcep } else if (entity instanceof Bundle) { Bundle bundle = (Bundle) entity; - if (Hibernate.isInitialized(bundle.getBitstreams())) { - for (Bitstream bitstream : Utils.emptyIfNull(bundle.getBitstreams())) { - uncacheEntity(bitstream); + try { + // Bundle.getBitstreams() creates a defensive copy (new ArrayList) which + // triggers lazy loading. We must catch LazyInitializationException in case + // the session is already closed (e.g. during CLI reindexing). + if (Hibernate.isInitialized(bundle.getBitstreams())) { + for (Bitstream bitstream : Utils.emptyIfNull(bundle.getBitstreams())) { + uncacheEntity(bitstream); + } } + } catch (LazyInitializationException e) { + log.debug("Skipping bitstream uncaching for bundle {} - session already closed", bundle.getID()); } // BITSTREAM // No specific child entities to decache From da0ee37f1ebcc30c21702991bb7dbfa451755d4d Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 26 Feb 2026 08:47:00 +0100 Subject: [PATCH 2/3] Fix Bundle.getBitstreams() triggering lazy loading in uncacheEntity Bundle.getBitstreams() creates a defensive copy via new ArrayList<>(this.bitstreams) which iterates the Hibernate proxy and triggers lazy loading BEFORE Hibernate.isInitialized() can check the proxy state. All other getters (Item.getBundles, DSpaceObject.getHandles, etc.) return the raw Hibernate proxy, so isInitialized() works correctly for them. Guard with session.contains(bundle) instead: if the bundle is still managed by the session, lazy loading works normally; if detached (e.g. after session.clear() during batch processing), we skip - there is nothing to evict from a closed session. --- .../dspace/core/HibernateDBConnection.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index 984e0abb9385..f57ceb474e39 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -25,7 +25,6 @@ import org.dspace.storage.rdbms.DatabaseConfigVO; import org.hibernate.FlushMode; import org.hibernate.Hibernate; -import org.hibernate.LazyInitializationException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; @@ -302,17 +301,16 @@ public void uncacheEntity(E entity) throws SQLExcep } else if (entity instanceof Bundle) { Bundle bundle = (Bundle) entity; - try { - // Bundle.getBitstreams() creates a defensive copy (new ArrayList) which - // triggers lazy loading. We must catch LazyInitializationException in case - // the session is already closed (e.g. during CLI reindexing). - if (Hibernate.isInitialized(bundle.getBitstreams())) { - for (Bitstream bitstream : Utils.emptyIfNull(bundle.getBitstreams())) { - uncacheEntity(bitstream); - } + // Bundle.getBitstreams() creates a defensive copy via new ArrayList<>(bitstreams) + // which iterates the Hibernate proxy, triggering lazy loading unconditionally. + // Unlike Item.getBundles() which returns the raw proxy, we cannot safely call + // getBitstreams() when the bundle is detached from the session. + // Guard with session.contains(): if the bundle is still managed, + // lazy loading will work; if detached (e.g. after session.clear()), we skip. + if (getSession().contains(bundle)) { + for (Bitstream bitstream : Utils.emptyIfNull(bundle.getBitstreams())) { + uncacheEntity(bitstream); } - } catch (LazyInitializationException e) { - log.debug("Skipping bitstream uncaching for bundle {} - session already closed", bundle.getID()); } // BITSTREAM // No specific child entities to decache From 3df3d45587ff3317757f1ecd88f5201e7954825a Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 26 Feb 2026 09:25:51 +0100 Subject: [PATCH 3/3] Fix flaky WorkflowCurationIT by clearing plugin cache before test WorkflowCurationIT.curationTest fails when CreateMissingIdentifiersIT runs first, because it pollutes the shared named plugin cache in LegacyPluginServiceImpl. Port upstream fix from DSpace/DSpace#11907 (dspace-7_x backport): add clearNamedPluginClasses() call at test start to reset plugin state. See: https://github.com/DSpace/DSpace/issues/8533 --- .../java/org/dspace/workflow/WorkflowCurationIT.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java index 66dd2cee807f..8a07aaf7141f 100644 --- a/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java +++ b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java @@ -22,6 +22,7 @@ import org.dspace.content.Community; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; +import org.dspace.core.LegacyPluginServiceImpl; import org.dspace.ctask.testing.MarkerTask; import org.dspace.eperson.EPerson; import org.dspace.util.DSpaceConfigurationInitializer; @@ -29,6 +30,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -46,6 +48,8 @@ public class WorkflowCurationIT extends AbstractIntegrationTestWithDatabase { @Inject private ItemService itemService; + @Autowired + private LegacyPluginServiceImpl legacyPluginService; /** * Basic smoke test of a curation task attached to a workflow step. @@ -57,6 +61,11 @@ public void curationTest() throws Exception { context.turnOffAuthorisationSystem(); + // Reset the named plugin cache to avoid pollution from other tests + // (e.g. CreateMissingIdentifiersIT) that may have run before this one. + // See https://github.com/DSpace/DSpace/issues/8533 + legacyPluginService.clearNamedPluginClasses(); + //** GIVEN ** // A submitter;