diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetServiceBean.java index a32141b8baf..806d4d21aa8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetServiceBean.java @@ -14,6 +14,7 @@ import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.engine.command.exception.CommandException; import edu.harvard.iq.dataverse.engine.command.impl.DestroyDatasetCommand; +import edu.harvard.iq.dataverse.engine.command.impl.FinalizeDatasetArchiveCommand; import edu.harvard.iq.dataverse.engine.command.impl.FinalizeDatasetPublicationCommand; import edu.harvard.iq.dataverse.engine.command.impl.GetDatasetStorageSizeCommand; import edu.harvard.iq.dataverse.export.ExportService; @@ -938,6 +939,32 @@ public void callFinalizePublishCommandAsynchronously(Long datasetId, CommandCont logger.warning("CommandException caught when executing the asynchronous portion of the Dataset Publication Command."); } } + + @Asynchronous + @TransactionAttribute(TransactionAttributeType.SUPPORTS) + public void callFinalizeArchiveCommandAsynchronously(Long datasetId, CommandContext ctxt, DataverseRequest request, boolean isPidPrePublished) { + + // Since we are calling the next command asynchronously anyway - sleep here + // for a few seconds, just in case, to make sure the database update of + // the dataset initiated by the PublishDatasetCommand has finished, + // to avoid any concurrency/optimistic lock issues. + // Aug. 2020/v5.0: It MAY be working consistently without any + // sleep here, after the call the method has been moved to the onSuccess() + // portion of the PublishDatasetCommand. I'm going to leave the 1 second + // sleep below, for just in case reasons: -- L.A. + try { + Thread.sleep(1000); + } catch (Exception ex) { + logger.warning("Failed to sleep for a second."); + } + logger.fine("Running FinalizeDatasetPublicationCommand, asynchronously"); + Dataset theDataset = find(datasetId); + try { + commandEngine.submit(new FinalizeDatasetArchiveCommand(theDataset, request, isPidPrePublished)); + } catch (CommandException cex) { + logger.warning("CommandException caught when executing the asynchronous portion of the Dataset Publication Command."); + } + } public long findStorageSize(Dataset dataset) throws IOException { return findStorageSize(dataset, false, GetDatasetStorageSizeCommand.Mode.STORAGE, null); 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 ad66fb468f4..51d59fcf969 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -1295,6 +1295,106 @@ public Response publishDataset(@Context ContainerRequestContext crc, @PathParam( } } + @POST + @AuthRequired + @Path("{id}/actions/:archive") + public Response archiveDataset(@Context ContainerRequestContext crc, @PathParam("id") String id, @QueryParam("type") String type, @QueryParam("assureIsIndexed") boolean mustBeIndexed) { + try { + if (type == null) { + return error(Response.Status.BAD_REQUEST, "Missing 'type' parameter (either 'major','minor', or 'updatecurrent')."); + } + boolean updateCurrent=false; + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); + type = type.toLowerCase(); + boolean isMinor=false; + switch (type) { + case "minor": + isMinor = true; + break; + case "major": + isMinor = false; + break; + case "updatecurrent": + if (user.isSuperuser()) { + updateCurrent = true; + } else { + return error(Response.Status.FORBIDDEN, "Only superusers can update the current version"); + } + break; + default: + return error(Response.Status.BAD_REQUEST, "Illegal 'type' parameter value '" + type + "'. It needs to be either 'major', 'minor', or 'updatecurrent'."); + } + + Dataset ds = findDatasetOrDie(id); + + boolean hasValidTerms = TermsOfUseAndAccessValidator.isTOUAValid(ds.getLatestVersion().getTermsOfUseAndAccess(), null); + if (!hasValidTerms) { + return error(Status.CONFLICT, BundleUtil.getStringFromBundle("dataset.message.toua.invalid")); + } + + if (mustBeIndexed) { + logger.fine("IT: " + ds.getIndexTime()); + logger.fine("MT: " + ds.getModificationTime()); + logger.fine("PIT: " + ds.getPermissionIndexTime()); + logger.fine("PMT: " + ds.getPermissionModificationTime()); + if (ds.getIndexTime() != null && ds.getModificationTime() != null) { + logger.fine("ITMT: " + (ds.getIndexTime().compareTo(ds.getModificationTime()) <= 0)); + } + /* + * Some calls, such as the /datasets/actions/:import* commands do not set the + * modification or permission modification times. The checks here are trying to + * see if indexing or permissionindexing could be pending, so they check to see + * if the relevant modification time is set and if so, whether the index is also + * set and if so, if it after the modification time. If the modification time is + * set and the index time is null or is before the mod time, the 409/conflict + * error is returned. + * + */ + if ((ds.getModificationTime()!=null && (ds.getIndexTime() == null || (ds.getIndexTime().compareTo(ds.getModificationTime()) <= 0))) || + (ds.getPermissionModificationTime()!=null && (ds.getPermissionIndexTime() == null || (ds.getPermissionIndexTime().compareTo(ds.getPermissionModificationTime()) <= 0)))) { + return error(Response.Status.CONFLICT, "Dataset is awaiting indexing"); + } + } + if (updateCurrent) { + /* + * Note: The code here mirrors that in the + * edu.harvard.iq.dataverse.DatasetPage:updateCurrentVersion method. Any changes + * to the core logic (i.e. beyond updating the messaging about results) should + * be applied to the code there as well. + */ + String errorMsg = null; + String successMsg = null; + try { + CurateArchivedDatasetVersionCommand cmd = new CurateArchivedDatasetVersionCommand(ds, createDataverseRequest(user)); + ds = commandEngine.submit(cmd); + successMsg = BundleUtil.getStringFromBundle("datasetversion.update.success"); + + // If configured, update archive copy as well + } catch (CommandException ex) { + errorMsg = BundleUtil.getStringFromBundle("datasetversion.update.failure") + " - " + ex.toString(); + logger.severe(ex.getMessage()); + } + if (errorMsg != null) { + return error(Response.Status.INTERNAL_SERVER_ERROR, errorMsg); + } else { + return Response.ok(Json.createObjectBuilder() + .add("status", ApiConstants.STATUS_OK) + .add("status_details", successMsg) + .add("data", json(ds)).build()) + .type(MediaType.APPLICATION_JSON) + .build(); + } + } else { + ArchiveDatasetResult res = execCommand(new ArchiveDatasetCommand(ds, + createDataverseRequest(user), + isMinor)); + return res.isWorkflow() ? accepted(json(res.getDataset())) : ok(json(res.getDataset())); + } + } catch (WrappedResponse ex) { + return ex.getResponse(); + } + } + @POST @AuthRequired @Path("{id}/actions/:releasemigrated") diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ArchiveDatasetCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ArchiveDatasetCommand.java new file mode 100644 index 00000000000..f0562dad287 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ArchiveDatasetCommand.java @@ -0,0 +1,243 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.Dataset; +import edu.harvard.iq.dataverse.DatasetLock; +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.engine.command.RequiredPermissions; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException; +import edu.harvard.iq.dataverse.workflow.Workflow; +import edu.harvard.iq.dataverse.workflow.WorkflowContext.TriggerType; +import java.util.Optional; +import java.util.logging.Logger; +import static java.util.stream.Collectors.joining; +import static edu.harvard.iq.dataverse.engine.command.impl.ArchiveDatasetResult.Status; +import static edu.harvard.iq.dataverse.dataset.DatasetUtil.validateDatasetMetadataExternally; +import edu.harvard.iq.dataverse.util.StringUtil; + + +/** + * Kick-off a dataset publication process. The process may complete immediately, + * but may also result in a workflow being started and pending on some external + * response. Either way, the process will be completed by an instance of + * {@link FinalizeDatasetPublicationCommand}. + * + * @see FinalizeDatasetPublicationCommand + * + * @author skraffmiller + * @author michbarsinai + */ +@RequiredPermissions(Permission.PublishDataset) +public class ArchiveDatasetCommand extends AbstractPublishDatasetCommand { + private static final Logger logger = Logger.getLogger(ArchiveDatasetCommand.class.getName()); + boolean minorRelease; + DataverseRequest request; + + /** + * The dataset was already released by an external system, and now Dataverse + * is just internally marking this release version as released. This is happening + * in scenarios like import or migration. + */ + final boolean datasetExternallyReleased; + + public ArchiveDatasetCommand(Dataset datasetIn, DataverseRequest aRequest, boolean minor) { + this( datasetIn, aRequest, minor, false ); + } + + public ArchiveDatasetCommand(Dataset datasetIn, DataverseRequest aRequest, boolean minor, boolean isPidPrePublished) { + super(datasetIn, aRequest); + minorRelease = minor; + datasetExternallyReleased = isPidPrePublished; + request = aRequest; + } + + @Override + public ArchiveDatasetResult execute(CommandContext ctxt) throws CommandException { + + verifyCommandArguments(ctxt); + + // Invariant 1: If we're here, publishing the dataset makes sense, from a "business logic" point of view. + // Invariant 2: The latest version of the dataset is the one being published, EVEN IF IT IS NOT DRAFT. + // When importing a released dataset, the latest version is marked as RELEASED. + + Dataset theDataset = getDataset(); + + validateOrDie(theDataset.getLatestVersion(), false); + + //ToDo - any reason to set the version in publish versus finalize? Failure in a prepub workflow or finalize will leave draft versions with an assigned version number as is. + //Changing the dataset in this transaction also potentially makes a race condition with a prepub workflow, possibly resulting in an OptimisticLockException there. + + // Set the version numbers: + + if (theDataset.getPublicationDate() == null) { + // First Release + theDataset.getLatestVersion().setVersionNumber(new Long(1)); // minor release is blocked by #verifyCommandArguments + theDataset.getLatestVersion().setMinorVersionNumber(new Long(0)); + + } else if ( minorRelease ) { + theDataset.getLatestVersion().setVersionNumber(new Long(theDataset.getVersionNumber())); + theDataset.getLatestVersion().setMinorVersionNumber(new Long(theDataset.getMinorVersionNumber() + 1)); + + } else { + // major, non-first release + theDataset.getLatestVersion().setVersionNumber(new Long(theDataset.getVersionNumber() + 1)); + theDataset.getLatestVersion().setMinorVersionNumber(new Long(0)); + } + + // Perform any optional validation steps, if defined: + if (ctxt.systemConfig().isExternalDatasetValidationEnabled()) { + // For admins, an override of the external validation step may be enabled: + if (!(getUser().isSuperuser() && ctxt.systemConfig().isExternalValidationAdminOverrideEnabled())) { + String executable = ctxt.systemConfig().getDatasetValidationExecutable(); + boolean result = validateDatasetMetadataExternally(theDataset, executable, getRequest()); + + if (!result) { + String rejectionMessage = ctxt.systemConfig().getDatasetValidationFailureMsg(); + throw new IllegalCommandException(rejectionMessage, this); + } + } + } + + //ToDo - should this be in onSuccess()? May relate to todo above + Optional prePubWf = ctxt.workflows().getDefaultWorkflow(TriggerType.PrePublishDataset); + if ( prePubWf.isPresent() ) { + // We start a workflow + theDataset = ctxt.em().merge(theDataset); + ctxt.em().flush(); + ctxt.workflows().start(prePubWf.get(), buildContext(theDataset, TriggerType.PrePublishDataset, datasetExternallyReleased), true); + return new ArchiveDatasetResult(theDataset, Status.Workflow); + + } else{ + // We will skip trying to register the global identifiers for datafiles + // if "dependent" file-level identifiers are requested, AND the naming + // protocol of the dataset global id is different from the + // one currently configured for the Dataverse. This is to specifically + // address the issue with the datasets with handle ids registered, + // that are currently configured to use DOI. + // If we are registering file-level identifiers, and there are more + // than the configured limit number of files, then call Finalize + // asychronously (default is 10) + // ... + // Additionaly in 4.9.3 we have added a system variable to disable + // registering file PIDs on the installation level. + + boolean validatePhysicalFiles = ctxt.systemConfig().isDatafileValidationOnPublishEnabled(); + + // As of v5.0, publishing a dataset is always done asynchronously, + // with the dataset locked for the duration of the operation. + + + String info = "Publishing the dataset; "; + info += validatePhysicalFiles ? "Validating Datafiles Asynchronously" : ""; + + AuthenticatedUser user = request.getAuthenticatedUser(); + /* + * datasetExternallyReleased is only true in the case of the + * Dataverses.importDataset() and importDatasetDDI() methods. In that case, we + * are still in the transaction that creates theDataset, so + * A) Trying to create a DatasetLock referncing that dataset in a new + * transaction (as ctxt.datasets().addDatasetLock() does) will fail since the + * dataset doesn't yet exist, and + * B) a lock isn't needed because no one can be trying to edit it yet (as it + * doesn't exist). + * Thus, we can/need to skip creating the lock. Since the calls to removeLocks + * in FinalizeDatasetPublicationCommand search for and remove existing locks, if + * one doesn't exist, the removal is a no-op in this case. + */ + if (!datasetExternallyReleased) { + DatasetLock lock = new DatasetLock(DatasetLock.Reason.finalizePublication, user); + lock.setDataset(theDataset); + lock.setInfo(info); + ctxt.datasets().addDatasetLock(theDataset, lock); + } + theDataset = ctxt.em().merge(theDataset); + // The call to FinalizePublicationCommand has been moved to the new @onSuccess() + // method: + //ctxt.datasets().callFinalizePublishCommandAsynchronously(theDataset.getId(), ctxt, request, datasetExternallyReleased); + return new ArchiveDatasetResult(theDataset, Status.Inprogress); + } + } + + /** + * See that publishing the dataset in the requested manner makes sense, at + * the given state of the dataset. + * + * @throws IllegalCommandException if the publication request is invalid. + */ + private void verifyCommandArguments(CommandContext ctxt) throws IllegalCommandException { + if (!getDataset().getOwner().isReleased()) { + throw new IllegalCommandException("This dataset may not be published because its host dataverse (" + getDataset().getOwner().getAlias() + ") has not been published.", this); + } + + if ( ! getUser().isAuthenticated() ) { + throw new IllegalCommandException("Only authenticated users can release a Dataset. Please authenticate and try again.", this); + } + + if (getDataset().getLatestVersion().getTermsOfUseAndAccess() == null + || (getDataset().getLatestVersion().getTermsOfUseAndAccess().getLicense() == null + && StringUtil.isEmpty(getDataset().getLatestVersion().getTermsOfUseAndAccess().getTermsOfUse()))) { + throw new IllegalCommandException("Dataset must have a valid license or Custom Terms Of Use configured before it can be published.", this); + } + + if ( (getDataset().isLockedFor(DatasetLock.Reason.Workflow)&&!ctxt.permissions().isMatchingWorkflowLock(getDataset(),request.getUser().getIdentifier(),request.getWFInvocationId())) + || getDataset().isLockedFor(DatasetLock.Reason.Ingest) + || getDataset().isLockedFor(DatasetLock.Reason.finalizePublication) + || getDataset().isLockedFor(DatasetLock.Reason.EditInProgress)) { + throw new IllegalCommandException("This dataset is locked. Reason: " + + getDataset().getLocks().stream().map(l -> l.getReason().name()).collect( joining(",") ) + + ". Please try publishing later.", this); + } + + if ( getDataset().isLockedFor(DatasetLock.Reason.FileValidationFailed)) { + throw new IllegalCommandException("This dataset cannot be published because some files have been found missing or corrupted. " + + ". Please contact support to address this.", this); + } + + if ( datasetExternallyReleased ) { + if ( ! getDataset().getLatestVersion().isReleased() ) { + throw new IllegalCommandException("Latest version of dataset " + getDataset().getIdentifier() + " is not marked as releasd.", this); + } + + } else { + if (getDataset().getLatestVersion().isReleased()) { + throw new IllegalCommandException("Latest version of dataset " + getDataset().getIdentifier() + " is already released. Only draft versions can be released.", this); + } + + // prevent publishing of 0.1 version + if (minorRelease && getDataset().getVersions().size() == 1 && getDataset().getLatestVersion().isDraft()) { + throw new IllegalCommandException("Cannot publish as minor version. Re-try as major release.", this); + } + + if (minorRelease && !getDataset().getLatestVersion().isMinorUpdate()) { + throw new IllegalCommandException("Cannot release as minor version. Re-try as major release.", this); + } + } + } + + + @Override + public boolean onSuccess(CommandContext ctxt, Object r) { + Dataset dataset = null; + try{ + dataset = (Dataset) r; + } catch (ClassCastException e){ + dataset = ((ArchiveDatasetResult) r).getDataset(); + } + + if (dataset != null) { + Optional prePubWf = ctxt.workflows().getDefaultWorkflow(TriggerType.PrePublishDataset); + //A pre-publication workflow will call FinalizeDatasetPublicationCommand itself when it completes + if (! prePubWf.isPresent() ) { + logger.fine("From onSuccess, calling FinalizeArchiveCommand for dataset " + dataset.getGlobalId().asString()); + ctxt.datasets().callFinalizeArchiveCommandAsynchronously(dataset.getId(), ctxt, request, datasetExternallyReleased); + } + return true; + } + + return false; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ArchiveDatasetResult.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ArchiveDatasetResult.java new file mode 100644 index 00000000000..2802a317f52 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ArchiveDatasetResult.java @@ -0,0 +1,56 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.Dataset; + +/** + * The result of an attempt to publish a dataset. + * + * @author michael + */ +public class ArchiveDatasetResult { + + private final Dataset dataset; + private final Status status; + + public enum Status { + /** Dataset has been published */ + Completed, + /** Publish workflow is in progress */ + Workflow, + /** Publishing is being finalized asynchronously */ + Inprogress + } + + + public ArchiveDatasetResult(Dataset dataset, Status status) { + this.dataset = dataset; + this.status = status; + } + + /** + * @return the dataset that was published. + */ + public Dataset getDataset() { + return dataset; + } + + /** + * A publication operation can either be completed or pending, in case there's + * a workflow that has to be completed before the dataset publication is done. + * Workflows can take an arbitrary amount of time, as they might require external + * systems to perform lengthy operations, or have a human manually validate them. + * + * @return {@code true} iff the publication process was completed. {@code false} otherwise. + */ + public boolean isCompleted() { + return status == Status.Completed; + } + + public boolean isWorkflow() { + return status == Status.Workflow; + } + + public boolean isInProgress() { + return status == Status.Inprogress; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CurateArchivedDatasetVersionCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CurateArchivedDatasetVersionCommand.java new file mode 100644 index 00000000000..4be1296961e --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CurateArchivedDatasetVersionCommand.java @@ -0,0 +1,197 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.engine.command.RequiredPermissions; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException; +import edu.harvard.iq.dataverse.export.ExportService; +import io.gdcc.spi.export.ExportException; +import edu.harvard.iq.dataverse.util.BundleUtil; +import edu.harvard.iq.dataverse.util.DatasetFieldUtil; +import edu.harvard.iq.dataverse.workflows.WorkflowComment; +import edu.harvard.iq.dataverse.Dataset; +import edu.harvard.iq.dataverse.DatasetVersion; +import edu.harvard.iq.dataverse.TermsOfUseAndAccess; +import edu.harvard.iq.dataverse.DataFile; +import edu.harvard.iq.dataverse.FileMetadata; +import edu.harvard.iq.dataverse.DataFileCategory; +import edu.harvard.iq.dataverse.DatasetVersionDifference; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author qqmyers + * + * Adapted from UpdateDatasetVersionCommand + */ +@RequiredPermissions(Permission.EditDataset) +public class CurateArchivedDatasetVersionCommand extends AbstractDatasetCommand { + + private static final Logger logger = Logger.getLogger(CurateArchivedDatasetVersionCommand.class.getCanonicalName()); + final private boolean validateLenient = false; + + public CurateArchivedDatasetVersionCommand(Dataset theDataset, DataverseRequest aRequest) { + super(aRequest, theDataset); + } + + public boolean isValidateLenient() { + return validateLenient; + } + + @Override + public Dataset execute(CommandContext ctxt) throws CommandException { + if (!getUser().isSuperuser()) { + throw new IllegalCommandException("Only superusers can curate published dataset versions", this); + } + + ctxt.permissions().checkEditDatasetLock(getDataset(), getRequest(), this); + // Invariant: Dataset has no locks preventing the update + DatasetVersion updateVersion = getDataset().getLatestVersionForCopy(); + + DatasetVersion newVersion = getDataset().getOrCreateEditVersion(); + // Copy metadata from draft version to latest published version + updateVersion.setDatasetFields(newVersion.initDatasetFields()); + + + + // final DatasetVersion editVersion = getDataset().getEditVersion(); + DatasetFieldUtil.tidyUpFields(updateVersion.getDatasetFields(), true); + + // Merge the new version into our JPA context + ctxt.em().merge(updateVersion); + + TermsOfUseAndAccess oldTerms = updateVersion.getTermsOfUseAndAccess(); + TermsOfUseAndAccess newTerms = newVersion.getTermsOfUseAndAccess(); + newTerms.setDatasetVersion(updateVersion); + updateVersion.setTermsOfUseAndAccess(newTerms); + //Put old terms on version that will be deleted.... + newVersion.setTermsOfUseAndAccess(oldTerms); + + //Validate metadata and TofA conditions + validateOrDie(updateVersion, isValidateLenient()); + + //Also set the fileaccessrequest boolean on the dataset to match the new terms + getDataset().setFileAccessRequest(updateVersion.getTermsOfUseAndAccess().isFileAccessRequest()); + List newComments = newVersion.getWorkflowComments(); + if (newComments!=null && newComments.size() >0) { + for(WorkflowComment wfc: newComments) { + wfc.setDatasetVersion(updateVersion); + } + updateVersion.getWorkflowComments().addAll(newComments); + } + + + // we have to merge to update the database but not flush because + // we don't want to create two draft versions! + Dataset tempDataset = ctxt.em().merge(getDataset()); + + updateVersion = tempDataset.getLatestVersionForCopy(); + + // Look for file metadata changes and update published metadata if needed + List pubFmds = updateVersion.getFileMetadatas(); + int pubFileCount = pubFmds.size(); + int newFileCount = tempDataset.getOrCreateEditVersion().getFileMetadatas().size(); + /* The policy for this command is that it should only be used when the change is a 'minor update' with no file changes. + * Nominally we could call .isMinorUpdate() for that but we're making the same checks as we go through the update here. + */ + if (pubFileCount != newFileCount) { + logger.severe("Draft version of dataset: " + tempDataset.getId() + " has: " + newFileCount + " while last published version has " + pubFileCount); + throw new IllegalCommandException(BundleUtil.getStringFromBundle("datasetversion.update.failure"), this); + } + Long thumbId = null; + if(tempDataset.getThumbnailFile()!=null) { + thumbId = tempDataset.getThumbnailFile().getId(); + }; + for (FileMetadata publishedFmd : pubFmds) { + DataFile dataFile = publishedFmd.getDataFile(); + FileMetadata draftFmd = dataFile.getLatestFileMetadata(); + boolean metadataUpdated = false; + if (draftFmd == null || draftFmd.getDatasetVersion().equals(updateVersion)) { + if (draftFmd == null) { + logger.severe("Unable to find latest FMD for file id: " + dataFile.getId()); + } else { + logger.severe("No filemetadata for file id: " + dataFile.getId() + " in draft version"); + } + throw new IllegalCommandException(BundleUtil.getStringFromBundle("datasetversion.update.failure"), this); + } else { + + metadataUpdated = DatasetVersionDifference.compareFileMetadatas(publishedFmd, draftFmd); + publishedFmd.setLabel(draftFmd.getLabel()); + publishedFmd.setDescription(draftFmd.getDescription()); + publishedFmd.setCategories(draftFmd.getCategories()); + publishedFmd.setRestricted(draftFmd.isRestricted()); + dataFile.setRestricted(draftFmd.isRestricted()); + publishedFmd.setProvFreeForm(draftFmd.getProvFreeForm()); + publishedFmd.copyVariableMetadata(draftFmd.getVariableMetadatas()); + publishedFmd.copyVarGroups(draftFmd.getVarGroups()); + + } + if (metadataUpdated) { + dataFile.setModificationTime(getTimestamp()); + } + // Now delete filemetadata from draft version before deleting the version itself + FileMetadata mergedFmd = ctxt.em().merge(draftFmd); + ctxt.em().remove(mergedFmd); + // including removing metadata from the list on the datafile + draftFmd.getDataFile().getFileMetadatas().remove(draftFmd); + tempDataset.getOrCreateEditVersion().getFileMetadatas().remove(draftFmd); + // And any references in the list held by categories + for (DataFileCategory cat : tempDataset.getCategories()) { + cat.getFileMetadatas().remove(draftFmd); + } + //And any thumbnail reference + if(publishedFmd.getDataFile().getId()==thumbId) { + tempDataset.setThumbnailFile(publishedFmd.getDataFile()); + } + } + + // Update modification time on the published version and the dataset + updateVersion.setLastUpdateTime(getTimestamp()); + tempDataset.setModificationTime(getTimestamp()); + ctxt.em().merge(updateVersion); + Dataset savedDataset = ctxt.em().merge(tempDataset); + + // Flush before calling DeleteDatasetVersion which calls + // PrivateUrlServiceBean.getPrivateUrlFromDatasetId() that will query the DB and + // fail if our changes aren't there + ctxt.em().flush(); + + // Now delete draft version + DeleteDatasetVersionCommand cmd; + + cmd = new DeleteDatasetVersionCommand(getRequest(), savedDataset); + ctxt.engine().submit(cmd); + // Running the command above reindexes the dataset, so we don't need to do it + // again in here. + + // And update metadata at PID provider + ctxt.engine().submit( + new UpdateDvObjectPIDMetadataCommand(savedDataset, getRequest())); + + //And the exported metadata files + try { + ExportService instance = ExportService.getInstance(); + instance.exportAllFormats(getDataset()); + } catch (ExportException ex) { + // Just like with indexing, a failure to export is not a fatal condition. + logger.log(Level.WARNING, "Curate Published DatasetVersion: exception while exporting metadata files:{0}", ex.getMessage()); + } + + + // Update so that getDataset() in updateDatasetUser will get the up-to-date copy + // (with no draft version) + setDataset(savedDataset); + updateDatasetUser(ctxt); + + + + + return savedDataset; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/FinalizeDatasetArchiveCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/FinalizeDatasetArchiveCommand.java index 42d434532c2..7859998c558 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/FinalizeDatasetArchiveCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/FinalizeDatasetArchiveCommand.java @@ -23,7 +23,6 @@ import edu.harvard.iq.dataverse.pidproviders.PidProvider; import edu.harvard.iq.dataverse.privateurl.PrivateUrl; import edu.harvard.iq.dataverse.util.BundleUtil; -import edu.harvard.iq.dataverse.workflow.WorkflowContext.TriggerType; import java.io.IOException; import java.sql.Timestamp; import java.util.Date; @@ -91,27 +90,6 @@ public Dataset execute(CommandContext ctxt) throws CommandException { validateOrDie(theDataset.getLatestVersion(), false); - /* - * Try to register the dataset identifier. For PID providers that have registerWhenPublished == false (all except the FAKE provider at present) - * the registerExternalIdentifier command will make one try to create the identifier if needed (e.g. if reserving at dataset creation wasn't done/failed). - * For registerWhenPublished == true providers, if a PID conflict is found, the call will retry with new PIDs. - */ - if ( theDataset.getGlobalIdCreateTime() == null ) { - try { - // This can potentially throw a CommandException, so let's make - // sure we exit cleanly: - - registerExternalIdentifier(theDataset, ctxt, false); - } catch (CommandException comEx) { - logger.warning("Failed to reserve the identifier "+theDataset.getGlobalId().asString()+"; notifying the user(s), unlocking the dataset"); - // Send failure notification to the user: - notifyUsersDatasetPublishStatus(ctxt, theDataset, UserNotification.Type.PUBLISHFAILED_PIDREG); - // Remove the dataset lock: - ctxt.datasets().removeDatasetLocks(theDataset, DatasetLock.Reason.finalizePublication); - // re-throw the exception: - throw comEx; - } - } // is this the first publication of the dataset? if (theDataset.getPublicationDate() == null) { diff --git a/src/main/java/edu/harvard/iq/dataverse/workflow/WorkflowServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/workflow/WorkflowServiceBean.java index 36529b8376d..67f30a607ff 100644 --- a/src/main/java/edu/harvard/iq/dataverse/workflow/WorkflowServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/workflow/WorkflowServiceBean.java @@ -418,7 +418,9 @@ private void workflowCompleted(Workflow wf, WorkflowContext ctxt) { DatasetLock lock = new DatasetLock(DatasetLock.Reason.finalizePublication, user); Dataset dataset = ctxt.getDataset(); lock.setDataset(dataset); + boolean validatePhysicalFiles = systemConfig.isDatafileValidationOnPublishEnabled(); String info = "Archiving the dataset; "; + info += validatePhysicalFiles ? "Validating Datafiles Asynchronously" : ""; lock.setInfo(info); lockDataset(ctxt, lock); ctxt.getDataset().addLock(lock);