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

IQSS / dataverse / #24490

06 Feb 2025 11:53PM UTC coverage: 22.757% (-0.005%) from 22.762%
#24490

Pull #11225

github

web-flow
Merge 2ee746b83 into 3aea1482d
Pull Request #11225: Flaky XmlMetadataTemplateTest fix

19944 of 87640 relevant lines covered (22.76%)

0.23 hits per line

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

76.61
/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

73
    public XmlMetadataTemplate() {
×
74
    }
×
75

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

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

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

94
    private void generateXML(DvObject dvObject, OutputStream outputStream) throws XMLStreamException {
95
        // Could/should use dataset metadata language for metadata from DvObject itself?
96
        String language = null; // machine locale? e.g. for Publisher which is global
1✔
97
        String metadataLanguage = null; // when set, otherwise = language?
1✔
98
        
99
        XMLStreamWriter xmlw = XMLOutputFactory.newInstance().createXMLStreamWriter(outputStream);
1✔
100
        xmlw.writeStartElement("resource");
1✔
101
        boolean deaccessioned=false;
1✔
102
        if(dvObject instanceof Dataset d) {
1✔
103
            deaccessioned=d.isDeaccessioned();
1✔
104
        } else if (dvObject instanceof DataFile df) {
×
105
            deaccessioned = df.isDeaccessioned();
×
106
        }
107
        xmlw.writeDefaultNamespace(XML_NAMESPACE);
1✔
108
        xmlw.writeAttribute("xmlns:xsi", XML_XSI);
1✔
109
        xmlw.writeAttribute("xsi:schemaLocation", XML_SCHEMA_LOCATION);
1✔
110

111
        writeIdentifier(xmlw, dvObject);
1✔
112
        writeCreators(xmlw, doiMetadata.getAuthors(), deaccessioned);
1✔
113
        writeTitles(xmlw, dvObject, language, deaccessioned);
1✔
114
        writePublisher(xmlw, dvObject, deaccessioned);
1✔
115
        writePublicationYear(xmlw, dvObject, deaccessioned);
1✔
116
        if (!deaccessioned) {
1✔
117
            writeSubjects(xmlw, dvObject);
1✔
118
            writeContributors(xmlw, dvObject);
1✔
119
            writeDates(xmlw, dvObject);
1✔
120
            writeLanguage(xmlw, dvObject);
1✔
121
        }
122
        writeResourceType(xmlw, dvObject);
1✔
123
        if (!deaccessioned) {
1✔
124
            writeAlternateIdentifiers(xmlw, dvObject);
1✔
125
            writeRelatedIdentifiers(xmlw, dvObject);
1✔
126
            writeSize(xmlw, dvObject);
1✔
127
            writeFormats(xmlw, dvObject);
1✔
128
            writeVersion(xmlw, dvObject);
1✔
129
            writeAccessRights(xmlw, dvObject);
1✔
130
        }
131
        writeDescriptions(xmlw, dvObject, deaccessioned);
1✔
132
        if (!deaccessioned) {
1✔
133
            writeGeoLocations(xmlw, dvObject);
1✔
134
            writeFundingReferences(xmlw, dvObject);
1✔
135
        }
136
        xmlw.writeEndElement();
1✔
137
        xmlw.flush();
1✔
138
    }
1✔
139

140
    /**
141
     * 3, Title(s) (with optional type sub-properties) (M)
142
     *
143
     * @param xmlw
144
     *            The Stream writer
145
     * @param dvObject
146
     *            The dataset/file
147
     * @param language
148
     *            the metadata language
149
     * @return
150
     * @throws XMLStreamException
151
     */
152
    private void writeTitles(XMLStreamWriter xmlw, DvObject dvObject, String language, boolean deaccessioned) throws XMLStreamException {
153
        String title = null;
1✔
154
        String subTitle = null;
1✔
155
        List<String> altTitles = new ArrayList<>();
1✔
156

157
        if (!deaccessioned) {
1✔
158
            title = doiMetadata.getTitle();
1✔
159

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

182
            if (StringUtils.isNotBlank(subTitle)) {
1✔
183
                attributes.put("titleType", "Subtitle");
1✔
184
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "title", attributes, subTitle);
1✔
185
            }
186
            if ((altTitles != null && !String.join("", altTitles).isBlank())) {
1✔
187
                attributes.clear();
1✔
188
                attributes.put("titleType", "AlternativeTitle");
1✔
189
                for (String altTitle : altTitles) {
1✔
190
                    XmlWriterUtil.writeFullElementWithAttributes(xmlw, "title", attributes, altTitle);
1✔
191
                }
1✔
192
            }
193
            xmlw.writeEndElement();
1✔
194
        }
195
    }
1✔
196

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

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

274
                if (StringUtils.isNotBlank(creatorName)) {
1✔
275
                    JsonObject creatorObj = PersonOrOrgUtil.getPersonOrOrganization(creatorName, false,
1✔
276
                            StringUtils.containsIgnoreCase(nameIdentifierScheme, "orcid"));
1✔
277
                    nothingWritten = false;
1✔
278
                    writeEntityElements(xmlw, "creator", null, creatorObj, affiliation, nameIdentifier, nameIdentifierScheme);
1✔
279
                }
