Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.exist.xmlrpc.RpcConnection Maven / Gradle / Ivy
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* http://www.exist-db.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xmlrpc;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.backup.Restore;
import org.exist.backup.restore.listener.RestoreListener;
import org.exist.dom.QName;
import org.exist.dom.persistent.*;
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.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.DBBroker.PreserveType;
import org.exist.storage.lock.Lock.LockMode;
import org.exist.storage.lock.LockManager;
import org.exist.storage.lock.LockedDocumentMap;
import org.exist.storage.lock.ManagedCollectionLock;
import org.exist.storage.lock.ManagedDocumentLock;
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.crypto.digest.DigestType;
import org.exist.util.crypto.digest.MessageDigest;
import org.exist.util.io.TemporaryFileManager;
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.ConsumerE;
import com.evolvedbinary.j8fu.function.Function2E;
import com.evolvedbinary.j8fu.function.Function3E;
import com.evolvedbinary.j8fu.function.SupplierE;
import com.evolvedbinary.j8fu.tuple.Tuple2;
import java.io.*;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.DeflaterOutputStream;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import org.xmldb.api.base.*;
import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
import static org.exist.xmldb.EXistXPathQueryService.BEGIN_PROTECTED_MAX_LOCKING_RETRIES;
import static java.nio.file.StandardOpenOption.*;
/**
* This class implements the actual methods defined by
* {@link org.exist.xmlrpc.RpcAPI}.
*
* @author Wolfgang Meier
* Modified by {Marco.Tampucci, Massimo.Martinelli} @isti.cnr.it
* @author Adam Retter
*/
public class RpcConnection implements RpcAPI {
private final static Logger LOG = LogManager.getLogger(RpcConnection.class);
public final static int MAX_DOWNLOAD_CHUNK_SIZE = 1024 * 1024; // 1 MB
private final static Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
private final XmldbRequestProcessorFactory factory;
private final Subject user;
private final Random random = new Random();
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, Optional.ofNullable(created).map(c -> new Tuple2<>(null, c.getTime())));
try(final ManagedCollectionLock collectionLock = broker.getBrokerPool().getLockManager().acquireCollectionWriteLock(collUri)) {
broker.saveCollection(transaction, current);
}
return null;
});
LOG.info("collection {} has been created", collUri);
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.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.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 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 {}ms.", System.currentTimeMillis() - start);
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 parameters) throws EXistException, PermissionDeniedException {
final String protectColl = (String) parameters.get(RpcAPI.PROTECTED_MODE);
if (protectColl == null) {
return null;
}
int retries = BEGIN_PROTECTED_MAX_LOCKING_RETRIES == - 1 ? -1 : BEGIN_PROTECTED_MAX_LOCKING_RETRIES - 2;
do {
MutableDocumentSet docs = null;
final LockedDocumentMap lockedDocuments = new LockedDocumentMap();
final LockMode documentLockMode = LockMode.WRITE_LOCK;
final LockMode collectionLockMode = broker.getBrokerPool().getLockManager().relativeCollectionLockMode(LockMode.READ_LOCK, documentLockMode);
try (final Collection coll = broker.openCollection(XmldbURI.createInternal(protectColl), collectionLockMode)) {
docs = new DefaultDocumentSet();
coll.allDocs(broker, docs, true, lockedDocuments, documentLockMode);
return lockedDocuments;
} catch (final LockException e) {
LOG.warn("Deadlock detected. Starting over again. Docs: {}; locked: {}. Cause: {}", docs.getDocumentCount(), lockedDocuments.size(), e.getMessage());
lockedDocuments.unlock();
}
retries--;
} while (retries >= -1);
throw new EXistException("Unable to beginProtected after " + BEGIN_PROTECTED_MAX_LOCKING_RETRIES + " retries");
}
/**
* @deprecated Use compileQuery lambda instead!
* @param broker the broker to use
* @param source the xquery to compile
* @param parameters context for the compilation of the query
* @return the compiled query
* @throws XPathException If the query contains errors
* @throws IOException If an error occurs reading of writing to the disk
* @throws PermissionDeniedException If the current user is not allowed to perform the action
*/
@Deprecated
private CompiledXQuery compile(final DBBroker broker, final Source source, final Map 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();
context.prepareForReuse();
}
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 namespaces = (Map) parameters.get(RpcAPI.NAMESPACES);
if (namespaces != null && namespaces.size() > 0) {
context.declareNamespaces(namespaces);
}
// declare static variables
final Map variableDecls = (Map) parameters.get(RpcAPI.VARIABLES);
if (variableDecls != null) {
for (final Map.Entry 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(context, source);
}
return compiled;
}
@Override
public String printDiagnostics(final String query, final Map parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(query);
return withDb((broker, transaction) -> {
try {
return this.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 the context
* @param parameters serialization options
* @throws XPathException If the query contains errors
*/
protected void checkPragmas(final XQueryContext context, final Map parameters) throws XPathException {
final Option pragma = context.getOption(Option.SERIALIZE_QNAME);
checkPragmas(pragma, parameters);
}
protected void checkPragmas(final Option pragma, final Map 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 + "'");
}
if(LOG.isDebugEnabled()) {
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 parameters) throws EXistException, PermissionDeniedException {
final Charset charset = Optional.ofNullable(encoding).map(Charset::forName).orElse(DEFAULT_ENCODING);
final String xpathString = new String(xpath, charset);
if(LOG.isDebugEnabled()) {
LOG.debug("query: {}", xpathString);
}
return executeQuery(xpathString, parameters);
}
@Override
public int executeQuery(final String xpath, final Map parameters) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final Source source = new StringSource(xpath);
final long startTime = System.currentTimeMillis();
try {
final QueryResult result = this.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 ("" +
'<' + type + '>' + message + "" + type + "> ";
}
@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) -> {
try(final Collection collection = broker.openCollection(uri, LockMode.READ_LOCK)) {
return collection != null;
}
});
}
@Override
public Map 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 getCollectionDesc(final XmldbURI rootUri) throws EXistException, PermissionDeniedException {
return this.>readCollection(rootUri).apply((collection, broker, transaction) -> {
final Map desc = new HashMap<>();
final List> docs = new ArrayList<>();
final List collections = new ArrayList<>();
if (collection.getPermissionsNoLock().validate(user, Permission.READ)) {
for (final Iterator i = collection.iterator(broker); i.hasNext(); ) {
final DocumentImpl doc = i.next();
try(final ManagedDocumentLock documentLock = broker.getBrokerPool().getLockManager().acquireDocumentReadLock(doc.getURI())) {
final Permission perms = doc.getPermissions();
final Map 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 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.getCreated()));
desc.put("owner", perms.getOwner().getName());
desc.put("group", perms.getGroup().getName());
desc.put("permissions", perms.getMode());
return desc;
});
}
@Override
public Map describeResource(final String resourceName)
throws EXistException, PermissionDeniedException {
try {
return describeResource(XmldbURI.xmldbUriFor(resourceName));
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
}
private Map describeResource(final XmldbURI resourceUri)
throws EXistException, PermissionDeniedException {
try {
return this.>readDocument(resourceUri).apply((document, broker, transaction) -> {
final Map 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.getMimeType());
hash.put("created", new Date(document.getCreated()));
hash.put("modified", new Date(document.getLastModified()));
if (document.getResourceType() == DocumentImpl.BINARY_FILE) {
hash.put("blob-id", ((BinaryDocument)document).getBlobId().getId());
final MessageDigest messageDigest = broker.getBinaryResourceContentDigest(transaction, (BinaryDocument)document, DigestType.BLAKE_256);
hash.put("digest-algorithm", messageDigest.getDigestType().getCommonNames()[0]);
hash.put("digest", messageDigest.getValue());
}
return hash;
});
} catch (final EXistException e) {
if(LOG.isDebugEnabled()) {
LOG.debug(e);
}
return new HashMap<>();
}
}
@Override
public Map 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 describeCollection
*
* Returns details of a collection - collections (list of sub-collections) -
* name - created - owner - group - permissions - acl
*
* 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 XmldbURI
value
* @return a Map
value
* @throws EXistException if an internal error occurs
* @throws PermissionDeniedException If the current user is not allowed to perform this action
*/
private Map describeCollection(final XmldbURI collUri)
throws EXistException, PermissionDeniedException {
return this.>readCollection(collUri).apply((collection, broker, transaction) -> {
final Map desc = new HashMap<>();
final List collections = new ArrayList<>();
if (collection.getPermissionsNoLock().validate(user, Permission.READ)) {
for (final Iterator 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.getCreated()));
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 Map getContentDigest(final String path, final String digestAlgorithm) throws EXistException, PermissionDeniedException {
try {
final DigestType digestType = DigestType.forCommonName(digestAlgorithm);
final MessageDigest messageDigest = this.readDocument(XmldbURI.xmldbUriFor(path)).apply((document, broker, transaction) -> {
if (document instanceof BinaryDocument) {
return broker.getBinaryResourceContentDigest(transaction, (BinaryDocument) document, digestType);
} else {
throw new EXistException("Only supported for binary documents");
}
});
final Map result = new HashMap<>();
result.put("digest-algorithm", messageDigest.getDigestType().getCommonNames()[0]);
result.put("digest", messageDigest.getValue());
return result;
} catch (final IllegalArgumentException | URISyntaxException e) {
throw new EXistException(e);
}
}
@Override
public byte[] getDocument(final String name, final Map parameters) throws EXistException,
PermissionDeniedException {
final Charset encoding = getEncoding(parameters);
final boolean compression = useCompression(parameters);
final String xml = getDocumentAsString(name, parameters);
if (compression) {
if(LOG.isDebugEnabled()) {
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 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 parameters) throws EXistException, PermissionDeniedException {
return this.readDocument(docUri).apply((document, broker, transaction) -> {
try (final StringWriter writer = new StringWriter()) {
serialize(broker, toProperties(parameters), saxSerializer -> saxSerializer.toSAX(document), writer);
return writer.toString();
}
});
}
private void serialize(final DBBroker broker, final Properties properties, final ConsumerE toSaxFunction, final Writer writer) throws SAXException, IOException {
final Serializer serializer = broker.borrowSerializer();
SAXSerializer saxSerializer = null;
try {
serializer.setUser(user);
serializer.setProperties(properties);
saxSerializer = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
saxSerializer.setOutput(writer, properties);
serializer.setSAXHandlers(saxSerializer, saxSerializer);
toSaxFunction.accept(serializer);
writer.flush();
} finally {
if (saxSerializer != null) {
SerializerPool.getInstance().returnObject(saxSerializer);
}
broker.returnSerializer(serializer);
}
}
@Override
public Map getDocumentData(final String docName, final Map parameters) throws EXistException, PermissionDeniedException {
final XmldbURI docUri;
try {
docUri = XmldbURI.xmldbUriFor(docName);
} catch (final URISyntaxException e) {
throw new EXistException(e);
}
return this.>readDocument(docUri).apply((document, broker, transaction) -> {
final Charset encoding = getEncoding(parameters);
// A tweak for very large resources, VirtualTempFile
final Map result = new HashMap<>();
final TemporaryFileManager temporaryFileManager = TemporaryFileManager.getInstance();
final Path tempFile = temporaryFileManager.getTemporaryFile();
if (document.getResourceType() == DocumentImpl.XML_FILE) {
try (final Writer writer = Files.newBufferedWriter(tempFile, encoding)) {
serialize(broker, toProperties(parameters), saxSerializer -> saxSerializer.toSAX(document), writer);
}
} else {
try (final OutputStream os = new BufferedOutputStream(Files.newOutputStream(tempFile))) {
broker.readBinaryResource(transaction, (BinaryDocument) document, os);
}
}
final byte[] firstChunk = getChunk(tempFile, 0);
result.put("data", firstChunk);
int offset = 0;
if (Files.size(tempFile) > MAX_DOWNLOAD_CHUNK_SIZE) {
offset = firstChunk.length;
final int handle = factory.resultSets.add(new SerializedResult(tempFile));
result.put("handle", Integer.toString(handle));
result.put("supports-long-offset", Boolean.TRUE);
} else {
temporaryFileManager.returnTemporaryFile(tempFile);
}
result.put("offset", offset);
return result;
});
}
private byte[] getChunk(final Path file, final int offset) throws IOException {
final long available = Files.size(file);
final int len = (int)Math.min(Math.min(available - offset, MAX_DOWNLOAD_CHUNK_SIZE), Integer.MAX_VALUE);
final byte[] chunk = new byte[len];
try (final InputStream is = Files.newInputStream(file)) {
is.skip(offset);
final int read = is.read(chunk);
if(read != len) {
throw new IOException("Unable to read full chunk at offset: " + offset + ", from file: " + file.toAbsolutePath().toString());
}
}
return chunk;
}
@Override
public Map 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 Path tempFile = sr.result;
if (offset <= 0 || offset > Files.size(tempFile)) {
factory.resultSets.remove(resultId);
throw new EXistException("No more data available");
}
final byte[] chunk = getChunk(tempFile, offset);
final long nextChunk = offset + chunk.length;
final Map result = new HashMap<>();
result.put("data", chunk);
result.put("handle", handle);
if (nextChunk > (long) Integer.MAX_VALUE || nextChunk >= Files.size(tempFile)) {
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 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 Path tempFile = sr.result;
final long longOffset = Long.parseLong(offset);
if (longOffset < 0 || longOffset > Files.size(tempFile)) {
factory.resultSets.remove(resultId);
throw new EXistException("No more data available");
}
final byte[] chunk = getChunk(tempFile, (int)longOffset);
final long nextChunk = longOffset + chunk.length;
final Map result = new HashMap<>();
result.put("data", chunk);
result.put("handle", handle);
if (nextChunk >= Files.size(tempFile)) {
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.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(transaction, (BinaryDocument) document)) {
final long resourceSize = document.getContentLength();
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.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 Reader reader = new StringReader(xupdate)) {
final XUpdateProcessor processor = new XUpdateProcessor(broker, docs);
final Modification modifications[] = processor.parse(new InputSource(reader));
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 MutableDocumentSet docs = this.readDocument(docUri).apply((document, broker1, transaction1) -> {
//TODO : register a lock (which one ?) within the transaction ?
final MutableDocumentSet documentSet = new DefaultDocumentSet();
documentSet.add(document);
return documentSet;
});
try(final Reader reader = new StringReader(xupdate)) {
final XUpdateProcessor processor = new XUpdateProcessor(broker, docs);
final Modification modifications[] = processor.parse(new InputSource(reader));
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 getDocumentListing() throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final DocumentSet docs = broker.getAllXMLResources(new DefaultDocumentSet());
final XmldbURI names[] = docs.getNames();
final List list = new ArrayList<>();
for (final XmldbURI name : names) {
list.add(name.toString());
}
return list;
});
}
@Override
public List getCollectionListing(final String collName) throws EXistException, PermissionDeniedException, URISyntaxException {
return getCollectionListing(XmldbURI.xmldbUriFor(collName));
}
private List getCollectionListing(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
try {
return this.>readCollection(collUri).apply((collection, broker, transaction) -> {
final List list = new ArrayList<>();
for (final Iterator i = collection.collectionIterator(broker); i.hasNext(); ) {
list.add(i.next().toString());
}
return list;
});
} catch (final EXistException e) {
return Collections.EMPTY_LIST;
}
}
@Override
public List getDocumentListing(final String collName)
throws EXistException, PermissionDeniedException, URISyntaxException {
return getDocumentListing(XmldbURI.xmldbUriFor(collName));
}
private List getDocumentListing(final XmldbURI collUri)
throws EXistException, PermissionDeniedException {
try {
return this.>readCollection(collUri).apply((collection, broker, transaction) -> {
final List list = new ArrayList<>();
for (final Iterator i = collection.iterator(broker); i.hasNext(); ) {
list.add(i.next().getFileURI().toString());
}
return list;
});
} catch (final EXistException e) {
if(LOG.isDebugEnabled()) {
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.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
*
* 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
*
* @param collUri URI of the collection to create the resource in
* @return the unique resource name
* @throws EXistException if an internal error occurs
* @throws PermissionDeniedException If the current user is not allowed to perform this action
*/
private String createResourceId(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
return this.readCollection(collUri).apply((collection, broker, transaction) -> {
XmldbURI id;
boolean ok;
do {
ok = true;
id = XmldbURI.create(Integer.toHexString(random.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 getPermissions(final String name)
throws EXistException, PermissionDeniedException, URISyntaxException {
return getPermissions(XmldbURI.xmldbUriFor(name));
}
private Map getPermissions(final XmldbURI uri) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
try(final Collection collection = broker.openCollection(uri, LockMode.READ_LOCK)) {
if (collection == null) {
try(final LockedDocument lockedDoc = broker.getXMLResource(uri, LockMode.READ_LOCK)) {
if (lockedDoc == null) {
throw new EXistException("document or collection " + uri + " not found");
}
final Permission permission = lockedDoc.getDocument().getPermissions();
return toMap(permission);
}
} else {
final Permission permission = collection.getPermissionsNoLock();
return toMap(permission);
}
}
});
}
@Override
public Map getSubCollectionPermissions(final String parentPath, final String name) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(parentPath);
return this.>readCollection(uri).apply((collection, broker, transaction) -> toMap(collection.getChildCollectionEntry(broker, name).getPermissions()));
}
@Override
public Map getSubResourcePermissions(final String parentPath, final String name) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(parentPath);
return this.>readCollection(uri).apply((collection, broker, transaction) -> toMap(collection.getResourceEntry(broker, name).getPermissions()));
}
@Override
public long getSubCollectionCreationTime(final String parentPath, final String name) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(parentPath);
return this.readCollection(uri).apply((collection, broker, transaction) -> collection.getChildCollectionEntry(broker, name).getCreated());
}
private Map toMap(final Permission permission) {
final Map result = new HashMap<>();
result.put("owner", permission.getOwner().getName());
result.put("group", permission.getGroup().getName());
result.put("permissions", permission.getMode());
if (permission instanceof ACLPermission) {
result.put("acl", getACEs(permission));
}
return result;
}
private List getACEs(final Permission perm) {
final List 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 listDocumentPermissions(final String name)
throws EXistException, PermissionDeniedException, URISyntaxException {
return listDocumentPermissions(XmldbURI.xmldbUriFor(name));
}
private Map listDocumentPermissions(final XmldbURI collUri) throws EXistException, PermissionDeniedException {
return this.>readCollection(collUri).apply((collection, broker, transaction) -> {
final Map result = new HashMap<>(collection.getDocumentCount(broker));
for (final Iterator i = collection.iterator(broker); i.hasNext(); ) {
final DocumentImpl doc = i.next();
try(final ManagedDocumentLock documentLock = broker.getBrokerPool().getLockManager().acquireDocumentReadLock(doc.getURI())) {
final Permission perm = doc.getPermissions();
result.put(doc.getFileURI().toString(), toList(perm));
}
}
return result;
});
}
@Override
public Map listCollectionPermissions(final String name)
throws EXistException, PermissionDeniedException, URISyntaxException {
return listCollectionPermissions(XmldbURI.xmldbUriFor(name));
}
private Map listCollectionPermissions(final XmldbURI collUri)
throws EXistException, PermissionDeniedException {
return this.>readCollection(collUri).apply((collection, broker, transaction) -> {
final Map result = new HashMap<>(collection.getChildCollectionCount(broker));
for (final Iterator 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(); // NOTE: we already have a READ lock on childColl implicitly
result.put(child, toList(perm));
}
return result;
});
}
private List toList(final Permission permission) {
final List result = new ArrayList<>(4);
result.add(permission.getOwner().getName());
result.add(permission.getGroup().getName());
result.add(permission.getMode());
if (permission instanceof ACLPermission) {
result.add(getACEs(permission));
}
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.readCollection(collUri).apply((collection, broker, transaction) -> new Date(collection.getCreated()));
}
@Override
public List getTimestamps(final String documentPath)
throws PermissionDeniedException, EXistException, URISyntaxException {
return getTimestamps(XmldbURI.xmldbUriFor(documentPath));
}
private List getTimestamps(final XmldbURI docUri) throws PermissionDeniedException, EXistException {
return this.>readDocument(docUri).apply((document, broker, transaction) -> {
final List list = new ArrayList<>(2);
list.add(new Date(document.getCreated()));
list.add(new Date(document.getLastModified()));
return list;
});
}
@Override
public boolean setLastModified(final String documentPath, final long lastModified) throws EXistException, PermissionDeniedException {
return this.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.setLastModified(lastModified);
broker.storeXMLResource(transaction, document);
return true;
});
}
@Override
public Map getAccount(final String name) throws EXistException, PermissionDeniedException {
return withDb((broker, transaction) -> {
final Account user = factory.getBrokerPool().getSecurityManager().getAccount(name);
if (user == null) {
throw new EXistException("account '" + name + "' does not exist");
}
return toMap(user);
});
}
@Override
public List> getAccounts() throws EXistException, PermissionDeniedException {
final java.util.Collection users = factory.getBrokerPool().getSecurityManager().getUsers();
final List> result = new ArrayList<>();
for (final Account user : users) {
result.add(toMap(user));
}
return result;
}
private Map toMap(final Account account) {
final Map result = new HashMap<>();
result.put("uid", account.getId());
result.put("name", account.getName());
result.put("groups", Arrays.asList(account.getGroups()));
final Group dg = account.getDefaultGroup();
if (dg != null) {
result.put("default-group-id", dg.getId());
result.put("default-group-realmId", dg.getRealmId());
result.put("default-group-name", dg.getName());
}
result.put("enabled", Boolean.toString(account.isEnabled()));
result.put("umask", account.getUserMask());
final Map metadata = new HashMap<>();
for (final SchemaType key : account.getMetadataKeys()) {
metadata.put(key.getNamespace(), account.getMetadataValue(key));
}
result.put("metadata", metadata);
return result;
}
@Override
public List getGroups() throws EXistException, PermissionDeniedException {
final java.util.Collection groups = factory.getBrokerPool().getSecurityManager().getGroups();
final List v = new ArrayList<>(groups.size());
for (final Group group : groups) {
v.add(group.getName());
}
return v;
}
@Override
public Map 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 map = new HashMap<>();
map.put("id", group.getId());
map.put("realmId", group.getRealmId());
map.put("name", name);
final List groupManagers = group.getManagers();
final List managers = new ArrayList<>(groupManagers.size());
for (final Account groupManager : groupManagers) {
managers.add(groupManager.getName());
}
map.put("managers", managers);
final Map 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, @Nullable final Date created, @Nullable final Date modified) throws EXistException, PermissionDeniedException {
return this.writeCollection(docUri.removeLastSegment()).apply((collection, broker, transaction) -> {
try(final ManagedDocumentLock lockedDocument = broker.getBrokerPool().getLockManager().acquireDocumentWriteLock(docUri)) {
if (overwrite == 0) {
final DocumentImpl old = collection.getDocument(broker, docUri.lastSegment()); // NOTE: we have the document write lock above
if (old != null) {
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
throw new PermissionDeniedException("Document exists and overwrite is not allowed");
}
}
final InputSource source = new StringInputSource(xml);
final long startTime = System.currentTimeMillis();
final MimeType mime = MimeTable.getInstance().getContentTypeFor(docUri.lastSegment());
broker.storeDocument(transaction, docUri.lastSegment(), source, mime, created, modified, null, null, null, collection);
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
if(LOG.isDebugEnabled()) {
LOG.debug("parsing {} took {}ms.", docUri, System.currentTimeMillis() - startTime);
}
return true;
}
});
}
/**
* Parse a file previously uploaded with upload.
*
* The temporary file will be removed.
*
* @param localFile the name of the temporary, uploaded file
* @param documentPath target of the parsed file
* @param overwrite true, if an existing file should be overwritten
* @param mimeType the mimeType of the uploaded file
* @return true, if the file is valid
* @throws EXistException if an internal error occurs
* @throws PermissionDeniedException If the current user is not allowed to perform this action
* @throws URISyntaxException if documentPath is not valid
*/
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.
*
* The temporary file will be removed.
*
* @param localFile the name of the temporary, uploaded file
* @param documentPath target of the parsed file
* @param overwrite true, if an existing file should be overwritten
* @param mimeType the mimeType of the uploaded file
* @param isXML true, if the file is XML
* @return true, if the file is valid
* @throws EXistException if an internal error occurs
* @throws PermissionDeniedException If the current user is not allowed to perform this action
* @throws URISyntaxException if documentPath is not valid
*/
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.writeCollection(docUri.removeLastSegment()).apply((collection, broker, transaction) -> {
try(final ManagedDocumentLock lockedDocument = broker.getBrokerPool().getLockManager().acquireDocumentWriteLock(docUri)) {
if (overwrite == 0) {
final DocumentImpl old = collection.getDocument(broker, docUri.lastSegment()); // NOTE: we have the document write lock above
if (old != null) {
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
throw new PermissionDeniedException("Old document exists and overwrite is not allowed");
}
}
// get the source for parsing
SupplierE sourceSupplier;
try {
final int handle = Integer.parseInt(localFile);
final SerializedResult sr = factory.resultSets.getSerializedResult(handle);
if (sr == null) {
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
throw new EXistException("Invalid handle specified");
}
sourceSupplier = () -> {
final FileInputSource source = new FileInputSource(sr.result);
sr.result = null; // de-reference the temp file in the SerializeResult, so it is not re-claimed before we need it
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)) {
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
throw new EXistException("unable to read file " + path.toAbsolutePath().toString());
}
sourceSupplier = () -> new FileInputSource(path);
}
// parse the source
try (final FileInputSource source = sourceSupplier.get()) {
final MimeType mime = Optional.ofNullable(MimeTable.getInstance().getContentType(mimeType)).orElse(MimeType.BINARY_TYPE);
broker.storeDocument(transaction, docUri.lastSegment(), source, mime, created, modified, null, null, null, collection);
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
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.writeCollection(docUri.removeLastSegment()).apply((collection, broker, transaction) -> {
// keep a write lock in the transaction
transaction.acquireCollectionLock(() -> broker.getBrokerPool().getLockManager().acquireCollectionWriteLock(collection.getURI()));
try(final ManagedDocumentLock lockedDocument = broker.getBrokerPool().getLockManager().acquireDocumentWriteLock(docUri)) {
if (overwrite == 0) {
final DocumentImpl old = collection.getDocument(broker, docUri.lastSegment()); // NOTE: we have the document write lock above
if (old != null) {
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
throw new PermissionDeniedException("Old document exists and overwrite is not allowed");
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("Storing binary resource to collection {}", collection.getURI());
}
broker.storeDocument(transaction, docUri.lastSegment(), new StringInputSource(data), MimeTable.getInstance().getContentType(mimeType), created, modified, null, null, null, collection);
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
return true;
}
});
}
public String upload(final byte[] chunk, final int length, @Nullable String fileName, final boolean compressed)
throws EXistException, IOException {
final OpenOption[] openOptions;
final Path tempFile;
if (fileName == null || fileName.length() == 0) {
// no fileName, so new file
openOptions = new OpenOption[] { CREATE, TRUNCATE_EXISTING, WRITE };
// create temporary file
tempFile = TemporaryFileManager.getInstance().getTemporaryFile();
final int handle = factory.resultSets.add(new SerializedResult(tempFile));
fileName = Integer.toString(handle);
} else {
if(LOG.isDebugEnabled()) {
LOG.debug("Appending to file {}", fileName);
}
// fileName was specified so this is an append
openOptions = new OpenOption[] { CREATE, APPEND, WRITE };
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();
tempFile = sr.result;
} catch (final NumberFormatException nfe) {
throw new EXistException("Syntactically invalid handle specified");
}
}
try (final OutputStream os = new BufferedOutputStream(Files.newOutputStream(tempFile, openOptions))) {
if (compressed) {
final int uncompressedLen = Compressor.uncompress(chunk, os);
if (uncompressedLen != length) {
throw new IOException("Expected " + length + " bytes of uncompressed data, but actually " + uncompressedLen);
}
} else {
os.write(chunk, 0, length);
}
}
return fileName;
}
protected String printAll(final DBBroker broker, final Sequence resultSet, int howmany,
int start, final Map properties, final long queryTime) throws EXistException, SAXException, XPathException, IOException {
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("\n");
}
buf.append(" ");
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("\n");
final Properties serializationProps = 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;
serialize(broker, serializationProps, saxSerializer -> saxSerializer.toSAX(node), writer);
} else {
writer.write("");
writer.write(item.getStringValue());
writer.write(" ");
}
}
writer.write("\n ");
return writer.toString();
}
public Map compile(final String query, final Map parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(query);
return withDb((broker, transaction) -> {
final Map 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 parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(xpath);
return withDb((broker, transaction) -> {
final long startTime = System.currentTimeMillis();
try {
final QueryResult qr = this.compileQuery(broker, transaction, source, parameters).apply(compiled -> doQuery(broker, compiled, null, parameters));
if (qr == null) {
return "\n"
+ " ";
}
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);
}
});
}
/**
* @deprecated Use {@link #queryPT(String, XmldbURI, String, Map)} instead.
* @param xpath the query to execute
* @param documentPath the collection to query
* @param s_id an id
* @param parameters map of options
* @return the result of the query
* @throws URISyntaxException if documentPath is invalid
* @throws EXistException if an internal error occurs
* @throws PermissionDeniedException If the current user is not allowed to perform this action
*/
public Map queryP(final String xpath, final String documentPath,
final String s_id, final Map parameters) throws URISyntaxException, EXistException, PermissionDeniedException {
return queryP(xpath,
(documentPath == null) ? null : XmldbURI.xmldbUriFor(documentPath),
s_id, parameters);
}
/**
* @deprecated Use {@link #queryPT(String, XmldbURI, String, Map)} instead.
* @param xpath the query to execute
* @param docUri the document to query
* @param s_id an id
* @param parameters map of options
* @return the result of the query
* @throws EXistException if an internal error occurs
* @throws PermissionDeniedException If the current user is not allowed to perform this action
*/
private Map queryP(final String xpath, final XmldbURI docUri,
final String s_id, final Map parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(xpath);
final Optional sortBy = Optional.ofNullable(parameters.get(RpcAPI.SORT_EXPR)).map(Object::toString);
return withDb((broker, transaction) -> {
final long startTime = System.currentTimeMillis();
final NodeSet nodes;
if (docUri != null && s_id != null) {
nodes = this.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 Map rpcResponse = this.>compileQuery(broker, transaction, source, parameters)
.apply(compiledQuery -> queryResultToRpcResponse(startTime, doQuery(broker, compiledQuery, nodes, parameters), sortBy));
return rpcResponse;
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
private Map queryResultToRpcResponse(final long startTime, final QueryResult queryResult, final Optional sortBy) throws XPathException {
final Map ret = new HashMap<>();
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 result = new ArrayList<>();
if (resultSeq != null) {
final SequenceIterator i = resultSeq.iterate();
if (i != null) {
while (i.hasNext()) {
final Item next = i.nextItem();
if (Type.subTypeOf(next.getType(), Type.NODE)) {
final List 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 {
if(LOG.isDebugEnabled()) {
LOG.debug("sequence iterator is null. Should not");
}
}
} else {
if(LOG.isDebugEnabled()) {
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;
}
@Override
public Map queryPT(final byte[] xquery, final Map parameters) throws EXistException, PermissionDeniedException {
return queryPT(new String(xquery, DEFAULT_ENCODING), null, null, parameters);
}
@Override
public Map queryPT(final byte[] xquery, @Nullable final String docName, @Nullable final String s_id, final Map parameters) throws EXistException, PermissionDeniedException, URISyntaxException {
return queryPT(new String(xquery, DEFAULT_ENCODING), docName == null ? null : XmldbURI.create(docName), s_id, parameters);
}
private Map queryPT(final String xquery, final XmldbURI docUri,
final String s_id, final Map parameters) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(xquery);
final Optional sortBy = Optional.ofNullable(parameters.get(RpcAPI.SORT_EXPR)).map(Object::toString);
return withDb((broker, transaction) -> {
final long startTime = System.currentTimeMillis();
final NodeSet nodes;
if (docUri != null && s_id != null) {
nodes = this.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 Map rpcResponse = this.>compileQuery(broker, transaction, source, parameters)
.apply(compiledQuery -> queryResultToTypedRpcResponse(startTime, doQuery(broker, compiledQuery, nodes, parameters), sortBy));
return rpcResponse;
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
private Map queryResultToTypedRpcResponse(final long startTime, final QueryResult queryResult, final Optional sortBy) throws XPathException {
final Map ret = new HashMap<>();
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> result = new ArrayList<>();
if (resultSeq != null) {
final SequenceIterator i = resultSeq.iterate();
if (i != null) {
while (i.hasNext()) {
final Item next = i.nextItem();
final Map entry;
if (Type.subTypeOf(next.getType(), Type.NODE)) {
entry = nodeMap(next);
} else {
entry = atomicMap(next);
}
if(entry != null) {
result.add(entry);
}
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("sequence iterator is null. Should not");
}
}
} else {
if (LOG.isDebugEnabled()) {
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;
}
private @Nullable Map nodeMap(final Item item) {
final Map result;
if(item instanceof NodeValue &&
((NodeValue)item).getImplementationType() == NodeValue.PERSISTENT_NODE) {
final NodeProxy p = (NodeProxy) item;
result = new HashMap<>();
result.put("type", Type.getTypeName(p.getType()));
result.put("docUri", p.getOwnerDocument().getURI().toString());
result.put("nodeId", p.getNodeId().toString());
} else if(item instanceof org.exist.dom.memtree.NodeImpl) {
final NodeImpl ni = (NodeImpl)item;
result = new HashMap<>();
result.put("type", Type.getTypeName(ni.getType()));
result.put("docUri", "temp_xquery/" + item.hashCode());
result.put("nodeId", String.valueOf(ni.getNodeNumber()));
} else {
LOG.error("Omitting from results, unsure how to process: {}", item.getClass());
result = null;
}
return result;
}
private Map atomicMap(final Item item) throws XPathException {
final Map result = new HashMap<>();
final int type = item.getType();
result.put("type", Type.getTypeName(type));
result.put("value", item.getStringValue());
return result;
}
@Deprecated
@Override
public Map execute(final String pathToQuery, final Map parameters) throws EXistException, PermissionDeniedException {
final long startTime = System.currentTimeMillis();
final Optional sortBy = Optional.ofNullable(parameters.get(RpcAPI.SORT_EXPR)).map(Object::toString);
return this.>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 Source source = new DBSource(broker, xquery, true);
try {
final Map rpcResponse = this.>compileQuery(broker, transaction, source, parameters)
.apply(compiledQuery -> queryResultToRpcResponse(startTime, doQuery(broker, compiledQuery, null, parameters), sortBy));
return rpcResponse;
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
@Override
public Map executeT(final String pathToQuery, final Map parameters) throws EXistException, PermissionDeniedException {
final long startTime = System.currentTimeMillis();
final Optional sortBy = Optional.ofNullable(parameters.get(RpcAPI.SORT_EXPR)).map(Object::toString);
return this.>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 Source source = new DBSource(broker, xquery, true);
try {
final Map rpcResponse = this.>compileQuery(broker, transaction, source, parameters)
.apply(compiledQuery -> queryResultToTypedRpcResponse(startTime, doQuery(broker, compiledQuery, null, parameters), sortBy));
return rpcResponse;
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
@Override
public boolean releaseQueryResult(final int handle) {
factory.resultSets.remove(handle);
if(LOG.isDebugEnabled()) {
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);
if(LOG.isDebugEnabled()) {
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.writeCollection(docUri.removeLastSegment()).apply((collection, broker, transaction) -> {
// keep a write lock in the transaction
transaction.acquireCollectionLock(() -> broker.getBrokerPool().getLockManager().acquireCollectionWriteLock(collection.getURI()));
try(final LockedDocument lockedDoc = collection.getDocumentWithLock(broker, docUri.lastSegment(), LockMode.WRITE_LOCK)) {
if (lockedDoc == null) {
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
throw new EXistException("Document " + docUri + " not found");
}
final DocumentImpl doc = lockedDoc.getDocument();
if (doc.getResourceType() == DocumentImpl.BINARY_FILE) {
collection.removeBinaryResource(transaction, broker, doc);
} else {
collection.removeXMLResource(transaction, broker, docUri.lastSegment());
}
// NOTE: early release of Collection lock inline with Asymmetrical Locking scheme
collection.close();
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.writeCollection(collURI).apply((collection, broker, transaction) -> {
// keep a write lock in the transaction
transaction.acquireCollectionLock(() -> broker.getBrokerPool().getLockManager().acquireCollectionWriteLock(collection.getURI()));
if(LOG.isDebugEnabled()) {
LOG.debug("removing collection {}", collURI);
}
return broker.removeCollection(transaction, collection);
});
} catch (final EXistException e) {
if(LOG.isDebugEnabled()) {
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 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 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 parameters) throws EXistException, PermissionDeniedException {
return this.readDocument(docUri).apply((document, broker, transaction) -> {
final NodeId nodeId = factory.getBrokerPool().getNodeFactory().createFromString(s_id);
final NodeProxy node = new NodeProxy(document, nodeId);
try (final StringWriter writer = new StringWriter()) {
serialize(broker, toProperties(parameters), saxSerializer -> saxSerializer.serialize(node), writer);
return writer.toString();
}
});
}
@Override
public Map retrieveFirstChunk(final String docName, final String id, final Map 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.>readDocument(docUri).apply((document, broker, transaction) -> {
final NodeId nodeId = factory.getBrokerPool().getNodeFactory().createFromString(id);
final NodeProxy node = new NodeProxy(document, nodeId);
final Map result = new HashMap<>();
final TemporaryFileManager temporaryFileManager = TemporaryFileManager.getInstance();
final Path tempFile = temporaryFileManager.getTemporaryFile();
if (compression && LOG.isDebugEnabled()) {
LOG.debug("retrieveFirstChunk with compression");
}
try (final OutputStream os = compression
? new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(tempFile)))
: new BufferedOutputStream(Files.newOutputStream(tempFile));
final Writer writer = new OutputStreamWriter(os, getEncoding(parameters))) {
serialize(broker, toProperties(parameters), saxSerializer -> saxSerializer.toSAX(node), writer);
}
final byte[] firstChunk = getChunk(tempFile, 0);
result.put("data", firstChunk);
int offset = 0;
if (Files.size(tempFile) > MAX_DOWNLOAD_CHUNK_SIZE) {
offset = firstChunk.length;
final int handle = factory.resultSets.add(new SerializedResult(tempFile));
result.put("handle", Integer.toString(handle));
result.put("supports-long-offset", Boolean.TRUE);
} else {
temporaryFileManager.returnTemporaryFile(tempFile);
}
result.put("offset", offset);
return result;
});
}
@Override
public byte[] retrieve(final int resultId, final int num, final Map parameters)
throws EXistException, PermissionDeniedException {
final Charset encoding = getEncoding(parameters);
final boolean compression = useCompression(parameters);
final String xml = retrieveAsString(resultId, num, parameters);
if (compression) {
if(LOG.isDebugEnabled()) {
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 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;
for (final Map.Entry entry : qr.serialization.entrySet()) {
parameters.put(entry.getKey().toString(), entry.getValue().toString());
}
try (final StringWriter writer = new StringWriter()) {
serialize(broker, toProperties(parameters), saxSerializer -> saxSerializer.toSAX(nodeValue), writer);
return writer.toString();
}
} else {
try {
return item.getStringValue();
} catch (final XPathException e) {
throw new EXistException(e);
}
}
});
}
@Override
public Map retrieveFirstChunk(final int resultId, final int num, final Map 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 result = new HashMap<>();
final TemporaryFileManager temporaryFileManager = TemporaryFileManager.getInstance();
final Path tempFile = temporaryFileManager.getTemporaryFile();
if (compression && LOG.isDebugEnabled()) {
LOG.debug("retrieveFirstChunk with compression");
}
try (final OutputStream os = compression
? new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(tempFile)))
: new BufferedOutputStream(Files.newOutputStream(tempFile));
final Writer writer = new OutputStreamWriter(os, getEncoding(parameters))) {
if (Type.subTypeOf(item.getType(), Type.NODE)) {
final NodeValue nodeValue = (NodeValue) item;
for (final Map.Entry entry : qr.serialization.entrySet()) {
parameters.put(entry.getKey().toString(), entry.getValue().toString());
}
serialize(broker, toProperties(parameters), saxSerializer -> saxSerializer.toSAX(nodeValue), writer);
} else {
writer.write(item.getStringValue());
}
} catch (final XPathException e) {
throw new EXistException(e);
}
final byte[] firstChunk = getChunk(tempFile, 0);
result.put("data", firstChunk);
int offset = 0;
if (Files.size(tempFile) > MAX_DOWNLOAD_CHUNK_SIZE) {
offset = firstChunk.length;
final int handle = factory.resultSets.add(new SerializedResult(tempFile));
result.put("handle", Integer.toString(handle));
result.put("supports-long-offset", Boolean.TRUE);
} else {
temporaryFileManager.returnTemporaryFile(tempFile);
}
result.put("offset", offset);
return result;
});
}
@Override
public byte[] retrieveAll(final int resultId, final Map parameters) throws EXistException,
PermissionDeniedException {
final String xml = retrieveAllAsString(resultId, parameters);
return xml.getBytes(getEncoding(parameters));
}
private String retrieveAllAsString(final int resultId, final Map 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 SAXSerializer handler = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
try (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();
return writer.toString();
} finally {
SerializerPool.getInstance().returnObject(handler);
}
});
}
@Override
public Map retrieveAllFirstChunk(final int resultId, final Map 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();
for (final Map.Entry entry : qr.serialization.entrySet()) {
parameters.put(entry.getKey().toString(), entry.getValue().toString());
}
final SAXSerializer handler = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
try {
final Map result = new HashMap<>();
final TemporaryFileManager temporaryFileManager = TemporaryFileManager.getInstance();
final Path tempFile = temporaryFileManager.getTemporaryFile();
if (compression && LOG.isDebugEnabled()) {
LOG.debug("retrieveAllFirstChunk with compression");
}
try (final OutputStream os = compression
? new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(tempFile)))
: new BufferedOutputStream(Files.newOutputStream(tempFile));
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();
}
final byte[] firstChunk = getChunk(tempFile, 0);
result.put("data", firstChunk);
int offset = 0;
if (Files.size(tempFile) > MAX_DOWNLOAD_CHUNK_SIZE) {
offset = firstChunk.length;
final int handle = factory.resultSets.add(new SerializedResult(tempFile));
result.put("handle", Integer.toString(handle));
result.put("supports-long-offset", Boolean.TRUE);
} else {
temporaryFileManager.returnTemporaryFile(tempFile);
}
result.put("offset", offset);
return result;
} finally {
SerializerPool.getInstance().returnObject(handler);
}
});
}
@Override
public boolean chgrp(final String resource, final String group) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.chown(broker, transaction, uri, Optional.empty(), Optional.ofNullable(group));
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.chown(broker, transaction, uri, Optional.ofNullable(owner), Optional.empty());
return true;
});
}
@Override
public boolean chown(final String resource, final String owner, final String group) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.chown(broker, transaction, uri, Optional.ofNullable(owner), Optional.ofNullable(group));
return true;
});
}
@Override
public boolean setPermissions(final String resource, final int mode) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.chmod(broker, transaction, uri, Optional.of(mode), Optional.empty());
return true;
});
}
@Override
public boolean setPermissions(final String resource, final String mode) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.chmod_str(broker, transaction, uri, Optional.ofNullable(mode), Optional.empty());
return true;
});
}
@Override
public boolean setPermissions(final String resource, final String owner, final String group, final String mode) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.chown(broker, transaction, uri, Optional.ofNullable(owner), Optional.ofNullable(group));
PermissionFactory.chmod_str(broker, transaction, uri, Optional.ofNullable(mode), Optional.empty());
return true;
});
}
@Override
public boolean setPermissions(final String resource, final String owner, final String group, final int mode) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.chown(broker, transaction, uri, Optional.ofNullable(owner), Optional.ofNullable(group));
PermissionFactory.chmod(broker, transaction, uri, Optional.of(mode), Optional.empty());
return true;
});
}
@Override
public boolean setPermissions(final String resource, final String owner, final String group, final int mode, final List aces) throws EXistException, PermissionDeniedException, URISyntaxException {
final XmldbURI uri = XmldbURI.xmldbUriFor(resource);
return withDb((broker, transaction) -> {
PermissionFactory.chown(broker, transaction, uri, Optional.ofNullable(owner), Optional.ofNullable(group));
PermissionFactory.chmod(broker, transaction, uri, Optional.of(mode), Optional.ofNullable(aces));
return true;
});
}
@Override
public boolean addAccount(final String name, String passwd, final String passwdDigest, final List groups, final Boolean enabled, final Integer umask, final Map 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 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 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 groups, final Boolean enabled, final Integer umask, final Map 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 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 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 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;
}
@Override
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 managers, final Map 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 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 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;
});
}
@Override
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
*
* 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.
*
* This is called via RemoteUserManagementService.addUserGroup(Account)
*
* @param name user name to update
* @param groups list of groups the user is added to
* @return true, if action succeeded
*/
@Override
public boolean updateAccount(final String name, final List groups) {
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) {
if(LOG.isDebugEnabled()) {
LOG.debug("addUserGroup encountered error", e);
}
return false;
}
}
/**
* Added by {Marco.Tampucci, Massimo.Martinelli} @isti.cnr.it
*
* 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.
*
* This is called via RemoteUserManagementService.removeGroup(Account,
* String)
*
* @param name username to update
* @param groups a list of groups
* @param rgroup the user will be removed from this group
* @return true, if the action succeeded
*/
public boolean updateAccount(final String name, final List groups, final String rgroup) {
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) {
if(LOG.isDebugEnabled()) {
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.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.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.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 summary(final String xpath) throws EXistException, PermissionDeniedException {
final Source source = new StringSource(xpath);
return this.>withDb((broker, transaction) -> {
final long startTime = System.currentTimeMillis();
final Map parameters = new HashMap<>();
try {
final QueryResult qr = this.compileQuery(broker, transaction, source, parameters).apply(compiledQuery -> doQuery(broker, compiledQuery, null, parameters));
if (qr == null) {
return new HashMap<>();
}
if (qr.hasErrors()) {
throw qr.getException();
}
if (qr.result == null) {
return summaryToMap(qr.queryTime, null, null, null);
}
final Tuple2, java.util.Collection> summary = summarise(qr.result);
return summaryToMap(System.currentTimeMillis() - startTime, qr.result, summary._1, summary._2);
} catch (final XPathException e) {
throw new EXistException(e);
}
});
}
public Map 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();
if (qr.result == null) {
return summaryToMap(qr.queryTime, null, null, null);
}
final Tuple2, java.util.Collection> summary = summarise(qr.result);
return summaryToMap(qr.queryTime, qr.result, summary._1, summary._2);
}
private Tuple2, java.util.Collection> summarise(final Sequence results) throws XPathException {
final Map nodeCounts = new HashMap<>();
final Map doctypeCounts = new HashMap<>();
NodeCount counter;
DoctypeCount doctypeCounter;
for (final SequenceIterator i = results.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) {
final NodeProxy p = (NodeProxy) nv;
final String docName = p.getOwnerDocument().getURI().toString();
final DocumentType doctype = p.getOwnerDocument().getDoctype();
if (nodeCounts.containsKey(docName)) {
counter = nodeCounts.get(docName);
counter.inc();
} else {
counter = new NodeCount(p.getOwnerDocument());
nodeCounts.put(docName, counter);
}
if (doctype == null) {
continue;
}
if (doctypeCounts.containsKey(doctype.getName())) {
doctypeCounter = doctypeCounts.get(doctype.getName());
doctypeCounter.inc();
} else {
doctypeCounter = new DoctypeCount(doctype);
doctypeCounts.put(doctype.getName(), doctypeCounter);
}
}
}
}
return new Tuple2<>(nodeCounts.values(), doctypeCounts.values());
}
private Map summaryToMap(final long queryTime, @Nullable final Sequence results,
@Nullable final java.util.Collection nodeCounts, @Nullable final java.util.Collection doctypeCounts) {
final Map result = new HashMap<>();
result.put("queryTime", queryTime);
if (results == null) {
result.put("hits", 0);
return result;
}
result.put("hits", results.getItemCount());
final List documents = new ArrayList<>();
for (final NodeCount nodeCount : nodeCounts) {
final List hitsByDoc = new ArrayList<>();
hitsByDoc.add(nodeCount.doc.getFileURI().toString());
hitsByDoc.add(nodeCount.doc.getDocId());
hitsByDoc.add(nodeCount.count);
documents.add(hitsByDoc);
}
result.put("documents", documents);
final List dtypes = new ArrayList<>();
for (final DoctypeCount docTemp : doctypeCounts) {
final List hitsByType = new ArrayList<>();
hitsByType.add(docTemp.doctype.getName());
hitsByType.add(docTemp.count);
dtypes.add(hitsByType);
}
result.put("doctypes", dtypes);
return result;
}
@Override
public List getIndexedElements(final String collectionName,
final boolean inclusive) throws EXistException, PermissionDeniedException, URISyntaxException {
return getIndexedElements(XmldbURI.xmldbUriFor(collectionName), inclusive);
}
private List getIndexedElements(final XmldbURI collUri,
final boolean inclusive) throws EXistException, PermissionDeniedException {
return this.>readCollection(collUri).apply((collection, broker, transaction) -> {
final Occurrences occurrences[] = broker.getElementIndex().scanIndexedElements(collection,
inclusive);
final 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 parameters) {
final Properties properties = new Properties();
properties.putAll(parameters);
return properties;
}
private static class DoctypeCount {
final DocumentType doctype;
int count = 1;
public DoctypeCount(final DocumentType doctype) {
this.doctype = doctype;
}
public void inc() {
count++;
}
}
private static class NodeCount {
final DocumentImpl doc;
int count = 1;
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")) {
if(LOG.isDebugEnabled()) {
LOG.debug("Read from: {} to: {}", start, start + len);
}
os.seek(start);
os.read(buffer);
}
return buffer;
}
@Deprecated
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, PreserveType.DEFAULT);
}
@Deprecated
public boolean moveOrCopyResource(final String documentPath, final String destinationPath,
final String newName, final boolean move, final PreserveType preserve)
throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyResource(XmldbURI.xmldbUriFor(documentPath),
XmldbURI.xmldbUriFor(destinationPath), XmldbURI.xmldbUriFor(newName), move, preserve);
}
private boolean moveOrCopyResource(final XmldbURI docUri, final XmldbURI destUri,
final XmldbURI newName, final boolean move, final PreserveType preserve)
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.withCollection(srcCollectionMode, broker, transaction, docUri.removeLastSegment()).apply((source, broker1, transaction1) ->
this.writeDocument(broker1, transaction1, source, docUri).apply((document, broker2, transaction2) ->
this.writeCollection(broker2, transaction2, destUri).apply((destination, broker3, transaction3) -> {
if (move) {
broker3.moveResource(transaction3, document, destination, newName);
} else {
broker3.copyResource(transaction3, document, destination, newName, preserve);
}
return true;
})
)
)
);
}
@Deprecated
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, PreserveType.DEFAULT);
}
@Deprecated
public boolean moveOrCopyCollection(final String collectionName, final String destinationPath,
final String newName, final boolean move, final PreserveType preserve)
throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyCollection(XmldbURI.xmldbUriFor(collectionName),
XmldbURI.xmldbUriFor(destinationPath), XmldbURI.xmldbUriFor(newName), move, preserve);
}
private boolean moveOrCopyCollection(final XmldbURI collUri, final XmldbURI destUri,
final XmldbURI newName, final boolean move, final PreserveType preserve)
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.withCollection(srcCollectionMode, broker, transaction, collUri).apply((source, broker1, transaction1) ->
this.writeCollection(broker1, transaction1, destUri).apply((destination, broker2, transaction2) -> {
if (move) {
broker2.moveCollection(transaction2, source, destination, newName);
} else {
broker2.copyCollection(transaction2, source, destination, newName, preserve);
}
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(transaction, collUri);
if(LOG.isDebugEnabled()) {
LOG.debug("collection {} and sub-collections reindexed", collUri);
}
return null;
});
}
@Override
public boolean reindexDocument(final String docUri) throws EXistException, PermissionDeniedException {
withDb((broker, transaction) -> {
try(final LockedDocument lockedDoc = broker.getXMLResource(XmldbURI.create(docUri), LockMode.READ_LOCK)) {
broker.reindexXMLResource(transaction, lockedDoc.getDocument(), DBBroker.IndexMode.STORE);
if(LOG.isDebugEnabled()) {
LOG.debug("document {} reindexed", docUri);
}
return null;
}
});
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 anything fails
* @throws URISyntaxException if the documentPath is invalid
* @throws EXistException if an internal error occurs
* @throws PermissionDeniedException If the current user is not allowed to perform this action
*/
@Override
public boolean isValid(final String documentPath)
throws PermissionDeniedException, URISyntaxException, EXistException {
return isValid(XmldbURI.xmldbUriFor(documentPath));
}
private boolean isValid(final XmldbURI docUri) throws EXistException {
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 getDocType(final String documentPath)
throws PermissionDeniedException, EXistException, URISyntaxException {
return getDocType(XmldbURI.xmldbUriFor(documentPath));
}
private List getDocType(final XmldbURI docUri)
throws PermissionDeniedException, EXistException {
return this.>readDocument(docUri).apply((document, broker, transaction) -> {
final List 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.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 (doctypename != null && !doctypename.isEmpty()) {
result = new DocumentTypeImpl(doctypename,
publicid != null && publicid.isEmpty() ? null : publicid,
systemid != null && systemid.isEmpty() ? 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, PreserveType.DEFAULT);
}
@Override
public boolean copyResource(final String docPath, final String destinationPath, final String newName, final String preserveType) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyResource(docPath, destinationPath, newName, false, PreserveType.valueOf(preserveType));
}
@Override
public boolean copyCollection(final String collectionPath, final String destinationPath, final String newName) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyCollection(collectionPath, destinationPath, newName, false, PreserveType.DEFAULT);
}
@Override
public boolean copyCollection(final String collectionPath, final String destinationPath, final String newName, final String preserveType) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyCollection(collectionPath, destinationPath, newName, false, PreserveType.valueOf(preserveType));
}
@Override
public boolean moveResource(final String docPath, final String destinationPath, final String newName) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyResource(docPath, destinationPath, newName, true, PreserveType.DEFAULT);
}
@Override
public boolean moveCollection(final String collectionPath, final String destinationPath, final String newName) throws EXistException, PermissionDeniedException, URISyntaxException {
return moveOrCopyCollection(collectionPath, destinationPath, newName, true, PreserveType.DEFAULT);
}
@Override
public List getDocumentChunk(final String name, final Map parameters) throws EXistException, PermissionDeniedException, IOException {
final List result = new ArrayList<>(2);
final TemporaryFileManager temporaryFileManager = TemporaryFileManager.getInstance();
final Path file = temporaryFileManager.getTemporaryFile();
try (final OutputStream os = new BufferedOutputStream(Files.newOutputStream(file))) {
os.write(getDocument(name, parameters));
}
result.add(FileUtils.fileName(file));
result.add(Long.toString(Files.size(file)));
return result;
}
@Override
public boolean copyCollection(final String name, final String namedest) throws PermissionDeniedException, EXistException {
createCollection(namedest);
final Map parameters = new HashMap<>();
parameters.put(OutputKeys.INDENT, "no");
parameters.put(EXistOutputKeys.EXPAND_XINCLUDES, "no");
parameters.put(OutputKeys.ENCODING, DEFAULT_ENCODING);
final Map 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 (Object document : documents) {
final Map