• 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

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

3
import edu.harvard.iq.dataverse.Dataset;
4
import edu.harvard.iq.dataverse.DatasetField;
5
import edu.harvard.iq.dataverse.DatasetVersion;
6
import edu.harvard.iq.dataverse.DatasetVersionDifference;
7
import edu.harvard.iq.dataverse.DatasetVersionUser;
8
import edu.harvard.iq.dataverse.Dataverse;
9
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
10
import edu.harvard.iq.dataverse.engine.command.AbstractCommand;
11
import edu.harvard.iq.dataverse.engine.command.CommandContext;
12
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
13
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
14
import edu.harvard.iq.dataverse.engine.command.exception.CommandExecutionException;
15
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
16
import edu.harvard.iq.dataverse.pidproviders.PidProvider;
17
import edu.harvard.iq.dataverse.pidproviders.PidUtil;
18
import edu.harvard.iq.dataverse.util.BundleUtil;
19

20
import java.sql.Timestamp;
21
import java.util.Date;
22
import java.util.Set;
23
import java.util.logging.Level;
24
import java.util.logging.Logger;
25
import static java.util.stream.Collectors.joining;
26

27
import jakarta.validation.ConstraintViolation;
28
import edu.harvard.iq.dataverse.MetadataBlock;
29
import edu.harvard.iq.dataverse.TermsOfUseAndAccess;
30
import edu.harvard.iq.dataverse.settings.JvmSettings;
31

32
/**
33
 *
34
 * Base class for commands that deal with {@code Dataset}s.Mainly here as a code
35
 * re-use mechanism.
36
 *
37
 * @author michael
38
 * @param <T> The type of the command's result. Normally {@link Dataset}.
39
 */
