/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-06 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.http.webdav.methods;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.IndexInfo;
import org.exist.dom.DocumentImpl;
import org.exist.dom.LockToken;
import org.exist.http.webdav.WebDAV;
import org.exist.http.webdav.WebDAVUtil;
import org.exist.security.PermissionDeniedException;
import org.exist.security.User;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.MimeTable;
import org.exist.util.MimeType;
import org.exist.xmldb.XmldbURI;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Implements the WebDAV LOCK method.
*
* @author Dannes Wessels
* @author wolf
*/
public class Lock extends AbstractWebDAVMethod {
private DocumentBuilderFactory docFactory;
public Lock(BrokerPool pool) {
super(pool);
docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
}
private LockToken getDefaultToken(User user){
// Fill in default information
LockToken lockToken = new LockToken();
lockToken.setOwner(user.getName());
lockToken.setType(LockToken.LOCK_TYPE_WRITE);
lockToken.setTimeOut(LockToken.LOCK_TIMEOUT_INFINITE);
lockToken.setScope(LockToken.LOCK_SCOPE_EXCLUSIVE);
lockToken.setDepth(LockToken.LOCK_DEPTH_0);
return lockToken;
}
private void createNullResource(User user,
HttpServletRequest request, HttpServletResponse response,
XmldbURI path){
LOG.debug("Create NullResource for '"+path+"'.");
DBBroker broker =null;
Txn txn =null;
TransactionManager txManager=null;
try {
broker = pool.get(user);
DocumentImpl resource = null;
LockToken lockToken = getDefaultToken(user);
lockToken.createOpaqueLockToken();
String contentType = request.getContentType();
txManager = pool.getTransactionManager();
txn = txManager.beginTransaction();
XmldbURI collName = path.removeLastSegment();
XmldbURI docName = path.lastSegment();
MimeType mime = MimeTable.getInstance().getContentTypeFor(path);
if (mime == null){
mime = MimeType.BINARY_TYPE;
}
LOG.debug("storing document '" + path + "' in collection '"+collName);
Collection collection = null;
try {
collection = broker.openCollection(collName, org.exist.storage.lock.Lock.READ_LOCK);
if(mime.isXMLType()) {
LOG.debug("Storing NULL xml resource");
String txt="<!-- place holder for null byte sized "
+"nullresource XML document --><nullresource/>";
IndexInfo info = collection.validateXMLResource(txn, broker, docName, txt);
//TODO : unlock the collection here ?
resource = info.getDocument();
info.getDocument().getMetadata().setMimeType(contentType);
collection.store(txn, broker, info, txt, false);
} else {
LOG.debug("Storing NULL byte binary resource.");
resource = collection.addBinaryResource(
txn, broker, docName, new byte[0], contentType);
}
} finally {
collection.release(org.exist.storage.lock.Lock.READ_LOCK);
}
resource.setUserLock(user);
lockToken.setResourceType(LockToken.RESOURCE_TYPE_NULL_RESOURCE);
resource.getMetadata().setLockToken(lockToken);
txManager.commit(txn);
try {
lockResource(request, response, lockToken, HttpServletResponse.SC_CREATED);
} catch (ServletException ex) {
LOG.error(ex);
} catch (IOException ex) {
LOG.error(ex);
}
LOG.debug("NullResourceLock done.");
} catch (PermissionDeniedException ex) {
LOG.error(ex);
txManager.abort(txn);
try {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage());
} catch (IOException e) {
LOG.error(e);
}
} catch (Exception ex) {
LOG.error(ex);
txManager.abort(txn);
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
} catch (IOException e) {
LOG.error(e);
}
} finally{
if(pool!=null){
pool.release(broker);
}
}
}
/* (non-Javadoc)
* @see org.exist.http.webdav.WebDAVMethod#process(org.exist.security.User, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.exist.collections.Collection, org.exist.dom.DocumentImpl)
*/
public void process(User user, HttpServletRequest request,
HttpServletResponse response, XmldbURI path)
throws ServletException, IOException {
DBBroker broker = null;
Collection collection = null;
DocumentImpl resource = null;
boolean isNullResource=false;
try {
broker = pool.get(user);
try {
resource = broker.getXMLResource(path, org.exist.storage.lock.Lock.READ_LOCK);
} catch (PermissionDeniedException ex) {
LOG.error(ex);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage());
return;
}
if(resource == null) {
// No document found, maybe a collection
LOG.info("resource==null, document not found.");
collection = broker.openCollection(path, org.exist.storage.lock.Lock.READ_LOCK);
if(collection == null){
LOG.info("collection==null, path does not point to collection");
LOG.debug("Create document as NullResource");
createNullResource(user, request, response, path);
isNullResource=true;
} else {
String txt="Locking on collections not supported yet.";
LOG.debug(txt);
response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, txt);
return;
}
//TODO : release collection lock here ?
} else {
LOG.debug("Aquire lock for existing document");
// TODO get information from webDAV client XML
LockToken lockToken = getDefaultToken(user);
// LockToken lockToken = getLockParameters(request, response);
// if(lockToken==null){
// // Error has been handled,skip test
// LOG.debug("No Locktoken. Stopped Lock request");
// pool.release(broker);
// return;
// }
LOG.debug("Received lock request [" + lockToken.getScope() + "] "
+"for owner " + lockToken.getOwner());
// Get current userlock
User lock = resource.getUserLock();
// Check if Resource is already locked.
if( lock!=null && !lock.getName().equals(user.getName()) ){
LOG.debug("Resource is locked.");
response.sendError(SC_RESOURCE_IS_LOCKED,
"Resource is locked by user "+ user.getName() +".");
return;
}
// Check for request fo shared lock.
if(lockToken.getScope() == LockToken.LOCK_SCOPE_SHARED) {
LOG.debug("Shared locks are not implemented.");
response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
"Shared locks are not implemented.");
return;
}
// Fill new Locktoken with new UUID
lockToken.createOpaqueLockToken();
resource.getMetadata().setLockToken(lockToken);
resource.setUserLock(user);
// Make token persistant
TransactionManager transact = pool.getTransactionManager();
Txn transaction = transact.beginTransaction();
broker.storeXMLResource(transaction, resource);
//TOUNDERSTAND : this lock is released below (in the finally clause)
//isn't it rather a user lock release attempt ?
// ?
resource.getUpdateLock().release(org.exist.storage.lock.Lock.READ_LOCK);
transact.commit(transaction);
LOG.debug("Sucessfully locked '"+path+"'.");
// Write XML response to client
lockResource(request, response, lockToken, HttpServletResponse.SC_OK);
}
} catch (EXistException e) {
LOG.error(e);
throw new ServletException(e);
} finally {
if(isNullResource){
if(resource!=null){
resource.getUpdateLock().release(org.exist.storage.lock.Lock.READ_LOCK);
}
if(collection != null){
collection.release(org.exist.storage.lock.Lock.READ_LOCK);
}
}
if(pool != null){
pool.release(broker);
}
}
}
/**
* Get LOCK information from HttpRequest
*
* @param request Http Object
* @param response Http Object
* @throws ServletException
* @throws IOException
* @return NULL if error is send to response object, or locktoken with
* details about scope, depth and owner
*/
private LockToken getLockParameters(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
LockToken token = new LockToken();
// Parse XML document
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
DocumentBuilder docBuilder;
try {
docBuilder = docFactory.newDocumentBuilder();
} catch (ParserConfigurationException e1) {
LOG.error(e1);
throw new ServletException(WebDAVUtil.XML_CONFIGURATION_ERR, e1);
}
// lockinfo
Document doc = WebDAVUtil.parseRequestContent(request, response, docBuilder);
Element lockinfo = doc.getDocumentElement();
if(!(lockinfo.getLocalName().equals("lockinfo") &&
lockinfo.getNamespaceURI().equals(WebDAV.DAV_NS))) {
LOG.debug(WebDAVUtil.UNEXPECTED_ELEMENT_ERR + lockinfo.getNodeName());
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
WebDAVUtil.UNEXPECTED_ELEMENT_ERR + lockinfo.getNodeName());
return null;
}
Node node = lockinfo.getFirstChild();
while(node != null) {
if(node.getNodeType() == Node.ELEMENT_NODE) {
if(node.getNamespaceURI().equals(WebDAV.DAV_NS)) {
// lockinfo.lockscope
if("lockscope".equals(node.getLocalName())) {
Node scopeNode = WebDAVUtil.firstElementNode(node);
if("exclusive".equals(scopeNode.getLocalName()))
token.setScope(LockToken.LOCK_SCOPE_EXCLUSIVE);
else if("shared".equals(scopeNode.getLocalName()))
token.setScope(LockToken.LOCK_SCOPE_SHARED);;
}
// lockinfo.locktype
if("locktype".equals(node.getLocalName())) {
Node typeNode = WebDAVUtil.firstElementNode(node);
if(!"write".equals(typeNode.getLocalName())) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
WebDAVUtil.UNEXPECTED_ELEMENT_ERR + typeNode.getNodeName());
return null;
}
token.setType(LockToken.LOCK_TYPE_WRITE);
}
// lockinfo.owner
if("owner".equals(node.getLocalName())) {
Node href = WebDAVUtil.firstElementNode(node);
String owner = WebDAVUtil.getElementContent(href);
token.setOwner(owner);
}
}
}
node = node.getNextSibling();
}
return token;
}
// Return Lock Info
private void lockResource(HttpServletRequest request, HttpServletResponse response,
LockToken lockToken, int status) throws ServletException, IOException {
response.setStatus(status);
response.addHeader("Lock-Token", "opaquelocktoken:" + lockToken.getOpaqueLockToken());
response.setContentType(MimeType.XML_CONTENT_TYPE.getName());
// response.setCharacterEncoding("utf-8");
ServletOutputStream sos = response.getOutputStream();
sos.println("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
sos.println("<D:prop xmlns:D=\"DAV:\">");
sos.println("<D:lockdiscovery>");
sos.println("<D:activelock>");
// Lock Type
sos.println("<D:locktype>");
switch( lockToken.getType() ){
case LockToken.LOCK_TYPE_WRITE :
sos.println("<D:write/>");
break;
default:
// This should never be reached
sos.println("<D:write/>");
break;
}
sos.println("</D:locktype>");
// Lockscope
sos.println("<D:lockscope>");
switch( lockToken.getScope() ){
case LockToken.LOCK_SCOPE_EXCLUSIVE :
sos.println("<D:exclusive/>");
break;
case LockToken.LOCK_SCOPE_SHARED :
sos.println("<D:shared/>");
break;
default:
// This should never be reached
sos.println("<D:exclusive/>");
break;
}
sos.println("</D:lockscope>");
// Depth
switch( lockToken.getDepth() ){
case LockToken.LOCK_DEPTH_INFINIY :
sos.println("<D:depth>Infinity</D:depth>");
break;
case LockToken.LOCK_DEPTH_0 :
sos.println("<D:depth>0</D:depth>");
break;
case LockToken.LOCK_DEPTH_1 :
sos.println("<D:depth>1</D:depth>");
break;
case LockToken.LOCK_DEPTH_NOT_SET :
// This should never be reached
sos.println("<D:depth>not set</D:depth>");
break;
default:
// This should never be reached
sos.println("<D:depth>null</D:depth>");
break;
}
// Owner
sos.println("<D:owner>");
sos.println("<D:href>"+lockToken.getOwner()+"</D:href>");
sos.println("</D:owner>");
// Timeout
if( lockToken.getTimeOut() == LockToken.LOCK_TIMEOUT_INFINITE ){
sos.println("<D:timeout>Infinite</D:timeout>");
} else {
sos.println("<D:timeout>Second-"+lockToken.getTimeOut()+"</D:timeout>");
}
// Lock token
sos.println("<D:locktoken>");
sos.println("<D:href>opaquelocktoken:"+lockToken.getOpaqueLockToken()+"</D:href>");
sos.println("</D:locktoken>");
sos.println("</D:activelock>");
sos.println("</D:lockdiscovery>");
sos.println("</D:prop>");
sos.flush();
}
}