• 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

35.57
/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java
1
package edu.harvard.iq.dataverse.pidproviders.doi;
2

3
import java.io.ByteArrayOutputStream;
4
import java.io.IOException;
5
import java.io.OutputStream;
6
import java.net.MalformedURLException;
7
import java.net.URI;
8
import java.net.URISyntaxException;
9
import java.net.URL;
10
import java.text.ParseException;
11
import java.text.SimpleDateFormat;
12
import java.util.ArrayList;
13
import java.util.Arrays;
14
import java.util.Date;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Optional;
20
import java.util.Set;
21
import java.util.logging.Logger;
22

23
import javax.xml.stream.XMLOutputFactory;
24
import javax.xml.stream.XMLStreamException;
25
import javax.xml.stream.XMLStreamWriter;
26

27
import org.apache.commons.lang3.StringUtils;
28
import org.apache.commons.text.StringEscapeUtils;
29
import org.ocpsoft.common.util.Strings;
30

31
import edu.harvard.iq.dataverse.AlternativePersistentIdentifier;
32
import edu.harvard.iq.dataverse.DataFile;
33
import edu.harvard.iq.dataverse.Dataset;
34
import edu.harvard.iq.dataverse.DatasetAuthor;
35
import edu.harvard.iq.dataverse.DatasetField;
36
import edu.harvard.iq.dataverse.DatasetFieldCompoundValue;
37
import edu.harvard.iq.dataverse.DatasetFieldConstant;
38
import edu.harvard.iq.dataverse.DatasetFieldServiceBean;
39
import edu.harvard.iq.dataverse.DatasetRelPublication;
40
import edu.harvard.iq.dataverse.DatasetVersion;
41
import edu.harvard.iq.dataverse.DvObject;
42
import edu.harvard.iq.dataverse.ExternalIdentifier;
43
import edu.harvard.iq.dataverse.FileMetadata;
44
import edu.harvard.iq.dataverse.GlobalId;
45
import edu.harvard.iq.dataverse.TermsOfUseAndAccess;
46
import edu.harvard.iq.dataverse.api.Util;
47
import edu.harvard.iq.dataverse.dataset.DatasetType;
48
import edu.harvard.iq.dataverse.dataset.DatasetUtil;
49
import edu.harvard.iq.dataverse.license.License;
50
import edu.harvard.iq.dataverse.pidproviders.AbstractPidProvider;
51
import edu.harvard.iq.dataverse.pidproviders.PidProvider;
52
import edu.harvard.iq.dataverse.pidproviders.PidUtil;
53
import edu.harvard.iq.dataverse.pidproviders.handle.HandlePidProvider;
54
import edu.harvard.iq.dataverse.pidproviders.perma.PermaLinkPidProvider;
55
import edu.harvard.iq.dataverse.util.BundleUtil;
56
import edu.harvard.iq.dataverse.util.PersonOrOrgUtil;
57
import edu.harvard.iq.dataverse.util.xml.XmlPrinter;
58
import edu.harvard.iq.dataverse.util.xml.XmlWriterUtil;
59
import jakarta.enterprise.inject.spi.CDI;
60
import jakarta.json.JsonObject;
61

62
public class XmlMetadataTemplate {
63

64
    private static final Logger logger = Logger.getLogger(XmlMetadataTemplate.class.getName());
1✔
65

66
    public static final String XML_NAMESPACE = "http://datacite.org/schema/kernel-4";
67
    public static final String XML_SCHEMA_LOCATION = "http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4.5/metadata.xsd";
68
    public static final String XML_XSI = "http://www.w3.org/2001/XMLSchema-instance";
69
    public static final String XML_SCHEMA_VERSION = "4.5";
70

71
    private DoiMetadata doiMetadata;
72
    //QDR - used to get ROR name from ExternalVocabularyValue via pidProvider.get
73
    private PidProvider pidProvider = null;
1✔
74

75
    public XmlMetadataTemplate() {
×
76
    }
×
77

78
    public XmlMetadataTemplate(DoiMetadata doiMetadata) {
1✔
79
        this.doiMetadata = doiMetadata;
1✔
80
    }
1✔
81

82
    public String generateXML(DvObject dvObject) {
83
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
1✔
84
            generateXML(dvObject, outputStream);
1✔
85

86
            String xml = outputStream.toString();
1✔
87
            logger.fine(xml);
1✔
88
            return XmlPrinter.prettyPrintXml(xml);
1✔
89
        } catch (XMLStreamException | IOException e) {
×
90
            logger.severe("Unable to generate DataCite XML for DOI: " + dvObject.getGlobalId().asString() + " : " + e.getMessage());
×
91
            e.printStackTrace();
×
92
        }
93
        return null;
×
94
    }
95

96
    private void generateXML(DvObject dvObject, OutputStream outputStream) throws XMLStreamException {
97
        // Could/should use dataset metadata language for metadata from DvObject itself?
98
        String language = null; // machine locale? e.g. for Publisher which is global
1✔
99
        String metadataLanguage = null; // when set, otherwise = language?
1✔
100
        
101
        //QDR - used to get ROR name from ExternalVocabularyValue via pidProvider.get
102
        GlobalId pid = null;
1✔
103
        pid = dvObject.getGlobalId();
1✔
104
        if ((pid == null) && (dvObject instanceof DataFile df)) {
1✔
105
                pid = df.getOwner().getGlobalId();
×
106
            }
107
        pidProvider = PidUtil.getPidProvider(pid.getProviderId());
1✔
108
        XMLStreamWriter xmlw = XMLOutputFactory.newInstance().createXMLStreamWriter(outputStream);
1✔
109
        xmlw.writeStartElement("resource");
1✔
110
        boolean deaccessioned=false;
1✔
111
        if(dvObject instanceof Dataset d) {
1✔
112
            deaccessioned=d.isDeaccessioned();
1✔
113
        } else if (dvObject instanceof DataFile df) {
×
114
            deaccessioned = df.isDeaccessioned();
×
115
        }
116
        xmlw.writeDefaultNamespace(XML_NAMESPACE);
1✔
117
        xmlw.writeAttribute("xmlns:xsi", XML_XSI);
1✔
118
        xmlw.writeAttribute("xsi:schemaLocation", XML_SCHEMA_LOCATION);
1✔
119

120
        writeIdentifier(xmlw, dvObject);
1✔
121
        writeCreators(xmlw, doiMetadata.getAuthors(), deaccessioned);
1✔
122
        writeTitles(xmlw, dvObject, language, deaccessioned);
1✔
123
        writePublisher(xmlw, dvObject, deaccessioned);
1✔
124
        writePublicationYear(xmlw, dvObject, deaccessioned);
1✔
125
        if (!deaccessioned) {
1✔
126
            writeSubjects(xmlw, dvObject);
1✔
127
            writeContributors(xmlw, dvObject);
1✔
128
            writeDates(xmlw, dvObject);
1✔
129
            writeLanguage(xmlw, dvObject);
1✔
130
        }
131
        writeResourceType(xmlw, dvObject);
1✔
132
        if (!deaccessioned) {
1✔
133
            writeAlternateIdentifiers(xmlw, dvObject);
1✔
134
            writeRelatedIdentifiers(xmlw, dvObject);
1✔
135
            writeSize(xmlw, dvObject);
1✔
136
            writeFormats(xmlw, dvObject);
1✔
137
            writeVersion(xmlw, dvObject);
1✔
138
            writeAccessRights(xmlw, dvObject);
1✔
139
        }
140
        writeDescriptions(xmlw, dvObject, deaccessioned);
1✔
141
        if (!deaccessioned) {
1✔
142
            writeGeoLocations(xmlw, dvObject);
1✔
143
            writeFundingReferences(xmlw, dvObject);
1✔
144
        }
145
        xmlw.writeEndElement();
1✔
146
        xmlw.flush();
1✔
147
    }
1✔
148

149
    /**
150
     * 3, Title(s) (with optional type sub-properties) (M)
151
     *
152
     * @param xmlw
153
     *            The Stream writer
154
     * @param dvObject
155
     *            The dataset/file
156
     * @param language
157
     *            the metadata language
158
     * @return
159
     * @throws XMLStreamException
160
     */
161
    private void writeTitles(XMLStreamWriter xmlw, DvObject dvObject, String language, boolean deaccessioned) throws XMLStreamException {
162
        String title = null;
1✔
163
        String subTitle = null;
1✔
164
        List<String> altTitles = new ArrayList<>();
1✔
165

166
        if (!deaccessioned) {
1✔
167
            title = doiMetadata.getTitle();
1✔
168

169
            // Only Datasets can have a subtitle or alternative titles
170
            if (dvObject instanceof Dataset d) {
1✔
171
                DatasetVersion dv = d.getLatestVersionForCopy();
1✔
172
                Optional<DatasetField> subTitleField = dv.getDatasetFields().stream().filter(f -> f.getDatasetFieldType().getName().equals(DatasetFieldConstant.subTitle)).findFirst();
1✔
173
                if (subTitleField.isPresent()) {
1✔
174
                    subTitle = subTitleField.get().getValue();
×
175
                }
176
                Optional<DatasetField> altTitleField = dv.getDatasetFields().stream().filter(f -> f.getDatasetFieldType().getName().equals(DatasetFieldConstant.alternativeTitle)).findFirst();
1✔
177
                if (altTitleField.isPresent()) {
1✔
178
                    altTitles = altTitleField.get().getValues();
×
179
                }
180
            }
1✔
181
        } else {
182
            title = AbstractDOIProvider.UNAVAILABLE;
×
183
        }
184
        if (StringUtils.isNotBlank(title) || StringUtils.isNotBlank(subTitle) || (altTitles != null && !String.join("", altTitles).isBlank())) {
1✔
185
            xmlw.writeStartElement("titles");
1✔
186
            if (StringUtils.isNotBlank(title)) {
1✔
187
                XmlWriterUtil.writeFullElement(xmlw, "title", title, language);
1✔
188
            }
189
            Map<String, String> attributes = new HashMap<String, String>();
1✔
190

191
            if (StringUtils.isNotBlank(subTitle)) {
1✔
192
                attributes.put("titleType", "Subtitle");
×
193
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "title", attributes, subTitle);
×
194
            }
195
            if ((altTitles != null && !String.join("", altTitles).isBlank())) {
1✔
196
                attributes.clear();
×
197
                attributes.put("titleType", "AlternativeTitle");
×
198
                for (String altTitle : altTitles) {
×
199
                    XmlWriterUtil.writeFullElementWithAttributes(xmlw, "title", attributes, altTitle);
×
200
                }
×
201
            }
202
            xmlw.writeEndElement();
1✔
203
        }
