Skip to content

Commit 3011d1f

Browse files
authored
Merge pull request #5586 from line-o/backport/5529
[6.x.x] allow module imports in one-off xqueries
2 parents 71a82ca + 98b2dbc commit 3011d1f

File tree

4 files changed

+1186
-32
lines changed

4 files changed

+1186
-32
lines changed

exist-core/src/main/java/org/exist/source/SourceFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public class SourceFactory {
111111
&& ((location.startsWith("/db") && !Files.exists(Paths.get(firstPathSegment(location))))
112112
|| (contextPath != null && contextPath.startsWith("/db") && !Files.exists(Paths.get(firstPathSegment(contextPath)))))) {
113113
final XmldbURI pathUri;
114-
if (contextPath == null) {
114+
if (contextPath == null || ".".equals(contextPath)) {
115115
pathUri = XmldbURI.create(location);
116116
} else {
117117
pathUri = XmldbURI.create(contextPath).append(location);

exist-core/src/main/java/org/exist/xquery/XQueryContext.java

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -574,46 +574,50 @@ public Optional<ExistRepository> getRepository() {
574574
// the repo and its eXist handler
575575
final Optional<ExistRepository> repo = getRepository();
576576

577+
if (!repo.isPresent()) {
578+
return null;
579+
}
577580
// try an internal module
578-
if (repo.isPresent()) {
579-
final Module jMod = repo.get().resolveJavaModule(namespace, this);
580-
if (jMod != null) {
581-
return jMod;
582-
}
581+
final Module jMod = repo.get().resolveJavaModule(namespace, this);
582+
if (jMod != null) {
583+
return jMod;
583584
}
584585

585586
// try an eXist-specific module
586-
if (repo.isPresent()) {
587-
final Path resolved = repo.get().resolveXQueryModule(namespace);
588-
589-
// use the resolved file or return null
590-
if (resolved != null) {
591-
592-
String location = "";
593-
594-
try {
595-
596-
// see if the src exists in the database and if so, use that instead
597-
Source src = repo.get().resolveStoredXQueryModuleFromDb(getBroker(), resolved);
598-
if (src != null) {
599-
// NOTE(AR) set the location of the module to import relative to this module's load path - so that transient imports of the imported module will resolve correctly!
600-
location = Paths.get(XmldbURI.create(moduleLoadPath).getCollectionPath()).relativize(Paths.get(((DBSource)src).getDocumentPath().getCollectionPath())).toString();
601-
} else {
602-
// else, fallback to the one from the filesystem
603-
src = new FileSource(resolved, false);
604-
}
587+
final Path resolved = repo.get().resolveXQueryModule(namespace);
605588

606-
// build a module object from the source
607-
final ExternalModule module = compileOrBorrowModule(prefix, namespace, location, src);
608-
return module;
589+
if (resolved == null) {
590+
return null;
591+
}
609592

610-
} catch (final PermissionDeniedException e) {
611-
throw new XPathException(e.getMessage(), e);
593+
// use the resolved file
594+
try {
595+
// see if the src exists in the database and if so, use that instead
596+
Source src = repo.get().resolveStoredXQueryModuleFromDb(getBroker(), resolved);
597+
String location = "";
598+
if (src == null) {
599+
// fallback to load the source from the filesystem
600+
src = new FileSource(resolved, false);
601+
} else {
602+
final String sourceCollection = ((DBSource)src).getDocumentPath().getCollectionPath();
603+
if (".".equals(moduleLoadPath)) {
604+
// module is a string passed to the xquery context, has therefore no location of its own
605+
location = sourceCollection;
606+
} else {
607+
// NOTE(AR) set the location of the module to import relative to this module's load path
608+
// - so that transient imports of the imported module will resolve correctly!
609+
final Path collectionPath = Paths.get(XmldbURI.create(moduleLoadPath).getCollectionPath());
610+
final Path sourcePath = Paths.get(sourceCollection);
611+
location = collectionPath.relativize(sourcePath).toString();
612612
}
613613
}
614-
}
615614

616-
return null;
615+
// build a module object from the source
616+
return compileOrBorrowModule(prefix, namespace, location, src);
617+
618+
} catch (final PermissionDeniedException | IllegalArgumentException e) {
619+
throw new XPathException(e.getMessage(), e);
620+
}
617621
}
618622

619623
/**
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
6+
* http://www.exist-db.org
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; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
package org.exist.xquery;
23+
24+
import com.evolvedbinary.j8fu.Either;
25+
26+
import org.exist.EXistException;
27+
import org.exist.security.PermissionDeniedException;
28+
import org.exist.storage.BrokerPool;
29+
import org.exist.storage.DBBroker;
30+
import org.exist.test.ExistEmbeddedServer;
31+
import org.exist.xquery.value.Sequence;
32+
import org.exist.xquery.value.StringValue;
33+
34+
import org.junit.ClassRule;
35+
import org.junit.Test;
36+
37+
import java.net.URISyntaxException;
38+
import java.nio.file.Path;
39+
import java.nio.file.Paths;
40+
41+
import static com.evolvedbinary.j8fu.Either.Left;
42+
import static com.evolvedbinary.j8fu.Either.Right;
43+
import static com.ibm.icu.impl.Assert.fail;
44+
import static org.exist.test.XQueryAssertions.assertThatXQResult;
45+
import static org.exist.test.XQueryAssertions.assertXQStaticError;
46+
import static org.hamcrest.Matchers.equalTo;
47+
48+
/**
49+
* Ensure library module imports work in one-off queries
50+
* needs functx to be installed => conf.xml => triggers => autodeploy
51+
*
52+
* @author <a href="mailto:[email protected]">Juri Leino</a>
53+
*/
54+
public class ModuleImportTest {
55+
@ClassRule
56+
public static final ExistEmbeddedServer server = new ExistEmbeddedServer(null, getConfigFile(), null, false, true);
57+
58+
protected static Either<XPathException, CompiledXQuery> compileQuery(final String string) throws EXistException, PermissionDeniedException {
59+
final BrokerPool pool = server.getBrokerPool();
60+
final XQuery xqueryService = pool.getXQueryService();
61+
try (final DBBroker broker = pool.getBroker()) {
62+
try {
63+
return Right(xqueryService.compile(new XQueryContext(broker.getDatabase()), string));
64+
} catch (final XPathException e) {
65+
return Left(e);
66+
}
67+
}
68+
}
69+
70+
protected static Either<XPathException, Sequence> executeQuery(final String string) throws EXistException, PermissionDeniedException {
71+
final BrokerPool pool = server.getBrokerPool();
72+
final XQuery xqueryService = pool.getXQueryService();
73+
try (final DBBroker broker = pool.getBroker()) {
74+
try {
75+
return Right(xqueryService.execute(broker, string, null));
76+
} catch (final XPathException e) {
77+
return Left(e);
78+
}
79+
}
80+
}
81+
82+
private static Path getConfigFile() {
83+
final ClassLoader loader = ModuleImportTest.class.getClassLoader();
84+
final char separator = System.getProperty("file.separator").charAt(0);
85+
final String packagePath = ModuleImportTest.class.getPackage().getName().replace('.', separator);
86+
87+
try {
88+
return Paths.get(loader.getResource(packagePath + separator + "conf.xml").toURI());
89+
} catch (final URISyntaxException e) {
90+
fail(e);
91+
return null;
92+
}
93+
}
94+
95+
@Test
96+
public void importLibraryWithoutLocation() throws EXistException, PermissionDeniedException {
97+
final Sequence expected = new StringValue("xs:integer");
98+
99+
final String query = "import module namespace functx='http://www.functx.com';" +
100+
"functx:atomic-type(4)";
101+
final Either<XPathException, Sequence> actual = executeQuery(query);
102+
103+
assertThatXQResult(actual, equalTo(expected));
104+
}
105+
@Test
106+
public void importLibraryFromDbLocation() throws EXistException, PermissionDeniedException {
107+
final Sequence expected = new StringValue("xs:integer");
108+
109+
final String query = "import module namespace functx='http://www.functx.com'" +
110+
" at '/db/system/repo/functx-1.0.1/functx/functx.xq';" +
111+
"functx:atomic-type(4)";
112+
final Either<XPathException, Sequence> actual = executeQuery(query);
113+
114+
assertThatXQResult(actual, equalTo(expected));
115+
}
116+
117+
@Test
118+
public void importLibraryFromXMLDBLocation() throws EXistException, PermissionDeniedException {
119+
final Sequence expected = new StringValue("xs:integer");
120+
121+
final String query = "import module namespace functx='http://www.functx.com'" +
122+
" at 'xmldb:/db/system/repo/functx-1.0.1/functx/functx.xq';" +
123+
"functx:atomic-type(4)";
124+
final Either<XPathException, Sequence> actual = executeQuery(query);
125+
126+
assertThatXQResult(actual, equalTo(expected));
127+
}
128+
129+
@Test
130+
public void importLibraryFromXMLDBLocationDoubleSlash() throws EXistException, PermissionDeniedException {
131+
final Sequence expected = new StringValue("xs:integer");
132+
133+
final String query = "import module namespace functx='http://www.functx.com'" +
134+
" at 'xmldb:///db/system/repo/functx-1.0.1/functx/functx.xq';" +
135+
"functx:atomic-type(4)";
136+
final Either<XPathException, Sequence> actual = executeQuery(query);
137+
138+
assertThatXQResult(actual, equalTo(expected));
139+
}
140+
141+
@Test
142+
public void importLibraryFromExistXMLDBLocation() throws EXistException, PermissionDeniedException {
143+
final Sequence expected = new StringValue("xs:integer");
144+
145+
final String query = "import module namespace functx='http://www.functx.com'" +
146+
" at 'xmldb:exist:///db/system/repo/functx-1.0.1/functx/functx.xq';" +
147+
"functx:atomic-type(4)";
148+
final Either<XPathException, Sequence> actual = executeQuery(query);
149+
150+
assertThatXQResult(actual, equalTo(expected));
151+
}
152+
153+
@Test
154+
public void importLibraryFromUnknownLocation() throws EXistException, PermissionDeniedException {
155+
156+
final String query = "import module namespace functx='http://www.functx.com'" +
157+
" at 'unknown:///db/system/repo/functx-1.0.1/functx/functx.xq';" +
158+
"functx:atomic-type(4)";
159+
final String expectedMessage = "error found while loading module functx: Source for module 'http://www.functx.com' not found module location hint URI 'unknown:///db/system/repo/functx-1.0.1/functx/functx.xq'.";
160+
161+
assertXQStaticError(ErrorCodes.XQST0059, -1,-1, expectedMessage, compileQuery(query));
162+
}
163+
164+
@Test
165+
public void importLibraryFromRelativeLocation() throws EXistException, PermissionDeniedException {
166+
final String query = "import module namespace functx='http://www.functx.com'" +
167+
" at './functx.xq';" +
168+
"functx:atomic-type(4)";
169+
final String expectedMessage = "error found while loading module functx: Source for module 'http://www.functx.com' not found module location hint URI './functx.xq'.";
170+
171+
assertXQStaticError(ErrorCodes.XQST0059, -1,-1, expectedMessage, compileQuery(query));
172+
}
173+
174+
}

0 commit comments

Comments
 (0)