diff --git a/exist-core/pom.xml b/exist-core/pom.xml index fa38a45d04..af30839b96 100644 --- a/exist-core/pom.xml +++ b/exist-core/pom.xml @@ -1257,6 +1257,7 @@ src/test/java/org/exist/xquery/functions/validate/JingXsdTest.java src/main/java/org/exist/xquery/functions/validation/Jaxp.java src/test/java/org/exist/xquery/functions/xmldb/DbStore2Test.java + src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java src/main/java/org/exist/xquery/functions/xmldb/XMLDBStore.java src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java src/test/java/org/exist/xquery/functions/xquery3/TryCatchTest.java @@ -1948,6 +1949,7 @@ src/test/java/org/exist/xquery/functions/xmldb/AbstractXMLDBTest.java src/test/java/org/exist/xquery/functions/xmldb/DbStore2Test.java src/test/java/org/exist/xquery/functions/xmldb/XMLDBAuthenticateTest.java + src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java src/main/java/org/exist/xquery/functions/xmldb/XMLDBStore.java src/test/java/org/exist/xquery/functions/xmldb/XMLDBStoreTest.java src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java diff --git a/exist-core/src/main/java/org/exist/http/RESTServer.java b/exist-core/src/main/java/org/exist/http/RESTServer.java index 7b0670fe45..163d27e3d7 100644 --- a/exist-core/src/main/java/org/exist/http/RESTServer.java +++ b/exist-core/src/main/java/org/exist/http/RESTServer.java @@ -936,13 +936,12 @@ public void doPost(final DBBroker broker, final Txn transaction, final HttpServl } final XUpdateProcessor processor = new XUpdateProcessor(broker, docs); + root.toSAX(broker, processor, new Properties()); + final List modifications = processor.getModifications(); long mods = 0; - try(final Reader reader = new StringReader(content)) { - final Modification modifications[] = processor.parse(new InputSource(reader)); - for (Modification modification : modifications) { - mods += modification.process(transaction); - broker.flush(); - } + for (final Modification modification : modifications) { + mods += modification.process(transaction); + broker.flush(); } // FD : Returns an XML doc diff --git a/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java b/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java index f328610ab7..3394ea4403 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java @@ -1,4 +1,28 @@ /* + * Elemental + * Copyright (C) 2024, Evolved Binary Ltd + * + * admin@evolvedbinary.com + * https://www.evolvedbinary.com | https://www.elemental.xyz + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * NOTE: Parts of this file contain code from 'The eXist-db Authors'. + * The original license header is included below. + * + * ===================================================================== + * * eXist-db Open Source Native XML Database * Copyright (C) 2001 The eXist-db Authors * @@ -64,7 +88,8 @@ public class XMLDBModule extends AbstractInternalModule { new FunctionDef(XMLDBLoadFromPattern.signatures[1], XMLDBLoadFromPattern.class), new FunctionDef(XMLDBLoadFromPattern.signatures[2], XMLDBLoadFromPattern.class), new FunctionDef(XMLDBLoadFromPattern.signatures[3], XMLDBLoadFromPattern.class), - new FunctionDef(XMLDBXUpdate.signature, XMLDBXUpdate.class), + new FunctionDef(XMLDBXUpdate.FS_UPDATE[0], XMLDBXUpdate.class), + new FunctionDef(XMLDBXUpdate.FS_UPDATE[1], XMLDBXUpdate.class), new FunctionDef(XMLDBCopy.FS_COPY_COLLECTION[0], XMLDBCopy.class), new FunctionDef(XMLDBCopy.FS_COPY_COLLECTION[1], XMLDBCopy.class), new FunctionDef(XMLDBCopy.FS_COPY_RESOURCE[0], XMLDBCopy.class), diff --git a/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java b/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java index 2953753806..462b49cf93 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java @@ -45,85 +45,117 @@ */ package org.exist.xquery.functions.xmldb; -import org.apache.commons.io.output.StringBuilderWriter; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.exist.dom.QName; -import org.exist.util.serializer.DOMSerializer; -import org.exist.xquery.Cardinality; +import org.exist.EXistException; +import org.exist.collections.Collection; +import org.exist.dom.persistent.*; +import org.exist.security.PermissionDeniedException; +import org.exist.storage.lock.Lock; +import org.exist.util.LockException; +import org.exist.xmldb.XmldbURI; +import org.exist.xquery.BasicFunction; import org.exist.xquery.FunctionSignature; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; -import org.exist.xquery.value.FunctionReturnSequenceType; import org.exist.xquery.value.FunctionParameterSequenceType; import org.exist.xquery.value.IntegerValue; -import org.exist.xquery.value.NodeValue; +import org.exist.xquery.value.Item; import org.exist.xquery.value.Sequence; -import org.exist.xquery.value.SequenceType; import org.exist.xquery.value.Type; -import org.xmldb.api.base.Collection; -import org.xmldb.api.base.XMLDBException; -import org.xmldb.api.modules.XUpdateQueryService; +import org.exist.xupdate.Modification; +import org.exist.xupdate.XUpdateProcessor; +import org.xml.sax.SAXException; + +import javax.annotation.Nullable; +import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.TransformerException; +import java.util.List; import java.util.Properties; +import static org.exist.xquery.FunctionDSL.*; +import static org.exist.xquery.functions.xmldb.XMLDBModule.functionSignatures; + /** - * - * @author wolf - * + * @author Adam Retter */ -public class XMLDBXUpdate extends XMLDBAbstractCollectionManipulator -{ - protected static final Logger logger = LogManager.getLogger(XMLDBXUpdate.class); - - public final static FunctionSignature signature = new FunctionSignature( - new QName("update", XMLDBModule.NAMESPACE_URI, XMLDBModule.PREFIX), - "Processes an XUpdate request, $modifications, against a collection $collection-uri. " - + XMLDBModule.COLLECTION_URI - + "The modifications are passed in a " - + "document conforming to the XUpdate specification. " - + "http://rx4rdf.liminalzone.org/xupdate-wd.html#N1a32e0" - + "The function returns the number of modifications caused by the XUpdate.", - new SequenceType[]{ - new FunctionParameterSequenceType("collection-uri", Type.STRING, Cardinality.EXACTLY_ONE, "The collection URI"), - new FunctionParameterSequenceType("modifications", Type.NODE, Cardinality.EXACTLY_ONE, "The XUpdate modifications to be processed")}, - new FunctionReturnSequenceType(Type.INTEGER, Cardinality.EXACTLY_ONE, "the number of modifications, as xs:integer, caused by the XUpdate")); - - public XMLDBXUpdate(XQueryContext context) { - super(context, signature); +public class XMLDBXUpdate extends BasicFunction { + private static final FunctionParameterSequenceType FS_PARAM_COLLECTION_URI = param("collection-uri", Type.STRING, "The collection URI"); + private static final FunctionParameterSequenceType FS_PARAM_DOCUMENT_URI = param("document-uri", Type.STRING, "The document URI"); + private static final FunctionParameterSequenceType FS_PARAM_MODIFICATIONS = param("modifications", Type.NODE, "The XUpdate modifications to be processed"); + + private static final String FS_UPDATE_NAME = "update"; + static final FunctionSignature[] FS_UPDATE = functionSignatures( + FS_UPDATE_NAME, + "Processes an XUpdate request, $modifications, against a collection $collection-uri. " + + XMLDBModule.COLLECTION_URI + + "The modifications are passed in a " + + "document conforming to the XUpdate specification. " + + "https://xmldb-org.sourceforge.net/xupdate/xupdate-wd.html" + + "The function returns the number of modifications caused by the XUpdate.", + returns(Type.INTEGER, "The number of modifications, as an xs:integer, caused by the XUpdate"), + arities( + arity( + FS_PARAM_COLLECTION_URI, + FS_PARAM_MODIFICATIONS + ), + arity( + FS_PARAM_COLLECTION_URI, + FS_PARAM_DOCUMENT_URI, + FS_PARAM_MODIFICATIONS + ) + ) + ); + + public XMLDBXUpdate(final XQueryContext context, final FunctionSignature functionSignature) { + super(context, functionSignature); } - /* (non-Javadoc) - * @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence) - */ - public Sequence evalWithCollection(Collection c, Sequence[] args, Sequence contextSequence) - throws XPathException { - final NodeValue data = (NodeValue) args[1].itemAt(0); - final String xupdate; - try (final StringBuilderWriter writer = new StringBuilderWriter()) { - final Properties properties = new Properties(); - properties.setProperty(OutputKeys.INDENT, "yes"); - final DOMSerializer serializer = new DOMSerializer(writer, properties); - serializer.serialize(data.getNode()); - xupdate = writer.toString(); - } catch(final TransformerException e) { - logger.debug("Exception while serializing XUpdate document", e); - throw new XPathException(this, "Exception while serializing XUpdate document: " + e.getMessage(), e); - } - - long modifications = 0; - try { - final XUpdateQueryService service = (XUpdateQueryService)c.getService("XUpdateQueryService", "1.0"); - logger.debug("Processing XUpdate request: {}", xupdate); - modifications = service.update(xupdate); - } catch(final XMLDBException e) { - throw new XPathException(this, "Exception while processing xupdate: " + e.getMessage(), e); - } - - context.getRootExpression().resetState(false); - return new IntegerValue(this, modifications); + @Override + public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException { + final String collectionUri = args[0].itemAt(0).getStringValue(); + @Nullable final String documentUri; + final Item modifications; + if (args.length == 3) { + documentUri = args[1].itemAt(0).getStringValue(); + modifications = args[2].itemAt(0); + } else { + documentUri = null; + modifications = args[1].itemAt(0); + } + + final MutableDocumentSet documentSet = new DefaultDocumentSet(); + try (final Collection collection = context.getBroker().openCollection(XmldbURI.create(collectionUri), Lock.LockMode.READ_LOCK)) { + if (documentUri == null) { + collection.allDocs(context.getBroker(), documentSet, true); + + } else { + try (final LockedDocument lockedDocument = collection.getDocumentWithLock(context.getBroker(), XmldbURI.create(documentUri), Lock.LockMode.READ_LOCK)) { + + // NOTE: early release of Collection lock inline with Asymmetrical Locking scheme + collection.close(); + + final DocumentImpl doc = lockedDocument == null ? null : lockedDocument.getDocument(); + if (doc == null) { + throw new XPathException(this, "Resource not found: " + documentUri); + } + documentSet.add(doc); + } + } + + final XUpdateProcessor processor = new XUpdateProcessor(context.getBroker(), documentSet); + modifications.toSAX(context.getBroker(), processor, new Properties()); + final List modificationList = processor.getModifications(); + long mods = 0; + for (final Modification modification : modificationList) { + mods += modification.process(context.getBroker().getCurrentTransaction()); + context.getBroker().flush(); + } + + context.getRootExpression().resetState(false); + + return new IntegerValue(this, mods); + + } catch (final PermissionDeniedException | LockException | EXistException | ParserConfigurationException | SAXException e) { + throw new XPathException(this, e); + } } } diff --git a/exist-core/src/main/java/org/exist/xupdate/XUpdateProcessor.java b/exist-core/src/main/java/org/exist/xupdate/XUpdateProcessor.java index 98fd444d06..911a2d3cd9 100644 --- a/exist-core/src/main/java/org/exist/xupdate/XUpdateProcessor.java +++ b/exist-core/src/main/java/org/exist/xupdate/XUpdateProcessor.java @@ -867,16 +867,20 @@ public void startDTD(final String name, final String publicId, final String syst public void startEntity(final String name) throws SAXException { } + public List getModifications() { + return modifications; + } + public void reset() { - if (this.whiteSpaceHandling != null) { - this.whiteSpaceHandling.clear(); - } - this.whiteSpaceHandlingIdx = 0; + if (this.whiteSpaceHandling != null) { + this.whiteSpaceHandling.clear(); + } + this.whiteSpaceHandlingIdx = 0; this.inModification = false; this.inAttribute = false; this.modification = null; this.builder.reset(); - this.doc = null; + this.doc = null; this.contents = null; if (this.stack != null) { this.stack.clear();