/*
* eXist Open Source Native XML Database
* Copyright (C) 2009 The eXist Project
* http://exist-db.org
*
* This program 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; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.xquery;
import org.exist.test.ExistXmldbEmbeddedServer;
import org.xmldb.api.base.Resource;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.exist.xmldb.EXistResource;
import org.exist.xmldb.XmldbURI;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.CompiledExpression;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.BinaryResource;
import org.xmldb.api.modules.CollectionManagementService;
import org.xmldb.api.modules.XQueryService;
import org.junit.*;
import static org.junit.Assert.*;
/**
* @author wolf
*
*/
public class StoredModuleTest {
@ClassRule
public static final ExistXmldbEmbeddedServer existEmbeddedServer = new ExistXmldbEmbeddedServer(false, true);
private final static String MODULE =
"module namespace itg-modules = \"http://localhost:80/itg/xquery\";\n" +
"declare variable $itg-modules:colls as xs:string+ external;\n" +
"declare variable $itg-modules:coll as xs:string external;\n" +
"declare variable $itg-modules:ordinal as xs:integer external;\n" +
"declare function itg-modules:check-coll() as xs:boolean {\n" +
" if (fn:empty($itg-modules:coll)) then fn:false()\n" +
" else fn:true()\n" +
"};";
private Collection createCollection(String collectionName) throws XMLDBException {
Collection collection = existEmbeddedServer.getRoot().getChildCollection(collectionName);
final CollectionManagementService cmService = (CollectionManagementService) existEmbeddedServer.getRoot().getService("CollectionManagementService", "1.0");
if (collection == null) {
//cmService.removeCollection(collectionName);
cmService.createCollection(collectionName);
}
collection = DatabaseManager.getCollection(XmldbURI.LOCAL_DB + "/" + collectionName, "admin", "");
assertNotNull(collection);
return collection;
}
private void writeModule(Collection collection, String modulename, String module) throws XMLDBException {
BinaryResource res = (BinaryResource) collection.createResource(modulename, "BinaryResource");
((EXistResource) res).setMimeType("application/xquery");
res.setContent(module.getBytes());
collection.storeResource(res);
collection.close();
}
@Test
public void testQuery() throws Exception {
Collection c = createCollection("test");
writeModule(c, "test.xqm", MODULE);
String query = "import module namespace itg-modules = \"http://localhost:80/itg/xquery\" at " +
"\"xmldb:exist://" + XmldbURI.ROOT_COLLECTION + "/test/test.xqm\"; itg-modules:check-coll()";
String cols[] = {"one", "two", "three"};
final XQueryService xqService = (XQueryService) existEmbeddedServer.getRoot().getService("XQueryService", "1.0");
xqService.setNamespace("itg-modules", "http://localhost:80/itg/xquery");
CompiledExpression compiledQuery = xqService.compile(query);
for (int i = 0; i < cols.length; i++) {
xqService.declareVariable("itg-modules:coll", cols[i]);
ResourceSet result = xqService.execute(compiledQuery);
result.getResource(0).getContent();
}
}
@Test
public void testModule1() throws Exception {
String collectionName = "module1";
String module = "module namespace mod1 = 'urn:module1';" +
"declare function mod1:showMe() as xs:string {" +
"'hi from module 1'" +
"};";
String query = "import module namespace mod1 = 'urn:module1' " +
"at 'xmldb:exist:/" + collectionName + "/module1.xqm'; " +
"mod1:showMe()";
Collection c = createCollection(collectionName);
writeModule(c, "module1.xqm", module);
ResourceSet rs = existEmbeddedServer.executeQuery(query);
String r = (String) rs.getResource(0).getContent();
assertEquals("hi from module 1", r);
}
private static final String module2 = "module namespace mod2 = 'urn:module2'; " +
"import module namespace mod3 = 'urn:module3' " +
"at 'module3/module3.xqm'; " +
"declare function mod2:showMe() as xs:string {" +
" mod3:showMe() " +
"};";
private static final String module2b = "module namespace mod2 = 'urn:module2'; " +
"import module namespace mod3 = 'urn:module3' " +
"at 'module3.xqm'; " +
"declare function mod2:showMe() as xs:string {" +
" mod3:showMe() " +
"};";
private static final String module3a = "module namespace mod3 = 'urn:module3';" +
"declare function mod3:showMe() as xs:string {" +
"'hi from module 3a'" +
"};";
private static final String module3b = "module namespace mod3 = 'urn:module3';" +
"import module namespace mod4 = 'urn:module4' " +
"at '../module2/module4.xqm'; " +
"declare function mod3:showMe() as xs:string {" +
"mod4:showMe()" +
"};";
private static final String module4 = "module namespace mod4 = 'urn:module4';" +
"declare function mod4:showMe() as xs:string {" +
"'hi from module 4'" +
"};";
// private static final String module5 = "module namespace mod5 = 'urn:module5';" +
// "declare variable $mod5:testvar := 'variable works' ;"+
// "declare function mod5:showMe() as xs:string {" +
// "concat('hi from module 5: ',$mod5:testvar)" +
// "};";
@Test(expected=XMLDBException.class)
public void testModule23_missingRelativeContext() throws XMLDBException {
String collection2Name = "module2";
String collection3Name = "module2/module3";
String query = "import module namespace mod2 = 'urn:module2' " +
"at 'module2/module2.xqm'; " +
"mod2:showMe()";
Collection c2 = createCollection(collection2Name);
writeModule(c2, "module2.xqm", module2);
Collection c3 = createCollection(collection3Name);
writeModule(c3, "module3.xqm", module3a);
ResourceSet rs = existEmbeddedServer.executeQuery(query);
String r = (String) rs.getResource(0).getContent();
assertEquals("hi from module 3a", r);
}
@Test
public void testRelativeImportDb() throws Exception {
String collection2Name = "module2";
String collection3Name = "module2/module3";
String query = "import module namespace mod2 = 'urn:module2' " +
"at 'xmldb:exist:/" + collection2Name + "/module2.xqm'; " +
"mod2:showMe()";
Collection c2 = createCollection(collection2Name);
writeModule(c2, "module2.xqm", module2);
writeModule(c2, "module3.xqm", module3b);
writeModule(c2, "module4.xqm", module4);
Collection c3 = createCollection(collection3Name);
writeModule(c3, "module3.xqm", module3a);
// test relative module import in subfolder
ResourceSet rs = existEmbeddedServer.executeQuery(query);
String r = (String) rs.getResource(0).getContent();
assertEquals("hi from module 3a", r);
// test relative module import in same folder, and using ".."
writeModule(c2, "module2.xqm", module2b);
rs = existEmbeddedServer.executeQuery(query);
r = (String) rs.getResource(0).getContent();
assertEquals("hi from module 4", r);
}
@Test
public void testRelativeImportFile() throws Exception {
String collection2Name = "module2";
String collection3Name = "module2/module3";
String query = "import module namespace mod2 = 'urn:module2' " +
"at '/test/temp/" + collection2Name + "/module2.xqm'; " +
"mod2:showMe()";
String c2 = "test/temp/" + collection2Name;
writeFile(c2 + "/module2.xqm", module2);
writeFile(c2 + "/module3.xqm", module3b);
writeFile(c2 + "/module4.xqm", module4);
String c3 = "test/temp/" + collection3Name;
writeFile(c3 + "/module3.xqm", module3a);
// test relative module import in subfolder
ResourceSet rs = existEmbeddedServer.executeQuery(query);
String r = (String) rs.getResource(0).getContent();
assertEquals("hi from module 3a", r);
// test relative module import in same folder, and using ".."
writeFile(c2 + "/module2.xqm", module2b);
rs = existEmbeddedServer.executeQuery(query);
r = (String) rs.getResource(0).getContent();
assertEquals("hi from module 4", r);
}
@Test
public void testCircularImports() throws XMLDBException {
final String index_module =
"import module namespace module1 = \"http://test/module1\" at \"xmldb:exist:///db/testCircular/module1.xqy\";" +
"module1:func()";
final String module1_module =
"module namespace module1 = \"http://test/module1\";" +
"import module namespace processor = \"http://test/processor\" at \"processor.xqy\";" +
"declare function module1:func()" +
"{" +
" processor:execute()" +
"};" +
"declare function module1:hello($name as xs:string)" +
"{" +
" <hello>{$name}</hello>" +
"};";
final String processor_module =
"module namespace processor = \"http://test/processor\";" +
"import module namespace impl = \"http://test/processor/impl/exist-db\" at \"impl.xqy\";" +
"declare function processor:execute()" +
"{" +
" impl:execute()" +
"};";
final String impl_module =
"module namespace impl = \"http://test/processor/impl/exist-db\";" +
"import module namespace controller = \"http://test/controller\" at \"controller.xqy\";" +
"declare function impl:execute()" +
"{" +
" controller:index()" +
"};";
final String controller_module =
"module namespace controller = \"http://test/controller\";" +
"import module namespace module1 = \"http://test/module1\" at \"module1.xqy\";" +
"declare function controller:index() as item()*" +
"{" +
" module1:hello(\"world\")" +
"};";
Collection testHome = createCollection("testCircular");
writeModule(testHome, "module1.xqy", module1_module);
writeModule(testHome, "processor.xqy", processor_module);
writeModule(testHome, "impl.xqy", impl_module);
writeModule(testHome, "controller.xqy", controller_module);
existEmbeddedServer.executeQuery(index_module);
}
@Test
public void testLocalVariableDeclarationCallsLocalFunction() throws XMLDBException {
final String index_module =
"xquery version \"1.0\";" +
"import module namespace xqmvc = \"http://scholarsportal.info/xqmvc/core\" at \"xmldb:exist:///db/testLocalVariableDeclaration/module1.xqm\";" +
"xqmvc:function1()";
final String module1_module =
"xquery version \"1.0\";" +
"module namespace xqmvc = \"http://scholarsportal.info/xqmvc/core\";" +
"declare variable $xqmvc:plugin-resource-dir as xs:string := fn:concat('/plugins/', xqmvc:current-plugin(), '/resources');" +
"declare function xqmvc:current-plugin() {" +
"\"somePlugin\"" +
"};" +
"declare function xqmvc:function1() {" +
"\"hello world\"" +
"};";
Collection testHome = createCollection("testLocalVariableDeclaration");
writeModule(testHome, "module1.xqm", module1_module);
existEmbeddedServer.executeQuery(index_module);
}
@Test
public void dyanmicModuleImport_for_same_namespace() throws XMLDBException {
Collection testHome = createCollection("testDynamicModuleImport");
final String module1 =
"xquery version \"1.0\";" +
"module namespace modA = \"http://moda\";" +
"declare function modA:hello() {" +
"\t<hello-from>module1</hello-from>" +
"};";
final String module2 =
"xquery version \"1.0\";" +
"module namespace modA = \"http://moda\";" +
"declare function modA:hello() {" +
"\t<hello-from>module2</hello-from>" +
"};";
final String implModule =
"xquery version \"1.0\";" +
"module namespace impl = \"http://impl\";" +
"declare function impl:execute-module-function($module-namespace as xs:anyURI, $controller-file as xs:anyURI, $function-name as xs:string) {" +
"\tlet $mod := util:import-module($module-namespace, 'pfx', xs:anyURI(fn:concat('xmldb:exist://', $controller-file))) return" +
"\t\tutil:eval(fn:concat('pfx:', $function-name, '()'), false())" +
"};";
final String processorModule =
"xquery version \"1.0\";" +
"module namespace processor = \"http://processor\";" +
"import module namespace impl = \"http://impl\" at \"xmldb:exist://" + testHome.getName() + "/impl.xqm\";" +
"declare function processor:execute-module-function($module-namespace as xs:anyURI, $controller-file as xs:anyURI, $function-name as xs:string) {" +
"\timpl:execute-module-function($module-namespace, $controller-file, $function-name)" +
"};";
writeModule(testHome, "module1.xqm", module1);
writeModule(testHome, "module2.xqm", module2);
writeModule(testHome, "impl.xqm", implModule);
writeModule(testHome, "processor.xqm", processorModule);
final String query1 =
"xquery version \"1.0\";" +
"import module namespace processor = \"http://processor\" at \"xmldb:exist://" + testHome.getName() + "/processor.xqm\";" +
"\tprocessor:execute-module-function(xs:anyURI('http://moda'), xs:anyURI('" + testHome.getName() + "/module1.xqm'), 'hello')";
final ResourceSet rs1 = existEmbeddedServer.executeQuery(query1);
assertEquals(1, rs1.getSize());
Resource r1 = rs1.getIterator().nextResource();
assertEquals("<hello-from>module1</hello-from>", (String)r1.getContent());
final String query2 =
"xquery version \"1.0\";" +
"import module namespace processor = \"http://processor\" at \"xmldb:exist://" + testHome.getName() + "/processor.xqm\";" +
"\tprocessor:execute-module-function(xs:anyURI('http://moda'), xs:anyURI('" + testHome.getName() + "/module2.xqm'), 'hello')";
final ResourceSet rs2 = existEmbeddedServer.executeQuery(query2);
assertEquals(1, rs2.getSize());
Resource r2 = rs2.getIterator().nextResource();
assertEquals("<hello-from>module2</hello-from>", (String)r2.getContent());
}
private void writeFile(String path, String module) throws IOException {
path = path.replace("/", File.separator);
final Path f = Paths.get(path);
final Path dir = f.getParent();
assertTrue (Files.exists(dir) || Files.createDirectories(dir) != null);
assertTrue (Files.isWritable(dir));
assertTrue (Files.isWritable(f) || Files.createFile(f) != null);
try(final PrintWriter writer = new PrintWriter(Files.newBufferedWriter(f))) {
writer.print(module);
}
}
}