From 1a5b5b71abe342ab99e1d25ee457a2b08931c833 Mon Sep 17 00:00:00 2001 From: josegar74 Date: Wed, 1 Jul 2026 15:17:02 +0200 Subject: [PATCH 1/8] Set/unset metadata publication date when a metadata is published / unpublished --- .../fao/geonet/kernel/setting/Settings.java | 1 + .../system-configuration.md | 6 +- .../process/publicationdate-add.xsl | 96 +++++++++++ .../process/publicationdate-remove.xsl | 49 ++++++ .../PublicationDateAddProcessTest.java | 154 ++++++++++++++++++ .../PublicationDateRemoveProcessTest.java | 95 +++++++++++ .../iso19139/process/publicationdate-add.xsl | 95 +++++++++++ .../process/publicationdate-remove.xsl | 47 ++++++ .../PublicationDateAddProcessTest.java | 153 +++++++++++++++++ .../PublicationDateRemoveProcessTest.java | 72 ++++++++ .../fao/geonet/schemas/XslProcessTest.java | 2 +- ...MetadataRemovePublicationDateListener.java | 72 ++++++++ ...MetadataUpdatePublicationDateListener.java | 73 +++++++++ .../MetadataUpdatePublicationDateService.java | 91 +++++++++++ .../resources/config-spring-geonetwork.xml | 4 + ...dataRemovePublicationDateListenerTest.java | 149 +++++++++++++++++ ...dataUpdatePublicationDateListenerTest.java | 150 +++++++++++++++++ ...PublicationDateServiceIntegrationTest.java | 118 ++++++++++++++ .../resources/catalog/locales/en-admin.json | 2 + .../setup/sql/data/data-db-default.sql | 5 +- .../sql/migrate/v4412/migrate-default.sql | 3 + 21 files changed, 1433 insertions(+), 4 deletions(-) create mode 100644 schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl create mode 100644 schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-remove.xsl create mode 100644 schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java create mode 100644 schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java create mode 100644 schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-add.xsl create mode 100644 schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-remove.xsl create mode 100644 schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java create mode 100644 schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java create mode 100644 services/src/main/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListener.java create mode 100644 services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListener.java create mode 100644 services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java create mode 100644 services/src/test/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListenerTest.java create mode 100644 services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListenerTest.java create mode 100644 services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java diff --git a/core/src/main/java/org/fao/geonet/kernel/setting/Settings.java b/core/src/main/java/org/fao/geonet/kernel/setting/Settings.java index 9e65245dacff..8783336d74aa 100644 --- a/core/src/main/java/org/fao/geonet/kernel/setting/Settings.java +++ b/core/src/main/java/org/fao/geonet/kernel/setting/Settings.java @@ -108,6 +108,7 @@ public class Settings { public static final String SYSTEM_HARVESTER_DISABLED_HARVESTER_TYPES = "system/harvester/disabledHarvesterTypes"; public static final String SYSTEM_METADATAPRIVS_USERGROUPONLY = "system/metadataprivs/usergrouponly"; public static final String SYSTEM_METADATAPRIVS_PUBLICATIONBYGROUPOWNERONLY = "system/metadataprivs/publicationbyrevieweringroupowneronly"; + public static final String SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE = "system/metadataprivs/publication/managepublicationdate"; public static final String SYSTEM_METADATAPRIVS_PUBLICATIONNOTIFICATION_EMAILS = "system/metadataprivs/publication/notificationEmails"; public static final String SYSTEM_METADATAPRIVS_PUBLICATION_NOTIFICATIONLEVEL = "system/metadataprivs/publication/notificationLevel"; public static final String SYSTEM_METADATAPRIVS_PUBLICATION_NOTIFICATIONGROUPS = "system/metadataprivs/publication/notificationGroups"; diff --git a/docs/manual/docs/administrator-guide/configuring-the-catalog/system-configuration.md b/docs/manual/docs/administrator-guide/configuring-the-catalog/system-configuration.md index b731bd6f15ae..582ed3e7aca8 100644 --- a/docs/manual/docs/administrator-guide/configuring-the-catalog/system-configuration.md +++ b/docs/manual/docs/administrator-guide/configuring-the-catalog/system-configuration.md @@ -248,7 +248,11 @@ Note: this option is only available for databases that have been tested. Those d ## Metadata Privileges -*Only set privileges to user's groups*: If enabled then only the groups that the user belongs to will be displayed in the metadata privileges page (unless the user is an Administrator). At the moment this option cannot be disabled and is likely to be deprecated in the next version of GeoNetwork. +- **Only set privileges to user's groups**: If enabled then only the groups that the user belongs to will be displayed in the metadata privileges page (unless the user is an Administrator). +- **Publication by users reviewer in record group only**: Allow publication by administrator and reviewer member of record group. If disabled, then also all users reviewer in group with editing rights can publish/unpublish a record. +- **Manage the publication date automatically**: When enabled the publication date of the metadata is set automatically when the metadata is published and removed when the metadata is unpublished. +- **Notification level when a metadata is published / unpublished**: Define which users to alert when a metadata is published / unpublished. +- **Groups to notify when a metadata is published / unpublished**: List of groups, separated by the char |, to notify when a metadata is published / unpublished (for 'Notify the group(s) emails' notification level). ## Metadata create diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl new file mode 100644 index 000000000000..421d17ba675e --- /dev/null +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-remove.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-remove.xsl new file mode 100644 index 000000000000..51d9473da438 --- /dev/null +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-remove.xsl @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + diff --git a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java new file mode 100644 index 000000000000..7e307d816df2 --- /dev/null +++ b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +package org.fao.geonet.schemas; + +import org.fao.geonet.schema.iso19115_3_2018.ISO19115_3_2018SchemaPlugin; +import org.fao.geonet.utils.Xml; +import org.jdom.Element; +import org.jdom.Namespace; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath; + +public class PublicationDateAddProcessTest extends XslProcessTest { + + public PublicationDateAddProcessTest() { + super(); + this.setXslFilename("process/publicationdate-add.xsl"); + this.setXmlFilename("metadata.xml"); + this.setNs(ISO19115_3_2018SchemaPlugin.allNamespaces); + } + + @Test + public void mustNotAlterARecordWhenNoParameterProvided() throws Exception { + super.testMustNotAlterARecordWhenNoParameterProvided(); + } + + @Test + public void testAddPublicationDate() throws Exception { + Element inputElement = Xml.loadFile(xmlFile); + String inputString = Xml.getString(inputElement); + + // Check no publication date exists + assertThat( + inputString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(ns) + ); + + // Add publication date + Map params = new HashMap<>(); + String newDate = "2026-07-02T09:23:00"; + params.put("publicationDate", newDate); + + Element resultElement = Xml.transform(inputElement, xslFile, params); + String resultString = Xml.getString(resultElement); + + assertThat( + resultString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + assertThat( + resultString, hasXPath("//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + ); + } + + @Test + public void testReplacePublicationDate() throws Exception { + Element inputElement = Xml.loadFile(xmlFile); + + // 1. Add a publication date first + Map params = new HashMap<>(); + params.put("publicationDate", "2020-01-01T00:00:00"); + Element midElement = Xml.transform(inputElement, xslFile, params); + String midString = Xml.getString(midElement); + + assertThat( + midString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + + // 2. Replace it with a new one + String newDate = "2026-07-02T09:23:00"; + params.put("publicationDate", newDate); + Element resultElement = Xml.transform(midElement, xslFile, params); + String resultString = Xml.getString(resultElement); + + assertThat( + resultString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + assertThat( + resultString, hasXPath("//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + ); + } + + @Test + public void testReplaceMultiplePublicationDates() throws Exception { + // Create an input with 2 publication dates + Element inputElement = Xml.loadFile(xmlFile); + + Map params = new HashMap<>(); + params.put("publicationDate", "2020-01-01T00:00:00"); + Element midElement = Xml.transform(inputElement, xslFile, params); + + // Add another one manually + Element citation = Xml.selectElement(midElement, "mdb:identificationInfo/*/mri:citation/cit:CI_Citation", new ArrayList<>(ISO19115_3_2018SchemaPlugin.allNamespaces)); + Namespace citNs = citation.getNamespace(); + Element date2 = new Element("date", citNs); + Element ciDate2 = new Element("CI_Date", citNs); + Element dateVal2 = new Element("date", citNs); + Element dateTime2 = new Element("DateTime", Namespace.getNamespace("gco", ns.get("gco"))); + dateTime2.setText("2021-01-01T00:00:00"); + dateVal2.addContent(dateTime2); + ciDate2.addContent(dateVal2); + Element dateType2 = new Element("dateType", citNs); + Element dateTypeCode2 = new Element("CI_DateTypeCode", citNs); + dateTypeCode2.setAttribute("codeListValue", "publication"); + dateTypeCode2.setAttribute("codeList", "someLocation"); + dateType2.addContent(dateTypeCode2); + ciDate2.addContent(dateType2); + date2.addContent(ciDate2); + citation.addContent(date2); + + String midStringWithTwo = Xml.getString(midElement); + assertThat( + midStringWithTwo, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("2")).withNamespaceContext(ns) + ); + + // Now run the process + String newDate = "2026-07-02T09:23:00"; + params.put("publicationDate", newDate); + Element resultElement = Xml.transform(midElement, xslFile, params); + String resultString = Xml.getString(resultElement); + + assertThat( + resultString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + assertThat( + resultString, hasXPath("//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + ); + } +} diff --git a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java new file mode 100644 index 000000000000..c6e0b9116bae --- /dev/null +++ b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +package org.fao.geonet.schemas; + +import org.fao.geonet.schema.iso19115_3_2018.ISO19115_3_2018SchemaPlugin; +import org.fao.geonet.utils.Xml; +import org.jdom.Element; +import org.jdom.Namespace; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath; + +public class PublicationDateRemoveProcessTest extends XslProcessTest { + + public PublicationDateRemoveProcessTest() { + super(); + this.setXslFilename("process/publicationdate-remove.xsl"); + this.setXmlFilename("metadata.xml"); + this.setNs(ISO19115_3_2018SchemaPlugin.allNamespaces); + } + + @Test + public void testRemovePublicationDate() throws Exception { + Element inputElement = Xml.loadFile(xmlFile); + + Namespace cit = Namespace.getNamespace("cit", "http://standards.iso.org/iso/19115/-3/cit/2.0"); + Namespace gco = Namespace.getNamespace("gco", "http://standards.iso.org/iso/19115/-3/gco/1.0"); + Namespace mdb = Namespace.getNamespace("mdb", "http://standards.iso.org/iso/19115/-3/mdb/2.0"); + Namespace mri = Namespace.getNamespace("mri", "http://standards.iso.org/iso/19115/-3/mri/1.0"); + + Element identificationInfo = inputElement.getChild("identificationInfo", mdb); + Element dataIdentification = identificationInfo.getChild("MD_DataIdentification", mri); + Element citationEl = dataIdentification.getChild("citation", mri); + Element ciCitation = citationEl.getChild("CI_Citation", cit); + + Element date = new Element("date", cit); + Element ciDate = new Element("CI_Date", cit); + Element dateEl = new Element("date", cit); + Element dateTime = new Element("DateTime", gco).setText("2026-07-02T10:00:00"); + Element dateType = new Element("dateType", cit); + Element ciDateTypeCode = new Element("CI_DateTypeCode", cit) + .setAttribute("codeList", "codeListLocation#CI_DateTypeCode") + .setAttribute("codeListValue", "publication"); + + dateEl.addContent(dateTime); + dateType.addContent(ciDateTypeCode); + ciDate.addContent(dateEl); + ciDate.addContent(dateType); + date.addContent(ciDate); + ciCitation.addContent(date); + + String midString = Xml.getString(inputElement); + + Map xslNs = new HashMap<>(); + xslNs.put("cit", "http://standards.iso.org/iso/19115/-3/cit/2.0"); + + assertThat( + midString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(xslNs) + ); + + // 2. Now remove it + Element resultElement = Xml.transform(inputElement, xslFile); + String resultString = Xml.getString(resultElement); + + assertThat( + resultString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(xslNs) + ); + } +} diff --git a/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-add.xsl b/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-add.xsl new file mode 100644 index 000000000000..13dd2e2aa550 --- /dev/null +++ b/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-add.xsl @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-remove.xsl b/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-remove.xsl new file mode 100644 index 000000000000..50e2020dd275 --- /dev/null +++ b/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-remove.xsl @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + diff --git a/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java b/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java new file mode 100644 index 000000000000..09742d784065 --- /dev/null +++ b/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +package org.fao.geonet.schemas; + +import org.fao.geonet.schema.iso19139.ISO19139SchemaPlugin; +import org.fao.geonet.utils.Xml; +import org.jdom.Element; +import org.jdom.Namespace; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath; + +public class PublicationDateAddProcessTest extends XslProcessTest { + + public PublicationDateAddProcessTest() { + super(); + this.setXslFilename("process/publicationdate-add.xsl"); + this.setXmlFilename("schemas/xsl/process/input.xml"); + this.setNs(ISO19139SchemaPlugin.allNamespaces); + } + + @Test + public void mustNotAlterARecordWhenNoParameterProvided() throws Exception { + super.testMustNotAlterARecordWhenNoParameterProvided(); + } + + @Test + public void testAddPublicationDate() throws Exception { + Element inputElement = Xml.loadFile(xmlFile); + String inputString = Xml.getString(inputElement); + + // Check no publication date exists + assertThat( + inputString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(ns) + ); + + // Add publication date + Map params = new HashMap<>(); + String newDate = "2026-07-02T09:23:00"; + params.put("publicationDate", newDate); + + Element resultElement = Xml.transform(inputElement, xslFile, params); + String resultString = Xml.getString(resultElement); + + assertThat( + resultString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + assertThat( + resultString, hasXPath("//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + ); + } + + @Test + public void testReplacePublicationDate() throws Exception { + Element inputElement = Xml.loadFile(xmlFile); + + // 1. Add a publication date first + Map params = new HashMap<>(); + params.put("publicationDate", "2020-01-01T00:00:00"); + Element midElement = Xml.transform(inputElement, xslFile, params); + String midString = Xml.getString(midElement); + + assertThat( + midString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + + // 2. Replace it with a new one + String newDate = "2026-07-02T09:23:00"; + params.put("publicationDate", newDate); + Element resultElement = Xml.transform(midElement, xslFile, params); + String resultString = Xml.getString(resultElement); + + assertThat( + resultString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + assertThat( + resultString, hasXPath("//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + ); + } + + @Test + public void testReplaceMultiplePublicationDates() throws Exception { + // Create an input with 2 publication dates + Element inputElement = Xml.loadFile(xmlFile); + + Map params = new HashMap<>(); + params.put("publicationDate", "2020-01-01T00:00:00"); + Element midElement = Xml.transform(inputElement, xslFile, params); + + // Add another one manually + Element citation = Xml.selectElement(midElement, "gmd:identificationInfo/*/gmd:citation/gmd:CI_Citation", new ArrayList<>(ISO19139SchemaPlugin.allNamespaces)); + Element date2 = new Element("date", citation.getNamespace()); + Element ciDate2 = new Element("CI_Date", citation.getNamespace()); + Element dateVal2 = new Element("date", citation.getNamespace()); + Element dateTime2 = new Element("DateTime", Namespace.getNamespace("gco", ns.get("gco"))); + dateTime2.setText("2021-01-01T00:00:00"); + dateVal2.addContent(dateTime2); + ciDate2.addContent(dateVal2); + Element dateType2 = new Element("dateType", citation.getNamespace()); + Element dateTypeCode2 = new Element("CI_DateTypeCode", citation.getNamespace()); + dateTypeCode2.setAttribute("codeListValue", "publication"); + dateTypeCode2.setAttribute("codeList", "someLocation"); + dateType2.addContent(dateTypeCode2); + ciDate2.addContent(dateType2); + date2.addContent(ciDate2); + citation.addContent(date2); + + String midStringWithTwo = Xml.getString(midElement); + assertThat( + midStringWithTwo, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("2")).withNamespaceContext(ns) + ); + + // Now run the process + String newDate = "2026-07-02T09:23:00"; + params.put("publicationDate", newDate); + Element resultElement = Xml.transform(midElement, xslFile, params); + String resultString = Xml.getString(resultElement); + + assertThat( + resultString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + assertThat( + resultString, hasXPath("//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + ); + } +} diff --git a/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java b/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java new file mode 100644 index 000000000000..f2973a8df278 --- /dev/null +++ b/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +package org.fao.geonet.schemas; + +import org.fao.geonet.schema.iso19139.ISO19139SchemaPlugin; +import org.fao.geonet.utils.Xml; +import org.jdom.Element; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath; + +public class PublicationDateRemoveProcessTest extends XslProcessTest { + + public PublicationDateRemoveProcessTest() { + super(); + this.setXslFilename("process/publicationdate-remove.xsl"); + this.setXmlFilename("schemas/xsl/process/input.xml"); + this.setNs(ISO19139SchemaPlugin.allNamespaces); + } + + @Test + public void testRemovePublicationDate() throws Exception { + Element inputElement = Xml.loadFile(xmlFile); + + // 1. Add a publication date first using the add process (we assume it works as it has its own tests) + // Or just manually check it doesn't exist and add it if we want to be sure. + // For this test, let's just use the add XSL to prepare the input. + java.nio.file.Path addXsl = xslFile.getParent().resolve("publicationdate-add.xsl"); + Map params = new HashMap<>(); + params.put("publicationDate", "2026-07-02T10:00:00"); + Element midElement = Xml.transform(inputElement, addXsl, params); + + String midString = Xml.getString(midElement); + assertThat( + midString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + + // 2. Now remove it + Element resultElement = Xml.transform(midElement, xslFile); + String resultString = Xml.getString(resultElement); + + assertThat( + resultString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(ns) + ); + } +} diff --git a/schemas/schema-core/src/test/java/org/fao/geonet/schemas/XslProcessTest.java b/schemas/schema-core/src/test/java/org/fao/geonet/schemas/XslProcessTest.java index 03c02cc421da..c9dbc7824c35 100644 --- a/schemas/schema-core/src/test/java/org/fao/geonet/schemas/XslProcessTest.java +++ b/schemas/schema-core/src/test/java/org/fao/geonet/schemas/XslProcessTest.java @@ -56,7 +56,7 @@ public abstract class XslProcessTest { - protected Map ns = new HashMap(); + protected Map ns = new HashMap<>(); public Map getNs() { return ns; } diff --git a/services/src/main/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListener.java b/services/src/main/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListener.java new file mode 100644 index 000000000000..b4208b88cf72 --- /dev/null +++ b/services/src/main/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListener.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +package org.fao.geonet.api.records.events; + +import org.fao.geonet.domain.AbstractMetadata; +import org.fao.geonet.domain.Metadata; +import org.fao.geonet.domain.MetadataType; +import org.fao.geonet.events.md.MetadataUnpublished; +import org.fao.geonet.kernel.datamanager.IMetadataUtils; +import org.fao.geonet.kernel.setting.SettingManager; +import org.fao.geonet.kernel.setting.Settings; +import org.fao.geonet.utils.Log; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; + +public class MetadataRemovePublicationDateListener implements ApplicationListener { + + @Autowired + private MetadataUpdatePublicationDateService metadataUpdatePublicationDateService; + + @Autowired + private SettingManager settingManager; + + @Autowired + private IMetadataUtils metadataUtils; + + @Override + public synchronized void onApplicationEvent(MetadataUnpublished event) { + if (!settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)) { + return; + } + + AbstractMetadata md = event.getMd(); + // On republication (approval workflow), the metadata received in the event is the draft + // version. The publication date must be removed from the approved record, not the draft. + if (!(md instanceof Metadata)) { + md = metadataUtils.findOneByUuid(md.getUuid()); + } + if (md == null || md.getDataInfo().getType() != MetadataType.METADATA) { + return; + } + + try { + metadataUpdatePublicationDateService.removePublicationDate(md); + } catch (Exception e) { + Log.error("org.fao.geonet.services.metadata", "Error removing publication date of metadata " + md.getId(), e); + } + } + +} + diff --git a/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListener.java b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListener.java new file mode 100644 index 000000000000..78ef9450aec8 --- /dev/null +++ b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListener.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +package org.fao.geonet.api.records.events; + +import org.fao.geonet.domain.AbstractMetadata; +import org.fao.geonet.domain.ISODate; +import org.fao.geonet.domain.Metadata; +import org.fao.geonet.domain.MetadataType; +import org.fao.geonet.events.md.MetadataPublished; +import org.fao.geonet.kernel.datamanager.IMetadataUtils; +import org.fao.geonet.kernel.setting.SettingManager; +import org.fao.geonet.kernel.setting.Settings; +import org.fao.geonet.utils.Log; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; + +public class MetadataUpdatePublicationDateListener implements ApplicationListener { + + @Autowired + private MetadataUpdatePublicationDateService metadataUpdatePublicationDateService; + + @Autowired + private SettingManager settingManager; + + @Autowired + private IMetadataUtils metadataUtils; + + @Override + public synchronized void onApplicationEvent(MetadataPublished event) { + if (!settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)) { + return; + } + + AbstractMetadata md = event.getMd(); + // On republication (approval workflow), the metadata received in the event is the draft + // version. The publication date must be set on the approved record, not the draft. + if (!(md instanceof Metadata)) { + md = metadataUtils.findOneByUuid(md.getUuid()); + } + if (md == null || md.getDataInfo().getType() != MetadataType.METADATA) { + return; + } + + try { + metadataUpdatePublicationDateService.addPublicationDate(md, new ISODate()); + } catch (Exception e) { + Log.error("org.fao.geonet.services.metadata", "Error setting publication date of metadata " + md.getId(), e); + } + } + +} + diff --git a/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java new file mode 100644 index 000000000000..bcd1d469a59b --- /dev/null +++ b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +package org.fao.geonet.api.records.events; + +import jeeves.server.context.ServiceContext; +import org.fao.geonet.api.processing.XslProcessUtils; +import org.fao.geonet.api.processing.report.Report; +import org.fao.geonet.api.processing.report.XsltMetadataProcessingReport; +import org.fao.geonet.domain.AbstractMetadata; +import org.fao.geonet.domain.ISODate; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class MetadataUpdatePublicationDateService { + public void addPublicationDate(AbstractMetadata md, ISODate publicationDate) { + + ServiceContext context = ServiceContext.get(); + + String process = "publicationdate-add"; + XsltMetadataProcessingReport report = + new XsltMetadataProcessingReport(process); + + Map params = new HashMap<>(); + params.put("publicationDate", new String[]{publicationDate.toString()}); + try { + XslProcessUtils.process(context, String.valueOf(md.getId()), process, true, true, true, report, "", params); + } catch (Exception e) { + throw new RuntimeException(e); + } + checkForErrors(report, md); + } + + public void removePublicationDate(AbstractMetadata md) { + + ServiceContext context = ServiceContext.get(); + + String process = "publicationdate-remove"; + XsltMetadataProcessingReport report = + new XsltMetadataProcessingReport(process); + + Map params = new HashMap<>(); + + try { + XslProcessUtils.process(context, String.valueOf(md.getId()), process, true, true, true, report, "", params); + } catch (Exception e) { + throw new RuntimeException(e); + } + checkForErrors(report, md); + } + + /** + * XslProcessUtils.process() catches its own processing exceptions and records them on the + * report instead of throwing, so a failure to set/remove the publication date would otherwise + * pass completely unnoticed. Surface it here so the caller can log/handle it. + */ + private void checkForErrors(XsltMetadataProcessingReport report, AbstractMetadata md) { + if (report.getNumberOfRecordsWithErrors() > 0) { + List errors = report.getMetadataErrors().values().stream() + .flatMap(List::stream).collect(Collectors.toList()); + String messages = errors.stream().map(Report::getMessage).collect(Collectors.joining("; ")); + throw new RuntimeException("Failed to process publication date XSL for metadata " + + md.getId() + ": " + messages); + } + } +} diff --git a/services/src/main/resources/config-spring-geonetwork.xml b/services/src/main/resources/config-spring-geonetwork.xml index 830a19585067..2d61df66bbf5 100644 --- a/services/src/main/resources/config-spring-geonetwork.xml +++ b/services/src/main/resources/config-spring-geonetwork.xml @@ -123,6 +123,10 @@ class="org.fao.geonet.api.records.formatters.cache.FormatterCacheDeletionListener"/> + + diff --git a/services/src/test/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListenerTest.java b/services/src/test/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListenerTest.java new file mode 100644 index 000000000000..a93a8d1ba50b --- /dev/null +++ b/services/src/test/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListenerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ +package org.fao.geonet.api.records.events; + +import org.fao.geonet.domain.Metadata; +import org.fao.geonet.domain.MetadataDataInfo; +import org.fao.geonet.domain.MetadataDraft; +import org.fao.geonet.domain.MetadataType; +import org.fao.geonet.events.md.MetadataUnpublished; +import org.fao.geonet.kernel.datamanager.IMetadataUtils; +import org.fao.geonet.kernel.setting.SettingManager; +import org.fao.geonet.kernel.setting.Settings; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class MetadataRemovePublicationDateListenerTest { + + private MetadataRemovePublicationDateListener listener; + private MetadataUpdatePublicationDateService service; + private SettingManager settingManager; + private IMetadataUtils metadataUtils; + + @Before + public void setUp() throws Exception { + listener = new MetadataRemovePublicationDateListener(); + service = mock(MetadataUpdatePublicationDateService.class); + settingManager = mock(SettingManager.class); + metadataUtils = mock(IMetadataUtils.class); + + setField("metadataUpdatePublicationDateService", service); + setField("settingManager", settingManager); + setField("metadataUtils", metadataUtils); + } + + private void setField(String name, Object value) throws Exception { + Field field = MetadataRemovePublicationDateListener.class.getDeclaredField(name); + field.setAccessible(true); + field.set(listener, value); + } + + private Metadata metadataWithType(MetadataType type, int id, String uuid) { + Metadata md = mock(Metadata.class); + MetadataDataInfo dataInfo = mock(MetadataDataInfo.class); + when(dataInfo.getType()).thenReturn(type); + when(md.getDataInfo()).thenReturn(dataInfo); + when(md.getId()).thenReturn(id); + when(md.getUuid()).thenReturn(uuid); + return md; + } + + @Test + public void doesNothingWhenSettingDisabled() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(false); + + Metadata md = metadataWithType(MetadataType.METADATA, 1, "uuid-1"); + listener.onApplicationEvent(new MetadataUnpublished(md)); + + verify(service, never()).removePublicationDate(any()); + } + + @Test + public void doesNothingForTemplates() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + Metadata md = metadataWithType(MetadataType.TEMPLATE, 1, "uuid-1"); + listener.onApplicationEvent(new MetadataUnpublished(md)); + + verify(service, never()).removePublicationDate(any()); + } + + @Test + public void removesPublicationDateForNormalMetadata() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + Metadata md = metadataWithType(MetadataType.METADATA, 1, "uuid-1"); + listener.onApplicationEvent(new MetadataUnpublished(md)); + + verify(service).removePublicationDate(eq(md)); + } + + @Test + public void resolvesApprovedRecordWhenEventCarriesADraft() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + MetadataDraft draft = mock(MetadataDraft.class); + when(draft.getUuid()).thenReturn("uuid-1"); + + Metadata approved = metadataWithType(MetadataType.METADATA, 42, "uuid-1"); + when(metadataUtils.findOneByUuid("uuid-1")).thenReturn(approved); + + listener.onApplicationEvent(new MetadataUnpublished(draft)); + + // The approved record must be used, not the draft itself. + verify(service).removePublicationDate(eq(approved)); + } + + @Test + public void doesNothingWhenDraftCannotBeResolved() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + MetadataDraft draft = mock(MetadataDraft.class); + when(draft.getUuid()).thenReturn("uuid-1"); + when(metadataUtils.findOneByUuid("uuid-1")).thenReturn(null); + + listener.onApplicationEvent(new MetadataUnpublished(draft)); + + verify(service, never()).removePublicationDate(any()); + } + + @Test + public void serviceFailureDoesNotPropagate() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + Metadata md = metadataWithType(MetadataType.METADATA, 1, "uuid-1"); + org.mockito.Mockito.doThrow(new RuntimeException("boom")).when(service).removePublicationDate(any()); + + // Must not throw: a failure removing the publication date must not break the unpublish action. + listener.onApplicationEvent(new MetadataUnpublished(md)); + } +} diff --git a/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListenerTest.java b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListenerTest.java new file mode 100644 index 000000000000..af20570032cd --- /dev/null +++ b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListenerTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ +package org.fao.geonet.api.records.events; + +import org.fao.geonet.domain.ISODate; +import org.fao.geonet.domain.Metadata; +import org.fao.geonet.domain.MetadataDataInfo; +import org.fao.geonet.domain.MetadataDraft; +import org.fao.geonet.domain.MetadataType; +import org.fao.geonet.events.md.MetadataPublished; +import org.fao.geonet.kernel.datamanager.IMetadataUtils; +import org.fao.geonet.kernel.setting.SettingManager; +import org.fao.geonet.kernel.setting.Settings; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class MetadataUpdatePublicationDateListenerTest { + + private MetadataUpdatePublicationDateListener listener; + private MetadataUpdatePublicationDateService service; + private SettingManager settingManager; + private IMetadataUtils metadataUtils; + + @Before + public void setUp() throws Exception { + listener = new MetadataUpdatePublicationDateListener(); + service = mock(MetadataUpdatePublicationDateService.class); + settingManager = mock(SettingManager.class); + metadataUtils = mock(IMetadataUtils.class); + + setField("metadataUpdatePublicationDateService", service); + setField("settingManager", settingManager); + setField("metadataUtils", metadataUtils); + } + + private void setField(String name, Object value) throws Exception { + Field field = MetadataUpdatePublicationDateListener.class.getDeclaredField(name); + field.setAccessible(true); + field.set(listener, value); + } + + private Metadata metadataWithType(MetadataType type, int id, String uuid) { + Metadata md = mock(Metadata.class); + MetadataDataInfo dataInfo = mock(MetadataDataInfo.class); + when(dataInfo.getType()).thenReturn(type); + when(md.getDataInfo()).thenReturn(dataInfo); + when(md.getId()).thenReturn(id); + when(md.getUuid()).thenReturn(uuid); + return md; + } + + @Test + public void doesNothingWhenSettingDisabled() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(false); + + Metadata md = metadataWithType(MetadataType.METADATA, 1, "uuid-1"); + listener.onApplicationEvent(new MetadataPublished(md)); + + verify(service, never()).addPublicationDate(any(), any()); + } + + @Test + public void doesNothingForTemplates() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + Metadata md = metadataWithType(MetadataType.TEMPLATE, 1, "uuid-1"); + listener.onApplicationEvent(new MetadataPublished(md)); + + verify(service, never()).addPublicationDate(any(), any()); + } + + @Test + public void setsPublicationDateForNormalMetadata() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + Metadata md = metadataWithType(MetadataType.METADATA, 1, "uuid-1"); + listener.onApplicationEvent(new MetadataPublished(md)); + + verify(service).addPublicationDate(eq(md), any(ISODate.class)); + } + + @Test + public void resolvesApprovedRecordWhenEventCarriesADraft() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + MetadataDraft draft = mock(MetadataDraft.class); + when(draft.getUuid()).thenReturn("uuid-1"); + + Metadata approved = metadataWithType(MetadataType.METADATA, 42, "uuid-1"); + when(metadataUtils.findOneByUuid("uuid-1")).thenReturn(approved); + + listener.onApplicationEvent(new MetadataPublished(draft)); + + // The approved record must be used, not the draft itself. + verify(service).addPublicationDate(eq(approved), any(ISODate.class)); + } + + @Test + public void doesNothingWhenDraftCannotBeResolved() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + MetadataDraft draft = mock(MetadataDraft.class); + when(draft.getUuid()).thenReturn("uuid-1"); + when(metadataUtils.findOneByUuid("uuid-1")).thenReturn(null); + + listener.onApplicationEvent(new MetadataPublished(draft)); + + verify(service, never()).addPublicationDate(any(), any()); + } + + @Test + public void serviceFailureDoesNotPropagate() { + when(settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)).thenReturn(true); + + Metadata md = metadataWithType(MetadataType.METADATA, 1, "uuid-1"); + org.mockito.Mockito.doThrow(new RuntimeException("boom")).when(service).addPublicationDate(any(), any()); + + // Must not throw: a failure updating the publication date must not break the publish action. + listener.onApplicationEvent(new MetadataPublished(md)); + } +} diff --git a/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java new file mode 100644 index 000000000000..fff92ba2a755 --- /dev/null +++ b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2001-2026 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +package org.fao.geonet.api.records.events; + +import jeeves.server.context.ServiceContext; +import org.fao.geonet.domain.AbstractMetadata; +import org.fao.geonet.domain.ISODate; +import org.fao.geonet.kernel.datamanager.IMetadataUtils; +import org.fao.geonet.schema.iso19139.ISO19139SchemaPlugin; +import org.fao.geonet.services.AbstractServiceIntegrationTest; +import org.fao.geonet.utils.Xml; +import org.jdom.Element; +import org.jdom.Namespace; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class MetadataUpdatePublicationDateServiceIntegrationTest extends AbstractServiceIntegrationTest { + + @Autowired + private MetadataUpdatePublicationDateService service; + + @Autowired + private IMetadataUtils repository; + + @PersistenceContext + private EntityManager entityManager; + + private ServiceContext context; + private AbstractMetadata metadata; + private List ns = new ArrayList<>(); + + @Before + public void setUp() throws Exception { + context = createServiceContext(); + loginAsAdmin(context); + + ns.addAll(ISO19139SchemaPlugin.allNamespaces); + + Element sample = getSampleISO19139MetadataXml(); + metadata = injectMetadataInDb(sample, context, true); + } + + @Test + public void testAddPublicationDate() throws Exception { + AbstractMetadata metadataRecord = repository.findOne(metadata.getId()); + Element initialXml = metadataRecord.getXmlData(false); + + // Ensure no publication date exists + assertEquals(0, Xml.selectNodes(initialXml, "//gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); + + ISODate pubDate = new ISODate("2026-07-02T10:00:00"); + service.addPublicationDate(metadataRecord, pubDate); + entityManager.flush(); + entityManager.clear(); + + AbstractMetadata updatedRecord = repository.findOne(metadata.getId()); + Element updatedXml = updatedRecord.getXmlData(false); + + assertEquals(1, Xml.selectNodes(updatedXml, "gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); + Element dateText = (Element) Xml.selectSingle(updatedXml, "gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime", ns); + assertNotNull(dateText); + assertEquals(pubDate.toString(), dateText.getText()); + } + + @Test + public void testRemovePublicationDate() throws Exception { + AbstractMetadata metadataRecord = repository.findOne(metadata.getId()); + + // 1. Add a publication date first + ISODate pubDate = new ISODate("2026-07-02T10:00:00"); + service.addPublicationDate(metadataRecord, pubDate); + entityManager.flush(); + entityManager.clear(); + + AbstractMetadata recordWithPubDate = repository.findOne(metadata.getId()); + assertEquals(1, Xml.selectNodes(recordWithPubDate.getXmlData(false), "gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); + + // 2. Remove it + service.removePublicationDate(recordWithPubDate); + entityManager.flush(); + entityManager.clear(); + + AbstractMetadata updatedRecord = repository.findOne(metadata.getId()); + Element updatedXml = updatedRecord.getXmlData(false); + + assertEquals(0, Xml.selectNodes(updatedXml, "gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); + } +} diff --git a/web-ui/src/main/resources/catalog/locales/en-admin.json b/web-ui/src/main/resources/catalog/locales/en-admin.json index f8b8161e45dd..2dbed32b793f 100644 --- a/web-ui/src/main/resources/catalog/locales/en-admin.json +++ b/web-ui/src/main/resources/catalog/locales/en-admin.json @@ -796,6 +796,8 @@ "system/metadataprivs/usergrouponly": "Only set privileges to user's groups", "system/metadataprivs/publicationbyrevieweringroupowneronly": "Publication by users reviewer in record group only", "system/metadataprivs/publicationbyrevieweringroupowneronly-help": "Allow publication by administrator and reviewer member of record group. If false, then also all users reviewer in group with editing rights can publish/unpublish a record.", + "system/metadataprivs/publication/managepublicationdate": "Manage the publication date automatically", + "system/metadataprivs/publication/managepublicationdate-help": "When enabled the publication date of the metadata is set automatically when the metadata is published and removed when the metadata is unpublished", "system/metadataprivs/publication/notificationLevel": "Notification level when a metadata is published / unpublished", "system/metadataprivs/publication/notificationLevel-help": "Define which users to alert when a metadata is published / unpublished", "system/metadataprivs/publication/notificationGroups": "Groups to notify when a metadata is published / unpublished", diff --git a/web/src/main/webapp/WEB-INF/classes/setup/sql/data/data-db-default.sql b/web/src/main/webapp/WEB-INF/classes/setup/sql/data/data-db-default.sql index 663443fe0e58..1be2255a320d 100644 --- a/web/src/main/webapp/WEB-INF/classes/setup/sql/data/data-db-default.sql +++ b/web/src/main/webapp/WEB-INF/classes/setup/sql/data/data-db-default.sql @@ -667,8 +667,9 @@ INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/metadataprivs/usergrouponly', 'false', 2, 9180, 'n'); INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/metadataprivs/publicationbyrevieweringroupowneronly', 'true', 2, 9181, 'n'); -INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/metadataprivs/publication/notificationLevel', '', 0, 9182, 'n'); -INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/metadataprivs/publication/notificationGroups', '', 0, 9183, 'n'); +INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/metadataprivs/publication/managepublicationdate', 'false', 2, 9182, 'n'); +INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/metadataprivs/publication/notificationLevel', '', 0, 9183, 'n'); +INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/metadataprivs/publication/notificationGroups', '', 0, 9184, 'n'); INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/threadedindexing/maxthreads', '1', 1, 9210, 'y'); INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/inspire/remotevalidation/url', '', 0, 7211, 'n'); diff --git a/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4412/migrate-default.sql b/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4412/migrate-default.sql index 359852783823..d1a300a5ae77 100644 --- a/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4412/migrate-default.sql +++ b/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4412/migrate-default.sql @@ -1,4 +1,7 @@ INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/csw/getRecordsIgnoreMetadataNotSupported', 'true', 2, 1321, 'y'); +INSERT INTO Settings (name, value, datatype, position, internal) SELECT distinct 'system/metadataprivs/publication/managepublicationdate', 'false', 2, 9182, 'n' from settings WHERE NOT EXISTS (SELECT name FROM Settings WHERE name = 'system/metadataprivs/publication/managepublicationdate'); +UPDATE Settings SET position=9183 WHERE name='system/metadataprivs/publication/notificationLevel'; +UPDATE Settings SET position=9184 WHERE name='system/metadataprivs/publication/notificationGroups'; UPDATE Settings SET value='4.4.12' WHERE name='system/platform/version'; UPDATE Settings SET value='SNAPSHOT' WHERE name='system/platform/subVersion'; From 199b7a4b8f4a3dec8d0b6a8499d89c00f2e1f446 Mon Sep 17 00:00:00 2001 From: josegar74 Date: Fri, 17 Jul 2026 10:05:30 +0200 Subject: [PATCH 2/8] Set/unset metadata publication date when a metadata is published / unpublished - use metadata publication date instead of resource publication date. Removed support for ISO19139 as doesn't have a metadata publication date, gmd:dateStamp is used internally for the metadata update date. --- .../process/publicationdate-add.xsl | 48 +++--- .../process/publicationdate-remove.xsl | 2 +- .../PublicationDateAddProcessTest.java | 105 ++++++++---- .../PublicationDateRemoveProcessTest.java | 19 +-- .../iso19139/process/publicationdate-add.xsl | 95 ----------- .../process/publicationdate-remove.xsl | 47 ------ .../PublicationDateAddProcessTest.java | 153 ------------------ .../PublicationDateRemoveProcessTest.java | 72 --------- ...MetadataRemovePublicationDateListener.java | 2 +- ...MetadataUpdatePublicationDateListener.java | 2 +- .../MetadataUpdatePublicationDateService.java | 27 ++++ ...PublicationDateServiceIntegrationTest.java | 16 +- .../resources/catalog/locales/en-admin.json | 4 +- 13 files changed, 153 insertions(+), 439 deletions(-) delete mode 100644 schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-add.xsl delete mode 100644 schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-remove.xsl delete mode 100644 schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java delete mode 100644 schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl index 421d17ba675e..05280eac5fd7 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl @@ -27,7 +27,6 @@ Stylesheet used to add a publication date of a metadata record. --> - + - + @@ -56,27 +58,35 @@ Stylesheet used to add a publication date of a metadata record. - + - + - + + select="mdb:metadataStandard| + mdb:metadataProfile| + mdb:alternativeMetadataReference| + mdb:otherLocale| + mdb:metadataLinkage| + mdb:spatialRepresentationInfo| + mdb:referenceSystemInfo| + mdb:metadataExtensionInfo| + mdb:identificationInfo| + mdb:contentInfo| + mdb:distributionInfo| + mdb:dataQualityInfo| + mdb:resourceLineage| + mdb:portrayalCatalogueInfo| + mdb:metadataConstraints| + mdb:applicationSchemaInfo| + mdb:metadataMaintenance| + mdb:acquisitionInformation + "/> diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-remove.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-remove.xsl index 51d9473da438..618be7ff4e41 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-remove.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-remove.xsl @@ -33,7 +33,7 @@ Stylesheet used to remove a publication date of a metadata record. version="2.0" exclude-result-prefixes="#all"> - + diff --git a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java index 7e307d816df2..9073a494b4d6 100644 --- a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java +++ b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java @@ -29,7 +29,6 @@ import org.jdom.Namespace; import org.junit.Test; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -58,7 +57,7 @@ public void testAddPublicationDate() throws Exception { // Check no publication date exists assertThat( - inputString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(ns) + inputString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(ns) ); // Add publication date @@ -70,10 +69,10 @@ inputString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateType String resultString = Xml.getString(resultElement); assertThat( - resultString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) ); assertThat( - resultString, hasXPath("//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) ); } @@ -88,7 +87,7 @@ public void testReplacePublicationDate() throws Exception { String midString = Xml.getString(midElement); assertThat( - midString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + midString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) ); // 2. Replace it with a new one @@ -98,10 +97,10 @@ midString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCo String resultString = Xml.getString(resultElement); assertThat( - resultString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) ); assertThat( - resultString, hasXPath("//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) ); } @@ -114,28 +113,13 @@ public void testReplaceMultiplePublicationDates() throws Exception { params.put("publicationDate", "2020-01-01T00:00:00"); Element midElement = Xml.transform(inputElement, xslFile, params); - // Add another one manually - Element citation = Xml.selectElement(midElement, "mdb:identificationInfo/*/mri:citation/cit:CI_Citation", new ArrayList<>(ISO19115_3_2018SchemaPlugin.allNamespaces)); - Namespace citNs = citation.getNamespace(); - Element date2 = new Element("date", citNs); - Element ciDate2 = new Element("CI_Date", citNs); - Element dateVal2 = new Element("date", citNs); - Element dateTime2 = new Element("DateTime", Namespace.getNamespace("gco", ns.get("gco"))); - dateTime2.setText("2021-01-01T00:00:00"); - dateVal2.addContent(dateTime2); - ciDate2.addContent(dateVal2); - Element dateType2 = new Element("dateType", citNs); - Element dateTypeCode2 = new Element("CI_DateTypeCode", citNs); - dateTypeCode2.setAttribute("codeListValue", "publication"); - dateTypeCode2.setAttribute("codeList", "someLocation"); - dateType2.addContent(dateTypeCode2); - ciDate2.addContent(dateType2); - date2.addContent(ciDate2); - citation.addContent(date2); + // Add another publication date manually at metadata level (mdb:dateInfo). + // The transformed root element is mdb:MD_Metadata. + midElement.addContent(newDateInfo(midElement.getNamespace(), "publication", "2021-01-01T00:00:00")); String midStringWithTwo = Xml.getString(midElement); assertThat( - midStringWithTwo, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("2")).withNamespaceContext(ns) + midStringWithTwo, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("2")).withNamespaceContext(ns) ); // Now run the process @@ -145,10 +129,75 @@ midStringWithTwo, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_Dat String resultString = Xml.getString(resultElement); assertThat( - resultString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) ); assertThat( - resultString, hasXPath("//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) ); } + + /** + * Regression test: a metadata date whose CI_DateTypeCode has no codeListValue attribute + * must be preserved when a publication date is added. A naive {@code @codeListValue != 'publication'} + * predicate silently drops such dates (an absent attribute is not "!= 'publication'" in XPath), + * so this guards against that data loss. + */ + @Test + public void testPreservesDateWithoutCodeListValueWhenAddingPublicationDate() throws Exception { + Element inputElement = Xml.loadFile(xmlFile); + + // Add a metadata date with a CI_DateTypeCode that has no codeListValue attribute. + // The loaded root element is mdb:MD_Metadata. + inputElement.addContent(newDateInfo(inputElement.getNamespace(), null, "2019-05-05T00:00:00")); + + String inputString = Xml.getString(inputElement); + assertThat( + inputString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode[not(@codeListValue)]])", equalTo("1")).withNamespaceContext(ns) + ); + + // Add the publication date + Map params = new HashMap<>(); + params.put("publicationDate", "2026-07-02T09:23:00"); + Element resultElement = Xml.transform(inputElement, xslFile, params); + String resultString = Xml.getString(resultElement); + + // The publication date is added ... + assertThat( + resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) + ); + // ... and the date without a codeListValue is preserved (not dropped). + assertThat( + resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode[not(@codeListValue)]])", equalTo("1")).withNamespaceContext(ns) + ); + } + + /** + * Build an {@code mdb:dateInfo} element with the given date type code list value + * (omitted when {@code codeListValue} is null) and date time value. + */ + private Element newDateInfo(Namespace mdbNs, String codeListValue, String dateTime) { + Namespace citNs = Namespace.getNamespace("cit", ns.get("cit")); + Namespace gcoNs = Namespace.getNamespace("gco", ns.get("gco")); + + Element dateInfo = new Element("dateInfo", mdbNs); + Element ciDate = new Element("CI_Date", citNs); + + Element dateVal = new Element("date", citNs); + Element dateTimeEl = new Element("DateTime", gcoNs); + dateTimeEl.setText(dateTime); + dateVal.addContent(dateTimeEl); + ciDate.addContent(dateVal); + + Element dateType = new Element("dateType", citNs); + Element dateTypeCode = new Element("CI_DateTypeCode", citNs); + if (codeListValue != null) { + dateTypeCode.setAttribute("codeListValue", codeListValue); + dateTypeCode.setAttribute("codeList", "codeListLocation#CI_DateTypeCode"); + } + dateType.addContent(dateTypeCode); + ciDate.addContent(dateType); + + dateInfo.addContent(ciDate); + return dateInfo; + } } diff --git a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java index c6e0b9116bae..d33e5c78e939 100644 --- a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java +++ b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java @@ -51,15 +51,9 @@ public void testRemovePublicationDate() throws Exception { Namespace cit = Namespace.getNamespace("cit", "http://standards.iso.org/iso/19115/-3/cit/2.0"); Namespace gco = Namespace.getNamespace("gco", "http://standards.iso.org/iso/19115/-3/gco/1.0"); - Namespace mdb = Namespace.getNamespace("mdb", "http://standards.iso.org/iso/19115/-3/mdb/2.0"); - Namespace mri = Namespace.getNamespace("mri", "http://standards.iso.org/iso/19115/-3/mri/1.0"); - Element identificationInfo = inputElement.getChild("identificationInfo", mdb); - Element dataIdentification = identificationInfo.getChild("MD_DataIdentification", mri); - Element citationEl = dataIdentification.getChild("citation", mri); - Element ciCitation = citationEl.getChild("CI_Citation", cit); - - Element date = new Element("date", cit); + // Add a metadata-level publication date (mdb:dateInfo). The loaded root is mdb:MD_Metadata. + Element dateInfo = new Element("dateInfo", inputElement.getNamespace()); Element ciDate = new Element("CI_Date", cit); Element dateEl = new Element("date", cit); Element dateTime = new Element("DateTime", gco).setText("2026-07-02T10:00:00"); @@ -72,16 +66,17 @@ public void testRemovePublicationDate() throws Exception { dateType.addContent(ciDateTypeCode); ciDate.addContent(dateEl); ciDate.addContent(dateType); - date.addContent(ciDate); - ciCitation.addContent(date); + dateInfo.addContent(ciDate); + inputElement.addContent(dateInfo); String midString = Xml.getString(inputElement); Map xslNs = new HashMap<>(); + xslNs.put("mdb", "http://standards.iso.org/iso/19115/-3/mdb/2.0"); xslNs.put("cit", "http://standards.iso.org/iso/19115/-3/cit/2.0"); assertThat( - midString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(xslNs) + midString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(xslNs) ); // 2. Now remove it @@ -89,7 +84,7 @@ midString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCo String resultString = Xml.getString(resultElement); assertThat( - resultString, hasXPath("count(//cit:date[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(xslNs) + resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(xslNs) ); } } diff --git a/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-add.xsl b/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-add.xsl deleted file mode 100644 index 13dd2e2aa550..000000000000 --- a/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-add.xsl +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-remove.xsl b/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-remove.xsl deleted file mode 100644 index 50e2020dd275..000000000000 --- a/schemas/iso19139/src/main/plugin/iso19139/process/publicationdate-remove.xsl +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java b/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java deleted file mode 100644 index 09742d784065..000000000000 --- a/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2001-2026 Food and Agriculture Organization of the - * United Nations (FAO-UN), United Nations World Food Programme (WFP) - * and United Nations Environment Programme (UNEP) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - * - * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, - * Rome - Italy. email: geonetwork@osgeo.org - */ - -package org.fao.geonet.schemas; - -import org.fao.geonet.schema.iso19139.ISO19139SchemaPlugin; -import org.fao.geonet.utils.Xml; -import org.jdom.Element; -import org.jdom.Namespace; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath; - -public class PublicationDateAddProcessTest extends XslProcessTest { - - public PublicationDateAddProcessTest() { - super(); - this.setXslFilename("process/publicationdate-add.xsl"); - this.setXmlFilename("schemas/xsl/process/input.xml"); - this.setNs(ISO19139SchemaPlugin.allNamespaces); - } - - @Test - public void mustNotAlterARecordWhenNoParameterProvided() throws Exception { - super.testMustNotAlterARecordWhenNoParameterProvided(); - } - - @Test - public void testAddPublicationDate() throws Exception { - Element inputElement = Xml.loadFile(xmlFile); - String inputString = Xml.getString(inputElement); - - // Check no publication date exists - assertThat( - inputString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(ns) - ); - - // Add publication date - Map params = new HashMap<>(); - String newDate = "2026-07-02T09:23:00"; - params.put("publicationDate", newDate); - - Element resultElement = Xml.transform(inputElement, xslFile, params); - String resultString = Xml.getString(resultElement); - - assertThat( - resultString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) - ); - assertThat( - resultString, hasXPath("//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) - ); - } - - @Test - public void testReplacePublicationDate() throws Exception { - Element inputElement = Xml.loadFile(xmlFile); - - // 1. Add a publication date first - Map params = new HashMap<>(); - params.put("publicationDate", "2020-01-01T00:00:00"); - Element midElement = Xml.transform(inputElement, xslFile, params); - String midString = Xml.getString(midElement); - - assertThat( - midString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) - ); - - // 2. Replace it with a new one - String newDate = "2026-07-02T09:23:00"; - params.put("publicationDate", newDate); - Element resultElement = Xml.transform(midElement, xslFile, params); - String resultString = Xml.getString(resultElement); - - assertThat( - resultString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) - ); - assertThat( - resultString, hasXPath("//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) - ); - } - - @Test - public void testReplaceMultiplePublicationDates() throws Exception { - // Create an input with 2 publication dates - Element inputElement = Xml.loadFile(xmlFile); - - Map params = new HashMap<>(); - params.put("publicationDate", "2020-01-01T00:00:00"); - Element midElement = Xml.transform(inputElement, xslFile, params); - - // Add another one manually - Element citation = Xml.selectElement(midElement, "gmd:identificationInfo/*/gmd:citation/gmd:CI_Citation", new ArrayList<>(ISO19139SchemaPlugin.allNamespaces)); - Element date2 = new Element("date", citation.getNamespace()); - Element ciDate2 = new Element("CI_Date", citation.getNamespace()); - Element dateVal2 = new Element("date", citation.getNamespace()); - Element dateTime2 = new Element("DateTime", Namespace.getNamespace("gco", ns.get("gco"))); - dateTime2.setText("2021-01-01T00:00:00"); - dateVal2.addContent(dateTime2); - ciDate2.addContent(dateVal2); - Element dateType2 = new Element("dateType", citation.getNamespace()); - Element dateTypeCode2 = new Element("CI_DateTypeCode", citation.getNamespace()); - dateTypeCode2.setAttribute("codeListValue", "publication"); - dateTypeCode2.setAttribute("codeList", "someLocation"); - dateType2.addContent(dateTypeCode2); - ciDate2.addContent(dateType2); - date2.addContent(ciDate2); - citation.addContent(date2); - - String midStringWithTwo = Xml.getString(midElement); - assertThat( - midStringWithTwo, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("2")).withNamespaceContext(ns) - ); - - // Now run the process - String newDate = "2026-07-02T09:23:00"; - params.put("publicationDate", newDate); - Element resultElement = Xml.transform(midElement, xslFile, params); - String resultString = Xml.getString(resultElement); - - assertThat( - resultString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) - ); - assertThat( - resultString, hasXPath("//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) - ); - } -} diff --git a/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java b/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java deleted file mode 100644 index f2973a8df278..000000000000 --- a/schemas/iso19139/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2001-2026 Food and Agriculture Organization of the - * United Nations (FAO-UN), United Nations World Food Programme (WFP) - * and United Nations Environment Programme (UNEP) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - * - * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, - * Rome - Italy. email: geonetwork@osgeo.org - */ - -package org.fao.geonet.schemas; - -import org.fao.geonet.schema.iso19139.ISO19139SchemaPlugin; -import org.fao.geonet.utils.Xml; -import org.jdom.Element; -import org.junit.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath; - -public class PublicationDateRemoveProcessTest extends XslProcessTest { - - public PublicationDateRemoveProcessTest() { - super(); - this.setXslFilename("process/publicationdate-remove.xsl"); - this.setXmlFilename("schemas/xsl/process/input.xml"); - this.setNs(ISO19139SchemaPlugin.allNamespaces); - } - - @Test - public void testRemovePublicationDate() throws Exception { - Element inputElement = Xml.loadFile(xmlFile); - - // 1. Add a publication date first using the add process (we assume it works as it has its own tests) - // Or just manually check it doesn't exist and add it if we want to be sure. - // For this test, let's just use the add XSL to prepare the input. - java.nio.file.Path addXsl = xslFile.getParent().resolve("publicationdate-add.xsl"); - Map params = new HashMap<>(); - params.put("publicationDate", "2026-07-02T10:00:00"); - Element midElement = Xml.transform(inputElement, addXsl, params); - - String midString = Xml.getString(midElement); - assertThat( - midString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) - ); - - // 2. Now remove it - Element resultElement = Xml.transform(midElement, xslFile); - String resultString = Xml.getString(resultElement); - - assertThat( - resultString, hasXPath("count(//gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("0")).withNamespaceContext(ns) - ); - } -} diff --git a/services/src/main/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListener.java b/services/src/main/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListener.java index b4208b88cf72..1bb2ad990a9c 100644 --- a/services/src/main/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListener.java +++ b/services/src/main/java/org/fao/geonet/api/records/events/MetadataRemovePublicationDateListener.java @@ -46,7 +46,7 @@ public class MetadataRemovePublicationDateListener implements ApplicationListene private IMetadataUtils metadataUtils; @Override - public synchronized void onApplicationEvent(MetadataUnpublished event) { + public void onApplicationEvent(MetadataUnpublished event) { if (!settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)) { return; } diff --git a/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListener.java b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListener.java index 78ef9450aec8..3f292d4f9650 100644 --- a/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListener.java +++ b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateListener.java @@ -47,7 +47,7 @@ public class MetadataUpdatePublicationDateListener implements ApplicationListene private IMetadataUtils metadataUtils; @Override - public synchronized void onApplicationEvent(MetadataPublished event) { + public void onApplicationEvent(MetadataPublished event) { if (!settingManager.getValueAsBool(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE)) { return; } diff --git a/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java index bcd1d469a59b..d39f011b22d0 100644 --- a/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java +++ b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java @@ -29,8 +29,11 @@ import org.fao.geonet.api.processing.report.XsltMetadataProcessingReport; import org.fao.geonet.domain.AbstractMetadata; import org.fao.geonet.domain.ISODate; +import org.fao.geonet.kernel.SchemaManager; import org.springframework.stereotype.Service; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,6 +46,12 @@ public void addPublicationDate(AbstractMetadata md, ISODate publicationDate) { ServiceContext context = ServiceContext.get(); String process = "publicationdate-add"; + if (!hasProcess(context, md, process)) { + // The record schema does not provide the process (only some schemas + // such as ISO19115-3 do), so publication date management does not apply. + return; + } + XsltMetadataProcessingReport report = new XsltMetadataProcessingReport(process); @@ -61,6 +70,12 @@ public void removePublicationDate(AbstractMetadata md) { ServiceContext context = ServiceContext.get(); String process = "publicationdate-remove"; + if (!hasProcess(context, md, process)) { + // The record schema does not provide the process (only some schemas + // such as ISO19115-3 do), so publication date management does not apply. + return; + } + XsltMetadataProcessingReport report = new XsltMetadataProcessingReport(process); @@ -74,6 +89,18 @@ public void removePublicationDate(AbstractMetadata md) { checkForErrors(report, md); } + /** + * The publication date processes are only provided by some schema plugins (eg. ISO19115-3). + * Records whose schema does not provide the process are skipped so that publishing records + * of other schemas is not affected. + */ + private boolean hasProcess(ServiceContext context, AbstractMetadata md, String process) { + SchemaManager schemaManager = context.getBean(SchemaManager.class); + String schema = md.getDataInfo().getSchemaId(); + Path xslProcessing = schemaManager.getSchemaDir(schema).resolve("process").resolve(process + ".xsl"); + return Files.exists(xslProcessing); + } + /** * XslProcessUtils.process() catches its own processing exceptions and records them on the * report instead of throwing, so a failure to set/remove the publication date would otherwise diff --git a/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java index fff92ba2a755..103e2c7911ac 100644 --- a/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java +++ b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java @@ -27,7 +27,7 @@ import org.fao.geonet.domain.AbstractMetadata; import org.fao.geonet.domain.ISODate; import org.fao.geonet.kernel.datamanager.IMetadataUtils; -import org.fao.geonet.schema.iso19139.ISO19139SchemaPlugin; +import org.fao.geonet.schema.iso19115_3_2018.ISO19115_3_2018SchemaPlugin; import org.fao.geonet.services.AbstractServiceIntegrationTest; import org.fao.geonet.utils.Xml; import org.jdom.Element; @@ -64,9 +64,9 @@ public void setUp() throws Exception { context = createServiceContext(); loginAsAdmin(context); - ns.addAll(ISO19139SchemaPlugin.allNamespaces); + ns.addAll(ISO19115_3_2018SchemaPlugin.allNamespaces); - Element sample = getSampleISO19139MetadataXml(); + Element sample = getSampleISO19115MetadataXml(); metadata = injectMetadataInDb(sample, context, true); } @@ -76,7 +76,7 @@ public void testAddPublicationDate() throws Exception { Element initialXml = metadataRecord.getXmlData(false); // Ensure no publication date exists - assertEquals(0, Xml.selectNodes(initialXml, "//gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); + assertEquals(0, Xml.selectNodes(initialXml, "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); ISODate pubDate = new ISODate("2026-07-02T10:00:00"); service.addPublicationDate(metadataRecord, pubDate); @@ -86,8 +86,8 @@ public void testAddPublicationDate() throws Exception { AbstractMetadata updatedRecord = repository.findOne(metadata.getId()); Element updatedXml = updatedRecord.getXmlData(false); - assertEquals(1, Xml.selectNodes(updatedXml, "gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); - Element dateText = (Element) Xml.selectSingle(updatedXml, "gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime", ns); + assertEquals(1, Xml.selectNodes(updatedXml, "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); + Element dateText = (Element) Xml.selectSingle(updatedXml, "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']/cit:CI_Date/cit:date/gco:DateTime", ns); assertNotNull(dateText); assertEquals(pubDate.toString(), dateText.getText()); } @@ -103,7 +103,7 @@ public void testRemovePublicationDate() throws Exception { entityManager.clear(); AbstractMetadata recordWithPubDate = repository.findOne(metadata.getId()); - assertEquals(1, Xml.selectNodes(recordWithPubDate.getXmlData(false), "gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); + assertEquals(1, Xml.selectNodes(recordWithPubDate.getXmlData(false), "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); // 2. Remove it service.removePublicationDate(recordWithPubDate); @@ -113,6 +113,6 @@ public void testRemovePublicationDate() throws Exception { AbstractMetadata updatedRecord = repository.findOne(metadata.getId()); Element updatedXml = updatedRecord.getXmlData(false); - assertEquals(0, Xml.selectNodes(updatedXml, "gmd:identificationInfo/*/gmd:citation/*/gmd:date[gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); + assertEquals(0, Xml.selectNodes(updatedXml, "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); } } diff --git a/web-ui/src/main/resources/catalog/locales/en-admin.json b/web-ui/src/main/resources/catalog/locales/en-admin.json index 2dbed32b793f..4dfaf07ccd6a 100644 --- a/web-ui/src/main/resources/catalog/locales/en-admin.json +++ b/web-ui/src/main/resources/catalog/locales/en-admin.json @@ -796,8 +796,8 @@ "system/metadataprivs/usergrouponly": "Only set privileges to user's groups", "system/metadataprivs/publicationbyrevieweringroupowneronly": "Publication by users reviewer in record group only", "system/metadataprivs/publicationbyrevieweringroupowneronly-help": "Allow publication by administrator and reviewer member of record group. If false, then also all users reviewer in group with editing rights can publish/unpublish a record.", - "system/metadataprivs/publication/managepublicationdate": "Manage the publication date automatically", - "system/metadataprivs/publication/managepublicationdate-help": "When enabled the publication date of the metadata is set automatically when the metadata is published and removed when the metadata is unpublished", + "system/metadataprivs/publication/managepublicationdate": "Manage the publication date automatically (ISO19115.3-2018 metadata)", + "system/metadataprivs/publication/managepublicationdate-help": "When enabled the publication date of the ISO19115.3-2018 metadata is set automatically when the metadata is published and removed when the metadata is unpublished", "system/metadataprivs/publication/notificationLevel": "Notification level when a metadata is published / unpublished", "system/metadataprivs/publication/notificationLevel-help": "Define which users to alert when a metadata is published / unpublished", "system/metadataprivs/publication/notificationGroups": "Groups to notify when a metadata is published / unpublished", From d07a5483949de7faf8f8ac0a3fcb0bade7058f55 Mon Sep 17 00:00:00 2001 From: josegar74 Date: Fri, 17 Jul 2026 10:31:27 +0200 Subject: [PATCH 3/8] Set/unset metadata publication date when a metadata is published / unpublished - fix test --- .../MetadataUpdatePublicationDateServiceIntegrationTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java index 103e2c7911ac..41eed27e13e9 100644 --- a/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java +++ b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java @@ -27,6 +27,7 @@ import org.fao.geonet.domain.AbstractMetadata; import org.fao.geonet.domain.ISODate; import org.fao.geonet.kernel.datamanager.IMetadataUtils; +import org.fao.geonet.kernel.search.IndexingMode; import org.fao.geonet.schema.iso19115_3_2018.ISO19115_3_2018SchemaPlugin; import org.fao.geonet.services.AbstractServiceIntegrationTest; import org.fao.geonet.utils.Xml; @@ -67,7 +68,7 @@ public void setUp() throws Exception { ns.addAll(ISO19115_3_2018SchemaPlugin.allNamespaces); Element sample = getSampleISO19115MetadataXml(); - metadata = injectMetadataInDb(sample, context, true); + metadata = injectMetadataInDb(sample, context, IndexingMode.full); } @Test From 70edf0fbe3aadded7ac722913a79b99f89a54dcd Mon Sep 17 00:00:00 2001 From: Francois Prunayre Date: Mon, 20 Jul 2026 12:37:27 +0200 Subject: [PATCH 4/8] Set/unset metadata publication date when a metadata is published / SQL migration in 4.4.13. --- .../classes/setup/sql/migrate/v4412/migrate-default.sql | 3 --- .../classes/setup/sql/migrate/v4413/migrate-default.sql | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4412/migrate-default.sql b/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4412/migrate-default.sql index 60cc071ba2fd..f293216c61c0 100644 --- a/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4412/migrate-default.sql +++ b/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4412/migrate-default.sql @@ -1,7 +1,4 @@ INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/csw/getRecordsIgnoreMetadataNotSupported', 'true', 2, 1321, 'y'); -INSERT INTO Settings (name, value, datatype, position, internal) SELECT distinct 'system/metadataprivs/publication/managepublicationdate', 'false', 2, 9182, 'n' from settings WHERE NOT EXISTS (SELECT name FROM Settings WHERE name = 'system/metadataprivs/publication/managepublicationdate'); -UPDATE Settings SET position=9183 WHERE name='system/metadataprivs/publication/notificationLevel'; -UPDATE Settings SET position=9184 WHERE name='system/metadataprivs/publication/notificationGroups'; INSERT INTO Settings (name, value, datatype, position, internal) SELECT distinct 'system/oai/enable', 'true', 2, 7000, 'n' from settings WHERE NOT EXISTS (SELECT name FROM Settings WHERE name = 'system/oai/enable'); diff --git a/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4413/migrate-default.sql b/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4413/migrate-default.sql index 7836b2c70d41..4b88ccd89496 100644 --- a/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4413/migrate-default.sql +++ b/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4413/migrate-default.sql @@ -1,2 +1,6 @@ +INSERT INTO Settings (name, value, datatype, position, internal) SELECT distinct 'system/metadataprivs/publication/managepublicationdate', 'false', 2, 9182, 'n' from settings WHERE NOT EXISTS (SELECT name FROM Settings WHERE name = 'system/metadataprivs/publication/managepublicationdate'); +UPDATE Settings SET position=9183 WHERE name='system/metadataprivs/publication/notificationLevel'; +UPDATE Settings SET position=9184 WHERE name='system/metadataprivs/publication/notificationGroups'; + UPDATE Settings SET value='4.4.13' WHERE name='system/platform/version'; UPDATE Settings SET value='SNAPSHOT' WHERE name='system/platform/subVersion'; From a5f20812056b3607928c6fca3a9116d3a6e53931 Mon Sep 17 00:00:00 2001 From: josegar74 Date: Mon, 20 Jul 2026 13:34:53 +0200 Subject: [PATCH 5/8] Set/unset metadata publication date when a metadata is published / unpublished - use date format instead of datetime format --- .../process/publicationdate-add.xsl | 2 +- .../PublicationDateAddProcessTest.java | 22 +++++++++---------- .../PublicationDateRemoveProcessTest.java | 4 ++-- .../MetadataUpdatePublicationDateService.java | 2 +- ...PublicationDateServiceIntegrationTest.java | 6 ++--- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl index 05280eac5fd7..1a6bee0b6226 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/publicationdate-add.xsl @@ -52,7 +52,7 @@ Stylesheet used to add a publication date of a metadata record. - + diff --git a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java index 9073a494b4d6..0c48eaacbb23 100644 --- a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java +++ b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateAddProcessTest.java @@ -62,7 +62,7 @@ inputString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_Date // Add publication date Map params = new HashMap<>(); - String newDate = "2026-07-02T09:23:00"; + String newDate = "2026-07-02"; params.put("publicationDate", newDate); Element resultElement = Xml.transform(inputElement, xslFile, params); @@ -72,7 +72,7 @@ inputString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_Date resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) ); assertThat( - resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:Date/text()", equalTo(newDate)).withNamespaceContext(ns) ); } @@ -82,7 +82,7 @@ public void testReplacePublicationDate() throws Exception { // 1. Add a publication date first Map params = new HashMap<>(); - params.put("publicationDate", "2020-01-01T00:00:00"); + params.put("publicationDate", "2020-01-01"); Element midElement = Xml.transform(inputElement, xslFile, params); String midString = Xml.getString(midElement); @@ -91,7 +91,7 @@ midString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTy ); // 2. Replace it with a new one - String newDate = "2026-07-02T09:23:00"; + String newDate = "2026-07-02"; params.put("publicationDate", newDate); Element resultElement = Xml.transform(midElement, xslFile, params); String resultString = Xml.getString(resultElement); @@ -100,7 +100,7 @@ midString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTy resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) ); assertThat( - resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:Date/text()", equalTo(newDate)).withNamespaceContext(ns) ); } @@ -110,12 +110,12 @@ public void testReplaceMultiplePublicationDates() throws Exception { Element inputElement = Xml.loadFile(xmlFile); Map params = new HashMap<>(); - params.put("publicationDate", "2020-01-01T00:00:00"); + params.put("publicationDate", "2020-01-01"); Element midElement = Xml.transform(inputElement, xslFile, params); // Add another publication date manually at metadata level (mdb:dateInfo). // The transformed root element is mdb:MD_Metadata. - midElement.addContent(newDateInfo(midElement.getNamespace(), "publication", "2021-01-01T00:00:00")); + midElement.addContent(newDateInfo(midElement.getNamespace(), "publication", "2021-01-01")); String midStringWithTwo = Xml.getString(midElement); assertThat( @@ -132,7 +132,7 @@ midStringWithTwo, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI resultString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication'])", equalTo("1")).withNamespaceContext(ns) ); assertThat( - resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:DateTime/text()", equalTo(newDate)).withNamespaceContext(ns) + resultString, hasXPath("//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']//gco:Date/text()", equalTo(newDate)).withNamespaceContext(ns) ); } @@ -148,7 +148,7 @@ public void testPreservesDateWithoutCodeListValueWhenAddingPublicationDate() thr // Add a metadata date with a CI_DateTypeCode that has no codeListValue attribute. // The loaded root element is mdb:MD_Metadata. - inputElement.addContent(newDateInfo(inputElement.getNamespace(), null, "2019-05-05T00:00:00")); + inputElement.addContent(newDateInfo(inputElement.getNamespace(), null, "2019-05-05")); String inputString = Xml.getString(inputElement); assertThat( @@ -157,7 +157,7 @@ inputString, hasXPath("count(//mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_Date // Add the publication date Map params = new HashMap<>(); - params.put("publicationDate", "2026-07-02T09:23:00"); + params.put("publicationDate", "2026-07-02"); Element resultElement = Xml.transform(inputElement, xslFile, params); String resultString = Xml.getString(resultElement); @@ -183,7 +183,7 @@ private Element newDateInfo(Namespace mdbNs, String codeListValue, String dateTi Element ciDate = new Element("CI_Date", citNs); Element dateVal = new Element("date", citNs); - Element dateTimeEl = new Element("DateTime", gcoNs); + Element dateTimeEl = new Element("Date", gcoNs); dateTimeEl.setText(dateTime); dateVal.addContent(dateTimeEl); ciDate.addContent(dateVal); diff --git a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java index d33e5c78e939..9fc047020057 100644 --- a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java +++ b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/schemas/PublicationDateRemoveProcessTest.java @@ -56,13 +56,13 @@ public void testRemovePublicationDate() throws Exception { Element dateInfo = new Element("dateInfo", inputElement.getNamespace()); Element ciDate = new Element("CI_Date", cit); Element dateEl = new Element("date", cit); - Element dateTime = new Element("DateTime", gco).setText("2026-07-02T10:00:00"); + Element date = new Element("Date", gco).setText("2026-07-02"); Element dateType = new Element("dateType", cit); Element ciDateTypeCode = new Element("CI_DateTypeCode", cit) .setAttribute("codeList", "codeListLocation#CI_DateTypeCode") .setAttribute("codeListValue", "publication"); - dateEl.addContent(dateTime); + dateEl.addContent(date); dateType.addContent(ciDateTypeCode); ciDate.addContent(dateEl); ciDate.addContent(dateType); diff --git a/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java index d39f011b22d0..143f43fb98e7 100644 --- a/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java +++ b/services/src/main/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateService.java @@ -56,7 +56,7 @@ public void addPublicationDate(AbstractMetadata md, ISODate publicationDate) { new XsltMetadataProcessingReport(process); Map params = new HashMap<>(); - params.put("publicationDate", new String[]{publicationDate.toString()}); + params.put("publicationDate", new String[]{publicationDate.getDateAsString()}); try { XslProcessUtils.process(context, String.valueOf(md.getId()), process, true, true, true, report, "", params); } catch (Exception e) { diff --git a/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java index 41eed27e13e9..bd96f23f1871 100644 --- a/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java +++ b/services/src/test/java/org/fao/geonet/api/records/events/MetadataUpdatePublicationDateServiceIntegrationTest.java @@ -79,7 +79,7 @@ public void testAddPublicationDate() throws Exception { // Ensure no publication date exists assertEquals(0, Xml.selectNodes(initialXml, "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); - ISODate pubDate = new ISODate("2026-07-02T10:00:00"); + ISODate pubDate = new ISODate("2026-07-02"); service.addPublicationDate(metadataRecord, pubDate); entityManager.flush(); entityManager.clear(); @@ -88,7 +88,7 @@ public void testAddPublicationDate() throws Exception { Element updatedXml = updatedRecord.getXmlData(false); assertEquals(1, Xml.selectNodes(updatedXml, "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']", ns).size()); - Element dateText = (Element) Xml.selectSingle(updatedXml, "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']/cit:CI_Date/cit:date/gco:DateTime", ns); + Element dateText = (Element) Xml.selectSingle(updatedXml, "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']/cit:CI_Date/cit:date/gco:Date", ns); assertNotNull(dateText); assertEquals(pubDate.toString(), dateText.getText()); } @@ -98,7 +98,7 @@ public void testRemovePublicationDate() throws Exception { AbstractMetadata metadataRecord = repository.findOne(metadata.getId()); // 1. Add a publication date first - ISODate pubDate = new ISODate("2026-07-02T10:00:00"); + ISODate pubDate = new ISODate("2026-07-02"); service.addPublicationDate(metadataRecord, pubDate); entityManager.flush(); entityManager.clear(); From 3537ad54bdbc8979be28631d1d22d9b32d883ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Prunayre?= Date: Mon, 20 Jul 2026 14:04:02 +0200 Subject: [PATCH 6/8] Update translation en-admin.json Updated publication date management descriptions for clarity. --- web-ui/src/main/resources/catalog/locales/en-admin.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-ui/src/main/resources/catalog/locales/en-admin.json b/web-ui/src/main/resources/catalog/locales/en-admin.json index dee8fa074088..01378978b4e5 100644 --- a/web-ui/src/main/resources/catalog/locales/en-admin.json +++ b/web-ui/src/main/resources/catalog/locales/en-admin.json @@ -796,8 +796,8 @@ "system/metadataprivs/usergrouponly": "Only set privileges to user's groups", "system/metadataprivs/publicationbyrevieweringroupowneronly": "Publication by users reviewer in record group only", "system/metadataprivs/publicationbyrevieweringroupowneronly-help": "Allow publication by administrator and reviewer member of record group. If false, then also all users reviewer in group with editing rights can publish/unpublish a record.", - "system/metadataprivs/publication/managepublicationdate": "Manage the publication date automatically (ISO19115.3-2018 metadata)", - "system/metadataprivs/publication/managepublicationdate-help": "When enabled the publication date of the ISO19115.3-2018 metadata is set automatically when the metadata is published and removed when the metadata is unpublished", + "system/metadataprivs/publication/managepublicationdate": "Manage the publication date automatically (only applies to ISO19115-3)", + "system/metadataprivs/publication/managepublicationdate-help": "When enabled the publication date of the ISO19115-3 metadata is set automatically when the metadata is published and removed when the metadata is unpublished", "system/metadataprivs/publication/notificationLevel": "Notification level when a metadata is published / unpublished", "system/metadataprivs/publication/notificationLevel-help": "Define which users to alert when a metadata is published / unpublished", "system/metadataprivs/publication/notificationGroups": "Groups to notify when a metadata is published / unpublished", From 4040a9ef91bb524a99eee26cdaca4f66a5b89af3 Mon Sep 17 00:00:00 2001 From: josegar74 Date: Mon, 20 Jul 2026 15:41:01 +0200 Subject: [PATCH 7/8] Set/unset metadata publication date when a metadata is published / unpublished - display publication date in the record view page --- .../plugin/iso19115-3.2018/index-fields/index.xsl | 8 ++++++++ .../default/templates/recordView/metadata.html | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/index-fields/index.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/index-fields/index.xsl index f9d22dd23a24..1ab6c59b826d 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/index-fields/index.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/index-fields/index.xsl @@ -213,6 +213,14 @@ + + + + + + diff --git a/web-ui/src/main/resources/catalog/views/default/templates/recordView/metadata.html b/web-ui/src/main/resources/catalog/views/default/templates/recordView/metadata.html index 92c79a4dd8a2..e1bf98a75af6 100644 --- a/web-ui/src/main/resources/catalog/views/default/templates/recordView/metadata.html +++ b/web-ui/src/main/resources/catalog/views/default/templates/recordView/metadata.html @@ -9,6 +9,19 @@

updatedOn

> +
+ + + +
+

publicationDate

+

{{mdView.current.record.publicationDate}}

+
+
+
Date: Mon, 20 Jul 2026 18:18:41 +0200 Subject: [PATCH 8/8] Set/unset metadata publication date when a metadata is published / unpublished - Add metadata publication tests --- .../api/records/MetadataSharingApiTest.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/services/src/test/java/org/fao/geonet/api/records/MetadataSharingApiTest.java b/services/src/test/java/org/fao/geonet/api/records/MetadataSharingApiTest.java index e5ee8d9d1293..55a037741363 100644 --- a/services/src/test/java/org/fao/geonet/api/records/MetadataSharingApiTest.java +++ b/services/src/test/java/org/fao/geonet/api/records/MetadataSharingApiTest.java @@ -40,13 +40,18 @@ import org.fao.geonet.domain.UserGroup; import org.fao.geonet.domain.MetadataStatus; import org.fao.geonet.domain.StatusValue; +import org.fao.geonet.domain.ISODate; import org.fao.geonet.kernel.setting.SettingManager; import org.fao.geonet.kernel.setting.Settings; import org.fao.geonet.repository.MetadataRepository; import org.fao.geonet.repository.MetadataStatusRepository; import org.fao.geonet.repository.OperationAllowedRepository; import org.fao.geonet.repository.UserRepositoryTest; +import org.fao.geonet.schema.iso19115_3_2018.ISO19115_3_2018SchemaPlugin; import org.fao.geonet.services.AbstractServiceIntegrationTest; +import org.fao.geonet.utils.Xml; +import org.jdom.Element; +import org.jdom.Namespace; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -57,6 +62,8 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -64,6 +71,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; @@ -75,6 +83,15 @@ **/ public class MetadataSharingApiTest extends AbstractServiceIntegrationTest { private static final int SAMPLE_GROUP_ID = 2; + + /** + * Selects the metadata-level publication date (mdb:dateInfo) added by the + * ISO19115-3.2018 {@code publicationdate-add} process. This does not match the + * resource-level publication date carried in the sample's identification info. + */ + private static final String PUBLICATION_DATE_INFO_XPATH = + "mdb:dateInfo[cit:CI_Date/cit:dateType/cit:CI_DateTypeCode/@codeListValue = 'publication']"; + @Autowired private WebApplicationContext wac; @@ -90,6 +107,9 @@ public class MetadataSharingApiTest extends AbstractServiceIntegrationTest { @Autowired private SettingManager settingManager; + @PersistenceContext + private EntityManager entityManager; + private User editorUser; private User reviewerUser; private int metadataId; @@ -631,6 +651,100 @@ private User createUserAdminWithGroupProfile(String username, Profile groupProfi return user; } + /** + * When {@code system/metadataprivs/publication/managepublicationdate} is enabled, publishing an + * ISO19115-3.2018 record must set the metadata-level publication date in the stored XML using the + * schema's {@code publicationdate-add} process. + */ + @Test + public void publishIso191153UpdatesPublicationDateWhenManageEnabled() throws Exception { + settingManager.setValue(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE, true); + // Do not let validation block publication of the sample record; this test targets the publication date only. + settingManager.setValue(Settings.METADATA_WORKFLOW_ALLOW_PUBLISH_INVALID_MD, true); + + int isoId = injectIso191153Record(); + String isoUuid = metadataRepository.findById(isoId).get().getUuid(); + + List ns = new ArrayList<>(ISO19115_3_2018SchemaPlugin.allNamespaces); + + // The sample has no metadata-level publication date before publishing. + Element beforeXml = metadataRepository.findById(isoId).get().getXmlData(false); + assertEquals(0, Xml.selectNodes(beforeXml, PUBLICATION_DATE_INFO_XPATH, ns).size()); + + String expectedDate = new ISODate().getDateAsString(); + + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + MockHttpSession mockHttpSession = loginAs(reviewerUser); + mockMvc.perform(put("/srv/api/records/" + isoUuid + "/publish") + .session(mockHttpSession)) + .andExpect(status().isNoContent()); + + // Re-read the stored record from the database. + entityManager.flush(); + entityManager.clear(); + Element afterXml = metadataRepository.findById(isoId).get().getXmlData(false); + + assertEquals("A single metadata-level publication date should be present after publishing", + 1, Xml.selectNodes(afterXml, PUBLICATION_DATE_INFO_XPATH, ns).size()); + Element publicationDate = (Element) Xml.selectSingle(afterXml, + PUBLICATION_DATE_INFO_XPATH + "/cit:CI_Date/cit:date/gco:Date", ns); + assertNotNull("Publication date value should be set", publicationDate); + assertEquals("Publication date should be the date of publication (today)", + expectedDate, publicationDate.getText()); + } + + /** + * When {@code system/metadataprivs/publication/managepublicationdate} is disabled, publishing an + * ISO19115-3.2018 record must leave the stored XML untouched (no publication date added). + */ + @Test + public void publishIso191153DoesNotUpdatePublicationDateWhenManageDisabled() throws Exception { + settingManager.setValue(Settings.SYSTEM_METADATAPRIVS_PUBLICATION_MANAGEPUBLICATIONDATE, false); + settingManager.setValue(Settings.METADATA_WORKFLOW_ALLOW_PUBLISH_INVALID_MD, true); + + int isoId = injectIso191153Record(); + String isoUuid = metadataRepository.findById(isoId).get().getUuid(); + + List ns = new ArrayList<>(ISO19115_3_2018SchemaPlugin.allNamespaces); + + Element beforeXml = metadataRepository.findById(isoId).get().getXmlData(false); + int dateInfoCountBefore = Xml.selectNodes(beforeXml, "mdb:dateInfo", ns).size(); + assertEquals(0, Xml.selectNodes(beforeXml, PUBLICATION_DATE_INFO_XPATH, ns).size()); + + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + MockHttpSession mockHttpSession = loginAs(reviewerUser); + mockMvc.perform(put("/srv/api/records/" + isoUuid + "/publish") + .session(mockHttpSession)) + .andExpect(status().isNoContent()); + + // The record is published ... + List ops = operationAllowedRepository.findAllById_MetadataId(isoId); + assertTrue("Record should be published", + ops.stream().anyMatch(op -> op.getId().getGroupId() == ReservedGroup.all.getId())); + + // ... but its XML must not gain a publication date, and its dateInfo blocks are unchanged. + entityManager.flush(); + entityManager.clear(); + Element afterXml = metadataRepository.findById(isoId).get().getXmlData(false); + + assertEquals("No publication date should be added when the setting is disabled", + 0, Xml.selectNodes(afterXml, PUBLICATION_DATE_INFO_XPATH, ns).size()); + assertEquals("The metadata dateInfo blocks should be unchanged when the setting is disabled", + dateInfoCountBefore, Xml.selectNodes(afterXml, "mdb:dateInfo", ns).size()); + } + + /** + * Injects an ISO19115-3.2018 sample record owned by {@code editorUser} in the sample group so that + * {@code reviewerUser} (a Reviewer in that group) can publish it. + */ + private int injectIso191153Record() throws Exception { + Metadata md = (Metadata) injectMetadataInDb(getSampleISO19115MetadataXml(), context); + md.getSourceInfo().setOwner(editorUser.getId()); + md.getSourceInfo().setGroupOwner(SAMPLE_GROUP_ID); + metadataRepository.save(md); + return md.getId(); + } + /** * Creates a new workspace group with the given name, saves it, and adds a * {@code UserGroup(profile=UserAdmin)} entry for {@code user} in that group.