Skip to content

Commit 7d4d5cc

Browse files
committed
[featue] Add a new xmldb:update#3 function that allows to update a single document
1 parent 6941cfc commit 7d4d5cc

File tree

4 files changed

+135
-71
lines changed

4 files changed

+135
-71
lines changed

exist-core/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,7 @@
12861286
<include>src/test/java/org/exist/xquery/functions/validate/JingXsdTest.java</include>
12871287
<include>src/main/java/org/exist/xquery/functions/validation/Jaxp.java</include>
12881288
<include>src/test/java/org/exist/xquery/functions/xmldb/DbStore2Test.java</include>
1289+
<include>src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java</include>
12891290
<include>src/main/java/org/exist/xquery/functions/xmldb/XMLDBStore.java</include>
12901291
<include>src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java</include>
12911292
<include>src/test/java/org/exist/xquery/functions/xquery3/TryCatchTest.java</include>
@@ -1991,6 +1992,7 @@
19911992
<exclude>src/test/java/org/exist/xquery/functions/xmldb/AbstractXMLDBTest.java</exclude>
19921993
<exclude>src/test/java/org/exist/xquery/functions/xmldb/DbStore2Test.java</exclude>
19931994
<exclude>src/test/java/org/exist/xquery/functions/xmldb/XMLDBAuthenticateTest.java</exclude>
1995+
<exclude>src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java</exclude>
19941996
<exclude>src/main/java/org/exist/xquery/functions/xmldb/XMLDBStore.java</exclude>
19951997
<exclude>src/test/java/org/exist/xquery/functions/xmldb/XMLDBStoreTest.java</exclude>
19961998
<exclude>src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java</exclude>

exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBModule.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*
21+
* NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+
* The original license header is included below.
23+
*
24+
* =====================================================================
25+
*
226
* eXist-db Open Source Native XML Database
327
* Copyright (C) 2001 The eXist-db Authors
428
*
@@ -64,7 +88,8 @@ public class XMLDBModule extends AbstractInternalModule {
6488
new FunctionDef(XMLDBLoadFromPattern.signatures[1], XMLDBLoadFromPattern.class),
6589
new FunctionDef(XMLDBLoadFromPattern.signatures[2], XMLDBLoadFromPattern.class),
6690
new FunctionDef(XMLDBLoadFromPattern.signatures[3], XMLDBLoadFromPattern.class),
67-
new FunctionDef(XMLDBXUpdate.signature, XMLDBXUpdate.class),
91+
new FunctionDef(XMLDBXUpdate.FS_UPDATE[0], XMLDBXUpdate.class),
92+
new FunctionDef(XMLDBXUpdate.FS_UPDATE[1], XMLDBXUpdate.class),
6893
new FunctionDef(XMLDBCopy.FS_COPY_COLLECTION[0], XMLDBCopy.class),
6994
new FunctionDef(XMLDBCopy.FS_COPY_COLLECTION[1], XMLDBCopy.class),
7095
new FunctionDef(XMLDBCopy.FS_COPY_RESOURCE[0], XMLDBCopy.class),

exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java

Lines changed: 98 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -45,85 +45,117 @@
4545
*/
4646
package org.exist.xquery.functions.xmldb;
4747

48-
import org.apache.commons.io.output.StringBuilderWriter;
49-
import org.apache.logging.log4j.LogManager;
50-
import org.apache.logging.log4j.Logger;
51-
52-
import org.exist.dom.QName;
53-
import org.exist.util.serializer.DOMSerializer;
54-
import org.exist.xquery.Cardinality;
48+
import org.exist.EXistException;
49+
import org.exist.collections.Collection;
50+
import org.exist.dom.persistent.*;
51+
import org.exist.security.PermissionDeniedException;
52+
import org.exist.storage.lock.Lock;
53+
import org.exist.util.LockException;
54+
import org.exist.xmldb.XmldbURI;
55+
import org.exist.xquery.BasicFunction;
5556
import org.exist.xquery.FunctionSignature;
5657
import org.exist.xquery.XPathException;
5758
import org.exist.xquery.XQueryContext;
58-
import org.exist.xquery.value.FunctionReturnSequenceType;
5959
import org.exist.xquery.value.FunctionParameterSequenceType;
6060
import org.exist.xquery.value.IntegerValue;
61-
import org.exist.xquery.value.NodeValue;
61+
import org.exist.xquery.value.Item;
6262
import org.exist.xquery.value.Sequence;
63-
import org.exist.xquery.value.SequenceType;
6463
import org.exist.xquery.value.Type;
65-
import org.xmldb.api.base.Collection;
66-
import org.xmldb.api.base.XMLDBException;
67-
import org.xmldb.api.modules.XUpdateQueryService;
64+
import org.exist.xupdate.Modification;
65+
import org.exist.xupdate.XUpdateProcessor;
66+
import org.xml.sax.SAXException;
67+
68+
import javax.annotation.Nullable;
69+
import javax.xml.parsers.ParserConfigurationException;
6870

69-
import javax.xml.transform.OutputKeys;
70-
import javax.xml.transform.TransformerException;
71+
import java.util.List;
7172
import java.util.Properties;
7273

74+
import static org.exist.xquery.FunctionDSL.*;
75+
import static org.exist.xquery.functions.xmldb.XMLDBModule.functionSignatures;
76+
7377
/**
74-
*
75-
* @author wolf
76-
*
78+
* @author <a href="mailto:[email protected]">Adam Retter</a>
7779
*/
78-
public class XMLDBXUpdate extends XMLDBAbstractCollectionManipulator
79-
{
80-
protected static final Logger logger = LogManager.getLogger(XMLDBXUpdate.class);
81-
82-
public final static FunctionSignature signature = new FunctionSignature(
83-
new QName("update", XMLDBModule.NAMESPACE_URI, XMLDBModule.PREFIX),
84-
"Processes an XUpdate request, $modifications, against a collection $collection-uri. "
85-
+ XMLDBModule.COLLECTION_URI
86-
+ "The modifications are passed in a "
87-
+ "document conforming to the XUpdate specification. "
88-
+ "http://rx4rdf.liminalzone.org/xupdate-wd.html#N1a32e0"
89-
+ "The function returns the number of modifications caused by the XUpdate.",
90-
new SequenceType[]{
91-
new FunctionParameterSequenceType("collection-uri", Type.STRING, Cardinality.EXACTLY_ONE, "The collection URI"),
92-
new FunctionParameterSequenceType("modifications", Type.NODE, Cardinality.EXACTLY_ONE, "The XUpdate modifications to be processed")},
93-
new FunctionReturnSequenceType(Type.INTEGER, Cardinality.EXACTLY_ONE, "the number of modifications, as xs:integer, caused by the XUpdate"));
94-
95-
public XMLDBXUpdate(XQueryContext context) {
96-
super(context, signature);
80+
public class XMLDBXUpdate extends BasicFunction {
81+
private static final FunctionParameterSequenceType FS_PARAM_COLLECTION_URI = param("collection-uri", Type.STRING, "The collection URI");
82+
private static final FunctionParameterSequenceType FS_PARAM_DOCUMENT_URI = param("document-uri", Type.STRING, "The document URI");
83+
private static final FunctionParameterSequenceType FS_PARAM_MODIFICATIONS = param("modifications", Type.NODE, "The XUpdate modifications to be processed");
84+
85+
private static final String FS_UPDATE_NAME = "update";
86+
static final FunctionSignature[] FS_UPDATE = functionSignatures(
87+
FS_UPDATE_NAME,
88+
"Processes an XUpdate request, $modifications, against a collection $collection-uri. "
89+
+ XMLDBModule.COLLECTION_URI
90+
+ "The modifications are passed in a "
91+
+ "document conforming to the XUpdate specification. "
92+
+ "https://xmldb-org.sourceforge.net/xupdate/xupdate-wd.html"
93+
+ "The function returns the number of modifications caused by the XUpdate.",
94+
returns(Type.INTEGER, "The number of modifications, as an xs:integer, caused by the XUpdate"),
95+
arities(
96+
arity(
97+
FS_PARAM_COLLECTION_URI,
98+
FS_PARAM_MODIFICATIONS
99+
),
100+
arity(
101+
FS_PARAM_COLLECTION_URI,
102+
FS_PARAM_DOCUMENT_URI,
103+
FS_PARAM_MODIFICATIONS
104+
)
105+
)
106+
);
107+
108+
public XMLDBXUpdate(final XQueryContext context, final FunctionSignature functionSignature) {
109+
super(context, functionSignature);
97110
}
98111

99-
/* (non-Javadoc)
100-
* @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence)
101-
*/
102-
public Sequence evalWithCollection(Collection c, Sequence[] args, Sequence contextSequence)
103-
throws XPathException {
104-
final NodeValue data = (NodeValue) args[1].itemAt(0);
105-
final String xupdate;
106-
try (final StringBuilderWriter writer = new StringBuilderWriter()) {
107-
final Properties properties = new Properties();
108-
properties.setProperty(OutputKeys.INDENT, "yes");
109-
final DOMSerializer serializer = new DOMSerializer(writer, properties);
110-
serializer.serialize(data.getNode());
111-
xupdate = writer.toString();
112-
} catch(final TransformerException e) {
113-
logger.debug("Exception while serializing XUpdate document", e);
114-
throw new XPathException(this, "Exception while serializing XUpdate document: " + e.getMessage(), e);
115-
}
116-
117-
long modifications = 0;
118-
try {
119-
final XUpdateQueryService service = c.getService(XUpdateQueryService.class);
120-
logger.debug("Processing XUpdate request: {}", xupdate);
121-
modifications = service.update(xupdate);
122-
} catch(final XMLDBException e) {
123-
throw new XPathException(this, "Exception while processing xupdate: " + e.getMessage(), e);
124-
}
125-
126-
context.getRootExpression().resetState(false);
127-
return new IntegerValue(this, modifications);
112+
@Override
113+
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
114+
final String collectionUri = args[0].itemAt(0).getStringValue();
115+
@Nullable final String documentUri;
116+
final Item modifications;
117+
if (args.length == 3) {
118+
documentUri = args[1].itemAt(0).getStringValue();
119+
modifications = args[2].itemAt(0);
120+
} else {
121+
documentUri = null;
122+
modifications = args[1].itemAt(0);
123+
}
124+
125+
final MutableDocumentSet documentSet = new DefaultDocumentSet();
126+
try (final Collection collection = context.getBroker().openCollection(XmldbURI.create(collectionUri), Lock.LockMode.READ_LOCK)) {
127+
if (documentUri == null) {
128+
collection.allDocs(context.getBroker(), documentSet, true);
129+
130+
} else {
131+
try (final LockedDocument lockedDocument = collection.getDocumentWithLock(context.getBroker(), XmldbURI.create(documentUri), Lock.LockMode.READ_LOCK)) {
132+
133+
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
134+
collection.close();
135+
136+
final DocumentImpl doc = lockedDocument == null ? null : lockedDocument.getDocument();
137+
if (doc == null) {
138+
throw new XPathException(this, "Resource not found: " + documentUri);
139+
}
140+
documentSet.add(doc);
141+
}
142+
}
143+
144+
final XUpdateProcessor processor = new XUpdateProcessor(context.getBroker(), documentSet);
145+
modifications.toSAX(context.getBroker(), processor, new Properties());
146+
final List<Modification> modificationList = processor.getModifications();
147+
long mods = 0;
148+
for (final Modification modification : modificationList) {
149+
mods += modification.process(context.getBroker().getCurrentTransaction());
150+
context.getBroker().flush();
151+
}
152+
153+
context.getRootExpression().resetState(false);
154+
155+
return new IntegerValue(this, mods);
156+
157+
} catch (final PermissionDeniedException | LockException | EXistException | ParserConfigurationException | SAXException e) {
158+
throw new XPathException(this, e);
159+
}
128160
}
129161
}

exist-core/src/main/java/org/exist/xupdate/XUpdateProcessor.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -867,16 +867,21 @@ public void startDTD(final String name, final String publicId, final String syst
867867
public void startEntity(final String name) throws SAXException {
868868
}
869869

870+
public List<Modification> getModifications() {
871+
return modifications;
872+
}
873+
870874
public void reset() {
871-
if (this.whiteSpaceHandling != null) {
875+
if (this.whiteSpaceHandling != null) {
872876
this.whiteSpaceHandling.clear();
873-
}
874-
this.whiteSpaceHandlingIdx = 0;
877+
}
878+
this.whiteSpaceHandlingIdx = 0;
879+
875880
this.inModification = false;
876881
this.inAttribute = false;
877882
this.modification = null;
878883
this.builder.reset();
879-
this.doc = null;
884+
this.doc = null;
880885
this.contents = null;
881886
if (this.stack != null) {
882887
this.stack.clear();

0 commit comments

Comments
 (0)