Skip to content

Commit 49dbd8d

Browse files
committed
[bugfix] Make sure that Element nodes can be correctly used as external variables for XQuery
1 parent 6074190 commit 49dbd8d

File tree

2 files changed

+212
-24
lines changed

2 files changed

+212
-24
lines changed

exist-core/src/main/java/org/exist/xqj/Marshaller.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.exist.xquery.NameTest;
5353
import org.exist.xquery.XPathException;
5454
import org.exist.xquery.value.*;
55+
import org.w3c.dom.Element;
5556
import org.w3c.dom.Node;
5657
import org.xml.sax.ContentHandler;
5758
import org.xml.sax.SAXException;
@@ -253,17 +254,17 @@ public static Sequence demarshall(NodeImpl node) throws XMLStreamException, XPat
253254
final InMemoryNodeSet values = new InMemoryNodeSet();
254255
node.selectChildren(new NameTest(Type.ELEMENT, VALUE_QNAME), values);
255256
for (final SequenceIterator i = values.iterate(); i.hasNext();) {
256-
final ElementImpl child = (ElementImpl) i.nextItem();
257+
final ElementImpl sxValue = (ElementImpl) i.nextItem();
257258

258259
int type = Type.ITEM;
259-
final String typeName = child.getAttribute(ATTR_TYPE);
260+
final String typeName = sxValue.getAttribute(ATTR_TYPE);
260261
if (!typeName.isEmpty()) {
261262
type = Type.getType(typeName);
262263
}
263264

264265
Item item;
265266
if (Type.subTypeOf(type, Type.NODE)) {
266-
item = (Item) child.getFirstChild();
267+
item = (Item) sxValue.getFirstChild();
267268
if (type == Type.DOCUMENT) {
268269
final DocumentImpl n = (DocumentImpl) item;
269270
final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(n.getExpression());
@@ -278,15 +279,21 @@ public static Sequence demarshall(NodeImpl node) throws XMLStreamException, XPat
278279
}
279280
} else {
280281
final StringBuilder data = new StringBuilder();
281-
Node txt = child.getFirstChild();
282-
while (txt != null) {
283-
if (!(txt.getNodeType() == Node.TEXT_NODE || txt.getNodeType() == Node.CDATA_SECTION_NODE)) {
284-
throw new XMLStreamException("sx:value should only contain text if type is " + typeName);
282+
Node value = sxValue.getFirstChild();
283+
284+
if (value instanceof Element && type == Type.ITEM) {
285+
item = (NodeImpl) value;
286+
287+
} else {
288+
while (value != null) {
289+
if (!(value.getNodeType() == Node.TEXT_NODE || value.getNodeType() == Node.CDATA_SECTION_NODE)) {
290+
throw new XMLStreamException("sx:value should only contain text if type is " + typeName);
291+
}
292+
data.append(value.getNodeValue());
293+
value = value.getNextSibling();
285294
}
286-
data.append(txt.getNodeValue());
287-
txt = txt.getNextSibling();
295+
item = new StringValue(data.toString()).convertTo(type);
288296
}
289-
item = new StringValue(data.toString()).convertTo(type);
290297
}
291298
result.add(item);
292299
}

exist-core/src/test/java/org/exist/http/RESTServiceTest.java

Lines changed: 195 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
import org.exist.test.ExistWebServer;
7171
import org.exist.util.*;
7272
import org.exist.xmldb.XmldbURI;
73+
import org.exist.xquery.XPathException;
74+
import org.exist.xquery.value.Type;
7375
import org.w3c.dom.Attr;
7476
import org.xml.sax.InputSource;
7577
import org.xml.sax.SAXException;
@@ -99,6 +101,7 @@
99101
import static org.junit.Assert.assertFalse;
100102
import static org.junit.Assert.assertNotNull;
101103
import static org.junit.Assert.assertTrue;
104+
import static org.junit.Assert.fail;
102105
import static org.junit.Assume.assumeThat;
103106

104107
/**
@@ -963,6 +966,158 @@ public void queryPostWithExternalVariableStringzSuppliedUntypeds() throws IOExce
963966
queryPostWithExternalVariable(HttpStatus.OK_200, expectedResult, "xs:string*", externalVariable);
964967
}
965968

969+
@Test
970+
public void queryPostWithExternalVariableUntypedSuppliedUntypedElementValue() throws IOException {
971+
final Tuple2<String, String> externalVariable = Tuple(null, "<hello>world</hello>");
972+
queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable);
973+
}
974+
975+
@Test
976+
public void queryPostWithExternalVariableUntypedSuppliedElement() throws IOException {
977+
final Tuple2<String, String> externalVariable = Tuple("element()", "<hello>world</hello>");
978+
queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable);
979+
}
980+
981+
@Test
982+
public void queryPostWithExternalVariableElementNotSupplied() throws IOException {
983+
queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()", null);
984+
}
985+
986+
@Test
987+
public void queryPostWithExternalVariableElementSuppliedEmpty() throws IOException {
988+
final Tuple2<String, String>[] externalVariable = new Tuple2[0];
989+
queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()", externalVariable);
990+
}
991+
992+
@Test
993+
public void queryPostWithExternalVariableElementSuppliedElement() throws IOException {
994+
final Tuple2<String, String> externalVariable = Tuple("element()", "<hello>world</hello>");
995+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()", externalVariable);
996+
}
997+
998+
@Test
999+
public void queryPostWithExternalVariableElementSuppliedElements() throws IOException {
1000+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple(null, "<hello>world</hello>"), Tuple(null, "<goodbye>see you soon</goodbye>") };
1001+
queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()", externalVariable);
1002+
}
1003+
1004+
@Test
1005+
public void queryPostWithExternalVariableElementSuppliedUntyped() throws IOException {
1006+
final Tuple2<String, String> externalVariable = Tuple(null, "<hello>world</hello>");
1007+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()", externalVariable);
1008+
}
1009+
1010+
@Test
1011+
public void queryPostWithExternalVariableUntypedSuppliedUntypedElements() throws IOException {
1012+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple(null, "<hello>world</hello>"), Tuple(null, "<goodbye>see you soon</goodbye>") };
1013+
queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable);
1014+
}
1015+
1016+
@Test
1017+
public void queryPostWithExternalVariableUntypedSuppliedElements() throws IOException {
1018+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple("element()", "<hello>world</hello>"), Tuple("element()", "<goodbye>see you soon</goodbye>") };
1019+
queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable);
1020+
}
1021+
1022+
@Test
1023+
public void queryPostWithExternalVariableOptElementNotSupplied() throws IOException {
1024+
queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()?", null);
1025+
}
1026+
1027+
@Test
1028+
public void queryPostWithExternalVariableOptElementSuppliedEmpty() throws IOException {
1029+
final Tuple2<String, String>[] externalVariable = new Tuple2[0];
1030+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()?", externalVariable);
1031+
}
1032+
1033+
@Test
1034+
public void queryPostWithExternalVariableOptElementSuppliedElement() throws IOException {
1035+
final Tuple2<String, String> externalVariable = Tuple("element()", "<hello>world</hello>");
1036+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()?", externalVariable);
1037+
}
1038+
1039+
@Test
1040+
public void queryPostWithExternalVariableOptElementSuppliedElements() throws IOException {
1041+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple("element()", "<hello>world</hello>"), Tuple("element()", "<goodbye>see you soon</goodbye>") };
1042+
queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()?", externalVariable);
1043+
}
1044+
1045+
@Test
1046+
public void queryPostWithExternalVariableOptElementSuppliedUntyped() throws IOException {
1047+
final Tuple2<String, String> externalVariable = Tuple(null, "<hello>world</hello>");
1048+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()?", externalVariable);
1049+
}
1050+
1051+
@Test
1052+
public void queryPostWithExternalVariableElementsNotSupplied() throws IOException {
1053+
queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()+", null);
1054+
}
1055+
1056+
@Test
1057+
public void queryPostWithExternalVariableElementsSuppliedEmpty() throws IOException {
1058+
final Tuple2<String, String>[] externalVariable = new Tuple2[0];
1059+
queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()+", externalVariable);
1060+
}
1061+
1062+
@Test
1063+
public void queryPostWithExternalVariableElementsString() throws IOException {
1064+
final Tuple2<String, String> externalVariable = Tuple("element()", "<hello>world</hello>");
1065+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()+", externalVariable);
1066+
}
1067+
1068+
@Test
1069+
public void queryPostWithExternalVariableElementsSuppliedElements() throws IOException {
1070+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple("element()", "<hello>world</hello>"), Tuple("element()", "<goodbye>see you soon</goodbye>") };
1071+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()+", externalVariable);
1072+
}
1073+
1074+
@Test
1075+
public void queryPostWithExternalVariableElementsSuppliedUntyped() throws IOException {
1076+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple(null, "<hello>world</hello>") };
1077+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()+", externalVariable);
1078+
}
1079+
1080+
@Test
1081+
public void queryPostWithExternalVariableElementsSuppliedUntypeds() throws IOException {
1082+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple(null, "<hello>world</hello>"), Tuple(null, "<goodbye>see you soon</goodbye>") };
1083+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()+", externalVariable);
1084+
}
1085+
1086+
@Test
1087+
public void queryPostWithExternalVariableElementzNotSupplied() throws IOException {
1088+
queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()*", null);
1089+
}
1090+
1091+
@Test
1092+
public void queryPostWithExternalVariableElementzSuppliedEmpty() throws IOException {
1093+
final Tuple2<String, String>[] externalVariable = new Tuple2[0];
1094+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable);
1095+
}
1096+
1097+
@Test
1098+
public void queryPostWithExternalVariableElementzSuppliedElement() throws IOException {
1099+
final Tuple2<String, String> externalVariable = Tuple("element()", "<hello>world</hello>");
1100+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable);
1101+
}
1102+
1103+
@Test
1104+
public void queryPostWithExternalVariableElementzSuppliedElements() throws IOException {
1105+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple("element()", "<hello>world</hello>"), Tuple("element()", "<goodbye>see you soon</goodbye>") };
1106+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable);
1107+
}
1108+
1109+
@Test
1110+
public void queryPostWithExternalVariableElementszSuppliedUntyped() throws IOException {
1111+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple(null, "<hello>world</hello>") };
1112+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable);
1113+
}
1114+
1115+
@Test
1116+
public void queryPostWithExternalVariableElementzSuppliedUntypeds() throws IOException {
1117+
final Tuple2<String, String>[] externalVariable = new Tuple2[]{ Tuple(null, "<hello>world</hello>"), Tuple(null, "<goodbye>see you soon</goodbye>") };
1118+
queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable);
1119+
}
1120+
9661121
private void queryPostWithExternalVariable(final int expectedResponseCode, @Nullable final String xqExternalVariableType, final Tuple2<String, String>... externalVariableSequence) throws IOException {
9671122
queryPostWithExternalVariable(expectedResponseCode, externalVariableSequence, xqExternalVariableType, externalVariableSequence);
9681123
}
@@ -991,26 +1146,52 @@ private static String buildExistVariableResultSequence(final Tuple2<String, Stri
9911146
final StringBuilder builder = new StringBuilder();
9921147
builder.append("<exist:result xmlns:exist=\"http://exist.sourceforge.net/NS/exist\" xmlns:sx=\"http://exist-db.org/xquery/types/serialized\" exist:count=\"").append(resultSequence.length).append("\" exist:hits=\"").append(resultSequence.length).append("\" exist:start=\"1\">\n");
9931148
for (final Tuple2<String, String> resultSequenceItem : resultSequence) {
994-
builder.append("\t<exist:value");
995-
if (resultSequenceItem._1 != null) {
996-
builder.append(" exist:type=\"").append(resultSequenceItem._1).append("\"");
1149+
1150+
// TODO(AR) When wrap="yes" is set for the eXist-db REST API it does not wrap Node Types in <exist:value> - that is probably a bug in eXist-db that should be fixed - https://github.com/eXist-db/exist/issues/5909
1151+
final boolean nodeType;
1152+
String resultType = resultSequenceItem._1;
1153+
if (resultType == null) {
1154+
nodeType = resultSequenceItem._2.startsWith("<");
1155+
} else {
1156+
nodeType = isNodeType(resultType);
1157+
}
1158+
1159+
if (!nodeType) {
1160+
builder.append("\t<exist:value");
1161+
if (resultSequenceItem._1 != null) {
1162+
builder.append(" exist:type=\"").append(resultSequenceItem._1).append("\"");
1163+
}
1164+
builder.append('>');
9971165
}
998-
builder.append('>');
1166+
9991167
builder.append(resultSequenceItem._2);
1000-
builder.append("</exist:value>\n");
1168+
1169+
if (!nodeType) {
1170+
builder.append("</exist:value>\n");
1171+
}
10011172
}
10021173
builder.append("</exist:result>");
10031174
return builder.toString();
10041175
}
10051176

1177+
private static boolean isNodeType(final String xdmTypeName) {
1178+
try {
1179+
final int xdmType = Type.getType(xdmTypeName);
1180+
return Type.subTypeOf(xdmType, Type.NODE);
1181+
} catch (final XPathException e) {
1182+
fail("Unable to find XDM type value for: " + xdmTypeName);
1183+
return false;
1184+
}
1185+
}
1186+
10061187
private static String buildQueryExternalVariable(@Nullable final String xqExternalVariableType, @Nullable final Tuple2<String, String>... externalVariableSequence) {
10071188
final StringBuilder builder = new StringBuilder();
1008-
builder.append("<query xmlns=\"http://exist.sourceforge.net/NS/exist\" xmlns:sx=\"http://exist-db.org/xquery/types/serialized\" wrap=\"yes\" typed=\"yes\">\n");
1189+
builder.append("<exist:query xmlns:exist=\"http://exist.sourceforge.net/NS/exist\" xmlns:sx=\"http://exist-db.org/xquery/types/serialized\" wrap=\"yes\" typed=\"yes\">\n");
10091190

10101191
if (externalVariableSequence!= null) {
1011-
builder.append("\t<variables>\n");
1012-
builder.append("\t\t<variable>\n");
1013-
builder.append("\t\t\t<qname><prefix>local</prefix><localname>my-variable</localname></qname>");
1192+
builder.append("\t<exist:variables>\n");
1193+
builder.append("\t\t<exist:variable>\n");
1194+
builder.append("\t\t\t<exist:qname><exist:prefix>local</exist:prefix><exist:localname>my-variable</exist:localname></exist:qname>");
10141195
builder.append("\t\t\t<sx:sequence>\n");
10151196
for (final Tuple2<String, String> externalVariableSequenceItem : externalVariableSequence) {
10161197
builder.append("\t\t\t\t<sx:value");
@@ -1022,19 +1203,19 @@ private static String buildQueryExternalVariable(@Nullable final String xqExtern
10221203
builder.append("</sx:value>\n");
10231204
}
10241205
builder.append("\t\t\t</sx:sequence>\n");
1025-
builder.append("\t\t</variable>\n");
1026-
builder.append("\t</variables>\n");
1206+
builder.append("\t\t</exist:variable>\n");
1207+
builder.append("\t</exist:variables>\n");
10271208
}
10281209

1029-
builder.append("\t<text><![CDATA[\n");
1210+
builder.append("\t<exist:text><![CDATA[\n");
10301211
builder.append("declare variable $local:my-variable");
10311212
if (xqExternalVariableType != null) {
10321213
builder.append(" as ").append(xqExternalVariableType);
10331214
}
10341215
builder.append(" external;\n");
10351216
builder.append("$local:my-variable\n");
1036-
builder.append("\t]]></text>\n");
1037-
builder.append("</query>\n");
1217+
builder.append("\t]]></exist:text>\n");
1218+
builder.append("</exist:query>\n");
10381219

10391220
return builder.toString();
10401221
}

0 commit comments

Comments
 (0)