From 3bd1d611ad3f41a82794e2922f02db1151061927 Mon Sep 17 00:00:00 2001 From: Francois Prunayre Date: Mon, 30 Oct 2023 11:45:25 +0100 Subject: [PATCH] API / Batch editing / XPath / Add support for attribute with namespace. eg. The following was failing to remove `gco:nilReason` attribute: ```json { "field":"XPath_1", "insertMode":"gn_delete", "xpath":".//gmd:pointOfContact[gmd:CI_ResponsibleParty/gmd:contactInfo/*/gmd:address/*/gmd:electronicMailAddress/gco:CharacterString = 'copernicus@eea.europa.eu']/@gco:nilReason[. = 'withheld']", "condition":"", "value":"", "isXpath":true} ``` --- .../java/org/fao/geonet/kernel/EditLib.java | 28 +++++++-- .../metadata/BatchEditsServiceTest.java | 61 +++++++++++++++++++ 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/fao/geonet/kernel/EditLib.java b/core/src/main/java/org/fao/geonet/kernel/EditLib.java index c08322ec61a..ae70dcd5e62 100644 --- a/core/src/main/java/org/fao/geonet/kernel/EditLib.java +++ b/core/src/main/java/org/fao/geonet/kernel/EditLib.java @@ -50,6 +50,7 @@ import org.apache.commons.jxpath.ri.parser.Token; import org.apache.commons.jxpath.ri.parser.XPathParser; import org.apache.commons.jxpath.ri.parser.XPathParserConstants; +import org.apache.commons.lang.StringUtils; import org.fao.geonet.constants.Edit; import org.fao.geonet.constants.Geonet; import org.fao.geonet.domain.Pair; @@ -583,7 +584,10 @@ public boolean addElementOrFragmentFromXpath(Element metadataRecord, } } else if (propNode instanceof Attribute) { Element parent = ((Attribute) propNode).getParent(); - parent.removeAttribute(((Attribute) propNode).getName()); + Attribute targetAttribute = (Attribute) propNode; + parent.removeAttribute( + targetAttribute.getName(), + targetAttribute.getNamespace()); } } else { // Update element content with node @@ -736,6 +740,7 @@ private boolean createAndAddFromXPath(Element metadataRecord, MetadataSchema met boolean isAttribute = false; String currentElementName = ""; String currentElementNamespacePrefix = ""; + String currentAttributeNamespacePrefix = ""; // Stop when token is null, start of an expression is found ie. "[" // @@ -758,10 +763,15 @@ private boolean createAndAddFromXPath(Element metadataRecord, MetadataSchema met isAttribute = true; } // Match namespace prefix - if (currentToken.kind == XPathParserLocalConstants.TEXT && previousToken.kind == XPathParserConstants.SLASH) { + if (currentToken.kind == XPathParserLocalConstants.TEXT && + previousToken.kind == XPathParserConstants.SLASH) { // get element namespace if element is text and previous was / // means qualified name only is supported currentElementNamespacePrefix = currentToken.image; + } else if (isAttribute && + previousToken.kind == XPathParserLocalConstants.TEXT && + currentToken.kind == XPathParserLocalConstants.NAMESPACE_SEP) { + currentAttributeNamespacePrefix = previousToken.image; } else if (currentToken.kind == XPathParserLocalConstants.TEXT && previousToken.kind == XPathParserLocalConstants.NAMESPACE_SEP) { // get element name if element is text and previous was / @@ -788,7 +798,9 @@ private boolean createAndAddFromXPath(Element metadataRecord, MetadataSchema met } else { LOGGER_ADD_ELEMENT.debug(" > add new node {} inserted in {}", qualifiedName, currentNode.getName()); - if (metadataSchema.getElementValues(qualifiedName, currentNode.getQualifiedName()) != null) { + if (isAttribute) { + existingElement = false; // Attribute is created and set after. + } else if (metadataSchema.getElementValues(qualifiedName, currentNode.getQualifiedName()) != null) { currentNode = addElement(metadataSchema, currentNode, qualifiedName); existingElement = false; } else { @@ -833,7 +845,15 @@ private boolean createAndAddFromXPath(Element metadataRecord, MetadataSchema met doAddFragmentFromXpath(metadataSchema, value.getNodeValue(), currentNode); } else { if (isAttribute) { - currentNode.setAttribute(previousToken.image, value.getStringValue()); + if (StringUtils.isNotEmpty(currentAttributeNamespacePrefix)) { + currentNode.setAttribute(previousToken.image, + value.getStringValue(), + Namespace.getNamespace(currentAttributeNamespacePrefix, + metadataSchema.getNS(currentAttributeNamespacePrefix))); + } else { + currentNode.setAttribute(previousToken.image, value.getStringValue()); + } + } else { currentNode.setText(value.getStringValue()); } diff --git a/services/src/test/java/org/fao/geonet/services/metadata/BatchEditsServiceTest.java b/services/src/test/java/org/fao/geonet/services/metadata/BatchEditsServiceTest.java index a3835e2f950..67ca12d6efb 100644 --- a/services/src/test/java/org/fao/geonet/services/metadata/BatchEditsServiceTest.java +++ b/services/src/test/java/org/fao/geonet/services/metadata/BatchEditsServiceTest.java @@ -39,6 +39,7 @@ import org.fao.geonet.domain.AbstractMetadata; import org.fao.geonet.kernel.datamanager.IMetadataUtils; import org.fao.geonet.kernel.mef.MEFLibIntegrationTest; +import org.fao.geonet.schema.iso19115_3_2018.ISO19115_3_2018Namespaces; import org.fao.geonet.services.AbstractServiceIntegrationTest; import org.jdom.Attribute; import org.jdom.Element; @@ -322,6 +323,66 @@ public void testUpdateRecordAddAttribute() throws Exception { Assert.assertEquals("value", ((Element) scope.get(0)).getAttributeValue("newAttribute")); } + @Test + public void testUpdateRecordAddAndDeleteAttributeWithNamespace() throws Exception { + final String uuid = "db07463b-6769-401e-944b-f22e2e3bcc26"; + BatchEditParameter[] listOfupdates = new BatchEditParameter[]{ + new BatchEditParameter( + "/mdb:MD_Metadata/mdb:metadataScope/@gco:nilReason", + "withheld" + ) + }; + + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + this.mockHttpSession = loginAsAdmin(); + + Gson gson = new GsonBuilder() + .create(); + JsonElement jsonEl = gson.toJsonTree(listOfupdates); + + this.mockMvc.perform(put("/srv/api/records/batchediting?uuids=" + uuid) + .content(jsonEl.toString()) + .contentType(MediaType.APPLICATION_JSON) + .session(this.mockHttpSession) + .accept(MediaType.parseMediaType("application/json"))) + .andExpect(status().is(201)); + + AbstractMetadata updatedRecord = repository.findOneByUuid(uuid); + Element xml = Xml.loadString(updatedRecord.getData(), false); + + List scope = org.fao.geonet.utils.Xml.selectNodes(xml, + "./mdb:metadataScope", + xml.getAdditionalNamespaces()); + Assert.assertEquals("withheld", ((Element) scope.get(0)).getAttributeValue("nilReason", ISO19115_3_2018Namespaces.GCO)); + + + listOfupdates = new BatchEditParameter[]{ + new BatchEditParameter( + "/mdb:MD_Metadata/mdb:metadataScope/@gco:nilReason", + "" + ) + }; + + jsonEl = gson.toJsonTree(listOfupdates); + + this.mockMvc.perform(put("/srv/api/records/batchediting?uuids=" + uuid) + .content(jsonEl.toString()) + .contentType(MediaType.APPLICATION_JSON) + .session(this.mockHttpSession) + .accept(MediaType.parseMediaType("application/json"))) + .andExpect(status().is(201)); + + updatedRecord = repository.findOneByUuid(uuid); + xml = Xml.loadString(updatedRecord.getData(), false); + + scope = org.fao.geonet.utils.Xml.selectNodes(xml, + "./mdb:metadataScope/gco:nilReason", + xml.getAdditionalNamespaces()); + Assert.assertEquals(0, scope.size()); + + } + + @Test public void testUpdateRecordElement() throws Exception { final String uuid = "db07463b-6769-401e-944b-f22e2e3bcc26";