diff --git a/elemental-parent/pom.xml b/elemental-parent/pom.xml index c94611c7a6..fb6d87cc18 100644 --- a/elemental-parent/pom.xml +++ b/elemental-parent/pom.xml @@ -374,9 +374,9 @@ 3.4.0 - com.code54.mojo - buildversion-plugin - 1.0.3 + com.evolvedbinary.maven.plugins + buildversion-maven-plugin + 2.0.0 org.apache.maven.plugins @@ -620,8 +620,8 @@ - com.code54.mojo - buildversion-plugin + com.evolvedbinary.maven.plugins + buildversion-maven-plugin validate @@ -814,19 +814,4 @@ - - - clojars.org - https://clojars.org/repo - - - - - - central-ossrh-staging - Central Portal - OSSRH Staging API - https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ - - - diff --git a/exist-core/src/main/java/org/exist/Namespaces.java b/exist-core/src/main/java/org/exist/Namespaces.java index ace3469b7f..e134a7b786 100644 --- a/exist-core/src/main/java/org/exist/Namespaces.java +++ b/exist-core/src/main/java/org/exist/Namespaces.java @@ -60,6 +60,7 @@ public interface Namespaces { String DTD_NS = XMLConstants.XML_DTD_NS_URI; String SCHEMA_NS = XMLConstants.W3C_XML_SCHEMA_NS_URI; + String SCHEMA_NS_PREFIX = "xs"; String SCHEMA_DATATYPES_NS = "http://www.w3.org/2001/XMLSchema-datatypes"; String SCHEMA_INSTANCE_NS = XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI; diff --git a/exist-core/src/main/java/org/exist/client/CommandlineOptions.java b/exist-core/src/main/java/org/exist/client/CommandlineOptions.java index 060e5881b0..0db5b0616a 100644 --- a/exist-core/src/main/java/org/exist/client/CommandlineOptions.java +++ b/exist-core/src/main/java/org/exist/client/CommandlineOptions.java @@ -83,6 +83,10 @@ public class CommandlineOptions { .description("do not make embedded mode available") .defaultValue(false) .build(); + private static final Argument noAutoDeployArg = optionArgument("-a", "--no-auto-deploy") + .description("Disable auto-deployment of EXPath Packages") + .defaultValue(false) + .build(); /* gui arguments */ @@ -168,7 +172,7 @@ private static Optional optUri(final ParsedArguments parsedArguments, public static CommandlineOptions parse(final String[] args) throws ArgumentException, URISyntaxException { final ParsedArguments arguments = CommandLineParser - .withArguments(userArg, passwordArg, useSslArg, embeddedArg, embeddedConfigArg, noEmbeddedModeArg) + .withArguments(userArg, passwordArg, useSslArg, embeddedArg, embeddedConfigArg, noEmbeddedModeArg, noAutoDeployArg) .andArguments(noGuiArg, guiQueryDialogArg) .andArguments(mkColArg, rmColArg, setColArg) .andArguments(parseDocsArg, getDocArg, rmDocArg) @@ -189,6 +193,7 @@ public static CommandlineOptions parse(final String[] args) throws ArgumentExcep final boolean embedded = getBool(arguments, embeddedArg); final Optional embeddedConfig = getPathOpt(arguments, embeddedConfigArg); final boolean noEmbeddedMode = getBool(arguments, noEmbeddedModeArg); + final boolean noAutoDeploy = getBool(arguments, noAutoDeployArg); final boolean startGUI = !getBool(arguments, noGuiArg); final boolean openQueryGUI = getBool(arguments, guiQueryDialogArg); @@ -233,6 +238,7 @@ public static CommandlineOptions parse(final String[] args) throws ArgumentExcep embedded, embeddedConfig, noEmbeddedMode, + noAutoDeploy, startGUI, openQueryGUI, mkCol, @@ -252,7 +258,7 @@ public static CommandlineOptions parse(final String[] args) throws ArgumentExcep ); } - public CommandlineOptions(boolean quiet, boolean verbose, Optional outputFile, Map options, Optional username, Optional password, boolean useSSL, boolean embedded, Optional embeddedConfig, boolean noEmbeddedMode, boolean startGUI, boolean openQueryGUI, Optional mkCol, Optional rmCol, Optional setCol, List parseDocs, Optional getDoc, Optional rmDoc, Optional xpath, List queryFiles, Optional howManyResults, Optional traceQueriesFile, Optional setDoc, Optional xupdateFile, boolean reindex, boolean reindexRecurse) { + public CommandlineOptions(boolean quiet, boolean verbose, Optional outputFile, Map options, Optional username, Optional password, boolean useSSL, boolean embedded, Optional embeddedConfig, boolean noEmbeddedMode, boolean noAutoDeploy, boolean startGUI, boolean openQueryGUI, Optional mkCol, Optional rmCol, Optional setCol, List parseDocs, Optional getDoc, Optional rmDoc, Optional xpath, List queryFiles, Optional howManyResults, Optional traceQueriesFile, Optional setDoc, Optional xupdateFile, boolean reindex, boolean reindexRecurse) { this.quiet = quiet; this.verbose = verbose; this.outputFile = outputFile; @@ -263,6 +269,7 @@ public CommandlineOptions(boolean quiet, boolean verbose, Optional outputF this.embedded = embedded; this.embeddedConfig = embeddedConfig; this.noEmbeddedMode = noEmbeddedMode; + this.noAutoDeploy = noAutoDeploy; this.startGUI = startGUI; this.openQueryGUI = openQueryGUI; this.mkCol = mkCol; @@ -292,6 +299,7 @@ public CommandlineOptions(boolean quiet, boolean verbose, Optional outputF final boolean embedded; final Optional embeddedConfig; final boolean noEmbeddedMode; + final boolean noAutoDeploy; final boolean startGUI; final boolean openQueryGUI; diff --git a/exist-core/src/main/java/org/exist/client/InteractiveClient.java b/exist-core/src/main/java/org/exist/client/InteractiveClient.java index e9b39f175f..7b4ac352af 100644 --- a/exist-core/src/main/java/org/exist/client/InteractiveClient.java +++ b/exist-core/src/main/java/org/exist/client/InteractiveClient.java @@ -147,6 +147,7 @@ public class InteractiveClient { public static final String CREATE_DATABASE = "create-database"; public static final String LOCAL_MODE = "local-mode-opt"; public static final String NO_EMBED_MODE = "NO_EMBED_MODE"; + public static final String NO_AUTO_DEPLOY = "no-autodeploy"; // values protected static final String EDIT_CMD = "emacsclient -t $file"; @@ -155,6 +156,7 @@ public class InteractiveClient { protected static final String SSL_ENABLE_DEFAULT = "FALSE"; protected static final String LOCAL_MODE_DEFAULT = "FALSE"; protected static final String NO_EMBED_MODE_DEFAULT = "FALSE"; + protected static final String NO_AUTO_DEPLOY_DEFAULT = "FALSE"; protected static final String USER_DEFAULT = SecurityManager.DBA_USER; protected static final String driver = "org.exist.xmldb.DatabaseImpl"; @@ -173,6 +175,7 @@ public class InteractiveClient { defaultProps.setProperty(PERMISSIONS, "false"); defaultProps.setProperty(EXPAND_XINCLUDES, "true"); defaultProps.setProperty(SSL_ENABLE, SSL_ENABLE_DEFAULT); + defaultProps.setProperty(NO_AUTO_DEPLOY, NO_AUTO_DEPLOY_DEFAULT); } protected static final int colSizes[] = new int[]{10, 10, 10, -1}; @@ -333,6 +336,7 @@ protected void connect() throws Exception { // Configure database database.setProperty(CREATE_DATABASE, "true"); database.setProperty(SSL_ENABLE, properties.getProperty(SSL_ENABLE)); + database.setProperty(NO_AUTO_DEPLOY, properties.getProperty(NO_AUTO_DEPLOY)); // secure empty configuration final String configProp = properties.getProperty(InteractiveClient.CONFIGURATION); @@ -1971,6 +1975,9 @@ protected void setPropertiesFromCommandLine(final CommandlineOptions options, fi if(options.noEmbeddedMode) { props.setProperty(NO_EMBED_MODE, "TRUE"); } + if (options.noAutoDeploy) { + props.setProperty(NO_AUTO_DEPLOY, "TRUE"); + } } /** diff --git a/exist-core/src/main/java/org/exist/collections/MutableCollection.java b/exist-core/src/main/java/org/exist/collections/MutableCollection.java index 59ed38b500..7975d5350b 100644 --- a/exist-core/src/main/java/org/exist/collections/MutableCollection.java +++ b/exist-core/src/main/java/org/exist/collections/MutableCollection.java @@ -481,7 +481,7 @@ public MutableDocumentSet allDocs(final DBBroker broker, final MutableDocumentSe if(recursive && subColls != null) { // process the child collections for(final XmldbURI subCol : subColls) { - try(final Collection child = broker.openCollection(subCol, NO_LOCK)) { // NOTE: the recursive call below to child.addDocs will take a lock + try(final Collection child = broker.openCollection(subCol, INTENTION_READ)) { // NOTE: the recursive call below to child.addDocs will take a lock //A collection may have been removed in the meantime, so check first if(child != null) { child.allDocs(broker, docs, recursive, lockMap); 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 d98d9f5998..f3e4a809e5 100644 --- a/exist-core/src/main/java/org/exist/http/RESTServer.java +++ b/exist-core/src/main/java/org/exist/http/RESTServer.java @@ -107,7 +107,6 @@ import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.AttributesImpl; -import org.xml.sax.helpers.XMLFilterImpl; import xyz.elemental.mediatype.MediaType; import xyz.elemental.mediatype.MediaTypeResolver; @@ -336,17 +335,37 @@ public void doGet(final DBBroker broker, final Txn transaction, final HttpServle query = getParameter(request, Query); } } - final String _var = getParameter(request, Variables); - List /**/ namespaces = null; - ElementImpl variables = null; + + @Nullable final String _contextItem = getParameter(request, Context_Item); + @Nullable ElementImpl contextItemParam = null; + try { + if (_contextItem != null) { + contextItemParam = parseXML(broker.getBrokerPool(), _contextItem); + } + } catch (final SAXException e) { + final XPathException x = new XPathException(contextItemParam != null ? contextItemParam.getExpression() : null, e.toString()); + writeXPathException(response, HttpServletResponse.SC_BAD_REQUEST, UTF_8.name(), query, path, x); + } + + @Nullable final String _defaultCollection = getParameter(request, Default_Collection); + @Nullable ElementImpl defaultCollectionParam = null; + try { + if (_defaultCollection != null) { + defaultCollectionParam = parseXML(broker.getBrokerPool(), _defaultCollection); + } + } catch (final SAXException e) { + final XPathException x = new XPathException(defaultCollectionParam != null ? defaultCollectionParam.getExpression() : null, e.toString()); + writeXPathException(response, HttpServletResponse.SC_BAD_REQUEST, UTF_8.name(), query, path, x); + } + + @Nullable final String _var = getParameter(request, Variables); + @Nullable ElementImpl variablesParam = null; try { if (_var != null) { - final NamespaceExtractor nsExtractor = new NamespaceExtractor(); - variables = parseXML(broker.getBrokerPool(), _var, nsExtractor); - namespaces = nsExtractor.getNamespaces(); + variablesParam = parseXML(broker.getBrokerPool(), _var); } } catch (final SAXException e) { - final XPathException x = new XPathException(variables != null ? variables.getExpression() : null, e.toString()); + final XPathException x = new XPathException(variablesParam != null ? variablesParam.getExpression() : null, e.toString()); writeXPathException(response, HttpServletResponse.SC_BAD_REQUEST, UTF_8.name(), query, path, x); } @@ -421,7 +440,7 @@ public void doGet(final DBBroker broker, final Txn transaction, final HttpServle if (query != null) { // query parameter specified, search method does all the rest of the work try { - search(broker, transaction, query, path, namespaces, variables, howmany, start, typed, outputProperties, + search(broker, transaction, query, path, null, contextItemParam, defaultCollectionParam, variablesParam, howmany, start, typed, outputProperties, wrap, cache, request, response); } catch (final XPathException e) { @@ -766,15 +785,16 @@ public void doPost(final DBBroker broker, final Txn transaction, final HttpServl int howmany = 10; int start = 1; boolean typed = false; - ElementImpl variables = null; + @Nullable ElementImpl contextItemParam = null; + @Nullable ElementImpl defaultCollectionParam = null; + @Nullable ElementImpl variablesParam = null; boolean enclose = true; boolean cache = false; String query = null; try { final String content = getRequestContent(request); - final NamespaceExtractor nsExtractor = new NamespaceExtractor(); - final ElementImpl root = parseXML(broker.getBrokerPool(), content, nsExtractor); + final ElementImpl root = parseXML(broker.getBrokerPool(), content); final String rootNS = root.getNamespaceURI(); if (rootNS != null && rootNS.equals(Namespaces.EXIST_NS)) { @@ -852,8 +872,14 @@ public void doPost(final DBBroker broker, final Txn transaction, final HttpServl } query = buf.toString(); + } else if (Context_Item.xmlKey().equals(child.getLocalName())) { + contextItemParam = (ElementImpl) child; + + } else if (Default_Collection.xmlKey().equals(child.getLocalName())) { + defaultCollectionParam = (ElementImpl) child; + } else if (Variables.xmlKey().equals(child.getLocalName())) { - variables = (ElementImpl) child; + variablesParam = (ElementImpl) child; } else if (Properties.xmlKey().equals(child.getLocalName())) { Node node = child.getFirstChild(); @@ -882,7 +908,7 @@ public void doPost(final DBBroker broker, final Txn transaction, final HttpServl if (query != null) { try { - search(broker, transaction, query, path, nsExtractor.getNamespaces(), variables, + search(broker, transaction, query, path, null, contextItemParam, defaultCollectionParam, variablesParam, howmany, start, typed, outputProperties, enclose, cache, request, response); } catch (final XPathException e) { @@ -979,22 +1005,19 @@ public void doPost(final DBBroker broker, final Txn transaction, final HttpServl } } - private ElementImpl parseXML(final BrokerPool pool, final String content, - final NamespaceExtractor nsExtractor) - throws SAXException, IOException { + private ElementImpl parseXML(final BrokerPool pool, final String content) throws SAXException, IOException { final InputSource src = new InputSource(new StringReader(content)); final XMLReaderPool parserPool = pool.getParserPool(); XMLReader reader = null; try { reader = parserPool.borrowXMLReader(); final SAXAdapter adapter = new SAXAdapter((Expression) null); - nsExtractor.setContentHandler(adapter); + + reader.setContentHandler(adapter); reader.setProperty(Namespaces.SAX_LEXICAL_HANDLER, adapter); - nsExtractor.setParent(reader); - nsExtractor.parse(src); + reader.parse(src); final Document doc = adapter.getDocument(); - return (ElementImpl) doc.getDocumentElement(); } finally { if (reader != null) { @@ -1003,42 +1026,14 @@ private ElementImpl parseXML(final BrokerPool pool, final String content, } } - private class NamespaceExtractor extends XMLFilterImpl { - - final List namespaces = new ArrayList<>(); - - @Override - public void startPrefixMapping(final String prefix, final String uri) - throws SAXException { - if (!Namespaces.EXIST_NS.equals(uri)) { - final Namespace ns = new Namespace(prefix, uri); - namespaces.add(ns); - } - super.startPrefixMapping(prefix, uri); - } - - public List getNamespaces() { - return namespaces; - } - } - public static class Namespace { - - private final String prefix; - private final String uri; + final String prefix; + final String uri; public Namespace(final String prefix, final String uri) { this.prefix = prefix; this.uri = uri; } - - public String getPrefix() { - return prefix; - } - - public String getUri() { - return uri; - } } /** @@ -1309,7 +1304,9 @@ private String getRequestContent(final HttpServletRequest request) throws IOExce * @param query the XQuery * @param path the path of the request * @param namespaces any XQuery namespace bindings - * @param variables any XQuery variable bindings + * @param contextItemParam optional XQuery Context Item + * @param defaultCollectionParam optional XQuery Default Collection + * @param variablesParam any XQuery variable bindings * @param howmany the number of items in the results to return * @param start the start position in the results to return * @param typed whether the result nodes should be typed @@ -1324,11 +1321,13 @@ private String getRequestContent(final HttpServletRequest request) throws IOExce * @throws XPathException if the XQuery raises an error */ protected void search(final DBBroker broker, final Txn transaction, final String query, - final String path, final List namespaces, - final ElementImpl variables, final int howmany, final int start, - final boolean typed, final Properties outputProperties, - final boolean wrap, final boolean cache, - final HttpServletRequest request, + final String path, @Nullable final List namespaces, + @Nullable final ElementImpl contextItemParam, + @Nullable final ElementImpl defaultCollectionParam, + @Nullable final ElementImpl variablesParam, final int howmany, + final int start, final boolean typed, + final Properties outputProperties, final boolean wrap, + final boolean cache, final HttpServletRequest request, final HttpServletResponse response) throws BadRequestException, PermissionDeniedException, XPathException { @@ -1398,10 +1397,14 @@ protected void search(final DBBroker broker, final Txn transaction, final String compilationTime = 0; } - declareVariables(context, variables, request, response); + setupDefaultCollection(context, defaultCollectionParam); + declareVariables(context, variablesParam, request, response); + + @Nullable final Item contextItem = extractContextItem(contextItemParam); + final Sequence contextSequence = contextItem != null ? new ValueSequence(contextItem) : null; final long executeStart = System.currentTimeMillis(); - final Sequence resultSequence = xquery.execute(broker, compiled, null, outputProperties); + final Sequence resultSequence = xquery.execute(broker, compiled, contextSequence, outputProperties); final long executionTime = System.currentTimeMillis() - executeStart; if (LOG.isDebugEnabled()) { @@ -1430,35 +1433,80 @@ protected void search(final DBBroker broker, final Txn transaction, final String } } - private void declareNamespaces(final XQueryContext context, - final List namespaces) throws XPathException { - + private void declareNamespaces(final XQueryContext context, @Nullable final List namespaces) throws XPathException { if (namespaces == null) { return; } for (final Namespace ns : namespaces) { - context.declareNamespace(ns.getPrefix(), ns.getUri()); + context.declareNamespace(ns.prefix, ns.uri); + } + } + + /** + * Extract the Context Item from the Element parameter. + * + * @param contextItem a parameter specifying the Context Item for the XQuery, or null + * + * @throws XPathException if an error occurs extracting the Context Item. + */ + private @Nullable Item extractContextItem(@Nullable final ElementImpl contextItem) throws XPathException { + if (contextItem == null) { + return null; + } + + @Nullable final NodeImpl value = contextItem.getFirstChild(new NameTest(Type.ELEMENT, Marshaller.VALUE_ELEMENT_QNAME)); + if (value == null) { + return null; + } + + try { + return Marshaller.demarshallValue(null, (ElementImpl) value); + } catch (final XMLStreamException xe) { + throw new XPathException((Expression) null, xe.toString()); + } + } + + private void setupDefaultCollection(final XQueryContext context, @Nullable final ElementImpl defaultCollectionParam) throws XPathException { + if (defaultCollectionParam == null) { + return; + } + + @Nullable final NodeImpl value = defaultCollectionParam.getFirstChild(new NameTest(Type.ELEMENT, Marshaller.SEQUENCE_ELEMENT_QNAME)); + if (value == null) { + return; + } + + try { + @Nullable final Sequence sequence = Marshaller.demarshall(context, value); + if (sequence != null) { + context.addDynamicallyAvailableCollection("", (broker, txn, uri) -> sequence); + } + } catch (final XMLStreamException xe) { + throw new XPathException((Expression) null, xe.toString()); } } /** * Pass the request, response and session objects to the XQuery context. * - * @param context - * @param request - * @param response - * @throws XPathException + * @param context the context for the XQuery + * @param variables variable bindings for the XQuery, or null + * @param request the HTTP request + * @param response the HTTP response + * + * @throws XPathException if an error occurs declaring variables */ private HttpRequestWrapper declareVariables(final XQueryContext context, - final ElementImpl variables, final HttpServletRequest request, + @Nullable final ElementImpl variables, + final HttpServletRequest request, final HttpServletResponse response) throws XPathException { final HttpRequestWrapper reqw = new HttpRequestWrapper(request, formEncoding, containerEncoding); final ResponseWrapper respw = new HttpResponseWrapper(response); context.setHttpContext(new XQueryContext.HttpContext(reqw, respw)); - //enable EXQuery Request Module (if present) + // enable EXQuery Request Module (if present) try { if(xqueryContextExqueryRequestAttribute != null && cstrHttpServletRequestAdapter != null) { final HttpRequest exqueryRequestAdapter = cstrHttpServletRequestAdapter.apply(request, () -> (String)context.getBroker().getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY)); @@ -1529,7 +1577,7 @@ private void declareExternalAndXQJVariables(final XQueryContext context, } // get serialized sequence - final NodeImpl value = variable.getFirstChild(new NameTest(Type.ELEMENT, Marshaller.SEQUENCE_ELEMENT_QNAME)); + @Nullable final NodeImpl value = variable.getFirstChild(new NameTest(Type.ELEMENT, Marshaller.SEQUENCE_ELEMENT_QNAME)); final Sequence sequence; try { sequence = value == null ? Sequence.EMPTY_SEQUENCE : Marshaller.demarshall(context, value); @@ -2050,10 +2098,10 @@ protected void writeCollection(final HttpServletResponse response, serializer.setOutput(writer, defaultProperties); serializer.startDocument(); - serializer.startPrefixMapping("exist", Namespaces.EXIST_NS); + serializer.startPrefixMapping(Namespaces.EXIST_NS_PREFIX, Namespaces.EXIST_NS); if (wrap) { - serializer.startElement(Namespaces.EXIST_NS, "result", "exist:result", null); + serializer.startElement(Namespaces.EXIST_NS, "result", Namespaces.EXIST_NS_PREFIX + ":result", null); } final AttributesImpl attrs = new AttributesImpl(); @@ -2073,8 +2121,7 @@ protected void writeCollection(final HttpServletResponse response, addPermissionAttributes(attrs, collection.getPermissionsNoLock()); - serializer.startElement(Namespaces.EXIST_NS, "collection", - "exist:collection", attrs); + serializer.startElement(Namespaces.EXIST_NS, "collection", Namespaces.EXIST_NS_PREFIX + ":collection", attrs); for (final Iterator i = collection.collectionIterator(broker); i.hasNext();) { final XmldbURI child = i.next(); @@ -2097,8 +2144,8 @@ protected void writeCollection(final HttpServletResponse response, } addPermissionAttributes(attrs, childCollection.getPermissionsNoLock()); - serializer.startElement(Namespaces.EXIST_NS, "collection", "exist:collection", attrs); - serializer.endElement(Namespaces.EXIST_NS, "collection", "exist:collection"); + serializer.startElement(Namespaces.EXIST_NS, "collection", Namespaces.EXIST_NS_PREFIX + ":collection", attrs); + serializer.endElement(Namespaces.EXIST_NS, "collection", Namespaces.EXIST_NS_PREFIX + ":collection"); } } @@ -2135,15 +2182,15 @@ protected void writeCollection(final HttpServletResponse response, } addPermissionAttributes(attrs, doc.getPermissions()); - serializer.startElement(Namespaces.EXIST_NS, "resource", "exist:resource", attrs); - serializer.endElement(Namespaces.EXIST_NS, "resource", "exist:resource"); + serializer.startElement(Namespaces.EXIST_NS, "resource", Namespaces.EXIST_NS_PREFIX + ":resource", attrs); + serializer.endElement(Namespaces.EXIST_NS, "resource", Namespaces.EXIST_NS_PREFIX + ":resource"); } } - serializer.endElement(Namespaces.EXIST_NS, "collection", "exist:collection"); + serializer.endElement(Namespaces.EXIST_NS, "collection", Namespaces.EXIST_NS_PREFIX + ":collection"); if (wrap) { - serializer.endElement(Namespaces.EXIST_NS, "result", "exist:result"); + serializer.endElement(Namespaces.EXIST_NS, "result", Namespaces.EXIST_NS_PREFIX + ":result"); } serializer.endDocument(); diff --git a/exist-core/src/main/java/org/exist/http/RESTServerParameter.java b/exist-core/src/main/java/org/exist/http/RESTServerParameter.java index 31afa0bd49..1fea532519 100644 --- a/exist-core/src/main/java/org/exist/http/RESTServerParameter.java +++ b/exist-core/src/main/java/org/exist/http/RESTServerParameter.java @@ -25,7 +25,7 @@ * Enumeration of each Parameter * used by the RESTServer * - * @author Adam Retter + * @author Adam Retter */ enum RESTServerParameter { @@ -76,12 +76,44 @@ enum RESTServerParameter { * encoding? = string * method? = string> * (exist:text, + * exist:context-item?, + * exist:default-collection? * exist:variables?, * exist:properties?) * */ Query, + /** + * Can be used in either the Query String of a GET request + * or in the body of a POST request to specify a value + * for the XQuery Context item. + * + * Contexts: GET, POST + * + * The value of this prarameter, is an XML element with the format + * + * + * (sx:value) + * + */ + Context_Item, + + /** + * Can be used in either the Query String of a GET request + * or in the body of a POST request to specify the values + * for the Default Collection of the XQuery Dynamic Context. + * + * Contexts: GET, POST + * + * The value of this prarameter, is an XML element with the format + * + * + * (sx:sequence) + * + */ + Default_Collection, + /** * Can be used in either the Query String of a GET request * or in the body of a POST request to specify values for diff --git a/exist-core/src/main/java/org/exist/jetty/JettyStart.java b/exist-core/src/main/java/org/exist/jetty/JettyStart.java index ce5e23e20a..a524dd8470 100644 --- a/exist-core/src/main/java/org/exist/jetty/JettyStart.java +++ b/exist-core/src/main/java/org/exist/jetty/JettyStart.java @@ -65,12 +65,18 @@ import org.exist.storage.BrokerPool; import org.exist.util.ConfigurationHelper; import org.exist.util.FileUtils; +import org.exist.util.OSUtil; import org.exist.util.SingleInstanceConfiguration; +import org.exist.util.SystemExitCodes; import org.exist.validation.XmlLibraryChecker; import org.exist.xmldb.DatabaseImpl; import org.exist.xmldb.ShutdownListener; import org.xmldb.api.DatabaseManager; import org.xmldb.api.base.Database; +import se.softhouse.jargo.Argument; +import se.softhouse.jargo.ArgumentException; +import se.softhouse.jargo.CommandLineParser; +import se.softhouse.jargo.ParsedArguments; import java.io.IOException; import java.io.InputStream; @@ -83,7 +89,10 @@ import java.util.*; import java.util.stream.Collectors; +import static org.exist.repo.AutoDeploymentTrigger.AUTODEPLOY_PROPERTY; +import static org.exist.util.ArgumentUtil.getBool; import static org.exist.util.ThreadUtils.newGlobalThread; +import static se.softhouse.jargo.Arguments.*; /** * This class provides a main method to start Jetty with eXist. It registers shutdown @@ -108,6 +117,19 @@ public class JettyStart extends Observable implements LifeCycle.Listener { private final static int STATUS_STOPPING = 2; private final static int STATUS_STOPPED = 3; + /* general arguments */ + private static final Argument jettyConfigFilePath = stringArgument() + .description("Path to Jetty Config File") + .build(); + private static final Argument existConfigFilePath = stringArgument() + .description("Path to Elemental Config File") + .build(); + private static final Argument noAutoDeployArg = optionArgument("-a", "--no-auto-deploy") + .description("Disable auto-deployment of EXPath Packages") + .defaultValue(false) + .build(); + private static final Argument helpArg = helpArgument("-h", "--help"); + @GuardedBy("this") private int status = STATUS_STOPPED; @GuardedBy("this") private Optional shutdownHookThread = Optional.empty(); @GuardedBy("this") private int primaryPort = 8080; @@ -116,11 +138,26 @@ public class JettyStart extends Observable implements LifeCycle.Listener { public static void main(final String[] args) { try { CompatibleJavaVersionCheck.checkForCompatibleJavaVersion(); + + final ParsedArguments arguments = CommandLineParser + .withArguments(jettyConfigFilePath, existConfigFilePath) + .andArguments(noAutoDeployArg, helpArg) + .programName("startup" + (OSUtil.IS_WINDOWS ? ".bat" : ".sh")) + .parse(args); + + final boolean noAutoDeploy = getBool(arguments, noAutoDeployArg); + if (noAutoDeploy) { + System.setProperty(AUTODEPLOY_PROPERTY, "off"); + } + } catch (final StartException e) { if (e.getMessage() != null && !e.getMessage().isEmpty()) { System.err.println(e.getMessage()); } System.exit(e.getErrorCode()); + } catch (final ArgumentException e) { + System.out.println(e.getMessageAndUsage()); + System.exit(SystemExitCodes.INVALID_ARGUMENT_EXIT_CODE); } final JettyStart start = new JettyStart(); diff --git a/exist-core/src/main/java/org/exist/storage/serializers/Serializer.java b/exist-core/src/main/java/org/exist/storage/serializers/Serializer.java index 628927d7ad..bf5a5472a9 100644 --- a/exist-core/src/main/java/org/exist/storage/serializers/Serializer.java +++ b/exist-core/src/main/java/org/exist/storage/serializers/Serializer.java @@ -52,6 +52,7 @@ import java.util.*; import javax.annotation.Nullable; +import javax.xml.XMLConstants; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Templates; @@ -1080,14 +1081,17 @@ public void toSAX(final Sequence seq, int start, final int count, final boolean attrs.addAttribute(ATTR_SESSION_ID, outputProperties.getProperty(PROPERTY_SESSION_ID)); } attrs.addAttribute(ATTR_COMPILATION_TIME_QNAME, Long.toString(compilationTime)); - attrs.addAttribute(ATTR_EXECUTION_TIME_QNAME, Long.toString(compilationTime)); + attrs.addAttribute(ATTR_EXECUTION_TIME_QNAME, Long.toString(executionTime)); if (!documentStarted) { receiver.startDocument(); documentStarted = true; } if (wrap) { - receiver.startPrefixMapping("exist", Namespaces.EXIST_NS); + receiver.startPrefixMapping(Namespaces.EXIST_NS_PREFIX, Namespaces.EXIST_NS); + if (typed) { + receiver.startPrefixMapping(Namespaces.SCHEMA_NS_PREFIX, Namespaces.SCHEMA_NS); + } receiver.startElement(ELEM_RESULT_QNAME, attrs); } @@ -1102,7 +1106,10 @@ public void toSAX(final Sequence seq, int start, final int count, final boolean if (wrap) { receiver.endElement(ELEM_RESULT_QNAME); - receiver.endPrefixMapping("exist"); + if (typed) { + receiver.endPrefixMapping(Namespaces.SCHEMA_NS_PREFIX); + } + receiver.endPrefixMapping(Namespaces.EXIST_NS_PREFIX); } receiver.endDocument(); } @@ -1174,7 +1181,10 @@ public void toSAX(final Item item, final boolean wrap, final boolean typed) thro } if (wrap) { - receiver.startPrefixMapping("exist", Namespaces.EXIST_NS); + receiver.startPrefixMapping(Namespaces.EXIST_NS_PREFIX, Namespaces.EXIST_NS); + if (typed) { + receiver.startPrefixMapping(Namespaces.SCHEMA_NS_PREFIX, Namespaces.SCHEMA_NS); + } receiver.startElement(ELEM_RESULT_QNAME, attrs); } @@ -1182,7 +1192,10 @@ public void toSAX(final Item item, final boolean wrap, final boolean typed) thro if (wrap) { receiver.endElement(ELEM_RESULT_QNAME); - receiver.endPrefixMapping("exist"); + if (typed) { + receiver.endPrefixMapping(Namespaces.SCHEMA_NS_PREFIX); + } + receiver.endPrefixMapping(Namespaces.EXIST_NS_PREFIX); } receiver.endDocument(); diff --git a/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java b/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java index 108a6d4be6..bdfae3061a 100644 --- a/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java +++ b/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java @@ -52,6 +52,8 @@ public class ExistEmbeddedServer extends ExternalResource { private static final Logger LOG = LogManager.getLogger(ExistEmbeddedServer.class); + public static final String USE_TEMPORARY_STORAGE_PROPERTY = "exist.use-temporary-storage"; + private final Optional instanceName; private final Optional configFile; private final Optional configProperties; @@ -94,7 +96,7 @@ public ExistEmbeddedServer(final String instanceName, final Path configFile, fin this(instanceName, configFile, configProperties, false, false); } - public ExistEmbeddedServer(@Nullable final String instanceName, @Nullable final Path configFile, @Nullable final Properties configProperties, @Nullable final boolean disableAutoDeploy, @Nullable final boolean useTemporaryStorage) { + public ExistEmbeddedServer(@Nullable final String instanceName, @Nullable final Path configFile, @Nullable final Properties configProperties, final boolean disableAutoDeploy, final boolean useTemporaryStorage) { this.instanceName = Optional.ofNullable(instanceName); this.configFile = Optional.ofNullable(configFile); this.configProperties = Optional.ofNullable(configProperties); @@ -141,7 +143,8 @@ public void startDb() throws DatabaseConfigurationException, EXistException, IOE } }); - if (useTemporaryStorage) { + final boolean propUseTemporaryStorage = Boolean.parseBoolean(System.getProperty(USE_TEMPORARY_STORAGE_PROPERTY, "false")); + if (useTemporaryStorage || propUseTemporaryStorage) { if (!temporaryStorage.isPresent()) { this.temporaryStorage = Optional.of(Files.createTempDirectory("org.exist.test.ExistEmbeddedServer")); } @@ -196,7 +199,8 @@ public void stopDb(final boolean clearTemporaryStorage) { // clear instance variables pool = null; - if(useTemporaryStorage && temporaryStorage.isPresent() && clearTemporaryStorage) { + final boolean propUseTemporaryStorage = Boolean.parseBoolean(System.getProperty(USE_TEMPORARY_STORAGE_PROPERTY, "false")); + if((useTemporaryStorage || propUseTemporaryStorage) && temporaryStorage.isPresent() && clearTemporaryStorage) { FileUtils.deleteQuietly(temporaryStorage.get()); temporaryStorage = Optional.empty(); } diff --git a/exist-core/src/main/java/org/exist/test/ExistWebServer.java b/exist-core/src/main/java/org/exist/test/ExistWebServer.java index da058038fc..00bfcf9d7a 100644 --- a/exist-core/src/main/java/org/exist/test/ExistWebServer.java +++ b/exist-core/src/main/java/org/exist/test/ExistWebServer.java @@ -48,6 +48,8 @@ public class ExistWebServer extends ExternalResource { private static final Logger LOG = LogManager.getLogger(ExistWebServer.class); + public static final String USE_TEMPORARY_STORAGE_PROPERTY = "exist.use-temporary-storage"; + private static final String CONFIG_PROP_FILES = "org.exist.db-connection.files"; private static final String CONFIG_PROP_JOURNAL_DIR = "org.exist.db-connection.recovery.journal-dir"; @@ -113,7 +115,8 @@ protected void before() throws Throwable { } if (server == null) { - if(useTemporaryStorage) { + final boolean propUseTemporaryStorage = Boolean.parseBoolean(System.getProperty(USE_TEMPORARY_STORAGE_PROPERTY, "false")); + if(useTemporaryStorage || propUseTemporaryStorage) { this.temporaryStorage = Optional.of(Files.createTempDirectory("org.exist.test.ExistWebServer")); final String absTemporaryStorage = temporaryStorage.get().toAbsolutePath().toString(); System.setProperty(CONFIG_PROP_FILES, absTemporaryStorage); @@ -166,7 +169,8 @@ protected void after() { server.shutdown(); server = null; - if(useTemporaryStorage && temporaryStorage.isPresent()) { + final boolean propUseTemporaryStorage = Boolean.parseBoolean(System.getProperty(USE_TEMPORARY_STORAGE_PROPERTY, "false")); + if((useTemporaryStorage || propUseTemporaryStorage) && temporaryStorage.isPresent()) { FileUtils.deleteQuietly(temporaryStorage.get()); temporaryStorage = Optional.empty(); System.clearProperty(CONFIG_PROP_JOURNAL_DIR); diff --git a/exist-core/src/main/java/org/exist/util/Collations.java b/exist-core/src/main/java/org/exist/util/Collations.java index 9cee3cfc9e..f3e3a7a7ac 100644 --- a/exist-core/src/main/java/org/exist/util/Collations.java +++ b/exist-core/src/main/java/org/exist/util/Collations.java @@ -274,6 +274,7 @@ public class Collations { } else if (uri.startsWith("java:")) { // java class specified: this should be a subclass of // com.ibm.icu.text.RuleBasedCollator + // TODO(AR) RuleBasedCollator is a final class - we should only need to sub-class Collator.class final String uriClassName = uri.substring("java:".length()); try { final Class collatorClass = Class.forName(uriClassName); diff --git a/exist-core/src/main/java/org/exist/util/Configuration.java b/exist-core/src/main/java/org/exist/util/Configuration.java index 4c1d44fe9c..0b576890ef 100644 --- a/exist-core/src/main/java/org/exist/util/Configuration.java +++ b/exist-core/src/main/java/org/exist/util/Configuration.java @@ -166,16 +166,27 @@ public Configuration(String configFilename, Optional existHomeDirname) thr // firstly, try to read the configuration from a file within the // classpath try { - is = Configuration.class.getClassLoader().getResourceAsStream(configFilename); - if(is != null) { - LOG.info("Reading configuration from classloader"); + // 1. we try the root of the class hierarchy + is = Configuration.class.getClassLoader().getResourceAsStream(configFilename); + if (is != null) { configFilePath = Optional.of(Paths.get(Configuration.class.getClassLoader().getResource(configFilename).toURI())); + LOG.info("Reading configuration from Class Loader: " + configFilePath.get()); + } + + if (is == null) { + // 2. we try the package org.exist.util + is = Configuration.class.getResourceAsStream(configFilename); + if (is != null) { + configFilePath = Optional.of(Paths.get(Configuration.class.getResource(configFilename).toURI())); + LOG.info("Reading configuration from Classpath org.exist.util: " + configFilePath.get()); + } } - } catch(final Exception e) { + + } catch (final Exception e) { // EB: ignore and go forward, e.g. in case there is an absolute // file name for configFileName - LOG.debug( e ); + LOG.debug(e); } // otherwise, secondly try to read configuration from file. Guess the diff --git a/exist-core/src/main/java/org/exist/xmldb/DatabaseImpl.java b/exist-core/src/main/java/org/exist/xmldb/DatabaseImpl.java index 08529a5af7..7d16374f1b 100644 --- a/exist-core/src/main/java/org/exist/xmldb/DatabaseImpl.java +++ b/exist-core/src/main/java/org/exist/xmldb/DatabaseImpl.java @@ -50,6 +50,8 @@ import java.util.Map; import java.util.Optional; +import static org.exist.repo.AutoDeploymentTrigger.AUTODEPLOY_PROPERTY; + /** * The XMLDB driver class for eXist. This driver manages two different * internal implementations. The first communicates with a remote @@ -100,6 +102,8 @@ public class DatabaseImpl implements Database { private Boolean ssl_allow_self_signed = true; private Boolean ssl_verify_hostname = false; + private Boolean no_autodeploy = false; + public DatabaseImpl() { final String initdb = System.getProperty("exist.initdb"); if (initdb != null) { @@ -122,6 +126,10 @@ private void configure(final String instanceName) throws XMLDBException { config.setProperty(Journal.PROPERTY_RECOVERY_JOURNAL_DIR, Paths.get(journalDir)); } + if (no_autodeploy) { + System.setProperty(AUTODEPLOY_PROPERTY, "off"); + } + BrokerPool.configure(instanceName, 1, 5, config); if (shutdown != null) { BrokerPool.getInstance(instanceName).registerShutdownListener(shutdown); @@ -382,6 +390,7 @@ public String[] getNames() throws XMLDBException { public final static String DATA_DIR = "data-dir"; public final static String JOURNAL_DIR = "journal-dir"; public final static String SSL_ENABLE = "ssl-enable"; + public final static String NO_AUTODEPLOY = "no-autodeploy"; public final static String SSL_ALLOW_SELF_SIGNED = "ssl-allow-self-signed"; public final static String SSL_VERIFY_HOSTNAME = "ssl-verify-hostname"; @@ -421,6 +430,10 @@ public String getProperty(final String property) throws XMLDBException { value = ssl_verify_hostname.toString(); break; + case NO_AUTODEPLOY: + value = no_autodeploy.toString(); + break; + default: value = null; } @@ -461,6 +474,10 @@ public void setProperty(final String property, final String value) throws XMLDBE case SSL_VERIFY_HOSTNAME: this.ssl_verify_hostname = Boolean.valueOf(value); break; + + case NO_AUTODEPLOY: + this.no_autodeploy = Boolean.valueOf(value); + break; } } } diff --git a/exist-core/src/main/java/org/exist/xqj/Marshaller.java b/exist-core/src/main/java/org/exist/xqj/Marshaller.java index cc941c4f92..9a2997b0fd 100644 --- a/exist-core/src/main/java/org/exist/xqj/Marshaller.java +++ b/exist-core/src/main/java/org/exist/xqj/Marshaller.java @@ -57,13 +57,9 @@ import org.exist.xquery.functions.array.ArrayType; import org.exist.xquery.functions.map.MapType; import org.exist.xquery.value.*; -import org.w3c.dom.Comment; -import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import org.w3c.dom.ProcessingInstruction; -import org.w3c.dom.Text; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @@ -100,15 +96,13 @@ public class Marshaller { public final static String NAMESPACE = "http://exist-db.org/xquery/types/serialized"; public final static String PREFIX = "sx"; - private final static Properties DEFAULT_OUTPUT_PROPERTIES = new Properties(); private final static String VALUE_ELEMENT = "value"; - private final static String VALUE_ELEMENT_QNAME = PREFIX + ":value"; - private final static QName VALUE_QNAME = new QName(VALUE_ELEMENT, NAMESPACE, PREFIX); + private final static String VALUE_ELEMENT_PREFIXED_NAME = PREFIX + ":value"; private final static String SEQ_ELEMENT = "sequence"; - private final static String SEQ_ELEMENT_QNAME = PREFIX + ":sequence"; + private final static String SEQ_ELEMENT_PREFIXED_NAME = PREFIX + ":sequence"; private final static String ATTR_TYPE = "type"; private final static String ATTR_ITEM_TYPE = "item-type"; @@ -116,6 +110,7 @@ public class Marshaller { private final static String ATTR_NAME = "name"; public final static QName SEQUENCE_ELEMENT_QNAME = new QName(SEQ_ELEMENT, NAMESPACE, PREFIX); + public final static QName VALUE_ELEMENT_QNAME = new QName(VALUE_ELEMENT, NAMESPACE, PREFIX); public final static QName ENTRY_ELEMENT_QNAME = new QName("entry", NAMESPACE, PREFIX); public final static QName KEY_ELEMENT_QNAME = new QName("key", NAMESPACE, PREFIX); @@ -133,11 +128,11 @@ public static void marshall(final DBBroker broker, final Sequence seq, final Con throws XPathException, SAXException { final AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute("", ATTR_ITEM_TYPE, ATTR_ITEM_TYPE, "CDATA", Type.getTypeName(seq.getItemType())); - handler.startElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_QNAME, attrs); + handler.startElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_PREFIXED_NAME, attrs); for (final SequenceIterator i = seq.iterate(); i.hasNext(); ) { marshallItem(broker, i.nextItem(), handler); } - handler.endElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_QNAME); + handler.endElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_PREFIXED_NAME); } @@ -157,12 +152,12 @@ public static void marshall(final DBBroker broker, final Sequence seq, final int final ContentHandler handler) throws XPathException, SAXException { final AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute("", ATTR_ITEM_TYPE, ATTR_ITEM_TYPE, "CDATA", Type.getTypeName(seq.getItemType())); - handler.startElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_QNAME, attrs); + handler.startElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_PREFIXED_NAME, attrs); for (int i = start; i < howmany && i < seq.getItemCount(); i++ ) { marshallItem(broker, seq.itemAt(i), handler); } - handler.endElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_QNAME); + handler.endElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_PREFIXED_NAME); } /** @@ -200,15 +195,15 @@ public static void marshallItem(final DBBroker broker, final Item item, final Co {type = ((NodeValue)item).getNode().getNodeType();} attrs.addAttribute("", ATTR_TYPE, ATTR_TYPE, "CDATA", Type.getTypeName(type)); if (Type.subTypeOf(item.getType(), Type.NODE)) { - handler.startElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_QNAME, attrs); + handler.startElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_PREFIXED_NAME, attrs); final NodeValue nv = (NodeValue) item; nv.toSAX(broker, handler, outputProperties); - handler.endElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_QNAME); + handler.endElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_PREFIXED_NAME); } else { - handler.startElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_QNAME, attrs); + handler.startElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_PREFIXED_NAME, attrs); final String value = item.getStringValue(); handler.characters(value.toCharArray(), 0, value.length()); - handler.endElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_QNAME); + handler.endElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_PREFIXED_NAME); } } @@ -244,13 +239,16 @@ public static Sequence demarshall(final XMLStreamReader parser) throws XMLStream while (event != XMLStreamConstants.START_ELEMENT) { event = parser.next(); } + if (!NAMESPACE.equals(parser.getNamespaceURI())) { throw new XMLStreamException("Root element is not in the correct namespace. Expected: " + NAMESPACE); } + if (!SEQ_ELEMENT.equals(parser.getLocalName())) { - throw new XMLStreamException("Root element should be a " + SEQ_ELEMENT_QNAME); + throw new XMLStreamException("Root element should be a " + SEQ_ELEMENT_PREFIXED_NAME); } - final ValueSequence result = new ValueSequence(); + + Sequence result = Sequence.EMPTY_SEQUENCE; while ((event = parser.next()) != XMLStreamConstants.END_DOCUMENT) { switch (event) { case XMLStreamConstants.START_ELEMENT : @@ -275,15 +273,22 @@ public static Sequence demarshall(final XMLStreamReader parser) throws XMLStream } else { item = new StringValue(null, parser.getElementText()).convertTo(type); } + + if (result == Sequence.EMPTY_SEQUENCE) { + result = new ValueSequence(); + } result.add(item); } break; + case XMLStreamConstants.END_ELEMENT : - if (NAMESPACE.equals(parser.getNamespaceURI()) && SEQ_ELEMENT.equals(parser.getLocalName())) - {return result;} + if (NAMESPACE.equals(parser.getNamespaceURI()) && SEQ_ELEMENT.equals(parser.getLocalName())) { + return result; + } break; } } + return result; } @@ -297,16 +302,21 @@ private static Sequence demarshallSequence(final XQueryContext context, final No throw new XMLStreamException("Sequence element is not in the correct namespace. Expected: " + NAMESPACE); } if (!SEQ_ELEMENT.equals(node.getLocalName())) { - throw new XMLStreamException("Element should be a " + SEQ_ELEMENT_QNAME); + throw new XMLStreamException("Element should be a " + SEQ_ELEMENT_PREFIXED_NAME); } return demarshallValues(context, node); } private static Sequence demarshallValues(final XQueryContext context, final NodeImpl node) throws XMLStreamException, XPathException { - final ValueSequence result = new ValueSequence(); final InMemoryNodeSet sxValues = new InMemoryNodeSet(); - node.selectChildren(new NameTest(Type.ELEMENT, VALUE_QNAME), sxValues); + node.selectChildren(new NameTest(Type.ELEMENT, VALUE_ELEMENT_QNAME), sxValues); + + if (sxValues.isEmpty()) { + return Sequence.EMPTY_SEQUENCE; + } + + final ValueSequence result = new ValueSequence(sxValues.size()); for (final SequenceIterator itSxValue = sxValues.iterate(); itSxValue.hasNext();) { final ElementImpl sxValue = (ElementImpl) itSxValue.nextItem(); final Item item = demarshallValue(context, sxValue); @@ -315,7 +325,7 @@ private static Sequence demarshallValues(final XQueryContext context, final Node return result; } - private static Item demarshallValue(final XQueryContext context, final ElementImpl sxValue) throws XMLStreamException, XPathException { + public static Item demarshallValue(final XQueryContext context, final ElementImpl sxValue) throws XMLStreamException, XPathException { int type = Type.ITEM; final String typeName = sxValue.getAttribute(ATTR_TYPE); if (!typeName.isEmpty()) { @@ -333,7 +343,8 @@ private static Item demarshallValue(final XQueryContext context, final ElementIm final InMemoryNodeSet sxEntries = new InMemoryNodeSet(); sxValue.selectChildren(new NameTest(Type.ELEMENT, ENTRY_ELEMENT_QNAME), sxEntries); - Node item = sxValue.getFirstChild(); + @Nullable Node item = null; + item = sxValue.getFirstChild(); if (type == Type.ATTRIBUTE || (type == Type.ITEM && attrNameString != null)) { if (attrNameString.isEmpty()) { @@ -369,50 +380,73 @@ private static Item demarshallValue(final XQueryContext context, final ElementIm switch (type) { case Type.ELEMENT: - if (item instanceof Document) { - return (ElementImpl) ((DocumentImpl) item).getDocumentElement(); - } else if (!(item instanceof Element)) { - throw new XMLStreamException("sx:value should only contain an Element if type is " + typeName); - } else { - return (ElementImpl) item; - } + do { + if (item.getNodeType() == Node.DOCUMENT_NODE) { + item = ((DocumentImpl) item).getDocumentElement(); + } + + if (item.getNodeType() == Node.ELEMENT_NODE) { + return (ElementImpl) item; + } + + item = item.getNextSibling(); + } while (item != null); + + throw new XMLStreamException("sx:value must contain an Element if type is " + typeName); + case Type.COMMENT: - if (!(item instanceof Comment)) { - throw new XMLStreamException("sx:value should only contain a Comment node if type is " + typeName); - } - return (CommentImpl) item; + do { + if (item.getNodeType() == Node.COMMENT_NODE) { + return (CommentImpl) item; + } + item = item.getNextSibling(); + } while (item != null); + + throw new XMLStreamException("sx:value must contain a Comment node if type is " + typeName); + case Type.PROCESSING_INSTRUCTION: - if (!(item instanceof ProcessingInstruction)) { - throw new XMLStreamException("sx:value should only contain a Processing Instruction node if type is " + typeName); - } - return (ProcessingInstructionImpl) item; + do { + if (item.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) { + return (ProcessingInstructionImpl) item; + } + item = item.getNextSibling(); + } while (item != null); + + throw new XMLStreamException("sx:value must contain a Processing Instruction node if type is " + typeName); + case Type.TEXT: - if (!(item instanceof Text)) { - throw new XMLStreamException("sx:value should only contain a Text node if type is " + typeName); - } - return (TextImpl) item; + do { + if (item.getNodeType() == Node.TEXT_NODE) { + return (TextImpl) item; + } + item = item.getNextSibling(); + } while (item != null); + case Type.DOCUMENT: default: - if (item instanceof Document || item instanceof Element) { - final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(((NodeImpl) item).getExpression()); - try { - receiver.startDocument(); - ((NodeImpl) item).copyTo(null, receiver); - receiver.endDocument(); - } catch (final SAXException e) { - throw new XPathException(item != null ? ((NodeImpl) item).getExpression() : null, "Error while demarshalling node: " + e.getMessage(), e); + do { + if (item.getNodeType() == Node.DOCUMENT_NODE || item.getNodeType() == Node.ELEMENT_NODE) { + final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(((NodeImpl) item).getExpression()); + try { + receiver.startDocument(); + ((NodeImpl) item).copyTo(null, receiver); + receiver.endDocument(); + } catch (final SAXException e) { + throw new XPathException(item != null ? ((NodeImpl) item).getExpression() : null, "Error while demarshalling node: " + e.getMessage(), e); + } + return (NodeImpl) receiver.getDocument(); } - return (NodeImpl) receiver.getDocument(); - } else { - throw new XMLStreamException("sx:value should only contain a Node if type is " + typeName); - } + item = item.getNextSibling(); + } while (item != null); + + throw new XMLStreamException("sx:value must contain a Document or Element if type is " + typeName); } - } else if (type == Type.ITEM && !(item instanceof Text)) { + } else if (type == Type.ITEM && item.getNodeType() != Node.TEXT_NODE) { // item() type requested and we have been given a node which is not a text() node return (NodeImpl) item; @@ -447,13 +481,13 @@ private static Item demarshallValue(final XQueryContext context, final ElementIm } else { // specific non-node type or text() final StringBuilder data = new StringBuilder(); - while (item != null) { - if (!(item.getNodeType() == Node.TEXT_NODE || item.getNodeType() == Node.CDATA_SECTION_NODE)) { - throw new XMLStreamException("sx:value should only contain text if type is " + typeName); + do { + if (item.getNodeType() == Node.TEXT_NODE || item.getNodeType() == Node.CDATA_SECTION_NODE) { + data.append(item.getNodeValue()); } - data.append(item.getNodeValue()); item = item.getNextSibling(); - } + } while (item != null); + return new StringValue(data.toString()).convertTo(type); } } diff --git a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java index 246c1274ce..8c318c66ae 100644 --- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java +++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java @@ -1231,6 +1231,7 @@ public DocumentSet getStaticallyKnownDocuments() throws XPathException { staticDocuments = protectedDocuments.toDocumentSet(); return staticDocuments; } + final MutableDocumentSet ndocs = new DefaultDocumentSet(40); if (staticDocumentPaths == null) { @@ -1269,7 +1270,8 @@ public DocumentSet getStaticallyKnownDocuments() throws XPathException { } } } - staticDocuments = ndocs; + + this.staticDocuments = ndocs; return staticDocuments; } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/ExtCollection.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/ExtCollection.java index 849ec2af35..012dfc8f62 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/ExtCollection.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/ExtCollection.java @@ -63,6 +63,7 @@ import org.exist.xquery.functions.xmldb.XMLDBModule; import org.exist.xquery.value.*; +import javax.annotation.Nullable; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -114,88 +115,120 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence()); } } + final List args = getParameterValues(contextSequence, contextItem); - final Sequence result; - try { - if (args.isEmpty()) { - final Sequence docs = toSequence(context.getStaticallyKnownDocuments()); - final Sequence dynamicCollection = context.getDynamicallyAvailableCollection(""); - if (dynamicCollection != null) { - result = new ValueSequence(); - result.addAll(docs); - result.addAll(dynamicCollection); - } else { - result = docs; - } - } else { - final Sequence dynamicCollection = context.getDynamicallyAvailableCollection(asUri(args.get(0)).toString()); - if (dynamicCollection != null) { - result = dynamicCollection; - } else { - final MutableDocumentSet ndocs = new DefaultDocumentSet(); - for (final String next : args) { - final XmldbURI uri = new AnyURIValue(this, next).toXmldbURI(); - try (final Collection coll = context.getBroker().openCollection(uri, Lock.LockMode.READ_LOCK)) { - if (coll == null) { - if (context.isRaiseErrorOnFailedRetrieval()) { - throw new XPathException(this, ErrorCodes.FODC0002, "Can not access collection '" + uri + "'"); - } - } else { - if (context.inProtectedMode()) { - context.getProtectedDocs().getDocsByCollection(coll, ndocs); - } else { - coll.allDocs(context.getBroker(), ndocs, - includeSubCollections, context.getProtectedDocs()); - } - } - } - } - result = toSequence(ndocs); - } - } - } catch (final XPathException e) { //From AnyURIValue constructor - e.setLocation(line, column); - Sequence flattenedArgs = Sequence.EMPTY_SEQUENCE; - try { - flattenedArgs = argsToSeq(contextSequence, contextItem); - } catch (final XPathException xe) { - LOG.warn(e.getMessage(), xe); - } - throw new XPathException(this, ErrorCodes.FODC0002, e.getMessage(), flattenedArgs, e); - } catch (final PermissionDeniedException e) { - Sequence flattenedArgs = Sequence.EMPTY_SEQUENCE; - try { - flattenedArgs = argsToSeq(contextSequence, contextItem); - } catch (final XPathException xe) { - LOG.warn(e.getMessage(), xe); - } - throw new XPathException(this, ErrorCodes.FODC0002, "Can not access collection '" + e.getMessage() + "'", flattenedArgs, e); - } catch (final LockException e) { - Sequence flattenedArgs = Sequence.EMPTY_SEQUENCE; - try { - flattenedArgs = argsToSeq(contextSequence, contextItem); - } catch (final XPathException xe) { - LOG.warn(e.getMessage(), xe); + + @Nullable final List collectionUris; + if (args.isEmpty()) { + collectionUris = null; + } else { + collectionUris = new ArrayList<>(args.size()); + for (final String arg : args) { + collectionUris.add(asUri(arg)); } - throw new XPathException(this, ErrorCodes.FODC0002, e.getMessage(), flattenedArgs, e); } - // iterate through all docs and create the node set + final Sequence result = getCollectionItems(collectionUris); registerUpdateListener(); if (context.getProfiler().isEnabled()) { context.getProfiler().end(this, "", result); } + return result; } - private Sequence argsToSeq(final Sequence contextSequence, final Item contextItem) throws XPathException { - final ValueSequence sequence = new ValueSequence(); - for (int i = 0; i < getArgumentCount(); i++) { - final Sequence seq = getArgument(i).eval(contextSequence, contextItem); - sequence.addAll(seq); + protected Sequence getCollectionItems(@Nullable final List collectionUris) throws XPathException { + if (collectionUris == null) { + // no collection-uri(s) + return getDefaultCollectionItems(); + } + + return getCollectionUriItems(collectionUris); + } + + private Sequence getDefaultCollectionItems() throws XPathException { + @Nullable Sequence items = null; + + @Nullable final Sequence dynamicCollection = context.getDynamicallyAvailableCollection(""); + if (dynamicCollection != null) { + items = new ValueSequence(dynamicCollection); + } + + if (items == null) { + final DocumentSet staticallyKnownDocuments = context.getStaticallyKnownDocuments(); + items = new ValueSequence(staticallyKnownDocuments.getDocumentCount()); + addAll(staticallyKnownDocuments, items); + } + + if (items == null) { + items = Sequence.EMPTY_SEQUENCE; + } + + return items; + } + + private Sequence getCollectionUriItems(final List collectionUris) throws XPathException { + @Nullable Sequence items = null; + for (final URI collectionUri : collectionUris) { + items = getCollectionUriItems(collectionUri, items); + } + + if (items == null) { + items = Sequence.EMPTY_SEQUENCE; + } + + return items; + } + + private @Nullable Sequence getCollectionUriItems(final URI collectionUri, @Nullable Sequence items) throws XPathException { + @Nullable final Sequence dynamicCollection = context.getDynamicallyAvailableCollection(collectionUri.toString()); + if (dynamicCollection != null) { + if (items == null) { + items = dynamicCollection; + } else { + items.addAll(dynamicCollection); + } + + return items; + + } else { + @Nullable MutableDocumentSet docs = null; + + final XmldbURI uri = XmldbURI.create(collectionUri); + try (@Nullable final Collection coll = context.getBroker().openCollection(uri, Lock.LockMode.READ_LOCK)) { + if (coll == null) { + if (context.isRaiseErrorOnFailedRetrieval()) { + throw new XPathException(this, ErrorCodes.FODC0002, "Can not access collection '" + uri + "'"); + } + } else { + docs = new DefaultDocumentSet(); + if (context.inProtectedMode()) { + context.getProtectedDocs().getDocsByCollection(coll, docs); + } else { + coll.allDocs(context.getBroker(), docs, includeSubCollections, context.getProtectedDocs()); + } + } + } catch (final XPathException e) { // From AnyURIValue constructor + throw new XPathException(this, ErrorCodes.FODC0002, e.getMessage(), new StringValue(collectionUri.toString()), e); + } catch (final PermissionDeniedException e) { + throw new XPathException(this, ErrorCodes.FODC0002, "Can not access collection '" + e.getMessage() + "'", new StringValue(collectionUri.toString()), e); + } catch (final LockException e) { + throw new XPathException(this, ErrorCodes.FODC0002, e.getMessage(), new StringValue(collectionUri.toString()), e); + } + + if (docs == null || docs.getDocumentCount() == 0) { + return Sequence.EMPTY_SEQUENCE; + } + + if (items == null) { + items = new ValueSequence(docs.getDocumentCount()); + } else { + addAll(docs, items); + } + + return items; } - return sequence; } private URI asUri(final String path) throws XPathException { @@ -231,8 +264,7 @@ private List getParameterValues(final Sequence contextSequence, final It return args; } - private Sequence toSequence(final DocumentSet docs) throws XPathException { - final Sequence result = new ValueSequence(); + private void addAll(final DocumentSet docs, final Sequence items) throws XPathException { final LockManager lockManager = context.getBroker().getBrokerPool().getLockManager(); for (final Iterator i = docs.getDocumentIterator(); i.hasNext(); ) { final DocumentImpl doc = i.next(); @@ -245,7 +277,7 @@ private Sequence toSequence(final DocumentSet docs) throws XPathException { if (!context.inProtectedMode()) { dlock = lockManager.acquireDocumentReadLock(doc.getURI()); } - result.add(new NodeProxy(null, doc)); + items.add(new NodeProxy(this, doc)); } catch (final LockException e) { throw new XPathException(this, ErrorCodes.FODC0002, e); } finally { @@ -255,8 +287,6 @@ private Sequence toSequence(final DocumentSet docs) throws XPathException { } } } - - return result; } protected void registerUpdateListener() { diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunAnalyzeString.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunAnalyzeString.java index 6c9155be99..add6146168 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunAnalyzeString.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunAnalyzeString.java @@ -45,9 +45,16 @@ */ package org.exist.xquery.functions.fn; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; import net.sf.saxon.Configuration; import net.sf.saxon.om.Item; import net.sf.saxon.regex.RegexIterator; @@ -72,6 +79,25 @@ */ public class FunAnalyzeString extends BasicFunction { + /** + * Implements a cglib MethodInterceptor to implement the `characters` + * method of Saxon 9's net.sf.saxon.regex.RegexIterator$MatchHandler + * and Saxon 12's net.sf.saxon.regex.RegexMatchHandler + */ + private static final MethodInterceptor CHARACTERS_INTERCEPTOR = (final Object obj, final Method method, final Object[] args, final MethodProxy proxy) -> { + if ("characters".equals(method.getName())) { + final MemTreeBuilder builder = ((AbstractSaxonRegexMatchHandler) obj).builder; + builder.characters(args[0].toString()); + return null; + } + return proxy.invokeSuper(obj, args); + }; + + @SuppressWarnings("unchecked") + private static final Class SAXON_MATCH_HANDLER_CLASS = createSaxonMatchHandlerClass(); + private static final Constructor SAXON_MATCH_HANDLER_CLASS_CONSTRUCTOR = getSaxonMatchHandlerClassConstructor(SAXON_MATCH_HANDLER_CLASS); + private static final Method SAXON_PROCESS_MATCHING_SUBSTRING_FN = getSaxonProcessMatchingSubstringFunction(); + private final static QName fnAnalyzeString = new QName("analyze-string", FnModule.NAMESPACE_URI); private final static QName QN_MATCH = new QName("match", FnModule.NAMESPACE_URI); @@ -189,26 +215,107 @@ private void analyzeString(final MemTreeBuilder builder, final String input, Str private void match(final MemTreeBuilder builder, final RegexIterator regexIterator) throws net.sf.saxon.trans.XPathException { builder.startElement(QN_MATCH, null); - regexIterator.processMatchingSubstring(new RegexIterator.MatchHandler() { - @Override - public void characters(final CharSequence s) { - builder.characters(s); + + try { + Enhancer.registerCallbacks(SAXON_MATCH_HANDLER_CLASS, new Callback[]{ CHARACTERS_INTERCEPTOR }); + try { + final AbstractSaxonRegexMatchHandler matchHandler = SAXON_MATCH_HANDLER_CLASS_CONSTRUCTOR.newInstance(builder); + SAXON_PROCESS_MATCHING_SUBSTRING_FN.invoke(regexIterator, matchHandler); + } finally { + Enhancer.registerCallbacks(SAXON_MATCH_HANDLER_CLASS, null); } + } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new net.sf.saxon.trans.XPathException("Unable to dynamically invoke net.sf.saxon.regex.RegexIterator#processMatchingSubstring: " + e.getMessage(), e); + } - @Override - public void onGroupStart(final int groupNumber) throws net.sf.saxon.trans.XPathException { - final AttributesImpl attributes = new AttributesImpl(); - attributes.addAttribute("", QN_NR.getLocalPart(), QN_NR.getLocalPart(), "int", Integer.toString(groupNumber)); + builder.endElement(); + } - builder.startElement(QN_GROUP, attributes); + private static Method getSaxonProcessMatchingSubstringFunction() { + final String saxonVersion = net.sf.saxon.Version.getProductVersion(); + try { + final Class matchHandlerInterfaceClazz; + if (saxonVersion.startsWith("12.")) { + matchHandlerInterfaceClazz = getSaxon12MatchHandlerInterfaceClass(); + } else { + matchHandlerInterfaceClazz = getSaxon9MatchHandlerInterfaceClass(); } - @Override - public void onGroupEnd(final int groupNumber) throws net.sf.saxon.trans.XPathException { - builder.endElement(); + return RegexIterator.class.getMethod("processMatchingSubstring", matchHandlerInterfaceClazz); + + } catch (final ClassNotFoundException | NoSuchMethodException e) { + throw new IllegalStateException("Unable to dynamically access Saxon RegexIterator#processMatchingSubstring method: " + e.getMessage(), e); + } + } + + private static Constructor getSaxonMatchHandlerClassConstructor(final Class saxonMatchHandlerClass) { + try { + return saxonMatchHandlerClass.getDeclaredConstructor(MemTreeBuilder.class); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException("Unable to get constructor of dynamic Saxon Match Handler class: " + e.getMessage(), e); + } + } + + private static Class createSaxonMatchHandlerClass() { + final String saxonVersion = net.sf.saxon.Version.getProductVersion(); + try { + if (saxonVersion.startsWith("12.")) { + return createSaxon12MatchHandlerClass(); + } else { + return createSaxon9MatchHandlerClass(); } - }); - builder.endElement(); + } catch (final ClassNotFoundException e) { + throw new IllegalStateException("Unable to dynamically create Saxon Match Handler class: " + e.getMessage(), e); + } + } + + private static Class getSaxon12MatchHandlerInterfaceClass() throws ClassNotFoundException { + return Class.forName("net.sf.saxon.regex.RegexMatchHandler"); + } + + private static Class getSaxon9MatchHandlerInterfaceClass() throws ClassNotFoundException { + return Class.forName("net.sf.saxon.regex.RegexIterator$MatchHandler"); + } + + private static Class createSaxon12MatchHandlerClass() throws ClassNotFoundException { + final Class matchHandlerInterfaceClazz = getSaxon12MatchHandlerInterfaceClass(); + return createSaxonMatchHandlerClass(matchHandlerInterfaceClazz); + } + + private static Class createSaxon9MatchHandlerClass() throws ClassNotFoundException { + final Class matchHandlerInterfaceClazz = getSaxon9MatchHandlerInterfaceClass(); + return createSaxonMatchHandlerClass(matchHandlerInterfaceClazz); + } + + @SuppressWarnings("unchecked") + private static Class createSaxonMatchHandlerClass(final Class saxonMatchHandlerInterface) { + final Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(AbstractSaxonRegexMatchHandler.class); + enhancer.setInterfaces(new Class[]{saxonMatchHandlerInterface}); + enhancer.setCallbackType(MethodInterceptor.class); + return (Class) enhancer.createClass(); + } + + /** + * Implements the common methods of Saxon 9's net.sf.saxon.regex.RegexIterator$MatchHandler + * and Saxon 12's net.sf.saxon.regex.RegexMatchHandler + */ + private static abstract class AbstractSaxonRegexMatchHandler { + private final MemTreeBuilder builder; + + public AbstractSaxonRegexMatchHandler(final MemTreeBuilder builder) { + this.builder = builder; + } + + public void onGroupStart(final int groupNumber) { + final AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute("", QN_NR.getLocalPart(), QN_NR.getLocalPart(), "int", Integer.toString(groupNumber)); + builder.startElement(QN_GROUP, attributes); + } + + public void onGroupEnd(final int groupNumber) { + builder.endElement(); + } } private void nonMatch(final MemTreeBuilder builder, final Item item) { diff --git a/exist-core/src/main/java/org/exist/xquery/value/AbstractDateTimeValue.java b/exist-core/src/main/java/org/exist/xquery/value/AbstractDateTimeValue.java index 8fdb17a09a..1bb2dfbcdb 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/AbstractDateTimeValue.java +++ b/exist-core/src/main/java/org/exist/xquery/value/AbstractDateTimeValue.java @@ -126,6 +126,12 @@ protected AbstractDateTimeValue(final Expression expression, String lexicalValue } } + protected static XMLGregorianCalendar toXMLGregorianCalendar(final GregorianCalendar gregorianCalendar) { + final XMLGregorianCalendar xgc = TimeUtils.getInstance().newXMLGregorianCalendar(gregorianCalendar); + xgc.normalize(); + return xgc; + } + /** * Utility method that is able to clone a calendar whose year is 0 * (whatever a year 0 means). diff --git a/exist-core/src/main/java/org/exist/xquery/value/DateTimeStampValue.java b/exist-core/src/main/java/org/exist/xquery/value/DateTimeStampValue.java index 51a9e5ab88..dd06de9c1b 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/DateTimeStampValue.java +++ b/exist-core/src/main/java/org/exist/xquery/value/DateTimeStampValue.java @@ -56,6 +56,7 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import java.nio.ByteBuffer; +import java.time.ZonedDateTime; /** * @author Radek Hübner @@ -83,6 +84,15 @@ public DateTimeStampValue(final Expression expression, final String dateTime) th checkValidTimezone(); } + public DateTimeStampValue(final ZonedDateTime zonedDateTime) throws XPathException { + this(null, zonedDateTime); + } + + public DateTimeStampValue(final Expression expression, final ZonedDateTime zonedDateTime) throws XPathException { + super(expression, zonedDateTime); + checkValidTimezone(); + } + private void checkValidTimezone() throws XPathException { if(calendar.getTimezone() == DatatypeConstants.FIELD_UNDEFINED) { throw new XPathException(getExpression(), ErrorCodes.ERROR, "Unable to create xs:dateTimeStamp, timezone missing."); diff --git a/exist-core/src/main/java/org/exist/xquery/value/DateTimeValue.java b/exist-core/src/main/java/org/exist/xquery/value/DateTimeValue.java index a7c6df74a8..0be3dc2459 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/DateTimeValue.java +++ b/exist-core/src/main/java/org/exist/xquery/value/DateTimeValue.java @@ -57,6 +57,11 @@ import javax.xml.namespace.QName; import java.io.IOException; import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Date; import java.util.GregorianCalendar; @@ -90,12 +95,39 @@ public DateTimeValue(@Nullable final Expression expression, final XMLGregorianCa normalize(); } + public DateTimeValue(final Instant instant) { + this(null, instant); + } + + public DateTimeValue(final Expression expression, final Instant instant) { + super(expression, toXMLGregorianCalendar(new Date(instant.toEpochMilli()))); + normalize(); + } + + public DateTimeValue(final ZonedDateTime zonedDateTime) { + this(null, zonedDateTime); + } + + public DateTimeValue(final Expression expression, final ZonedDateTime zonedDateTime) { + super(expression, toXMLGregorianCalendar(zonedDateTime)); + normalize(); + } + + public DateTimeValue(final LocalDateTime localDateTime) { + this(null, localDateTime); + } + + public DateTimeValue(final Expression expression, final LocalDateTime localDateTime) { + super(expression, toXMLGregorianCalendar(localDateTime)); + normalize(); + } + public DateTimeValue(final Date date) { this(null, date); } public DateTimeValue(final Expression expression, Date date) { - super(expression, dateToXMLGregorianCalendar(date)); + super(expression, toXMLGregorianCalendar(date)); normalize(); } @@ -115,14 +147,35 @@ public DateTimeValue(final Expression expression, String dateTime) throws XPathE normalize(); } - private static XMLGregorianCalendar dateToXMLGregorianCalendar(Date date) { + private static XMLGregorianCalendar toXMLGregorianCalendar(final Date date) { final GregorianCalendar gc = new GregorianCalendar(); gc.setTime(date); - final XMLGregorianCalendar xgc = TimeUtils.getInstance().newXMLGregorianCalendar(gc); + return toXMLGregorianCalendar(gc); + } + + private static XMLGregorianCalendar toXMLGregorianCalendar(final LocalDateTime localDateTime) { + final XMLGregorianCalendar xgc = TimeUtils.getInstance().newXMLGregorianCalendar(); + xgc.setYear(localDateTime.getYear()); + xgc.setMonth(localDateTime.getMonthValue()); + xgc.setDay(localDateTime.getDayOfMonth()); + xgc.setHour(localDateTime.getHour()); + xgc.setMinute(localDateTime.getMinute()); + xgc.setSecond(localDateTime.getSecond()); + xgc.setMillisecond(localDateTime.getNano() / 1_000_000); + xgc.setTimezone(DatatypeConstants.FIELD_UNDEFINED); xgc.normalize(); return xgc; } + private static XMLGregorianCalendar toXMLGregorianCalendar(final Instant instant) { + return toXMLGregorianCalendar(LocalDateTime.ofInstant(instant, ZoneOffset.UTC)); + } + + private static XMLGregorianCalendar toXMLGregorianCalendar(final ZonedDateTime zonedDateTime) { + final GregorianCalendar gc = GregorianCalendar.from(zonedDateTime); + return toXMLGregorianCalendar(gc); + } + private static XMLGregorianCalendar fillCalendar(XMLGregorianCalendar calendar) { if (calendar.getHour() == DatatypeConstants.FIELD_UNDEFINED) { calendar.setHour(0); @@ -222,6 +275,12 @@ public T toJavaObject(final Class target) throws XPathException { final ByteBuffer buf = ByteBuffer.allocate(SERIALIZED_SIZE); serialize(buf); return (T) buf; + } else if (target == ZonedDateTime.class) { + return (T) calendar.toGregorianCalendar().toZonedDateTime(); + } else if (target == OffsetDateTime.class) { + return (T) calendar.toGregorianCalendar().toZonedDateTime().toOffsetDateTime(); + } else if (target == LocalDateTime.class) { + return (T)calendar.toGregorianCalendar().toZonedDateTime().toLocalDateTime(); } else { return super.toJavaObject(target); } diff --git a/exist-core/src/main/java/org/exist/xquery/value/DateValue.java b/exist-core/src/main/java/org/exist/xquery/value/DateValue.java index 8f41a0c8e3..aa16b91af8 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/DateValue.java +++ b/exist-core/src/main/java/org/exist/xquery/value/DateValue.java @@ -55,6 +55,7 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import java.nio.ByteBuffer; +import java.time.LocalDate; import java.util.GregorianCalendar; /** @@ -93,7 +94,7 @@ public DateValue(final XMLGregorianCalendar calendar) throws XPathException { this(null, calendar); } - public DateValue(final Expression expression, XMLGregorianCalendar calendar) throws XPathException { + public DateValue(final Expression expression, final XMLGregorianCalendar calendar) throws XPathException { super(expression, stripCalendar(cloneXMLGregorianCalendar(calendar))); } @@ -182,6 +183,8 @@ public T toJavaObject(final Class target) throws XPathException { return (T) buf; } else if (target == Long.class || target == long.class) { return (T) Long.valueOf(serializeToLong()); + } else if (target == LocalDate.class) { + return (T)calendar.toGregorianCalendar().toZonedDateTime().toLocalDate(); } else { return super.toJavaObject(target); } diff --git a/exist-core/src/main/java/org/exist/xquery/value/DayTimeDurationValue.java b/exist-core/src/main/java/org/exist/xquery/value/DayTimeDurationValue.java index 94699c00a9..ff4f90be36 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/DayTimeDurationValue.java +++ b/exist-core/src/main/java/org/exist/xquery/value/DayTimeDurationValue.java @@ -72,11 +72,11 @@ public class DayTimeDurationValue extends OrderedDurationValue { public static final Duration CANONICAL_ZERO_DURATION = TimeUtils.getInstance().newDuration(true, null, null, null, null, null, ZERO_DECIMAL); - DayTimeDurationValue(final Duration duration) throws XPathException { + public DayTimeDurationValue(final Duration duration) throws XPathException { this(null, duration); } - DayTimeDurationValue(final Expression expression, Duration duration) throws XPathException { + public DayTimeDurationValue(final Expression expression, Duration duration) throws XPathException { super(expression, duration); if (duration.isSet(DatatypeConstants.YEARS) || duration.isSet(DatatypeConstants.MONTHS)) { throw new XPathException(getExpression(), ErrorCodes.XPTY0004, "the value '" + duration + "' is not an xdt:dayTimeDuration since it specifies year or month values"); diff --git a/exist-core/src/main/java/org/exist/xquery/value/TimeValue.java b/exist-core/src/main/java/org/exist/xquery/value/TimeValue.java index cfc66d0d80..f3c44eeb5d 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/TimeValue.java +++ b/exist-core/src/main/java/org/exist/xquery/value/TimeValue.java @@ -56,6 +56,8 @@ import javax.xml.namespace.QName; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.time.LocalTime; +import java.time.OffsetTime; import java.util.GregorianCalendar; /** @@ -174,6 +176,10 @@ public T toJavaObject(final Class target) throws XPathException { return (T) buf; } else if (target == Long.class || target == long.class) { return (T) Long.valueOf(serializeToLong()); + } else if (target == OffsetTime.class) { + return (T)calendar.toGregorianCalendar().toZonedDateTime().toOffsetDateTime().toOffsetTime(); + } else if (target == LocalTime.class) { + return (T)calendar.toGregorianCalendar().toZonedDateTime().toLocalTime(); } else { return super.toJavaObject(target); } diff --git a/exist-core/src/main/java/org/exist/xquery/value/YearMonthDurationValue.java b/exist-core/src/main/java/org/exist/xquery/value/YearMonthDurationValue.java index c1d786abd4..e72a498ada 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/YearMonthDurationValue.java +++ b/exist-core/src/main/java/org/exist/xquery/value/YearMonthDurationValue.java @@ -71,11 +71,11 @@ public class YearMonthDurationValue extends OrderedDurationValue { public static final Duration CANONICAL_ZERO_DURATION = TimeUtils.getInstance().newDuration(true, null, BigInteger.ZERO, null, null, null, null); - YearMonthDurationValue(final Duration duration) throws XPathException { + public YearMonthDurationValue(final Duration duration) throws XPathException { this(null, duration); } - YearMonthDurationValue(final Expression expression, Duration duration) throws XPathException { + public YearMonthDurationValue(final Expression expression, Duration duration) throws XPathException { super(expression, duration); if (!duration.equals(DurationValue.CANONICAL_ZERO_DURATION)) { if (duration.isSet(DatatypeConstants.DAYS) || diff --git a/exist-core/src/main/xsd/rest-api.xsd b/exist-core/src/main/xsd/rest-api.xsd index c1edbc8334..3052fbb7b8 100644 --- a/exist-core/src/main/xsd/rest-api.xsd +++ b/exist-core/src/main/xsd/rest-api.xsd @@ -35,6 +35,20 @@ + + + + + + + + + + + + + + diff --git a/exist-core/src/test/java/org/exist/http/RESTExternalVariableTest.java b/exist-core/src/test/java/org/exist/http/RESTExternalVariableTest.java index 0968c06ba4..452698c03e 100644 --- a/exist-core/src/test/java/org/exist/http/RESTExternalVariableTest.java +++ b/exist-core/src/test/java/org/exist/http/RESTExternalVariableTest.java @@ -21,7 +21,7 @@ package org.exist.http; import com.evolvedbinary.j8fu.tuple.Tuple2; -import com.googlecode.junittoolbox.ParallelRunner; +import com.googlecode.junittoolbox.ParallelParameterized; import org.apache.commons.codec.binary.Base64; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -39,7 +39,9 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.w3c.dom.Attr; +import org.w3c.dom.Node; import org.xmlunit.diff.DefaultNodeMatcher; import org.xmlunit.diff.ElementSelectors; import org.xmlunit.matchers.CompareMatcher; @@ -50,6 +52,7 @@ import javax.xml.namespace.QName; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; import java.util.Map; import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; @@ -66,15 +69,30 @@ import static org.exist.http.RESTExternalVariableTest.UntypedNamedValueRep.value; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.xmlunit.matchers.HasXPathMatcher.hasXPath; /** * See: [BUG] The JavaDoc comments for variable in the REST API are inconsistent with the implementation. * * @author data() { + return Arrays.asList(new Object[][] { + { "xmlns-prefixed-ns", true }, + { "xmlns-default-ns", false } + }); + } + + @Parameterized.Parameter + public String testTypeName; + + @Parameterized.Parameter(value = 1) + public boolean useXmlnsPrefixes; + @ClassRule public static final ExistWebServer existWebServer = new ExistWebServer(true, false, true, true); @@ -316,13 +334,23 @@ public void queryPostWithExternalVariableStringzSuppliedUntypeds() throws IOExce @Test public void queryPostWithExternalVariableUntypedSuppliedUntypedElementValue() throws IOException { - final ExternalVariableValueRep externalVariable = UntypedValueRep.value("world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = UntypedValueRep.value("world"); + } else { + externalVariable = UntypedValueRep.value("world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable); } @Test public void queryPostWithExternalVariableUntypedSuppliedElement() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.ELEMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.ELEMENT, "world"); + } else { + externalVariable = value(Type.ELEMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable); } @@ -339,31 +367,56 @@ public void queryPostWithExternalVariableElementSuppliedEmpty() throws IOExcepti @Test public void queryPostWithExternalVariableElementSuppliedElement() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.ELEMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.ELEMENT, "world"); + } else { + externalVariable = value(Type.ELEMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()", externalVariable); } @Test public void queryPostWithExternalVariableElementSuppliedElements() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()", externalVariable); } @Test public void queryPostWithExternalVariableElementSuppliedUntyped() throws IOException { - final ExternalVariableValueRep externalVariable = UntypedValueRep.value("world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = UntypedValueRep.value("world"); + } else { + externalVariable = UntypedValueRep.value("world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()", externalVariable); } @Test public void queryPostWithExternalVariableUntypedSuppliedUntypedElements() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable); } @Test public void queryPostWithExternalVariableUntypedSuppliedElements() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable); } @@ -380,19 +433,34 @@ public void queryPostWithExternalVariableOptElementSuppliedEmpty() throws IOExce @Test public void queryPostWithExternalVariableOptElementSuppliedElement() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.ELEMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.ELEMENT, "world"); + } else { + externalVariable = value(Type.ELEMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()?", externalVariable); } @Test public void queryPostWithExternalVariableOptElementSuppliedElements() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + } queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "element()?", externalVariable); } @Test public void queryPostWithExternalVariableOptElementSuppliedUntyped() throws IOException { - final ExternalVariableValueRep externalVariable = UntypedValueRep.value("world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = UntypedValueRep.value("world"); + } else { + externalVariable = UntypedValueRep.value("world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()?", externalVariable); } @@ -409,25 +477,45 @@ public void queryPostWithExternalVariableElementsSuppliedEmpty() throws IOExcept @Test public void queryPostWithExternalVariableElementsSuppliedElement() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.ELEMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.ELEMENT, "world"); + } else { + externalVariable = value(Type.ELEMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()+", externalVariable); } @Test public void queryPostWithExternalVariableElementsSuppliedElements() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()+", externalVariable); } @Test public void queryPostWithExternalVariableElementsSuppliedUntyped() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; +final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()+", externalVariable); } @Test public void queryPostWithExternalVariableElementsSuppliedUntypeds() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()+", externalVariable); } @@ -444,37 +532,67 @@ public void queryPostWithExternalVariableElementzSuppliedEmpty() throws IOExcept @Test public void queryPostWithExternalVariableElementzSuppliedElement() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.ELEMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.ELEMENT, "world"); + } else { + externalVariable = value(Type.ELEMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable); } @Test public void queryPostWithExternalVariableElementzSuppliedElements() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { value(Type.ELEMENT, "world"), value(Type.ELEMENT, "see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable); } @Test public void queryPostWithExternalVariableElementszSuppliedUntyped() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable); } @Test public void queryPostWithExternalVariableElementzSuppliedUntypeds() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, "element()*", externalVariable); } @Test public void queryPostWithExternalVariableUntypedSuppliedUntypedDocument() throws IOException { - final ExternalVariableValueRep externalVariable = UntypedValueRep.value("world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = UntypedValueRep.value("world"); + } else { + externalVariable = UntypedValueRep.value("world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable); } @Test public void queryPostWithExternalVariableUntypedSuppliedDocument() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.DOCUMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.DOCUMENT, "world"); + } else { + externalVariable = value(Type.DOCUMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable); } @@ -491,32 +609,57 @@ public void queryPostWithExternalVariableDocumentSuppliedEmpty() throws IOExcept @Test public void queryPostWithExternalVariableDocumentSuppliedDocument() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.DOCUMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.DOCUMENT, "world"); + } else { + externalVariable = value(Type.DOCUMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "document-node()", externalVariable); } @Test public void queryPostWithExternalVariableDocumentSuppliedDocuments() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "document-node()", externalVariable); } @Test public void queryPostWithExternalVariableDocumentSuppliedUntyped() throws IOException { - final ExternalVariableValueRep externalVariable = UntypedValueRep.value("world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = UntypedValueRep.value("world"); + } else { + externalVariable = UntypedValueRep.value("world"); + } final String expectedResponseError = "/db/test/test.xmlerr:XPTY0004 Invalid type for variable $local:my-variable. Expected document-node(), got element()"; queryPostWithExternalVariable(Tuple(HttpStatus.BAD_REQUEST_400, expectedResponseError), "document-node()", externalVariable); } @Test public void queryPostWithExternalVariableUntypedSuppliedUntypedDocuments() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable); } @Test public void queryPostWithExternalVariableUntypedSuppliedDocuments() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, null, externalVariable); } @@ -533,19 +676,34 @@ public void queryPostWithExternalVariableOptDocumentSuppliedEmpty() throws IOExc @Test public void queryPostWithExternalVariableOptDocumentSuppliedDocument() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.DOCUMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.DOCUMENT, "world"); + } else { + externalVariable = value(Type.DOCUMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "document-node()?", externalVariable); } @Test public void queryPostWithExternalVariableOptDocumentSuppliedDocuments() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + } queryPostWithExternalVariable(HttpStatus.BAD_REQUEST_400, "document-node()?", externalVariable); } @Test public void queryPostWithExternalVariableOptDocumentSuppliedUntyped() throws IOException { - final ExternalVariableValueRep externalVariable = UntypedValueRep.value("world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = UntypedValueRep.value("world"); + } else { + externalVariable = UntypedValueRep.value("world"); + } final String expectedResponseError = "/db/test/test.xmlerr:XPTY0004 Invalid type for variable $local:my-variable. Expected document-node(), got element()"; queryPostWithExternalVariable(Tuple(HttpStatus.BAD_REQUEST_400, expectedResponseError), "document-node()?", externalVariable); } @@ -563,26 +721,46 @@ public void queryPostWithExternalVariableDocumentsSuppliedEmpty() throws IOExcep @Test public void queryPostWithExternalVariableDocumentsSuppliedDocument() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.DOCUMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.DOCUMENT, "world"); + } else { + externalVariable = value(Type.DOCUMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "document-node()+", externalVariable); } @Test public void queryPostWithExternalVariableDocumentsSuppliedDocuments() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, "document-node()+", externalVariable); } @Test public void queryPostWithExternalVariableDocumentsSuppliedUntyped() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + } final String expectedResponseError = "/db/test/test.xmlerr:XPTY0004 Invalid type for variable $local:my-variable. Expected document-node(), got element()"; queryPostWithExternalVariable(Tuple(HttpStatus.BAD_REQUEST_400, expectedResponseError), "document-node()+", externalVariable); } @Test public void queryPostWithExternalVariableDocumentsSuppliedUntypeds() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } final String expectedResponseError = "/db/test/test.xmlerr:XPTY0004 Invalid type for variable $local:my-variable. Expected document-node(), got element()"; queryPostWithExternalVariable(Tuple(HttpStatus.BAD_REQUEST_400, expectedResponseError), "document-node()+", externalVariable); } @@ -600,26 +778,46 @@ public void queryPostWithExternalVariableDocumentzSuppliedEmpty() throws IOExcep @Test public void queryPostWithExternalVariableDocumentzSuppliedDocument() throws IOException { - final ExternalVariableValueRep externalVariable = value(Type.DOCUMENT, "world"); + final ExternalVariableValueRep externalVariable; + if (useXmlnsPrefixes) { + externalVariable = value(Type.DOCUMENT, "world"); + } else { + externalVariable = value(Type.DOCUMENT, "world"); + } queryPostWithExternalVariable(HttpStatus.OK_200, "document-node()*", externalVariable); } @Test public void queryPostWithExternalVariableDocumentzSuppliedDocuments() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { value(Type.DOCUMENT, "world"), value(Type.DOCUMENT, "see you soon") }; + } queryPostWithExternalVariable(HttpStatus.OK_200, "document-node()*", externalVariable); } @Test public void queryPostWithExternalVariableDocumentszSuppliedUntyped() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world") }; + } final String expectedResponseError = "/db/test/test.xmlerr:XPTY0004 Invalid type for variable $local:my-variable. Expected document-node(), got element()"; queryPostWithExternalVariable(Tuple(HttpStatus.BAD_REQUEST_400, expectedResponseError), "document-node()*", externalVariable); } @Test public void queryPostWithExternalVariableDocumentzSuppliedUntypeds() throws IOException { - final ExternalVariableValueRep[] externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + final ExternalVariableValueRep[] externalVariable; + if (useXmlnsPrefixes) { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } else { + externalVariable = new ExternalVariableValueRep[] { UntypedValueRep.value("world"), UntypedValueRep.value("see you soon") }; + } final String expectedResponseError = "/db/test/test.xmlerr:XPTY0004 Invalid type for variable $local:my-variable. Expected document-node(), got element()"; queryPostWithExternalVariable(Tuple(HttpStatus.BAD_REQUEST_400, expectedResponseError), "document-node()*", externalVariable); } @@ -1622,6 +1820,75 @@ public void queryPostWithExternalVariableMapzSuppliedUntypeds() throws IOExcepti queryPostWithExternalVariable(HttpStatus.OK_200, "map(*)*", externalVariable); } + @Test + public void queryPostWrappedTypedWithExternalVariableStringConstructElement() throws IOException { + queryPostWithExternalVariableStringConstructElement(true, true); + } + + @Test + public void queryPostWrappedNotTypedWithExternalVariableStringConstructElement() throws IOException { + queryPostWithExternalVariableStringConstructElement(true, false); + } + + @Test + public void queryPostNotWrappedTypedWithExternalVariableStringConstructElement() throws IOException { + queryPostWithExternalVariableStringConstructElement(false, true); + } + + @Test + public void queryPostNotWrappedNotTypedWithExternalVariableStringConstructElement() throws IOException { + queryPostWithExternalVariableStringConstructElement(false, false); + } + + private void queryPostWithExternalVariableStringConstructElement(final boolean wrap, final boolean typed) throws IOException { + final String query; + if (useXmlnsPrefixes) { + query = "\n" + + "\t\n" + + "\t\t\n" + + "\t\t\t\n" + + "\t\t\t\tmy-variable\n" + + "\t\t\t\n" + + "\t\t\t\n" + + "\t\t\t\tgreeting" + + "\t\t\t\n" + + "\t\t\n" + + "\t\n" + + "\t\n" + + "\n"; + } else { + query = "\n" + + "\t\n" + + "\t\t\n" + + "\t\t\t\n" + + "\t\t\t\tmy-variable\n" + + "\t\t\t\n" + + "\t\t\t\n" + + "\t\t\t\tgreeting" + + "\t\t\t\n" + + "\t\t\n" + + "\t\n" + + "\t\n" + + "\n"; + } + + final HttpResponse response = doPostWithAuth(getResourceUri(), query); + final int resultStatusCode = response.getStatusLine() + .getStatusCode(); + + assertEquals("Server returned response code: " + resultStatusCode, HttpStatus.OK_200, resultStatusCode); + + //final String actual = "Hello, world."; + final String actual = readResponse(response.getEntity()); + assertThat(actual, hasXPath("//greeting[namespace-uri() = ''][text() = 'Hello, world.']").withNamespaceContext(NS_CONTEXT)); + } + private void queryPostWithExternalVariable(final int expectedResponseCode, @Nullable final String xqExternalVariableType, final ExternalVariableValueRep... externalVariableSequence) throws IOException { queryPostWithExternalVariable(Tuple(expectedResponseCode, null), externalVariableSequence, xqExternalVariableType, externalVariableSequence); } @@ -1791,38 +2058,73 @@ private static boolean isMap(final int xdmType, final ExternalVariableValueRep e return xdmType == Type.MAP || (xdmType == Type.ITEM && externalVariableValueRep instanceof MapRep); } - private static String buildQueryExternalVariable(@Nullable final String xqExternalVariableType, @Nullable final ExternalVariableValueRep... externalVariableSequence) { + private String buildQueryExternalVariable(@Nullable final String xqExternalVariableType, @Nullable final ExternalVariableValueRep... externalVariableSequence) { final StringBuilder builder = new StringBuilder(); - builder.append("\n"); - if (externalVariableSequence!= null) { - builder.append("\t\n"); - builder.append("\t\t\n"); - builder.append("\t\t\tlocalmy-variable\n"); - buildQueryExternalVariableSequence(builder, 3, externalVariableSequence); - builder.append("\t\t\n"); - builder.append("\t\n"); - } + if (useXmlnsPrefixes) { + builder.append("\n"); - builder.append("\t\n"); + builder.append("\t\t\n"); + builder.append("\t\t\tlocalmy-variable\n"); + buildQueryExternalVariableSequence(builder, 3, externalVariableSequence); + builder.append("\t\t\n"); + builder.append("\t\n"); + } + + builder.append("\t\n"); + builder.append("\n"); + + } else { + builder.append("\n"); + + if (externalVariableSequence != null) { + builder.append("\t\n"); + builder.append("\t\t\n"); + builder.append("\t\t\tlocalmy-variable\n"); + buildQueryExternalVariableSequence(builder, 3, externalVariableSequence); + builder.append("\t\t\n"); + builder.append("\t\n"); + } + + builder.append("\t\n"); + builder.append("\n"); } - builder.append(" external;\n"); - builder.append("$local:my-variable\n"); - builder.append("\t]]>\n"); - builder.append("\n"); return builder.toString(); } private static final char[] INDENTS = { '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t' }; - private static void buildQueryExternalVariableSequence(final StringBuilder builder, final int indentCount, final ExternalVariableValueRep... externalVariableSequence) { - builder.append(INDENTS, 0, indentCount).append("\n"); + private void buildQueryExternalVariableSequence(final StringBuilder builder, final int indentCount, final ExternalVariableValueRep... externalVariableSequence) { + if (useXmlnsPrefixes) { + builder.append(INDENTS, 0, indentCount).append("\n"); + } else { + builder.append(INDENTS, 0, indentCount).append("\n"); + } + for (final ExternalVariableValueRep externalVariableSequenceItem : externalVariableSequence) { - builder.append(INDENTS, 0, indentCount + 1).append("\n"); - builder.append(INDENTS, 0, indentCount + 3).append("\n"); + builder.append(INDENTS, 0, indentCount + 3).append("\n"); + builder.append(INDENTS, 0, indentCount + 3).append("').append(key.getContent()).append("\n"); + if (useXmlnsPrefixes) { + builder.append('>').append(key.getContent()).append("\n"); + } else { + builder.append('>').append(key.getContent()).append("\n"); + } + buildQueryExternalVariableSequence(builder, indentCount + 3, entryRep.value.values); - builder.append(INDENTS, 0, indentCount + 2).append("\n"); + + if (useXmlnsPrefixes) { + builder.append(INDENTS, 0, indentCount + 2).append("\n"); + } else { + builder.append(INDENTS, 0, indentCount + 2).append("\n"); + } } builder.append(INDENTS, 0, indentCount + 1); @@ -1862,9 +2180,18 @@ private static void buildQueryExternalVariableSequence(final StringBuilder build builder.append(((ValueRep) externalVariableSequenceItem).getContent()); } - builder.append("\n"); + if (useXmlnsPrefixes) { + builder.append("\n"); + } else { + builder.append("\n"); + } + } + + if (useXmlnsPrefixes) { + builder.append(INDENTS, 0, indentCount).append("\n"); + } else { + builder.append(INDENTS, 0, indentCount).append("\n"); } - builder.append(INDENTS, 0, indentCount).append("\n"); } private static String getServerUri() { diff --git a/exist-core/src/test/java/org/exist/xquery/EmbeddedBinariesTest.java b/exist-core/src/test/java/org/exist/xquery/EmbeddedBinariesTest.java index 56c3aef0e6..530d4441dd 100644 --- a/exist-core/src/test/java/org/exist/xquery/EmbeddedBinariesTest.java +++ b/exist-core/src/test/java/org/exist/xquery/EmbeddedBinariesTest.java @@ -143,7 +143,7 @@ protected QueryResultAccessor executeXQuery(final String consumer2E.accept(results); } finally { - //TODO(AR) performing #runCleanupTasks causes the stream to be closed, so if we do so before we are finished with the results, serialization fails. + // NOTE(AR) performing #runCleanupTasks causes the stream to be closed, so if we do so before we are finished with the results, serialization fails. if (fContext != null) { fContext.runCleanupTasks(); } diff --git a/extensions/modules/file/src/test/java/org/exist/xquery/modules/file/EmbeddedBinariesTest.java b/extensions/modules/file/src/test/java/org/exist/xquery/modules/file/EmbeddedBinariesTest.java index e51cdd042c..cfc3b21fba 100644 --- a/extensions/modules/file/src/test/java/org/exist/xquery/modules/file/EmbeddedBinariesTest.java +++ b/extensions/modules/file/src/test/java/org/exist/xquery/modules/file/EmbeddedBinariesTest.java @@ -147,7 +147,7 @@ protected QueryResultAccessor executeXQuery(final String consumer2E.accept(results); } finally { - //TODO(AR) performing #runCleanupTasks causes the stream to be closed, so if we do so before we are finished with the results, serialization fails. + // NOTE(AR) performing #runCleanupTasks causes the stream to be closed, so if we do so before we are finished with the results, serialization fails. if (fContext != null) { fContext.runCleanupTasks(); } diff --git a/schema/exist-rest-api.xsd b/schema/exist-rest-api.xsd index ad34ef5511..da282f56f7 100644 --- a/schema/exist-rest-api.xsd +++ b/schema/exist-rest-api.xsd @@ -55,6 +55,26 @@ + + + Context Item for the XQuery + + + + + + + + + + Values for the Default Collection of the Dynamic Context + + + + + + + Properties to pass to the Serializer.