280

281
                
282
            }
1✔
283
        }
284
        if (nothingWritten) {
1✔
285
            // Authors unavailable
286
            xmlw.writeStartElement("creator");
×
287
            XmlWriterUtil.writeFullElement(xmlw, "creatorName", AbstractPidProvider.UNAVAILABLE);
×
288
            xmlw.writeEndElement();
×
289
        }
290
        xmlw.writeEndElement(); // </creators>
1✔
291
    }
1✔
292

293
    private void writePublisher(XMLStreamWriter xmlw, DvObject dvObject, boolean deaccessioned) throws XMLStreamException {
294
        // publisher should already be non null - :unav if it wasn't available
295
        if(deaccessioned) {
1✔
296
            doiMetadata.setPublisher(AbstractPidProvider.UNAVAILABLE);
×
297
        }
298
        XmlWriterUtil.writeFullElement(xmlw, "publisher", doiMetadata.getPublisher());
1✔
299
    }
1✔
300

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

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

346
        } else if (dvObject instanceof DataFile df) {
1✔
347
            subjects = df.getTagLabels();
×
348
        }
349
        for (String subject : subjects) {
1✔
350
            if (StringUtils.isNotBlank(subject)) {
1✔
351
                subjectsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "subjects", subjectsCreated);
1✔
352
                XmlWriterUtil.writeFullElement(xmlw, "subject", StringEscapeUtils.escapeXml10(subject));
1✔
353
            }
354
        }
1✔
355
        for (DatasetFieldCompoundValue keywordFieldValue : compoundKeywords) {
1✔
356
            String keyword = null;
1✔
357
            String scheme = null;
1✔
358
            String schemeUri = null;
1✔
359

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

390
            for (DatasetField subField : topicFieldValue.getChildDatasetFields()) {
×
391

392
                switch (subField.getDatasetFieldType().getName()) {
×
393
                case DatasetFieldConstant.topicClassValue:
394
                    topic = subField.getValue();
×
395
                    break;
×
396
                case DatasetFieldConstant.topicClassVocab:
397
                    scheme = subField.getValue();
×
398
                    break;
×
399
                case DatasetFieldConstant.topicClassVocabURI:
400
                    schemeUri = subField.getValue();
×
401
                    break;
402
                }
403
            }
×
404
            if (StringUtils.isNotBlank(topic)) {
×
405
                Map<String, String> attributesMap = new HashMap<String, String>();
×
406
                if (StringUtils.isNotBlank(scheme)) {
×
407
                    attributesMap.put("subjectScheme", scheme);
×
408
                }
409
                if (StringUtils.isNotBlank(schemeUri)) {
×
410
                    attributesMap.put("schemeURI", schemeUri);
×
411
                }
412
                subjectsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "subjects", subjectsCreated);
×
413
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "subject", attributesMap, StringEscapeUtils.escapeXml10(topic));
×
414
            }
415
        }
×
416
        if (subjectsCreated) {
1✔
417
            xmlw.writeEndElement();
1✔
418
        }
419
    }
1✔
420

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

447
        if (dvObject instanceof Dataset d) {
1✔
448
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
449
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
450
                switch (dsf.getDatasetFieldType().getName()) {
1✔
451
                case DatasetFieldConstant.producer:
452
                    compoundProducers = dsf.getDatasetFieldCompoundValues();
1✔
453
                    break;
1✔
454
                case DatasetFieldConstant.distributor:
455
                    compoundDistributors = dsf.getDatasetFieldCompoundValues();
1✔
456
                    break;
1✔
457
                case DatasetFieldConstant.datasetContact:
458
                    compoundContacts = dsf.getDatasetFieldCompoundValues();
1✔
459
                    break;
1✔
460
                case DatasetFieldConstant.contributor:
461
                    compoundContributors = dsf.getDatasetFieldCompoundValues();
1✔
462
                }
463
            }
1✔
464
        }
465

466
        for (DatasetFieldCompoundValue producerFieldValue : compoundProducers) {
1✔
467
            String producer = null;
1✔
468
            String affiliation = null;
1✔
469

470
            for (DatasetField subField : producerFieldValue.getChildDatasetFields()) {
1✔
471

472
                switch (subField.getDatasetFieldType().getName()) {
1✔
473
                case DatasetFieldConstant.producerName:
474
                    producer = subField.getValue();
1✔
475
                    break;
1✔
476
                case DatasetFieldConstant.producerAffiliation:
477
                    affiliation = subField.getValue();
1✔
478
                    break;
479
                }
480
            }
1✔
481
            if (StringUtils.isNotBlank(producer)) {
1✔
482
                contributorsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "contributors", contributorsCreated);
1✔
483
                JsonObject entityObject = PersonOrOrgUtil.getPersonOrOrganization(producer, false, false);
1✔
484
                writeEntityElements(xmlw, "contributor", "Producer", entityObject, affiliation, null, null);
1✔
485
            }
486

487
        }
1✔
488

