From 709b4da87c0186efc049a9b7625fd04fdddd9797 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Mon, 24 Nov 2025 16:16:15 -0500 Subject: [PATCH 1/4] try async command for archiving --- .../edu/harvard/iq/dataverse/DatasetPage.java | 25 ++++++++---------- .../iq/dataverse/EjbDataverseEngine.java | 26 +++++++++++++++++++ src/main/java/propertyFiles/Bundle.properties | 1 + 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index 20617160a1c..b97b8ec6578 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -6101,20 +6101,17 @@ public void archiveVersion(Long id) { AbstractSubmitToArchiveCommand cmd = ArchiverUtil.createSubmitToArchiveCommand(className, dvRequestService.getDataverseRequest(), dv); if (cmd != null) { try { - DatasetVersion version = commandEngine.submit(cmd); - if (!version.getArchivalCopyLocationStatus().equals(DatasetVersion.ARCHIVAL_STATUS_FAILURE)) { - logger.info( - "DatasetVersion id=" + version.getId() + " submitted to Archive, status: " + dv.getArchivalCopyLocationStatus()); - } else { - logger.severe("Error submitting version " + version.getId() + " due to conflict/error at Archive"); - } - if (version.getArchivalCopyLocation() != null) { - setVersionTabList(resetVersionTabList()); - this.setVersionTabListForPostLoad(getVersionTabList()); - JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("datasetversion.archive.success")); - } else { - JsfHelper.addErrorMessage(BundleUtil.getStringFromBundle("datasetversion.archive.failure")); - } + commandEngine.submitAsync(cmd); + + // Set initial pending status + dv.setArchivalCopyLocation(DatasetVersion.ARCHIVAL_STATUS_PENDING); + + logger.info( + "DatasetVersion id=" + dv.getId() + " submitted to Archive, status: " + dv.getArchivalCopyLocationStatus()); + setVersionTabList(resetVersionTabList()); + this.setVersionTabListForPostLoad(getVersionTabList()); + JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("datasetversion.archive.inprogress")); + } catch (CommandException ex) { logger.log(Level.SEVERE, "Unexpected Exception calling submit archive command", ex); JsfHelper.addErrorMessage(BundleUtil.getStringFromBundle("datasetversion.archive.failure")); diff --git a/src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java b/src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java index 4d6d59cb013..5a3f105497d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java +++ b/src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java @@ -31,6 +31,9 @@ import java.util.Map; import java.util.Set; + +import jakarta.ejb.AsyncResult; +import jakarta.ejb.Asynchronous; import jakarta.ejb.EJB; import jakarta.ejb.Stateless; import jakarta.inject.Named; @@ -45,6 +48,7 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.Stack; +import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import jakarta.annotation.Resource; @@ -348,6 +352,28 @@ public R submit(Command aCommand) throws CommandException { logSvc.log(logRec); } } + + /** + * Submits a command for asynchronous execution. + * The command will be executed in a separate thread and won't block the caller. + * + * @param The return type of the command + * @param aCommand The command to execute + * @param user The user executing the command + * @return A Future representing the pending result + * @throws CommandException if the command cannot be submitted + */ + @Asynchronous + public Future submitAsync(Command aCommand) throws CommandException { + try { + logger.log(Level.INFO, "Submitting async command: {0}", aCommand.getClass().getSimpleName()); + R result = submit(aCommand); + return new AsyncResult<>(result); + } catch (Exception e) { + logger.log(Level.SEVERE, "Async command execution failed: " + aCommand.getClass().getSimpleName(), e); + throw e; + } + } protected void completeCommand(Command command, Object r, Stack called) { diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index f6c0054a43a..d9b9fd7bc48 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -2699,6 +2699,7 @@ dataset.notlinked.msg=There was a problem linking this dataset to yours: dataset.linking.popop.already.linked.note=Note: This dataset is already linked to the following dataverse(s): dataset.linking.popup.not.linked.note=Note: This dataset is not linked to any of your accessible dataverses datasetversion.archive.success=Archival copy of Version successfully submitted +datasetversion.archive.inprogress= Data Project archiving has been started datasetversion.archive.failure=Error in submitting an archival copy datasetversion.update.failure=Dataset Version Update failed. Changes are still in the DRAFT version. datasetversion.update.archive.failure=Dataset Version Update succeeded, but the attempt to update the archival copy failed. From 6487c1433f1c960d645250cea421c1659120d3c9 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Mon, 24 Nov 2025 17:07:48 -0500 Subject: [PATCH 2/4] save status --- src/main/java/edu/harvard/iq/dataverse/DatasetPage.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index b97b8ec6578..0bf0db42728 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -6101,10 +6101,12 @@ public void archiveVersion(Long id) { AbstractSubmitToArchiveCommand cmd = ArchiverUtil.createSubmitToArchiveCommand(className, dvRequestService.getDataverseRequest(), dv); if (cmd != null) { try { - commandEngine.submitAsync(cmd); - + // Set initial pending status dv.setArchivalCopyLocation(DatasetVersion.ARCHIVAL_STATUS_PENDING); + dv = datasetVersionService.merge(dv); + + commandEngine.submitAsync(cmd); logger.info( "DatasetVersion id=" + dv.getId() + " submitted to Archive, status: " + dv.getArchivalCopyLocationStatus()); From 9d32051fe76d0914fc35d21f693211054fc0c38a Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Thu, 8 Jan 2026 13:07:23 -0500 Subject: [PATCH 3/4] refactor, use persistArchivalCopyLocation everywhere --- .../edu/harvard/iq/dataverse/DatasetPage.java | 2 +- .../iq/dataverse/DatasetVersionServiceBean.java | 17 +++++++++++++++++ .../edu/harvard/iq/dataverse/api/Datasets.java | 1 + .../impl/AbstractSubmitToArchiveCommand.java | 3 ++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index 0bf0db42728..281734cd66e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -6104,7 +6104,7 @@ public void archiveVersion(Long id) { // Set initial pending status dv.setArchivalCopyLocation(DatasetVersion.ARCHIVAL_STATUS_PENDING); - dv = datasetVersionService.merge(dv); + datasetVersionService.persistArchivalCopyLocation(dv); commandEngine.submitAsync(cmd); diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java index 60df1fd3dfd..7656f975d2a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java @@ -1333,4 +1333,21 @@ public Long getDatasetVersionCount(Long datasetId, boolean canViewUnpublishedVer return em.createQuery(cq).getSingleResult(); } + + + /** + * Update the archival copy location for a specific version of a dataset. Archiving can be long-running and other parallel updates to the datasetversion have likely occurred + * + * @param dv + * The dataset version whose archival copy location we want to update. Must not be {@code null}. + * @param archivalStatusPending + * the JSON status string, may be {@code null}. + */ + public void persistArchivalCopyLocation(DatasetVersion dv) { + em.createNativeQuery( + "UPDATE datasetversion SET archivalcopylocation = ?1 WHERE id = ?2") + .setParameter(1, dv.getArchivalCopyLocation()) + .setParameter(2, dv.getId()) + .executeUpdate(); + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 1b3016ec2f4..c8e66115575 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -1280,6 +1280,7 @@ public Response publishDataset(@Context ContainerRequestContext crc, @PathParam( if (archiveCommand != null) { // Delete the record of any existing copy since it is now out of date/incorrect updateVersion.setArchivalCopyLocation(null); + datasetVersionSvc.persistArchivalCopyLocation(updateVersion); /* * Then try to generate and submit an archival copy. Note that running this * command within the CuratePublishedDatasetVersionCommand was causing an error: diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractSubmitToArchiveCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractSubmitToArchiveCommand.java index 29c27d0396d..7e39a8e7b85 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractSubmitToArchiveCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractSubmitToArchiveCommand.java @@ -63,7 +63,8 @@ public DatasetVersion execute(CommandContext ctxt) throws CommandException { token = ctxt.authentication().generateApiTokenForUser(user); } performArchiveSubmission(version, token, requestedSettings); - return ctxt.em().merge(version); + ctxt.datasetVersion().persistArchivalCopyLocation(version); + return version; } /** From ec5046cc161193fd102481a9a53cb439c5768f94 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Mon, 12 Jan 2026 10:55:48 -0500 Subject: [PATCH 4/4] catch OLE when persisting archivalcopylocation --- .../dataverse/DatasetVersionServiceBean.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java index 7656f975d2a..b5e964e5673 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java @@ -33,6 +33,7 @@ import jakarta.json.JsonObjectBuilder; import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; +import jakarta.persistence.OptimisticLockException; import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; import jakarta.persistence.TypedQuery; @@ -1336,18 +1337,25 @@ public Long getDatasetVersionCount(Long datasetId, boolean canViewUnpublishedVer /** - * Update the archival copy location for a specific version of a dataset. Archiving can be long-running and other parallel updates to the datasetversion have likely occurred + * Update the archival copy location for a specific version of a dataset. Archiving can be long-running and other parallel updates to the datasetversion have likely occurred so this method will check + * for OptimisticLockExceptions and retry the update with the latest version. * * @param dv * The dataset version whose archival copy location we want to update. Must not be {@code null}. - * @param archivalStatusPending - * the JSON status string, may be {@code null}. */ public void persistArchivalCopyLocation(DatasetVersion dv) { - em.createNativeQuery( - "UPDATE datasetversion SET archivalcopylocation = ?1 WHERE id = ?2") - .setParameter(1, dv.getArchivalCopyLocation()) - .setParameter(2, dv.getId()) - .executeUpdate(); + try { + em.merge(dv); + em.flush(); // Force the update and version check immediately + } catch (OptimisticLockException ole) { + logger.log(Level.INFO, "OptimisticLockException while persisting archival copy location for DatasetVersion id={0}. Retrying on latest version.", dv.getId()); + DatasetVersion currentVersion = find(dv.getId()); + if (currentVersion != null) { + currentVersion.setArchivalCopyLocation(dv.getArchivalCopyLocation()); + em.merge(currentVersion); + } else { + logger.log(Level.SEVERE, "Could not find DatasetVersion with id={0} to retry persisting archival copy location after OptimisticLockException.", dv.getId()); + } + } } }