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

IQSS / dataverse / #23566

25 Oct 2024 02:19PM UTC coverage: 20.892% (+0.02%) from 20.87%
#23566

Pull #10790

github

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

48 of 69 new or added lines in 7 files covered. (69.57%)

574 existing lines in 5 files now uncovered.

17994 of 86128 relevant lines covered (20.89%)

0.21 hits per line

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

29.3
/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
    private HashSet<String> managedSet;
40

41
    private HashSet<String> excludedSet;
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
        this.managedSet = new HashSet<String>();
1✔
51
        this.excludedSet = new HashSet<String>();
1✔
52
    }
1✔
53

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

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

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

94
    protected Map<String, String> addBasicMetadata(DvObject dvObjectIn, Map<String, String> metadata) {
95

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

101
        String producerString = pidProviderService.getProducer();
×
102

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

107
        String titleString = dvObjectIn.getCurrentName();
×
108

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

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

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

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

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

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

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

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

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

169
    public abstract boolean alreadyRegistered(GlobalId globalId, boolean noProviderDefault) throws Exception;
170

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

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

223
    private String generateDatasetIdentifier(Dataset dataset) {
224
        String shoulder = getShoulder();
×
225

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

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

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

258
        return true;
×
259
    }
260

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

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

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

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

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

325
    public GlobalId parsePersistentId(String protocol, String authority, String identifier) {
326
        logger.fine("Parsing: " + protocol + ":" + authority + getSeparator() + identifier + " in " + getId());
1✔
327
        if (!PidProvider.isValidGlobalId(protocol, authority, identifier)) {
1✔
328
            return null;
×
329
        }
330
        // Check authority/identifier if this is a provider that manages specific
331
        // identifiers
332
        // /is not one of the unmanaged providers that has null authority
333
        if (getAuthority() != null) {
1✔
334

335
            String cleanIdentifier = protocol + ":" + authority + getSeparator() + identifier;
1✔
336
            /*
337
             * Test if this provider manages this identifier - return null if it does not.
338
             * It does match if ((the identifier's authority and shoulder match the
339
             * provider's), or the identifier is in the managed set), and, in either case,
340
             * the identifier is not in the excluded set.
341
             */
342
            logger.fine("clean pid in " + getId() + ": " + cleanIdentifier);
1✔
343
            logger.fine("managed in " + getId() + ": " + getManagedSet().contains(cleanIdentifier));
1✔
344
            logger.fine("excluded from " + getId() + ": " + getExcludedSet().contains(cleanIdentifier));
1✔
345

346
            if (!(((authority.equals(getAuthority()) && identifier.startsWith(getShoulder()))
1✔
347
                    || getManagedSet().contains(cleanIdentifier)) && !getExcludedSet().contains(cleanIdentifier))) {
1✔
348
                return null;
1✔
349
            }
350
        }
351
        return new GlobalId(protocol, authority, identifier, getSeparator(), getUrlPrefix(), getId());
1✔
352
    }
353

354
    public String getSeparator() {
355
        // The standard default
356
        return SEPARATOR;
1✔
357
    }
358

359
    private String generateDataFileIdentifier(DataFile datafile) {
360
        String doiDataFileFormat = getDatafilePidFormat();
×
361

362
        String prepend = "";
×
363
        if (doiDataFileFormat.equals(SystemConfig.DataFilePIDFormat.DEPENDENT.toString())) {
×
364
            // If format is dependent then pre-pend the dataset identifier
365
            prepend = datafile.getOwner().getIdentifier() + SEPARATOR;
×
366
            datafile.setProtocol(datafile.getOwner().getProtocol());
×
367
            datafile.setAuthority(datafile.getOwner().getAuthority());
×
368
        } else {
369
            // If there's a shoulder prepend independent identifiers with it
370
            prepend = getShoulder();
×
371
            datafile.setProtocol(getProtocol());
×
372
            datafile.setAuthority(getAuthority());
×
373
        }
374

375
        switch (getIdentifierGenerationStyle()) {
×
376
        case "randomString":
377
            return generateIdentifierAsRandomString(datafile, prepend);
×
378
        case "storedProcGenerated":
379
            if (doiDataFileFormat.equals(SystemConfig.DataFilePIDFormat.INDEPENDENT.toString())) {
×
380
                return generateIdentifierFromStoredProcedureIndependent(datafile, prepend);
×
381
            } else {
382
                return generateIdentifierFromStoredProcedureDependent(datafile, prepend);
×
383
            }
384
        default:
385
            /* Should we throw an exception instead?? -- L.A. 4.6.2 */
386
            return generateIdentifierAsRandomString(datafile, prepend);
×
387
        }
388
    }
389

390
    /*
391
     * This method checks locally for a DvObject with the same PID and if that is
392
     * OK, checks with the PID service.
393
     * 
394
     * @param dvo - the object to check (ToDo - get protocol/authority from this
395
     * PidProvider object)
396
     * 
397
     * @param prepend - for Datasets, this is always the shoulder, for DataFiles, it
398
     * could be the shoulder or the parent Dataset identifier
399
     */
400
    private String generateIdentifierAsRandomString(DvObject dvo, String prepend) {
401
        String identifier = null;
×
402
        do {
403
            identifier = prepend + RandomStringUtils.randomAlphanumeric(6).toUpperCase();
×
404
        } while (!isGlobalIdUnique(new GlobalId(dvo.getProtocol(), dvo.getAuthority(), identifier, this.getSeparator(),
×
405
                this.getUrlPrefix(), this.getId())));
×
406

407
        return identifier;
×
408
    }
409

410
    /*
411
     * This method checks locally for a DvObject with the same PID and if that is
412
     * OK, checks with the PID service.
413
     * 
414
     * @param dvo - the object to check (ToDo - get protocol/authority from this
415
     * PidProvider object)
416
     * 
417
     * @param prepend - for Datasets, this is always the shoulder, for DataFiles, it
418
     * could be the shoulder or the parent Dataset identifier
419
     */
420

421
    private String generateIdentifierFromStoredProcedureIndependent(DvObject dvo, String prepend) {
422
        String identifier;
423
        do {
424
            String identifierFromStoredProcedure = pidProviderService.generateNewIdentifierByStoredProcedure();
×
425
            // some diagnostics here maybe - is it possible to determine that it's failing
426
            // because the stored procedure hasn't been created in the database?
427
            if (identifierFromStoredProcedure == null) {
×
428
                return null;
×
429
            }
430
            identifier = prepend + identifierFromStoredProcedure;
×
431
        } while (!isGlobalIdUnique(new GlobalId(dvo.getProtocol(), dvo.getAuthority(), identifier, this.getSeparator(),
×
432
                this.getUrlPrefix(), this.getId())));
×
433

434
        return identifier;
×
435
    }
436

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

450
        // This will catch identifiers already assigned in the current transaction (e.g.
451
        // in FinalizeDatasetPublicationCommand) that haven't been committed to the db
452
        // without having to make a call to the PIDProvider
453
        Set<String> existingIdentifiers = new HashSet<String>();
×
454
        List<DataFile> files = datafile.getOwner().getFiles();
×
455
        for (DataFile f : files) {
×
456
            existingIdentifiers.add(f.getIdentifier());
×
457
        }
×
458

459
        do {
460
            retVal++;
×
461
            identifier = prepend + retVal.toString();
×
462

463
        } while (existingIdentifiers.contains(identifier) || !isGlobalIdUnique(new GlobalId(datafile.getProtocol(),
×
464
                datafile.getAuthority(), identifier, this.getSeparator(), this.getUrlPrefix(), this.getId())));
×
465

466
        return identifier;
×
467
    }