489
        for (DatasetFieldCompoundValue distributorFieldValue : compoundDistributors) {
1✔
490
            String distributor = null;
1✔
491
            String affiliation = null;
1✔
492

493
            for (DatasetField subField : distributorFieldValue.getChildDatasetFields()) {
1✔
494

495
                switch (subField.getDatasetFieldType().getName()) {
1✔
496
                case DatasetFieldConstant.distributorName:
497
                    distributor = subField.getValue();
1✔
498
                    break;
1✔
499
                case DatasetFieldConstant.distributorAffiliation:
500
                    affiliation = subField.getValue();
1✔
501
                    break;
502
                }
503
            }
1✔
504
            if (StringUtils.isNotBlank(distributor)) {
1✔
505
                contributorsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "contributors", contributorsCreated);
1✔
506
                JsonObject entityObject = PersonOrOrgUtil.getPersonOrOrganization(distributor, false, false);
1✔
507
                writeEntityElements(xmlw, "contributor", "Distributor", entityObject, affiliation, null, null);
1✔
508
            }
509

510
        }
1✔
511
        for (DatasetFieldCompoundValue contactFieldValue : compoundContacts) {
1✔
512
            String contact = null;
1✔
513
            String affiliation = null;
1✔
514

515
            for (DatasetField subField : contactFieldValue.getChildDatasetFields()) {
1✔
516

517
                switch (subField.getDatasetFieldType().getName()) {
1✔
518
                case DatasetFieldConstant.datasetContactName:
519
                    contact = subField.getValue();
1✔
520
                    break;
1✔
521
                case DatasetFieldConstant.datasetContactAffiliation:
522
                    affiliation = subField.getValue();
1✔
523
                    break;
524
                }
525
            }
1✔
526
            if (StringUtils.isNotBlank(contact)) {
1✔
527
                contributorsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "contributors", contributorsCreated);
1✔
528
                JsonObject entityObject = PersonOrOrgUtil.getPersonOrOrganization(contact, false, false);
1✔
529
                writeEntityElements(xmlw, "contributor", "ContactPerson", entityObject, affiliation, null, null);
1✔
530
            }
531

532
        }
1✔
533
        for (DatasetFieldCompoundValue contributorFieldValue : compoundContributors) {
1✔
534
            String contributor = null;
1✔
535
            String contributorType = null;
1✔
536

537
            for (DatasetField subField : contributorFieldValue.getChildDatasetFields()) {
1✔
538

539
                switch (subField.getDatasetFieldType().getName()) {
1✔
540
                    case DatasetFieldConstant.contributorName:
541
                        contributor = subField.getValue();
1✔
542
                        break;
1✔
543
                    case DatasetFieldConstant.contributorType:
544
                        contributorType = subField.getValue();
1✔
545
                        if (contributorType != null) {
1✔
546
                            contributorType = contributorType.replace(" ", "");
1✔
547
                        }
548
                        break;
549
                }
550
            }
1✔
551
            // QDR - doesn't have Funder in the contributor type list.
552
            // Using a string isn't i18n
553
            if (StringUtils.isNotBlank(contributor) && !StringUtils.equalsIgnoreCase("Funder", contributorType)) {
1✔
554
                contributorType = getCanonicalContributorType(contributorType);
1✔
555
                contributorsCreated = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "contributors", contributorsCreated);
1✔
556
                JsonObject entityObject = PersonOrOrgUtil.getPersonOrOrganization(contributor, false, false);
1✔
557
                writeEntityElements(xmlw, "contributor", contributorType, entityObject, null, null, null);
1✔
558
            }
559

560
        }
1✔
561

562
        if (contributorsCreated) {
1✔
563
            xmlw.writeEndElement();
1✔
564
        }
565
    }
1✔
566

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

572
    private String getCanonicalContributorType(String contributorType) {
573
        if(StringUtils.isBlank(contributorType) || !contributorTypes.contains(contributorType)) {
1✔
574
            return "Other";
×
575
        }
576
        return contributorType;
1✔
577
    }
578

579
    private void writeEntityElements(XMLStreamWriter xmlw, String elementName, String type, JsonObject entityObject, String affiliation, String nameIdentifier, String nameIdentifierScheme) throws XMLStreamException {
580
        xmlw.writeStartElement(elementName);
1✔
581
        Map<String, String> attributeMap = new HashMap<String, String>();
1✔
582
        if (StringUtils.isNotBlank(type)) {
1✔
583
            xmlw.writeAttribute("contributorType", type);
1✔
584
        }
585
        // person name=<FamilyName>, <FirstName>
586
        if (entityObject.getBoolean("isPerson")) {
1✔
587
            attributeMap.put("nameType", "Personal");
1✔
588
        } else {
589
            attributeMap.put("nameType", "Organizational");
1✔
590
        }
591
        XmlWriterUtil.writeFullElementWithAttributes(xmlw, elementName + "Name", attributeMap,
1✔
592
                StringEscapeUtils.escapeXml10(entityObject.getString("fullName")));
1✔
593
        if (entityObject.containsKey("givenName")) {
1✔
594
            XmlWriterUtil.writeFullElement(xmlw, "givenName", StringEscapeUtils.escapeXml10(entityObject.getString("givenName")));
1✔
595
        }
596
        if (entityObject.containsKey("familyName")) {
1✔
597
            XmlWriterUtil.writeFullElement(xmlw, "familyName", StringEscapeUtils.escapeXml10(entityObject.getString("familyName")));
1✔
598
        }
599

600
        if (nameIdentifier != null) {
1✔
601
            attributeMap.clear();
1✔
602
            URL url;
603
            try {
604
                url = new URL(nameIdentifier);
1✔
605
                String protocol = url.getProtocol();
1✔
606
                String authority = url.getAuthority();
1✔
607
                String site = String.format("%s://%s", protocol, authority);
1✔
608
                attributeMap.put("schemeURI", site);
1✔
609
                attributeMap.put("nameIdentifierScheme", nameIdentifierScheme);
1✔
610
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "nameIdentifier", attributeMap, nameIdentifier);
1✔
611
            } catch (MalformedURLException e) {
×
612
                logger.warning("DatasetAuthor.getIdentifierAsUrl returned a Malformed URL: " + nameIdentifier);
×
613
            }
1✔
614
        }
615

616
        if (StringUtils.isNotBlank(affiliation)) {
1✔
617
            attributeMap.clear();
1✔
618
            boolean isROR=false;
1✔
619
            String orgName = affiliation;
1✔
620
            ExternalIdentifier externalIdentifier = ExternalIdentifier.ROR_FULL_URL;
1✔
621
            if (externalIdentifier.isValidIdentifier(orgName)) {
1✔
622
                isROR = true;
×
623
                JsonObject jo = getExternalVocabularyValue(orgName);
×
624
                if (jo != null) {
×
625
                    orgName = jo.getString("termName");
×
626
                }
627
            }
628
          
629
            if (isROR) {
1✔
630

631
                attributeMap.put("schemeURI", "https://ror.org");
×
632
                attributeMap.put("affiliationIdentifierScheme", "ROR");
×
633
                attributeMap.put("affiliationIdentifier", affiliation);
×
634
            }
635

636
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "affiliation", attributeMap, StringEscapeUtils.escapeXml10(orgName));
1✔
637
        }
638
        xmlw.writeEndElement();
1✔
639
    }
1✔
640

641
    private JsonObject getExternalVocabularyValue(String id) {
642
        return CDI.current().select(DatasetFieldServiceBean.class).get().getExternalVocabularyValue(id);
×
643
    }
644

645
    /**
646
     * 8, Date (with type sub-property) (R)
647
     *
648
     * @param xmlw
649
     *            The Steam writer
650
     * @param dvObject
651
     *            The dataset/datafile
652
     * @throws XMLStreamException
653
     */
654
    private void writeDates(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
655
        boolean datesWritten = false;
1✔
656
        String dateOfDistribution = null;
1✔
657
        String dateOfProduction = null;
1✔
658
        String dateOfDeposit = null;
1✔
659
        Date releaseDate = null;
1✔
660
        String publicationDate = null;
1✔
661
        boolean isAnUpdate=false;
1✔
662
        List<DatasetFieldCompoundValue> datesOfCollection = new ArrayList<DatasetFieldCompoundValue>();
1✔
663
        List<DatasetFieldCompoundValue> timePeriods = new ArrayList<DatasetFieldCompoundValue>();
1✔
664

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

722
        if (StringUtils.isNotBlank(dateOfProduction)) {
1✔
723
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
1✔
724
            attributes.put("dateType", "Created");
1✔
725
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, dateOfProduction);
1✔
726
        }
727
        if (StringUtils.isNotBlank(dateOfDeposit)) {
1✔
728
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
1✔
729
            attributes.put("dateType", "Submitted");
1✔
730
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, dateOfDeposit);
1✔
731
        }
732

733
        if (publicationDate != null) {
1✔
734
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
735

736
            attributes.put("dateType", "Available");
×
737
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, publicationDate);
×
738
        }
739
        if (isAnUpdate) {
1✔
740
            String date = Util.getDateFormat().format(releaseDate);
×
741
            datesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "dates", datesWritten);
×
742

743
            attributes.put("dateType", "Updated");
×
744
            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "date", attributes, date);
×
745
        }
746
        if (datesOfCollection != null) {
1✔
747
            for (DatasetFieldCompoundValue collectionDateFieldValue : datesOfCollection) {
1✔
748
                String startDate = null;
1✔
749
                String endDate = null;
1✔
750

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

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

804
    private String cleanUpDate(String date) {
805
        String newDate = null;
1✔
806
        if (!StringUtils.isBlank(date)) {
1✔
807
            try {
808
                SimpleDateFormat sdf = Util.getDateFormat();
1✔
809
                Date start = sdf.parse(date);
1✔
810
                newDate = sdf.format(start);
1✔
811
            } catch (ParseException e) {
×
812
                logger.warning("Could not parse date: " + date);
×
813
            }
1✔
814
        }
815
        return newDate;
1✔
816
    }
817

818
    // 9, Language (MA), language
819
    private void writeLanguage(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
820
        // Currently not supported. Spec indicates one 'primary' language. Could send
821
        // the first entry in DatasetFieldConstant.language or send iff there is only
822
        // one entry, and/or default to the machine's default lang, or the dataverse metadatalang?
823
        return;
1✔
824
    }
825

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

860
        } else {
861
            // Write an attribute only element if there are no kindOfData values.
862
            xmlw.writeStartElement("resourceType");
1✔
863
            xmlw.writeAttribute("resourceTypeGeneral", attributes.get("resourceTypeGeneral"));
1✔
864
            xmlw.writeEndElement();
1✔
865
        }
866

867
    }
1✔
868

869
    /**
870
     * 11 AlternateIdentifier (with type sub-property) (O)
871
     *
872
     * @param xmlw
873
     *            The Steam writer
874
     * @param dvObject
875
     *            The dataset/datafile
876
     * @throws XMLStreamException
877
     */
878
    private void writeAlternateIdentifiers(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
879
        List<DatasetFieldCompoundValue> otherIdentifiers = new ArrayList<DatasetFieldCompoundValue>();
1✔
880
        Set<AlternativePersistentIdentifier> altPids = dvObject.getAlternativePersistentIndentifiers();
1✔
881

882
        boolean alternatesWritten = false;
1✔
883

884
        Map<String, String> attributes = new HashMap<String, String>();
1✔
885
        if (dvObject instanceof Dataset d) {
1✔
886
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
887
            for (DatasetField dsf : dv.getDatasetFields()) {
1✔
888
                if (DatasetFieldConstant.otherId.equals(dsf.getDatasetFieldType().getName())) {
1✔
889
                    otherIdentifiers = dsf.getDatasetFieldCompoundValues();
1✔
890
                    break;
1✔
891
                }
892
            }
1✔
893
        }
894

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

919
            }
×
920
        }
921

922
        for (DatasetFieldCompoundValue otherIdentifier : otherIdentifiers) {
1✔
923
            String identifierType = null;
1✔
924
            String identifier = null;
1✔
925
            for (DatasetField subField : otherIdentifier.getChildDatasetFields()) {
1✔
926
                identifierType = ":unav";
1✔
927
                switch (subField.getDatasetFieldType().getName()) {
1✔
928
                case DatasetFieldConstant.otherIdAgency:
929
                    identifierType = subField.getValue();
1✔
930
                    break;
1✔
931
                case DatasetFieldConstant.otherIdValue:
932
                    identifier = subField.getValue();
1✔
933
                    break;
934
                }
935
            }
1✔
936
            attributes.put("alternateIdentifierType", identifierType);
1✔
937
            if (!StringUtils.isBlank(identifier)) {
1✔
938
                alternatesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "alternateIdentifiers", alternatesWritten);
1✔
939

940
                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "alternateIdentifier", attributes, identifier);
1✔
941
            }
942
        }
1✔
943
        if (alternatesWritten) {
1✔
944
            xmlw.writeEndElement();
1✔
945
        }
946
    }
1✔
947

948
    /**
949
     * 12, RelatedIdentifier (with type and relation type sub-properties) (R)
950
     *
951
     * @param xmlw
952
     *            The Steam writer
953
     * @param dvObject
954
     *            the dataset/datafile
955
     * @throws XMLStreamException
956
     */
957
    private void writeRelatedIdentifiers(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
958

959
        boolean relatedIdentifiersWritten = false;
1✔
960

961
        Map<String, String> attributes = new HashMap<String, String>();
1✔
962

963
        if (dvObject instanceof Dataset dataset) {
1✔
964
            List<DatasetRelPublication> relatedPublications = dataset.getLatestVersionForCopy().getRelatedPublications();
1✔
965
            if (!relatedPublications.isEmpty()) {
1✔
966
                for (DatasetRelPublication relatedPub : relatedPublications) {
1✔
967
                    attributes.clear();
1✔
968

969
                    String pubIdType = relatedPub.getIdType();
1✔
970
                    String identifier = relatedPub.getIdNumber();
1✔
971
                    String url = relatedPub.getUrl();
1✔
972
                    String relationType = relatedPub.getRelationType();
1✔
973
                    if(StringUtils.isBlank(relationType)) {
1✔
974
                        relationType = "IsSupplementTo";
1✔
975
                    }
976
                    /*
977
                     * Note - with identifier and url fields, it's not clear that there's a single
978
                     * way those two fields are used for all identifier types. The code here is
979
                     * ~best effort to interpret those fields.
980
                     */
981
                    logger.fine("Found relpub: " + pubIdType + " " + identifier + " " + url);
1✔
982

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

1098
                    attributes.clear();
×
1099
                    attributes.put("relationType", "IsPartOf");
×
1100
                    attributes.put("relatedIdentifierType", pubIdType);
×
1101
                    relatedIdentifiersWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "relatedIdentifiers", relatedIdentifiersWritten);
×
1102
                    XmlWriterUtil.writeFullElementWithAttributes(xmlw, "relatedIdentifier", attributes, pid.asRawIdentifier());
×
1103
                }
1104
            }
1105
        }
1106
        if (relatedIdentifiersWritten) {
1✔
1107
            xmlw.writeEndElement();
1✔
1108
        }
1109
    }
1✔
1110

1111
    static HashMap<String, String> relatedIdentifierTypeMap = new HashMap<String, String>();
1✔
1112