204
    }
1✔
205

206
    /**
207
     * 1, Identifier (with mandatory type sub-property) (M) Note DataCite expects
208
     * identifierType="DOI" but OpenAire allows several others (see
209
     * https://guidelines.readthedocs.io/en/latest/data/field_identifier.html#d-identifiertype)
210
     * Dataverse is currently only capable of creating DOI, Handle, or URL types
211
     * from the OpenAire list (the last from PermaLinks) ToDo - If we add,e.g., an
212
     * ARK or PURL provider, this code has to change or we'll need to refactor so
213
     * that the identifiertype and id value can be sent via the JSON/ORE
214
     * 
215
     * @param xmlw
216
     *            The Steam writer
217
     * @param dvObject
218
     *            The dataset or file with the PID
219
     * @throws XMLStreamException
220
     */
221
    private void writeIdentifier(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
222
        GlobalId pid = dvObject.getGlobalId();
1✔
223
        String identifierType = null;
1✔
224
        String identifier = null;
1✔
225
        switch (pid.getProtocol()) {
1✔
226
        case AbstractDOIProvider.DOI_PROTOCOL:
227
            identifierType = AbstractDOIProvider.DOI_PROTOCOL.toUpperCase();
1✔
228
            identifier = pid.asRawIdentifier();
1✔
229
            break;
1✔
230
        case HandlePidProvider.HDL_PROTOCOL:
231
            identifierType = "Handle";
×
232
            identifier = pid.asRawIdentifier();
×
233
            break;
×
234
        case PermaLinkPidProvider.PERMA_PROTOCOL:
235
            identifierType = "URL";
×
236
            identifier = pid.asURL();
×
237
            break;
238
        }
239
        Map<String, String> attributeMap = new HashMap<String, String>();
1✔
240
        attributeMap.put("identifierType", identifierType);
1✔
241
        XmlWriterUtil.writeFullElementWithAttributes(xmlw, "identifier", attributeMap, identifier);
1✔
242
    }
1✔
243

244
    /**
245
     * 2, Creator (with optional given name, family name, name identifier and
246
     * affiliation sub-properties) (M)
247
     *
248
     * @param xmlw
249
     *            The stream writer
250
     * @param authorList
251
     *            - the list of authors
252
     * @throws XMLStreamException
253
     */
254
    public void writeCreators(XMLStreamWriter xmlw, List<DatasetAuthor> authorList, boolean deaccessioned) throws XMLStreamException {
255
        // creators -> creator -> creatorName with nameType attribute, givenName,
256
        // familyName, nameIdentifier
257
        // write all creators
258
        xmlw.writeStartElement("creators"); // <creators>
1✔
259
        if(deaccessioned) {
1✔
260
            //skip the loop below
261
            authorList = null;
×
262
        }
263
        boolean nothingWritten = true;
1✔
264
        if (authorList != null && !authorList.isEmpty()) {
1✔
265
            for (DatasetAuthor author : authorList) {
1✔
266
                String creatorName = author.getName().getDisplayValue();
1✔
267
                String affiliation = null;
1✔
268
                if (author.getAffiliation() != null && !author.getAffiliation().getValue().isEmpty()) {
1✔
269
                    affiliation = author.getAffiliation().getValue();
1✔
270
                }
271
                String nameIdentifier = null;
1✔
272
                String nameIdentifierScheme = null;
1✔
273
                if (StringUtils.isNotBlank(author.getIdValue()) && StringUtils.isNotBlank(author.getIdType())) {
1✔
274
                    nameIdentifier = author.getIdValue();
×
275
                    if (nameIdentifier != null) {
×
276
                        // Normalizes to the URL form of the identifier, returns null if the identifier
277
                        // is not valid given the type
278
                        nameIdentifier = author.getIdentifierAsUrl();
×
279
                    }
280
                    nameIdentifierScheme = author.getIdType();
×
281
                }
282

283
                if (StringUtils.isNotBlank(creatorName)) {
1✔
284
                    JsonObject creatorObj = PersonOrOrgUtil.getPersonOrOrganization(creatorName, false,
1✔
285
                            StringUtils.containsIgnoreCase(nameIdentifierScheme, "orcid"));
1✔
286
                    nothingWritten = false;
1✔
287
                    writeEntityElements(xmlw, "creator", null, creatorObj, affiliation, nameIdentifier, nameIdentifierScheme);
1✔
288
                }
289

290
                
291
            }
1✔
292
        }
293
        if (nothingWritten) {
1✔
294
            // Authors unavailable
295
            xmlw.writeStartElement("creator");
×
296
            XmlWriterUtil.writeFullElement(xmlw, "creatorName", AbstractPidProvider.UNAVAILABLE);
×
297
            xmlw.writeEndElement();
×
298
        }
299
        xmlw.writeEndElement(); // </creators>
1✔
300
    }
1✔
301

302
    private void writePublisher(XMLStreamWriter xmlw, DvObject dvObject, boolean deaccessioned) throws XMLStreamException {
303
        // publisher should already be non null - :unav if it wasn't available
304
        if(deaccessioned) {
1✔
305
            doiMetadata.setPublisher(AbstractPidProvider.UNAVAILABLE);
×
306
        }
307
        XmlWriterUtil.writeFullElement(xmlw, "publisher", doiMetadata.getPublisher());
1✔
308
    }
1✔
309

310
    private void writePublicationYear(XMLStreamWriter xmlw, DvObject dvObject, boolean deaccessioned) throws XMLStreamException {
311
        // Can't use "UNKNOWN" here because DataCite will respond with "[facet
312
        // 'pattern'] the value 'unknown' is not accepted by the pattern '[\d]{4}'"
313
        String pubYear = "9999";
1✔
314
        // FIXME: Investigate why this.publisherYear is sometimes null now that pull
315
        // request #4606 has been merged.
316
        if (! deaccessioned && (doiMetadata.getPublisherYear() != null)) {
1✔
317
            // Added to prevent a NullPointerException when trying to destroy datasets when
318
            // using DataCite rather than EZID.
319
            pubYear = doiMetadata.getPublisherYear();
×
320
        }
321
        XmlWriterUtil.writeFullElement(xmlw, "publicationYear", String.valueOf(pubYear));
1✔
322
    }
1✔
323

324
    /**
325
     * 6, Subject (with scheme sub-property) R
326
     *
327
     * @param xmlw
328
     *            The Steam writer
329
     * @param dvObject
330
     *            The Dataset/DataFile
331
     * @throws XMLStreamException
332
     */
333
    private void writeSubjects(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
334
        // subjects -> subject with subjectScheme and schemeURI attributes when
335
        // available
336
        boolean subjectsCreated = false;
1✔
337
        List<String> subjects = new ArrayList<String>();
1✔
338
        List<DatasetFieldCompoundValue> compoundKeywords = new ArrayList<DatasetFieldCompoundValue>();
1✔
339
        List<DatasetFieldCompoundValue> compoundTopics = new ArrayList<DatasetFieldCompoundValue>();
1✔
340
        // Dataset Subject= Dataverse subject, keyword, and/or topic classification
341
        // fields
342
        if (dvObject instanceof Dataset d) {
1✔
343
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
344
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
345
                if (dsf.getDatasetFieldType().getName().equals(DatasetFieldConstant.subject)) {
1✔
346
                    subjects.addAll(dsf.getValues());
×
347
                }
348
                if (dsf.getDatasetFieldType().getName().equals(DatasetFieldConstant.keyword)) {
1✔
349
                    compoundKeywords = dsf.getDatasetFieldCompoundValues();
×
350
                } else if (dsf.getDatasetFieldType().getName().equals(DatasetFieldConstant.topicClassification)) {
1✔
351
                    compoundTopics = dsf.getDatasetFieldCompoundValues();
×
352
                }
353
            }
1✔
354

355
        } else if (dvObject instanceof DataFile df) {
1✔
356
            subjects = df.getTagLabels();
×
357
        }
358
        for (String subject : subjects) {
1✔
359
            if (StringUtils.isNotBlank(subject)) {
×
360
                subjectsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "subjects", subjectsCreated);
×
361
                XmlWriterUtil.writeFullElement(xmlw, "subject", StringEscapeUtils.escapeXml10(subject));
×
362
            }
363
        }
×
364
        for (DatasetFieldCompoundValue keywordFieldValue : compoundKeywords) {
1✔
365
            String keyword = null;
×
366
            String scheme = null;
×
367
            String schemeUri = null;
×
368

369
            for (DatasetField subField : keywordFieldValue.getChildDatasetFields()) {
×
370
                switch (subField.getDatasetFieldType().getName()) {
×
371
                case DatasetFieldConstant.keywordValue:
372
                    keyword = subField.getValue();
×
373
                    break;
×
374
                case DatasetFieldConstant.keywordVocab:
375
                    scheme = subField.getValue();
×
376
                    break;
×
377
                case DatasetFieldConstant.keywordVocabURI:
378
                    schemeUri = subField.getValue();
×
379
                    break;
380
                }
381
            }
×
382
            if (StringUtils.isNotBlank(keyword)) {
×
383
                Map<String, String> attributesMap = new HashMap<String, String>();
×
384
                if (StringUtils.isNotBlank(scheme)) {
×
385
                    attributesMap.put("subjectScheme", scheme);
×
386
                }
387
                if (StringUtils.isNotBlank(schemeUri)) {
×
388
                    attributesMap.put("schemeURI", schemeUri);
×
389
                }
390
                subjectsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "subjects", subjectsCreated);
×
391
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "subject", attributesMap, StringEscapeUtils.escapeXml10(keyword));
×
392
            }
