/*
* eXist Open Source Native XML Database
* Copyright (C) 2010 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.webdav;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.IndexInfo;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.DocumentImpl;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.util.LockException;
import org.exist.util.MimeTable;
import org.exist.util.MimeType;
import org.exist.util.VirtualTempFile;
import org.exist.util.VirtualTempFileInputSource;
import org.exist.webdav.exceptions.CollectionDoesNotExistException;
import org.exist.webdav.exceptions.CollectionExistsException;
import org.exist.xmldb.XmldbURI;
import org.xml.sax.SAXException;
/**
* Class for accessing the Collection class of the exist-db native API.
*
* @author Dannes Wessels (dizzzz_at_exist-db.org)
*/
public class ExistCollection extends ExistResource {
public ExistCollection(XmldbURI uri, BrokerPool pool) {
if(LOG.isTraceEnabled())
LOG.trace("New collection object for " + uri);
brokerPool = pool;
this.xmldbUri = uri;
}
/**
* Initialize Collection, authenticate() is required first
*/
//@Override
public void initMetadata() {
if (user == null) {
LOG.error("User not initialized yet");
return;
}
// check if initialization is required
if (isInitialized) {
LOG.debug("Already initialized");
return;
}
DBBroker broker = null;
Collection collection = null;
try {
// Get access to collection
broker = brokerPool.get(user);
collection = broker.openCollection(xmldbUri, Lock.READ_LOCK);
if (collection == null) {
LOG.error("Collection for " + xmldbUri + " cannot be opened for metadata");
return;
}
// Retrieve some meta data
permissions = collection.getPermissions();
readAllowed = permissions.validate(user, Permission.READ);
writeAllowed = permissions.validate(user, Permission.WRITE);
updateAllowed = permissions.validate(user, Permission.UPDATE);
creationTime = collection.getCreationTime();
lastModified = creationTime; // Collection does not have more information.
ownerUser = permissions.getOwner();
ownerGroup = permissions.getOwnerGroup();
} catch (EXistException e) {
LOG.error(e);
} finally {
// Clean up collection
if (collection != null) {
collection.release(Lock.READ_LOCK);
}
// Return broker
brokerPool.release(broker);
// Set flag
isInitialized = true;
}
}
/**
* Retrieve full URIs of all Collections in this collection.
*/
public List<XmldbURI> getCollectionURIs() {
List<XmldbURI> collectionURIs = new ArrayList<XmldbURI>();
DBBroker broker = null;
Collection collection = null;
try {
// Try to read as specified user
broker = brokerPool.get(user);
collection = broker.openCollection(xmldbUri, Lock.READ_LOCK);
// Get all collections
Iterator<XmldbURI> collections = collection.collectionIteratorNoLock(); // QQ: use collectionIterator ?
while (collections.hasNext()) {
collectionURIs.add(xmldbUri.append(collections.next()));
}
} catch (EXistException e) {
LOG.error(e);
collectionURIs = null;
} finally {
if (collection != null) {
collection.release(Lock.READ_LOCK);
}
brokerPool.release(broker);
}
return collectionURIs;
}
/**
* Retrieve full URIs of all Documents in the collection.
*/
public List<XmldbURI> getDocumentURIs() {
List<XmldbURI> documentURIs = new ArrayList<XmldbURI>();
DBBroker broker = null;
Collection collection = null;
try {
// Try to read as specified user
broker = brokerPool.get(user);
collection = broker.openCollection(xmldbUri, Lock.READ_LOCK);
// Get all documents
Iterator<DocumentImpl> documents = collection.iteratorNoLock(broker); // QQ: use 'iterator'
while (documents.hasNext()) {
documentURIs.add(documents.next().getURI());
}
} catch (EXistException e) {
LOG.error(e);
documentURIs = null;
} finally {
// Clean up resources
if (collection != null) {
collection.release(Lock.READ_LOCK);
}
brokerPool.release(broker);
}
return documentURIs;
}
/*
* Delete document or collection.
*/
void delete() {
if(LOG.isDebugEnabled())
LOG.debug("Deleting '" + xmldbUri + "'");
DBBroker broker = null;
Collection collection = null;
TransactionManager transact = brokerPool.getTransactionManager();
Txn txn = transact.beginTransaction();
try {
broker = brokerPool.get(user);
// Open collection if possible, else abort
collection = broker.openCollection(xmldbUri, Lock.WRITE_LOCK);
if (collection == null) {
transact.abort(txn);
return;
}
// Remove collection
broker.removeCollection(txn, collection);
// Commit change
transact.commit(txn);
if(LOG.isDebugEnabled())
LOG.debug("Document deleted sucessfully");
} catch (EXistException e) {
LOG.error(e);
transact.abort(txn);
} catch (IOException e) {
LOG.error(e);
transact.abort(txn);
} catch (PermissionDeniedException e) {
LOG.error(e);
transact.abort(txn);
// } catch (TriggerException e) {
// LOG.error(e);
// transact.abort(txn);
} finally {
// TODO: check if can be done earlier
if (collection != null) {
collection.release(Lock.WRITE_LOCK);
}
brokerPool.release(broker);
if(LOG.isDebugEnabled())
LOG.debug("Finished delete");
}
}
public XmldbURI createCollection(String name) throws PermissionDeniedException, CollectionExistsException, EXistException {
if(LOG.isDebugEnabled())
LOG.debug("Create '" + name + "' in '" + xmldbUri + "'");
XmldbURI newCollection = xmldbUri.append(name);
DBBroker broker = null;
Collection collection = null;
TransactionManager transact = brokerPool.getTransactionManager();
Txn txn = transact.beginTransaction();
try {
broker = brokerPool.get(user);
// Check if collection exists. not likely to happen since availability is
// checked by ResourceFactory
collection = broker.openCollection(newCollection, Lock.WRITE_LOCK);
if (collection != null) {
LOG.debug("Collection already exists");
transact.abort(txn);
throw new CollectionExistsException("Collection already exists");
}
// Create collection
Collection created = broker.getOrCreateCollection(txn, newCollection);
broker.saveCollection(txn, created);
broker.flush();
// Commit change
transact.commit(txn);
if(LOG.isDebugEnabled())
LOG.debug("Collection created sucessfully");
} catch (EXistException e) {
LOG.error(e);
transact.abort(txn);
throw e;
} catch (IOException e) {
LOG.error(e);
transact.abort(txn);
} catch (PermissionDeniedException e) {
LOG.error(e);
transact.abort(txn);
throw e;
} catch (Throwable e) {
LOG.error(e);
transact.abort(txn);
throw new EXistException(e);
} finally {
// TODO: check if can be done earlier
if (collection != null) {
collection.release(Lock.WRITE_LOCK);
}
brokerPool.release(broker);
if(LOG.isDebugEnabled())
LOG.debug("Finished creation");
}
return newCollection;
}
public XmldbURI createFile(String newName, InputStream is, Long length, String contentType)
throws IOException, PermissionDeniedException, CollectionDoesNotExistException {
if(LOG.isDebugEnabled())
LOG.debug("Create '" + newName + "' in '" + xmldbUri + "'");
XmldbURI newNameUri = XmldbURI.create(newName);
// Get mime, or NULL when not available
MimeType mime = MimeTable.getInstance().getContentTypeFor(newName);
if (mime == null) {
mime = MimeType.BINARY_TYPE;
}
// References to the database
DBBroker broker = null;
Collection collection = null;
// create temp file and store. Existdb needs to read twice from a stream.
BufferedInputStream bis = new BufferedInputStream(is);
VirtualTempFile vtf = new VirtualTempFile();
BufferedOutputStream bos = new BufferedOutputStream(vtf);
// Perform actual copy
IOUtils.copy(bis, bos);
bis.close();
bos.close();
vtf.close();
// To support LockNullResource, a 0-byte XML document can received. Since 0-byte
// XML documents are not supported a small file will be created.
if (mime.isXMLType() && vtf.length() == 0L) {
if(LOG.isDebugEnabled())
LOG.debug("Creating dummy XML file for null resource lock '" + newNameUri + "'");
vtf = new VirtualTempFile();
IOUtils.write("<null_resource/>", vtf);
vtf.close();
}
// Start transaction
TransactionManager transact = brokerPool.getTransactionManager();
Txn txn = transact.beginTransaction();
try {
broker = brokerPool.get(user);
// Check if collection exists. not likely to happen since availability is checked
// by ResourceFactory
collection = broker.openCollection(xmldbUri, Lock.WRITE_LOCK);
if (collection == null) {
LOG.debug("Collection " + xmldbUri + " does not exist");
transact.abort(txn);
throw new CollectionDoesNotExistException(xmldbUri + "");
}
if (mime.isXMLType()) {
if(LOG.isDebugEnabled())
LOG.debug("Inserting XML document '" + mime.getName() + "'");
// Stream into database
VirtualTempFileInputSource vtfis = new VirtualTempFileInputSource(vtf);
IndexInfo info = collection.validateXMLResource(txn, broker, newNameUri, vtfis);
DocumentImpl doc = info.getDocument();
doc.getMetadata().setMimeType(mime.getName());
collection.store(txn, broker, info, vtfis, false);
} else {
if(LOG.isDebugEnabled())
LOG.debug("Inserting BINARY document '" + mime.getName() + "'");
// Stream into database
InputStream fis = vtf.getByteStream();
bis = new BufferedInputStream(fis);
DocumentImpl doc = collection.addBinaryResource(txn, broker, newNameUri, bis,
mime.getName(), length.intValue());
bis.close();
}
// Commit change
transact.commit(txn);
if(LOG.isDebugEnabled())
LOG.debug("Document created sucessfully");
} catch (EXistException e) {
LOG.error(e);
transact.abort(txn);
throw new IOException(e.getMessage());
} catch (TriggerException e) {
LOG.error(e);
transact.abort(txn);
throw new IOException(e.getMessage());
} catch (SAXException e) {
LOG.error(e);
transact.abort(txn);
throw new IOException(e.getMessage());
} catch (LockException e) {
LOG.error(e);
transact.abort(txn);
throw new PermissionDeniedException(xmldbUri + "");
} catch (IOException e) {
LOG.error(e);
transact.abort(txn);
throw e;
} catch (PermissionDeniedException e) {
LOG.error(e);
transact.abort(txn);
throw e;
} finally {
if (vtf != null) {
vtf.delete();
}
// TODO: check if can be done earlier
if (collection != null) {
collection.release(Lock.WRITE_LOCK);
}
brokerPool.release(broker);
if(LOG.isDebugEnabled())
LOG.debug("Finished creation");
}
// Send the result back to the client
XmldbURI newResource = xmldbUri.append(newName);
return newResource;
}
void resourceCopyMove(XmldbURI destCollectionUri, String newName, Mode mode) throws EXistException {
if(LOG.isDebugEnabled())
LOG.debug(mode + " '" + xmldbUri + "' to '" +
destCollectionUri + "' named '" + newName + "'");
XmldbURI newNameUri = null;
try {
newNameUri = XmldbURI.xmldbUriFor(newName);
} catch (URISyntaxException ex) {
LOG.error(ex);
throw new EXistException(ex.getMessage());
}
DBBroker broker = null;
Collection srcCollection = null;
Collection destCollection = null;
TransactionManager txnManager = brokerPool.getTransactionManager();
Txn txn = txnManager.beginTransaction();
try {
broker = brokerPool.get(user);
// This class contains already the URI of the resource that shall be moved/copied
XmldbURI srcCollectionUri = xmldbUri;
// Open collection if possible, else abort
srcCollection = broker.openCollection(srcCollectionUri, Lock.WRITE_LOCK);
if (srcCollection == null) {
txnManager.abort(txn);
return; // TODO throw
}
// Open collection if possible, else abort
destCollection = broker.openCollection(destCollectionUri, Lock.WRITE_LOCK);
if (destCollection == null) {
LOG.debug("Destination collection " + xmldbUri + " does not exist.");
txnManager.abort(txn);
return; // TODO throw?
}
// Perform actial move/copy
if (mode == Mode.COPY) {
broker.copyCollection(txn, srcCollection, destCollection, newNameUri);
} else {
broker.moveCollection(txn, srcCollection, destCollection, newNameUri);
}
// Commit change
txnManager.commit(txn);
if(LOG.isDebugEnabled())
LOG.debug("Collection " + mode + "d sucessfully");
} catch (LockException e) {
LOG.error("Resource is locked.", e);
txnManager.abort(txn);
throw new EXistException(e.getMessage());
} catch (EXistException e) {
LOG.error(e);
txnManager.abort(txn);
throw e;
} catch (IOException e) {
LOG.error(e);
txnManager.abort(txn);
throw new EXistException(e.getMessage());
} catch (PermissionDeniedException e) {
LOG.error(e);
txnManager.abort(txn);
throw new EXistException(e.getMessage());
// } catch (TriggerException e) {
// LOG.error(e);
// txnManager.abort(txn);
// throw new EXistException(e.getMessage());
} finally {
if (destCollection != null) {
destCollection.release(Lock.WRITE_LOCK);
}
if (srcCollection != null) {
srcCollection.release(Lock.WRITE_LOCK);
}
brokerPool.release(broker);
if(LOG.isDebugEnabled())
LOG.debug("Finished " + mode);
}
}
}