• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IQSS / dataverse / #22002

01 Apr 2024 07:56PM CUT coverage: 20.716% (+0.5%) from 20.173%
#22002

push

github

web-flow
Merge pull request #10453 from IQSS/develop

Merge 6.2 into master

704 of 2679 new or added lines in 152 files covered. (26.28%)

81 existing lines in 49 files now uncovered.

17160 of 82836 relevant lines covered (20.72%)

0.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommand.java
1
package edu.harvard.iq.dataverse.engine.command.impl;
2

3
import edu.harvard.iq.dataverse.Dataset;
4
import edu.harvard.iq.dataverse.DatasetLock;
5
import edu.harvard.iq.dataverse.authorization.Permission;
6
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
7
import edu.harvard.iq.dataverse.engine.command.Command;
8
import edu.harvard.iq.dataverse.engine.command.CommandContext;
9
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
10
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
11
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
12
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
13
import edu.harvard.iq.dataverse.pidproviders.PidProvider;
14
import edu.harvard.iq.dataverse.privateurl.PrivateUrl;
15
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
16
import edu.harvard.iq.dataverse.util.BundleUtil;
17
import edu.harvard.iq.dataverse.workflow.Workflow;
18
import edu.harvard.iq.dataverse.workflow.WorkflowContext.TriggerType;
19
import java.util.Date;
20
import java.util.List;
21
import java.util.Optional;
22
import java.util.logging.Logger;
23
import static java.util.stream.Collectors.joining;
24
import static edu.harvard.iq.dataverse.engine.command.impl.PublishDatasetResult.Status;
25
import static edu.harvard.iq.dataverse.dataset.DatasetUtil.validateDatasetMetadataExternally;
26
import edu.harvard.iq.dataverse.util.StringUtil;
27

28

29
/**
30
 * Kick-off a dataset publication process. The process may complete immediately, 
31
 * but may also result in a workflow being started and pending on some external 
32
 * response. Either way, the process will be completed by an instance of 
33
 * {@link FinalizeDatasetPublicationCommand}.
34
 * 
35
 * @see FinalizeDatasetPublicationCommand
36
 * 
37
 * @author skraffmiller
38
 * @author michbarsinai
39
 */