393
        }
×
394
        for (DatasetFieldCompoundValue topicFieldValue : compoundTopics) {
1✔
395
            String topic = null;
×
396
            String scheme = null;
×
397
            String schemeUri = null;
×
398

399
            for (DatasetField subField : topicFieldValue.getChildDatasetFields()) {
×
400

401
                switch (subField.getDatasetFieldType().getName()) {
×
402
                case DatasetFieldConstant.topicClassValue:
403
                    topic = subField.getValue();
×
404
                    break;
×
405
                case DatasetFieldConstant.topicClassVocab:
406
                    scheme = subField.getValue();
×
407
                    break;
×
408
                case DatasetFieldConstant.topicClassVocabURI:
409
                    schemeUri = subField.getValue();
×
410
                    break;
411
                }
412
            }
×
413
            if (StringUtils.isNotBlank(topic)) {
×
414
                Map<String, String> attributesMap = new HashMap<String, String>();
×
415
                if (StringUtils.isNotBlank(scheme)) {
×
416
                    attributesMap.put("subjectScheme", scheme);
×
417
                }
418
                if (StringUtils.isNotBlank(schemeUri)) {
×
419
                    attributesMap.put("schemeURI", schemeUri);
×
420
                }
421
                subjectsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "subjects", subjectsCreated);
×
422
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "subject", attributesMap, StringEscapeUtils.escapeXml10(topic));
×
423
            }
424
        }
×
425
        if (subjectsCreated) {
1✔
426
            xmlw.writeEndElement();
×
427
        }
428
    }
1✔
429

430
    /**
431
     * 7, Contributor (with optional given name, family name, name identifier and
432
     * affiliation sub-properties)
433
     *
434
     * @see #writeContributorElement(javax.xml.stream.XMLStreamWriter,
435
     *      java.lang.String, java.lang.String, java.lang.String)
436
     *
437
     * @param xmlw
438
     *            The stream writer
439
     * @param dvObject
440
     *            The Dataset/DataFile
441
     * @throws XMLStreamException
442
     */
443
    private void writeContributors(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
444
        boolean contributorsCreated = false;
1✔
445
        List<DatasetFieldCompoundValue> compoundProducers = new ArrayList<DatasetFieldCompoundValue>();
1✔
446
        List<DatasetFieldCompoundValue> compoundDistributors = new ArrayList<DatasetFieldCompoundValue>();
1✔
447
        List<DatasetFieldCompoundValue> compoundContacts = new ArrayList<DatasetFieldCompoundValue>();
1✔
448
        List<DatasetFieldCompoundValue> compoundContributors = new ArrayList<DatasetFieldCompoundValue>();
1✔
449
        // Dataset Subject= Dataverse subject, keyword, and/or topic classification
450
        // fields
451
        // ToDo Include for files?
452
        /*
453
         * if(dvObject instanceof DataFile df) { dvObject = df.getOwner(); }
454
         */
455

456
        if (dvObject instanceof Dataset d) {
1✔
457
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
458
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
459
                switch (dsf.getDatasetFieldType().getName()) {
1✔
460
                case DatasetFieldConstant.producer:
461
                    compoundProducers = dsf.getDatasetFieldCompoundValues();
×
462
                    break;
×
463
                case DatasetFieldConstant.distributor:
464
                    compoundDistributors = dsf.getDatasetFieldCompoundValues();
×
465
                    break;
×
466
                case DatasetFieldConstant.datasetContact:
467
                    compoundContacts = dsf.getDatasetFieldCompoundValues();
×
468
                    break;
×
469
                case DatasetFieldConstant.contributor:
470
                    compoundContributors = dsf.getDatasetFieldCompoundValues();
×
471
                }
472
            }
1✔
473
        }
474

475
        for (DatasetFieldCompoundValue producerFieldValue : compoundProducers) {
1✔
476
            String producer = null;
×
477
            String affiliation = null;
×
478

479
            for (DatasetField subField : producerFieldValue.getChildDatasetFields()) {
×
480

481
                switch (subField.getDatasetFieldType().getName()) {
×
482
                case DatasetFieldConstant.producerName:
483
                    producer = subField.getValue();
×
484
                    break;
×
485
                case DatasetFieldConstant.producerAffiliation:
486
                    affiliation = subField.getValue();
×
487
                    break;
488
                }
489
            }
×
490
            if (StringUtils.isNotBlank(producer)) {
×
491
                contributorsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "contributors", contributorsCreated);
×
492
                JsonObject entityObject = PersonOrOrgUtil.getPersonOrOrganization(producer, false, false);
×
493
                writeEntityElements(xmlw, "contributor", "Producer", entityObject, affiliation, null, null);
×
494
            }
495

496
        }
×
497

498
        for (DatasetFieldCompoundValue distributorFieldValue : compoundDistributors) {
1✔
499
            String distributor = null;
×
500
            String affiliation = null;
×
501

502
            for (DatasetField subField : distributorFieldValue.getChildDatasetFields()) {
×
503

504
                switch (subField.getDatasetFieldType().getName()) {
×
505
                case DatasetFieldConstant.distributorName:
506
                    distributor = subField.getValue();
×
507
                    break;
×
508
                case DatasetFieldConstant.distributorAffiliation:
509
                    affiliation = subField.getValue();
×
510
                    break;
511
                }
512
            }
×
513
            if (StringUtils.isNotBlank(distributor)) {
×
514
                contributorsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "contributors", contributorsCreated);
×
515
                JsonObject entityObject = PersonOrOrgUtil.getPersonOrOrganization(distributor, false, false);
×
516
                writeEntityElements(xmlw, "contributor", "Distributor", entityObject, affiliation, null, null);
×
517
            }
518

519
        }
×
520
        for (DatasetFieldCompoundValue contactFieldValue : compoundContacts) {
1✔
521
            String contact = null;
×
522
            String affiliation = null;
×
523

524
            for (DatasetField subField : contactFieldValue.getChildDatasetFields()) {
×
525

526
                switch (subField.getDatasetFieldType().getName()) {
×
527
                case DatasetFieldConstant.datasetContactName:
528
                    contact = subField.getValue();
×
529
                    break;
×
530
                case DatasetFieldConstant.datasetContactAffiliation:
531
                    affiliation = subField.getValue();
×
532
                    break;
533
                }
534
            }
×
535
            if (StringUtils.isNotBlank(contact)) {
×
536
                contributorsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "contributors", contributorsCreated);
×
537
                JsonObject entityObject = PersonOrOrgUtil.getPersonOrOrganization(contact, false, false);
×
538
                writeEntityElements(xmlw, "contributor", "ContactPerson", entityObject, affiliation, null, null);
×
539
            }
540

541
        }
×
542
        for (DatasetFieldCompoundValue contributorFieldValue : compoundContributors) {
1✔
543
            String contributor = null;
×
544
            String contributorType = null;
×
545

546
            for (DatasetField subField : contributorFieldValue.getChildDatasetFields()) {
×
547

548
                switch (subField.getDatasetFieldType().getName()) {
×
549
                    case DatasetFieldConstant.contributorName:
550
                        contributor = subField.getValue();
×
551
                        break;
×
552
                    case DatasetFieldConstant.contributorType:
553
                        contributorType = subField.getValue();
×
554
                        if (contributorType != null) {
×
555
                            contributorType = contributorType.replace(" ", "");
×
556
                        }
557
                        break;
558
                }
559
            }
×
560
            // QDR - doesn't have Funder in the contributor type list.
561
            // Using a string isn't i18n
562
            if (StringUtils.isNotBlank(contributor) && !StringUtils.equalsIgnoreCase("Funder", contributorType)) {
×
563
                contributorType = getCanonicalContributorType(contributorType);
×
564
                contributorsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "contributors", contributorsCreated);
×
565
                JsonObject entityObject = PersonOrOrgUtil.getPersonOrOrganization(contributor, false, false);
×
566
                writeEntityElements(xmlw, "contributor", contributorType, entityObject, null, null, null);
×
567
            }
568

569
        }
×
570

571
        if (contributorsCreated) {
1✔
572
            xmlw.writeEndElement();
×
573
        }
574
    }
1✔
575

576
    //List from https://schema.datacite.org/meta/kernel-4/include/datacite-contributorType-v4.xsd
577
    private Set<String> contributorTypes = new HashSet<>(Arrays.asList("ContactPerson", "DataCollector", "DataCurator", "DataManager", "Distributor", "Editor", 
1✔
578
                "HostingInstitution", "Other", "Producer", "ProjectLeader", "ProjectManager", "ProjectMember", "RegistrationAgency", "RegistrationAuthority", 
579
                "RelatedPerson", "ResearchGroup", "RightsHolder", "Researcher", "Sponsor", "Supervisor", "WorkPackageLeader"));
580

581
    private String getCanonicalContributorType(String contributorType) {
582
        if(StringUtils.isBlank(contributorType) || !contributorTypes.contains(contributorType)) {
×
583
            return "Other";
×
584
        }
585
        return contributorType;
×
586
    }
587

588
    private void writeEntityElements(XMLStreamWriter xmlw, String elementName, String type, JsonObject entityObject, String affiliation, String nameIdentifier, String nameIdentifierScheme) throws XMLStreamException {
589
        xmlw.writeStartElement(elementName);
1✔
590
        Map<String, String> attributeMap = new HashMap<String, String>();
1✔
591
        if (StringUtils.isNotBlank(type)) {
1✔
592
            xmlw.writeAttribute("contributorType", type);
×
593
        }
594
        // person name=<FamilyName>, <FirstName>
595
        if (entityObject.getBoolean("isPerson")) {
1✔
596
            attributeMap.put("nameType", "Personal");
1✔
597
        } else {
598
            attributeMap.put("nameType", "Organizational");
×
599
        }
600
        XmlWriterUtil.writeFullElementWithAttributes(xmlw, elementName + "Name", attributeMap,
1✔
601
                StringEscapeUtils.escapeXml10(entityObject.getString("fullName")));
1✔
602
        if (entityObject.containsKey("givenName")) {
1✔
603
            XmlWriterUtil.writeFullElement(xmlw, "givenName", StringEscapeUtils.escapeXml10(entityObject.getString("givenName")));
1✔
604
        }
605
        if (entityObject.containsKey("familyName")) {
1✔
606
            XmlWriterUtil.writeFullElement(xmlw, "familyName", StringEscapeUtils.escapeXml10(entityObject.getString("familyName")));
×
607
        }
608

609
        if (nameIdentifier != null) {
1✔
610
            attributeMap.clear();
×
611
            URL url;
612
            try {
613
                url = new URL(nameIdentifier);
×
614
                String protocol = url.getProtocol();
×
615
                String authority = url.getAuthority();
×
616
                String site = String.format("%s://%s", protocol, authority);
×
617
                attributeMap.put("schemeURI", site);
×
618
                attributeMap.put("nameIdentifierScheme", nameIdentifierScheme);
×
619
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "nameIdentifier", attributeMap, nameIdentifier);
×
620
            } catch (MalformedURLException e) {
×
621
                logger.warning("DatasetAuthor.getIdentifierAsUrl returned a Malformed URL: " + nameIdentifier);
×
622
            }
×
623
        }
624

625
        if (StringUtils.isNotBlank(affiliation)) {
1✔
626
            attributeMap.clear();
1✔
627
            boolean isROR=false;
1✔
628
            String orgName = affiliation;
1✔
629
            ExternalIdentifier externalIdentifier = ExternalIdentifier.ROR;
1✔
630
            if (externalIdentifier.isValidIdentifier(orgName)) {
1✔
631
                isROR = true;
×
632
                JsonObject jo = getExternalVocabularyValue(orgName);
×
633
                if (jo != null) {
×
634
                    orgName = jo.getString("termName");
×
635
                }
636
            }
637
          
638
            if (isROR) {
1✔
639

640
                attributeMap.put("schemeURI", "https://ror.org");
×
641
                attributeMap.put("affiliationIdentifierScheme", "ROR");
×
642
                attributeMap.put("affiliationIdentifier", orgName);
×
643
            }
644

645
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "affiliation", attributeMap, StringEscapeUtils.escapeXml10(orgName));
1✔
646
        }
647
        xmlw.writeEndElement();
1✔
648
    }
1✔
649

650
    private JsonObject getExternalVocabularyValue(String id) {
651
        return CDI.current().select(DatasetFieldServiceBean.class).get().getExternalVocabularyValue(id);
×
652
    }
653

654
    /**
655
     * 8, Date (with type sub-property) (R)
656
     *
657
     * @param xmlw
658
     *            The Steam writer
659
     * @param dvObject
660
     *            The dataset/datafile
661
     * @throws XMLStreamException
662
     */
663
    private void writeDates(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
664
        boolean datesWritten = false;
1✔
665
        String dateOfDistribution = null;
1✔
666
        String dateOfProduction = null;
1✔
667
        String dateOfDeposit = null;
1✔
668
        Date releaseDate = null;
1✔
669
        String publicationDate = null;
1✔
670
        boolean isAnUpdate=false;
1✔
671
        List<DatasetFieldCompoundValue> datesOfCollection = new ArrayList<DatasetFieldCompoundValue>();
1✔
672
        List<DatasetFieldCompoundValue> timePeriods = new ArrayList<DatasetFieldCompoundValue>();
1✔
673

674
        if (dvObject instanceof DataFile df) {
1✔
675
            // Find the first released version the file is in to give a published date
676
            List<FileMetadata> fmds = df.getFileMetadatas();
×
677
            DatasetVersion initialVersion = null;
×
678
            for (FileMetadata fmd : fmds) {
×
679
                DatasetVersion dv = fmd.getDatasetVersion();
×
680
                if (dv.isReleased()) {
×
681
                    initialVersion = dv;
×
682
                    publicationDate = Util.getDateFormat().format(dv.getReleaseTime());
×
683
                    break;
×
684
                }
685
            }
×
686
            // And the last update is the most recent
687
            for (int i = fmds.size() - 1; i >= 0; i--) {
×
688
                DatasetVersion dv = fmds.get(i).getDatasetVersion();
×
689
                if (dv.isReleased() && !dv.equals(initialVersion)) {
×
690
                    releaseDate = dv.getReleaseTime();
×
691
                    isAnUpdate=true;
×
692
                    break;
×
693
                }
694
            }
695
        } else if (dvObject instanceof Dataset d) {
1✔
696
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
697
            Long versionNumber = dv.getVersionNumber();
1✔
698
            if (versionNumber != null && !(versionNumber.equals(1) && dv.getMinorVersionNumber().equals(0))) {
1✔
699
                isAnUpdate = true;
×
700
            }
701
            releaseDate = dv.getReleaseTime();
1✔
702
            publicationDate = d.getPublicationDateFormattedYYYYMMDD();
1✔
703
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
704
                switch (dsf.getDatasetFieldType().getName()) {
1✔
705
                case DatasetFieldConstant.distributionDate:
706
                    dateOfDistribution = dsf.getValue();
×
707
                    break;
×
708
                case DatasetFieldConstant.productionDate:
709
                    dateOfProduction = dsf.getValue();
×
710
                    break;
×
711
                case DatasetFieldConstant.dateOfDeposit:
712
                    dateOfDeposit = dsf.getValue();
×
713
                    break;
×
714
                case DatasetFieldConstant.dateOfCollection:
715
                    datesOfCollection = dsf.getDatasetFieldCompoundValues();
×
716
                    break;
×
717
                case DatasetFieldConstant.timePeriodCovered:
718
                    timePeriods = dsf.getDatasetFieldCompoundValues();
×
719
                    break;
720
                }
721
            }
1✔
722
        }
723
        Map<String, String> attributes = new HashMap<String, String>();
1✔
724
        if (StringUtils.isNotBlank(dateOfDistribution)) {
1✔
725
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
726
            attributes.put("dateType", "Issued");
×
727
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, dateOfDistribution);
×
728
        }
729
        // dates -> date with dateType attribute
730

731
        if (StringUtils.isNotBlank(dateOfProduction)) {
1✔
732
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
733
            attributes.put("dateType", "Created");
×
734
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, dateOfProduction);
×
735
        }
736
        if (StringUtils.isNotBlank(dateOfDeposit)) {
1✔
737
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
738
            attributes.put("dateType", "Submitted");
×
739
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, dateOfDeposit);
×
740
        }
741

742
        if (publicationDate != null) {
1✔
743
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
744

745
            attributes.put("dateType", "Available");
×
746
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, publicationDate);
×
747
        }
748
        if (isAnUpdate) {
1✔
749
            String date = Util.getDateFormat().format(releaseDate);
×
750
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
751

752
            attributes.put("dateType", "Updated");
×
753
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, date);
×
754
        }
755
        if (datesOfCollection != null) {
1✔
756
            for (DatasetFieldCompoundValue collectionDateFieldValue : datesOfCollection) {
1✔
757
                String startDate = null;
×
758
                String endDate = null;
×
759

760
                for (DatasetField subField : collectionDateFieldValue.getChildDatasetFields()) {
×
761
                    switch (subField.getDatasetFieldType().getName()) {
×
762
                    case DatasetFieldConstant.dateOfCollectionStart:
763
                        startDate = subField.getValue();
×
764
                        break;
×
765
                    case DatasetFieldConstant.dateOfCollectionEnd:
766
                        endDate = subField.getValue();
×
767
                        break;
768
                    }
769
                }
×
770
                // Minimal clean-up - useful? Parse/format would remove unused chars, and an
771
                // exception would clear the date so we don't send nonsense
772
                startDate = cleanUpDate(startDate);
×
773
                endDate = cleanUpDate(endDate);
×
774
                if (StringUtils.isNotBlank(startDate) || StringUtils.isNotBlank(endDate)) {
×
775
                    datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
776
                    attributes.put("dateType", "Collected");
×
777
                    XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, (startDate + "/" + endDate).trim());
×
778
                }
779
            }
×
780
        }
781
        if (timePeriods != null) {
1✔
782
            for (DatasetFieldCompoundValue timePeriodFieldValue : timePeriods) {
1✔
783
                String startDate = null;
×
784
                String endDate = null;
×
785

786
                for (DatasetField subField : timePeriodFieldValue.getChildDatasetFields()) {
×
787
                    switch (subField.getDatasetFieldType().getName()) {
×
788
                    case DatasetFieldConstant.timePeriodCoveredStart:
789
                        startDate = subField.getValue();
×
790
                        break;
×
791
                    case DatasetFieldConstant.timePeriodCoveredEnd:
792
                        endDate = subField.getValue();
×
793
                        break;
794
                    }
795
                }
×
796
                // Minimal clean-up - useful? Parse/format would remove unused chars, and an
797
                // exception would clear the date so we don't send nonsense
798
                startDate = cleanUpDate(startDate);
×
799
                endDate = cleanUpDate(endDate);
×
800
                if (StringUtils.isNotBlank(startDate) || StringUtils.isNotBlank(endDate)) {
×
801
                    datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
802
                    attributes.put("dateType", "Other");
×
803
                    attributes.put("dateInformation", "Time period covered by the data");
×
804
                    XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, (startDate + "/" + endDate).trim());
×
805
                }
806
            }
