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

IQSS / dataverse / #24468

05 Feb 2025 03:21PM UTC coverage: 22.788% (+0.04%) from 22.752%
#24468

Pull #10790

github

web-flow
Merge b078516cf into aebec0536
Pull Request #10790: fix: issues in exporters and citations for PermaLink/non-DOI PIDs

51 of 69 new or added lines in 7 files covered. (73.91%)

1 existing line in 1 file now uncovered.

19949 of 87542 relevant lines covered (22.79%)

0.23 hits per line

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

40.0
/src/main/java/edu/harvard/iq/dataverse/pidproviders/AbstractPidProvider.java
1
package edu.harvard.iq.dataverse.pidproviders;
2

3
import edu.harvard.iq.dataverse.DataFile;
4
import edu.harvard.iq.dataverse.Dataset;
5
import edu.harvard.iq.dataverse.DatasetField;
6
import edu.harvard.iq.dataverse.DvObject;
7
import edu.harvard.iq.dataverse.GlobalId;
8
import edu.harvard.iq.dataverse.util.SystemConfig;
9
import jakarta.json.Json;
10
import jakarta.json.JsonObject;
11
import jakarta.json.JsonObjectBuilder;
12

13
import java.util.*;
14
import java.util.logging.Level;
15
import java.util.logging.Logger;
16

17
import org.apache.commons.lang3.RandomStringUtils;
18
import com.beust.jcommander.Strings;
19

