/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2015 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xmlrpc;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.dom.QName;
import org.exist.dom.persistent.NodeProxy;
import org.exist.dom.persistent.DocumentMetadata;
import org.exist.dom.persistent.DocumentSet;
import org.exist.dom.persistent.DocumentImpl;
import org.exist.dom.persistent.MutableDocumentSet;
import org.exist.dom.persistent.ExtArrayNodeSet;
import org.exist.dom.persistent.DocumentTypeImpl;
import org.exist.dom.persistent.BinaryDocument;
import org.exist.dom.persistent.NodeSet;
import org.exist.dom.persistent.SortedNodeSet;
import org.exist.dom.persistent.DefaultDocumentSet;
import org.exist.EXistException;
import org.exist.Namespaces;
import org.exist.Version;
import org.exist.backup.Backup;
import org.exist.collections.Collection;
import org.exist.collections.CollectionConfigurationException;
import org.exist.collections.CollectionConfigurationManager;
import org.exist.collections.IndexInfo;
import org.exist.dom.memtree.NodeImpl;
import org.exist.numbering.NodeId;
import org.exist.protocolhandler.embedded.EmbeddedInputStream;
import org.exist.protocolhandler.xmldb.XmldbURL;
import org.exist.scheduler.SystemTaskJob;
import org.exist.scheduler.impl.ShutdownTask;
import org.exist.scheduler.impl.SystemTaskJobImpl;
import org.exist.security.ACLPermission;
import org.exist.security.AXSchemaType;
import org.exist.security.Account;
import org.exist.security.EXistSchemaType;
import org.exist.security.Group;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.PermissionFactory;
import org.exist.security.SchemaType;
import org.exist.security.SecurityManager;
import org.exist.security.Subject;
import org.exist.security.internal.aider.ACEAider;
import org.exist.security.internal.aider.GroupAider;
import org.exist.security.internal.aider.UserAider;
import org.exist.source.DBSource;
import org.exist.source.Source;
import org.exist.source.StringSource;
import org.exist.storage.*;
import org.exist.storage.lock.Lock.LockMode;
import org.exist.storage.lock.LockedDocumentMap;
import org.exist.storage.serializers.EXistOutputKeys;
import org.exist.storage.serializers.Serializer;
import org.exist.storage.sync.Sync;
import org.exist.storage.txn.Txn;
import org.exist.util.*;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.exist.validation.ValidationReport;
import org.exist.validation.Validator;
import org.exist.xmldb.XmldbURI;
import org.exist.xmlrpc.function.XmlRpcCollectionFunction;
import org.exist.xmlrpc.function.XmlRpcCompiledXQueryFunction;
import org.exist.xmlrpc.function.XmlRpcDocumentFunction;
import org.exist.xmlrpc.function.XmlRpcFunction;
import org.exist.xquery.*;
import org.exist.xquery.util.HTTPUtils;
import org.exist.xquery.value.*;
import org.exist.xupdate.Modification;
import org.exist.xupdate.XUpdateProcessor;
import org.w3c.dom.DocumentType;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import com.evolvedbinary.j8fu.function.Function2E;
import com.evolvedbinary.j8fu.function.Function3E;
import com.evolvedbinary.j8fu.function.SupplierE;
import java.io.*;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.DeflaterOutputStream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import org.xmldb.api.base.*;
/**
* This class implements the actual methods defined by
* {@link org.exist.xmlrpc.RpcAPI}.
*
* @author Wolfgang Meier (wolfgang@exist-db.org)
* Modified by {Marco.Tampucci, Massimo.Martinelli} @isti.cnr.it
* @author Adam Retter <adam.retter@googlemail.com>
*/
public class RpcConnection implements RpcAPI {
private final static Logger LOG = LogManager.getLogger(RpcConnection.class);
private final static int MAX_DOWNLOAD_CHUNK_SIZE = 0x40000;
private final static Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
private final XmldbRequestProcessorFactory factory;
private final Subject user;
public RpcConnection(final XmldbRequestProcessorFactory factory, final Subject user) {
super();
this.factory = factory;
this.user = user;
}
@Override
public String getVersion() {
return Version.getVersion();
}
@Override
public boolean createCollection(final String name) throws EXistException, PermissionDeniedException {
return createCollection(name, null);
}
@Override
public boolean createCollection(final String name, final Date created) throws EXistException, PermissionDeniedException {
try {
return createCollection(XmldbURI.xmldbUriFor(name), created);
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
private boolean createCollection(final XmldbURI collUri, final Date created) throws PermissionDeniedException, EXistException {
withDb((broker, transaction) -> {
Collection current = broker.getCollection(collUri);
if (current != null) {
return true;
}
current = broker.getOrCreateCollection(transaction, collUri);
//TODO : register a lock (wich one ?) within the transaction ?
if (created != null) {
current.setCreationTime(created.getTime());
}
LOG.debug("creating collection " + collUri);
broker.saveCollection(transaction, current);
return null;
});
LOG.info("collection " + collUri + " has been created");
return true;
}
@Override
public boolean configureCollection(final String collName, final String configuration)
throws EXistException, PermissionDeniedException {
try {
return configureCollection(XmldbURI.xmldbUriFor(collName), configuration);
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
private boolean configureCollection(final XmldbURI collUri, final String configuration)
throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
final Collection colRef = this.<Collection>readCollection(broker, transaction, collUri).apply((collection, broker1, transaction1) -> collection);
final CollectionConfigurationManager mgr = factory.getBrokerPool().getConfigurationManager();
try {
mgr.addConfiguration(transaction, broker, colRef, configuration);
} catch (final CollectionConfigurationException e) {
throw new EXistException(e.getMessage());
}
return null;
});
LOG.info("Configured '" + collUri + "'");
return true;
}
public String createId(final String collName) throws EXistException, URISyntaxException, PermissionDeniedException {
return createId(XmldbURI.xmldbUriFor(collName));
}
private String createId(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
return this.<String>readCollection(collUri).apply((collection, broker, transaction) -> {
XmldbURI id;
final Random rand = new Random();
boolean ok;
do {
ok = true;
id = XmldbURI.create(Integer.toHexString(rand.nextInt()) + ".xml");
// check if this id does already exist
if (collection.hasDocument(broker, id)) {
ok = false;
}
if (collection.hasChildCollection(broker, id)) {
ok = false;
}
} while (!ok);
return id.toString();
});
}
protected QueryResult doQuery(final DBBroker broker, final CompiledXQuery compiled,
final NodeSet contextSet, final Map<String, Object> parameters) throws XPathException, EXistException, PermissionDeniedException {
final XQuery xquery = broker.getBrokerPool().getXQueryService();
checkPragmas(compiled.getContext(), parameters);
LockedDocumentMap lockedDocuments = null;
try {
final long start = System.currentTimeMillis();
lockedDocuments = beginProtected(broker, parameters);
if (lockedDocuments != null) {
compiled.getContext().setProtectedDocs(lockedDocuments);
}
final Properties outputProperties = new Properties();
final Sequence result = xquery.execute(broker, compiled, contextSet, outputProperties);
// pass last modified date to the HTTP response
HTTPUtils.addLastModifiedHeader(result, compiled.getContext());
LOG.info("query took " + (System.currentTimeMillis() - start) + "ms.");
return new QueryResult(result, outputProperties);
} catch (final XPathException e) {
return new QueryResult(e);
} finally {
if (lockedDocuments != null) {
lockedDocuments.unlock();
}
}
}
protected LockedDocumentMap beginProtected(final DBBroker broker, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final String protectColl = (String) parameters.get(RpcAPI.PROTECTED_MODE);
if (protectColl == null) {
return null;
}
do {
MutableDocumentSet docs = null;
final LockedDocumentMap lockedDocuments = new LockedDocumentMap();
try {
final Collection coll = broker.getCollection(XmldbURI.createInternal(protectColl));
docs = new DefaultDocumentSet();
coll.allDocs(broker, docs, true, lockedDocuments, LockMode.WRITE_LOCK);
return lockedDocuments;
} catch (final LockException e) {
LOG.debug("Deadlock detected. Starting over again. Docs: " + docs.getDocumentCount() + "; locked: "
+ lockedDocuments.size());
lockedDocuments.unlock();
}
} while (true);
}
/**
* @deprecated Use compileQuery lambda instead!
*/
@Deprecated
private CompiledXQuery compile(final DBBroker broker, final Source source, final Map<String, Object> parameters) throws XPathException, IOException, PermissionDeniedException {
final XQuery xquery = broker.getBrokerPool().getXQueryService();
final XQueryPool pool = broker.getBrokerPool().getXQueryPool();
CompiledXQuery compiled = pool.borrowCompiledXQuery(broker, source);
XQueryContext context;
if (compiled == null) {
context = new XQueryContext(broker.getBrokerPool());
} else {
context = compiled.getContext();
}
final String base = (String) parameters.get(RpcAPI.BASE_URI);
if (base != null) {
context.setBaseURI(new AnyURIValue(base));
}
final String moduleLoadPath = (String) parameters.get(RpcAPI.MODULE_LOAD_PATH);
if (moduleLoadPath != null) {
context.setModuleLoadPath(moduleLoadPath);
}
final Map<String, String> namespaces = (Map<String, String>) parameters.get(RpcAPI.NAMESPACES);
if (namespaces != null && namespaces.size() > 0) {
context.declareNamespaces(namespaces);
}
// declare static variables
final Map<String, Object> variableDecls = (Map<String, Object>) parameters.get(RpcAPI.VARIABLES);
if (variableDecls != null) {
for (final Map.Entry<String, Object> entry : variableDecls.entrySet()) {
if (LOG.isDebugEnabled()) {
LOG.debug("declaring " + entry.getKey() + " = " + entry.getValue());
}
context.declareVariable(entry.getKey(), entry.getValue());
}
}
final Object[] staticDocuments = (Object[]) parameters.get(RpcAPI.STATIC_DOCUMENTS);
if (staticDocuments != null) {
try {
final XmldbURI[] d = new XmldbURI[staticDocuments.length];
for (int i = 0; i < staticDocuments.length; i++) {
XmldbURI next = XmldbURI.xmldbUriFor((String) staticDocuments[i]);
d[i] = next;
}
context.setStaticallyKnownDocuments(d);
} catch (final URISyntaxException e) {
throw new XPathException(e);
}
} else if (context.isBaseURIDeclared()) {
context.setStaticallyKnownDocuments(new XmldbURI[]{context.getBaseURI().toXmldbURI()});
}
if (compiled == null) {
compiled = xquery.compile(broker, context, source);
}
return compiled;
}
@Override
public String printDiagnostics(final String query, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(query);
return withDb((broker, transaction) -> {
try {
return this.<String>compileQuery(broker, transaction, source, parameters).apply(compiledQuery -> {
final StringWriter writer = new StringWriter();
compiledQuery.dump(writer);
return writer.toString();
});
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
/**
* Check if the XQuery contains pragmas that define serialization settings.
* If yes, copy the corresponding settings to the current set of output
* properties.
*
* @param context
* @param parameters
* @throws org.exist.xquery.XPathException
*/
protected void checkPragmas(final XQueryContext context, final Map<String, Object> parameters) throws XPathException {
final Option pragma = context.getOption(Option.SERIALIZE_QNAME);
checkPragmas(pragma, parameters);
}
protected void checkPragmas(final Option pragma, final Map<String, Object> parameters) throws XPathException {
if (pragma == null) {
return;
}
final String[] contents = pragma.tokenizeContents();
for (final String content : contents) {
final String[] pair = Option.parseKeyValuePair(content);
if (pair == null) {
throw new XPathException("Unknown parameter found in " + pragma.getQName().getStringValue()
+ ": '" + content + "'");
}
LOG.debug("Setting serialization property from pragma: " + pair[0] + " = " + pair[1]);
parameters.put(pair[0], pair[1]);
}
}
@Override
public int executeQuery(final byte[] xpath, final String encoding, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final Charset charset = Optional.ofNullable(encoding).map(Charset::forName).orElse(DEFAULT_ENCODING);
final String xpathString = new String(xpath, charset);
LOG.debug("query: " + xpathString);
return executeQuery(xpathString, parameters);
}
@Override
public int executeQuery(final String xpath, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final Source source = new StringSource(xpath);
final long startTime = System.currentTimeMillis();
try {
final QueryResult result = this.<QueryResult>compileQuery(broker, transaction, source, parameters).apply(compiledQuery -> doQuery(broker, compiledQuery, null, parameters));
if (result.hasErrors()) {
throw new EXistException(result.getException());
}
result.queryTime = System.currentTimeMillis() - startTime;
return factory.resultSets.add(result);
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
protected String formatErrorMsg(final String message) {
return formatErrorMsg("error", message);
}
protected String formatErrorMsg(final String type, final String message) {
return ("<exist:result xmlns:exist=\"" + Namespaces.EXIST_NS + "\" ") + "hitCount=\"0\">" +
'<' + type + '>' + message + "</" + type + "></exist:result>";
}
@Override
public boolean existsAndCanOpenCollection(final String collectionUri) throws EXistException, PermissionDeniedException {
final XmldbURI uri;
try {
uri = XmldbURI.xmldbUriFor(collectionUri);
} catch (final URISyntaxException use) {
throw new EXistException("Collection '" + collectionUri + "' does not indicate a valid collection URI: " + use.getMessage(), use);
}
return withDb((broker, transaction) -> {
Collection collection = null;
try {
collection = broker.openCollection(uri, LockMode.READ_LOCK);
return collection != null;
} finally {
if (collection != null) {
collection.release(LockMode.READ_LOCK);
}
}
});
}
@Override
public Map<String, Object> getCollectionDesc(final String rootCollection) throws EXistException, PermissionDeniedException {
try {
return getCollectionDesc((rootCollection == null) ? XmldbURI.ROOT_COLLECTION_URI : XmldbURI.xmldbUriFor(rootCollection));
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
private Map<String, Object> getCollectionDesc(final XmldbURI rootUri) throws EXistException, PermissionDeniedException {
return this.<Map<String, Object>>readCollection(rootUri).apply((collection, broker, transaction) -> {
final Map<String, Object> desc = new HashMap<>();
final List<Map<String, Object>> docs = new ArrayList<>();
final List<String> collections = new ArrayList<>();
if (collection.getPermissionsNoLock().validate(user, Permission.READ)) {
for (final Iterator<DocumentImpl> i = collection.iterator(broker); i.hasNext(); ) {
final DocumentImpl doc = i.next();
final Permission perms = doc.getPermissions();
final Map<String, Object> hash = new HashMap<>(5);
hash.put("name", doc.getFileURI().toString());
hash.put("owner", perms.getOwner().getName());
hash.put("group", perms.getGroup().getName());
hash.put("permissions", perms.getMode());
hash.put("type", doc.getResourceType() == DocumentImpl.BINARY_FILE ? "BinaryResource" : "XMLResource");
docs.add(hash);
}
for (final Iterator<XmldbURI> i = collection.collectionIterator(broker); i.hasNext(); ) {
collections.add(i.next().toString());
}
}
final Permission perms = collection.getPermissionsNoLock();
desc.put("collections", collections);
desc.put("documents", docs);
desc.put("name", collection.getURI().toString());
desc.put("created", Long.toString(collection.getCreationTime()));
desc.put("owner", perms.getOwner().getName());
desc.put("group", perms.getGroup().getName());
desc.put("permissions", perms.getMode());
return desc;
});
}
@Override
public Map<String, Object> describeResource(final String resourceName)
throws EXistException, PermissionDeniedException {
try {
return describeResource(XmldbURI.xmldbUriFor(resourceName));
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
private Map<String, Object> describeResource(final XmldbURI resourceUri)
throws EXistException, PermissionDeniedException {
try {
return this.<Map<String, Object>>readDocument(resourceUri).apply((document, broker, transaction) -> {
final Map<String, Object> hash = new HashMap<>(11);
final Permission perms = document.getPermissions();
hash.put("name", resourceUri.toString());
hash.put("owner", perms.getOwner().getName());
hash.put("group", perms.getGroup().getName());
hash.put("permissions", perms.getMode());
if (perms instanceof ACLPermission) {
hash.put("acl", getACEs(perms));
}
hash.put("type",
document.getResourceType() == DocumentImpl.BINARY_FILE
? "BinaryResource"
: "XMLResource");
final long resourceLength = document.getContentLength();
hash.put("content-length", (resourceLength > (long) Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) resourceLength);
hash.put("content-length-64bit", Long.toString(resourceLength));
hash.put("mime-type", document.getMetadata().getMimeType());
hash.put("created", new Date(document.getMetadata().getCreated()));
hash.put("modified", new Date(document.getMetadata().getLastModified()));
return hash;
});
} catch (final EXistException e) {
LOG.debug(e);
return new HashMap<>();
}
}
@Override
public Map<String, Object> describeCollection(final String rootCollection) throws EXistException, PermissionDeniedException {
try {
return describeCollection((rootCollection == null) ? XmldbURI.ROOT_COLLECTION_URI : XmldbURI.xmldbUriFor(rootCollection));
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
/**
* The method <code>describeCollection</code>
* <p>
* Returns details of a collection - collections (list of sub-collections) -
* name - created - owner - group - permissions - acl
* <p>
* If you do not have read access on the collection, the list of
* sub-collections will be empty, an exception will not be thrown!
*
* @param collUri a <code>XmldbURI</code> value
* @return a <code>Map</code> value
* @throws Exception if an error occurs
*/
private Map<String, Object> describeCollection(final XmldbURI collUri)
throws EXistException, PermissionDeniedException {
return this.<Map<String, Object>>readCollection(collUri).apply((collection, broker, transaction) -> {
final Map<String, Object> desc = new HashMap<>();
final List<String> collections = new ArrayList<>();
if (collection.getPermissionsNoLock().validate(user, Permission.READ)) {
for (final Iterator<XmldbURI> i = collection.collectionIterator(broker); i.hasNext(); ) {
collections.add(i.next().toString());
}
}
final Permission perms = collection.getPermissionsNoLock();
desc.put("collections", collections);
desc.put("name", collection.getURI().toString());
desc.put("created", Long.toString(collection.getCreationTime()));
desc.put("owner", perms.getOwner().getName());
desc.put("group", perms.getGroup().getName());
desc.put("permissions", perms.getMode());
if (perms instanceof ACLPermission) {
desc.put("acl", getACEs(perms));
}
return desc;
});
}
@Override
public byte[] getDocument(final String name, final Map<String, Object> parameters) throws EXistException,
PermissionDeniedException {
final Charset encoding = getEncoding(parameters);
final boolean compression = useCompression(parameters);
final String xml = getDocumentAsString(name, parameters);
if (compression) {
LOG.debug("getDocument with compression");
try {
return Compressor.compress(xml.getBytes(encoding));
} catch (final IOException ioe) {
throw new EXistException(ioe);
}
} else {
return xml.getBytes(encoding);
}
}
@Override
public String getDocumentAsString(final String docName, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
try {
return getDocumentAsString(XmldbURI.xmldbUriFor(docName), parameters);
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
private String getDocumentAsString(final XmldbURI docUri, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return this.<String>readDocument(docUri).apply((document, broker, transaction) -> {
final Serializer serializer = broker.getSerializer();
serializer.setProperties(toProperties(parameters));
return serializer.serialize(document);
});
}
@Override
public Map<String, Object> getDocumentData(final String docName, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final XmldbURI docUri;
try {
docUri = XmldbURI.xmldbUriFor(docName);
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
return this.<Map<String, Object>>readDocument(docUri).apply((document, broker, transaction) -> {
final Charset encoding = getEncoding(parameters);
// A tweak for very large resources, VirtualTempFile
final Map<String, Object> result = new HashMap<>();
VirtualTempFile vtempFile = null;
try {
vtempFile = new VirtualTempFile(MAX_DOWNLOAD_CHUNK_SIZE, MAX_DOWNLOAD_CHUNK_SIZE);
vtempFile.setTempPrefix("eXistRPCC");
// binary check TODO dwes
if (document.getResourceType() == DocumentImpl.XML_FILE) {
vtempFile.setTempPostfix(".xml");
final Serializer serializer = broker.getSerializer();
serializer.setProperties(toProperties(parameters));
try (final Writer writer = new OutputStreamWriter(vtempFile, encoding)) {
serializer.serialize(document, writer);
}
} else {
vtempFile.setTempPostfix(".bin");
broker.readBinaryResource((BinaryDocument) document, vtempFile);
}
} finally {
if (vtempFile != null) {
vtempFile.close();
}
}
final byte[] firstChunk = vtempFile.getChunk(0);
result.put("data", firstChunk);
int offset = 0;
if (vtempFile.length() > MAX_DOWNLOAD_CHUNK_SIZE) {
offset = firstChunk.length;
final int handle = factory.resultSets.add(new SerializedResult(vtempFile));
result.put("handle", Integer.toString(handle));
result.put("supports-long-offset", Boolean.TRUE);
} else {
vtempFile.delete();
}
result.put("offset", offset);
return result;
});
}
@Override
public Map<String, Object> getNextChunk(final String handle, final int offset)
throws EXistException, PermissionDeniedException {
try {
final int resultId = Integer.parseInt(handle);
final SerializedResult sr = factory.resultSets.getSerializedResult(resultId);
if (sr == null) {
throw new EXistException("Invalid handle specified");
}
// This will keep the serialized result in the cache
sr.touch();
final VirtualTempFile vfile = sr.result;
if (offset <= 0 || offset > vfile.length()) {
factory.resultSets.remove(resultId);
throw new EXistException("No more data available");
}
final byte[] chunk = vfile.getChunk(offset);
final long nextChunk = offset + chunk.length;
final Map<String, Object> result = new HashMap<>();
result.put("data", chunk);
result.put("handle", handle);
if (nextChunk > (long) Integer.MAX_VALUE || nextChunk == vfile.length()) {
factory.resultSets.remove(resultId);
result.put("offset", 0);
} else {
result.put("offset", nextChunk);
}
return result;
} catch (final NumberFormatException | IOException e) {
throw new EXistException(e);
}
}
@Override
public Map<String, Object> getNextExtendedChunk(final String handle, final String offset)
throws EXistException, PermissionDeniedException {
try {
final int resultId = Integer.parseInt(handle);
final SerializedResult sr = factory.resultSets.getSerializedResult(resultId);
if (sr == null) {
throw new EXistException("Invalid handle specified");
}
// This will keep the serialized result in the cache
sr.touch();
final VirtualTempFile vfile = sr.result;
final long longOffset = Long.parseLong(offset);
if (longOffset < 0 || longOffset > vfile.length()) {
factory.resultSets.remove(resultId);
throw new EXistException("No more data available");
}
final byte[] chunk = vfile.getChunk(longOffset);
final long nextChunk = longOffset + chunk.length;
final Map<String, Object> result = new HashMap<>();
result.put("data", chunk);
result.put("handle", handle);
if (nextChunk == vfile.length()) {
factory.resultSets.remove(resultId);
result.put("offset", Long.toString(0));
} else {
result.put("offset", Long.toString(nextChunk));
}
return result;
} catch (final NumberFormatException | IOException e) {
throw new EXistException(e);
}
}
@Override
public byte[] getBinaryResource(final String name)
throws EXistException, PermissionDeniedException, URISyntaxException {
return getBinaryResource(XmldbURI.xmldbUriFor(name));
}
private byte[] getBinaryResource(final XmldbURI name) throws EXistException, PermissionDeniedException {
return getBinaryResource(name, Permission.READ);
}
private byte[] getBinaryResource(final XmldbURI name, final int requiredPermissions) throws EXistException, PermissionDeniedException {
return this.<byte[]>readDocument(name).apply((document, broker, transaction) -> {
if (document.getResourceType() != DocumentImpl.BINARY_FILE) {
throw new EXistException("Document " + name + " is not a binary resource");
}
if (!document.getPermissions().validate(user, requiredPermissions)) {
throw new PermissionDeniedException("Insufficient privileges to access resource");
}
try (final InputStream is = broker.getBinaryResource((BinaryDocument) document)) {
final long resourceSize = broker.getBinaryResourceSize((BinaryDocument) document);
if (resourceSize > (long) Integer.MAX_VALUE) {
throw new EXistException("Resource too big to be read using this method.");
}
final byte[] data = new byte[(int) resourceSize];
is.read(data);
return data;
}
});
}
@Override
public int xupdate(final String collectionName, final byte[] xupdate) throws PermissionDeniedException, EXistException {
try {
return xupdate(XmldbURI.xmldbUriFor(collectionName), new String(xupdate, DEFAULT_ENCODING));
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
private int xupdate(final XmldbURI collUri, final String xupdate) throws PermissionDeniedException, EXistException {
return withDb((broker, transaction) -> {
final Collection collectionRef = this.<Collection>readCollection(collUri).apply((collection, broker1, transaction1) -> collection);
//TODO : register a lock (which one ?) in the transaction ?
final DocumentSet docs = collectionRef.allDocs(broker, new DefaultDocumentSet(), true);
try {
final XUpdateProcessor processor = new XUpdateProcessor(broker, docs);
final Modification modifications[] = processor.parse(new InputSource(new StringReader(xupdate)));
long mods = 0;
for (final Modification modification : modifications) {
mods += modification.process(transaction);
broker.flush();
}
return (int) mods;
} catch (final XPathException | ParserConfigurationException e) {
throw new EXistException(e);
}
});
}
@Override
public int xupdateResource(final String resource, final byte[] xupdate, final String encoding) throws PermissionDeniedException, EXistException {
try {
return xupdateResource(XmldbURI.xmldbUriFor(resource), new String(xupdate, Charset.forName(encoding)));
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
private int xupdateResource(final XmldbURI docUri, final String xupdate) throws PermissionDeniedException, EXistException {
return withDb((broker, transaction) -> {
final DocumentImpl documentRef = this.<DocumentImpl>readDocument(docUri).apply((document, broker1, transaction1) -> document);
//TODO : register a lock (which one ?) within the transaction ?
final MutableDocumentSet docs = new DefaultDocumentSet();
docs.add(documentRef);
try {
final XUpdateProcessor processor = new XUpdateProcessor(broker, docs);
final Modification modifications[] = processor.parse(new InputSource(new StringReader(xupdate)));
long mods = 0;
for (final Modification modification : modifications) {
mods += modification.process(transaction);
broker.flush();
}
return (int) mods;
} catch (final XPathException | ParserConfigurationException e) {
throw new EXistException(e);
}
});
}
@Override
public boolean sync() {
try {
return withDbAsSystem((broker, transaction) -> {
broker.sync(Sync.MAJOR);
return true;
});
} catch (final EXistException | PermissionDeniedException e) {
LOG.error(e.getMessage(), e);
return false;
}
}
@Override
public boolean dataBackup(final String dest) {
factory.getBrokerPool().triggerSystemTask(new DataBackup(Paths.get(dest)));
return true;
}
@Override
public List<String> getDocumentListing() throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final DocumentSet docs = broker.getAllXMLResources(new DefaultDocumentSet());
final XmldbURI names[] = docs.getNames();
final List<String> list = new ArrayList<>();
for (final XmldbURI name : names) {
list.add(name.toString());
}
return list;
});
}
@Override
public List<String> getCollectionListing(final String collName) throws EXistException, PermissionDeniedException, URISyntaxException {
return getCollectionListing(XmldbURI.xmldbUriFor(collName));
}
private List<String> getCollectionListing(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
try {
return this.<List<String>>readCollection(collUri).apply((collection, broker, transaction) -> {
final List<String> list = new ArrayList<>();
for (final Iterator<XmldbURI> i = collection.collectionIterator(broker); i.hasNext(); ) {
list.add(i.next().toString());
}
return list;
});
} catch (final EXistException e) {
return Collections.EMPTY_LIST;
}
}
@Override
public List<String> getDocumentListing(final String collName)
throws EXistException, PermissionDeniedException, URISyntaxException {
return getDocumentListing(XmldbURI.xmldbUriFor(collName));
}
private List<String> getDocumentListing(final XmldbURI collUri)
throws EXistException, PermissionDeniedException {
try {
return this.<List<String>>readCollection(collUri).apply((collection, broker, transaction) -> {
final List<String> list = new ArrayList<>();
for (final Iterator<DocumentImpl> i = collection.iterator(broker); i.hasNext(); ) {
list.add(i.next().getFileURI().toString());
}
return list;
});
} catch (final EXistException e) {
LOG.debug(e);
return Collections.EMPTY_LIST;
}
}
@Override
public int getResourceCount(final String collectionName)
throws EXistException, PermissionDeniedException, URISyntaxException {
return getResourceCount(XmldbURI.xmldbUriFor(collectionName));
}
private int getResourceCount(final XmldbURI collUri)
throws EXistException, PermissionDeniedException {
return this.<Integer>readCollection(collUri).apply((collection, broker, transaction) -> collection.getDocumentCount(broker));
}
@Override
public String createResourceId(final String collectionName)
throws EXistException, PermissionDeniedException, URISyntaxException {
return createResourceId(XmldbURI.xmldbUriFor(collectionName));
}
/**
* Creates a unique name for a database resource Uniqueness is only
* guaranteed within the eXist instance
* <p>
* The name is based on a hex encoded string of a random integer and will
* have the format xxxxxxxx.xml where x is in the range 0 to 9 and a to f
*
* @return the unique resource name
*/
private String createResourceId(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
return this.<String>readCollection(collUri).apply((collection, broker, transaction) -> {
XmldbURI id;
final Random rand = new Random();
boolean ok;
do {
ok = true;
id = XmldbURI.create(Integer.toHexString(rand.nextInt()) + ".xml");
// check if this id does already exist
if (collection.hasDocument(broker, id)) {
ok = false;
}
if (collection.hasChildCollection(broker, id)) {
ok = false;
}
} while (!ok);
return id.toString();
});
}
@Override
public int getHits(final int resultId) throws EXistException {
final QueryResult qr = factory.resultSets.getResult(resultId);
if (qr == null) {
throw new EXistException("result set unknown or timed out");
}
qr.touch();
if (qr.result == null) {
return 0;
}
return qr.result.getItemCount();
}
@Override
public Map<String, Object> getPermissions(final String name)
throws EXistException, PermissionDeniedException, URISyntaxException {
return getPermissions(XmldbURI.xmldbUriFor(name));
}
private Map<String, Object> getPermissions(final XmldbURI uri) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
Collection collection = null;
try {
collection = broker.openCollection(uri, LockMode.READ_LOCK);
final Permission perm;
if (collection == null) {
DocumentImpl doc = null;
try {
doc = broker.getXMLResource(uri, LockMode.READ_LOCK);
if (doc == null) {
throw new EXistException("document or collection " + uri + " not found");
}
perm = doc.getPermissions();
} finally {
if (doc != null) {
doc.getUpdateLock().release(LockMode.READ_LOCK);
}
}
} else {
perm = collection.getPermissionsNoLock();
}
final Map<String, Object> result = new HashMap<>();
result.put("owner", perm.getOwner().getName());
result.put("group", perm.getGroup().getName());
result.put("permissions", perm.getMode());
if (perm instanceof ACLPermission) {
result.put("acl", getACEs(perm));
}
return result;
} finally {
if (collection != null) {
collection.release(LockMode.READ_LOCK);
}
}
});
}
@Override
public Map<String, Object> getSubCollectionPermissions(final String parentPath, final String name) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(parentPath);
final Permission perm = this.<Permission>readCollection(uri).apply((collection, broker, transaction) -> collection.getChildCollectionEntry(broker, name).getPermissions());
final Map<String, Object> result = new HashMap<>();
result.put("owner", perm.getOwner().getName());
result.put("group", perm.getGroup().getName());
result.put("permissions", perm.getMode());
if (perm instanceof ACLPermission) {
result.put("acl", getACEs(perm));
}
return result;
}
@Override
public Map<String, Object> getSubResourcePermissions(final String parentPath, final String name) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(parentPath);
final Permission perm = this.<Permission>readCollection(uri).apply((collection, broker, transaction) -> collection.getResourceEntry(broker, name).getPermissions());
final Map<String, Object> result = new HashMap<>();
result.put("owner", perm.getOwner().getName());
result.put("group", perm.getGroup().getName());
result.put("permissions", perm.getMode());
if (perm instanceof ACLPermission) {
result.put("acl", getACEs(perm));
}
return result;
}
@Override
public long getSubCollectionCreationTime(final String parentPath, final String name) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(parentPath);
return this.<Long>readCollection(uri).apply((collection, broker, transaction) -> collection.getChildCollectionEntry(broker, name).getCreated());
}
private List<ACEAider> getACEs(final Permission perm) {
final List<ACEAider> aces = new ArrayList<>();
final ACLPermission aclPermission = (ACLPermission) perm;
for (int i = 0; i < aclPermission.getACECount(); i++) {
aces.add(new ACEAider(aclPermission.getACEAccessType(i), aclPermission.getACETarget(i), aclPermission.getACEWho(i), aclPermission.getACEMode(i)));
}
return aces;
}
@Override
public Map<String, List> listDocumentPermissions(final String name)
throws EXistException, PermissionDeniedException, URISyntaxException {
return listDocumentPermissions(XmldbURI.xmldbUriFor(name));
}
private Map<String, List> listDocumentPermissions(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
return this.<Map<String, List>>readCollection(collUri).apply((collection, broker, transaction) -> {
final Map<String, List> result = new HashMap<>(collection.getDocumentCount(broker));
for (final Iterator<DocumentImpl> i = collection.iterator(broker); i.hasNext(); ) {
final DocumentImpl doc = i.next();
final Permission perm = doc.getPermissions();
final List tmp = new ArrayList(4);
tmp.add(perm.getOwner().getName());
tmp.add(perm.getGroup().getName());
tmp.add(perm.getMode());
if (perm instanceof ACLPermission) {
tmp.add(getACEs(perm));
}
result.put(doc.getFileURI().toString(), tmp);
}
return result;
});
}
@Override
public Map<XmldbURI, List> listCollectionPermissions(final String name)
throws EXistException, PermissionDeniedException, URISyntaxException {
return listCollectionPermissions(XmldbURI.xmldbUriFor(name));
}
private Map<XmldbURI, List> listCollectionPermissions(final XmldbURI collUri)
throws EXistException, PermissionDeniedException {
return this.<Map<XmldbURI, List>>readCollection(collUri).apply((collection, broker, transaction) -> {
final Map<XmldbURI, List> result = new HashMap<>(collection.getChildCollectionCount(broker));
for (final Iterator<XmldbURI> i = collection.collectionIterator(broker); i.hasNext(); ) {
final XmldbURI child = i.next();
final XmldbURI path = collUri.append(child);
final Collection childColl = broker.getCollection(path);
final Permission perm = childColl.getPermissionsNoLock();
final List tmp = new ArrayList(4);
tmp.add(perm.getOwner().getName());
tmp.add(perm.getGroup().getName());
tmp.add(perm.getMode());
if (perm instanceof ACLPermission) {
tmp.add(getACEs(perm));
}
result.put(child, tmp);
}
return result;
});
}
@Override
public Date getCreationDate(final String collectionPath)
throws PermissionDeniedException, EXistException, URISyntaxException {
return getCreationDate(XmldbURI.xmldbUriFor(collectionPath));
}
private Date getCreationDate(final XmldbURI collUri) throws PermissionDeniedException, EXistException {
return this.<Date>readCollection(collUri).apply((collection, broker, transaction) -> new Date(collection.getCreationTime()));
}
@Override
public List<Date> getTimestamps(final String documentPath)
throws PermissionDeniedException, EXistException, URISyntaxException {
return getTimestamps(XmldbURI.xmldbUriFor(documentPath));
}
private List<Date> getTimestamps(final XmldbURI docUri) throws PermissionDeniedException, EXistException {
return this.<List<Date>>readDocument(docUri).apply((document, broker, transaction) -> {
final DocumentMetadata metadata = document.getMetadata();
final List<Date> list = new ArrayList<>(2);
list.add(new Date(metadata.getCreated()));
list.add(new Date(metadata.getLastModified()));
return list;
});
}
@Override
public boolean setLastModified(final String documentPath, final long lastModified) throws EXistException, PermissionDeniedException {
return this.<Boolean>writeDocument(XmldbURI.create(documentPath)).apply((document, broker, transaction) -> {
//TODO : register the lock within the transaction ?
if (!document.getPermissions().validate(user, Permission.WRITE)) {
throw new PermissionDeniedException("User is not allowed to lock resource " + documentPath);
}
document.getMetadata().setLastModified(lastModified);
broker.storeXMLResource(transaction, document);
return true;
});
}
@Override
public Map<String, Object> getAccount(final String name) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final Account u = factory.getBrokerPool().getSecurityManager().getAccount(name);
if (u == null) {
throw new EXistException("account '" + name + "' does not exist");
}
final Map<String, Object> tab = new HashMap<>();
tab.put("uid", user.getId());
tab.put("name", u.getName());
tab.put("groups", Arrays.asList(u.getGroups()));
final Group dg = u.getDefaultGroup();
if (dg != null) {
tab.put("default-group-id", dg.getId());
tab.put("default-group-realmId", dg.getRealmId());
tab.put("default-group-name", dg.getName());
}
tab.put("enabled", Boolean.toString(u.isEnabled()));
tab.put("umask", u.getUserMask());
final Map<String, String> metadata = new HashMap<>();
for (final SchemaType key : u.getMetadataKeys()) {
metadata.put(key.getNamespace(), u.getMetadataValue(key));
}
tab.put("metadata", metadata);
return tab;
});
}
@Override
public List<Map<String, Object>> getAccounts() throws EXistException, PermissionDeniedException {
final java.util.Collection<Account> users = factory.getBrokerPool().getSecurityManager().getUsers();
final List<Map<String, Object>> r = new ArrayList<>();
for (final Account user : users) {
final Map<String, Object> tab = new HashMap<>();
tab.put("uid", user.getId());
tab.put("name", user.getName());
tab.put("groups", Arrays.asList(user.getGroups()));
tab.put("enabled", Boolean.toString(user.isEnabled()));
tab.put("umask", user.getUserMask());
final Map<String, String> metadata = new HashMap<>();
for (final SchemaType key : user.getMetadataKeys()) {
metadata.put(key.getNamespace(), user.getMetadataValue(key));
}
tab.put("metadata", metadata);
r.add(tab);
}
return r;
}
@Override
public List<String> getGroups() throws EXistException, PermissionDeniedException {
final java.util.Collection<Group> groups = factory.getBrokerPool().getSecurityManager().getGroups();
final List<String> v = new ArrayList<>(groups.size());
for (final Group group : groups) {
v.add(group.getName());
}
return v;
}
@Override
public Map<String, Object> getGroup(final String name) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final SecurityManager securityManager = factory.getBrokerPool().getSecurityManager();
final Group group = securityManager.getGroup(name);
if (group != null) {
final Map<String, Object> map = new HashMap<>();
map.put("id", group.getId());
map.put("realmId", group.getRealmId());
map.put("name", name);
final List<Account> groupManagers = group.getManagers();
final List<String> managers = new ArrayList<>(groupManagers.size());
for (final Account groupManager : groupManagers) {
managers.add(groupManager.getName());
}
map.put("managers", managers);
final Map<String, String> metadata = new HashMap<>();
for (final SchemaType key : group.getMetadataKeys()) {
metadata.put(key.getNamespace(), group.getMetadataValue(key));
}
map.put("metadata", metadata);
return map;
}
return null;
});
}
@Override
public void removeGroup(final String name) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> broker.getBrokerPool().getSecurityManager().deleteGroup(name));
}
@Override
public boolean hasDocument(final String documentPath) throws URISyntaxException, EXistException, PermissionDeniedException {
return hasDocument(XmldbURI.xmldbUriFor(documentPath));
}
private boolean hasDocument(final XmldbURI docUri) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> broker.getXMLResource(docUri) != null);
}
@Override
public boolean hasCollection(final String collectionName) throws EXistException, URISyntaxException, PermissionDeniedException {
return hasCollection(XmldbURI.xmldbUriFor(collectionName));
}
private boolean hasCollection(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> broker.getCollection(collUri) != null);
}
@Override
public boolean parse(byte[] xml, String documentPath, int overwrite) throws URISyntaxException, EXistException, PermissionDeniedException {
return parse(xml, documentPath, overwrite, null, null);
}
@Override
public boolean parse(final byte[] xml, final String documentPath,
final int overwrite, final Date created, final Date modified) throws URISyntaxException, EXistException, PermissionDeniedException {
return parse(xml, XmldbURI.xmldbUriFor(documentPath), overwrite, created, modified);
}
private boolean parse(final byte[] xml, final XmldbURI docUri,
final int overwrite, final Date created, final Date modified) throws EXistException, PermissionDeniedException {
return this.<Boolean>writeCollection(docUri.removeLastSegment()).apply((collection, broker, transaction) -> {
if (overwrite == 0) {
final DocumentImpl old = collection.getDocument(broker, docUri.lastSegment());
if (old != null) {
throw new PermissionDeniedException("Document exists and overwrite is not allowed");
}
}
try (final InputStream is = new ByteArrayInputStream(xml)) {
final InputSource source = new InputSource(is);
final long startTime = System.currentTimeMillis();
final IndexInfo info = collection.validateXMLResource(transaction, broker, docUri.lastSegment(), source);
final MimeType mime = MimeTable.getInstance().getContentTypeFor(docUri.lastSegment());
if (mime != null && mime.isXMLType()) {
info.getDocument().getMetadata().setMimeType(mime.getName());
}
if (created != null) {
info.getDocument().getMetadata().setCreated(created.getTime());
}
if (modified != null) {
info.getDocument().getMetadata().setLastModified(modified.getTime());
}
collection.store(transaction, broker, info, source);
LOG.debug("parsing " + docUri + " took " + (System.currentTimeMillis() - startTime) + "ms.");
return true;
}
});
}
/**
* Parse a file previously uploaded with upload.
* <p>
* The temporary file will be removed.
*
* @param localFile
* @param documentPath
* @param overwrite
* @param mimeType
* @return
* @throws EXistException
* @throws java.net.URISyntaxException
*/
public boolean parseLocal(final String localFile, final String documentPath,
final int overwrite, final String mimeType) throws EXistException, PermissionDeniedException, URISyntaxException {
return parseLocal(localFile, documentPath, overwrite, mimeType, null, null);
}
/**
* Parse a file previously uploaded with upload, forcing it to XML or
* Binary.
* <p>
* The temporary file will be removed.
*
* @param localFile
* @param documentPath
* @param overwrite
* @param mimeType
* @param isXML
* @return
* @throws EXistException
* @throws java.net.URISyntaxException
*/
public boolean parseLocalExt(final String localFile, final String documentPath,
final int overwrite, final String mimeType, final int isXML) throws EXistException, PermissionDeniedException, URISyntaxException {
return parseLocalExt(localFile, documentPath, overwrite, mimeType, isXML, null, null);
}
@SuppressWarnings("unused")
private boolean parseLocal(final String localFile, final XmldbURI docUri,
final int overwrite, final String mimeType) throws EXistException, PermissionDeniedException {
return parseLocal(localFile, docUri, overwrite, mimeType, null, null, null);
}
@SuppressWarnings("unused")
private boolean parseLocalExt(final String localFile, final XmldbURI docUri,
final int overwrite, final String mimeType, final int isXML) throws EXistException, PermissionDeniedException {
return parseLocal(localFile, docUri, overwrite, mimeType, isXML != 0, null, null);
}
public boolean parseLocal(final String localFile, final String documentPath, final int overwrite,
final String mimeType, final Date created, final Date modified) throws URISyntaxException, EXistException, PermissionDeniedException {
return parseLocal(localFile, XmldbURI.xmldbUriFor(documentPath), overwrite, mimeType, null, created, modified);
}
public boolean parseLocalExt(final String localFile, final String documentPath, final int overwrite,
final String mimeType, final int isXML, final Date created, final Date modified) throws URISyntaxException, EXistException, PermissionDeniedException {
return parseLocal(localFile, XmldbURI.xmldbUriFor(documentPath), overwrite, mimeType, isXML != 0, created, modified);
}
private boolean parseLocal(final String localFile, final XmldbURI docUri, final int overwrite, final String mimeType, final Boolean isXML, final Date created, final Date modified)
throws EXistException, PermissionDeniedException {
return this.<Boolean>writeCollection(docUri.removeLastSegment()).apply((collection, broker, transaction) -> {
if (overwrite == 0) {
final DocumentImpl old = collection.getDocument(broker, docUri.lastSegment());
if (old != null) {
throw new PermissionDeniedException("Old document exists and overwrite is not allowed");
}
}
// get the source for parsing
SupplierE<VirtualTempFileInputSource, IOException> sourceSupplier;
try {
final int handle = Integer.parseInt(localFile);
final SerializedResult sr = factory.resultSets.getSerializedResult(handle);
if (sr == null) {
throw new EXistException("Invalid handle specified");
}
sourceSupplier = () -> {
final VirtualTempFileInputSource source = new VirtualTempFileInputSource(sr.result);
sr.result = null; // de-reference the VirtualTempFile in the SerializeResult
factory.resultSets.remove(handle);
return source;
};
} catch (final NumberFormatException nfe) {
// As this file can be a non-temporal one, we should not
// blindly erase it!
final Path path = Paths.get(localFile);
if (!Files.isReadable(path)) {
throw new EXistException("unable to read file " + path.toAbsolutePath().toString());
}
sourceSupplier = () -> new VirtualTempFileInputSource(path);
}
// parse the source
try (final VirtualTempFileInputSource source = sourceSupplier.get()) {
final MimeType mime = Optional.ofNullable(MimeTable.getInstance().getContentType(mimeType)).orElse(MimeType.BINARY_TYPE);
final boolean treatAsXML = (isXML != null && isXML) || (isXML == null && mime.isXMLType());
if (treatAsXML) {
final IndexInfo info = collection.validateXMLResource(transaction, broker, docUri.lastSegment(), source);
if (created != null) {
info.getDocument().getMetadata().setCreated(created.getTime());
}
if (modified != null) {
info.getDocument().getMetadata().setLastModified(modified.getTime());
}
collection.store(transaction, broker, info, source);
} else {
try (final InputStream is = source.getByteStream()) {
final DocumentImpl doc = collection.addBinaryResource(transaction, broker, docUri.lastSegment(), is, mime.getName(), source.getByteStreamLength());
if (created != null) {
doc.getMetadata().setCreated(created.getTime());
}
if (modified != null) {
doc.getMetadata().setLastModified(modified.getTime());
}
}
}
return true;
}
});
}
public boolean storeBinary(final byte[] data, final String documentPath, final String mimeType,
final int overwrite) throws EXistException, PermissionDeniedException, URISyntaxException {
return storeBinary(data, documentPath, mimeType, overwrite, null, null);
}
@SuppressWarnings("unused")
private boolean storeBinary(final byte[] data, final XmldbURI docUri, final String mimeType,
final int overwrite) throws EXistException, PermissionDeniedException {
return storeBinary(data, docUri, mimeType, overwrite, null, null);
}
public boolean storeBinary(final byte[] data, final String documentPath, final String mimeType,
final int overwrite, final Date created, final Date modified) throws URISyntaxException, EXistException, PermissionDeniedException {
return storeBinary(data, XmldbURI.xmldbUriFor(documentPath), mimeType, overwrite, created, modified);
}
private boolean storeBinary(final byte[] data, final XmldbURI docUri, final String mimeType,
final int overwrite, final Date created, final Date modified) throws EXistException, PermissionDeniedException {
return this.<Boolean>writeCollection(docUri.removeLastSegment()).apply((collection, broker, transaction) -> {
// keep the write lock in the transaction
transaction.registerLock(collection.getLock(), LockMode.WRITE_LOCK);
if (overwrite == 0) {
final DocumentImpl old = collection.getDocument(broker, docUri.lastSegment());
if (old != null) {
throw new PermissionDeniedException("Old document exists and overwrite is not allowed");
}
}
LOG.debug("Storing binary resource to collection " + collection.getURI());
final DocumentImpl doc = collection.addBinaryResource(transaction, broker, docUri.lastSegment(), data, mimeType);
if (created != null) {
doc.getMetadata().setCreated(created.getTime());
}
if (modified != null) {
doc.getMetadata().setLastModified(modified.getTime());
}
return doc != null;
});
}
public String upload(final byte[] chunk, final int length, String fileName, final boolean compressed)
throws EXistException, IOException {
VirtualTempFile vtempFile;
if (fileName == null || fileName.length() == 0) {
// create temporary file
vtempFile = new VirtualTempFile(MAX_DOWNLOAD_CHUNK_SIZE, MAX_DOWNLOAD_CHUNK_SIZE);
vtempFile.setTempPrefix("rpc");
vtempFile.setTempPostfix(".xml");
final int handle = factory.resultSets.add(new SerializedResult(vtempFile));
fileName = Integer.toString(handle);
} else {
// LOG.debug("appending to file " + fileName);
try {
final int handle = Integer.parseInt(fileName);
final SerializedResult sr = factory.resultSets.getSerializedResult(handle);
if (sr == null) {
throw new EXistException("Invalid handle specified");
}
// This will keep the serialized result in the cache
sr.touch();
vtempFile = sr.result;
} catch (final NumberFormatException nfe) {
throw new EXistException("Syntactically invalid handle specified");
}
}
if (compressed) {
Compressor.uncompress(chunk, vtempFile);
} else {
vtempFile.write(chunk, 0, length);
}
return fileName;
}
protected String printAll(final DBBroker broker, final Sequence resultSet, int howmany,
int start, final Map<String, Object> properties, final long queryTime) throws EXistException, SAXException, XPathException {
if (resultSet.isEmpty()) {
final StringBuilder buf = new StringBuilder();
final String opt = (String) properties.get(OutputKeys.OMIT_XML_DECLARATION);
if (opt == null || opt.equalsIgnoreCase("no")) {
buf.append("<?xml version=\"1.0\"?>\n");
}
buf.append("<exist:result xmlns:exist=\"").append(Namespaces.EXIST_NS).append("\" ");
buf.append("hitCount=\"0\"/>");
return buf.toString();
}
if (howmany > resultSet.getItemCount() || howmany == 0) {
howmany = resultSet.getItemCount();
}
if (start < 1 || start > resultSet.getItemCount()) {
throw new EXistException("start parameter out of range");
}
final StringWriter writer = new StringWriter();
writer.write("<exist:result xmlns:exist=\"");
writer.write(Namespaces.EXIST_NS);
writer.write("\" hits=\"");
writer.write(Integer.toString(resultSet.getItemCount()));
writer.write("\" start=\"");
writer.write(Integer.toString(start));
writer.write("\" count=\"");
writer.write(Integer.toString(howmany));
writer.write("\">\n");
final Serializer serializer = broker.getSerializer();
serializer.reset();
serializer.setProperties(toProperties(properties));
Item item;
for (int i = --start; i < start + howmany; i++) {
item = resultSet.itemAt(i);
if (item == null) {
continue;
}
if (item.getType() == Type.ELEMENT) {
final NodeValue node = (NodeValue) item;
writer.write(serializer.serialize(node));
} else {
writer.write("<exist:value type=\"");
writer.write(Type.getTypeName(item.getType()));
writer.write("\">");
writer.write(item.getStringValue());
writer.write("</exist:value>");
}
}
writer.write("\n</exist:result>");
return writer.toString();
}
public Map<String, Object> compile(final String query, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(query);
return withDb((broker, transaction) -> {
final Map<String, Object> ret = new HashMap<>();
try {
compileQuery(broker, transaction, source, parameters).apply(compiledQuery -> null);
} catch (final XPathException e) {
ret.put(RpcAPI.ERROR, e.getMessage());
if (e.getLine() != 0) {
ret.put(RpcAPI.LINE, e.getLine());
ret.put(RpcAPI.COLUMN, e.getColumn());
}
}
return ret;
});
}
public String query(final String xpath, final int howmany, final int start,
final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(xpath);
return withDb((broker, transaction) -> {
final long startTime = System.currentTimeMillis();
try {
final QueryResult qr = this.<QueryResult>compileQuery(broker, transaction, source, parameters).apply(compiled -> doQuery(broker, compiled, null, parameters));
if (qr == null) {
return "<?xml version=\"1.0\"?>\n"
+ "<exist:result xmlns:exist=\"" + Namespaces.EXIST_NS + "\" "
+ "hitCount=\"0\"/>";
}
if (qr.hasErrors()) {
throw qr.getException();
}
return printAll(broker, qr.result, howmany, start, parameters, (System.currentTimeMillis() - startTime));
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
public Map<String, Object> queryP(final String xpath, final String documentPath,
final String s_id, final Map<String, Object> parameters) throws URISyntaxException, EXistException, PermissionDeniedException {
return queryP(xpath,
(documentPath == null) ? null : XmldbURI.xmldbUriFor(documentPath),
s_id, parameters);
}
private Map<String, Object> queryP(final String xpath, final XmldbURI docUri,
final String s_id, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(xpath);
final Optional<String> sortBy = Optional.ofNullable(parameters.get(RpcAPI.SORT_EXPR)).map(Object::toString);
return withDb((broker, transaction) -> {
final long startTime = System.currentTimeMillis();
final Map<String, Object> ret = new HashMap<>();
final List<Object> result = new ArrayList<>();
Sequence resultSeq = null;
final NodeSet nodes;
if (docUri != null && s_id != null) {
nodes = this.<NodeSet>readDocument(broker, transaction, docUri).apply((document, broker1, transaction1) -> {
final Object[] docs = new Object[1];
docs[0] = docUri.toString();
parameters.put(RpcAPI.STATIC_DOCUMENTS, docs);
if (s_id.length() > 0) {
final NodeId nodeId = factory.getBrokerPool().getNodeFactory().createFromString(s_id);
final NodeProxy node = new NodeProxy(document, nodeId);
final NodeSet nodeSet = new ExtArrayNodeSet(1);
nodeSet.add(node);
return nodeSet;
} else {
return null;
}
});
} else {
nodes = null;
}
try {
final QueryResult queryResult = this.<QueryResult>compileQuery(broker, transaction, source, parameters).apply(compiledQuery -> doQuery(broker, compiledQuery, nodes, parameters));
if (queryResult == null) {
return ret;
}
if (queryResult.hasErrors()) {
// return an error description
final XPathException e = queryResult.getException();
ret.put(RpcAPI.ERROR, e.getMessage());
if (e.getLine() != 0) {
ret.put(RpcAPI.LINE, e.getLine());
ret.put(RpcAPI.COLUMN, e.getColumn());
}
return ret;
}
resultSeq = queryResult.result;
if (LOG.isDebugEnabled()) {
LOG.debug("found " + resultSeq.getItemCount());
}
if (sortBy.isPresent()) {
final SortedNodeSet sorted = new SortedNodeSet(factory.getBrokerPool(), user, sortBy.get());
sorted.addAll(resultSeq);
resultSeq = sorted;
}
NodeProxy p;
List<String> entry;
if (resultSeq != null) {
final SequenceIterator i = resultSeq.iterate();
if (i != null) {
Item next;
while (i.hasNext()) {
next = i.nextItem();
if (Type.subTypeOf(next.getType(), Type.NODE)) {
entry = new ArrayList<>();
if (((NodeValue) next).getImplementationType() == NodeValue.PERSISTENT_NODE) {
p = (NodeProxy) next;
entry.add(p.getOwnerDocument().getURI().toString());
entry.add(p.getNodeId().toString());
} else {
entry.add("temp_xquery/" + next.hashCode());
entry.add(String.valueOf(((NodeImpl) next).getNodeNumber()));
}
result.add(entry);
} else {
result.add(next.getStringValue());
}
}
} else {
LOG.debug("sequence iterator is null. Should not");
}
} else {
LOG.debug("result sequence is null. Skipping it...");
}
queryResult.result = resultSeq;
queryResult.queryTime = (System.currentTimeMillis() - startTime);
final int id = factory.resultSets.add(queryResult);
ret.put("id", id);
ret.put("hash", queryResult.hashCode());
ret.put("results", result);
return ret;
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
@Override
public Map<String, Object> execute(final String pathToQuery, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final long startTime = System.currentTimeMillis();
final Optional<String> sortBy = Optional.ofNullable(parameters.get(RpcAPI.SORT_EXPR)).map(Object::toString);
return this.<Map<String, Object>>readDocument(XmldbURI.createInternal(pathToQuery)).apply((document, broker, transaction) -> {
final BinaryDocument xquery = (BinaryDocument) document;
if (xquery.getResourceType() != DocumentImpl.BINARY_FILE) {
throw new EXistException("Document " + pathToQuery + " is not a binary resource");
}
if (!xquery.getPermissions().validate(user, Permission.READ | Permission.EXECUTE)) {
throw new PermissionDeniedException("Insufficient privileges to access resource");
}
final Map<String, Object> ret = new HashMap<>();
final Source source = new DBSource(broker, xquery, true);
try {
final QueryResult queryResult = this.<QueryResult>compileQuery(broker, transaction, source, parameters).apply(compiledQuery -> doQuery(broker, compiledQuery, null, parameters));
if (queryResult == null) {
return ret;
}
if (queryResult.hasErrors()) {
// return an error description
final XPathException e = queryResult.getException();
ret.put(RpcAPI.ERROR, e.getMessage());
if (e.getLine() != 0) {
ret.put(RpcAPI.LINE, e.getLine());
ret.put(RpcAPI.COLUMN, e.getColumn());
}
return ret;
}
Sequence resultSeq = queryResult.result;
if (LOG.isDebugEnabled()) {
LOG.debug("found " + resultSeq.getItemCount());
}
if (sortBy.isPresent()) {
final SortedNodeSet sorted = new SortedNodeSet(factory.getBrokerPool(), user, sortBy.get());
sorted.addAll(resultSeq);
resultSeq = sorted;
}
final List<Object> result = new ArrayList<>();
if (resultSeq != null) {
final SequenceIterator i = resultSeq.iterate();
if (i != null) {
Item next;
while (i.hasNext()) {
next = i.nextItem();
if (Type.subTypeOf(next.getType(), Type.NODE)) {
final List<String> entry = new ArrayList<>();
if (((NodeValue) next).getImplementationType() == NodeValue.PERSISTENT_NODE) {
final NodeProxy p = (NodeProxy) next;
entry.add(p.getOwnerDocument().getURI().toString());
entry.add(p.getNodeId().toString());
} else {
entry.add("temp_xquery/" + next.hashCode());
entry.add(String.valueOf(((NodeImpl) next).getNodeNumber()));
}
result.add(entry);
} else {
result.add(next.getStringValue());
}
}
} else {
LOG.debug("sequence iterator is null. Should not be!");
}
} else {
LOG.debug("result sequence is null. Skipping it...");
}
queryResult.result = resultSeq;
queryResult.queryTime = (System.currentTimeMillis() - startTime);
final int id = factory.resultSets.add(queryResult);
ret.put("id", id);
ret.put("hash", queryResult.hashCode());
ret.put("results", result);
return ret;
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
@Override
public boolean releaseQueryResult(final int handle) {
factory.resultSets.remove(handle);
LOG.debug("removed query result with handle " + handle);
return true;
}
@Override
public boolean releaseQueryResult(final int handle, final int hash) {
factory.resultSets.remove(handle, hash);
LOG.debug("removed query result with handle " + handle);
return true;
}
@Override
public boolean remove(final String documentPath) throws URISyntaxException, EXistException, PermissionDeniedException {
return remove(XmldbURI.xmldbUriFor(documentPath));
}
private boolean remove(final XmldbURI docUri) throws EXistException, PermissionDeniedException {
return this.<Boolean>writeCollection(docUri.removeLastSegment()).apply((collection, broker, transaction) -> {
// keep the write lock in the transaction
transaction.registerLock(collection.getLock(), LockMode.WRITE_LOCK);
final DocumentImpl doc = collection.getDocument(broker, docUri.lastSegment());
if (doc == null) {
throw new EXistException("Document " + docUri + " not found");
}
if (doc.getResourceType() == DocumentImpl.BINARY_FILE) {
collection.removeBinaryResource(transaction, broker, doc);
} else {
collection.removeXMLResource(transaction, broker, docUri.lastSegment());
}
return true;
});
}
@Override
public boolean removeCollection(final String collectionName) throws URISyntaxException, EXistException, PermissionDeniedException {
return removeCollection(XmldbURI.xmldbUriFor(collectionName));
}
private boolean removeCollection(final XmldbURI collURI) throws EXistException, PermissionDeniedException {
try {
return this.<Boolean>writeCollection(collURI).apply((collection, broker, transaction) -> {
// keep the write lock in the transaction
transaction.registerLock(collection.getLock(), LockMode.WRITE_LOCK);
LOG.debug("removing collection " + collURI);
return broker.removeCollection(transaction, collection);
});
} catch (final EXistException e) {
LOG.debug(e);
return false;
}
}
@Override
public boolean removeAccount(final String name) throws EXistException, PermissionDeniedException {
final SecurityManager manager = factory.getBrokerPool().getSecurityManager();
if (!manager.hasAdminPrivileges(user)) {
throw new PermissionDeniedException("you are not allowed to remove users");
}
withDb((broker, transaction) -> manager.deleteAccount(name));
return true;
}
@Override
public byte[] retrieve(final String doc, final String id, final Map<String, Object> parameters)
throws EXistException, PermissionDeniedException {
try {
final String xml = retrieveAsString(doc, id, parameters);
return xml.getBytes(getEncoding(parameters));
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
@Override
public String retrieveAsString(final String documentPath, final String s_id,
final Map<String, Object> parameters) throws URISyntaxException, EXistException, PermissionDeniedException {
return retrieve(XmldbURI.xmldbUriFor(documentPath), s_id, parameters);
}
private String retrieve(final XmldbURI docUri, final String s_id,
final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return this.<String>readDocument(docUri).apply((document, broker, transaction) -> {
final NodeId nodeId = factory.getBrokerPool().getNodeFactory().createFromString(s_id);
final NodeProxy node = new NodeProxy(document, nodeId);
final Serializer serializer = broker.getSerializer();
serializer.reset();
serializer.setProperties(toProperties(parameters));
return serializer.serialize(node);
});
}
@Override
public Map<String, Object> retrieveFirstChunk(final String docName, final String id, final Map<String, Object> parameters)
throws EXistException, PermissionDeniedException {
final boolean compression = useCompression(parameters);
final XmldbURI docUri;
try {
docUri = XmldbURI.xmldbUriFor(docName);
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
return this.<Map<String, Object>>readDocument(docUri).apply((document, broker, transaction) -> {
final NodeId nodeId = factory.getBrokerPool().getNodeFactory().createFromString(id);
final NodeProxy node = new NodeProxy(document, nodeId);
final Serializer serializer = broker.getSerializer();
serializer.reset();
serializer.setProperties(toProperties(parameters));
final Map<String, Object> result = new HashMap<>();
VirtualTempFile vtempFile = new VirtualTempFile(MAX_DOWNLOAD_CHUNK_SIZE, MAX_DOWNLOAD_CHUNK_SIZE);
vtempFile.setTempPrefix("eXistRPCC");
vtempFile.setTempPostfix(".xml");
OutputStream os = null;
if (compression) {
LOG.debug("retrieveFirstChunk with compression");
os = new DeflaterOutputStream(vtempFile);
} else {
os = vtempFile;
}
try {
try (final Writer writer = new OutputStreamWriter(os, getEncoding(parameters))) {
serializer.serialize(node, writer);
}
} finally {
try {
os.close();
} catch (final IOException ioe) {
//IgnoreIT(R)
}
if (os != vtempFile) {
try {
vtempFile.close();
} catch (final IOException ioe) {
//IgnoreIT(R)
}
}
}
final byte[] firstChunk = vtempFile.getChunk(0);
result.put("data", firstChunk);
int offset = 0;
if (vtempFile.length() > MAX_DOWNLOAD_CHUNK_SIZE) {
offset = firstChunk.length;
final int handle = factory.resultSets.add(new SerializedResult(vtempFile));
result.put("handle", Integer.toString(handle));
result.put("supports-long-offset", Boolean.TRUE);
} else {
vtempFile.delete();
}
result.put("offset", offset);
return result;
});
}
@Override
public byte[] retrieve(final int resultId, final int num, final Map<String, Object> parameters)
throws EXistException, PermissionDeniedException {
final Charset encoding = getEncoding(parameters);
final boolean compression = useCompression(parameters);
final String xml = retrieveAsString(resultId, num, parameters);
if (compression) {
LOG.debug("retrieve with compression");
try {
return Compressor.compress(xml.getBytes(encoding));
} catch (final IOException ioe) {
throw new EXistException(ioe);
}
} else {
return xml.getBytes(encoding);
}
}
private String retrieveAsString(final int resultId, final int num,
final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final QueryResult qr = factory.resultSets.getResult(resultId);
if (qr == null) {
throw new EXistException("result set unknown or timed out");
}
qr.touch();
final Item item = qr.result.itemAt(num);
if (item == null) {
throw new EXistException("index out of range");
}
if (Type.subTypeOf(item.getType(), Type.NODE)) {
final NodeValue nodeValue = (NodeValue) item;
final Serializer serializer = broker.getSerializer();
serializer.reset();
for (final Map.Entry<Object, Object> entry : qr.serialization.entrySet()) {
parameters.put(entry.getKey().toString(), entry.getValue().toString());
}
serializer.setProperties(toProperties(parameters));
return serializer.serialize(nodeValue);
} else {
try {
return item.getStringValue();
} catch (final XPathException e) {
throw new EXistException(e);
}
}
});
}
@Override
public Map<String, Object> retrieveFirstChunk(final int resultId, final int num, final Map<String, Object> parameters)
throws EXistException, PermissionDeniedException {
final boolean compression = useCompression(parameters);
return withDb((broker, transaction) -> {
final QueryResult qr = factory.resultSets.getResult(resultId);
if (qr == null) {
throw new EXistException("result set unknown or timed out: " + resultId);
}
qr.touch();
final Item item = qr.result.itemAt(num);
if (item == null) {
throw new EXistException("index out of range");
}
final Map<String, Object> result = new HashMap<>();
VirtualTempFile vtempFile = new VirtualTempFile(MAX_DOWNLOAD_CHUNK_SIZE, MAX_DOWNLOAD_CHUNK_SIZE);
vtempFile.setTempPrefix("eXistRPCC");
vtempFile.setTempPostfix(".xml");
OutputStream os;
if (compression) {
LOG.debug("retrieveFirstChunk with compression");
os = new DeflaterOutputStream(vtempFile);
} else {
os = vtempFile;
}
try {
try (final Writer writer = new OutputStreamWriter(os, getEncoding(parameters))) {
if (Type.subTypeOf(item.getType(), Type.NODE)) {
final NodeValue nodeValue = (NodeValue) item;
final Serializer serializer = broker.getSerializer();
serializer.reset();
for (final Map.Entry<Object, Object> entry : qr.serialization.entrySet()) {
parameters.put(entry.getKey().toString(), entry.getValue().toString());
}
serializer.setProperties(toProperties(parameters));
serializer.serialize(nodeValue, writer);
} else {
writer.write(item.getStringValue());
}
} catch (final XPathException e) {
throw new EXistException(e);
}
} finally {
try {
os.close();
} catch (final IOException ioe) {
//IgnoreIT(R)
}
if (os != vtempFile) {
try {
vtempFile.close();
} catch (final IOException ioe) {
//IgnoreIT(R)
}
}
}
final byte[] firstChunk = vtempFile.getChunk(0);
result.put("data", firstChunk);
int offset = 0;
if (vtempFile.length() > MAX_DOWNLOAD_CHUNK_SIZE) {
offset = firstChunk.length;
final int handle = factory.resultSets.add(new SerializedResult(vtempFile));
result.put("handle", Integer.toString(handle));
result.put("supports-long-offset", Boolean.TRUE);
} else {
vtempFile.delete();
}
result.put("offset", offset);
return result;
});
}
@Override
public byte[] retrieveAll(final int resultId, final Map<String, Object> parameters) throws EXistException,
PermissionDeniedException {
final String xml = retrieveAllAsString(resultId, parameters);
return xml.getBytes(getEncoding(parameters));
}
private String retrieveAllAsString(final int resultId, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final QueryResult qr = factory.resultSets.getResult(resultId);
if (qr == null) {
throw new EXistException("result set unknown or timed out");
}
qr.touch();
final Serializer serializer = broker.getSerializer();
serializer.reset();
serializer.setProperties(qr.serialization);
final SAXSerializer handler = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
final StringWriter writer = new StringWriter();
handler.setOutput(writer, toProperties(parameters));
// serialize results
handler.startDocument();
handler.startPrefixMapping("exist", Namespaces.EXIST_NS);
handler.startPrefixMapping("xs", Namespaces.SCHEMA_NS);
final AttributesImpl attribs = new AttributesImpl();
attribs.addAttribute("", "hitCount", "hitCount", "CDATA", Integer.toString(qr.result.getItemCount()));
handler.startElement(Namespaces.EXIST_NS, "result", "exist:result", attribs);
Item current;
char[] value;
try {
for (final SequenceIterator i = qr.result.iterate(); i.hasNext(); ) {
current = i.nextItem();
if (Type.subTypeOf(current.getType(), Type.NODE)) {
current.toSAX(broker, handler, null);
} else {
final AttributesImpl typeAttr = new AttributesImpl();
typeAttr.addAttribute("", "type", "type", "CDATA", Type.getTypeName(current.getType()));
handler.startElement(Namespaces.EXIST_NS, "value", "exist:value", typeAttr);
value = current.toString().toCharArray();
handler.characters(value, 0, value.length);
handler.endElement(Namespaces.EXIST_NS, "value", "exist:value");
}
}
} catch (final XPathException e) {
throw new EXistException(e);
}
handler.endElement(Namespaces.EXIST_NS, "result", "exist:result");
handler.endPrefixMapping("xs");
handler.endPrefixMapping("exist");
handler.endDocument();
SerializerPool.getInstance().returnObject(handler);
return writer.toString();
});
}
@Override
public Map<String, Object> retrieveAllFirstChunk(final int resultId, final Map<String, Object> parameters)
throws EXistException, PermissionDeniedException {
final boolean compression = useCompression(parameters);
return withDb((broker, transaction) -> {
final QueryResult qr = factory.resultSets.getResult(resultId);
if (qr == null) {
throw new EXistException("result set unknown or timed out");
}
qr.touch();
final Serializer serializer = broker.getSerializer();
serializer.reset();
for (final Map.Entry<Object, Object> entry : qr.serialization.entrySet()) {
parameters.put(entry.getKey().toString(), entry.getValue().toString());
}
serializer.setProperties(toProperties(parameters));
final SAXSerializer handler = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
final Map<String, Object> result = new HashMap<>();
VirtualTempFile vtempFile = new VirtualTempFile(MAX_DOWNLOAD_CHUNK_SIZE, MAX_DOWNLOAD_CHUNK_SIZE);
vtempFile.setTempPrefix("eXistRPCC");
vtempFile.setTempPostfix(".xml");
OutputStream os;
if (compression) {
LOG.debug("retrieveAllFirstChunk compression");
os = new DeflaterOutputStream(vtempFile);
} else {
os = vtempFile;
}
try {
try (final Writer writer = new OutputStreamWriter(os, getEncoding(parameters))) {
handler.setOutput(writer, toProperties(parameters));
// serialize results
handler.startDocument();
handler.startPrefixMapping("exist", Namespaces.EXIST_NS);
final AttributesImpl attribs = new AttributesImpl();
attribs.addAttribute(
"",
"hitCount",
"hitCount",
"CDATA",
Integer.toString(qr.result.getItemCount()));
handler.startElement(
Namespaces.EXIST_NS,
"result",
"exist:result",
attribs);
Item current;
char[] value;
try {
for (final SequenceIterator i = qr.result.iterate(); i.hasNext(); ) {
current = i.nextItem();
if (Type.subTypeOf(current.getType(), Type.NODE)) {
((NodeValue) current).toSAX(broker, handler, null);
} else {
value = current.toString().toCharArray();
handler.characters(value, 0, value.length);
}
}
} catch (final XPathException e) {
throw new EXistException(e);
}
handler.endElement(Namespaces.EXIST_NS, "result", "exist:result");
handler.endPrefixMapping("exist");
handler.endDocument();
SerializerPool.getInstance().returnObject(handler);
}
} finally {
try {
os.close();
} catch (final IOException ioe) {
//IgnoreIT(R)
}
if (os != vtempFile) {
try {
vtempFile.close();
} catch (final IOException ioe) {
//IgnoreIT(R)
}
}
}
final byte[] firstChunk = vtempFile.getChunk(0);
result.put("data", firstChunk);
int offset = 0;
if (vtempFile.length() > MAX_DOWNLOAD_CHUNK_SIZE) {
offset = firstChunk.length;
final int handle = factory.resultSets.add(new SerializedResult(vtempFile));
result.put("handle", Integer.toString(handle));
result.put("supports-long-offset", Boolean.TRUE);
} else {
vtempFile.delete();
}
result.put("offset", offset);
return result;
});
}
@Override
public boolean chgrp(final String resource, final String ownerGroup) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.updatePermissions(broker, uri, permission -> permission.setGroup(ownerGroup));
return true;
});
}
@Override
public boolean chown(final String resource, final String owner) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.updatePermissions(broker, uri, permission -> permission.setOwner(owner));
return true;
});
}
@Override
public boolean chown(final String resource, final String owner, final String ownerGroup) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.updatePermissions(broker, uri, permission -> {
permission.setOwner(owner);
permission.setGroup(ownerGroup);
});
return true;
});
}
@Override
public boolean setPermissions(final String resource, final int permissions) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.updatePermissions(broker, uri, permission -> permission.setMode(permissions));
return true;
});
}
@Override
public boolean setPermissions(final String resource, final String permissions) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.updatePermissions(broker, uri, permission -> {
try {
permission.setMode(permissions);
} catch (final SyntaxException se) {
throw new PermissionDeniedException("Unrecognised mode syntax: " + se.getMessage(), se);
}
});
return true;
});
}
@Override
public boolean setPermissions(final String resource, final String owner, final String ownerGroup, final String permissions) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.updatePermissions(broker, uri, permission -> {
permission.setOwner(owner);
permission.setGroup(ownerGroup);
try {
permission.setMode(permissions);
} catch (final SyntaxException se) {
throw new PermissionDeniedException("Unrecognised mode syntax: " + se.getMessage(), se);
}
});
return true;
});
}
@Override
public boolean setPermissions(final String resource, final String owner, final String ownerGroup, final int permissions) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.updatePermissions(broker, uri, permission -> {
permission.setOwner(owner);
permission.setGroup(ownerGroup);
permission.setMode(permissions);
});
return true;
});
}
@Override
public boolean setPermissions(final String resource, final String owner, final String group, final int mode, final List<ACEAider> aces) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.updatePermissions(broker, uri, permission -> {
permission.setOwner(owner);
permission.setGroup(group);
permission.setMode(mode);
if (permission instanceof ACLPermission) {
final ACLPermission aclPermission = ((ACLPermission) permission);
aclPermission.clear();
for (final ACEAider ace : aces) {
aclPermission.addACE(ace.getAccessType(), ace.getTarget(), ace.getWho(), ace.getMode());
}
}
});
return true;
});
}
@Override
public boolean addAccount(final String name, String passwd, final String passwdDigest, final List<String> groups, final Boolean enabled, final Integer umask, final Map<String, String> metadata) throws EXistException, PermissionDeniedException {
if (passwd.length() == 0) {
passwd = null;
}
final SecurityManager manager = factory.getBrokerPool().getSecurityManager();
if (manager.hasAccount(name)) {
throw new PermissionDeniedException("Account '" + name + "' exist");
}
if (!manager.hasAdminPrivileges(user)) {
throw new PermissionDeniedException("Account '" + user.getName() + "' not allowed to create new account");
}
final UserAider u = new UserAider(name);
u.setEncodedPassword(passwd);
u.setPasswordDigest(passwdDigest);
for (final String g : groups) {
if (!u.hasGroup(g)) {
u.addGroup(g);
}
}
if (enabled != null) {
u.setEnabled(enabled);
}
if (umask != null) {
u.setUserMask(umask);
}
if (metadata != null) {
for (final Map.Entry<String, String> m : metadata.entrySet()) {
if (AXSchemaType.valueOfNamespace(m.getKey()) != null) {
u.setMetadataValue(AXSchemaType.valueOfNamespace(m.getKey()), m.getValue());
} else if (EXistSchemaType.valueOfNamespace(m.getKey()) != null) {
u.setMetadataValue(EXistSchemaType.valueOfNamespace(m.getKey()), m.getValue());
}
}
}
withDb((broker, transaction) -> manager.addAccount(u));
return true;
}
@Override
public boolean updateAccount(final String name, final String passwd, final String passwdDigest, final List<String> groups) throws EXistException, PermissionDeniedException {
return updateAccount(name, passwd, passwdDigest, groups, null, null, null);
}
@Override
public boolean updateAccount(final String name, String passwd, final String passwdDigest, final List<String> groups, final Boolean enabled, final Integer umask, final Map<String, String> metadata) throws EXistException, PermissionDeniedException {
if (passwd.length() == 0) {
passwd = null;
}
final UserAider account = new UserAider(name);
account.setEncodedPassword(passwd);
account.setPasswordDigest(passwdDigest);
for (final String g : groups) {
account.addGroup(g);
}
if (enabled != null) {
account.setEnabled(enabled);
}
if (umask != null) {
account.setUserMask(umask);
}
if (metadata != null) {
for (final Map.Entry<String, String> m : metadata.entrySet()) {
if (AXSchemaType.valueOfNamespace(m.getKey()) != null) {
account.setMetadataValue(AXSchemaType.valueOfNamespace(m.getKey()), m.getValue());
} else if (EXistSchemaType.valueOfNamespace(m.getKey()) != null) {
account.setMetadataValue(EXistSchemaType.valueOfNamespace(m.getKey()), m.getValue());
}
}
}
final SecurityManager manager = factory.getBrokerPool().getSecurityManager();
withDb((broker, transaction) -> manager.updateAccount(account));
return true;
}
@Override
public boolean addGroup(final String name, final Map<String, String> metadata) throws EXistException, PermissionDeniedException {
final SecurityManager manager = factory.getBrokerPool().getSecurityManager();
if (!manager.hasGroup(name)) {
if (!manager.hasAdminPrivileges(user)) {
throw new PermissionDeniedException("Not allowed to create group");
}
final Group role = new GroupAider(name);
for (final Map.Entry<String, String> m : metadata.entrySet()) {
if (AXSchemaType.valueOfNamespace(m.getKey()) != null) {
role.setMetadataValue(AXSchemaType.valueOfNamespace(m.getKey()), m.getValue());
} else if (EXistSchemaType.valueOfNamespace(m.getKey()) != null) {
role.setMetadataValue(EXistSchemaType.valueOfNamespace(m.getKey()), m.getValue());
}
}
withDb((broker, transaction) -> manager.addGroup(broker, role));
return true;
}
return false;
}
public boolean setUserPrimaryGroup(final String username, final String groupName) throws EXistException, PermissionDeniedException {
final SecurityManager manager = factory.getBrokerPool().getSecurityManager();
if (!manager.hasGroup(groupName)) {
throw new EXistException("Group '" + groupName + "' does not exist!");
}
if (!manager.hasAdminPrivileges(user)) {
throw new PermissionDeniedException("Not allowed to modify user");
}
withDb((broker, transaction) -> {
final Account account = manager.getAccount(username);
final Group group = manager.getGroup(groupName);
account.setPrimaryGroup(group);
manager.updateAccount(account);
return null;
});
return true;
}
@Override
public boolean updateGroup(final String name, final List<String> managers, final Map<String, String> metadata) throws EXistException, PermissionDeniedException {
final SecurityManager manager = factory.getBrokerPool().getSecurityManager();
if (manager.hasGroup(name)) {
final GroupAider group = new GroupAider(name);
for (final String groupManager : managers) {
group.addManager(new UserAider(groupManager));
}
if (metadata != null) {
for (final Map.Entry<String, String> m : metadata.entrySet()) {
if (AXSchemaType.valueOfNamespace(m.getKey()) != null) {
group.setMetadataValue(AXSchemaType.valueOfNamespace(m.getKey()), m.getValue());
} else if (EXistSchemaType.valueOfNamespace(m.getKey()) != null) {
group.setMetadataValue(EXistSchemaType.valueOfNamespace(m.getKey()), m.getValue());
}
}
}
withDb((broker, transaction) -> manager.updateGroup(group));
return true;
} else {
return false;
}
}
@Override
public List<String> getGroupMembers(final String groupName) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> broker.getBrokerPool().getSecurityManager().findAllGroupMembers(groupName));
}
@Override
public void addAccountToGroup(final String accountName, final String groupName) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
final SecurityManager sm = broker.getBrokerPool().getSecurityManager();
final Account account = sm.getAccount(accountName);
account.addGroup(groupName);
sm.updateAccount(account);
return null;
});
}
@Override
public void addGroupManager(final String manager, final String groupName) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
final SecurityManager sm = broker.getBrokerPool().getSecurityManager();
final Account account = sm.getAccount(manager);
final Group group = sm.getGroup(groupName);
group.addManager(account);
sm.updateGroup(group);
return null;
});
}
@Override
public void removeGroupManager(final String groupName, final String manager) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
final SecurityManager sm = broker.getBrokerPool().getSecurityManager();
final Group group = sm.getGroup(groupName);
final Account account = sm.getAccount(manager);
group.removeManager(account);
sm.updateGroup(group);
return null;
});
}
public void removeGroupMember(final String group, final String member) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
final SecurityManager sm = broker.getBrokerPool().getSecurityManager();
final Account account = sm.getAccount(member);
account.remGroup(group);
sm.updateAccount(account);
return null;
});
}
/**
* Added by {Marco.Tampucci, Massimo.Martinelli} @isti.cnr.it
* <p>
* modified by Chris Tomlinson based on above updateAccount - it appears
* that this code can rely on the SecurityManager to enforce policy about
* whether user is or is not permitted to update the Account with name.
* <p>
* This is called via RemoteUserManagementService.addUserGroup(Account)
*
* @param name
* @return
* @throws org.exist.security.PermissionDeniedException
*/
public boolean updateAccount(final String name, final List<String> groups) throws EXistException, PermissionDeniedException {
try {
return withDb((broker, transaction) -> {
final SecurityManager manager = broker.getBrokerPool().getSecurityManager();
Account u;
if (!manager.hasAccount(name)) {
u = new UserAider(name);
} else {
u = manager.getAccount(name);
}
for (final String g : groups) {
if (!u.hasGroup(g)) {
u.addGroup(g);
}
}
return manager.updateAccount(u);
});
} catch (final EXistException | PermissionDeniedException e) {
LOG.debug("addUserGroup encountered error", e);
return false;
}
}
/**
* Added by {Marco.Tampucci, Massimo.Martinelli} @isti.cnr.it
* <p>
* modified by Chris Tomlinson based on above updateAccount - it appears
* that this code can rely on the SecurityManager to enforce policy about
* whether user is or is not permitted to update the Account with name.
* <p>
* This is called via RemoteUserManagementService.removeGroup(Account,
* String)
*
* @param name
* @param groups
* @param rgroup
* @return
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
public boolean updateAccount(final String name, final List<String> groups, final String rgroup) throws EXistException, PermissionDeniedException {
try {
return withDb((broker, transaction) -> {
final SecurityManager manager = broker.getBrokerPool().getSecurityManager();
final Account u = manager.getAccount(name);
for (final String g : groups) {
if (g.equals(rgroup)) {
u.remGroup(g);
}
}
return manager.updateAccount(u);
});
} catch (final EXistException | PermissionDeniedException ex) {
LOG.debug("removeGroup encountered error", ex);
return false;
}
}
@Override
public boolean lockResource(final String documentPath, final String userName) throws EXistException, PermissionDeniedException, URISyntaxException {
return lockResource(XmldbURI.xmldbUriFor(documentPath), userName);
}
private boolean lockResource(final XmldbURI docURI, final String userName) throws EXistException, PermissionDeniedException {
return this.<Boolean>writeDocument(docURI).apply((document, broker, transaction) -> {
//TODO : register the lock within the transaction ?
if (!document.getPermissions().validate(user, Permission.WRITE)) {
throw new PermissionDeniedException("User is not allowed to lock resource " + docURI);
}
final SecurityManager manager = factory.getBrokerPool().getSecurityManager();
if (!(userName.equals(user.getName()) || manager.hasAdminPrivileges(user))) {
throw new PermissionDeniedException("User " + user.getName() + " is not allowed "
+ "to lock the resource for user " + userName);
}
final Account lockOwner = document.getUserLock();
if (lockOwner != null && (!lockOwner.equals(user)) && (!manager.hasAdminPrivileges(user))) {
throw new PermissionDeniedException("Resource is already locked by user "
+ lockOwner.getName());
}
document.setUserLock(user);
broker.storeXMLResource(transaction, document);
return true;
});
}
@Override
public String hasUserLock(final String documentPath) throws URISyntaxException, EXistException, PermissionDeniedException {
return hasUserLock(XmldbURI.xmldbUriFor(documentPath));
}
private String hasUserLock(final XmldbURI docURI) throws EXistException, PermissionDeniedException {
return this.<String>readDocument(docURI).apply((document, broker, transaction) -> {
if (!document.getPermissions().validate(user, Permission.READ)) {
throw new PermissionDeniedException("Insufficient privileges to read resource");
}
final Account u = document.getUserLock();
return u == null ? "" : u.getName();
});
}
@Override
public boolean unlockResource(final String documentPath) throws URISyntaxException, EXistException, PermissionDeniedException {
return unlockResource(XmldbURI.xmldbUriFor(documentPath));
}
private boolean unlockResource(final XmldbURI docURI) throws EXistException, PermissionDeniedException {
return this.<Boolean>writeDocument(docURI).apply((document, broker, transaction) -> {
if (!document.getPermissions().validate(user, Permission.WRITE)) {
throw new PermissionDeniedException("User is not allowed to lock resource " + docURI);
}
final SecurityManager manager = factory.getBrokerPool().getSecurityManager();
final Account lockOwner = document.getUserLock();
if (lockOwner != null && (!lockOwner.equals(user)) && (!manager.hasAdminPrivileges(user))) {
throw new PermissionDeniedException("Resource is already locked by user "
+ lockOwner.getName());
}
document.setUserLock(null);
broker.storeXMLResource(transaction, document);
return true;
});
}
public Map<String, Object> summary(final String xpath) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(xpath);
return this.<Map<String, Object>>withDb((broker, transaction) -> {
final long startTime = System.currentTimeMillis();
final Map<String, Object> parameters = new HashMap<>();
try {
final QueryResult qr = this.<QueryResult>compileQuery(broker, transaction, source, parameters).apply(compiledQuery -> doQuery(broker, compiledQuery, null, parameters));
if (qr == null) {
return new HashMap<>();
}
if (qr.hasErrors()) {
throw qr.getException();
}
final Map<String, NodeCount> map = new HashMap<>();
final Map<String, DoctypeCount> doctypes = new HashMap<>();
NodeProxy p;
String docName;
DocumentType doctype;
NodeCount counter;
DoctypeCount doctypeCounter;
for (final SequenceIterator i = qr.result.iterate(); i.hasNext(); ) {
final Item item = i.nextItem();
if (Type.subTypeOf(item.getType(), Type.NODE)) {
final NodeValue nv = (NodeValue) item;
if (nv.getImplementationType() == NodeValue.PERSISTENT_NODE) {
p = (NodeProxy) nv;
docName = p.getOwnerDocument().getURI().toString();
doctype = p.getOwnerDocument().getDoctype();
if (map.containsKey(docName)) {
counter = map.get(docName);
counter.inc();
} else {
counter = new NodeCount(p.getOwnerDocument());
map.put(docName, counter);
}
if (doctype == null) {
continue;
}
if (doctypes.containsKey(doctype.getName())) {
doctypeCounter = doctypes.get(doctype.getName());
doctypeCounter.inc();
} else {
doctypeCounter = new DoctypeCount(doctype);
doctypes.put(doctype.getName(), doctypeCounter);
}
}
}
}
final Map<String, Object> result = new HashMap<>();
result.put("queryTime", System.currentTimeMillis() - startTime);
result.put("hits", qr.result.getItemCount());
final List<List> documents = new ArrayList<>();
for (final NodeCount nodeCounter : map.values()) {
final List<Object> hitsByDoc = new ArrayList<>();
hitsByDoc.add(nodeCounter.doc.getFileURI().toString());
hitsByDoc.add(nodeCounter.doc.getDocId());
hitsByDoc.add(nodeCounter.count);
documents.add(hitsByDoc);
}
result.put("documents", documents);
final List<List> dtypes = new ArrayList<>();
for (final DoctypeCount docTemp : doctypes.values()) {
final List<Object> hitsByType = new ArrayList<>();
hitsByType.add(docTemp.doctype.getName());
hitsByType.add(docTemp.count);
dtypes.add(hitsByType);
}
result.put("doctypes", dtypes);
return result;
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
public Map<String, Object> summary(final int resultId) throws EXistException, XPathException {
final QueryResult qr = factory.resultSets.getResult(resultId);
if (qr == null) {
throw new EXistException("result set unknown or timed out");
}
qr.touch();
final Map<String, Object> result = new HashMap<>();
result.put("queryTime", qr.queryTime);
if (qr.result == null) {
result.put("hits", 0);
return result;
}
final Map<String, NodeCount> map = new HashMap<>();
final Map<String, DoctypeCount> doctypes = new HashMap<>();
NodeProxy p;
String docName;
DocumentType doctype;
NodeCount counter;
DoctypeCount doctypeCounter;
for (final SequenceIterator i = qr.result.iterate(); i.hasNext(); ) {
final Item item = i.nextItem();
if (Type.subTypeOf(item.getType(), Type.NODE)) {
final NodeValue nv = (NodeValue) item;
if (nv.getImplementationType() == NodeValue.PERSISTENT_NODE) {
p = (NodeProxy) nv;
docName = p.getOwnerDocument().getURI().toString();
doctype = p.getOwnerDocument().getDoctype();
if (map.containsKey(docName)) {
counter = map.get(docName);
counter.inc();
} else {
counter = new NodeCount(p.getOwnerDocument());
map.put(docName, counter);
}
if (doctype == null) {
continue;
}
if (doctypes.containsKey(doctype.getName())) {
doctypeCounter = (DoctypeCount) doctypes.get(doctype
.getName());
doctypeCounter.inc();
} else {
doctypeCounter = new DoctypeCount(doctype);
doctypes.put(doctype.getName(), doctypeCounter);
}
}
}
}
result.put("hits", qr.result.getItemCount());
final List<List> documents = new ArrayList<>();
for (final NodeCount nodeCounter : map.values()) {
final List<Object> hitsByDoc = new ArrayList<>();
hitsByDoc.add(nodeCounter.doc.getFileURI().toString());
hitsByDoc.add(nodeCounter.doc.getDocId());
hitsByDoc.add(nodeCounter.count);
documents.add(hitsByDoc);
}
result.put("documents", documents);
final List<List> dtypes = new ArrayList<>();
for (final DoctypeCount docTemp : doctypes.values()) {
final List<Object> hitsByType = new ArrayList<>();
hitsByType.add(docTemp.doctype.getName());
hitsByType.add(docTemp.count);
dtypes.add(hitsByType);
}
result.put("doctypes", dtypes);
return result;
}
@Override
public List<List> getIndexedElements(final String collectionName,
final boolean inclusive) throws EXistException, PermissionDeniedException, URISyntaxException {
return getIndexedElements(XmldbURI.xmldbUriFor(collectionName), inclusive);
}
private List<List> getIndexedElements(final XmldbURI collUri,
final boolean inclusive) throws EXistException, PermissionDeniedException {
return this.<List<List>>readCollection(collUri).apply((collection, broker, transaction) -> {
final Occurrences occurrences[] = broker.getElementIndex().scanIndexedElements(collection,
inclusive);
final List<List> result = new ArrayList<>(occurrences.length);
for (final Occurrences occurrence : occurrences) {
final QName qname = (QName) occurrence.getTerm();
final List temp = new ArrayList(4);
temp.add(qname.getLocalPart());
temp.add(qname.getNamespaceURI());
temp.add(qname.getPrefix() == null ? "" : qname.getPrefix());
temp.add(occurrence.getOccurrences());
result.add(temp);
}
return result;
});
}
public void synchronize() {
}
private Properties toProperties(final Map<String, Object> parameters) {
final Properties properties = new Properties();
properties.putAll(parameters);
return properties;
}
static class CachedQuery {
final PathExpr expression;
final String queryString;
final long timestamp;
public CachedQuery(final PathExpr expr, final String query) {
this.expression = expr;
this.queryString = query;
this.timestamp = System.currentTimeMillis();
}
}
static class DoctypeCount {
int count = 1;
final DocumentType doctype;
public DoctypeCount(final DocumentType doctype) {
this.doctype = doctype;
}
public void inc() {
count++;
}
}
static class NodeCount {
int count = 1;
final DocumentImpl doc;
public NodeCount(final DocumentImpl doc) {
this.doc = doc;
}
public void inc() {
count++;
}
}
// FIXME: Check it for possible security hole. Check name.
@Override
public byte[] getDocumentChunk(final String name, final int start, final int len)
throws EXistException, PermissionDeniedException, IOException {
final Path file = Paths.get(System.getProperty("java.io.tmpdir")).resolve(name);
if (!Files.isReadable(file)) {
throw new EXistException("unable to read file " + name);
}
if (FileUtils.sizeQuietly(file) < start + len) {
throw new EXistException("address too big " + name);
}
final byte buffer[] = new byte[len];
try (final RandomAccessFile os = new RandomAccessFile(file.toFile(), "r")) {
LOG.debug("Read from: " + start + " to: " + (start + len));
os.seek(start);
os.read(buffer);
}
return buffer;
}
public boolean moveOrCopyResource(final String documentPath, final String destinationPath,
final String newName, final boolean move)
throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyResource(XmldbURI.xmldbUriFor(documentPath),
XmldbURI.xmldbUriFor(destinationPath), XmldbURI.xmldbUriFor(newName), move);
}
private boolean moveOrCopyResource(final XmldbURI docUri, final XmldbURI destUri,
final XmldbURI newName, final boolean move)
throws EXistException, PermissionDeniedException {
// use WRITE_LOCK if moving or if src and dest collection are the same
final LockMode srcCollectionMode = move
|| docUri.removeLastSegment().equals(destUri) ? LockMode.WRITE_LOCK : LockMode.READ_LOCK;
return withDb((broker, transaction) ->
this.<Boolean>withCollection(srcCollectionMode, broker, transaction, docUri.removeLastSegment()).apply((source, broker1, transaction1) ->
this.<Boolean>writeDocument(broker1, transaction1, source, docUri).apply((document, broker2, transaction2) ->
this.<Boolean>writeCollection(broker2, transaction2, destUri).apply((destination, broker3, transaction3) -> {
if (move) {
broker3.moveResource(transaction3, document, destination, newName);
} else {
broker3.copyResource(transaction3, document, destination, newName);
}
return true;
})
)
)
);
}
public boolean moveOrCopyCollection(final String collectionName, final String destinationPath,
final String newName, final boolean move)
throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyCollection(XmldbURI.xmldbUriFor(collectionName),
XmldbURI.xmldbUriFor(destinationPath), XmldbURI.xmldbUriFor(newName), move);
}
private boolean moveOrCopyCollection(final XmldbURI collUri, final XmldbURI destUri,
final XmldbURI newName, final boolean move)
throws EXistException, PermissionDeniedException {
// use WRITE_LOCK if moving or if src and dest collection are the same
final LockMode srcCollectionMode = move
|| collUri.equals(destUri) ? LockMode.WRITE_LOCK : LockMode.READ_LOCK;
return withDb((broker, transaction) ->
this.<Boolean>withCollection(srcCollectionMode, broker, transaction, collUri).apply((source, broker1, transaction1) ->
this.<Boolean>writeCollection(broker1, transaction1, destUri).apply((destination, broker2, transaction2) -> {
if (move) {
broker2.moveCollection(transaction2, source, destination, newName);
} else {
broker2.copyCollection(transaction2, source, destination, newName);
}
return true;
})
)
);
}
@Override
public boolean reindexCollection(final String collectionName) throws URISyntaxException, EXistException, PermissionDeniedException {
reindexCollection(XmldbURI.xmldbUriFor(collectionName));
return true;
}
private void reindexCollection(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
broker.reindexCollection(collUri);
LOG.debug("collection " + collUri + " and sub-collections reindexed");
return null;
});
}
@Override
public boolean reindexDocument(final String docUri) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
DocumentImpl doc = null;
try {
doc = broker.getXMLResource(XmldbURI.create(docUri), LockMode.READ_LOCK);
broker.reindexXMLResource(transaction, doc, DBBroker.IndexMode.STORE);
LOG.debug("document " + docUri + " reindexed");
return null;
} finally {
if (doc != null) {
doc.getUpdateLock().release(LockMode.READ_LOCK);
}
}
});
return true;
}
@Override
public boolean backup(final String userbackup, final String password,
final String destcollection, final String collection) throws EXistException, PermissionDeniedException {
try {
final Backup backup = new Backup(
userbackup,
password,
Paths.get(destcollection + "-backup"),
XmldbURI.xmldbUriFor(XmldbURI.EMBEDDED_SERVER_URI.toString() + collection));
backup.backup(false, null);
} catch (final URISyntaxException | IOException | SAXException | XMLDBException e) {
throw new EXistException(e);
}
return true;
}
/**
* Validate if specified document is Valid.
*
* @param documentPath Path to XML document in database
* @return TRUE if document is valid, FALSE if not or errors or.....
* @throws java.net.URISyntaxException
* @throws PermissionDeniedException User is not allowed to perform action.
* @throws org.exist.EXistException
*/
@Override
public boolean isValid(final String documentPath)
throws PermissionDeniedException, URISyntaxException, EXistException {
return isValid(XmldbURI.xmldbUriFor(documentPath));
}
private boolean isValid(final XmldbURI docUri) throws EXistException, PermissionDeniedException {
try {
// Setup validator
final Validator validator = new Validator(factory.getBrokerPool());
// Get inputstream
// TODO DWES reconsider
try (final InputStream is = new EmbeddedInputStream(new XmldbURL(docUri))) {
// Perform validation
final ValidationReport report = validator.validate(is);
// Return validation result
return report.isValid();
}
} catch (final IOException e) {
throw new EXistException(e);
}
}
@Override
public List<String> getDocType(final String documentPath)
throws PermissionDeniedException, EXistException, URISyntaxException {
return getDocType(XmldbURI.xmldbUriFor(documentPath));
}
private List<String> getDocType(final XmldbURI docUri)
throws PermissionDeniedException, EXistException {
return this.<List<String>>readDocument(docUri).apply((document, broker, transaction) -> {
final List<String> list = new ArrayList<>(3);
if (document.getDoctype() != null) {
list.add(document.getDoctype().getName());
if (document.getDoctype().getPublicId() != null) {
list.add(document.getDoctype().getPublicId());
} else {
list.add("");
}
if (document.getDoctype().getSystemId() != null) {
list.add(document.getDoctype().getSystemId());
} else {
list.add("");
}
} else {
list.add("");
list.add("");
list.add("");
}
return list;
});
}
@Override
public boolean setDocType(final String documentPath, final String doctypename, final String publicid, final String systemid) throws
URISyntaxException, EXistException, PermissionDeniedException {
return setDocType(XmldbURI.xmldbUriFor(documentPath), doctypename, publicid, systemid);
}
private boolean setDocType(final XmldbURI docUri, final String doctypename, final String publicid, final String systemid) throws EXistException, PermissionDeniedException {
return this.<Boolean>writeDocument(docUri).apply((document, broker, transaction) -> {
//TODO : register the lock within the transaction ?
if (!document.getPermissions().validate(user, Permission.WRITE)) {
throw new PermissionDeniedException("User is not allowed to lock resource " + docUri);
}
DocumentType result = null;
if (!"".equals(doctypename)) {
result = new DocumentTypeImpl(doctypename,
"".equals(publicid) ? null : publicid,
"".equals(systemid) ? null : systemid);
}
document.setDocumentType(result);
broker.storeXMLResource(transaction, document);
return true;
});
}
@Override
public boolean copyResource(final String docPath, final String destinationPath, final String newName) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyResource(docPath, destinationPath, newName, false);
}
@Override
public boolean copyCollection(final String collectionPath, final String destinationPath, final String newName) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyCollection(collectionPath, destinationPath, newName, false);
}
@Override
public boolean moveResource(final String docPath, final String destinationPath, final String newName) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyResource(docPath, destinationPath, newName, true);
}
@Override
public boolean moveCollection(final String collectionPath, final String destinationPath, final String newName) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyCollection(collectionPath, destinationPath, newName, true);
}
@Override
public List<String> getDocumentChunk(final String name, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException, IOException {
final List<String> result = new ArrayList<>(2);
final File file = File.createTempFile("rpc", ".xml");
file.deleteOnExit();
try (final FileOutputStream os = new FileOutputStream(file.getAbsolutePath(), true)) {
os.write(getDocument(name, parameters));
}
result.add(file.getName());
result.add(Long.toString(file.length()));
return result;
}
@Override
public boolean copyCollection(final String name, final String namedest) throws PermissionDeniedException, EXistException {
try {
createCollection(namedest);
final Map<String, Object> parameters = new HashMap<>();
parameters.put(OutputKeys.INDENT, "no");
parameters.put(EXistOutputKeys.EXPAND_XINCLUDES, "no");
parameters.put(OutputKeys.ENCODING, DEFAULT_ENCODING);
final Map<String, Object> desc = getCollectionDesc(name);
final Object[] collections = (Object[]) desc.get("collections");
final Object[] documents = (Object[]) desc.get("documents");
//recurse the collection
for (final Object collection : collections) {
final String nome = collection.toString();
createCollection(namedest + "/" + nome);
copyCollection(name + "/" + nome, namedest + "/" + nome);
}
//Copy i file
int p, dsize = documents.length;
for (int i = 0; i < dsize; i++) {
final Map<String, Object> hash = (Map<String, Object>) documents[i];
String nome = (String) hash.get("name");
//TODO : use dedicated function in XmldbURI
if ((p = nome.lastIndexOf("/")) != Constants.STRING_NOT_FOUND) {
nome = nome.substring(p + 1);
}
final byte[] xml = getDocument(name + "/" + nome, parameters);
parse(xml, namedest + "/" + nome);
}
return true;
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
@Override
public int xupdateResource(final String resource, final byte[] xupdate) throws PermissionDeniedException, EXistException, SAXException {
return xupdateResource(resource, xupdate, DEFAULT_ENCODING.name());
}
@Override
public Map<String, Object> querySummary(final int resultId) throws EXistException, PermissionDeniedException, XPathException {
return summary(resultId);
}
@Override
public int executeQuery(final byte[] xpath, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return executeQuery(xpath, null, parameters);
}
@Override
public boolean storeBinary(final byte[] data, final String docName, final String mimeType, final boolean replace, final Date created, final Date modified) throws EXistException, PermissionDeniedException, URISyntaxException {
return storeBinary(data, docName, mimeType, replace ? 1 : 0, created, modified);
}
@Override
public boolean storeBinary(final byte[] data, final String docName, final String mimeType, final boolean replace) throws EXistException, PermissionDeniedException, URISyntaxException {
return storeBinary(data, docName, mimeType, replace ? 1 : 0, null, null);
}
@Override
public boolean parseLocalExt(final String localFile, final String docName, final boolean replace, final String mimeType, final boolean treatAsXML, final Date created, final Date modified) throws EXistException, PermissionDeniedException, SAXException, URISyntaxException {
return parseLocalExt(localFile, docName, replace ? 1 : 0, mimeType, treatAsXML ? 1 : 0, created, modified);
}
@Override
public boolean parseLocal(final String localFile, final String docName, final boolean replace, final String mimeType, final Date created, final Date modified) throws EXistException, PermissionDeniedException, SAXException, URISyntaxException {
return parseLocal(localFile, docName, replace ? 1 : 0, mimeType, created, modified);
}
@Override
public boolean parseLocalExt(final String localFile, final String docName, final boolean replace, final String mimeType, final boolean treatAsXML) throws EXistException, PermissionDeniedException, SAXException, URISyntaxException {
return parseLocalExt(localFile, docName, replace ? 1 : 0, mimeType, treatAsXML ? 1 : 0, null, null);
}
@Override
public boolean parseLocal(final String localFile, final String docName, final boolean replace, final String mimeType) throws EXistException, PermissionDeniedException, SAXException, URISyntaxException {
return parseLocal(localFile, docName, replace ? 1 : 0, mimeType, null, null);
}
@Override
public String uploadCompressed(final String file, final byte[] data, final int length) throws EXistException, PermissionDeniedException, IOException {
return upload(data, length, file, true);
}
@Override
public String uploadCompressed(final byte[] data, final int length) throws EXistException, PermissionDeniedException, IOException {
return upload(data, length, null, true);
}
@Override
public String upload(final String file, final byte[] chunk, final int length) throws EXistException, PermissionDeniedException, IOException {
return upload(chunk, length, file, false);
}
@Override
public String upload(final byte[] chunk, final int length) throws EXistException, PermissionDeniedException, IOException {
return upload(chunk, length, null, false);
}
@Override
public boolean parse(final String xml, final String docName) throws EXistException, PermissionDeniedException, URISyntaxException {
return parse(xml.getBytes(DEFAULT_ENCODING), docName, 0);
}
@Override
public boolean parse(final String xml, final String docName, final int overwrite) throws EXistException, PermissionDeniedException, URISyntaxException {
return parse(xml.getBytes(DEFAULT_ENCODING), docName, overwrite);
}
@Override
public boolean parse(final byte[] xmlData, final String docName) throws EXistException, PermissionDeniedException, URISyntaxException {
return parse(xmlData, docName, 0);
}
@Override
public Map<String, Object> querySummary(final String xquery) throws EXistException, PermissionDeniedException {
return summary(xquery);
}
@Override
public byte[] query(final byte[] xquery, final int howmany, final int start, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
final String result = query(new String(xquery, DEFAULT_ENCODING), howmany, start, parameters);
return result.getBytes(getEncoding(parameters));
}
@Override
public Map<String, Object> queryP(final byte[] xpath, final String docName, final String s_id, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException, URISyntaxException {
return queryP(new String(xpath, DEFAULT_ENCODING), docName, s_id, parameters);
}
@Override
public Map<String, Object> queryP(final byte[] xpath, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return queryP(new String(xpath, DEFAULT_ENCODING), (XmldbURI) null, null, parameters);
}
@Override
public Map<String, Object> compile(final byte[] xquery, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return compile(new String(xquery, DEFAULT_ENCODING), parameters);
}
@Override
public byte[] retrieve(final String doc, final String id) throws EXistException, PermissionDeniedException {
return retrieve(doc, id, null);
}
@Override
public String getDocumentAsString(final String name, final int prettyPrint, final String stylesheet) throws EXistException, PermissionDeniedException {
final Map<String, Object> parameters = new HashMap<>();
parameters.put(OutputKeys.INDENT, prettyPrint > 0 ? "yes" : "no");
if (stylesheet != null) {
parameters.put(EXistOutputKeys.STYLESHEET, stylesheet);
}
return getDocumentAsString(name, parameters);
}
@Override
public String getDocumentAsString(final String name, final int prettyPrint) throws EXistException, PermissionDeniedException {
return getDocumentAsString(name, prettyPrint, null);
}
@Override
public byte[] getDocument(final String name, final String encoding, final int prettyPrint, final String stylesheet) throws EXistException, PermissionDeniedException {
final Map<String, Object> parameters = new HashMap<>();
parameters.put(OutputKeys.INDENT, prettyPrint > 0 ? "yes" : "no");
if (stylesheet != null) {
parameters.put(EXistOutputKeys.STYLESHEET, stylesheet);
}
parameters.put(OutputKeys.ENCODING, encoding);
return getDocument(name, parameters);
}
@Override
public byte[] getDocument(final String name, final String encoding, final int prettyPrint) throws EXistException, PermissionDeniedException {
return getDocument(name, encoding, prettyPrint, null);
}
@Override
public boolean setTriggersEnabled(final String path, final String value) throws EXistException, PermissionDeniedException {
final boolean triggersEnabled = Boolean.parseBoolean(value);
return withDb((broker, transaction) -> {
final Collection collection = broker.getCollection(XmldbURI.create(path));
if (collection == null) {
return false;
}
collection.setTriggersEnabled(triggersEnabled);
return true;
});
}
@Override
public boolean shutdown() throws PermissionDeniedException {
factory.getBrokerPool().shutdown();
return true;
}
@Override
public boolean shutdown(final String delay) throws PermissionDeniedException {
return shutdown(Long.parseLong(delay));
}
@Override
public boolean shutdown(final long delay) throws PermissionDeniedException {
if (!user.hasDbaRole()) {
throw new PermissionDeniedException("not allowed to shut down" + "the database");
}
final SystemTaskJob shutdownJob = new SystemTaskJobImpl("rpc-api.shutdown", new ShutdownTask());
return factory.getBrokerPool().getScheduler().createPeriodicJob(0, shutdownJob, delay, new Properties(), 0);
}
public boolean enterServiceMode() throws PermissionDeniedException, EXistException {
final BrokerPool brokerPool = factory.getBrokerPool();
brokerPool.enterServiceMode(user);
return true;
}
public void exitServiceMode() throws PermissionDeniedException, EXistException {
final BrokerPool brokerPool = factory.getBrokerPool();
brokerPool.exitServiceMode(user);
}
@Override
public void runCommand(final XmldbURI collectionURI, final List<String> params) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
org.exist.plugin.command.Commands.command(collectionURI, params.toArray(new String[params.size()]));
return null;
});
}
/**
* Gets the encoding parameter or returns the default encoding
*/
private Charset getEncoding(final Map<String, Object> parameters) {
return Optional.ofNullable(parameters.get(OutputKeys.ENCODING)).map(p -> Charset.forName(p.toString())).orElse(DEFAULT_ENCODING);
}
/**
* Determines if compression is switched on in the parameters
*/
private boolean useCompression(final Map<String, Object> parameters) {
return Optional.ofNullable(parameters.get(EXistOutputKeys.COMPRESS_OUTPUT)).map(c -> c.toString().equalsIgnoreCase("yes")).orElse(false);
}
/**
* Takes a query from the pool or compiles a new one
*/
private <R> Function3E<XmlRpcCompiledXQueryFunction<R>, R, EXistException, PermissionDeniedException, XPathException> compileQuery(final DBBroker broker, final Txn transaction, final Source source, final Map<String, Object> parameters) throws EXistException, PermissionDeniedException {
return compiledOp -> {
final XQueryPool pool = broker.getBrokerPool().getXQueryPool();
CompiledXQuery compiled = null;
try {
compiled = compile(broker, source, parameters);
return compiledOp.apply(compiled);
} catch (final IOException e) {
throw new EXistException(e);
} finally {
if (compiled != null) {
compiled.getContext().runCleanupTasks();
pool.returnCompiledXQuery(source, compiled);
}
}
};
}
/**
* Higher-order function for performing read locked operations on a collection
*
* @param uri The full XmldbURI of the collection
* @return A function to receive an operation to perform on the locked database collection
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcCollectionFunction<R>, R, EXistException, PermissionDeniedException> readCollection(final XmldbURI uri) throws EXistException, PermissionDeniedException {
return readOp -> withDb((broker, transaction) -> this.<R>readCollection(broker, transaction, uri).apply(readOp));
}
/**
* Higher-order function for performing read locked operations on a collection
*
* @param broker The broker to use for the operation
* @param transaction The transaction to use for the operation
* @param uri The full XmldbURI of the collection
* @return A function to receive an operation to perform on the locked database collection
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcCollectionFunction<R>, R, EXistException, PermissionDeniedException> readCollection(final DBBroker broker, final Txn transaction, final XmldbURI uri) throws EXistException, PermissionDeniedException {
return withCollection(LockMode.READ_LOCK, broker, transaction, uri);
}
/**
* Higher-order function for performing write locked operations on a collection
*
* @param uri The full XmldbURI of the collection
* @return A function to receive an operation to perform on the locked database collection
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcCollectionFunction<R>, R, EXistException, PermissionDeniedException> writeCollection(final XmldbURI uri) throws EXistException, PermissionDeniedException {
return writeOp -> withDb((broker, transaction) -> this.<R>writeCollection(broker, transaction, uri).apply(writeOp));
}
/**
* Higher-order function for performing write locked operations on a collection
*
* @param broker The broker to use for the operation
* @param transaction The transaction to use for the operation
* @param uri The full XmldbURI of the collection
* @return A function to receive an operation to perform on the locked database collection
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcCollectionFunction<R>, R, EXistException, PermissionDeniedException> writeCollection(final DBBroker broker, final Txn transaction, final XmldbURI uri) throws EXistException, PermissionDeniedException {
return withCollection(LockMode.WRITE_LOCK, broker, transaction, uri);
}
/**
* Higher-order function for performing lockable operations on a collection
*
* @param lockMode
* @param broker The broker to use for the operation
* @param transaction The transaction to use for the operation
* @param uri The full XmldbURI of the collection
* @return A function to receive an operation to perform on the locked database collection
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcCollectionFunction<R>, R, EXistException, PermissionDeniedException> withCollection(final LockMode lockMode, final DBBroker broker, final Txn transaction, final XmldbURI uri) throws EXistException, PermissionDeniedException {
return readOp -> {
Collection collection = null;
try {
collection = broker.openCollection(uri, lockMode);
if (collection == null) {
final String msg = "collection " + uri + " not found!";
if (LOG.isDebugEnabled()) {
LOG.debug(msg);
}
throw new EXistException(msg);
}
return readOp.apply(collection, broker, transaction);
} finally {
if (collection != null) {
collection.release(lockMode);
}
}
};
}
/**
* Higher-order function for performing read locked operations on a document
*
* @param uri The full XmldbURI of the document
* @return A function to receive an operation to perform on the locked database document
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcDocumentFunction<R>, R, EXistException, PermissionDeniedException> readDocument(final XmldbURI uri) throws EXistException, PermissionDeniedException {
return readOp -> withDb((broker, transaction) -> this.<R>readDocument(broker, transaction, uri).apply(readOp));
}
/**
* Higher-order function for performing read locked operations on a document
*
* @param broker The broker to use for the operation
* @param transaction The transaction to use for the operation
* @param uri The full XmldbURI of the document
* @return A function to receive an operation to perform on the locked database document
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcDocumentFunction<R>, R, EXistException, PermissionDeniedException> readDocument(final DBBroker broker, final Txn transaction, final XmldbURI uri) throws EXistException, PermissionDeniedException {
return withDocument(LockMode.READ_LOCK, broker, transaction, uri);
}
/**
* Higher-order function for performing write locked operations on a document
*
* @param uri The full XmldbURI of the document
* @return A function to receive an operation to perform on the locked database document
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcDocumentFunction<R>, R, EXistException, PermissionDeniedException> writeDocument(final XmldbURI uri) throws EXistException, PermissionDeniedException {
return writeOp -> withDb((broker, transaction) -> this.<R>writeDocument(broker, transaction, uri).apply(writeOp));
}
/**
* Higher-order function for performing write locked operations on a document
*
* @param broker The broker to use for the operation
* @param transaction The transaction to use for the operation
* @param uri The full XmldbURI of the document
* @return A function to receive an operation to perform on the locked database document
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcDocumentFunction<R>, R, EXistException, PermissionDeniedException> writeDocument(final DBBroker broker, final Txn transaction, final XmldbURI uri) throws EXistException, PermissionDeniedException {
return withDocument(LockMode.WRITE_LOCK, broker, transaction, uri);
}
/**
* Higher-order function for performing write locked operations on a document
*
* @param broker The broker to use for the operation
* @param transaction The transaction to use for the operation
* @param collection The collection in which the document resides
* @param uri The full XmldbURI of the document
* @return A function to receive an operation to perform on the locked database document
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcDocumentFunction<R>, R, EXistException, PermissionDeniedException> writeDocument(final DBBroker broker, final Txn transaction, final Collection collection, final XmldbURI uri) throws EXistException, PermissionDeniedException {
return withDocument(LockMode.WRITE_LOCK, broker, transaction, collection, uri);
}
//TODO(AR) consider interleaving the collection and document access, i.e. we could be finished with (and release the lock on) the collection once we have access to a handle to the document
/**
* Higher-order function for performing lockable operations on a document
*
* @param lockMode
* @param broker The broker to use for the operation
* @param transaction The transaction to use for the operation
* @param uri The full XmldbURI of the document
* @return A function to receive an operation to perform on the locked database document
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcDocumentFunction<R>, R, EXistException, PermissionDeniedException> withDocument(final LockMode lockMode, final DBBroker broker, final Txn transaction, final XmldbURI uri) throws EXistException, PermissionDeniedException {
return withOp -> this.<R>readCollection(broker, transaction, uri.removeLastSegment()).apply((collection, broker1, transaction1) -> this.<R>withDocument(lockMode, broker1, transaction1, collection, uri).apply(withOp));
}
/**
* Higher-order function for performing lockable operations on a document
*
* @param lockMode
* @param broker The broker to use for the operation
* @param transaction The transaction to use for the operation
* @param collection The collection in which the document resides
* @param uri The full XmldbURI of the document
* @return A function to receive an operation to perform on the locked database document
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> Function2E<XmlRpcDocumentFunction<R>, R, EXistException, PermissionDeniedException> withDocument(final LockMode lockMode, final DBBroker broker, final Txn transaction, final Collection collection, final XmldbURI uri) throws EXistException, PermissionDeniedException {
return readOp -> {
DocumentImpl document = null;
try {
document = collection.getDocumentWithLock(broker, uri.lastSegment(), lockMode);
if (document == null) {
final String msg = "document " + uri + " not found!";
if (LOG.isDebugEnabled()) {
LOG.debug(msg);
}
throw new EXistException(msg);
}
return readOp.apply(document, broker, transaction);
} catch (final LockException e) {
throw new EXistException(e);
} finally {
if (document != null) {
collection.releaseDocument(document, lockMode);
}
}
};
}
/**
* Higher-order-function for performing an XMLDB operation on
* the database as the SYSTEM_USER.
*
* @param dbOperation The operation to perform on the database
* @param <R> The return type of the operation
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> R withDbAsSystem(final XmlRpcFunction<R> dbOperation) throws EXistException, PermissionDeniedException {
return withDb(factory.getBrokerPool().getSecurityManager().getSystemSubject(), dbOperation);
}
/**
* Higher-order-function for performing an XMLDB operation on
* the database.
* <p>
* Performs the operation as the current user of the RpcConnection
*
* @param dbOperation The operation to perform on the database
* @param <R> The return type of the operation
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> R withDb(final XmlRpcFunction<R> dbOperation) throws EXistException, PermissionDeniedException {
return withDb(user, dbOperation);
}
/**
* Higher-order-function for performing an XMLDB operation on
* the database
*
* @param user The user to execute the operation as
* @param dbOperation The operation to perform on the database
* @param <R> The return type of the operation
* @throws org.exist.EXistException
* @throws org.exist.security.PermissionDeniedException
*/
private <R> R withDb(final Subject user, final XmlRpcFunction<R> dbOperation) throws EXistException, PermissionDeniedException {
try (final DBBroker broker = factory.getBrokerPool().get(Optional.of(user));
final Txn transaction = factory.getBrokerPool().getTransactionManager().beginTransaction()) {
final R result = dbOperation.apply(broker, transaction);
transaction.commit();
return result;
}
}
}