×
807
        }
808
        if (datesWritten) {
1✔
809
            xmlw.writeEndElement();
×
810
        }
811
    }
1✔
812

813
    private String cleanUpDate(String date) {
814
        String newDate = null;
×
815
        if (!StringUtils.isBlank(date)) {
×
816
            try {
817
                SimpleDateFormat sdf = Util.getDateFormat();
×
818
                Date start = sdf.parse(date);
×
819
                newDate = sdf.format(start);
×
820
            } catch (ParseException e) {
×
821
                logger.warning("Could not parse date: " + date);
×
822
            }
×
823
        }
824
        return newDate;
×
825
    }
826

827
    // 9, Language (MA), language
828
    private void writeLanguage(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
829
        // Currently not supported. Spec indicates one 'primary' language. Could send
830
        // the first entry in DatasetFieldConstant.language or send iff there is only
831
        // one entry, and/or default to the machine's default lang, or the dataverse metadatalang?
832
        return;
1✔
833
    }
834

835
    // 10, ResourceType (with mandatory general type
836
    // description sub- property) (M)
837
    private void writeResourceType(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
838
        List<String> kindOfDataValues = new ArrayList<String>();
1✔
839
        Map<String, String> attributes = new HashMap<String, String>();
1✔
840
        String resourceType = "Dataset";
1✔
841
        if (dvObject instanceof Dataset dataset) {
1✔
842
            String datasetTypeName = dataset.getDatasetType().getName();
1✔
843
            resourceType = switch (datasetTypeName) {
1✔
844
            case DatasetType.DATASET_TYPE_DATASET -> "Dataset";
1✔
845
            case DatasetType.DATASET_TYPE_SOFTWARE -> "Software";
×
846
            case DatasetType.DATASET_TYPE_WORKFLOW -> "Workflow";
×
847
            default -> "Dataset";
1✔
848
            };
849
        }
850
        attributes.put("resourceTypeGeneral", resourceType);
1✔
851
        if (dvObject instanceof Dataset d) {
1✔
852
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
853
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
854
                switch (dsf.getDatasetFieldType().getName()) {
1✔
855
                case DatasetFieldConstant.kindOfData:
856
                    List<String> vals = dsf.getValues();
×
857
                    for(String val: vals) {
×
858
                        if(StringUtils.isNotBlank(val)) {
×
859
                            kindOfDataValues.add(val);
×
860
                        }
861
                    }
×
862
                    break;
863
                }
864
            }
1✔
865
        }
866
        if (!kindOfDataValues.isEmpty()) {
1✔
867
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "resourceType", attributes, String.join(";", kindOfDataValues));
×
868

869
        } else {
870
            // Write an attribute only element if there are no kindOfData values.
871
            xmlw.writeStartElement("resourceType");
1✔
872
            xmlw.writeAttribute("resourceTypeGeneral", attributes.get("resourceTypeGeneral"));
1✔
873
            xmlw.writeEndElement();
1✔
874
        }
875

876
    }
1✔
877

878
    /**
879
     * 11 AlternateIdentifier (with type sub-property) (O)
880
     *
881
     * @param xmlw
882
     *            The Steam writer
883
     * @param dvObject
884
     *            The dataset/datafile
885
     * @throws XMLStreamException
886
     */
887
    private void writeAlternateIdentifiers(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
888
        List<DatasetFieldCompoundValue> otherIdentifiers = new ArrayList<DatasetFieldCompoundValue>();
1✔
889
        Set<AlternativePersistentIdentifier> altPids = dvObject.getAlternativePersistentIndentifiers();
1✔
890

891
        boolean alternatesWritten = false;
1✔
892

893
        Map<String, String> attributes = new HashMap<String, String>();
1✔
894
        if (dvObject instanceof Dataset d) {
1✔
895
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
896
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
897
                if (DatasetFieldConstant.otherId.equals(dsf.getDatasetFieldType().getName())) {
1✔
898
                    otherIdentifiers = dsf.getDatasetFieldCompoundValues();
×
899
                    break;
×
900
                }
901
            }
1✔
902
        }
903

904
        if (altPids != null && !altPids.isEmpty()) {
1✔
905
            alternatesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "alternateIdentifiers", alternatesWritten);
×
906
            for (AlternativePersistentIdentifier altPid : altPids) {
×
907
                String identifierType = null;
×
908
                String identifier = null;
×
909
                switch (altPid.getProtocol()) {
×
910
                case AbstractDOIProvider.DOI_PROTOCOL:
911
                    identifierType = AbstractDOIProvider.DOI_PROTOCOL.toUpperCase();
×
912
                    identifier = altPid.getAuthority() + "/" + altPid.getIdentifier();
×
913
                    break;
×
914
                case HandlePidProvider.HDL_PROTOCOL:
915
                    identifierType = "Handle";
×
916
                    identifier = altPid.getAuthority() + "/" + altPid.getIdentifier();
×
917
                    break;
×
918
                default:
919
                    // The AlternativePersistentIdentifier class isn't really ready for anything but
920
                    // doi or handle pids, but will add this as a default.
921
                    identifierType = ":unav";
×
922
                    identifier = altPid.getAuthority() + altPid.getIdentifier();
×
923
                    break;
924
                }
925
                attributes.put("alternateIdentifierType", identifierType);
×
926
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "alternateIdentifier", attributes, identifier);
×
927

928
            }
×
929
        }
930

931
        for (DatasetFieldCompoundValue otherIdentifier : otherIdentifiers) {
1✔
932
            String identifierType = null;
×
933
            String identifier = null;
×
934
            for (DatasetField subField : otherIdentifier.getChildDatasetFields()) {
×
935
                identifierType = ":unav";
×
936
                switch (subField.getDatasetFieldType().getName()) {
×
937
                case DatasetFieldConstant.otherIdAgency:
938
                    identifierType = subField.getValue();
×
939
                    break;
×
940
                case DatasetFieldConstant.otherIdValue:
941
                    identifier = subField.getValue();
×
942
                    break;
943
                }
944
            }
×
945
            attributes.put("alternateIdentifierType", identifierType);
×
946
            if (!StringUtils.isBlank(identifier)) {
×
947
                alternatesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "alternateIdentifiers", alternatesWritten);
×
948

949
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "alternateIdentifier", attributes, identifier);
×
950
            }
951
        }
×
952
        if (alternatesWritten) {
1✔
953
            xmlw.writeEndElement();
×
954
        }
955
    }
1✔
956

957
    /**
958
     * 12, RelatedIdentifier (with type and relation type sub-properties) (R)
959
     *
960
     * @param xmlw
961
     *            The Steam writer
962
     * @param dvObject
963
     *            the dataset/datafile
964
     * @throws XMLStreamException
965
     */