20
public abstract class AbstractPidProvider implements PidProvider {
21

22
    private static final Logger logger = Logger.getLogger(AbstractPidProvider.class.getCanonicalName());
1✔
23

24
    public static String UNAVAILABLE = ":unav";
1✔
25
    public static final String SEPARATOR = "/";
26

27
    protected PidProviderFactoryBean pidProviderService;
28

29
    private String protocol;
30

31
    private String authority = null;
1✔
32

33
    private String shoulder = null;
1✔
34

35
    private String identifierGenerationStyle = null;
1✔
36

37
    private String datafilePidFormat = null;
1✔
38

39
    protected HashSet<String> managedSet = new HashSet<String>();
1✔
40

41
    protected HashSet<String> excludedSet = new HashSet<String>();
1✔
42

43
    private String id;
44
    private String label;
45

46
    protected AbstractPidProvider(String id, String label, String protocol) {
1✔
47
        this.id = id;
1✔
48
        this.label = label;
1✔
49
        this.protocol = protocol;
1✔
50
    }
1✔
51

52
    protected AbstractPidProvider(String id, String label, String protocol, String authority, String shoulder,
53
            String identifierGenerationStyle, String datafilePidFormat, String managedList, String excludedList) {
1✔
54
        this.id = id;
1✔
55
        this.label = label;
1✔
56
        this.protocol = protocol;
1✔
57
        this.authority = authority;
1✔
58
        this.shoulder = shoulder;
1✔
59
        this.identifierGenerationStyle = identifierGenerationStyle;
1✔
60
        this.datafilePidFormat = datafilePidFormat;
1✔
61
        if(!managedList.isEmpty()) {
1✔
62
            this.managedSet.addAll(Arrays.asList(managedList.split(",\\s")));
1✔
63
        }
64
        if(!excludedList.isEmpty()) {
1✔
65
            this.excludedSet.addAll(Arrays.asList(excludedList.split(",\\s")));
1✔
66
        }
67
        if (logger.isLoggable(Level.FINE)) {
1✔
68
            Iterator<String> iter = managedSet.iterator();
×
69
            while (iter.hasNext()) {
×
70
                logger.fine("managedSet in " + getId() + ": " + iter.next());
×
71
            }
72
            iter = excludedSet.iterator();
×
73
            while (iter.hasNext()) {
×
74
                logger.fine("excludedSet in " + getId() + ": " + iter.next());
×
75
            }
76
        }
77
    }
1✔
78

79
    @Override
80
    public Map<String, String> getMetadataForCreateIndicator(DvObject dvObjectIn) {
81
        logger.log(Level.FINE, "getMetadataForCreateIndicator(DvObject)");
×
82
        Map<String, String> metadata = new HashMap<>();
×
83
        metadata = addBasicMetadata(dvObjectIn, metadata);
×
84
        metadata.put("datacite.publicationyear", generateYear(dvObjectIn));
×
85
        metadata.put("_target", getTargetUrl(dvObjectIn));
×
86
        return metadata;
×
87
    }
88

89
    protected Map<String, String> getUpdateMetadata(DvObject dvObjectIn) {
90
        logger.log(Level.FINE, "getUpdateMetadataFromDataset");
×
91
        Map<String, String> metadata = new HashMap<>();
×
92
        metadata = addBasicMetadata(dvObjectIn, metadata);
×
93
        return metadata;
×
94
    }
95

96
    protected Map<String, String> addBasicMetadata(DvObject dvObjectIn, Map<String, String> metadata) {
97

98
        String authorString = dvObjectIn.getAuthorString();
×
99
        if (authorString.isEmpty() || authorString.contains(DatasetField.NA_VALUE)) {
×
100
            authorString = UNAVAILABLE;
×
101
        }
102

103
        String producerString = pidProviderService.getProducer();
×
104

105
        if (producerString.isEmpty() || producerString.equals(DatasetField.NA_VALUE)) {
×
106
            producerString = UNAVAILABLE;
×
107
        }
108

109
        String titleString = dvObjectIn.getCurrentName();
×
110

111
        if (titleString.isEmpty() || titleString.equals(DatasetField.NA_VALUE)) {
×
112
            titleString = UNAVAILABLE;
×
113
        }
114

115
        metadata.put("datacite.creator", authorString);
×
116
        metadata.put("datacite.title", titleString);
×
117
        metadata.put("datacite.publisher", producerString);
×
118
        metadata.put("datacite.publicationyear", generateYear(dvObjectIn));
×
119
        return metadata;
×
120
    }
121

122
    protected Map<String, String> addDOIMetadataForDestroyedDataset(DvObject dvObjectIn) {
123
        Map<String, String> metadata = new HashMap<>();
×
124
        String authorString = UNAVAILABLE;
×
125
        String producerString = UNAVAILABLE;
×
126
        String titleString = "This item has been removed from publication";
×
127

128
        metadata.put("datacite.creator", authorString);
×
129
        metadata.put("datacite.title", titleString);
×
130
        metadata.put("datacite.publisher", producerString);
×
131
        metadata.put("datacite.publicationyear", "9999");
×
132
        return metadata;
×
133
    }
134

135
    protected String getTargetUrl(DvObject dvObjectIn) {
136
        logger.log(Level.FINE, "getTargetUrl");
×
137
        return SystemConfig.getDataverseSiteUrlStatic() + dvObjectIn.getTargetUrl()
×
138
                + dvObjectIn.getGlobalId().asString();
×
139
    }
140

141
    @Override
142
    public String getIdentifier(DvObject dvObject) {
143
        GlobalId gid = dvObject.getGlobalId();
×
144
        return gid != null ? gid.asString() : null;
×
145
    }
146

147
    protected String generateYear(DvObject dvObjectIn) {
148
        return dvObjectIn.getYearPublishedCreated();
×
149
    }
150

151
    public Map<String, String> getMetadataForTargetURL(DvObject dvObject) {
152
        logger.log(Level.FINE, "getMetadataForTargetURL");
×
153
        HashMap<String, String> metadata = new HashMap<>();
×
154
        metadata.put("_target", getTargetUrl(dvObject));
×
155
        return metadata;
×
156
    }
157

158
    @Override
159
    public boolean alreadyRegistered(DvObject dvo) throws Exception {
160
        if (dvo == null) {
×
161
            logger.severe("Null DvObject sent to alreadyRegistered().");
×
162
            return false;
×
163
        }
164
        GlobalId globalId = dvo.getGlobalId();
×
165
        if (globalId == null) {
×
166
            return false;
×
167
        }
168
        return alreadyRegistered(globalId, false);
×
169
    }
170

171
    public abstract boolean alreadyRegistered(GlobalId globalId, boolean noProviderDefault) throws Exception;
172

173
    /*
174
     * ToDo: the DvObject being sent in provides partial support for the case where
175
     * it has a different authority/protocol than what is configured (i.e. a legacy
176
     * Pid that can actually be updated by the Pid account being used.) Removing
177
     * this now would potentially break/make it harder to handle that case prior to
178
     * support for configuring multiple Pid providers. Once that exists, it would be
179
     * cleaner to always find the PidProvider associated with the
180
     * protocol/authority/shoulder of the current dataset and then not pass the
181
     * DvObject as a param. (This would also remove calls to get the settings since
182
     * that would be done at construction.)
183
     */
184
    @Override
185
    public DvObject generatePid(DvObject dvObject) {
186

187
        if (dvObject.getProtocol() == null) {
1✔
188
            dvObject.setProtocol(getProtocol());
1✔
189
        } else {
190
            if (!dvObject.getProtocol().equals(getProtocol())) {
×
191
                logger.warning("The protocol of the DvObject (" + dvObject.getProtocol()
×
192
                        + ") does not match the configured protocol (" + getProtocol() + ")");
×
193
                throw new IllegalArgumentException("The protocol of the DvObject (" + dvObject.getProtocol()
×
194
                        + ") doesn't match that of the provider, id: " + getId());
×
195
            }
196
        }
197
        if (dvObject.getAuthority() == null) {
1✔
198
            dvObject.setAuthority(getAuthority());
1✔
199
        } else {
200
            if (!dvObject.getAuthority().equals(getAuthority())) {
×
201
                logger.warning("The authority of the DvObject (" + dvObject.getAuthority()
×
202
                        + ") does not match the configured authority (" + getAuthority() + ")");
×
203
                throw new IllegalArgumentException("The authority of the DvObject (" + dvObject.getAuthority()
×
204
                        + ") doesn't match that of the provider, id: " + getId());
×
205
            }
206
        }
207
        if (dvObject.getSeparator() == null) {
1✔
208
            dvObject.setSeparator(getSeparator());
1✔
209
        } else {
NEW
210
            if (!dvObject.getSeparator().equals(getSeparator())) {
×
NEW
211
                logger.warning("The separator of the DvObject (" + dvObject.getSeparator()
×
NEW
212
                        + ") does not match the configured separator (" + getSeparator() + ")");
×
NEW
213
                throw new IllegalArgumentException("The separator of the DvObject (" + dvObject.getSeparator()
×
NEW
214
                        + ") doesn't match that of the provider, id: " + getId());
×
215
            }
216
        }
217
        if (dvObject.isInstanceofDataset()) {
1✔
218
            dvObject.setIdentifier(generateDatasetIdentifier((Dataset) dvObject));
1✔
219
        } else {
220
            dvObject.setIdentifier(generateDataFileIdentifier((DataFile) dvObject));
×
221
        }
222
        return dvObject;
1✔
223
    }
224

225
    private String generateDatasetIdentifier(Dataset dataset) {
226
        String shoulder = getShoulder();
1✔
227

228
        switch (getIdentifierGenerationStyle()) {
1✔
229
        case "randomString":
230
            return generateIdentifierAsRandomString(dataset, shoulder);
1✔
231
        case "storedProcGenerated":
232
            return generateIdentifierFromStoredProcedureIndependent(dataset, shoulder);
×
233
        default:
234
            /* Should we throw an exception instead?? -- L.A. 4.6.2 */
235
            return generateIdentifierAsRandomString(dataset, shoulder);
×
236
        }
237
    }
238

239
    /**
240
     * Check that a identifier entered by the user is unique (not currently used for
241
     * any other study in this Dataverse Network) also check for duplicate in EZID
242
     * if needed
243
     * 
244
     * @param userIdentifier
245
     * @param dataset
246
     * @return {@code true} if the identifier is unique, {@code false} otherwise.
247
     */
248
    public boolean isGlobalIdUnique(GlobalId globalId) {
249
        if (!pidProviderService.isGlobalIdLocallyUnique(globalId)) {
1✔
250
            return false; // duplication found in local database
×
251
        }
252

253
        // not in local DB, look in the persistent identifier service
254
        try {
255
            return !alreadyRegistered(globalId, false);
1✔
256
        } catch (Exception e) {
×
257
            // we can live with failure - means identifier not found remotely
258
        }
259

260
        return true;
×
261
    }
262

263
    /**
264
     * Parse a Persistent Id and set the protocol, authority, and identifier
265
     * 
266
     * Example 1: doi:10.5072/FK2/BYM3IW protocol: doi authority: 10.5072
267
     * identifier: FK2/BYM3IW
268
     * 
269
     * Example 2: hdl:1902.1/111012 protocol: hdl authority: 1902.1 identifier:
270
     * 111012
271
     *
272
     * @param identifierString
273
     * @param separator        the string that separates the authority from the
274
     *                         identifier.
275
     * @param destination      the global id that will contain the parsed data.
276
     * @return {@code destination}, after its fields have been updated, or
277
     *         {@code null} if parsing failed.
278
     */
279
    @Override
280
    public GlobalId parsePersistentId(String fullIdentifierString) {
281
        // Occasionally, the protocol separator character ':' comes in still
282
        // URL-encoded as %3A (usually as a result of the URL having been
283
        // encoded twice):
284
        fullIdentifierString = fullIdentifierString.replace("%3A", ":");
1✔
285

286
        int index1 = fullIdentifierString.indexOf(':');
1✔
287
        if (index1 > 0) { // ':' found with one or more characters before it
1✔
288
            String protocol = fullIdentifierString.substring(0, index1);
1✔
289
            GlobalId globalId = parsePersistentId(protocol, fullIdentifierString.substring(index1 + 1));
1✔
290
            return globalId;
1✔
291
        }
292
        logger.log(Level.INFO, "Error parsing identifier: {0}: ''<protocol>:'' not found in string",
×
293
                fullIdentifierString);
294
        return null;
×
295
    }
296

297
    protected GlobalId parsePersistentId(String protocol, String identifierString) {
298
        String authority;
299
        String identifier;
300
        if (identifierString == null) {
1✔
301
            return null;
×
302
        }
303
        int index = identifierString.indexOf(getSeparator());
1✔
304
        if (index > 0 && (index + 1) < identifierString.length()) {
1✔
305
            // '/' found with one or more characters
306
            // before and after it
307
            // Strip any whitespace, ; and ' from authority (should finding them cause a
308
            // failure instead?)
309
            authority = PidProvider.formatIdentifierString(identifierString.substring(0, index));
1✔
310

311
            if (PidProvider.testforNullTerminator(authority)) {
1✔
312
                return null;
×
313
            }
314
            identifier = PidProvider.formatIdentifierString(identifierString.substring(index + 1));
1✔
315
            if (PidProvider.testforNullTerminator(identifier)) {
1✔
316
                return null;
×
317
            }
318

319
        } else {
320
            logger.log(Level.INFO, "Error parsing identifier: {0}: '':<authority>/<identifier>'' not found in string",
×
321
                    identifierString);
322
            return null;
×
323
        }
324
        return parsePersistentId(protocol, authority, identifier);
1✔
325
    }
326

327
    public GlobalId parsePersistentId(String protocol, String authority, String identifier) {
328
        return parsePersistentId(protocol, authority, identifier, false);
1✔
329
    }
330
    
331
    public GlobalId parsePersistentId(String protocol, String authority, String identifier, boolean isCaseInsensitive) {
332
        logger.fine("Parsing: " + protocol + ":" + authority + getSeparator() + identifier + " in " + getId());
1✔
333
        if (!PidProvider.isValidGlobalId(protocol, authority, identifier)) {
1✔
334
            return null;
×
335
        }
336
        if(isCaseInsensitive) {
1✔
337
            identifier = identifier.toUpperCase();
1✔
338
        }
339
        // Check authority/identifier if this is a provider that manages specific
340
        // identifiers
341
        // /is not one of the unmanaged providers that has null authority
342
        if (getAuthority() != null) {
1✔
343

344
            String cleanIdentifier = protocol + ":" + authority + getSeparator() + identifier;
1✔
345
            /*
346
             * Test if this provider manages this identifier - return null if it does not.
347
             * It does match if ((the identifier's authority and shoulder match the
348
             * provider's), or the identifier is in the managed set), and, in either case,
349
             * the identifier is not in the excluded set.
350
             */
351
            logger.fine("clean pid in " + getId() + ": " + cleanIdentifier);
1✔
352
            logger.fine("managed in " + getId() + ": " + getManagedSet().contains(cleanIdentifier));
1✔
353
            logger.fine("excluded from " + getId() + ": " + getExcludedSet().contains(cleanIdentifier));
1✔
354

355
            if (!(((authority.equals(getAuthority()) && identifier.startsWith(getShoulder().toUpperCase()))
1✔
356
                    || getManagedSet().contains(cleanIdentifier)) && !getExcludedSet().contains(cleanIdentifier))) {
1✔
357
                return null;
1✔
358
            }
359
        }
360
        return new GlobalId(protocol, authority, identifier, getSeparator(), getUrlPrefix(), getId());
1✔
361
    }
362

363
    public String getSeparator() {
364
        // The standard default
365
        return SEPARATOR;
1✔
366
    }
367

368
    private String generateDataFileIdentifier(DataFile datafile) {
369
        String doiDataFileFormat = getDatafilePidFormat();
×
370

371
        String prepend = "";
×
372
        if (doiDataFileFormat.equals(SystemConfig.DataFilePIDFormat.DEPENDENT.toString())) {
×
373
            // If format is dependent then pre-pend the dataset identifier
374
            prepend = datafile.getOwner().getIdentifier() + SEPARATOR;
×
375
            datafile.setProtocol(datafile.getOwner().getProtocol());
×
376
            datafile.setAuthority(datafile.getOwner().getAuthority());
×
377
        } else {
378
            // If there's a shoulder prepend independent identifiers with it
379
            prepend = getShoulder();
×
380
            datafile.setProtocol(getProtocol());
×
381
            datafile.setAuthority(getAuthority());
×
382
        }
383

384
        switch (getIdentifierGenerationStyle()) {
×
385
        case "randomString":
386
            return generateIdentifierAsRandomString(datafile, prepend);
×
387
        case "storedProcGenerated":
388
            if (doiDataFileFormat.equals(SystemConfig.DataFilePIDFormat.INDEPENDENT.toString())) {
×
389
                return generateIdentifierFromStoredProcedureIndependent(datafile, prepend);
×
390
            } else {
391
                return generateIdentifierFromStoredProcedureDependent(datafile, prepend);
×
392
            }
393
        default:
394
            /* Should we throw an exception instead?? -- L.A. 4.6.2 */
395
            return generateIdentifierAsRandomString(datafile, prepend);
×
396
        }
397
    }
398

399
    /*
400
     * This method checks locally for a DvObject with the same PID and if that is
401
     * OK, checks with the PID service.
402
     * 
403
     * @param dvo - the object to check (ToDo - get protocol/authority from this
404
     * PidProvider object)
405
     * 
406
     * @param prepend - for Datasets, this is always the shoulder, for DataFiles, it
407
     * could be the shoulder or the parent Dataset identifier
408
     */
409
    private String generateIdentifierAsRandomString(DvObject dvo, String prepend) {
410
        String identifier = null;
1✔
411
        do {
412
            identifier = prepend + RandomStringUtils.randomAlphanumeric(6).toUpperCase();
1✔
413
        } while (!isGlobalIdUnique(new GlobalId(dvo.getProtocol(), dvo.getAuthority(), identifier, this.getSeparator(),
1✔
414
                this.getUrlPrefix(), this.getId())));
1✔
415

416
        return identifier;
1✔
417
    }
418

419
    /*
420
     * This method checks locally for a DvObject with the same PID and if that is
421
     * OK, checks with the PID service.
422
     * 
423
     * @param dvo - the object to check (ToDo - get protocol/authority from this
424
     * PidProvider object)
425
     * 
426
     * @param prepend - for Datasets, this is always the shoulder, for DataFiles, it
427
     * could be the shoulder or the parent Dataset identifier
428
     */
429

430
    private String generateIdentifierFromStoredProcedureIndependent(DvObject dvo, String prepend) {
431
        String identifier;
432
        do {
433
            String identifierFromStoredProcedure = pidProviderService.generateNewIdentifierByStoredProcedure();
×
434
            // some diagnostics here maybe - is it possible to determine that it's failing
435
            // because the stored procedure hasn't been created in the database?
436
            if (identifierFromStoredProcedure == null) {
×
437
                return null;
×
438
            }
439
            identifier = prepend + identifierFromStoredProcedure;
×
440
        } while (!isGlobalIdUnique(new GlobalId(dvo.getProtocol(), dvo.getAuthority(), identifier, this.getSeparator(),
×
441
                this.getUrlPrefix(), this.getId())));
×
442

443
        return identifier;
×
444
    }
445

446
    /*
447
     * This method is only used for DataFiles with DEPENDENT Pids. It is not for
448
     * Datasets
449
     * 
450
     */
451
    private String generateIdentifierFromStoredProcedureDependent(DataFile datafile, String prepend) {
452
        String identifier;
453
        Long retVal;
454
        retVal = Long.valueOf(0L);
×
455
        // ToDo - replace loops with one lookup for largest entry? (the do loop runs
456
        // ~n**2/2 calls). The check for existingIdentifiers means this is mostly a
457
        // local loop now, versus involving db or PidProvider calls, but still...)
458

459
        // This will catch identifiers already assigned in the current transaction (e.g.
460
        // in FinalizeDatasetPublicationCommand) that haven't been committed to the db
461
        // without having to make a call to the PIDProvider
462
        Set<String> existingIdentifiers = new HashSet<String>();
×
463
        List<DataFile> files = datafile.getOwner().getFiles();
×
464
        for (DataFile f : files) {
×
465
            existingIdentifiers.add(f.getIdentifier());
×
466
        }
×
467

468
        do {
469
            retVal++;
×
470
            identifier = prepend + retVal.toString();
×
471

472
        } while (existingIdentifiers.contains(identifier) || !isGlobalIdUnique(new GlobalId(datafile.getProtocol(),
×
473
                datafile.getAuthority(), identifier, this.getSeparator(), this.getUrlPrefix(), this.getId())));
×
474

475
        return identifier;
×
476
    }
477

478

479
    @Override
480
    public boolean canManagePID() {
481
        // The default expectation is that PID providers are configured to manage some
482
        // set (i.e. based on protocol/authority/shoulder) of PIDs
483
        return true;
1✔
484
    }
485

486
    @Override
487
    public void setPidProviderServiceBean(PidProviderFactoryBean pidProviderServiceBean) {
488
        this.pidProviderService = pidProviderServiceBean;
1✔
489
    }
1✔
490

491
    @Override
492
    public String getProtocol() {
493
        return protocol;
1✔
494
    }
495

496
    @Override
497
    public String getAuthority() {
498
        return authority;
1✔
499
    }
500

501
    @Override
502
    public String getShoulder() {
503
        return shoulder;
1✔
504
    }
505

506
    @Override
507
    public String getIdentifierGenerationStyle() {
508
        return identifierGenerationStyle;
1✔
509
    }
510

511
    @Override
512
    public String getDatafilePidFormat() {
513
        return datafilePidFormat;
×
514
    }
515

516
    @Override
517
    public Set<String> getManagedSet() {
518
        return managedSet;
1✔
519
    }
520

521
    @Override
522
    public Set<String> getExcludedSet() {
523
        return excludedSet;
1✔
524
    }
525

526
    @Override
527
    public String getId() {
528
        return id;
1✔
529
    }
530

531
    @Override
532
    public String getLabel() {
533
        return label;
1✔
534
    }
535

536
    @Override
537
    /**
538
     * True if this provider can manage PIDs in general, this pid is not in the
539
     * managedSet (meaning it is managed but the provider does not generally manage
540
     * it's protocol/authority/separator/shoulder) and either this provider is the
541
     * same as the pid's or we're allowed to create INDEPENDENT pids. The latter
542
     * clause covers the potential case where the effective pid provider/generator
543
     * for the dataset is set to a different one that handles the dataset's pid
544
     * itself. In this case, we can create file PIDs if they are independent.
545
     * 
546
     * @param pid - the related pid to check
547
     * @return true if this provider can manage PIDs like the one supplied
548
     */
549
    public boolean canCreatePidsLike(GlobalId pid) {
550
        return canManagePID() && !managedSet.contains(pid.asString())
1✔
551
                && (getIdentifierGenerationStyle().equals("INDEPENDENT") || getId().equals(pid.getProviderId()));
1✔
552
    }
553
    
554
    @Override
555
    public JsonObject getProviderSpecification() {
556
        JsonObjectBuilder providerSpecification = Json.createObjectBuilder();
×
557
        providerSpecification.add("id", id);
×
558
        providerSpecification.add("label", label);
×
559
        providerSpecification.add("protocol", protocol);
×
560
        providerSpecification.add("authority", authority);
×
561
        providerSpecification.add("separator", getSeparator());
×
562
        providerSpecification.add("shoulder", shoulder);
×
563
        providerSpecification.add("identifierGenerationStyle", identifierGenerationStyle);
×
564
        providerSpecification.add("datafilePidFormat", datafilePidFormat);
×
565
        providerSpecification.add("managedSet", Strings.join(",", managedSet.toArray()));
×
566
        providerSpecification.add("excludedSet", Strings.join(",", excludedSet.toArray()));
×
567
        return providerSpecification.build();
×
568
    }
569
    
570
    @Override
571
    public boolean updateIdentifier(DvObject dvObject) {
572
        //By default, these are the same
573
        return publicizeIdentifier(dvObject);
×
574
    }
575
}
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