Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add xquery recursive collection creation with just one path expression #5062

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7ee7a8d
Added create collection from a path, and the one parameter signature.
enima007 Sep 17, 2023
206772a
Added create collection with path signature.
enima007 Sep 17, 2023
66a060e
Added test file for create-collection with path.
enima007 Sep 17, 2023
f907b10
Removed wildcard imports.
enima007 Sep 26, 2023
5135da7
Changed new-collection-uri to collection-uri.
enima007 Sep 26, 2023
78bf82e
Fixed order to public final static as per the JLS.
enima007 Sep 26, 2023
e898899
Fixed catch blocs and thrown exception.
enima007 Sep 26, 2023
31abe51
Fixed current test-case collection names.
enima007 Sep 26, 2023
ff155b8
Merge branch 'eXist-db:develop' into feature/add-xquery-collection-cr…
enima007 Sep 26, 2023
e7118ed
Added cleanCollectionUri function.
enima007 Sep 26, 2023
ac5f200
Remove unused parameter collectionURI.
enima007 Sep 26, 2023
7cbd6dd
Fixed duplicating root collection.
enima007 Sep 26, 2023
43f331f
Fixed indentation.
enima007 Sep 26, 2023
cf1a8de
Fixed function doc.
enima007 Sep 26, 2023
1915cfa
Fixed log level and removed unecessary logs.
enima007 Sep 26, 2023
3ed9193
Removed sequenced constructor and added a new test case.
enima007 Sep 26, 2023
213d9bd
Merge branch 'feature/add-xquery-collection-create-with-just-the-path…
enima007 Sep 26, 2023
11b270b
Use ROOT_COLLECTION constant instead of /db string
enima007 Sep 27, 2023
138b435
Merge branch 'eXist-db:develop' into feature/add-xquery-collection-cr…
enima007 Oct 5, 2023
fa16fd4
Merge branch 'eXist-db:develop' into feature/add-xquery-collection-cr…
enima007 Oct 9, 2023
61faaae
Merge branch 'eXist-db:develop' into feature/add-xquery-collection-cr…
enima007 Oct 11, 2023
72bc0ee
[feature] used Automatic Resource Management to close collection.
enima007 Oct 12, 2023
bd4d1d2
[feature] Add final keyword to automatic resource management try catc…
enima007 Oct 16, 2023
b963c00
[feature] Fix creating a collection with a path not starting by /db.
enima007 Oct 16, 2023
4f71bdc
[feature] Fixed test case and added a new assert error test.
enima007 Oct 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,65 +24,182 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.exist.dom.persistent.NodeProxy;
import org.exist.dom.QName;
import org.exist.xquery.Cardinality;
import org.exist.xmldb.LocalCollection;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.Cardinality;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;
import org.exist.xquery.value.Item;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.XMLDBException;

import java.net.URISyntaxException;
import java.util.Optional;