966
    private void writeRelatedIdentifiers(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
967

968
        boolean relatedIdentifiersWritten = false;
1✔
969

970
        Map<String, String> attributes = new HashMap<String, String>();
1✔
971

972
        if (dvObject instanceof Dataset dataset) {
1✔
973
            List<DatasetRelPublication> relatedPublications = dataset.getLatestVersionForCopy().getRelatedPublications();
1✔
974
            if (!relatedPublications.isEmpty()) {
1✔
975
                for (DatasetRelPublication relatedPub : relatedPublications) {
×
976
                    attributes.clear();
×
977

978
                    String pubIdType = relatedPub.getIdType();
×
979
                    String identifier = relatedPub.getIdNumber();
×
980
                    String url = relatedPub.getUrl();
×
981
                    String relationType = relatedPub.getRelationType();
×
982
                    if(StringUtils.isBlank(relationType)) {
×
983
                        relationType = "IsSupplementTo";
×
984
                    }
985
                    /*
986
                     * Note - with identifier and url fields, it's not clear that there's a single
987
                     * way those two fields are used for all identifier types. The code here is
988
                     * ~best effort to interpret those fields.
989
                     */
990
                    logger.fine("Found relpub: " + pubIdType + " " + identifier + " " + url);
×
991

992
                    pubIdType = getCanonicalPublicationType(pubIdType);
×
993
                    logger.fine("Canonical type: " + pubIdType);
×
994
                    // Prefer identifier if set, otherwise check url
995
                    String relatedIdentifier = identifier;
×
996
                    if (StringUtils.isBlank(relatedIdentifier)) {
×
997
                        relatedIdentifier = url;
×
998
                    }
999
                    logger.fine("Related identifier: " + relatedIdentifier);
×
1000
                    // For types where we understand the protocol, get the canonical form
1001
                    if (StringUtils.isNotBlank(relatedIdentifier)) {
×
1002
                        switch (pubIdType != null ? pubIdType : "none") {
×
1003
                        case "DOI":
1004
                            if (!(relatedIdentifier.startsWith("doi:") || relatedIdentifier.startsWith("http"))) {
×
1005
                                relatedIdentifier = "doi:" + relatedIdentifier;
×
1006
                            }
1007
                            logger.fine("Intermediate Related identifier: " + relatedIdentifier);
×
1008
                            try {
1009
                                GlobalId pid = PidUtil.parseAsGlobalID(relatedIdentifier);
×
1010
                                relatedIdentifier = pid.asRawIdentifier();
×
1011
                            } catch (IllegalArgumentException e) {
×
1012
                                logger.warning("Invalid DOI: " + e.getLocalizedMessage());
×
1013
                                relatedIdentifier = null;
×
1014
                            }
×
1015
                            logger.fine("Final Related identifier: " + relatedIdentifier);
×
1016
                            break;
×
1017
                        case "Handle":
1018
                            if (!relatedIdentifier.startsWith("hdl:") || !relatedIdentifier.startsWith("http")) {
×
1019
                                relatedIdentifier = "hdl:" + relatedIdentifier;
×
1020
                            }
1021
                            try {
1022
                                GlobalId pid = PidUtil.parseAsGlobalID(relatedIdentifier);
×
1023
                                relatedIdentifier = pid.asRawIdentifier();
×
1024
                            } catch (IllegalArgumentException e) {
×
1025
                                relatedIdentifier = null;
×
1026
                            }
×
1027
                            break;
×
1028
                        case "URL":
1029
                            // If a URL is given, split the string to get a schemeUri
1030
                            try {
1031
                                URL relatedUrl = new URI(relatedIdentifier).toURL();
×
1032
                                String protocol = relatedUrl.getProtocol();
×
1033
                                String authority = relatedUrl.getAuthority();
×
1034
                                String site = String.format("%s://%s", protocol, authority);
×
1035
                                relatedIdentifier = relatedIdentifier.substring(site.length());
×
1036
                                attributes.put("schemeURI", site);
×
1037
                            } catch (URISyntaxException | MalformedURLException | IllegalArgumentException e) {
×
1038
                                // Just an identifier but without a pubIdType we won't include it
1039
                                logger.warning("Invalid Identifier of type URL: " + relatedIdentifier);
×
1040
                                relatedIdentifier = null;
×
1041
                            }
×
1042
                            break;
×
1043
                        case "none":
1044
                            //Try to identify PIDs and URLs and send them as related identifiers
1045
                            if (relatedIdentifier != null) {
×
1046
                                // See if it is a GlobalID we know
1047
                                try {
1048
                                    GlobalId pid = PidUtil.parseAsGlobalID(relatedIdentifier);
×
1049
                                    relatedIdentifier = pid.asRawIdentifier();
×
1050
                                    pubIdType = getCanonicalPublicationType(pid.getProtocol());
×
1051
                                } catch (IllegalArgumentException e) {
×
1052
                                }
×
1053
                                // For non-URL types, if a URL is given, split the string to get a schemeUri
1054
                                try {
1055
                                    URL relatedUrl = new URI(relatedIdentifier).toURL();
×
1056
                                    String protocol = relatedUrl.getProtocol();
×
1057
                                    String authority = relatedUrl.getAuthority();
×
1058
                                    String site = String.format("%s://%s", protocol, authority);
×
1059
                                    relatedIdentifier = relatedIdentifier.substring(site.length());
×
1060
                                    attributes.put("schemeURI", site);
×
1061
                                    pubIdType = "URL";
×
1062
                                } catch (URISyntaxException | MalformedURLException | IllegalArgumentException e) {
×
1063
                                    // Just an identifier but without a pubIdType we won't include it
1064
                                    logger.warning("Related Identifier found without type: " + relatedIdentifier);
×
1065
                                    //Won't be sent since pubIdType is null - could also set relatedIdentifier to null
1066
                                }
×
1067
                            }
1068
                            break;
1069
                        default:
1070
                            //Some other valid type - we just send the identifier w/o optional attributes
1071
                            //To Do - validation for other types?
1072
                            break;
1073
                        }
1074
                    }
1075
                    if (StringUtils.isNotBlank(relatedIdentifier) && StringUtils.isNotBlank(pubIdType)) {
×
1076
                        // Still have a valid entry
1077
                        attributes.put("relatedIdentifierType", pubIdType);
×
1078
                        attributes.put("relationType", relationType);
×
1079
                        relatedIdentifiersWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "relatedIdentifiers", relatedIdentifiersWritten);
×
1080
                        XmlWriterUtil.writeFullElementWithAttributes(xmlw, "relatedIdentifier", attributes, relatedIdentifier);
×
1081
                    }
1082
                }
×
1083
            }
1084
            List<FileMetadata> fmds = dataset.getLatestVersionForCopy().getFileMetadatas();
1✔
1085
            if (!((fmds==null) && fmds.isEmpty())) {
1✔
1086
                attributes.clear();
1✔
1087
                attributes.put("relationType", "HasPart");
1✔
1088
                for (FileMetadata fmd : fmds) {
1✔
1089
                    DataFile dataFile = fmd.getDataFile();
×
1090
                    GlobalId pid = dataFile.getGlobalId();
×
1091
                    if (pid != null) {
×
1092
                        String pubIdType = getCanonicalPublicationType(pid.getProtocol());
×
1093
                        if (pubIdType != null) {
×
1094
                            attributes.put("relatedIdentifierType", pubIdType);
×
1095
                            relatedIdentifiersWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "relatedIdentifiers", relatedIdentifiersWritten);
×
1096
                            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "relatedIdentifier", attributes, pid.asRawIdentifier());
×
1097
                        }
1098
                    }
1099
                }
×
1100
            }
1101
        } else if (dvObject instanceof DataFile df) {
1✔
1102
            GlobalId pid = df.getOwner().getGlobalId();
×
1103
            if (pid != null) {
×
1104
                String pubIdType = getCanonicalPublicationType(pid.getProtocol());
×
1105
                if (pubIdType != null) {
×
1106

1107
                    attributes.clear();
×
1108
                    attributes.put("relationType", "IsPartOf");
×
1109
                    attributes.put("relatedIdentifierType", pubIdType);
×
1110
                    relatedIdentifiersWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "relatedIdentifiers", relatedIdentifiersWritten);
×
1111
                    XmlWriterUtil.writeFullElementWithAttributes(xmlw, "relatedIdentifier", attributes, pid.asRawIdentifier());
×
1112
                }
1113
            }
1114
        }
1115
        if (relatedIdentifiersWritten) {
1✔
1116
            xmlw.writeEndElement();
×
1117
        }
1118
    }
1✔
1119

1120
    static HashMap<String, String> relatedIdentifierTypeMap = new HashMap<String, String>();
1✔
1121

1122
    private static String getCanonicalPublicationType(String pubIdType) {
1123
        if (relatedIdentifierTypeMap.isEmpty()) {
×
1124
            relatedIdentifierTypeMap.put("ARK".toLowerCase(), "ARK");
×
1125
            relatedIdentifierTypeMap.put("arXiv", "arXiv");
×
1126
            relatedIdentifierTypeMap.put("bibcode".toLowerCase(), "bibcode");
×
1127
            relatedIdentifierTypeMap.put("DOI".toLowerCase(), "DOI");
×
1128
            relatedIdentifierTypeMap.put("EAN13".toLowerCase(), "EAN13");
×
1129
            relatedIdentifierTypeMap.put("EISSN".toLowerCase(), "EISSN");
×
1130
            relatedIdentifierTypeMap.put("Handle".toLowerCase(), "Handle");
×
1131
            relatedIdentifierTypeMap.put("IGSN".toLowerCase(), "IGSN");
×
1132
            relatedIdentifierTypeMap.put("ISBN".toLowerCase(), "ISBN");
×
1133
            relatedIdentifierTypeMap.put("ISSN".toLowerCase(), "ISSN");
×
1134
            relatedIdentifierTypeMap.put("ISTC".toLowerCase(), "ISTC");
×
1135
            relatedIdentifierTypeMap.put("LISSN".toLowerCase(), "LISSN");
×
1136
            relatedIdentifierTypeMap.put("LSID".toLowerCase(), "LSID");
×
1137
            relatedIdentifierTypeMap.put("PISSN".toLowerCase(), "PISSN");
×
1138
            relatedIdentifierTypeMap.put("PMID".toLowerCase(), "PMID");
×
1139
            relatedIdentifierTypeMap.put("PURL".toLowerCase(), "PURL");
×
1140
            relatedIdentifierTypeMap.put("UPC".toLowerCase(), "UPC");
×
1141
            relatedIdentifierTypeMap.put("URL".toLowerCase(), "URL");
×
1142
            relatedIdentifierTypeMap.put("URN".toLowerCase(), "URN");
×
1143
            relatedIdentifierTypeMap.put("WOS".toLowerCase(), "WOS");
×
1144
            // Add entry for Handle,Perma protocols so this can be used with GlobalId/getProtocol()
1145
            relatedIdentifierTypeMap.put("hdl".toLowerCase(), "Handle");
×
1146
            relatedIdentifierTypeMap.put("perma".toLowerCase(), "URL");
×
1147
            
1148
        }
1149
        return relatedIdentifierTypeMap.get(pubIdType);
×
1150
    }
1151

1152
    private void writeSize(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1153
        // sizes -> size
1154
        boolean sizesWritten = false;
1✔
1155
        List<DataFile> dataFiles = new ArrayList<DataFile>();
1✔
1156

1157
        if (dvObject instanceof Dataset dataset) {
1✔
1158
            dataFiles = dataset.getFiles();
1✔
1159
        } else if (dvObject instanceof DataFile df) {
×
1160
            dataFiles.add(df);
×
1161
        }
1162
        if (dataFiles != null && !dataFiles.isEmpty()) {
1✔
1163
            for (DataFile dataFile : dataFiles) {
×
1164
                Long size = dataFile.getFilesize();
×
1165
                if (size != -1) {
×
1166
                    sizesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "sizes", sizesWritten);
×
1167
                    XmlWriterUtil.writeFullElement(xmlw, "size", size.toString());
×
1168
                }
1169
            }
×
1170
        }
1171
        if (sizesWritten) {
1✔
1172
            xmlw.writeEndElement();
×
1173
        }
1174

1175
    }
1✔
1176