40
@RequiredPermissions(Permission.PublishDataset)
41
public class PublishDatasetCommand extends AbstractPublishDatasetCommand<PublishDatasetResult> {
42
    private static final Logger logger = Logger.getLogger(PublishDatasetCommand.class.getName());
×
43
    boolean minorRelease;
44
    DataverseRequest request;
45
    
46
    /** 
47
     * The dataset was already released by an external system, and now Dataverse
48
     * is just internally marking this release version as released. This is happening
49
     * in scenarios like import or migration.
50
     */
51
    final boolean datasetExternallyReleased;
52
    
53
    public PublishDatasetCommand(Dataset datasetIn, DataverseRequest aRequest, boolean minor) {
54
        this( datasetIn, aRequest, minor, false );
×
55
    }
×
56
    
57
    public PublishDatasetCommand(Dataset datasetIn, DataverseRequest aRequest, boolean minor, boolean isPidPrePublished) {
58
        super(datasetIn, aRequest);
×
59
        minorRelease = minor;
×
60
        datasetExternallyReleased = isPidPrePublished;
×
61
        request = aRequest;
×
62
    }
×
63

64
    @Override
65
    public PublishDatasetResult execute(CommandContext ctxt) throws CommandException {
66
        
67
        verifyCommandArguments(ctxt);
×
68
        
69
        // Invariant 1: If we're here, publishing the dataset makes sense, from a "business logic" point of view.
70
        // Invariant 2: The latest version of the dataset is the one being published, EVEN IF IT IS NOT DRAFT.
71
        //              When importing a released dataset, the latest version is marked as RELEASED.
72

73
        Dataset theDataset = getDataset();
×
74
        
75
        validateOrDie(theDataset.getLatestVersion(), false);
×
76

77
        //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.
78
        //Changing the dataset in this transaction also potentially makes a race condition with a prepub workflow, possibly resulting in an OptimisticLockException there.
79
        
80
        // Set the version numbers:
81

82
        if (theDataset.getPublicationDate() == null) {
×
83
            // First Release
84
            theDataset.getLatestVersion().setVersionNumber(new Long(1)); // minor release is blocked by #verifyCommandArguments
×
85
            theDataset.getLatestVersion().setMinorVersionNumber(new Long(0));
×
86
            
87
        } else if ( minorRelease ) {
×
88
            theDataset.getLatestVersion().setVersionNumber(new Long(theDataset.getVersionNumber()));
×
89
            theDataset.getLatestVersion().setMinorVersionNumber(new Long(theDataset.getMinorVersionNumber() + 1));
×
90
            
91
        } else {
92
            // major, non-first release
93
            theDataset.getLatestVersion().setVersionNumber(new Long(theDataset.getVersionNumber() + 1));
×
94
            theDataset.getLatestVersion().setMinorVersionNumber(new Long(0));
×
95
        }
96
        
97
        // Perform any optional validation steps, if defined:
98
        if (ctxt.systemConfig().isExternalDatasetValidationEnabled()) {
×
99
            // For admins, an override of the external validation step may be enabled: 
100
            if (!(getUser().isSuperuser() && ctxt.systemConfig().isExternalValidationAdminOverrideEnabled())) {
×
101
                String executable = ctxt.systemConfig().getDatasetValidationExecutable();
×
102
                boolean result = validateDatasetMetadataExternally(theDataset, executable, getRequest());
×
103
            
104
                if (!result) {
×
105
                    String rejectionMessage = ctxt.systemConfig().getDatasetValidationFailureMsg();
×
106
                    throw new IllegalCommandException(rejectionMessage, this);
×
107
                }
108
            } 
109
        }
110
        
111
        //ToDo - should this be in onSuccess()? May relate to todo above 
112
        Optional<Workflow> prePubWf = ctxt.workflows().getDefaultWorkflow(TriggerType.PrePublishDataset);
×
113
        if ( prePubWf.isPresent() ) {
×
114
            // We start a workflow
115
            theDataset = ctxt.em().merge(theDataset);
×
116
            ctxt.em().flush();
×
117
            ctxt.workflows().start(prePubWf.get(), buildContext(theDataset, TriggerType.PrePublishDataset, datasetExternallyReleased), true);
×
118
            return new PublishDatasetResult(theDataset, Status.Workflow);
×
119
            
120
        } else{
121
            // We will skip trying to register the global identifiers for datafiles 
122
            // if "dependent" file-level identifiers are requested, AND the naming 
123
            // protocol of the dataset global id is different from the 
124
            // one currently configured for the Dataverse. This is to specifically 
125
            // address the issue with the datasets with handle ids registered, 
126
            // that are currently configured to use DOI.
127
            // If we are registering file-level identifiers, and there are more 
128
            // than the configured limit number of files, then call Finalize 
129
            // asychronously (default is 10)
130
            // ...
131
            // Additionaly in 4.9.3 we have added a system variable to disable 
132
            // registering file PIDs on the installation level.
133
            boolean registerGlobalIdsForFiles = 
×
NEW
134
                    ctxt.systemConfig().isFilePIDsEnabledForCollection(getDataset().getOwner()) &&
×
NEW
135
                            ctxt.dvObjects().getEffectivePidGenerator(getDataset()).canCreatePidsLike(getDataset().getGlobalId());
×
136
            
137
            boolean validatePhysicalFiles = ctxt.systemConfig().isDatafileValidationOnPublishEnabled();
×
138

139
            // As of v5.0, publishing a dataset is always done asynchronously, 
140
            // with the dataset locked for the duration of the operation. 
141
            
142
                
UNCOV
143
            String info = "Publishing the dataset; "; 
×
144
            info += registerGlobalIdsForFiles ? "Registering PIDs for Datafiles; " : "";
×
145
            info += validatePhysicalFiles ? "Validating Datafiles Asynchronously" : "";
×
146
            
147
            AuthenticatedUser user = request.getAuthenticatedUser();
×
148
            /*
149
             * datasetExternallyReleased is only true in the case of the
150
             * Dataverses.importDataset() and importDatasetDDI() methods. In that case, we
151
             * are still in the transaction that creates theDataset, so
152
             * A) Trying to create a DatasetLock referncing that dataset in a new 
153
             * transaction (as ctxt.datasets().addDatasetLock() does) will fail since the 
154
             * dataset doesn't yet exist, and 
155
             * B) a lock isn't needed because no one can be trying to edit it yet (as it
156
             * doesn't exist).
157
             * Thus, we can/need to skip creating the lock. Since the calls to removeLocks
158
             * in FinalizeDatasetPublicationCommand search for and remove existing locks, if
159
             * one doesn't exist, the removal is a no-op in this case.
160
             */
161
            if (!datasetExternallyReleased) {
×
162
                DatasetLock lock = new DatasetLock(DatasetLock.Reason.finalizePublication, user);
×
163
                lock.setDataset(theDataset);
×
164
                lock.setInfo(info);
×
165
                ctxt.datasets().addDatasetLock(theDataset, lock);
×
166
            }
167
            theDataset = ctxt.em().merge(theDataset);
×
168
            // The call to FinalizePublicationCommand has been moved to the new @onSuccess()
169
            // method:
170
            //ctxt.datasets().callFinalizePublishCommandAsynchronously(theDataset.getId(), ctxt, request, datasetExternallyReleased);
171
            return new PublishDatasetResult(theDataset, Status.Inprogress);
×
172
        }
173
    }
174
    
175
    /**
176
     * See that publishing the dataset in the requested manner makes sense, at
177
     * the given state of the dataset.
178
     * 
179
     * @throws IllegalCommandException if the publication request is invalid.
180
     */
181
    private void verifyCommandArguments(CommandContext ctxt) throws IllegalCommandException {
182
        if (!getDataset().getOwner().isReleased()) {
×
183
            throw new IllegalCommandException("This dataset may not be published because its host dataverse (" + getDataset().getOwner().getAlias() + ") has not been published.", this);
×
184
        }
185
        
186
        if ( ! getUser().isAuthenticated() ) {
×
187
            throw new IllegalCommandException("Only authenticated users can release a Dataset. Please authenticate and try again.", this);
×
188
        }
189
        
190
        if (getDataset().getLatestVersion().getTermsOfUseAndAccess() == null
×
191
                || (getDataset().getLatestVersion().getTermsOfUseAndAccess().getLicense() == null 
×
192
                && StringUtil.isEmpty(getDataset().getLatestVersion().getTermsOfUseAndAccess().getTermsOfUse()))) {
×
193
            throw new IllegalCommandException("Dataset must have a valid license or Custom Terms Of Use configured before it can be published.", this);
×
194
        }
195
        
196
        if ( (getDataset().isLockedFor(DatasetLock.Reason.Workflow)&&!ctxt.permissions().isMatchingWorkflowLock(getDataset(),request.getUser().getIdentifier(),request.getWFInvocationId())) 
×
197
                || getDataset().isLockedFor(DatasetLock.Reason.Ingest) 
×
198
                || getDataset().isLockedFor(DatasetLock.Reason.finalizePublication)
×
199
                || getDataset().isLockedFor(DatasetLock.Reason.EditInProgress)) {
×
200
            throw new IllegalCommandException("This dataset is locked. Reason: " 
×
201
                    + getDataset().getLocks().stream().map(l -> l.getReason().name()).collect( joining(",") )
×
202
                    + ". Please try publishing later.", this);
203
        }
204
        
205
        if ( getDataset().isLockedFor(DatasetLock.Reason.FileValidationFailed)) {
×
206
            throw new IllegalCommandException("This dataset cannot be published because some files have been found missing or corrupted. " 
×
207
                    + ". Please contact support to address this.", this);
208
        }
209
        
210
        if ( datasetExternallyReleased ) {
×
211
            if ( ! getDataset().getLatestVersion().isReleased() ) {
×
212
                throw new IllegalCommandException("Latest version of dataset " + getDataset().getIdentifier() + " is not marked as releasd.", this);
×
213
            }
214
                
215
        } else {
216
            if (getDataset().getLatestVersion().isReleased()) {
×
217
                throw new IllegalCommandException("Latest version of dataset " + getDataset().getIdentifier() + " is already released. Only draft versions can be released.", this);
×
218
            }
219

220
            // prevent publishing of 0.1 version
221
            if (minorRelease && getDataset().getVersions().size() == 1 && getDataset().getLatestVersion().isDraft()) {
×
222
                throw new IllegalCommandException("Cannot publish as minor version. Re-try as major release.", this);
×
223
            }
224

225
            if (minorRelease && !getDataset().getLatestVersion().isMinorUpdate()) {
×
226
                throw new IllegalCommandException("Cannot release as minor version. Re-try as major release.", this);
×
227
            }
228
        }
229
    }
×
230
    
231
    
232
    @Override
233
    public boolean onSuccess(CommandContext ctxt, Object r) {
234
        Dataset dataset = null;
×
235
        try{
236
            dataset = (Dataset) r;
×
237
        } catch (ClassCastException e){
×
238
            dataset  = ((PublishDatasetResult) r).getDataset();
×
239
        }
×
240

241
        if (dataset != null) {
×
242
            Optional<Workflow> prePubWf = ctxt.workflows().getDefaultWorkflow(TriggerType.PrePublishDataset);
×
243
            //A pre-publication workflow will call FinalizeDatasetPublicationCommand itself when it completes
244
            if (! prePubWf.isPresent() ) {
×
245
                logger.fine("From onSuccess, calling FinalizeDatasetPublicationCommand for dataset " + dataset.getGlobalId().asString());
×
246
                ctxt.datasets().callFinalizePublishCommandAsynchronously(dataset.getId(), ctxt, request, datasetExternallyReleased);
×
247
            } 
248
            return true;
×
249
        }
250
        
251
        return false;
×
252
    }
253
    
254
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc