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 3 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 @@ -25,64 +25,161 @@
import org.apache.logging.log4j.Logger;

import org.exist.dom.QName;
import org.exist.xquery.Cardinality;
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.value.FunctionParameterSequenceType;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.Type;
import org.exist.dom.persistent.NodeProxy;
import org.exist.xmldb.LocalCollection;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.*;
dizzzz marked this conversation as resolved.
Show resolved Hide resolved
import org.exist.xquery.value.*;
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 final static FunctionSignature SIGNATURE_WITH_PARENT = new FunctionSignature(
dizzzz marked this conversation as resolved.
Show resolved Hide resolved
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);
}
public final static FunctionSignature SIGNATURE_WITH_URI = new FunctionSignature(
dizzzz marked this conversation as resolved.
Show resolved Hide resolved
new QName("create-collection", XMLDBModule.NAMESPACE_URI,
XMLDBModule.PREFIX),
"Create a new collection by specifying the full uri $new-collection-uri. " + XMLDBModule.COLLECTION_URI +
"Returns the path to the new collection if successfully created, " +
"otherwise the empty sequence.",
new SequenceType[]{
new FunctionParameterSequenceType("new-collection-uri", Type.STRING, Cardinality.EXACTLY_ONE, "The new collection URI")},
dizzzz marked this conversation as resolved.
Show resolved Hide resolved
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);
}

/*
* (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)
dizzzz marked this conversation as resolved.
Show resolved Hide resolved
throws XPathException {
String collectionName = null;
if(2 == args.length) {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about:

final collectionName;
if (args.length > 1) {
    collectionName = args[1].getStringValue();
} else {
    collectionName = args[0].getStringValue();
}

Differences:

  1. collectionName is improved by being final.
  2. There is a space after the if(
  3. args.length is on the LHS side of the comparison, this is the most common formulation throughout the code base.
  4. The else instead of the else if means that collectionName can never be null, and therefore cannot potentially cause an NPE elsewhere.

However it strikes me that when using the 1 argument version of the function, then collectionName is perhaps a bit of a misleading name, as for that function it will hold the collection path.

collectionName = args[1].getStringValue();
} else if(1 == args.length) {
collectionName = args[0].getStringValue();
}

/*
* (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 {
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..) ?


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

try {
final Collection newCollection = createCollectionPath(collection, collectionName);
if (newCollection == null)
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not certain, but the code formatting whitespace here looks a bit mangled to me.

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

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.
* */
@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence)

Check warning on line 105 in exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBCreateCollection.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBCreateCollection.java#L105

The method 'eval(Sequence, Sequence)' has an NPath complexity of 1350, current threshold is 200
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 boolean collectionNeedsClose = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to use an ARM (Automatic Resource Management) approach to handle collection closure through utilising Java's try-with-resources statement instead?

Collection collection = null;
final Item item = args[paramNumber].itemAt(0);
if (Type.subTypeOf(item.getType(), Type.NODE)) {
final NodeValue node = (NodeValue) item;
if (logger.isDebugEnabled()) {
logger.debug("Found node");
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need log this.

}
if (node.getImplementationType() == NodeValue.PERSISTENT_NODE) {
final org.exist.collections.Collection internalCol = ((NodeProxy) node).getOwnerDocument().getCollection();
if (logger.isDebugEnabled()) {
logger.debug("Found node");
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't thin we need log this.

}
try {
//TODO: use xmldbURI
collection = getLocalCollection(this, context, internalCol.getURI().toString());
return new StringValue(this, collection.getName());
} catch (final XMLDBException e) {
if (logger.isDebugEnabled()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be trace level instead

logger.debug("Couldn't find parent collection, creating a new one.");
}
}
} else {
return Sequence.EMPTY_SEQUENCE;
}
}

final String collectionURI = args[paramNumber].getStringValue();
if (collection == null && collectionURI != null) {
try {
collection = getCollection(this, context, collectionURI, Optional.empty(), Optional.empty());
return new StringValue(this, collection.getName());
} catch (final XMLDBException xe) {
if (logger.isDebugEnabled()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be trace level instead

logger.debug("Couldn't find parent collection, creating a new one.");
}
}
} else {
try {
return new StringValue(this, collection.getName());
} catch (XMLDBException e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

exception should be marked final

throw new RuntimeException(e);

Check warning on line 156 in exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBCreateCollection.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBCreateCollection.java#L156

Avoid throwing raw exception types.
dizzzz marked this conversation as resolved.
Show resolved Hide resolved
}
}

Sequence s = Sequence.EMPTY_SEQUENCE;
try {
collection = getRootCollection(context, collectionURI);
s = evalWithCollection(collection, args, contextSequence);
} finally {
if (collectionNeedsClose && collection != null) {
try {
collection.close();
} catch (final Exception e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid catch-all Exception. Instead specify explicitly multiple exceptions separated by |

Copy link
Member

Choose a reason for hiding this comment

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

code has gone?

throw new XPathException(this, "Unable to close collection", e);
}
}
}
return s;
}

} 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 Collection getRootCollection(final XQueryContext context, final String collectionUri) throws XPathException {
Collection rootCollection = null;
try {
rootCollection = new LocalCollection(context.getSubject(), context.getBroker().getBrokerPool(), XmldbURI.xmldbUriFor("/db", false));
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid /db, instead use existing constant from the XmldbURI class.

} catch (final XMLDBException | URISyntaxException e) {
throw new XPathException(this, "Failed to access the root collection", e);
}
return rootCollection;
}
}
}
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
51 changes: 51 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,51 @@
(:
: 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 := "path/to/new-collection";
dizzzz marked this conversation as resolved.
Show resolved Hide resolved

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)
};

declare
%test:assertEquals("/db/path/to/new-collection")
function t:fnDocAvailableOnHiddenResource() {
dizzzz marked this conversation as resolved.
Show resolved Hide resolved
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 (
dizzzz marked this conversation as resolved.
Show resolved Hide resolved
$collection
)
};