1113
    private static String getCanonicalPublicationType(String pubIdType) {
1114
        if (relatedIdentifierTypeMap.isEmpty()) {
1✔
1115
            relatedIdentifierTypeMap.put("ARK".toLowerCase(), "ARK");
1✔
1116
            relatedIdentifierTypeMap.put("arXiv", "arXiv");
1✔
1117
            relatedIdentifierTypeMap.put("bibcode".toLowerCase(), "bibcode");
1✔
1118
            relatedIdentifierTypeMap.put("DOI".toLowerCase(), "DOI");
1✔
1119
            relatedIdentifierTypeMap.put("EAN13".toLowerCase(), "EAN13");
1✔
1120
            relatedIdentifierTypeMap.put("EISSN".toLowerCase(), "EISSN");
1✔
1121
            relatedIdentifierTypeMap.put("Handle".toLowerCase(), "Handle");
1✔
1122
            relatedIdentifierTypeMap.put("IGSN".toLowerCase(), "IGSN");
1✔
1123
            relatedIdentifierTypeMap.put("ISBN".toLowerCase(), "ISBN");
1✔
1124
            relatedIdentifierTypeMap.put("ISSN".toLowerCase(), "ISSN");
1✔
1125
            relatedIdentifierTypeMap.put("ISTC".toLowerCase(), "ISTC");
1✔
1126
            relatedIdentifierTypeMap.put("LISSN".toLowerCase(), "LISSN");
1✔
1127
            relatedIdentifierTypeMap.put("LSID".toLowerCase(), "LSID");
1✔
1128
            relatedIdentifierTypeMap.put("PISSN".toLowerCase(), "PISSN");
1✔
1129
            relatedIdentifierTypeMap.put("PMID".toLowerCase(), "PMID");
1✔
1130
            relatedIdentifierTypeMap.put("PURL".toLowerCase(), "PURL");
1✔
1131
            relatedIdentifierTypeMap.put("UPC".toLowerCase(), "UPC");
1✔
1132
            relatedIdentifierTypeMap.put("URL".toLowerCase(), "URL");
1✔
1133
            relatedIdentifierTypeMap.put("URN".toLowerCase(), "URN");
1✔
1134
            relatedIdentifierTypeMap.put("WOS".toLowerCase(), "WOS");
1✔
1135
            // Add entry for Handle,Perma protocols so this can be used with GlobalId/getProtocol()
1136
            relatedIdentifierTypeMap.put("hdl".toLowerCase(), "Handle");
1✔
1137
            relatedIdentifierTypeMap.put("perma".toLowerCase(), "URL");
1✔
1138
            
1139
        }
1140
        return relatedIdentifierTypeMap.get(pubIdType);
1✔
1141
    }
1142

1143
    private void writeSize(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1144
        // sizes -> size
1145
        boolean sizesWritten = false;
1✔
1146
        List<DataFile> dataFiles = new ArrayList<DataFile>();
1✔
1147

1148
        if (dvObject instanceof Dataset dataset) {
1✔
1149
            dataFiles = dataset.getFiles();
1✔
1150
        } else if (dvObject instanceof DataFile df) {
×
1151
            dataFiles.add(df);
×
1152
        }
1153
        if (dataFiles != null && !dataFiles.isEmpty()) {
1✔
1154
            for (DataFile dataFile : dataFiles) {
×
1155
                Long size = dataFile.getFilesize();
×
1156
                if (size != -1) {
×
1157
                    sizesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "sizes", sizesWritten);
×
1158
                    XmlWriterUtil.writeFullElement(xmlw, "size", size.toString());
×
1159
                }
1160
            }
×
1161
        }
1162
        if (sizesWritten) {
1✔
1163
            xmlw.writeEndElement();
×
1164
        }
1165

1166
    }
1✔
1167

1168
    private void writeFormats(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1169

1170
        boolean formatsWritten = false;
1✔
1171
        List<DataFile> dataFiles = new ArrayList<DataFile>();
1✔
1172

1173
        if (dvObject instanceof Dataset dataset) {
1✔
1174
            dataFiles = dataset.getFiles();
1✔
1175
        } else if (dvObject instanceof DataFile df) {
×
1176
            dataFiles.add(df);
×
1177
        }
1178
        if (dataFiles != null && !dataFiles.isEmpty()) {
1✔
1179
            for (DataFile dataFile : dataFiles) {
×
1180
                String format = dataFile.getContentType();
×
1181
                if (StringUtils.isNotBlank(format)) {
×
1182
                    formatsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "formats", formatsWritten);
×
1183
                    XmlWriterUtil.writeFullElement(xmlw, "format", format);
×
1184
                }
1185
                /*
1186
                 * Should original formats be sent? What about original sizes above?
1187
                 * if(dataFile.isTabularData()) { String originalFormat =
1188
                 * dataFile.getOriginalFileFormat(); if(StringUtils.isNotBlank(originalFormat))
1189
                 * { XmlWriterUtil.writeFullElement(xmlw, "format", format); } }
1190
                 */
1191
            }
×
1192
        }
1193
        if (formatsWritten) {
1✔
1194
            xmlw.writeEndElement();
×
1195
        }
1196

1197
    }
1✔
1198

