/*
* $Header: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/LockMethod.java,v 1.69.2.4 2004/09/20 08:46:02 ozeigermann Exp $
* $Revision: 1.69.2.4 $
* $Date: 2004/09/20 08:46:02 $
*
* ====================================================================
*
* Copyright 1999-2002 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.slide.webdav.method;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import javax.transaction.Transaction;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.NamespaceConfig;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.event.VetoException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.lock.ObjectIsAlreadyLockedException;
import org.apache.slide.lock.ObjectLockedException;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.LinkedObjectNotFoundException;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.transaction.ExternalTransactionContext;
import org.apache.slide.util.XMLValue;
import org.apache.slide.webdav.WebdavException;
import org.apache.slide.webdav.WebdavServletConfig;
import org.apache.slide.webdav.util.PropertyHelper;
import org.apache.slide.webdav.util.WebdavConstants;
import org.apache.slide.webdav.util.WebdavStatus;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.output.XMLOutputter;
/**
* LOCK method.
*
*/
public class LockMethod extends AbstractMultistatusResponseMethod implements
WebdavConstants, WriteMethod {
// -------------------------------------------------------------- Constants
/**
* Create a new lock.
*/
private static final int LOCK_CREATION = 0;
/**
* Refresh lock.
*/
private static final int LOCK_REFRESH = 1;
/**
* Maximum and default timeout.
*/
private static final int MAX_TIMEOUT = Integer.MAX_VALUE;
private static final int DEFAULT_TIMEOUT = MAX_TIMEOUT;
/**
* The default owner if not explicitely specified by the request.
*/
public static final String DEFAULT_LOCK_OWNER = "";
// ----------------------------------------------------- Instance Variables
/**
* Depth.
*/
private int depth;
/**
* Type of the LOCK method ({@link #LOCK_CREATION}or {@link #LOCK_REFRESH}).
*/
private int lockType;
/**
* Lock duration.
*/
private int lockDuration = DEFAULT_TIMEOUT;
/**
* Lock scope.
*/
private String lockInfo_lockScope;
/*
* Lock type.
*/
private String lockInfo_lockType;
/**
* Lock owner.
*/
private String lockInfo_lockOwner;
/**
* Lock subject.
*/
private String lockInfo_lockSubject;
/**
* The PropertyHelper used by this instance.
*/
protected PropertyHelper propertyHelper = null;
// ----------------------------------------------------------- Constructors
/**
* Constructor.
*
* @param token
* the token for accessing the namespace
* @param config
* configuration of the WebDAV servlet
*/
public LockMethod(NamespaceAccessToken token, WebdavServletConfig config) {
super(token, config);
}
// ------------------------------------------------------ Protected Methods
/**
* Parse request.
*
* @exception WebdavException
* Does not happen
*/
protected void parseRequest() throws WebdavException {
propertyHelper = PropertyHelper.getPropertyHelper(slideToken, token,
getConfig());
// readRequestContent();
// Loads the associated object from the store.
lockInfo_lockSubject = requestUri;
if (lockInfo_lockSubject == null) {
lockInfo_lockSubject = "/";
}
depth = requestHeaders.getDepth(INFINITY);
if (depth != 0 && depth != INFINITY) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError(sc, "Invalid header Depth: " + depth);
throw new WebdavException(sc);
}
lockDuration = requestHeaders.getTimeout(MAX_TIMEOUT);
if (req.getContentLength() > 0 || isRequestChunked()) {
parseLockInfo();
} else {
lockType = LOCK_REFRESH;
}
}
/**
* Parses the <code><lockinfo></code> request content document.
*
* @throws WebdavException
* if parsing the request failed or if the request is not valid.
*/
private void parseLockInfo() throws WebdavException {
lockType = LOCK_CREATION;
try {
Iterator childrenIterator = parseRequestContent(E_LOCKINFO)
.getChildren().iterator();
while (childrenIterator.hasNext()) {
Element currentElement = (Element) childrenIterator.next();
if (E_LOCKSCOPE.equals(currentElement.getName())) {
parseLockScope(currentElement);
} else if (E_LOCKTYPE.equals(currentElement.getName())) {
parseLockType(currentElement);
} else if (E_OWNER.equals(currentElement.getName())) {
parseOwner(currentElement);
}
}
} catch (JDOMException e) {
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError(statusCode, e);
throw new WebdavException(statusCode);
} catch (IOException e) {
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError(statusCode, e);
throw new WebdavException(statusCode);
}
}
/**
* Parses the <code><lockscope></code> part of the request content
* document.
*
* @param lockScopeElement
* the <code><lockscope></code> to parse.
*
* @throws JDOMException
* if parsing the request failed or if the request is not valid.
*/
private void parseLockScope(Element lockScopeElement) throws JDOMException {
if (lockScopeElement == null) {
throw new JDOMException("Expected <" + E_LOCKSCOPE + "> element");
}
List children = lockScopeElement.getChildren();
if (children.size() != 1) {
throw new JDOMException("<" + E_LOCKSCOPE
+ "> must have exactly one child element");
}
lockInfo_lockScope = ((Element) children.get(0)).getName();
if (!(E_EXCLUSIVE.equals(lockInfo_lockScope)
|| E_SHARED.equals(lockInfo_lockScope) || E_LOCAL
.equals(lockInfo_lockScope))) {
throw new JDOMException("<" + E_LOCKSCOPE
+ "> can only contain one of <" + E_EXCLUSIVE + "> or <"
+ E_SHARED + "> or <" + E_LOCAL + ">");
}
}
/**
* Parses the <code><locktype></code> part of the request content
* document.
*
* @param lockTypeElement
* the <code><locktype></code> to parse.
*
* @throws JDOMException
* if parsing the request failed or if the request is not valid.
*/
private void parseLockType(Element lockTypeElement) throws JDOMException {
if (lockTypeElement == null) {
throw new JDOMException("Expected <" + E_LOCKTYPE + "> element");
}
List children = lockTypeElement.getChildren();
if (children.size() != 1) {
throw new JDOMException("<" + E_LOCKTYPE
+ "> must have exactly one child element");
}
String lockTypeName = ((Element) children.get(0)).getName();
if (!E_WRITE.equals(lockTypeName)
&& !E_TRANSACTION.equals(lockTypeName)) {
throw new JDOMException(
"Only write and transaction locks are supported");
}
lockInfo_lockType = ((Element) children.get(0)).getName();
}
/**
* Parses the <code><owner></code> part of the request content
* document.
*
* @param ownerElement
* the <code><owner></code> to parse.
*
* @throws JDOMException
* if parsing the request failed or if the request is not valid.
*/
private void parseOwner(Element ownerElement) throws JDOMException {
if (ownerElement == null) {
lockInfo_lockOwner = DEFAULT_LOCK_OWNER;
return;
// throw new JDOMException("Expected <"+E_OWNER+"> element");
}
StringWriter stringWriter = new StringWriter();
XMLOutputter xmlOutputter = new XMLOutputter();
try {
xmlOutputter.outputElementContent(ownerElement, stringWriter);
} catch (IOException e) {
// this should not happen since we do no "real" I/O but
// only print to a PrintWriter
e.printStackTrace();
}
lockInfo_lockOwner = stringWriter.toString();
if (lockInfo_lockOwner.length() == 0) {
lockInfo_lockOwner = DEFAULT_LOCK_OWNER;
//throw new JDOMException("<"+E_OWNER+"> element must not be
// empty");
}
}
/**
* Execute request.
*
* @exception WebdavException
* Unrecoverable error while renewing lock
*/
protected void executeRequest() throws WebdavException {
// Prevent dirty reads
slideToken.setForceStoreEnlistment(true);
SubjectNode toLockSubject = null;
boolean isCollection = isCollection(lockInfo_lockSubject);
boolean inheritance = false;
Date lockDate = null;
switch (lockType) {
case LOCK_CREATION:
if (lockInfo_lockType.equals(E_TRANSACTION)) {
try {
NamespaceConfig namespaceConfig = token
.getNamespaceConfig();
toLockSubject = getToLockSubject();
if (lockDate == null)
lockDate = new Date((new Date()).getTime()
+ ((long) lockDuration * 1000L));
NodeLock lockToken = new NodeLock(toLockSubject.getUri(),((SubjectNode)security.getPrincipal(slideToken)).getUri(),
namespaceConfig.getCreateObjectAction().getUri(), lockDate,
inheritance, NodeLock.LOCAL, lockInfo_lockOwner);
token.setTransactionTimeout(lockDuration * 1000);
Transaction transaction = token.getTransactionManager().suspend();
//String txId = lockToken.getLockId();
String fullTxId = "<" + S_LOCK_TOKEN + lockToken.getLockId() + ">";
ExternalTransactionContext.registerContext(fullTxId, transaction);
slideToken.setExternalTx();
resp.setHeader("Lock-Token", fullTxId);
showLockDiscoveryInfo(lockToken);
} catch (Exception e) {
int statusCode = getErrorCode(e);
sendError(statusCode, e);
throw new WebdavException(statusCode);
}
} else if (lockInfo_lockType.equals(E_WRITE)) {
try {
if (!checkIfHeaders()) {
return;
}
NamespaceConfig namespaceConfig = token
.getNamespaceConfig();
toLockSubject = getToLockSubject();
NodeLock lockToken = null;
inheritance = (depth != 0);
boolean exclusive = !(lockInfo_lockScope.equals(E_SHARED));
if (lockDate == null)
lockDate = new Date((new Date()).getTime()
+ ((long) lockDuration * 1000L));
lockToken = new NodeLock(toLockSubject,
(SubjectNode) security.getPrincipal(slideToken),
namespaceConfig.getCreateObjectAction(), lockDate,
inheritance, exclusive, lockInfo_lockOwner);
lock.lock(slideToken, lockToken);
// Set the lock-token header
// [RFC 2518, 9.5] " The Lock-Token response header is used
// with the LOCK method to indicate the lock token created
// as
// a result of a successful LOCK request to create a new
// lock."
resp.setHeader("Lock-Token", "<" + S_LOCK_TOKEN
+ lockToken.getLockId() + ">");
resp.setStatus(WebdavStatus.SC_OK);
// The lock token on which the DAV module will have the info
showLockDiscoveryInfo(lockToken);
} catch (ObjectIsAlreadyLockedException e) {
if (inheritance
&& generateMultiStatusResponse(isCollection, e,
requestUri)) {
// error is on the resource which we attempted to lock
String errorMessage = generateErrorMessage(e);
// Write it on the servlet writer
resp.setContentType(TEXT_XML_UTF_8);
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
try {
resp.getWriter().write(errorMessage);
} catch (IOException ex) {
// Critical error ... Servlet container is dead or
// something
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError(statusCode, e);
throw new WebdavException(statusCode);
}
} else {
// Returning 207 on non-collection requests is generally
// considered bad. So let's not do it, since this way
// makes clients generally behave better.
resp.setStatus(WebdavStatus.SC_LOCKED);
}
//
// make sure the transaction is aborted
// throw any WebDAV exception to indicate the transaction
// wants to be aborted
//
throw new WebdavException(WebdavStatus.SC_ACCEPTED, false);
} catch (Exception e) {
int statusCode = getErrorCode(e);
sendError(statusCode, e);
throw new WebdavException(statusCode);
}
}
break;
case LOCK_REFRESH:
try {
Enumeration lockTokens = lock.enumerateLocks(slideToken,
lockInfo_lockSubject, false);
NodeLock currentLockToken = null;
Date newExpirationDate = new Date((new Date()).getTime()
+ ((long) lockDuration * 1000L));
while (lockTokens.hasMoreElements()) {
currentLockToken = (NodeLock) lockTokens.nextElement();
lock.renew(slideToken, currentLockToken, newExpirationDate);
}
showLockDiscoveryInfo(currentLockToken);
} catch (SlideException e) {
int statusCode = WebdavStatus.SC_PRECONDITION_FAILED;
sendError(statusCode, e);
throw new WebdavException(statusCode);
}
break;
}
}
protected SubjectNode getToLockSubject() throws ObjectAlreadyExistsException, ObjectNotFoundException,
AccessDeniedException, RevisionAlreadyExistException, LinkedObjectNotFoundException, ObjectLockedException,
ServiceAccessException, VetoException {
SubjectNode toLockSubject;
try {
toLockSubject = (SubjectNode) structure.retrieve(slideToken, lockInfo_lockSubject);
} catch (ObjectNotFoundException ex) {
// Creating a lock null resource
toLockSubject = new SubjectNode();
// Creating new subject
structure.create(slideToken, toLockSubject, lockInfo_lockSubject);
NodeRevisionDescriptor revisionDescriptor = new NodeRevisionDescriptor(0);
// Resource type
XMLValue lockNull = new XMLValue(new Element(E_LOCKNULL, DNSP));
revisionDescriptor.setResourceType(lockNull.toString());
NodeRevisionContent nrc = new NodeRevisionContent();
nrc.setContent(new byte[0]);
// Creating the revision descriptor
content.create(slideToken, lockInfo_lockSubject, revisionDescriptor, nrc);
}
return toLockSubject;
}
/**
* Get return status based on exception type.
*/
protected int getErrorCode(Exception ex) {
try {
throw ex;
} catch (ObjectNotFoundException e) {
return WebdavStatus.SC_PRECONDITION_FAILED;
} catch (Exception e) {
return super.getErrorCode(e);
}
}
/**
* Show lockdiscovery info.
*
* @exception WebdavException
* Something is wrong with the servlet container
*/
protected void showLockDiscoveryInfo(NodeLock token) throws WebdavException {
// Generating XML response
org.jdom.Element prop = new org.jdom.Element(E_PROP, DNSP);
org.jdom.Element lockdiscovery = new org.jdom.Element(E_LOCKDISCOVERY,
DNSP);
prop.addContent(lockdiscovery);
XMLValue xmlValue = propertyHelper.computeLockDiscovery(token,
getSlideContextPath());
Iterator iterator = xmlValue.iterator();
while (iterator.hasNext()) {
lockdiscovery.addContent((org.jdom.Element) iterator.next());
}
try {
//System.out.println("Query result");
//System.out.println(generatedXML.toString());
resp.setContentType(TEXT_XML_UTF_8);
Writer writer = resp.getWriter();
org.jdom.output.Format format = org.jdom.output.Format
.getPrettyFormat();
format.setIndent(XML_RESPONSE_INDENT);
new org.jdom.output.XMLOutputter(format).output(
new org.jdom.Document(prop), writer);
writer.flush();
} catch (Exception e) {
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError(statusCode, e);
throw new WebdavException(statusCode);
}
}
private boolean checkIfHeaders() throws AccessDeniedException, LinkedObjectNotFoundException, ServiceAccessException, ObjectLockedException, VetoException, IOException
{
try {
NodeRevisionDescriptors revisionDescriptors =
content.retrieve(slideToken, this.requestUri);
// Retrieve latest revision descriptor
NodeRevisionDescriptor revisionDescriptor =
content.retrieve(slideToken, revisionDescriptors);
if (revisionDescriptor != null) {
ResourceInfo resourceInfo =
new ResourceInfo(this.requestUri, revisionDescriptor);
return checkIfHeaders(req, resp, resourceInfo);
} else {
return checkIfHeaders(req, resp, new ResourceInfo(this.requestUri));
}
}
catch (RevisionDescriptorNotFoundException e) {
return checkIfHeaders(req, resp, new ResourceInfo(this.requestUri));
}
catch (ObjectNotFoundException e) {
return checkIfHeaders(req, resp, new ResourceInfo(this.requestUri));
}
}
}