1177
    private void writeFormats(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1178

1179
        boolean formatsWritten = false;
1✔
1180
        List<DataFile> dataFiles = new ArrayList<DataFile>();
1✔
1181

1182
        if (dvObject instanceof Dataset dataset) {
1✔
1183
            dataFiles = dataset.getFiles();
1✔
1184
        } else if (dvObject instanceof DataFile df) {
×
1185
            dataFiles.add(df);
×
1186
        }
1187
        if (dataFiles != null && !dataFiles.isEmpty()) {
1✔
1188
            for (DataFile dataFile : dataFiles) {
×
1189
                String format = dataFile.getContentType();
×
1190
                if (StringUtils.isNotBlank(format)) {
×
1191
                    formatsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "formats", formatsWritten);
×
1192
                    XmlWriterUtil.writeFullElement(xmlw, "format", format);
×
1193
                }
1194
                /*
1195
                 * Should original formats be sent? What about original sizes above?
1196
                 * if(dataFile.isTabularData()) { String originalFormat =
1197
                 * dataFile.getOriginalFileFormat(); if(StringUtils.isNotBlank(originalFormat))
1198
                 * { XmlWriterUtil.writeFullElement(xmlw, "format", format); } }
1199
                 */
1200
            }
×
1201
        }
1202
        if (formatsWritten) {
1✔
1203
            xmlw.writeEndElement();
×
1204
        }
1205

1206
    }
1✔
1207

1208
    private void writeVersion(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1209
        Dataset d = null;
1✔
1210
        if (dvObject instanceof Dataset) {
1✔
1211
            d = (Dataset) dvObject;
1✔
1212
        } else if (dvObject instanceof DataFile) {
×
1213
            d = ((DataFile) dvObject).getOwner();
×
1214
        }
1215
        if (d != null) {
1✔
1216
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
1217
            String version = dv.getFriendlyVersionNumber();
1✔
1218
            if (StringUtils.isNotBlank(version)) {
1✔
1219
                XmlWriterUtil.writeFullElement(xmlw, "version", version);
1✔
1220
            }
1221
        }
1222

1223
    }
1✔
1224

1225
    private void writeAccessRights(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1226
        // rightsList -> rights with rightsURI attribute
1227
        xmlw.writeStartElement("rightsList"); // <rightsList>
1✔
1228

1229
        // set terms from the info:eu-repo-Access-Terms vocabulary
1230
        xmlw.writeStartElement("rights"); // <rights>
1✔
1231
        DatasetVersion dv = null;
1✔
1232
        boolean closed = false;
1✔
1233
        if (dvObject instanceof Dataset d) {
1✔
1234
            dv = d.getLatestVersionForCopy();
1✔
1235
            closed = dv.isHasRestrictedFile();
1✔
1236
        } else if (dvObject instanceof DataFile df) {
×
1237
            dv = df.getOwner().getLatestVersionForCopy();
×
1238

1239
            closed = df.isRestricted();
×
1240
        }
1241
        TermsOfUseAndAccess terms = dv.getTermsOfUseAndAccess();
1✔
1242
        boolean requestsAllowed = terms.isFileAccessRequest();
1✔
1243
        License license = terms.getLicense();
1✔
1244

1245
        if (requestsAllowed && closed) {
1✔
1246
            xmlw.writeAttribute("rightsURI", "info:eu-repo/semantics/restrictedAccess");
×
1247
        } else if (!requestsAllowed && closed) {
1✔
1248
            xmlw.writeAttribute("rightsURI", "info:eu-repo/semantics/closedAccess");
×
1249
        } else {
1250
            xmlw.writeAttribute("rightsURI", "info:eu-repo/semantics/openAccess");
1✔
1251
        }
1252
        xmlw.writeEndElement(); // </rights>
1✔
1253
        xmlw.writeStartElement("rights"); // <rights>
1✔
1254

1255
        if (license != null) {
1✔
1256
            xmlw.writeAttribute("rightsURI", license.getUri().toString());
×
1257
            xmlw.writeCharacters(license.getName());
×
1258
        } else {
1259
            xmlw.writeAttribute("rightsURI", DatasetUtil.getLicenseURI(dv));
1✔
1260
            xmlw.writeCharacters(BundleUtil.getStringFromBundle("license.custom.description"));
1✔
1261
            ;
1262
        }
1263
        xmlw.writeEndElement(); // </rights>
1✔
1264
        xmlw.writeEndElement(); // </rightsList>
1✔
1265
    }
1✔
1266

1267
    private void writeDescriptions(XMLStreamWriter xmlw, DvObject dvObject, boolean deaccessioned) throws XMLStreamException {
1268
        // descriptions -> description with descriptionType attribute
1269
        boolean descriptionsWritten = false;
1✔
1270
        List<String> descriptions = null;
1✔
1271
        DatasetVersion dv = null;
1✔
1272
        if(deaccessioned) {
1✔
1273
            descriptions = new ArrayList<String>();
×
1274
            descriptions.add(AbstractDOIProvider.UNAVAILABLE);
×
1275
        } else {
1276
            if (dvObject instanceof Dataset d) {
1✔
1277
                dv = d.getLatestVersionForCopy();
1✔
1278
                descriptions = dv.getDescriptions();
1✔
1279
            } else if (dvObject instanceof DataFile df) {
×
1280
                String description = df.getDescription();
×
1281
                if (description != null) {
×
1282
                    descriptions = new ArrayList<String>();
×
1283
                    descriptions.add(description);
×
1284
                }
1285
            }
1286
        }
1287
        Map<String, String> attributes = new HashMap<String, String>();
1✔
1288
        attributes.put("descriptionType", "Abstract");
1✔
1289
        if (descriptions != null) {
1✔
1290
            for (String description : descriptions) {
1✔
1291
                descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
×
1292
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, StringEscapeUtils.escapeXml10(description));
×
1293
            }
×
1294
        }
1295

1296
        if (dv != null) {
1✔
1297
            List<DatasetField> dsfs = dv.getDatasetFields();
1✔
1298

1299
            for (DatasetField dsf : dsfs) {
1✔
1300

1301
                switch (dsf.getDatasetFieldType().getName()) {
1✔
1302
                    case DatasetFieldConstant.software:
1303
                        attributes.clear();
×
1304
                        attributes.put("descriptionType", "TechnicalInfo");
×
1305
                        List<DatasetFieldCompoundValue> dsfcvs = dsf.getDatasetFieldCompoundValues();
×
1306
                        for (DatasetFieldCompoundValue dsfcv : dsfcvs) {
×
1307

1308
                            String softwareName = null;
×
1309
                            String softwareVersion = null;
×
1310
                            List<DatasetField> childDsfs = dsfcv.getChildDatasetFields();
×
1311
                            for (DatasetField childDsf : childDsfs) {
×
1312
                                if (DatasetFieldConstant.softwareName.equals(childDsf.getDatasetFieldType().getName())) {
×
1313
                                    softwareName = childDsf.getValue();
×
1314
                                } else if (DatasetFieldConstant.softwareVersion.equals(childDsf.getDatasetFieldType().getName())) {
×
1315
                                    softwareVersion = childDsf.getValue();
×
1316
                                }
1317
                            }
×
1318
                            if (StringUtils.isNotBlank(softwareName)) {
×
1319
                                if (StringUtils.isNotBlank(softwareVersion)) {
×
UNCOV
1320
                                    softwareName = softwareName + ", " + softwareVersion;
×
1321
                                }
1322
                                descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
×
1323
                                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, softwareName);
×
1324
                            }
1325
                        }
×
1326
                        break;
×
1327
                    case DatasetFieldConstant.originOfSources:
1328
                    case DatasetFieldConstant.characteristicOfSources:
1329
                    case DatasetFieldConstant.accessToSources:
1330
                        attributes.clear();
×
1331
                        attributes.put("descriptionType", "Methods");
×
1332
                        String method = dsf.getValue();
×
1333
                        if (StringUtils.isNotBlank(method)) {
×
1334
                            descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
×
1335
                            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, method);
×
1336

1337
                        }
1338
                        break;
1339
                    case DatasetFieldConstant.series:
1340
                        attributes.clear();
×
1341
                        attributes.put("descriptionType", "SeriesInformation");
×
1342
                        dsfcvs = dsf.getDatasetFieldCompoundValues();
×
1343
                        for (DatasetFieldCompoundValue dsfcv : dsfcvs) {
×
1344
                            List<DatasetField> childDsfs = dsfcv.getChildDatasetFields();
×
1345
                            for (DatasetField childDsf : childDsfs) {
×
1346

1347
                                if (DatasetFieldConstant.seriesName.equals(childDsf.getDatasetFieldType().getName())) {
×
1348
                                    String seriesInformation = childDsf.getValue();
×
1349
                                    if (StringUtils.isNotBlank(seriesInformation)) {
×
1350
                                        descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
×
1351
                                        XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, seriesInformation);
×
1352
                                    }
1353
                                    break;
1354
                                }
1355
                            }
×
1356
                        }
×
1357
                        break;
×
1358
                    case DatasetFieldConstant.notesText:
1359
                        attributes.clear();
×
1360
                        attributes.put("descriptionType", "Other");
×
1361
                        String notesText = dsf.getValue();
×
1362
                        if (StringUtils.isNotBlank(notesText)) {
×
1363
                            descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
×
1364
                            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, notesText);
×
1365
                        }
1366
                        break;
1367

1368
                }
1369
            }
1✔
1370

1371
        }
1372

1373
        if (descriptionsWritten) {
1✔
1374
            xmlw.writeEndElement(); // </descriptions>
×
1375
        }
1376
    }
1✔
1377