/**
* Implements eXist's xmldb:create-collection() function.
*
* @author wolf
*/
public class XMLDBCreateCollection extends XMLDBAbstractCollectionManipulator {
private static final Logger logger = LogManager.getLogger(XMLDBCreateCollection.class);
public final static FunctionSignature signature = new FunctionSignature(
private static final Logger logger = LogManager.getLogger(XMLDBCreateCollection.class);

public static final FunctionSignature SIGNATURE_WITH_PARENT = new FunctionSignature(
new QName("create-collection", XMLDBModule.NAMESPACE_URI,
XMLDBModule.PREFIX),
"Create a new collection with name $new-collection as a child of " +
"$target-collection-uri. " + XMLDBModule.COLLECTION_URI +
"Returns the path to the new collection if successfully created, " +
"otherwise the empty sequence.",
"Create a new collection with name $new-collection as a child of " +
"$target-collection-uri. " + XMLDBModule.COLLECTION_URI +
"Returns the path to the new collection if successfully created, " +
"otherwise the empty sequence.",
new SequenceType[]{
new FunctionParameterSequenceType("target-collection-uri", Type.STRING, Cardinality.EXACTLY_ONE, "The target collection URI"),
new FunctionParameterSequenceType("new-collection", Type.STRING, Cardinality.EXACTLY_ONE, "The name of the new collection to create")},
new FunctionParameterSequenceType("target-collection-uri", Type.STRING, Cardinality.EXACTLY_ONE, "The target collection URI"),
new FunctionParameterSequenceType("new-collection", Type.STRING, Cardinality.EXACTLY_ONE, "The name of the new collection to create")},
new FunctionReturnSequenceType(Type.STRING, Cardinality.ZERO_OR_ONE, "the path to the new collection if successfully created, otherwise the empty sequence"));

public XMLDBCreateCollection(XQueryContext context) {
super(context, signature);
}

/*
* (non-Javadoc)
*
* @see org.exist.xquery.Expression#eval(org.exist.dom.persistent.DocumentSet,
* org.exist.xquery.value.Sequence, org.exist.xquery.value.Item)
*/
public Sequence evalWithCollection(Collection collection, Sequence[] args, Sequence contextSequence)
throws XPathException {

final String collectionName = args[1].getStringValue();

try {
final Collection newCollection = createCollectionPath(collection, collectionName);

if (newCollection == null)
{return Sequence.EMPTY_SEQUENCE;}
else
{return new StringValue(this, newCollection.getName());}

} catch (final XMLDBException e) {
logger.error("Unable to create new collection {}", collectionName, e);
throw new XPathException(this, "failed to create new collection " + collectionName + ": " + e.getMessage(), e);
public static final FunctionSignature SIGNATURE_WITH_URI = new FunctionSignature(
new QName("create-collection", XMLDBModule.NAMESPACE_URI,
XMLDBModule.PREFIX),
"Create a new collection by specifying the full uri $collection-uri. " + XMLDBModule.COLLECTION_URI +
"Returns the path to the new collection if successfully created, " +
"otherwise the empty sequence.",
new SequenceType[]{
new FunctionParameterSequenceType("collection-uri", Type.STRING, Cardinality.EXACTLY_ONE, "The new collection URI")},
new FunctionReturnSequenceType(Type.STRING, Cardinality.ZERO_OR_ONE, "the path to the new collection if successfully created, otherwise the empty sequence"));

public XMLDBCreateCollection(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}

/**
* Creates a new collection using the provided arguments.
* @param collection The parent collection.
* @param args The arguments given to the function.
* @param contextSequence The context sequence for the function or null.
* @return Sequence The collection uri.
* @throws XPathException
*/
public Sequence evalWithCollection(Collection collection, Sequence[] args, Sequence contextSequence) throws XPathException {
final String collectionName;
if (args.length > 1) {
collectionName = args[1].getStringValue();
} else {
collectionName = cleanCollectionUri(args[0].getStringValue());
}

try {
final Collection newCollection = createCollectionPath(collection, collectionName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shoudl this go in to try(final Collection newCollection..) ?


if (newCollection == null) {
return Sequence.EMPTY_SEQUENCE;
} else {
return new StringValue(this, newCollection.getName());
}

} catch (final XMLDBException e) {
logger.error("Unable to create new collection {}", collectionName, e);
throw new XPathException(this, "Failed to create new collection " + collectionName + ": " + e.getMessage(), e);
}
}



/**
adamretter marked this conversation as resolved.
Show resolved Hide resolved
* Override of the eval method, so we can create a collection using just its path.
* @param args The arguments given to the function.
* @param contextSequence The context sequence for the function or null.
* @return Sequence The collection uri.
* @throws XPathException
*/
@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence)
throws XPathException {
int paramNumber = 0;

if (0 == args.length) {
throw new XPathException(this, "Expected a collection as the argument " + (paramNumber + 1) + ".");
} else if (2 == args.length) {
return super.eval(args, contextSequence);
}

final Item item = args[paramNumber].itemAt(0);
if (Type.subTypeOf(item.getType(), Type.NODE)) {
final NodeValue node = (NodeValue) item;
if (node.getImplementationType() == NodeValue.PERSISTENT_NODE) {
final org.exist.collections.Collection internalCol = ((NodeProxy) node).getOwnerDocument().getCollection();
//TODO: use xmldbURI
try (final Collection collection = getLocalCollection(this, context, internalCol.getURI().toString())) {
return new StringValue(this, collection.getName());
} catch (final XMLDBException e) {
if (logger.isTraceEnabled()) {
logger.debug("Couldn't find parent collection, creating a new one.");
}

final String collectionURI = args[paramNumber].getStringValue();
if (collectionURI != null) {
try (final Collection collection = getCollection(this, context, collectionURI, Optional.empty(), Optional.empty())) {
return new StringValue(this, collection.getName());
} catch (final XMLDBException xe) {
if (logger.isTraceEnabled()) {
logger.debug("Couldn't find parent collection, creating a new one.");
}
}
}
}
} else {
return Sequence.EMPTY_SEQUENCE;
}
}

Sequence s = Sequence.EMPTY_SEQUENCE;
try (final Collection rootCollection = getRootCollection(context)) {
s = evalWithCollection(rootCollection, args, contextSequence);
} catch (final XMLDBException e) {
throw new XPathException(this, "Unable to close collection", e);
}
return s;
}

public Collection getRootCollection(final XQueryContext context) throws XPathException {
Collection rootCollection = null;
try {
rootCollection = new LocalCollection(context.getSubject(), context.getBroker().getBrokerPool(), XmldbURI.xmldbUriFor(XmldbURI.ROOT_COLLECTION, false));
} catch (final XMLDBException | URISyntaxException e) {
throw new XPathException(this, "Failed to access the root collection", e);
}
return rootCollection;
}

public String cleanCollectionUri(final String collectionUri) throws XPathException{
final String newCollectionUri;
if (!collectionUri.startsWith("xmldb:")) {
newCollectionUri = collectionUri;
} else if (collectionUri.startsWith("xmldb:exist:///")) {
newCollectionUri = collectionUri.replaceFirst("xmldb:exist://", "");
} else if (collectionUri.startsWith("xmldb:exist://embedded-eXist-server")) {
newCollectionUri = collectionUri.replaceFirst("xmldb:exist://embedded-eXist-server", "");
} else if (collectionUri.startsWith("xmldb:exist://localhost")) {
newCollectionUri = collectionUri.replaceFirst("xmldb:exist://localhost", "");
} else if (collectionUri.startsWith("xmldb:exist://127.0.0.1")) {
newCollectionUri = collectionUri.replaceFirst("xmldb:exist://127.0.0.1", "");
} else {
// Maybe it's better to check for the existence of /db/ and remove the preceding part. @@TODO
throw new XPathException(this, "The collection name provided is incorrect.");
}

if (!newCollectionUri.startsWith("/db/")) {
throw new XPathException(this, "Wrong collection path provided.");
}

return newCollectionUri.replaceFirst("/db", "");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ public class XMLDBModule extends AbstractInternalModule {
public final static String ANY_URI = "Resource URIs can be specified either as a simple collection path, an XMLDB URI or any URI.";

public final static FunctionDef[] functions = {
new FunctionDef(XMLDBCreateCollection.signature, XMLDBCreateCollection.class),
new FunctionDef(XMLDBCreateCollection.SIGNATURE_WITH_URI, XMLDBCreateCollection.class),
new FunctionDef(XMLDBCreateCollection.SIGNATURE_WITH_PARENT, XMLDBCreateCollection.class),
new FunctionDef(XMLDBRegisterDatabase.signature, XMLDBRegisterDatabase.class),
new FunctionDef(XMLDBStore.FS_STORE[0], XMLDBStore.class),
new FunctionDef(XMLDBStore.FS_STORE[1], XMLDBStore.class),
Expand Down
66 changes: 66 additions & 0 deletions exist-core/src/test/xquery/xmldb/collection-create-tests.xql
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
(:
: eXist-db Open Source Native XML Database
: Copyright (C) 2001 The eXist-db Authors
:
: [email protected]
: http://www.exist-db.org
:
: This library is free software; you can redistribute it and/or
: modify it under the terms of the GNU Lesser General Public
: License as published by the Free Software Foundation; either
: version 2.1 of the License, or (at your option) any later version.
:
: This library is distributed in the hope that it will be useful,
: but WITHOUT ANY WARRANTY; without even the implied warranty of
: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
: Lesser General Public License for more details.
:
: You should have received a copy of the GNU Lesser General Public
: License along with this library; if not, write to the Free Software
: Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
:)
xquery version "3.0";

module namespace t="http://exist-db.org/testsuite/collection-create";

import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";

declare variable $t:parent-collection-name := "/parent-collection";
declare variable $t:parent-collection := "/db" || $t:parent-collection-name;
declare variable $t:path-collection := $t:parent-collection || "/path/to/new-collection";
declare variable $t:path-collection-from-root := "/db/path/to/new-collection-from-root";
declare variable $t:wrong-path-collection := "/wrong/path-to-collection";

declare
%test:setUp
function t:setup() {
xmldb:create-collection("/db", $t:parent-collection-name)
};

declare
%test:tearDown
function t:cleanup() {
xmldb:remove($t:parent-collection),
xmldb:remove($t:path-collection-from-root)
};

declare
%test:assertEquals("/db/parent-collection/path/to/new-collection")
function t:fn-create-new-recursive-collection() {
let $collection := xmldb:create-collection($t:path-collection)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly that you are currently testing that:

xmldb:create-collection("/parent-collection/path/to/new-collection")

creates the collection /db/parent-collection/path/to/new-collection?

If so, that should not work! The xmldb:create-collection#1 function should always take an absolute path, i.e. starting with /db. This test should be modified to check for an error result, and the implementation updated accordingly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me

return $collection
};

declare
%test:assertEquals("/db/path/to/new-collection-from-root")
function t:fn-create-new-recursive-collection-from-root() {
let $collection := xmldb:create-collection($t:path-collection-from-root)
return $collection
};

declare
%test:assertError
function t:fn-create-new-recursive-collection-with-wrong-path() {
xmldb:create-collection($t:wrong-path-collection)
};

Loading