/* * 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.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URISyntaxException; import org.exist.collections.Collection; import org.exist.collections.triggers.TriggerException; import org.exist.util.LockException; import org.exist.EXistException; import org.exist.dom.BinaryDocument; import org.exist.dom.DocumentImpl; import org.exist.dom.LockToken; import org.exist.http.webdav.WebDAV; import org.exist.security.User; import org.exist.security.Permission; import org.exist.security.PermissionDeniedException; import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.storage.lock.Lock; import org.exist.storage.serializers.Serializer; import org.exist.storage.txn.TransactionManager; import org.exist.storage.txn.Txn; import org.exist.util.VirtualTempFile; import org.exist.webdav.exceptions.DocumentAlreadyLockedException; import org.exist.webdav.exceptions.DocumentNotLockedException; 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 ExistDocument extends ExistResource { public ExistDocument(XmldbURI uri, BrokerPool pool) { if(LOG.isTraceEnabled()) LOG.trace("New document 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; DocumentImpl document = null; try { broker = brokerPool.get(user); // If it is not a collection, check if it is a document document = broker.getXMLResource(xmldbUri, Lock.READ_LOCK); if(document.getResourceType() == DocumentImpl.XML_FILE) { isXmlDocument=true; } // Get meta data creationTime = document.getMetadata().getCreated(); lastModified = document.getMetadata().getLastModified(); mimeType = document.getMetadata().getMimeType(); // Retrieve perssions permissions = document.getPermissions(); readAllowed = permissions.validate(user, Permission.READ); writeAllowed = permissions.validate(user, Permission.WRITE); updateAllowed = permissions.validate(user, Permission.UPDATE); ownerUser = permissions.getOwner(); ownerGroup = permissions.getOwnerGroup(); // Get (estimated) file size contentLength = document.getContentLength(); } catch (EXistException e) { LOG.error(e); } catch (PermissionDeniedException e) { LOG.error(e); } finally { // Cleanup resources if (document != null) { document.getUpdateLock().release(Lock.READ_LOCK); } brokerPool.release(broker); isInitialized = true; } } private String mimeType; public String getMimeType() { return mimeType; } private int contentLength = 0; public int getContentLength() { return contentLength; } private boolean isXmlDocument=false; public boolean isXmlDocument() { return isXmlDocument; } /** * Stream document to framework. */ public void stream(OutputStream os) throws IOException, PermissionDeniedException { if(LOG.isDebugEnabled()) LOG.debug("Stream started"); long startTime = System.currentTimeMillis(); DBBroker broker = null; DocumentImpl document = null; try { broker = brokerPool.get(user); // If it is not a collection, check if it is a document document = broker.getXMLResource(xmldbUri, Lock.READ_LOCK); if (document.getResourceType() == DocumentImpl.XML_FILE) { // Stream XML document Serializer serializer = broker.getSerializer(); serializer.reset(); try { serializer.setProperties(WebDAV.OUTPUT_PROPERTIES); Writer w = new OutputStreamWriter(os, "UTF-8"); serializer.serialize(document, w); w.flush(); w.close(); // don;t flush if(! (os instanceof VirtualTempFile)) os.flush(); } catch (SAXException e) { LOG.error(e); throw new IOException("Error while serializing XML document: " + e.getMessage()); } } else { // Stream NON-XML document broker.readBinaryResource((BinaryDocument) document, os); os.flush(); } } catch (EXistException e) { LOG.error(e); throw new IOException(e.getMessage()); } catch (PermissionDeniedException e) { LOG.error(e); throw e; } finally { if (document != null) { document.getUpdateLock().release(Lock.READ_LOCK); } brokerPool.release(broker); if(LOG.isDebugEnabled()) LOG.debug("Stream stopped, duration " + (System.currentTimeMillis()-startTime) + " msec."); } } /** * Remove document from database. */ void delete() { if(LOG.isDebugEnabled()) LOG.debug("Deleting " + xmldbUri); DBBroker broker = null; Collection collection = null; DocumentImpl resource = null; TransactionManager transact = brokerPool.getTransactionManager(); Txn txn = transact.beginTransaction(); try { broker = brokerPool.get(user); // Need to split path into collection and document name XmldbURI collName = xmldbUri.removeLastSegment(); XmldbURI docName = xmldbUri.lastSegment(); // Open collection if possible, else abort collection = broker.openCollection(collName, Lock.WRITE_LOCK); if (collection == null) { LOG.debug("Collection does not exist"); transact.abort(txn); return; } // Open document if possible, else abort resource = collection.getDocument(broker, docName); if (resource == null) { LOG.debug("No resource found for path: " + xmldbUri); transact.abort(txn); return; } if (resource.getResourceType() == DocumentImpl.BINARY_FILE) { collection.removeBinaryResource(txn, broker, resource.getFileURI()); } else { collection.removeXMLResource(txn, broker, resource.getFileURI()); } // Commit change transact.commit(txn); if(LOG.isDebugEnabled()) LOG.debug("Document deleted sucessfully"); } catch (LockException e) { LOG.error("Resource is locked.", e); transact.abort(txn); } catch (EXistException e) { LOG.error(e); transact.abort(txn); } catch (TriggerException e) { LOG.error(e); transact.abort(txn); } catch (PermissionDeniedException 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"); } } /** * Get lock token from database. */ public LockToken getCurrentLock() { if(LOG.isDebugEnabled()) LOG.debug("Get current lock " + xmldbUri); DBBroker broker = null; DocumentImpl document = null; try { broker = brokerPool.get(user); // If it is not a collection, check if it is a document document = broker.getXMLResource(xmldbUri, Lock.READ_LOCK); if (document == null) { LOG.debug("No resource found for path: " + xmldbUri); return null; } // TODO consider. A Webdav lock can be set without user lock. User lock = document.getUserLock(); if (lock == null) { if(LOG.isDebugEnabled()) LOG.debug("Document " + xmldbUri + " does not contain userlock"); return null; } // Retrieve Locktoken from document metadata org.exist.dom.LockToken token = document.getMetadata().getLockToken(); if (token == null) { if(LOG.isDebugEnabled()) LOG.debug("Document meta data does not contain a LockToken"); return null; } if(LOG.isDebugEnabled()) LOG.debug("Successfully retrieved token"); return token; } catch (EXistException e) { LOG.error(e); return null; } catch (PermissionDeniedException e) { LOG.error(e); return null; } finally { if (document != null) { document.getUpdateLock().release(Lock.READ_LOCK); } brokerPool.release(broker); if(LOG.isDebugEnabled()) LOG.debug("Finished probe lock"); } } /** * Lock document. */ public LockToken lock(LockToken inputToken) throws PermissionDeniedException, DocumentAlreadyLockedException, EXistException { if(LOG.isDebugEnabled()) LOG.debug("create lock " + xmldbUri); DBBroker broker = null; DocumentImpl document = null; try { broker = brokerPool.get(user); // Try to get document (add catch?) document = broker.getXMLResource(xmldbUri, Lock.WRITE_LOCK); if (document == null) { if(LOG.isDebugEnabled()) LOG.debug("No resource found for path: " + xmldbUri); return null; // throw exception? } // Get current userlock User userLock = document.getUserLock(); // Check if Resource is already locked. @@ToDo if (userLock != null) { if(LOG.isDebugEnabled()) LOG.debug("Resource was already locked locked."); } if (userLock != null && userLock.getName()!=null && !userLock.getName().equals(user.getName())) { if(LOG.isDebugEnabled()) LOG.debug("Resource is locked by user " + userLock.getName() + "."); throw new PermissionDeniedException(userLock.getName()); } // Check for request fo shared lock. @@TODO if (inputToken.getScope() == LockToken.LOCK_SCOPE_SHARED) { if(LOG.isDebugEnabled()) LOG.debug("Shared locks are not implemented."); throw new EXistException("Shared locks are not implemented."); } // Update locktoken inputToken.setOwner(user.getName()); inputToken.createOpaqueLockToken(); //inputToken.setTimeOut(inputToken.getTimeOut()); inputToken.setTimeOut(LockToken.LOCK_TIMEOUT_INFINITE); // Update document document.getMetadata().setLockToken(inputToken); document.setUserLock(user); // Make token persistant TransactionManager transact = brokerPool.getTransactionManager(); Txn transaction = transact.beginTransaction(); broker.storeXMLResource(transaction, document); transact.commit(transaction); if(LOG.isDebugEnabled()) LOG.debug("Successfully retrieved token"); return inputToken; } catch (EXistException e) { LOG.error(e); throw e; } catch (PermissionDeniedException e) { LOG.error(e); throw e; } finally { // TODO: check if can be done earlier if (document != null) { document.getUpdateLock().release(Lock.WRITE_LOCK); } brokerPool.release(broker); if(LOG.isDebugEnabled()) LOG.debug("Finished create lock"); } } /** * Unlock document in database. */ void unlock() throws PermissionDeniedException, DocumentNotLockedException, EXistException { if(LOG.isDebugEnabled()) LOG.debug("unlock " + xmldbUri); DBBroker broker = null; DocumentImpl document = null; TransactionManager transact = brokerPool.getTransactionManager(); Txn transaction = transact.beginTransaction(); try { broker = brokerPool.get(user); // Try to get document (add catch?) document = broker.getXMLResource(xmldbUri, Lock.WRITE_LOCK); if (document == null) { LOG.debug("No resource found for path: " + xmldbUri); throw new EXistException("No resource found for path: " + xmldbUri); } // Get current userlock User lock = document.getUserLock(); // Check if Resource is already locked. if (lock == null) { LOG.debug("Resource " + xmldbUri + " is not locked."); throw new DocumentNotLockedException("" + xmldbUri); } // Check if Resource is from user if (!lock.getName().equals(user.getName())) { LOG.debug("Resource lock is from user " + lock.getName()); throw new PermissionDeniedException(lock.getName()); } // Update document document.setUserLock(null); document.getMetadata().setLockToken(null); // Make it persistant broker.storeXMLResource(transaction, document); transact.commit(transaction); } catch (EXistException e) { transact.abort(transaction); LOG.error(e); throw e; } catch (PermissionDeniedException e) { transact.abort(transaction); LOG.error(e); throw e; } finally { if(document!=null){ document.getUpdateLock().release(Lock.WRITE_LOCK); } brokerPool.release(broker); if(LOG.isDebugEnabled()) LOG.debug("Finished create lock"); } } /** * Copy document or collection in database. */ 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; DocumentImpl srcDocument = null; Collection destCollection = null; TransactionManager txnManager = brokerPool.getTransactionManager(); Txn txn = txnManager.beginTransaction(); try { broker = brokerPool.get(user); // Need to split path into collection and document name XmldbURI srcCollectionUri = xmldbUri.removeLastSegment(); XmldbURI srdDocumentUri = xmldbUri.lastSegment(); // Open collection if possible, else abort srcCollection = broker.openCollection(srcCollectionUri, Lock.WRITE_LOCK); if (srcCollection == null) { txnManager.abort(txn); return; // TODO throw } // Open document if possible, else abort srcDocument = srcCollection.getDocument(broker, srdDocumentUri); if (srcDocument == null) { LOG.debug("No resource found for path: " + xmldbUri); txnManager.abort(txn); return; } // 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; } // Perform actial move/copy if(mode==Mode.COPY){ broker.copyResource(txn, srcDocument, destCollection, newNameUri); } else { broker.moveResource(txn, srcDocument, destCollection, newNameUri); } // Commit change txnManager.commit(txn); if(LOG.isDebugEnabled()) LOG.debug("Document " + 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 { // TODO: check if can be done earlier 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); } } public LockToken refreshLock(String token) throws PermissionDeniedException, DocumentAlreadyLockedException, EXistException, DocumentNotLockedException { if(LOG.isDebugEnabled()) LOG.debug("refresh lock " + xmldbUri + " lock="+token); DBBroker broker = null; DocumentImpl document = null; if (token == null) { if(LOG.isDebugEnabled()) LOG.debug("token is null"); throw new EXistException("token is null"); } try { broker = brokerPool.get(user); // Try to get document (add catch?) document = broker.getXMLResource(xmldbUri, Lock.WRITE_LOCK); if (document == null) { if(LOG.isDebugEnabled()) LOG.debug("No resource found for path: " + xmldbUri); return null; // throw exception? } // Get current userlock User userLock = document.getUserLock(); // Check if Resource is already locked. if (userLock == null) { if(LOG.isDebugEnabled()) LOG.debug("Resource was not locked."); throw new DocumentNotLockedException("Resource was not locked."); } if (userLock.getName()!=null && !userLock.getName().equals(user.getName())) { if(LOG.isDebugEnabled()) LOG.debug("Resource is locked by "+userLock.getName()); throw new PermissionDeniedException(userLock.getName()); } LockToken lockToken = document.getMetadata().getLockToken(); if(!token.equals(lockToken.getOpaqueLockToken())){ if(LOG.isDebugEnabled()) LOG.debug("Token does not match"); throw new PermissionDeniedException("Token "+token+" does not match "+lockToken.getOpaqueLockToken()); } lockToken.setTimeOut(LockToken.LOCK_TIMEOUT_INFINITE); // Make token persistant TransactionManager transact = brokerPool.getTransactionManager(); Txn transaction = transact.beginTransaction(); broker.storeXMLResource(transaction, document); transact.commit(transaction); if(LOG.isDebugEnabled()) LOG.debug("Successfully retrieved token"); return lockToken; } catch (EXistException e) { LOG.error(e); throw e; } catch (PermissionDeniedException e) { LOG.error(e); throw e; } finally { // TODO: check if can be done earlier if (document != null) { document.getUpdateLock().release(Lock.WRITE_LOCK); } brokerPool.release(broker); if(LOG.isDebugEnabled()) LOG.debug("Finished create lock"); } } }