1378
    private void writeGeoLocations(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1379
        if (dvObject instanceof Dataset d) {
1✔
1380
            boolean geoLocationsWritten = false;
1✔
1381
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
1382

1383
            List<String[]> places = dv.getGeographicCoverage();
1✔
1384
            if (places != null && !places.isEmpty()) {
1✔
1385
                // geoLocationPlace
1386
                geoLocationsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "geoLocations", geoLocationsWritten);
×
1387
                for (String[] place : places) {
×
1388
                    xmlw.writeStartElement("geoLocation"); // <geoLocation>
×
1389
                    
1390
                    ArrayList<String> placeList = new ArrayList<String>();
×
1391
                    for (String placePart : place) {
×
1392
                        if (!StringUtils.isBlank(placePart)) {
×
1393
                            placeList.add(placePart);
×
1394
                        }
1395
                    }
1396
                    XmlWriterUtil.writeFullElement(xmlw, "geoLocationPlace", Strings.join(placeList, ", "));
×
1397
                    xmlw.writeEndElement(); // </geoLocation>
×
1398
                }
×
1399
                
1400
            }
1401
            boolean boundingBoxFound = false;
1✔
1402
            boolean productionPlaceFound = false;
1✔
1403
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
1404
                switch (dsf.getDatasetFieldType().getName()) {
1✔
1405
                case DatasetFieldConstant.geographicBoundingBox:
1406
                    boundingBoxFound = true;
×
1407
                    for (DatasetFieldCompoundValue dsfcv : dsf.getDatasetFieldCompoundValues()) {
×
1408
                        List<DatasetField> childDsfs = dsfcv.getChildDatasetFields();
×
1409
                        String nLatitude = null;
×
1410
                        String sLatitude = null;
×
1411
                        String eLongitude = null;
×
1412
                        String wLongitude = null;
×
1413
                        for (DatasetField childDsf : childDsfs) {
×
1414
                            switch (childDsf.getDatasetFieldType().getName()) {
×
1415
                            case DatasetFieldConstant.northLatitude:
1416
                                nLatitude = childDsf.getValue();
×
1417
                                break;
×
1418
                            case DatasetFieldConstant.southLatitude:
1419
                                sLatitude = childDsf.getValue();
×
1420
                                break;
×
1421
                            case DatasetFieldConstant.eastLongitude:
1422
                                eLongitude = childDsf.getValue();
×
1423
                                break;
×
1424
                            case DatasetFieldConstant.westLongitude:
1425
                                wLongitude = childDsf.getValue();
×
1426

1427
                            }
1428
                        }
×
1429
                        if (StringUtils.isNoneBlank(wLongitude, eLongitude, nLatitude, sLatitude)) {
×
1430
                            geoLocationsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "geoLocations", geoLocationsWritten);
×
1431
                            xmlw.writeStartElement("geoLocation"); // <geoLocation>
×
1432
                            if (wLongitude.equals(eLongitude) && nLatitude.equals(sLatitude)) {
×
1433
                                // A point
1434
                                xmlw.writeStartElement("geoLocationPoint");
×
1435
                                XmlWriterUtil.writeFullElement(xmlw, "pointLongitude", eLongitude);
×
1436
                                XmlWriterUtil.writeFullElement(xmlw, "pointLatitude", sLatitude);
×
1437
                                xmlw.writeEndElement();
×
1438
                            } else {
1439
                                // A box
1440
                                xmlw.writeStartElement("geoLocationBox");
×
1441
                                XmlWriterUtil.writeFullElement(xmlw, "westBoundLongitude", wLongitude);
×
1442
                                XmlWriterUtil.writeFullElement(xmlw, "eastBoundLongitude", eLongitude);
×
1443
                                XmlWriterUtil.writeFullElement(xmlw, "southBoundLatitude", sLatitude);
×
1444
                                XmlWriterUtil.writeFullElement(xmlw, "northBoundLatitude", nLatitude);
×
1445
                                xmlw.writeEndElement();
×
1446

1447
                            }
1448
                            xmlw.writeEndElement(); // </geoLocation>
×
1449
                        }
1450
                    }
×
1451
                case DatasetFieldConstant.productionPlace:
1452
                    productionPlaceFound = true;
×
1453
                    // geoLocationPlace
1454
                    geoLocationsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "geoLocations", geoLocationsWritten);
×
1455
                    List<String> prodPlaces = dsf.getValues();
×
1456
                    for (String prodPlace : prodPlaces) {
×
1457
                        xmlw.writeStartElement("geoLocation"); // <geoLocation>
×
1458
                        XmlWriterUtil.writeFullElement(xmlw, "geoLocationPlace", prodPlace);
×
1459
                        xmlw.writeEndElement(); // </geoLocation>
×
1460
                    }
×
1461
                    break;
1462
                }
1463
                if (boundingBoxFound && productionPlaceFound) {
1✔
1464
                    break;
×
1465
                }
1466
            }
1✔
1467
            if (geoLocationsWritten) {
1✔
1468
                xmlw.writeEndElement(); // <geoLocations>
×
1469
            }
1470
        }
1471

1472
    }
1✔
1473

1474
    private void writeFundingReferences(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1475
        // fundingReferences -> fundingReference -> funderName, awardNumber
1476
        boolean fundingReferenceWritten = false;
1✔
1477
        DatasetVersion dv = null;
1✔
1478
        if (dvObject instanceof Dataset d) {
1✔
1479
            dv = d.getLatestVersionForCopy();
1✔
1480
        } else if (dvObject instanceof DataFile df) {
×
1481
            dv = df.getOwner().getLatestVersionForCopy();
×
1482
        }
1483
        if (dv != null) {
1✔
1484
            List<String> retList = new ArrayList<>();
1✔
1485
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
1486
                if (dsf.getDatasetFieldType().getName().equals(DatasetFieldConstant.contributor)) {
1✔
1487
                    boolean addFunder = false;
×
1488
                    for (DatasetFieldCompoundValue contributorValue : dsf.getDatasetFieldCompoundValues()) {
×
1489
                        String contributorName = null;
×
1490
                        String contributorType = null;
×
1491
                        for (DatasetField subField : contributorValue.getChildDatasetFields()) {
×
1492
                            if (subField.getDatasetFieldType().getName().equals(DatasetFieldConstant.contributorName)) {
×
1493
                                contributorName = subField.getDisplayValue();
×
1494
                            }
1495
                            if (subField.getDatasetFieldType().getName().equals(DatasetFieldConstant.contributorType)) {
×
1496
                                contributorType = subField.getRawValue();
×
1497
                            }
1498
                        }
×
1499
                        // SEK 02/12/2019 move outside loop to prevent contrib type to carry over to
1500
                        // next contributor
1501
                        // TODO: Consider how this will work in French, Chinese, etc.
1502
                        if ("Funder".equals(contributorType)) {
×
1503
                            if (!StringUtils.isBlank(contributorName)) {
×
1504
                                fundingReferenceWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "fundingReferences", fundingReferenceWritten);
×
1505
                                xmlw.writeStartElement("fundingReference"); // <fundingReference>
×
1506
                                XmlWriterUtil.writeFullElement(xmlw, "funderName", StringEscapeUtils.escapeXml10(contributorName));
×
1507
                                xmlw.writeEndElement(); // </fundingReference>
×
1508
                            }
1509
                        }
1510
                    }
×
1511
                }
1512
                if (dsf.getDatasetFieldType().getName().equals(DatasetFieldConstant.grantNumber)) {
1✔
1513
                    for (DatasetFieldCompoundValue grantObject : dsf.getDatasetFieldCompoundValues()) {
×
1514
                        String funder = null;
×
1515
                        String awardNumber = null;
×
1516
                        for (DatasetField subField : grantObject.getChildDatasetFields()) {
×
1517
                            // It would be nice to do something with grantNumberValue (the actual number)
1518
                            // but schema.org doesn't support it.
1519
                            if (subField.getDatasetFieldType().getName().equals(DatasetFieldConstant.grantNumberAgency)) {
×
1520
                                String grantAgency = subField.getDisplayValue();
×
1521
                                funder = grantAgency;
×
1522
                            } else if (subField.getDatasetFieldType().getName().equals(DatasetFieldConstant.grantNumberValue)) {
×
1523
                                String grantNumberValue = subField.getDisplayValue();
×
1524
                                awardNumber = grantNumberValue;
×
1525
                            }
1526
                        }
×
1527
                        if (!StringUtils.isBlank(funder)) {
×
1528
                            fundingReferenceWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "fundingReferences", fundingReferenceWritten);
×
1529
                            boolean isROR=false;
×
1530
                            String funderIdentifier = null;
×
1531
                            ExternalIdentifier externalIdentifier = ExternalIdentifier.ROR;
×
1532
                            if (externalIdentifier.isValidIdentifier(funder)) {
×
1533
                                isROR = true;
×
1534
                                JsonObject jo = getExternalVocabularyValue(funder);
×
1535
                                if (jo != null) {
×
1536
                                    funderIdentifier = funder;
×
1537
                                    funder = jo.getString("termName");
×
1538
                                }
1539
                            }
1540
                          
1541
                            xmlw.writeStartElement("fundingReference"); // <fundingReference>
×
1542
                            XmlWriterUtil.writeFullElement(xmlw, "funderName", StringEscapeUtils.escapeXml10(funder));
×
1543
                            if (isROR) {
×
1544
                                Map<String, String> attributeMap = new HashMap<>();
×
1545
                                attributeMap.put("schemeURI", "https://ror.org");
×
1546
                                attributeMap.put("funderIdentifierType", "ROR");
×
1547
                                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "funderIdentifier", attributeMap, StringEscapeUtils.escapeXml10(funderIdentifier));
×
1548
                            }
1549
                            if (StringUtils.isNotBlank(awardNumber)) {
×
1550
                                XmlWriterUtil.writeFullElement(xmlw, "awardNumber", StringEscapeUtils.escapeXml10(awardNumber));
×
1551
                            }
1552
                            xmlw.writeEndElement(); // </fundingReference>
×
1553
                        }
1554

1555
                    }
×
1556
                }
1557
            }
1✔
1558

1559
            if (fundingReferenceWritten) {
1✔
1560
                xmlw.writeEndElement(); // </fundingReferences>
×
1561
            }
1562

1563
        }
1564
    }
1✔
1565
}
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