1199
    private void writeVersion(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1200
        Dataset d = null;
1✔
1201
        if (dvObject instanceof Dataset) {
1✔
1202
            d = (Dataset) dvObject;
1✔
1203
        } else if (dvObject instanceof DataFile) {
×
1204
            d = ((DataFile) dvObject).getOwner();
×
1205
        }
1206
        if (d != null) {
1✔
1207
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
1208
            String version = dv.getFriendlyVersionNumber();
1✔
1209
            if (StringUtils.isNotBlank(version)) {
1✔
1210
                XmlWriterUtil.writeFullElement(xmlw, "version", version);
1✔
1211
            }
1212
        }
1213

1214
    }
1✔
1215

1216
    private void writeAccessRights(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1217
        // rightsList -> rights with rightsURI attribute
1218
        xmlw.writeStartElement("rightsList"); // <rightsList>
1✔
1219

1220
        // set terms from the info:eu-repo-Access-Terms vocabulary
1221
        xmlw.writeStartElement("rights"); // <rights>
1✔
1222
        DatasetVersion dv = null;
1✔
1223
        boolean closed = false;
1✔
1224
        if (dvObject instanceof Dataset d) {
1✔
1225
            dv = d.getLatestVersionForCopy();
1✔
1226
            closed = dv.isHasRestrictedFile();
1✔
1227
        } else if (dvObject instanceof DataFile df) {
×
1228
            dv = df.getOwner().getLatestVersionForCopy();
×
1229

1230
            closed = df.isRestricted();
×
1231
        }
1232
        TermsOfUseAndAccess terms = dv.getTermsOfUseAndAccess();
1✔
1233
        boolean requestsAllowed = terms.isFileAccessRequest();
1✔
1234
        License license = terms.getLicense();
1✔
1235

1236
        if (requestsAllowed && closed) {
1✔
1237
            xmlw.writeAttribute("rightsURI", "info:eu-repo/semantics/restrictedAccess");
×
1238
        } else if (!requestsAllowed && closed) {
1✔
1239
            xmlw.writeAttribute("rightsURI", "info:eu-repo/semantics/closedAccess");
×
1240
        } else {
1241
            xmlw.writeAttribute("rightsURI", "info:eu-repo/semantics/openAccess");
1✔
1242
        }
1243
        xmlw.writeEndElement(); // </rights>
1✔
1244
        xmlw.writeStartElement("rights"); // <rights>
1✔
1245

1246
        if (license != null) {
1✔
1247
            xmlw.writeAttribute("rightsURI", license.getUri().toString());
×
1248
            xmlw.writeCharacters(license.getName());
×
1249
        } else {
1250
            xmlw.writeAttribute("rightsURI", DatasetUtil.getLicenseURI(dv));
1✔
1251
            xmlw.writeCharacters(BundleUtil.getStringFromBundle("license.custom.description"));
1✔
1252
            ;
1253
        }
1254
        xmlw.writeEndElement(); // </rights>
1✔
1255
        xmlw.writeEndElement(); // </rightsList>
1✔
1256
    }
1✔
1257

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

1287
        if (dv != null) {
1✔
1288
            List<DatasetField> dsfs = dv.getDatasetFields();
1✔
1289

1290
            for (DatasetField dsf : dsfs) {
1✔
1291

1292
                switch (dsf.getDatasetFieldType().getName()) {
1✔
1293
                    case DatasetFieldConstant.software:
1294
                        attributes.clear();
1✔
1295
                        attributes.put("descriptionType", "TechnicalInfo");
1✔
1296
                        List<DatasetFieldCompoundValue> dsfcvs = dsf.getDatasetFieldCompoundValues();
1✔
1297
                        for (DatasetFieldCompoundValue dsfcv : dsfcvs) {
1✔
1298

1299
                            String softwareName = null;
1✔
1300
                            String softwareVersion = null;
1✔
1301
                            List<DatasetField> childDsfs = dsfcv.getChildDatasetFields();
1✔
1302
                            for (DatasetField childDsf : childDsfs) {
1✔
1303
                                if (DatasetFieldConstant.softwareName.equals(childDsf.getDatasetFieldType().getName())) {
1✔
1304
                                    softwareName = childDsf.getValue();
1✔
1305
                                } else if (DatasetFieldConstant.softwareVersion.equals(childDsf.getDatasetFieldType().getName())) {
1✔
1306
                                    softwareVersion = childDsf.getValue();
1✔
1307
                                }
1308
                            }
1✔
1309
                            if (StringUtils.isNotBlank(softwareName)) {
1✔
1310
                                if (StringUtils.isNotBlank(softwareVersion)) {
1✔
1311
                                    softwareName = softwareName + ", " + softwareVersion;
1✔
1312
                                }
1313
                                descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
1✔
1314
                                XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, softwareName);
1✔
1315
                            }
1316
                        }
1✔
1317
                        break;
1✔
1318
                    case DatasetFieldConstant.originOfSources:
1319
                    case DatasetFieldConstant.characteristicOfSources:
1320
                    case DatasetFieldConstant.accessToSources:
1321
                        attributes.clear();
1✔
1322
                        attributes.put("descriptionType", "Methods");
1✔
1323
                        String method = dsf.getValue();
1✔
1324
                        if (StringUtils.isNotBlank(method)) {
1✔
1325
                            descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
1✔
1326
                            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, method);
1✔
1327

1328
                        }
1329
                        break;
1330
                    case DatasetFieldConstant.series:
1331
                        attributes.clear();
1✔
1332
                        attributes.put("descriptionType", "SeriesInformation");
1✔
1333
                        dsfcvs = dsf.getDatasetFieldCompoundValues();
1✔
1334
                        for (DatasetFieldCompoundValue dsfcv : dsfcvs) {
1✔
1335
                            List<DatasetField> childDsfs = dsfcv.getChildDatasetFields();
1✔
1336
                            for (DatasetField childDsf : childDsfs) {
1✔
1337

1338
                                if (DatasetFieldConstant.seriesName.equals(childDsf.getDatasetFieldType().getName())) {
1✔
1339
                                    String seriesInformation = childDsf.getValue();
1✔
1340
                                    if (StringUtils.isNotBlank(seriesInformation)) {
1✔
1341
                                        descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
1✔
1342
                                        XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, seriesInformation);
1✔
1343
                                    }
1344
                                    break;
1345
                                }
1346
                            }
×
1347
                        }
