From b95ae8baa5d32da33d3f6e47b7b89624cdf8b13f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:23:31 +0000 Subject: [PATCH 1/4] Port DataCite datafile-info mode support from DANS PR253 Co-authored-by: janvanmansum <3862694+janvanmansum@users.noreply.github.com> --- .../source/installation/config.rst | 19 ++++ .../pidproviders/PidProviderFactoryBean.java | 3 +- .../pidproviders/doi/XmlMetadataTemplate.java | 101 +++++++++++++++--- .../datacite/DOIDataCiteRegisterService.java | 49 +++++++-- .../doi/datacite/DataCiteDOIProvider.java | 13 ++- .../doi/datacite/DataCiteProviderFactory.java | 3 +- .../iq/dataverse/settings/JvmSettings.java | 4 + .../dataverse/pidproviders/PidUtilTest.java | 2 +- .../doi/datacite/DataCiteProviderTest.java | 2 +- .../doi/datacite/XmlMetadataTemplateTest.java | 80 ++++++++++++++ .../dataverse/settings/JvmSettingsTest.java | 6 ++ 11 files changed, 249 insertions(+), 33 deletions(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 3ac34266f92..41d827931a8 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -848,6 +848,25 @@ Here are the configuration options for PermaLinks: You must restart Payara after making changes to these settings. +.. _datacite-settings: + +DataCite Settings +----------------- + +.. _dataverse.datacite.xml.datafile-info: + +dataverse.datacite.xml.datafile-info +++++++++++++++++++++++++++++++++++++ + +``dataverse.datacite.xml.datafile-info`` controls how DataCite XML for datasets represents +datafile sizes and formats. The default value, ``expanded``, preserves the current behavior and emits +one ``size`` and one ``format`` element per datafile. Set the value to ``brief`` to emit a single +``size`` element with the sum of all known datafile sizes and one ``format`` element per distinct +datafile format. Set the value to ``none`` to omit datafile ``size`` and ``format`` elements entirely. +This setting applies to both the DataCite metadata export and to DataCite XML sent by the PID provider, +such as when publishing a new dataset version or explicitly updating PID-provider metadata for a +published dataset. + .. _auth-modes: Auth Modes: Local vs. Remote vs. Both diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidProviderFactoryBean.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidProviderFactoryBean.java index 267cbab3edd..ceac8e5b14c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidProviderFactoryBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidProviderFactoryBean.java @@ -174,10 +174,11 @@ private void loadProviders() { // Defaults for testing where no account is set up String dcUsername = JvmSettings.LEGACY_DATACITE_USERNAME.lookup(); String dcPassword = JvmSettings.LEGACY_DATACITE_PASSWORD.lookup(); + String datafileInfoMode = JvmSettings.DATACITE_XML_DATAFILE_INFO.lookupOptional().orElse("expanded"); if (mdsUrl != null && restUrl != null && dcUsername != null && dcPassword != null) { legacy = new DataCiteDOIProvider("legacy", "legacy", authority, shoulder, identifierGenerationStyle, dataFilePidFormat, "", "", mdsUrl, restUrl, dcUsername, - dcPassword); + dcPassword, datafileInfoMode); } break; case "FAKE": diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java index 954ddc46f36..cb220213d04 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java @@ -16,6 +16,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -58,13 +59,43 @@ public class XmlMetadataTemplate { public static final String XML_XSI = "http://www.w3.org/2001/XMLSchema-instance"; public static final String XML_SCHEMA_VERSION = "4.7"; + public enum DatafileInfoMode { + EXPANDED("expanded"), + BRIEF("brief"), + NONE("none"); + + private final String value; + + DatafileInfoMode(String value) { + this.value = value; + } + + public static DatafileInfoMode from(String value) { + if (value != null) { + for (DatafileInfoMode mode : values()) { + if (mode.value.equalsIgnoreCase(value.trim())) { + return mode; + } + } + logger.warning("Unknown DataCite datafile info mode '" + value + "', using expanded"); + } + return EXPANDED; + } + } + private DoiMetadata doiMetadata; + private DatafileInfoMode datafileInfoMode = DatafileInfoMode.EXPANDED; public XmlMetadataTemplate() { } public XmlMetadataTemplate(DoiMetadata doiMetadata) { + this(doiMetadata, DatafileInfoMode.EXPANDED); + } + + public XmlMetadataTemplate(DoiMetadata doiMetadata, DatafileInfoMode datafileInfoMode) { this.doiMetadata = doiMetadata; + this.datafileInfoMode = datafileInfoMode; } public String generateXML(DvObject dvObject) { @@ -1192,19 +1223,35 @@ private static String getCanonicalPublicationType(String pubIdType) { private void writeSize(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException { // sizes -> size boolean sizesWritten = false; - List dataFiles = new ArrayList(); - - if (dvObject instanceof Dataset dataset) { - dataFiles = dataset.getFiles(); - } else if (dvObject instanceof DataFile df) { - dataFiles.add(df); + if (datafileInfoMode == DatafileInfoMode.NONE) { + return; + } + List dataFiles = getDataFiles(dvObject); + if (datafileInfoMode == DatafileInfoMode.BRIEF) { + long totalSize = 0L; + boolean hasKnownSize = false; + if (dataFiles != null && !dataFiles.isEmpty()) { + for (DataFile dataFile : dataFiles) { + long size = dataFile.getFilesize(); + if (size != -1) { + totalSize += size; + hasKnownSize = true; + } + } + } + if (hasKnownSize) { + xmlw.writeStartElement("sizes"); + XmlWriterUtil.writeFullElement(xmlw, "size", Long.toString(totalSize)); + xmlw.writeEndElement(); + } + return; } if (dataFiles != null && !dataFiles.isEmpty()) { for (DataFile dataFile : dataFiles) { - Long size = dataFile.getFilesize(); + long size = dataFile.getFilesize(); if (size != -1) { sizesWritten = XmlWriterUtil.writeOpenTagIfNeeded(xmlw, "sizes", sizesWritten); - XmlWriterUtil.writeFullElement(xmlw, "size", size.toString()); + XmlWriterUtil.writeFullElement(xmlw, "size", Long.toString(size)); } } } @@ -1217,12 +1264,28 @@ private void writeSize(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStream private void writeFormats(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException { boolean formatsWritten = false; - List dataFiles = new ArrayList(); - - if (dvObject instanceof Dataset dataset) { - dataFiles = dataset.getFiles(); - } else if (dvObject instanceof DataFile df) { - dataFiles.add(df); + if (datafileInfoMode == DatafileInfoMode.NONE) { + return; + } + List dataFiles = getDataFiles(dvObject); + if (datafileInfoMode == DatafileInfoMode.BRIEF) { + Set uniqueFormats = new LinkedHashSet<>(); + if (dataFiles != null && !dataFiles.isEmpty()) { + for (DataFile dataFile : dataFiles) { + String format = dataFile.getContentType(); + if (StringUtils.isNotBlank(format)) { + uniqueFormats.add(format); + } + } + } + if (!uniqueFormats.isEmpty()) { + xmlw.writeStartElement("formats"); + for (String format : uniqueFormats) { + XmlWriterUtil.writeFullElement(xmlw, "format", format); + } + xmlw.writeEndElement(); + } + return; } if (dataFiles != null && !dataFiles.isEmpty()) { for (DataFile dataFile : dataFiles) { @@ -1245,6 +1308,16 @@ private void writeFormats(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStr } + private List getDataFiles(DvObject dvObject) { + List dataFiles = new ArrayList<>(); + if (dvObject instanceof Dataset dataset) { + return dataset.getFiles() != null ? dataset.getFiles() : dataFiles; + } else if (dvObject instanceof DataFile df) { + dataFiles.add(df); + } + return dataFiles; + } + private void writeVersion(XMLStreamWriter xmlw, DvObject dvObject) throws XMLStreamException { Dataset d = null; if (dvObject instanceof Dataset) { diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java index e106b4fca4f..b8363117178 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java @@ -23,6 +23,8 @@ import edu.harvard.iq.dataverse.pidproviders.AbstractPidProvider; import edu.harvard.iq.dataverse.pidproviders.doi.DoiMetadata; import edu.harvard.iq.dataverse.pidproviders.doi.XmlMetadataTemplate; +import edu.harvard.iq.dataverse.pidproviders.doi.XmlMetadataTemplate.DatafileInfoMode; +import edu.harvard.iq.dataverse.settings.JvmSettings; import org.xmlunit.builder.DiffBuilder; import org.xmlunit.builder.Input; @@ -58,8 +60,13 @@ public DOIDataCiteRegisterService(String url, String restApiUrl, String username * https://support.datacite.org/docs/mds-api-guide#doi-states */ public String reserveIdentifier(String identifier, Map metadata, DvObject dvObject) throws IOException { + return reserveIdentifier(identifier, metadata, dvObject, getConfiguredDatafileInfoMode()); + } + + public String reserveIdentifier(String identifier, Map metadata, DvObject dvObject, + DatafileInfoMode datafileInfoMode) throws IOException { String retString = ""; - String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject); + String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject, datafileInfoMode); retString = client.postMetadata(xmlMetadata); @@ -67,8 +74,13 @@ public String reserveIdentifier(String identifier, Map metadata, } public String registerIdentifier(String identifier, Map metadata, DvObject dvObject) throws IOException { + return registerIdentifier(identifier, metadata, dvObject, getConfiguredDatafileInfoMode()); + } + + public String registerIdentifier(String identifier, Map metadata, DvObject dvObject, + DatafileInfoMode datafileInfoMode) throws IOException { String retString = ""; - String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject); + String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject, datafileInfoMode); String target = metadata.get("_target"); retString = client.postMetadata(xmlMetadata); @@ -79,10 +91,15 @@ public String registerIdentifier(String identifier, Map metadata public String reRegisterIdentifier(String identifier, Map metadata, DvObject dvObject) throws IOException { + return reRegisterIdentifier(identifier, metadata, dvObject, getConfiguredDatafileInfoMode()); + } + + public String reRegisterIdentifier(String identifier, Map metadata, DvObject dvObject, + DatafileInfoMode datafileInfoMode) throws IOException { String retString = ""; // "bare identifier" is the canonical pid with the "doi:" prefix stripped String bareIdentifier = identifier.substring(identifier.indexOf(":") + 1); - String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject); + String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject, datafileInfoMode); String target = metadata.get("_target"); String currentMetadata = null; boolean hasDifferences = false; @@ -131,8 +148,17 @@ public String deactivateIdentifier(String identifier, Map metada return retString; } - - public static String getMetadataFromDvObject(String identifier, Map metadata, DvObject dvObject) { + + private static DatafileInfoMode getConfiguredDatafileInfoMode() { + return DatafileInfoMode.from(JvmSettings.DATACITE_XML_DATAFILE_INFO.lookupOptional().orElse("expanded")); + } + + public static String getMetadataFromDvObject(String identifier, Map metadata, DvObject dvObject) { + return getMetadataFromDvObject(identifier, metadata, dvObject, getConfiguredDatafileInfoMode()); + } + + public static String getMetadataFromDvObject(String identifier, Map metadata, DvObject dvObject, + DatafileInfoMode datafileInfoMode) { Dataset dataset = null; @@ -182,7 +208,7 @@ public static String getMetadataFromDvObject(String identifier, Map metadata, DvObject dvObject) throws IOException { + return modifyIdentifier(identifier, metadata, dvObject, getConfiguredDatafileInfoMode()); + } + + public String modifyIdentifier(String identifier, Map metadata, DvObject dvObject, + DatafileInfoMode datafileInfoMode) throws IOException { - String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject); + String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject, datafileInfoMode); logger.fine("XML to send to DataCite: " + xmlMetadata); diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteDOIProvider.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteDOIProvider.java index 1fc08b42ad4..46c483ae3f5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteDOIProvider.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteDOIProvider.java @@ -15,6 +15,7 @@ import edu.harvard.iq.dataverse.FileMetadata; import edu.harvard.iq.dataverse.GlobalId; import edu.harvard.iq.dataverse.pidproviders.doi.AbstractDOIProvider; +import edu.harvard.iq.dataverse.pidproviders.doi.XmlMetadataTemplate; import edu.harvard.iq.dataverse.settings.FeatureFlags; import edu.harvard.iq.dataverse.util.json.JsonUtil; import jakarta.json.JsonObject; @@ -47,18 +48,20 @@ public class DataCiteDOIProvider extends AbstractDOIProvider { private String apiUrl; private String username; private String password; + private XmlMetadataTemplate.DatafileInfoMode datafileInfoMode; private DOIDataCiteRegisterService doiDataCiteRegisterService; public DataCiteDOIProvider(String id, String label, String providerAuthority, String providerShoulder, String identifierGenerationStyle, String datafilePidFormat, String managedList, String excludedList, - String mdsUrl, String apiUrl, String username, String password) { + String mdsUrl, String apiUrl, String username, String password, String datafileInfoMode) { super(id, label, providerAuthority, providerShoulder, identifierGenerationStyle, datafilePidFormat, managedList, excludedList); this.mdsUrl = mdsUrl; this.apiUrl = apiUrl; this.username = username; this.password = password; + this.datafileInfoMode = XmlMetadataTemplate.DatafileInfoMode.from(datafileInfoMode); doiDataCiteRegisterService = new DOIDataCiteRegisterService(mdsUrl, apiUrl, username, password); } @@ -95,7 +98,7 @@ public String createIdentifier(DvObject dvObject) throws Exception { Map metadata = getMetadataForCreateIndicator(dvObject); metadata.put("_status", DRAFT); try { - String retString = doiDataCiteRegisterService.reserveIdentifier(identifier, metadata, dvObject); + String retString = doiDataCiteRegisterService.reserveIdentifier(identifier, metadata, dvObject, datafileInfoMode); logger.log(Level.FINE, "create DOI identifier retString : " + retString); return retString; } catch (Exception e) { @@ -132,7 +135,7 @@ public String modifyIdentifierTargetURL(DvObject dvObject) throws Exception { try { Map metadata = getIdentifierMetadata(dvObject); metadata.put("_target", getTargetUrl(dvObject)); - doiDataCiteRegisterService.modifyIdentifier(identifier, metadata, dvObject); + doiDataCiteRegisterService.modifyIdentifier(identifier, metadata, dvObject, datafileInfoMode); } catch (Exception e) { logger.log(Level.WARNING, "modifyMetadata failed", e); throw e; @@ -219,9 +222,9 @@ public boolean publicizeIdentifier(DvObject dvObject) { metadata.put("_target", getTargetUrl(dvObject)); try { if (FeatureFlags.ONLY_UPDATE_DATACITE_WHEN_NEEDED.enabled()) { - doiDataCiteRegisterService.reRegisterIdentifier(identifier, metadata, dvObject); + doiDataCiteRegisterService.reRegisterIdentifier(identifier, metadata, dvObject, datafileInfoMode); } else { - doiDataCiteRegisterService.registerIdentifier(identifier, metadata, dvObject); + doiDataCiteRegisterService.registerIdentifier(identifier, metadata, dvObject, datafileInfoMode); } return true; } catch (Exception e) { diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteProviderFactory.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteProviderFactory.java index 99d13b2647c..057d67c627b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteProviderFactory.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteProviderFactory.java @@ -31,9 +31,10 @@ public PidProvider createPidProvider(String providerId) { String apiUrl = JvmSettings.DATACITE_REST_API_URL.lookupOptional(providerId).orElse("https://api.test.datacite.org"); String username = JvmSettings.DATACITE_USERNAME.lookup(providerId); String password = JvmSettings.DATACITE_PASSWORD.lookup(providerId); + String datafileInfoMode = JvmSettings.DATACITE_XML_DATAFILE_INFO.lookupOptional().orElse("expanded"); return new DataCiteDOIProvider(providerId, providerLabel, providerAuthority, providerShoulder, identifierGenerationStyle, - datafilePidFormat, managedList, excludedList, mdsUrl, apiUrl, username, password); + datafilePidFormat, managedList, excludedList, mdsUrl, apiUrl, username, password, datafileInfoMode); } public String getType() { diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java index 8ed18b4c63f..8c59a152024 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java @@ -117,6 +117,10 @@ public enum JvmSettings { MDB_SYSTEM_METADATA_KEYS(SCOPE_METADATA, "block-system-metadata-keys"), MDB_SYSTEM_KEY_FOR(MDB_SYSTEM_METADATA_KEYS), + // DATACITE SETTINGS (applies to both DataCite PID provider and DataCite metadata export) + SCOPE_DATACITE(PREFIX, "datacite"), + DATACITE_XML_DATAFILE_INFO(SCOPE_DATACITE, "xml.datafile-info"), + // PERSISTENT IDENTIFIER SETTINGS SCOPE_PID(PREFIX, "pid"), PID_PROVIDERS(SCOPE_PID, "providers"), diff --git a/src/test/java/edu/harvard/iq/dataverse/pidproviders/PidUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/pidproviders/PidUtilTest.java index d73ca414b7a..19b2fa4f000 100644 --- a/src/test/java/edu/harvard/iq/dataverse/pidproviders/PidUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/pidproviders/PidUtilTest.java @@ -498,7 +498,7 @@ public void testLegacyConfig() throws IOException { if (mdsUrl != null && restUrl != null && dcUsername != null && dcPassword != null) { legacy = new DataCiteDOIProvider("legacy", "legacy", authority, shoulder, identifierGenerationStyle, dataFilePidFormat, "", "", mdsUrl, restUrl, dcUsername, - dcPassword); + dcPassword, "expanded"); } break; case "FAKE": diff --git a/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteProviderTest.java b/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteProviderTest.java index 572fc722272..79fad589d2d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteProviderTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DataCiteProviderTest.java @@ -83,7 +83,7 @@ public void testDoiLifecycle() throws IOException { String shoulder = System.getenv("DataCiteShoulder"); DataCiteDOIProvider provider = new DataCiteDOIProvider("test", "test", authority, shoulder, "randomString", SystemConfig.DataFilePIDFormat.DEPENDENT.toString(), "", "", "https://mds.test.datacite.org", - "https://api.test.datacite.org", username, password); + "https://api.test.datacite.org", username, password, "expanded"); provider.setPidProviderServiceBean(pidService); diff --git a/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java b/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java index 19da198399a..49c4c171384 100644 --- a/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java @@ -2,6 +2,7 @@ import edu.harvard.iq.dataverse.ControlledVocabularyValue; import edu.harvard.iq.dataverse.DataCitation; +import edu.harvard.iq.dataverse.DataFile; import edu.harvard.iq.dataverse.Dataset; import edu.harvard.iq.dataverse.DatasetAuthor; import edu.harvard.iq.dataverse.DatasetField; @@ -22,6 +23,7 @@ import edu.harvard.iq.dataverse.pidproviders.PidProviderFactoryBean; import edu.harvard.iq.dataverse.pidproviders.doi.DoiMetadata; import edu.harvard.iq.dataverse.pidproviders.doi.XmlMetadataTemplate; +import edu.harvard.iq.dataverse.pidproviders.doi.XmlMetadataTemplate.DatafileInfoMode; import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.json.CompoundVocabularyException; @@ -420,6 +422,84 @@ public void testDataCiteXMLCreationAllFieldsMultipleGeoLocations() throws IOExce } + @Test + public void testDataCiteXMLDatafileInfoModes() throws Exception { + Dataset dataset = createDatasetWithFiles( + createDataFile(100L, "text/plain"), + createDataFile(200L, "application/pdf"), + createDataFile(300L, "text/plain"), + createDataFile(-1L, null)); + DoiMetadata doiMetadata = createMinimalDoiMetadata(); + + String expandedXml = new XmlMetadataTemplate(doiMetadata, DatafileInfoMode.EXPANDED).generateXML(dataset); + assertEquals(List.of("100", "200", "300"), XmlPath.from(expandedXml).getList("resource.sizes.size")); + assertEquals(List.of("text/plain", "application/pdf", "text/plain"), + XmlPath.from(expandedXml).getList("resource.formats.format")); + + String briefXml = new XmlMetadataTemplate(doiMetadata, DatafileInfoMode.BRIEF).generateXML(dataset); + assertDataCiteXmlIsSchemaValid(briefXml); + assertEquals(List.of("600"), XmlPath.from(briefXml).getList("resource.sizes.size")); + assertEquals(List.of("text/plain", "application/pdf"), + XmlPath.from(briefXml).getList("resource.formats.format")); + + String noneXml = new XmlMetadataTemplate(doiMetadata, DatafileInfoMode.NONE).generateXML(dataset); + assertFalse(noneXml.contains("")); + assertFalse(noneXml.contains("")); + + String defaultXml = new XmlMetadataTemplate(doiMetadata).generateXML(dataset); + assertEquals(XmlPath.from(expandedXml).getList("resource.sizes.size"), + XmlPath.from(defaultXml).getList("resource.sizes.size")); + assertEquals(XmlPath.from(expandedXml).getList("resource.formats.format"), + XmlPath.from(defaultXml).getList("resource.formats.format")); + } + + private void assertDataCiteXmlIsSchemaValid(String xml) throws Exception { + StreamSource source = new StreamSource(new StringReader(xml)); + source.setSystemId("DataCite XML for test dataset"); + assertTrue(XmlValidator.validateXmlSchema(source, + new URL("https://schema.datacite.org/meta/kernel-4/metadata.xsd"))); + } + + private DoiMetadata createMinimalDoiMetadata() { + DoiMetadata doiMetadata = new DoiMetadata(); + doiMetadata.setTitle("A Title"); + doiMetadata.setPublisher("Dataverse"); + doiMetadata.setAuthors(new ArrayList<>()); + return doiMetadata; + } + + private Dataset createDatasetWithFiles(DataFile... dataFiles) { + Dataset dataset = new Dataset(); + dataset.setGlobalId(new GlobalId("doi", "10.5072", "FK2/FILES", null, null, null)); + dataset.setFiles(List.of(dataFiles)); + + DatasetVersion datasetVersion = new DatasetVersion(); + datasetVersion.setVersionState(VersionState.DRAFT); + datasetVersion.setDataset(dataset); + datasetVersion.setTermsOfUseAndAccess(new TermsOfUseAndAccess()); + + DatasetFieldType titleFieldType = new DatasetFieldType(DatasetFieldConstant.title, + DatasetFieldType.FieldType.TEXT, false); + DatasetField titleField = new DatasetField(); + titleField.setDatasetVersion(datasetVersion); + titleField.setDatasetFieldType(titleFieldType); + titleField.setSingleValue("First Title"); + datasetVersion.setDatasetFields(List.of(titleField)); + + dataset.setVersions(new ArrayList<>(List.of(datasetVersion))); + DatasetType datasetType = new DatasetType(); + datasetType.setName(DatasetType.DATASET_TYPE_DATASET); + dataset.setDatasetType(datasetType); + return dataset; + } + + private DataFile createDataFile(long filesize, String contentType) { + DataFile dataFile = new DataFile(); + dataFile.setFilesize(filesize); + dataFile.setContentType(contentType); + return dataFile; + } + @Test public void testDataCiteXMLCreationInvalidDates() throws IOException { Dataverse collection = new Dataverse(); diff --git a/src/test/java/edu/harvard/iq/dataverse/settings/JvmSettingsTest.java b/src/test/java/edu/harvard/iq/dataverse/settings/JvmSettingsTest.java index f4494b7116e..0d0b586e851 100644 --- a/src/test/java/edu/harvard/iq/dataverse/settings/JvmSettingsTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/settings/JvmSettingsTest.java @@ -21,6 +21,12 @@ void lookupSetting() { void lookupPidProviderSetting() { assertEquals("test", JvmSettings.DATACITE_USERNAME.lookup("datacite")); } + + @Test + @SystemProperty(key = "dataverse.datacite.xml.datafile-info", value = "brief") + void lookupDataciteDatafileInfoSetting() { + assertEquals("brief", JvmSettings.DATACITE_XML_DATAFILE_INFO.lookup()); + } @Test @SystemProperty(key = "dataverse.ingest.rserve.port", value = "1234") From c4af774afdf3165c72e8340aca6a9776356f3ac1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:25:22 +0000 Subject: [PATCH 2/4] Address review nits in docs heading and DataCite service spacing Co-authored-by: janvanmansum <3862694+janvanmansum@users.noreply.github.com> --- doc/sphinx-guides/source/installation/config.rst | 2 +- .../pidproviders/doi/datacite/DOIDataCiteRegisterService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 41d827931a8..e146d4e59f7 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -856,7 +856,7 @@ DataCite Settings .. _dataverse.datacite.xml.datafile-info: dataverse.datacite.xml.datafile-info -++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++ ``dataverse.datacite.xml.datafile-info`` controls how DataCite XML for datasets represents datafile sizes and formats. The default value, ``expanded``, preserves the current behavior and emits diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java index b8363117178..fd2949c59ec 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java @@ -222,7 +222,7 @@ public static String getMetadataForDeactivateIdentifier(String identifier, Map Date: Tue, 1 Sep 2026 16:26:00 +0000 Subject: [PATCH 3/4] Document fallback behavior for DataCite mode overloads Co-authored-by: janvanmansum <3862694+janvanmansum@users.noreply.github.com> --- .../pidproviders/doi/datacite/DOIDataCiteRegisterService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java index fd2949c59ec..0fb0d2bb2a3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/DOIDataCiteRegisterService.java @@ -149,6 +149,9 @@ public String deactivateIdentifier(String identifier, Map metada return retString; } + /** + * Backward-compatibility fallback for callers that still use no-arg overloads. + */ private static DatafileInfoMode getConfiguredDatafileInfoMode() { return DatafileInfoMode.from(JvmSettings.DATACITE_XML_DATAFILE_INFO.lookupOptional().orElse("expanded")); } From f2c7c4608fe1311464a0f3f4bc34a79381eeb063 Mon Sep 17 00:00:00 2001 From: Jan van Mansum Date: Thu, 3 Sep 2026 12:18:41 +0200 Subject: [PATCH 4/4] Add release note --- doc/release-notes/12662-smaller-datacite-xml.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/release-notes/12662-smaller-datacite-xml.md diff --git a/doc/release-notes/12662-smaller-datacite-xml.md b/doc/release-notes/12662-smaller-datacite-xml.md new file mode 100644 index 00000000000..16f26c689fd --- /dev/null +++ b/doc/release-notes/12662-smaller-datacite-xml.md @@ -0,0 +1,4 @@ +Introduces a new JVM-option `dataverse.datacite.xml.datafile-info` that allows you to configure the number of `size` and `format` elements included in the +DataCite XML metadata. Valid options are `expanded` (default, the current behavior of including one `size` and one `format` element per datafile), `brief` +(one `size` element with the sum of all datafile sizes and one `format` element per unique datafile format), and `none` (no `size` or `format` elements included +in the DataCite XML metadata). See #12662 for details.