40
public abstract class AbstractDatasetCommand<T> extends AbstractCommand<T> {
41

42
    private static final Logger logger = Logger.getLogger(AbstractDatasetCommand.class.getName());
1✔
43
    private static final int FOOLPROOF_RETRIAL_ATTEMPTS_LIMIT = 2 ^ 8;
44
    private Dataset dataset;
45
    private final Timestamp timestamp = new Timestamp(new Date().getTime());
1✔
46

47
    public AbstractDatasetCommand(DataverseRequest aRequest, Dataset aDataset, Dataverse parent) {
48
        super(aRequest, parent);
1✔
49
        if (aDataset == null) {
1✔
50
            throw new IllegalArgumentException("aDataset cannot be null");
1✔
51
        }
52
        dataset = aDataset;
×
53
    }
×
54

55
    public AbstractDatasetCommand(DataverseRequest aRequest, Dataset aDataset) {
56
        super(aRequest, aDataset);
1✔
57
        if (aDataset == null) {
1✔
58
            throw new IllegalArgumentException("aDataset cannot be null");
1✔
59
        }
60
        dataset = aDataset;
1✔
61
    }
1✔
62

63
    /**
64
     * Creates/updates the {@link DatasetVersionUser} for our {@link #dataset}. After
65
     * calling this method, there is a {@link DatasetUser} object connecting
66
     * {@link #dataset} and the {@link AuthenticatedUser} who issued this
67
     * command, with the {@code lastUpdate} field containing {@link #timestamp}.
68
     *
69
     * @param ctxt The command context in which this command runs.
70
     */
71
    protected void updateDatasetUser(CommandContext ctxt) {
72
        DatasetVersionUser datasetDataverseUser = ctxt.datasets().getDatasetVersionUser(getDataset().getLatestVersion(), getUser());
1✔
73

74
        if (datasetDataverseUser != null) {
1✔
75
            // Update existing dataset-user
76
            datasetDataverseUser.setLastUpdateDate(getTimestamp());
×
77
            ctxt.em().merge(datasetDataverseUser);
×
78

79
        } else {
80
            // create a new dataset-user
81
            createDatasetUser(ctxt);
1✔
82
        }
83
    }
1✔
84
    
85
    protected void createDatasetUser(CommandContext ctxt) {
86
        DatasetVersionUser datasetDataverseUser = new DatasetVersionUser();
1✔
87
        datasetDataverseUser.setDatasetVersion(getDataset().getLatestVersion());
1✔
88
        datasetDataverseUser.setLastUpdateDate(getTimestamp());
1✔
89
        datasetDataverseUser.setAuthenticatedUser((AuthenticatedUser) getUser());
1✔
90
        ctxt.em().persist(datasetDataverseUser);
1✔
91
    }
1✔
92
    
93
    /**
94
     * Validates the fields of the {@link DatasetVersion} passed. Throws an
95
     * informational error if validation fails.
96
     *
97
     * @param dsv The dataset version whose fields we validate
98
     * @param lenient when {@code true}, invalid fields are populated with N/A
99
     * value.
100
     * @throws CommandException if and only if {@code lenient=false}, and field
101
     * validation failed.
102
     */
103
    protected void validateOrDie(DatasetVersion dsv, Boolean lenient) throws CommandException {
104
        Set<ConstraintViolation> constraintViolations = dsv.validate();
1✔
105
        if (!constraintViolations.isEmpty()) {
1✔
106
            if (lenient) {
×
107
                // populate invalid fields with N/A
108
                constraintViolations.stream()
×
109
                    .filter(cv -> cv.getRootBean() instanceof DatasetField)
×
110
                    .map(cv -> ((DatasetField) cv.getRootBean()))
×
111
                    .forEach(f -> f.setSingleValue(DatasetField.NA_VALUE));
×
112

113
            } else {
114
                // explode with a helpful message
115
                String validationMessage = constraintViolations.stream()
×
116
                    .map(cv -> cv.getMessage() + " (Invalid value:" + cv.getInvalidValue() + ")")
×
117
                    .collect(joining(", ", "Validation Failed: ", "."));
×
118
                
119
                validationMessage  += constraintViolations.stream()
×
120
                    .filter(cv -> cv.getRootBean() instanceof TermsOfUseAndAccess)
×
121
                    .map(cv -> cv.toString());
×
122
                
123
                for (ConstraintViolation cv : constraintViolations){
×
124
                    if (cv.getRootBean() instanceof TermsOfUseAndAccess){
×
125
                        throw new IllegalCommandException(validationMessage,  this);
×
126
                    }
127
                }
×
128

129
                throw new IllegalCommandException(validationMessage, this);
×
130
            }
131
        }
132
    }
1✔
133

134

135

136
    /**
137
     * Whether it's EZID or DataCite, if the registration is refused because the
138
     * identifier already exists, we'll generate another one and try to register
139
     * again... but only up to some reasonably high number of times - so that we
140
     * don't go into an infinite loop here, if EZID is giving us these duplicate
141
     * messages in error.
142
     *
143
     * (and we do want the limit to be a "reasonably high" number! true, if our
144
     * identifiers are randomly generated strings, then it is highly unlikely
145
     * that we'll ever run into a duplicate race condition repeatedly; but if
146
     * they are sequential numeric values, than it is entirely possible that a
147
     * large enough number of values will be legitimately registered by another
148
     * entity sharing the same authority...)
149
     *
150
     * @param theDataset
151
     * @param ctxt
152
     * @throws CommandException
153
     */
154
    protected void registerExternalIdentifier(Dataset theDataset, CommandContext ctxt, boolean retry) throws CommandException {
155
        if (!theDataset.isIdentifierRegistered()) {
×
NEW
156
            PidProvider pidProvider = PidUtil.getPidProvider(theDataset.getGlobalId().getProviderId());
×
NEW
157
            if ( pidProvider != null ) {
×
158
                try {
NEW
159
                    if (pidProvider.alreadyRegistered(theDataset)) {
×
160
                        int attempts = 0;
×
161
                        if(retry) {
×
162
                            do  {
NEW
163
                                pidProvider.generatePid(theDataset);
×
164
                                logger.log(Level.INFO, "Attempting to register external identifier for dataset {0} (trying: {1}).",
×
165
                                    new Object[]{theDataset.getId(), theDataset.getIdentifier()});
×
166
                                attempts++;
×
NEW
167
                            } while (pidProvider.alreadyRegistered(theDataset) && attempts <= FOOLPROOF_RETRIAL_ATTEMPTS_LIMIT);
×
168
                        }
169
                        if(!retry) {
×
170
                            logger.warning("Reserving PID for: "  + getDataset().getId() + " during publication failed.");
×
171
                            throw new IllegalCommandException(BundleUtil.getStringFromBundle("publishDatasetCommand.pidNotReserved"), this);
×
172
                        }
173
                        if(attempts > FOOLPROOF_RETRIAL_ATTEMPTS_LIMIT) {
×
174
                            //Didn't work - we existed the loop with too many tries
175
                            throw new CommandExecutionException("This dataset may not be published because its identifier is already in use by another dataset; "
×
176
                                + "gave up after " + attempts + " attempts. Current (last requested) identifier: " + theDataset.getIdentifier(), this);
×
177
                        }
178
                    }
179
                    // Invariant: Dataset identifier does not exist in the remote registry
180
                    try {
NEW
181
                        pidProvider.createIdentifier(theDataset);
×
182
                        theDataset.setGlobalIdCreateTime(getTimestamp());
×
183
                        theDataset.setIdentifierRegistered(true);
×
184
                    } catch (Throwable ex) {
×
185
                        logger.info("Call to globalIdServiceBean.createIdentifier failed: " + ex);
×
186
                    }
×
187

188
                } catch (Throwable e) {
×
NEW
189
                    throw new CommandException(BundleUtil.getStringFromBundle("dataset.publish.error", pidProvider.getProviderInformation()), this);
×
190
                }
×
191
            } else {
192
                throw new IllegalCommandException("This dataset may not be published because its id registry service is not supported.", this);
×
193
            }
194

195
        }
196
    }
×
197

198
    protected Dataset getDataset() {
199
        return dataset;
1✔
200
    }
201

202
    public void setDataset(Dataset dataset) {
203
        this.dataset = dataset;
×
204
    }
×
205

206
    /**
207
     * The time the command instance was created. Note: This is not the time the
208
     * command was submitted to the engine. If the difference can be large
209
     * enough, consider using another timestamping mechanism. This is a
210
     * convenience method fit for most cases.
211
     *
212
     * @return the time {@code this} command was created.
213
     */
214
    protected Timestamp getTimestamp() {
215
        return timestamp;
1✔
216
    }
217

218
    protected void checkSystemMetadataKeyIfNeeded(DatasetVersion newVersion, DatasetVersion persistedVersion) throws IllegalCommandException {
219
        Set<MetadataBlock> changedMDBs = DatasetVersionDifference.getBlocksWithChanges(newVersion, persistedVersion);
1✔
220
        for (MetadataBlock mdb : changedMDBs) {
1✔
221
            logger.fine(mdb.getName() + " has been changed");
1✔
222
            String smdbString = JvmSettings.MDB_SYSTEM_KEY_FOR.lookupOptional(mdb.getName())
1✔
223
                    .orElse(null);
1✔
224
            if (smdbString != null) {
1✔
225
                logger.fine("Found key: " + smdbString);
×
226
                String mdKey = getRequest().getSystemMetadataBlockKeyFor(mdb.getName());
×
227
                logger.fine("Found supplied key: " + mdKey);
×
228
                if (mdKey == null || !mdKey.equalsIgnoreCase(smdbString)) {
×
229
                    throw new IllegalCommandException("Updating system metadata in block " + mdb.getName() + " requires a valid key", this);
×
230
                }
231
            }
232
        }
1✔
233
    }
1✔
234
}
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