468

469

470
    @Override
471
    public boolean canManagePID() {
472
        // The default expectation is that PID providers are configured to manage some
473
        // set (i.e. based on protocol/authority/shoulder) of PIDs
474
        return true;
1✔
475
    }
476

477
    @Override
478
    public void setPidProviderServiceBean(PidProviderFactoryBean pidProviderServiceBean) {
479
        this.pidProviderService = pidProviderServiceBean;
1✔
480
    }
1✔
481

482
    @Override
483
    public String getProtocol() {
484
        return protocol;
×
485
    }
486

487
    @Override
488
    public String getAuthority() {
489
        return authority;
1✔
490
    }
491

492
    @Override
493
    public String getShoulder() {
494
        return shoulder;
1✔
495
    }
496

497
    @Override
498
    public String getIdentifierGenerationStyle() {
499
        return identifierGenerationStyle;
1✔
500
    }
501

502
    @Override
503
    public String getDatafilePidFormat() {
504
        return datafilePidFormat;
×
505
    }
506

507
    @Override
508
    public Set<String> getManagedSet() {
509
        return managedSet;
1✔
510
    }
511

512
    @Override
513
    public Set<String> getExcludedSet() {
514
        return excludedSet;
1✔
515
    }
516

517
    @Override
518
    public String getId() {
519
        return id;
1✔
520
    }
521

522
    @Override
523
    public String getLabel() {
524
        return label;
1✔
525
    }
526

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