1✔
1348
                        break;
1✔
1349
                    case DatasetFieldConstant.notesText:
1350
                        attributes.clear();
1✔
1351
                        attributes.put("descriptionType", "Other");
1✔
1352
                        String notesText = dsf.getValue();
1✔
1353
                        if (StringUtils.isNotBlank(notesText)) {
1✔
1354
                            descriptionsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "descriptions", descriptionsWritten);
1✔
1355
                            XmlWriterUtil.writeFullElementWithAttributes(xmlw, "description", attributes, notesText);
1✔
1356
                        }
1357
                        break;
1358

1359
                }
1360
            }
1✔
1361

1362
        }
1363

1364
        if (descriptionsWritten) {
1✔
1365
            xmlw.writeEndElement(); // </descriptions>
1✔
1366
        }
1367
    }
1✔
1368

1369
    private void writeGeoLocations(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException {
1370
        if (dvObject instanceof Dataset d) {
1✔
1371
            boolean geoLocationsWritten = false;
1✔
1372
            DatasetVersion dv = d.getLatestVersionForCopy();
1✔
1373

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

1418
                            }
1419
                        }
1✔
1420
                        if (StringUtils.isNoneBlank(wLongitude, eLongitude, nLatitude, sLatitude)) {
1✔
1421
                            geoLocationsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "geoLocations", geoLocationsWritten);
1✔
1422
                            xmlw.writeStartElement("geoLocation"); // <geoLocation>
1✔
1423
                            if (wLongitude.equals(eLongitude) && nLatitude.equals(sLatitude)) {
1✔
1424
                                // A point
1425
                                xmlw.writeStartElement("geoLocationPoint");
×
1426
                                XmlWriterUtil.writeFullElement(xmlw, "pointLongitude", eLongitude);
×
1427
                                XmlWriterUtil.writeFullElement(xmlw, "pointLatitude", sLatitude);
×
1428
                                xmlw.writeEndElement();
×
1429
                            } else {
1430
                                // A box
1431
                                xmlw.writeStartElement("geoLocationBox");
1✔
1432
                                XmlWriterUtil.writeFullElement(xmlw, "westBoundLongitude", wLongitude);
1✔
1433
                                XmlWriterUtil.writeFullElement(xmlw, "eastBoundLongitude", eLongitude);
1✔
1434
                                XmlWriterUtil.writeFullElement(xmlw, "southBoundLatitude", sLatitude);
1✔
1435
                                XmlWriterUtil.writeFullElement(xmlw, "northBoundLatitude", nLatitude);
1✔
1436
                                xmlw.writeEndElement();
1✔
1437

1438
                            }
1439
                            xmlw.writeEndElement(); // </geoLocation>
1✔
1440
                        }
1441
                    }
1✔
1442
                case DatasetFieldConstant.productionPlace:
1443
                    productionPlaceFound = true;
1✔
1444
                    // geoLocationPlace
1445
                    geoLocationsWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "geoLocations", geoLocationsWritten);
1✔
1446
                    List<String> prodPlaces = dsf.getValues();
1✔
1447
                    for (String prodPlace : prodPlaces) {
1✔
1448
                        xmlw.writeStartElement("geoLocation"); // <geoLocation>
1✔
1449
                        XmlWriterUtil.writeFullElement(xmlw, "geoLocationPlace", prodPlace);
1✔
1450
                        xmlw.writeEndElement(); // </geoLocation>
1✔
1451
                    }
1✔
1452
                    break;
1453
                }
1454
                if (boundingBoxFound && productionPlaceFound) {
1✔
1455
                    break;
1✔
1456
                }
1457
            }
1✔
1458
            if (geoLocationsWritten) {
1✔
1459
                xmlw.writeEndElement(); // <geoLocations>
1✔
1460
            }
1461
        }
1462

1463
    }
1✔
1464

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

1546
                    }
1✔
1547
                }
1548
            }
1✔
1549

1550
            if (fundingReferenceWritten) {
1✔
1551
                xmlw.writeEndElement(); // </fundingReferences>
1✔
1552
            }
1553

1554
        }
1555
    }
1✔
1556
}
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