AbstractWebdavMethod.java
| Index Score | ||
|---|---|---|
![]() |
![]() |
org.apache.slide.webdav.method |
![]() |
![]() |
Jakarta Slide |
View: Reasons, Metrics, Source Code
These are the metrics that contribute to the Enerjy Score for this file, ranked by impact. So the metrics listed at the top influence the score to a greater extent that the metrics listed at the bottom.
/*
* $Header$
* $Revision: 529868 $
* $Date: 2007-04-18 01:18:19 -0400 (Wed, 18 Apr 2007) $
*
* ====================================================================
*
* 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.PrintWriter;
import java.io.StringWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.apache.commons.transaction.locking.GenericLock;
import org.apache.commons.transaction.locking.GenericLockManager;
import org.apache.commons.transaction.locking.LockManager;
import org.apache.commons.transaction.locking.MultiLevelLock;
import org.apache.commons.transaction.util.PrintWriterLogger;
import org.apache.slide.authenticate.CredentialsToken;
import org.apache.slide.common.Domain;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.NestedSlideException;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.common.SlideToken;
import org.apache.slide.content.Content;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.NodeProperty.NamespaceCache;
import org.apache.slide.lock.Lock;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.lock.ObjectLockedException;
import org.apache.slide.macro.ConflictException;
import org.apache.slide.macro.Macro;
import org.apache.slide.search.Search;
import org.apache.slide.security.Security;
import org.apache.slide.store.ConcurrencyConflictError;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.Structure;
import org.apache.slide.transaction.ExternalTransactionContext;
import org.apache.slide.util.Configuration;
import org.apache.slide.util.Messages;
import org.apache.slide.util.XMLValue;
import org.apache.slide.util.logger.Logger;
import org.apache.slide.webdav.WebdavException;
import org.apache.slide.webdav.WebdavMethod;
import org.apache.slide.webdav.WebdavServletConfig;
import org.apache.slide.webdav.util.BindConstants;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.NotificationConstants;
import org.apache.slide.webdav.util.PreconditionViolationException;
import org.apache.slide.webdav.util.TransactionConstants;
import org.apache.slide.webdav.util.UnlockListenerImpl;
import org.apache.slide.webdav.util.UriHandler;
import org.apache.slide.webdav.util.VersioningHelper;
import org.apache.slide.webdav.util.ViolatedPrecondition;
import org.apache.slide.webdav.util.WebdavConstants;
import org.apache.slide.webdav.util.WebdavStatus;
import org.apache.slide.webdav.util.WebdavUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
/**
* WebDAV method.
*
*/
public abstract class AbstractWebdavMethod implements WebdavMethod, WebdavConstants,
DeltavConstants, BindConstants, NotificationConstants, TransactionConstants {
// -------------------------------------------------------------- Constants
/**
* String constant for <code>no-cache</code>.
*/
protected static final String NO_CACHE = "no-cache";
/**
* String constant for <code>private</code>.
*/
protected static final String PRIVATE_CACHE = "private";
/**
* String constant for <code>http://</code>.
*/
public static final String HTTP_PROTOCOL = "http://";
/**
* String constant for <code>HTTP/1.1</code>.
*/
public static final String HTTP_VERSION = "HTTP/1.1";
/**
* String constant for <code>text/xml</code>.
*/
public static final String TEXT_XML = "text/xml";
/**
* String constant for <code>text/xml; charset="UTF-8"</code>.
*/
public static final String TEXT_XML_UTF_8 = "text/xml; charset=UTF-8";
/**
* The indent to use in the XML response.
*/
public static final String XML_RESPONSE_INDENT = " ";
private static final String LOG_CHANNEL =
AbstractWebdavMethod.class.getName();
private static final String LOG_CHANNEL_LOCKING = LOG_CHANNEL + ".locking";
// public static final String PRINCIPAL =
// "org.apache.slide.webdav.method.principal";
public static final int INFINITY = Integer.MAX_VALUE;
protected static final Namespace DNSP = NamespaceCache.DEFAULT_NAMESPACE;
/**
* The set of SimpleDateFormat formats to use in getDateHeader().
*/
protected static final SimpleDateFormat formats[] = {
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
};
private static final int LOCK_LOG_LEVEL = Logger.DEBUG;
protected static final int READ_LOCK = 1;
protected static final int WRITE_LOCK = 2;
private static final int SHARED_LOCK = 1;
private static final int EXCLUSIVE_LOCK = 2;
// global read/write lock to allow for deadlock-free access
private static final MultiLevelLock GLOBAL_LOCK = new GenericLock("global", WRITE_LOCK,
new PrintWriterLogger(new PrintWriter(System.out), LOG_CHANNEL, false));
private static final LockManager FINE_LOCK_MANAGER = new GenericLockManager(WRITE_LOCK,
new PrintWriterLogger(new PrintWriter(System.out), LOG_CHANNEL, false));
private static final GenericLock GLOBAL_READ_LOCK = new GenericLock("GLOBAL READ", EXCLUSIVE_LOCK,
new PrintWriterLogger(new PrintWriter(System.out), LOG_CHANNEL, false));
private static final GenericLock GLOBAL_WRITE_LOCK = new GenericLock("GLOBAL WRITE", EXCLUSIVE_LOCK,
new PrintWriterLogger(new PrintWriter(System.out), LOG_CHANNEL, false));
private static final int DEFAULT_MAX_RETRY_REPEATS = 1;
// ----------------------------------------------------- Instance Variables
/**
* Requested Slide-Uri.
*/
protected String requestUri;
/**
* Servlet request.
*/
protected HttpServletRequest req;
/**
* Servlet response.
*/
protected HttpServletResponse resp;
/**
* Configuration.
*/
protected WebdavServletConfig config;
protected String slideContextPath;
/**
* Request body.
*/
protected String requestBody;
/**
* Namespace access token.
*/
protected NamespaceAccessToken token;
/**
* Structure helper.
*/
protected Structure structure;
/**
* Content helper.
*/
protected Content content;
/**
* Security helper.
*/
protected Security security;
/**
* Lock helper.
*/
protected Lock lock;
/** wam
* Search helper.
*/
protected Search search;
/**
* Macro helper.
*/
protected Macro macro;
/**
* Versioning helper.
*/
protected VersioningHelper versioningHelper;
/**
* Slide token.
*/
protected SlideToken slideToken;
/**
* Fine grain locks acquired by this method.
*/
protected Set locks = new HashSet();
/**
* The request content (XML) Document.
*/
private Document requestContentDocument = null;
/**
* Indicates if the request content has already been parsed.
*/
private boolean isRequestContentParsed = false;
/**
* Request headers
*/
protected RequestHeaders requestHeaders = new RequestHeaders();
/**
* Indicates if the request will be repeated upon concurrency failure.
*/
protected boolean retryUponConflict;
// -------------------------------------------------- Static Initialization
// ----------------------------------------------------------- Constructors
/**
* Constructor.
*
* @param token the token for accessing the namespace
* @param config configuration of the WebDAV servlet
*/
public AbstractWebdavMethod(NamespaceAccessToken token,
WebdavServletConfig config) {
this.config = config;
this.token = token;
// initialize helpers
structure = token.getStructureHelper();
content = token.getContentHelper();
security = token.getSecurityHelper();
lock = token.getLockHelper();
macro = token.getMacroHelper();
}
// -------------------------------------------- WebdavMethod Implementation
/**
* Exceute method.
*
* @exception WebdavException
*/
public void run(HttpServletRequest req, HttpServletResponse resp)
throws WebdavException {
// XXX this is a pretty ugly spot and way to set this
// TODO find a better solution
UriHandler.setGloballyUseHistoryCollectionHack(useHistoryCollectionHack());
this.req = req;
this.resp = resp;
this.slideToken = WebdavUtils.getSlideToken(req);
String forceLowercaseLogin = token.getNamespaceConfig().getParameter("force-lowercase-login");
if ("true".equals(forceLowercaseLogin)) {
String name = slideToken.getCredentialsToken().getPrincipal().getName().toLowerCase();
slideToken.setCredentialsToken(new CredentialsToken(name));
}
this.requestUri = WebdavUtils.getRelativePath(req, config);
this.slideContextPath = req.getContextPath();
if (!this.config.isDefaultServlet()) {
this.slideContextPath += req.getServletPath();
}
// TODO this is a workaround to pass the slideContextPath to the search
// implementation
slideToken.addParameter("slideContextPath", this.slideContextPath);
this.versioningHelper = VersioningHelper.getVersioningHelper(
slideToken, token, req, resp, getConfig(), this);
parseRequestHeaders();
boolean transactionIsStarted = false;
boolean globalLockObtained = false;
String txId = null;
try {
parseRequest();
ExternalTransactionContext externalTransaction = null;
txId = requestHeaders.getTxId();
if (txId != null) {
externalTransaction = ExternalTransactionContext.lookupContext(txId);
if (externalTransaction != null) {
Domain.log("Using external transaction " + txId, LOG_CHANNEL, Logger.INFO);
slideToken.setExternalTx();
// pure reads must be guaranteed to be inside transaction as well
slideToken.setForceStoreEnlistment(true);
Transaction tx = externalTransaction.getTransaction();
token.getTransactionManager().resume(tx);
transactionIsStarted = true;
}
}
if (!slideToken.isExternalTransaction()) {
token.begin();
transactionIsStarted = true;
if (txForAllRequests()) {
slideToken.setForceStoreEnlistment(true);
}
assureGlobalLocks();
globalLockObtained = true;
}
boolean done = false;
retryUponConflict = isRepeatUponConflict() && !slideToken.isExternalTransaction();
int retries = getMaxRetryRepeats();
ConcurrencyConflictError finalCce = null;
while (!done && retries != 0) {
try {
executeRedirect();
done = true;
} catch (ConcurrencyConflictError cce) {
finalCce = cce;
if (retryUponConflict) {
retries--;
token.getLogger().log(cce.getMessage(), LOG_CHANNEL, Logger.WARNING);
token.rollback();
token.begin();
} else {
break;
}
}
}
if (!done) {
// rethrow it as an ordinary conflict exception
throw new ConflictException(finalCce.getUri(), finalCce);
}
if (!slideToken.isExternalTransaction() && transactionIsStarted) {
token.commit();
transactionIsStarted = false;
}
} catch (WebdavException ex) {
// Ignore the WebDav Exception and assume that the response code
// is already set.
} catch (SlideException ex) {
int statusCode = getErrorCode( ex );
if (statusCode == WebdavStatus.SC_UNAUTHORIZED) {
// This means the user is required to authenticate (correctly),
// so add the specific header and invalidate session to force
// new authentication
String authentication = config.getAuthenticationHeader();
resp.setHeader("WWW-Authenticate", authentication);
req.getSession().invalidate();
}
sendError( statusCode, ex );
// do not throw exception as the response code has already been set,
// otherwise the servlet will log this as an error and issue a stack trace
// throw new WebdavException( statusCode );
} catch (Exception ex) {
token.getLogger().log(ex,LOG_CHANNEL,Logger.ERROR);
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError( statusCode, ex );
throw new WebdavException( statusCode );
} finally {
try {
if (!slideToken.isExternalTransaction() && transactionIsStarted) {
// Something went wrong, we are here and the TA is still
// open
try {
token.rollback();
} catch (Exception e) {
token.getLogger().log(e,LOG_CHANNEL,Logger.ERROR);
}
}
if (slideToken.isExternalTransaction()) {
Transaction transaction;
try {
if (token.getStatus() == javax.transaction.Status.STATUS_ACTIVE) {
transaction = token.getTransactionManager().suspend();
if (transaction != null) {
ExternalTransactionContext.registerContext(txId, transaction);
}
}
} catch (SystemException e) {
token.getLogger().log(e,LOG_CHANNEL,Logger.ERROR);
}
}
} finally {
if (globalLockObtained) {
releaseGlobalLocks();
}
}
}
}
protected void executeRedirect() throws IOException, SlideException, IllegalStateException,
SecurityException, HeuristicMixedException, HeuristicRollbackException,
RollbackException, NotSupportedException, SystemException {
/*
* Check for object existence and cleanup locks but only if this is
* not a request to finalize an external transaction. Otherwise we are
* making calls to the store that require a transaction to be in process
* while we're trying to commit or abort the current transaction.
*/
if (!isEndofTransactionRequest()) {
try {
// retrive to check it exists, otherwise it can't have locks
structure.retrieve(slideToken, requestUri);
// clear expired lock-tokens
UnlockListenerImpl listener = new UnlockListenerImpl(slideToken, token, versioningHelper);
lock.clearExpiredLocks(slideToken, requestUri, listener);
if (listener.getUnlockCount() > 0) {
// If we have have cleared any lock or any lock-null resource in
// the previous step we commit this changes, otherwise they will
// be lost if executeRequest() exits with an exception (e.g.
// because of Not Found 404)
token.commit();
token.begin();
}
} catch (ObjectNotFoundException e) {
// ignore, it can't have locks
}
}
boolean responseIsRedirected = false;
/*
* Check whether to apply to redirect ref but only if this is not an
* external transaction request to commit or abort the current transaction.
* Otherwise db deadlock can occur.
*/
if (!requestHeaders.getApplyToRedirectRef(false)
&& !isEndofTransactionRequest()) {
try {
NodeRevisionDescriptors revisionDescriptors
= content.retrieve(slideToken, requestUri);
NodeRevisionDescriptor revisionDescriptor
= content.retrieve(slideToken, revisionDescriptors);
if (WebdavUtils.isRedirectref(revisionDescriptor)) {
NodeProperty refTarget
= revisionDescriptor.getProperty(PN_REFTARGET);
// Add the header indicating that this redirect is the
// result of a redirect reference.
resp.addHeader(H_REDIRECT_REF,
refTarget.getValue().toString());
// Set the location header for the redirect.
resp.addHeader("Location",
refTarget.getValue().toString());
// Determine the appropriate status code.
boolean permanent
= revisionDescriptor.propertyValueContains(
P_REDIRECT_LIFETIME, "permanent");
if (permanent) {
resp.setStatus(WebdavStatus.SC_MOVED_PERMANENTLY);
resp.addHeader(H_CACHE_CONTROL, PRIVATE_CACHE);
}
else {
resp.setStatus(WebdavStatus.SC_MOVED_TEMPORARILY);
resp.addHeader(H_CACHE_CONTROL, NO_CACHE);
}
// Set this flag so that we don't attempt to execute the
// request on the redirect reference itself.
responseIsRedirected = true;
}
}
catch (ObjectNotFoundException notFound) {
// TODO is this really an error?
// token.getLogger().log("Error while trying to send redirect",
// notFound, LOG_CHANNEL, Logger.ERROR);
}
catch (RevisionDescriptorNotFoundException e) {
// TODO not sure, but this makes VcPutVHR testcase passing
}
catch (ObjectLockedException e) {
// TODO not sure, but this makes nonOwnerUsesLocktoken testcase passing
}
}
if (!responseIsRedirected) {
executeRequest();
}
}
// --------------------------------------------------------- Public Methods
/**
* Returns a boolean indicating whether this request is an
* external transaction request to commit or abort the
* current transaction. These requests must be given a
* short path to UnlockMethod.execute() that avoids db
* interaction -- even reads.
*
* @return true if the current request is an external transaction commit or abort request.
*/
public boolean isEndofTransactionRequest()
{
/*
* if this is an UnlockMethod and has a transaction
* command set in the request then this request is
* trying to commit or abort an external transaction.
*/
if (this instanceof UnlockMethod)
{
UnlockMethod meth = (UnlockMethod)this;
if (meth.getCommand() != UnlockMethod.NO_TRANSACTION)
return true;
}
return false;
}
/**
* Returns the configuration of the WebdavServlet.
*
* @return WebdavServletConfig
*/
public WebdavServletConfig getConfig() {
return config;
}
/**
* Return an absolute URL path (absolute in the HTTP sense) based on a Slide
* path.
*/
public String getFullPath(String slidePath) {
return WebdavUtils.getAbsolutePath(slidePath, req, getConfig());
}
/**
* Returns a Slide path based on an absolute URL
* (absolute in the HTTP sense)
*/
public String getSlidePath(String fullpath) {
return WebdavUtils.getSlidePath(fullpath, getSlideContextPath());
}
public String getSlideContextPath() {
return this.slideContextPath;
}
/**
* Returns the slide uri requested.
*/
public String getRequestUri() {
return this.requestUri;
}
public SlideToken getSlideToken() {
return this.slideToken;
}
public NamespaceAccessToken getNamespaceAccessToken() {
return this.token;
}
public Content getContent() {
return content;
}
public Lock getLock() {
return lock;
}
public Macro getMacro() {
return macro;
}
public Search getSearch() {
return search;
}
public Security getSecurity() {
return security;
}
public Structure getStructure() {
return structure;
}
public VersioningHelper getVersioning() {
return versioningHelper;
}
public HttpServletRequest getRequest() {
return this.req;
}
public HttpServletResponse getResponse() {
return this.resp;
}
// ------------------------------------------------------ Protected Methods
/**
* Read request contents.
*
* @param req Request object handed out by the servlet container
* @return char[] Array of char which contains the body of the request
*/
protected void readRequestContent() {
if (req.getContentLength() == 0)
return;
// TODO : Modify this and make it chunking aware
try {
requestBody = new String(NodeRevisionContent.read(req.getInputStream()),
getEncodingString(req.getCharacterEncoding()));
}
catch (Exception e) {
token.getLogger().log(e,LOG_CHANNEL,Logger.ERROR);
}
}
/**
* Translate the encoding string into a proper Java encoding String.
*/
public static String getEncodingString(String httpEncoding) {
String result = httpEncoding;
if (result == null) result = System.getProperty("file.encoding");
if (result.startsWith("\"")) result = result.substring(1, result.length());
if (result.endsWith("\"")) result = result.substring(0, result.length()-1);
return result;
}
/**
* Method parseHeaders
*
*/
private void parseRequestHeaders() throws WebdavException {
requestHeaders.parse();
}
/**
* Test if a resource given by a path is a collection
*/
protected boolean isCollection(String path) {
return WebdavUtils.isCollection(token, slideToken, path);
}
/**
* Test whether the resource given by lowerNode is a descendant of the
* resource given by upperNode
*
* @param lowerNode an ObjectNode
* @param upperNode an ObjectNode
*
* @return true, if lowerNode is below upperNode in the namespace
* @throws ServiceAccessException
*
*/
protected boolean isDescendant( ObjectNode lowerNode, ObjectNode upperNode ) throws ServiceAccessException {
if (lowerNode.getUuri().equals(upperNode.getUuri())) {
return true;
}
if (upperNode.hasBinding(lowerNode)) {
return true;
}
NodeRevisionDescriptors lowerNrds = null;
NodeRevisionDescriptor lowerNrd = null;
try {
lowerNrds = content.retrieve(slideToken, lowerNode.getUri());
lowerNrd = content.retrieve(slideToken, lowerNrds);
NodeProperty psProp = lowerNrd.getProperty(PN_PARENT_SET);
// FIXME psProp may be null
XMLValue xv = new XMLValue( String.valueOf(psProp.getValue()) );
Iterator i = xv.getList().iterator();
while (i.hasNext()) {
// iterate over parent elements
Element pElm = (Element)i.next();
String hrPath = pElm.getChild( E_HREF, DNSP ).getText();
ObjectNode hrNode = structure.retrieve( slideToken, hrPath );
return isDescendant( hrNode, upperNode );
}
} catch (ServiceAccessException e) {
throw e;
} catch (Exception e) {}
return false;
}
protected boolean isRequestChunked() {
String te = req.getHeader("Transfer-Encoding");
if (te == null) return false;
return te.indexOf("chunked") != -1;
}
/**
* Parse WebDAV XML query.
*
* @exception WebdavException
*/
protected abstract void parseRequest()
throws WebdavException;
/**
* Returns the request content (XML) Document.
*
* @return the request content (XML) Document.
*/
protected Document getRequestContent() {
return requestContentDocument;
}
//--
/**
* precondition: sourceUri != null
*/
protected String parseUri(String uri) throws WebdavException { // TODO: better name
int protocolIndex = uri.indexOf("://");
if (protocolIndex >= 0) {
// if the Destination URL contains the protocol, we can safely
// trim everything upto the first "/" character after "://"
int firstSeparator =
uri.indexOf("/", protocolIndex + 4);
if (firstSeparator < 0) {
uri = "/";
} else {
uri = uri.substring(firstSeparator);
}
} else {
String hostName = req.getServerName();
if ((hostName != null) && (uri.startsWith(hostName))) {
uri = uri.substring(hostName.length());
}
int portIndex = uri.indexOf(":");
if (portIndex >= 0) {
uri = uri.substring(portIndex);
}
if (uri.startsWith(":")) {
int firstSeparator = uri.indexOf("/");
if (firstSeparator < 0) {
uri = "/";
} else {
uri = uri.substring(firstSeparator);
}
}
}
// headers are "ISO-8859-1" encoded [not any more with TC 4.1.18
uri = WebdavUtils.normalizeURL(WebdavUtils.fixTomcatURL(uri));
String contextPath = req.getContextPath();
if ((contextPath != null) && (uri.startsWith(contextPath))) {
uri = uri.substring(contextPath.length());
}
String pathInfo = req.getPathInfo();
if (pathInfo != null) {
String servletPath = req.getServletPath();
if ((servletPath != null) && (uri.startsWith(servletPath))) {
uri = uri.substring(servletPath.length());
}
}
uri = getConfig().getScope() + uri;
return uri;
}
protected Element parseRequestContent(String rootName) throws JDOMException, IOException {
Document document;
Element root;
document = parseRequestContent();
if (document == null) {
throw new JDOMException("Request content missing");
}
root = document.getRootElement();
if( root == null || !root.getName().equals(rootName) ) {
Domain.warn( "Root element must be "+rootName );
throw new JDOMException("Root element must be <"+rootName+">");
}
return root;
}
/**
* Parses the request content (XML) Document.
*
* @return the request content (XML) Document.
*
* @throws IOException if an I/O error occurred.
* @throws JDOMException if parsing the document failed.
*/
protected Document parseRequestContent() throws JDOMException, IOException {
if (isRequestContentParsed) {
return requestContentDocument;
}
if (req.getContentLength() == 0 || req.getContentLength() == -1) {
return requestContentDocument;
}
try {
requestContentDocument = new SAXBuilder().build(req.getInputStream());
isRequestContentParsed = true;
}
catch (JDOMException e) {
if (e.getCause() instanceof IOException) {
throw (IOException)e.getCause();
}
else {
throw e;
}
}
return requestContentDocument;
}
/**
* Generate XML response.
*
* @exception WebdavException
*/
protected abstract void executeRequest()
throws WebdavException, IOException;
/**
* Simulate MS IIS5 ?
*
* @return boolean
*/
protected boolean isMsProprietarySupport() {
return (token.getNamespaceConfig().getParameter("ms") != null);
}
/**
* Sends a precondition vilolation response.
*
* @param pve the ProconditionViolationException that describes the violated
* precondition.
*/
protected void sendPreconditionViolation(PreconditionViolationException pve) throws IOException {
if (pve != null) {
ViolatedPrecondition violatedPrecondition = pve.getViolatedPrecondition();
int statusCode = violatedPrecondition.getStatusCode();
printStackTrace( pve, statusCode );
String statusText = WebdavStatus.getStatusText(statusCode);
if (violatedPrecondition.getExplanation() != null) {
statusText = statusText+": "+violatedPrecondition.getExplanation();
}
resp.setStatus(statusCode, statusText);
resp.setContentType(TEXT_XML_UTF_8);
org.jdom.output.Format format = org.jdom.output.Format.getPrettyFormat();
format.setIndent(XML_RESPONSE_INDENT);
new XMLOutputter(format).
output(new Document(MethodUtil.getPreconditionViolationError(pve.getViolatedPrecondition())), resp.getWriter());
}
}
// -------------------------------------------------------- Private Methods
/**
* Get return status based on exception type.
*/
protected int getErrorCode(Throwable ex) {
return WebdavUtils.getErrorCode(ex);
}
/**
* Get return status based on exception type.
*/
protected int getErrorCode(SlideException ex) {
return WebdavUtils.getErrorCode(ex);
}
/**
* Get return status based on exception type.
*/
protected int getErrorCode(ServiceAccessException ex) {
return WebdavUtils.getErrorCode(ex);
}
/**
* Returns the value of a boolean init parameter of the servlet.
* Default value: false.
*/
protected boolean getBooleanInitParameter( String name ) {
return "true".equalsIgnoreCase( getConfig().getInitParameter(name) );
}
protected boolean isAutoCreateUsers() {
return ("true".equalsIgnoreCase(token.getNamespaceConfig().getParameter("auto-create-users")));
}
protected String getUsersPath() {
return token.getNamespaceConfig().getUsersPath();
}
protected String getRolesPath() {
return token.getNamespaceConfig().getRolesPath();
}
protected String getHistoryPath() {
return Domain.getParameter(I_HISTORYPATH, I_HISTORYPATH_DEFAULT);
}
/**
* Checks whether the hack that restricts the size of collections in the
* history collection is configured to be used.
*/
protected boolean useHistoryCollectionHack() {
return "true".equalsIgnoreCase(token.getNamespaceConfig().getParameter("history-collection-hack"));
}
/**
* Checks whether all requests shall be done inside of transactions.
*/
protected boolean txForAllRequests() {
return "true".equalsIgnoreCase(token.getNamespaceConfig().getParameter("all-methods-in-transactions"));
}
/**
* Checks if Slide is configured to allow at most a single write request at a time.
* @return <code>true</code> if there can be at most one write request at a time
*/
protected boolean isSequentialWrite() {
String sm = token.getNamespaceConfig().getParameter("sequential-mode");
return ("write".equalsIgnoreCase(sm) || "full".equalsIgnoreCase(sm));
}
/**
* Checks if Slide is configured to allow reads while write requests are being executed.
* @return <code>true</code> if reads are disallowed during writes
*/
protected boolean isSequentialRead() {
return "full".equalsIgnoreCase(token.getNamespaceConfig().getParameter("sequential-mode"));
}
/**
* Checks if Slide is configured to restrict access using fine grained
* locks.
*
* @return <code>true</code> if Slide is configured to lock in a fain
* grained way
*/
protected boolean isFineGrainSequential() {
return "fine-grain".equalsIgnoreCase(token.getNamespaceConfig().getParameter(
"sequential-mode"));
}
protected boolean isFailFastSequential() {
return "fail-fast".equalsIgnoreCase(token.getNamespaceConfig().getParameter(
"sequential-mode"));
}
/**
* Checks if Slide is configured to internally repeat a request if it conflicts with other
* concurrent ones.
*/
protected boolean isRepeatUponConflict() {
return "true".equalsIgnoreCase(token.getNamespaceConfig().getParameter(
"repeat-upon-conflict"));
}
/**
* Checks if Slide is configured to internally repeat a request if it conflicts with other
* concurrent ones.
*/
protected int getMaxRetryRepeats() {
String maxRepeat = token.getNamespaceConfig().getParameter("max-retry-repeat");
if (maxRepeat != null) {
try {
int repeats = Integer.parseInt(maxRepeat);
return repeats;
} catch (NumberFormatException nfe) {
token.getLogger().log("max-retry-repeat must be an integer, using default",
LOG_CHANNEL_LOCKING, Logger.WARNING);
}
}
return DEFAULT_MAX_RETRY_REPEATS;
}
protected void assureGlobalLocks() throws ConflictException {
if (isFailFastSequential()) {
boolean locked = true;
if (this instanceof ReadMethod) {
try {
// NON-BLOCKING
locked = GLOBAL_LOCK.acquire(this, 1, false, true, Long.MAX_VALUE);
} catch (InterruptedException e) {
}
} else if (this instanceof WriteMethod) {
try {
// NON-BLOCKING
locked = GLOBAL_LOCK.acquire(this, 2, false, true, Long.MAX_VALUE);
} catch (InterruptedException e) {
}
}
if (!locked)
throw new ConflictException("/");
} else if (this instanceof FineGrainedLockingMethod && isFineGrainSequential()) {
((FineGrainedLockingMethod) this).acquireFineGrainLocks();
} else if (this instanceof ReadMethod) {
if (isSequentialRead()) {
try {
GLOBAL_LOCK.acquire(this, 1, true, true, Long.MAX_VALUE);
} catch (InterruptedException e) {
}
}
} else if (this instanceof WriteMethod) {
if (isSequentialWrite()) {
try {
GLOBAL_LOCK.acquire(this, 2, true, true, Long.MAX_VALUE);
} catch (InterruptedException e) {
}
}
}
}
protected void releaseGlobalLocks() {
GLOBAL_LOCK.release(this);
if (this instanceof FineGrainedLockingMethod && isFineGrainSequential()) {
// no need to do this atomically
releaseLocks();
}
}
protected void acquireHistoryLocks(String path) {
// some writing request have an additional request to the history
// folder if auto versioning is turned on
if (Configuration.useVersionControl() && isAutoVersionControl(path)
&& !isExcludedForVersionControl(path)) {
acquireLock(getHistoryPath(), this instanceof WriteMethod ? WRITE_LOCK : READ_LOCK);
}
}
/**
* Acquires a global read lock to disallow any writing.
* Should be used by methods that do not allow any writing while running.
*/
protected void acquireGlobalReadLock() {
// we have a shared global read lock which means we are compatible with
// any other read
acquireLock(GLOBAL_READ_LOCK, SHARED_LOCK);
// this means no other write request may run in parallel, but if another global read
// has the lock, we will *not* be blocked
blockWithLock(GLOBAL_WRITE_LOCK, EXCLUSIVE_LOCK);
}
/**
* Acquires a global write lock to disallow any concurrent access.
*/
protected void acquireGlobalWriteLock() {
// we really want to read and write alone:
acquireLock(GLOBAL_READ_LOCK, EXCLUSIVE_LOCK);
acquireLock(GLOBAL_WRITE_LOCK, EXCLUSIVE_LOCK);
}
/**
* Inhibits a global read lock. Should be called by all fine grain writing methods.
*/
protected void inhibitGlobalReadLock() {
// we have a shared global write lock which means other fine grained write
// methods are compatible
acquireLock(GLOBAL_WRITE_LOCK, SHARED_LOCK);
// do not allow global read as long as we write to any resource, but
// if another global write has the lock, we will *not* be blocked
blockWithLock(GLOBAL_READ_LOCK, EXCLUSIVE_LOCK);
}
/**
* Acquires the standard locks applicable to most methods. Those are
* <ol>
* <li>READ locks for the accessed path and all ancestors on all levels up
* to the root
* <li>recursive READ locks to the users and roles folder and an additional
* write lock to the users folder if auto creation of users is enabled
* <li>upgrade requests to descendants of the history folder to the full
* history folder, effectively making all requests to the history folder
* global
* <li>assures no writing requests run concurrently with a search request
* as it is unpredictable which resources a search will touch
* </ol>
*
* @param path
* the path to acquire standard locks for
*/
protected void acquireStandardLocks(String path) {
// (1) all requests need read access from uri to root
List paths = getPathsToRoot(path);
acquireLock(paths, READ_LOCK);
// (2) all requests need read access to users and roles and even write
// when users are auto created
String usersPath = getUsersPath();
String rolesPath = getRolesPath();
acquireLock(usersPath, isAutoCreateUsers() ? WRITE_LOCK : READ_LOCK);
acquireLock(rolesPath, READ_LOCK);
// (2a) adapt explicite calls to users and roles to general lock
// above
// this effectively gives a *single* lock for the users and roles folder
// each
if (path.startsWith(getUsersPath())) {
acquireLock(usersPath, this instanceof WriteMethod ? WRITE_LOCK : READ_LOCK);
} else if (path.startsWith(rolesPath)) {
acquireLock(rolesPath, this instanceof WriteMethod ? WRITE_LOCK : READ_LOCK);
}
// (3) adapt explicite read to history folder to general lock
// above
// this effectively gives a *single* lock for history folder
if (Configuration.useVersionControl() && path.startsWith(getHistoryPath())) {
acquireLock(getHistoryPath(), READ_LOCK);
}
// (4) be sure to write only when no search or other global read currently takes place
// this is the counter part to acquireSearchLock()
if (this instanceof WriteMethod) {
inhibitGlobalReadLock();
}
}
/**
* Locks the parent of a path with either a read or a write lock.
*
* @param path
* the child of the path to lock
* @param level
* either {@link #READ_LOCK} or {@link #WRITE_LOCK}
*
* @see #acquireLock(String, int)
*/
protected void acquireParentLock(String path, int level) {
int lastSlash = path.lastIndexOf('/');
if (lastSlash > 0) {
String parentPath = path.substring(0, lastSlash);
acquireLock(parentPath, level);
}
}
/**
* Locks a path with either a read or a write lock. <br>
* <br>
* <em>Caution #1:</em> All methods need read locks to all ancestors on
* all levels. Effectively this means a write lock is always recursive and
* applies to all descendants on all levels.
*
* <br>
* <br>
* <em>Caution #2:</em> A write lock implies a read lock.
*
* @param path
* the path to lock
* @param level
* either {@link #READ_LOCK}or {@link #WRITE_LOCK}
*/
protected void acquireLock(String path, int level) {
GenericLock lock = (GenericLock) FINE_LOCK_MANAGER.atomicGetOrCreateLock(path);
acquireLock(lock, level);
}
/**
* Acquires a lock.
*
* @param lock
* the lock to acquire
* @param level
* either {@link #READ_LOCK} or {@link #WRITE_LOCK}
*
* @see #acquireLock(String, int)
*/
protected void acquireLock(GenericLock lock, int level) {
try {
lock.acquire(this, level, true, true, Long.MAX_VALUE);
} catch (InterruptedException e) {
}
locks.add(lock);
token.getLogger().log(this +
(level == READ_LOCK ? " read" : " write") + " locking "
+ lock.getResourceId().toString(), LOG_CHANNEL_LOCKING, LOCK_LOG_LEVEL);
}
/**
* Acquires a supporting block lock.
*
* @param lock
* the lock to acquire
* @param level
* either {@link #READ_LOCK} or {@link #WRITE_LOCK}
*
* @see #acquireLock(String, int)
*/
protected void blockWithLock(GenericLock lock, int level) {
try {
lock.acquire(this, level, true, GenericLock.COMPATIBILITY_REENTRANT_AND_SUPPORT,
Long.MAX_VALUE);
} catch (InterruptedException e) {
}
locks.add(lock);
token.getLogger().log(
this + (level == READ_LOCK ? " read" : " write") + " locking "
+ lock.getResourceId().toString(), LOG_CHANNEL_LOCKING, LOCK_LOG_LEVEL);
}
/**
* Locks a list of paths with either a read or a write lock.
*
* @param paths
* the paths to lock
* @param level
* either {@link #READ_LOCK} or {@link #WRITE_LOCK}
*
* @see #acquireLock(String, int)
*/
protected void acquireLock(List paths, int level) {
for (Iterator i = paths.iterator(); i.hasNext();) {
String path = (String) i.next();
acquireLock(path, level);
}
}
protected void releaseLocks() {
GenericLock lock;
for (Iterator i = locks.iterator(); i.hasNext();) {
lock = (GenericLock) i.next();
lock.release(this);
token.getLogger().log(
this + " releasing " + lock.getResourceId().toString(),
LOG_CHANNEL_LOCKING, LOCK_LOG_LEVEL);
}
}
protected List getPathsToRoot(String uri) {
StringTokenizer tokenizer = new StringTokenizer(uri, "/");
List paths = new ArrayList(tokenizer.countTokens() + 1);
paths.add("/");
String path = "";
while (tokenizer.hasMoreTokens()) {
path += "/" + tokenizer.nextToken();
paths.add(path);
}
return paths;
}
/**
* Returns the value of an integer init parameter of the servlet.
* Default value: -1.
*/
protected int getIntInitParameter( String name ) {
int result = -1;
try {
result = Integer.parseInt( getConfig().getInitParameter(name) );
}
catch( NumberFormatException x ) {};
return result;
}
/**
* Error handling routine
*/
protected void sendError( int statusCode ) {
try { resp.sendError( statusCode ); } catch( Throwable x ) {};
}
/**
* Error handling routine
*/
protected void sendError( int statusCode, String message ) {
String statusText =
WebdavStatus.getStatusText(statusCode)+
(message != null
? ": "+Messages.format( message, (Object)null )
: "");
try { resp.sendError( statusCode, statusText ); } catch( Throwable x ) {};
}
/**
* Error handling routine
*/
protected void sendError( int statusCode, String message, Object[] args ) {
String statusText =
WebdavStatus.getStatusText(statusCode)+": "+
Messages.format( message, args );
try { resp.sendError( statusCode, statusText ); } catch( Throwable x ) {};
}
/**
* Error handling routine
*/
protected void sendError( int statusCode, Throwable t ) {
printStackTrace( t, statusCode );
String explanation = (t == null || t.getMessage() == null || "".equals(t.getMessage())
? Messages.format(t.getClass().getName(), (Object)null)
: t.getMessage()
);
String statusText =
WebdavStatus.getStatusText(statusCode)+": "+explanation;
try { resp.sendError( statusCode, statusText ); } catch( Throwable x ) {};
}
/**
* Prints the stack trace of the given exception if the specified status code
* is greater-or-equal the value of the servlet init-parameter printStackTrace.
* If the init-parameter is not specified, stack traces are printed for status
* codes >= 500.
*/
protected void printStackTrace( Throwable x, int statusCode ) {
int printStackTraceFrom = getIntInitParameter( "printStackTrace" );
if( printStackTraceFrom < 0 )
printStackTraceFrom = 500;
if( statusCode >= printStackTraceFrom )
x.printStackTrace();
}
/**
* Generate status text.
*
* @param parentElement the parent Element to append to.
* @param href Slide-Uri of the object
* @param statusCode HTTP status code of the error
*/
protected void generateStatusText(Element parentElement, String href, int statusCode) {
Element hrefElement = new Element(E_HREF, DNSP);
parentElement.addContent(hrefElement);
hrefElement.setText(getFullPath(href));
Element statusElement = new Element(E_STATUS, DNSP);
parentElement.addContent(statusElement);
statusElement.setText("HTTP/1.1 " + statusCode + " "
+ WebdavStatus.getStatusText(statusCode));
}
/**
* Generate an XML error message.
*
* @param macroException Nested exception
* @return String XML message
*/
protected String generateErrorMessage
(NestedSlideException nestedException) {
Element multistatus = new Element(E_MULTISTATUS, DNSP);
Enumeration nestedExceptionsList =
nestedException.enumerateExceptions();
while (nestedExceptionsList.hasMoreElements()) {
Element response = new Element(E_RESPONSE, DNSP);
multistatus.addContent(response);
SlideException ex =
(SlideException) nestedExceptionsList.nextElement();
generateStatusText(response, MethodUtil.getErrorMessage(ex),
getErrorCode(ex));
if (ex instanceof PreconditionViolationException) {
response.addContent(MethodUtil.getPreconditionViolationResponseDescription((PreconditionViolationException)ex));
}
}
StringWriter stringWriter = new StringWriter();
try {
new XMLOutputter().output(multistatus, stringWriter);
}
catch (IOException e) {
Domain.log(e);
}
return stringWriter.toString();
}
protected boolean exists( String uriStr ) throws SlideException {
boolean destinationExists = true;
try {
content.retrieve(slideToken, uriStr);
}
catch (ObjectNotFoundException x) {
destinationExists = false;
}
return destinationExists;
}
protected boolean isLocked( String uriStr ) throws ServiceAccessException {
// use a non-blocking slide token.
boolean isLocked = false;
try {
Enumeration locks = lock.enumerateLocks (slideToken, uriStr, false);
while (locks.hasMoreElements()) {
if (lock.isLocked(slideToken,(NodeLock) locks.nextElement(),false)) {
isLocked = true;
}
}
}
catch (ServiceAccessException x) {
throw x;
}
catch (SlideException x) {
// ignore silently
}
return isLocked;
}
protected boolean isLockNull( String uriStr ) throws ServiceAccessException {
boolean isLockNull = false;
try {
NodeRevisionDescriptor nrd =
content.retrieve(slideToken, content.retrieve(slideToken, uriStr));
isLockNull = isLockNull( nrd );
}
catch (ServiceAccessException x) {
throw x;
}
catch (SlideException x) {
// ignore silently
}
return isLockNull;
}
protected boolean isLockNull( NodeRevisionDescriptor nrd ) {
return nrd.propertyValueContains(P_RESOURCETYPE, E_LOCKNULL);
}
protected boolean isAutoVersionControl(String resourcePath) {
return new Boolean(Domain.getParameter(I_AUTO_VERSION_CONTROL,
I_AUTO_VERSION_CONTROL_DEFAULT,
token.getUri(slideToken, resourcePath).getStore()))
.booleanValue();
}
protected boolean isExcludedForVersionControl(String resourcePath) {
String versionControlExcludePaths =
Domain.getParameter(I_VERSIONCONTROL_EXCLUDEPATH,
I_VERSIONCONTROL_EXCLUDEPATH_DEFAULT,
token.getUri(slideToken, resourcePath).getStore());
if (versionControlExcludePaths != null && versionControlExcludePaths.length() > 0) {
StringTokenizer st = new StringTokenizer(versionControlExcludePaths, ";");
while (st.hasMoreTokens()) {
if (isExcluded(resourcePath, st.nextToken())) {
return true;
}
}
}
return false;
}
private boolean isExcluded(String resourcePath, String excludePath) {
UriHandler uh = UriHandler.getUriHandler(resourcePath);
if (excludePath != null && excludePath.length() > 0) {
UriHandler exUh = UriHandler.getUriHandler(excludePath);
if (exUh.isAncestorOf(uh)) {
return true;
}
}
return false;
}
/**
* Check if the conditions specified in the optional If headers are
* satisfied.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param resourceInfo File object
* @return boolean true if the resource meets all the specified conditions,
* and false if any of the conditions is not satisfied, in which case
* request processing is stopped
*/
protected boolean checkIfHeaders(HttpServletRequest request,
HttpServletResponse response,
ResourceInfo resourceInfo)
throws IOException
{
// the ETag without apostrophes ("), we use apostrophes as delimiters
// to because some clients provide If header with out apostrophes
String eTag = getETagValue(resourceInfo, true);
long lastModified = resourceInfo.date;
StringTokenizer commaTokenizer;
String headerValue;
// Checking If-Match
headerValue = request.getHeader("If-Match");
if (headerValue != null) {
if (headerValue.indexOf("*") == -1) {
commaTokenizer = new StringTokenizer(headerValue, ", \"");
boolean matchingTagFound = false;
while (!matchingTagFound && commaTokenizer.hasMoreTokens()) {
matchingTagFound = commaTokenizer.nextToken().equals(eTag);
}
// If none of the given ETags match, 412 Precodition failed is
// sent back
if (!matchingTagFound) {
response.sendError(
HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
} else {
if (!resourceInfo.exists()) {
response.sendError(
HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
}
}
// Checking If-Modified-Since
headerValue = request.getHeader("If-Modified-Since");
if (headerValue != null) {
// If an If-None-Match header has been specified, if modified since
// is ignored.
if (request.getHeader("If-None-Match") == null) {
Date date = parseHttpDate(headerValue);
if ((date != null)
&& (lastModified <= (date.getTime() + 1000)) ) {
// The entity has not been modified since the date
// specified by the client. This is not an error case.
response.sendError
(HttpServletResponse.SC_NOT_MODIFIED);
return false;
}
}
}
// Checking If-None-Match
headerValue = request.getHeader("If-None-Match");
if (headerValue != null) {
if (headerValue.indexOf("*") == -1) {
commaTokenizer = new StringTokenizer(headerValue, ", \"");
while (commaTokenizer.hasMoreTokens()) {
if (commaTokenizer.nextToken().equals(eTag)) {
// For GET and HEAD, we respond with 304 Not Modified.
// For every other method, 412 Precondition Failed
if ( ("GET".equals(request.getMethod()))
|| ("HEAD".equals(request.getMethod())) )
{
response.sendError(
HttpServletResponse.SC_NOT_MODIFIED);
return false;
} else {
response.sendError(
HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
}
}
} else {
if (resourceInfo.exists()) {
response.sendError(
HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
}
}
// Checking If-Unmodified-Since
headerValue = request.getHeader("If-Unmodified-Since");
if (headerValue != null) {
Date date = parseHttpDate(headerValue);
if ( (date != null) && (lastModified > date.getTime()) ) {
// The entity has not been modified since the date
// specified by the client. This is not an error case.
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
}
return true;
}
/**
* Parses the date string given as one of the {@link #formats}.
* If the date does not fit any of these formats it returns <code>null</code>.
*
* @param headerValue date string from and HTTP header (e.g. If-Modified)
* @return a Date representing the date given of <code>null</code> if
* the string has no valid format.
*/
protected Date parseHttpDate(String headerValue) {
Date date = null;
// Parsing the HTTP Date
for (int i = 0; (date == null) && (i < formats.length); i++) {
try {
synchronized (formats[i]) {
date = formats[i].parse(headerValue);
}
} catch (ParseException e) {
// ignore the invalid format and try the next
}
}
return date;
}
/**
* Get the ETag value associated with a file.
*
* @param resourceInfo File object
* @param strong True if we want a strong ETag, in which case a checksum
* of the file has to be calculated
*/
protected String getETagValue(ResourceInfo resourceInfo, boolean strong) {
// FIXME : Compute a strong ETag if requested, using an MD5 digest
// of the file contents
if (resourceInfo.exists()) {
return resourceInfo.etag;
}
else {
return resourceInfo.length + "-" + resourceInfo.date;
}
}
/**
* Get the ETag associated with a file.
*
* @param resourceInfo File object
* @param strong True if we want a strong ETag, in which case a checksum
* of the file has to be calculated
*/
protected String getETag(ResourceInfo resourceInfo, boolean strong) {
if (strong)
return "\"" + getETagValue(resourceInfo, strong) + "\"";
else
return "W/\"" + getETagValue(resourceInfo, strong) + "\"";
}
protected class ResourceInfo {
/**
* Constructor.
*
* @param path Path name of the resource
*/
public ResourceInfo(String path, NodeRevisionDescriptor properties) {
this.path = path;
this.exists = true;
this.creationDate = properties.getCreationDateAsDate().getTime();
this.date = properties.getLastModifiedAsDate().getTime();
this.httpDate = properties.getLastModified();
this.length = properties.getContentLength();
this.etag = properties.getETag();
}
/**
* Creates a ResourceInfo for a non existing resource.
* @param path Path of the resource
*/
public ResourceInfo(String path) {
this.path = path;
this.exists = false;
this.length = 0;
this.date = System.currentTimeMillis();
}
public String path;
public long creationDate;
public String httpDate;
public long date;
public long length;
public String etag;
//public boolean collection;
public boolean exists;
/**
* Test if the associated resource exists.
*/
public boolean exists() {
return exists;
}
/**
* String representation.
*/
public String toString() {
return path;
}
}
protected class RequestHeaders {
private static final int
ST_UNDEFINED = 0,
ST_INVALID = 1,
ST_DEFINED = 2;
// raw headers
private String hIfStr;
private String hLockTokenStr;
private String hDepthStr;
private String hDestinationStr;
private String hOverwriteStr;
private String hTimeoutStr;
private String hLabelStr;
private String hNotificationTypeStr;
private String hCallbackStr;
private String hSubscriptionIdStr;
private String hNotificationDelayStr;
private String hSubscriptionLifetimeStr;
private String hTxIdStr;
private String hTxMethodStr;
private String hContentTypeStr;
private String hApplyToRedirectRefStr;
// parsed headers
private List hIf;
private String hLockToken;
private int hDepth;
private String hDestination;
private boolean hOverwrite;
private int hTimeout;
private String hLabel;
private String hNotificationType;
private String hCallback;
private int []hSubscriptionId;
private int hNotificationDelay;
private int hSubscriptionLifetime;
private String hTxId;
private String hTxMethod;
private String hContentType;
private boolean hApplyToRedirectRef;
// states
private int stIf = ST_UNDEFINED;
private int stLockToken = ST_UNDEFINED;
private int stDepth = ST_UNDEFINED;
private int stDestination = ST_UNDEFINED;
private int stOverwrite = ST_UNDEFINED;
private int stTimeout = ST_UNDEFINED;
private int stLabel = ST_UNDEFINED;
private int stNotificationType = ST_UNDEFINED;
private int stCallback = ST_UNDEFINED;
private int stSubscriptionId = ST_UNDEFINED;
private int stNotificationDelay = ST_UNDEFINED;
private int stSubscriptionLifetime = ST_UNDEFINED;
private int stTxId = ST_UNDEFINED;
private int stTxMethod= ST_UNDEFINED;
private int stContentType = ST_UNDEFINED;
private int stApplyToRedirectRef = ST_UNDEFINED;
protected RequestHeaders() {
}
protected boolean isDefined( String header ) {
return req.getHeader(header) != null;
}
protected List getIf() throws WebdavException {
if (stIf == ST_UNDEFINED) {
return Collections.EMPTY_LIST;
}
else if (stIf == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid header If: "+hIfStr );
throw new WebdavException( sc );
}
else {
return hIf;
}
}
protected String getLockToken() throws WebdavException {
if (stLockToken == ST_UNDEFINED) {
return null;
}
else if (stLockToken == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid header LockToken: "+hLockTokenStr );
throw new WebdavException( sc );
}
else {
return hLockToken;
}
}
protected int getDepth( int defaultValue ) throws WebdavException {
if (stDepth == ST_UNDEFINED) {
return defaultValue;
}
else if (stDepth == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid header Depth: "+hDepthStr );
throw new WebdavException( sc );
}
else {
return hDepth;
}
}
protected String getDestination() throws WebdavException {
if (stDestination == ST_UNDEFINED) {
return null;
}
else if (stDestination == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid header Destination: "+hDestinationStr );
throw new WebdavException( sc );
}
else {
return hDestination;
}
}
protected boolean getOverwrite( boolean defaultValue ) throws WebdavException {
if (stOverwrite == ST_UNDEFINED) {
return defaultValue;
}
else if (stOverwrite == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid header Overwrite: "+hOverwriteStr );
throw new WebdavException( sc );
}
else {
return hOverwrite;
}
}
protected String getLabel() throws WebdavException {
if (stLabel == ST_UNDEFINED) {
return null;
}
else if (stLabel == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid header Label: "+hLabelStr );
throw new WebdavException( sc );
}
else {
return hLabel;
}
}
protected int getTimeout( int defaultValue) throws WebdavException {
if (stTimeout == ST_UNDEFINED) {
return defaultValue;
}
else if (stTimeout == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid header Timeout: "+hTimeoutStr );
throw new WebdavException( sc );
}
else {
return hTimeout;
}
}
protected String getNotificationType() throws WebdavException {
if (stNotificationType == ST_UNDEFINED) {
return null;
} else if (stNotificationType == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid notification type: "+hNotificationTypeStr );
throw new WebdavException( sc );
}
else {
return hNotificationType;
}
}
protected int []getSubscriptionId() throws WebdavException {
if (stSubscriptionId == ST_UNDEFINED) {
return new int[0];
} else if (stSubscriptionId == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid subscription ID: "+hSubscriptionIdStr );
throw new WebdavException( sc );
}
else {
return hSubscriptionId;
}
}
protected String getCallback() throws WebdavException {
if (stCallback == ST_UNDEFINED) {
return null;
} else if (stCallback == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid callback: "+hCallbackStr );
throw new WebdavException( sc );
}
else {
return hCallback;
}
}
protected int getNotificationDelay( int defaultValue ) throws WebdavException {
if (stNotificationDelay == ST_UNDEFINED) {
return defaultValue;
}
else if (stNotificationDelay == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid notification delay: "+hNotificationDelayStr );
throw new WebdavException( sc );
}
else {
return hNotificationDelay;
}
}
protected int getSubscriptionLifetime( int defaultValue ) throws WebdavException {
if (stSubscriptionLifetime == ST_UNDEFINED) {
return defaultValue;
}
else if (stSubscriptionLifetime == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid subscription lifetime: "+hSubscriptionLifetimeStr );
throw new WebdavException( sc );
}
else {
return hSubscriptionLifetime;
}
}
protected String getTxId() throws WebdavException {
if (stTxId == ST_UNDEFINED) {
return null;
} else if (stTxId == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid transaction id: "+hTxIdStr );
throw new WebdavException( sc );
}
else {
return hTxId;
}
}
protected String getTxMethod() throws WebdavException {
if (stTxMethod == ST_UNDEFINED) {
return null;
} else if (stTxMethod == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid transaction method: "+hTxMethodStr );
throw new WebdavException( sc );
}
else {
return hTxMethod;
}
}
protected String getContentType() {
if (stContentType == ST_UNDEFINED) {
return null;
} else {
return hContentType;
}
}
protected boolean getApplyToRedirectRef( boolean defaultValue )
throws WebdavException
{
if (stApplyToRedirectRef == ST_UNDEFINED) {
return defaultValue;
}
else if (stApplyToRedirectRef == ST_INVALID) {
int sc = WebdavStatus.SC_PRECONDITION_FAILED;
sendError( sc, "Invalid header Apply-To-Redirect-Ref: " +
hApplyToRedirectRefStr );
throw new WebdavException( sc );
}
else {
return hApplyToRedirectRef;
}
}
protected void parse() {
// TransactionId header
hTxIdStr = req.getHeader(H_TRANSACTION);
if (hTxIdStr != null) {
stTxId = ST_DEFINED;
try {
hTxId = hTxIdStr;
} catch (Exception e) {
stTxId = ST_INVALID;
}
}
// TransactionMethod header
hTxMethodStr = req.getHeader(H_TRANSACTION_METHOD);
if (hTxMethodStr != null) {
stTxMethod = ST_DEFINED;
try {
hTxMethod = hTxMethodStr;
} catch (Exception e) {
stTxMethod = ST_INVALID;
}
}
// NotificationType header
hNotificationTypeStr = req.getHeader(H_NOTIFICATION_TYPE);
if (hNotificationTypeStr != null) {
stNotificationType = ST_DEFINED;
try {
hNotificationType = hNotificationTypeStr;
} catch (Exception e) {
stNotificationType = ST_INVALID;
}
}
// NotificationDelay header
hNotificationDelayStr = req.getHeader(H_NOTIFICATION_DELAY);
if (hNotificationDelayStr != null) {
stNotificationDelay = ST_DEFINED;
try {
hNotificationDelay = Integer.parseInt(hNotificationDelayStr);
} catch (Exception e) {
stNotificationDelay = ST_INVALID;
}
}
// SubscriptionLifetime header
hSubscriptionLifetimeStr = req.getHeader(H_SUBSCRIPTION_LIFETIME);
if (hSubscriptionLifetimeStr != null) {
stSubscriptionLifetime = ST_DEFINED;
try {
hSubscriptionLifetime = Integer.parseInt(hSubscriptionLifetimeStr);
} catch (Exception e) {
stSubscriptionLifetime = ST_INVALID;
}
}
// SubscriptionID header
hSubscriptionIdStr = req.getHeader(H_SUBSCRIPTION_ID);
if (hSubscriptionIdStr != null) {
stSubscriptionId = ST_DEFINED;
try {
StringTokenizer tokenizer = new StringTokenizer(hSubscriptionIdStr, ",");
hSubscriptionId = new int[tokenizer.countTokens()];
int i = 0;
while ( tokenizer.hasMoreTokens() ) {
hSubscriptionId[i] = Integer.parseInt(tokenizer.nextToken().trim());
i++;
}
} catch (Exception e) {
stSubscriptionId = ST_INVALID;
}
}
// Call back header
hCallbackStr = req.getHeader(H_CALL_BACK);
if (hCallbackStr != null) {
stCallback = ST_DEFINED;
try {
hCallback = hCallbackStr;
} catch (Exception e) {
stCallback = ST_INVALID;
}
}
// If header
hIfStr = req.getHeader(H_IF);
if (hIfStr != null) {
stIf = ST_DEFINED;
try {
hIf = extractLockTokens(hIfStr);
}
catch (Exception e) {
stIf = ST_INVALID;
}
}
// Lock-Token header
hLockTokenStr = req.getHeader(H_LOCK_TOKEN);
if (hLockTokenStr != null) {
stLockToken = ST_DEFINED;
try {
List tl = extractLockTokens(hLockTokenStr);
hLockToken = (String)tl.get(0);
}
catch (Exception e) {
stLockToken = ST_INVALID;
}
}
// Depth header
hDepthStr = req.getHeader(H_DEPTH);
if (hDepthStr != null) {
stDepth = ST_DEFINED;
if ("0".equals(hDepthStr)) {
hDepth = 0;
}
else if ("1".equals(hDepthStr)) {
hDepth = 1;
}
else if ("infinity".equalsIgnoreCase(hDepthStr)) {
hDepth = INFINITY;
}
else {
stDepth = ST_INVALID;
hDepth = Integer.parseInt(hDepthStr);
}
}
// Destination header
hDestinationStr = req.getHeader(H_DESTINATION);
if (hDestinationStr != null) {
stDestination = ST_DEFINED;
hDestination = hDestinationStr;
}
// Overwrite header
hOverwriteStr = req.getHeader(H_OVERWRITE);
if (hOverwriteStr != null) {
stOverwrite = ST_DEFINED;
if ("T".equalsIgnoreCase(hOverwriteStr)) {
hOverwrite = true;
}
else if ("F".equalsIgnoreCase(hOverwriteStr)) {
hOverwrite = false;
}
else {
stOverwrite = ST_INVALID;
}
}
// Timeout header
hTimeoutStr = req.getHeader(H_TIMEOUT);
if (hTimeoutStr != null) {
stTimeout = ST_DEFINED;
try {
hTimeout = extractLockDuration( hTimeoutStr );
}
catch (Exception e) {
stTimeout = ST_INVALID;
}
}
// Label header
hLabelStr = req.getHeader(H_LABEL);
if (hLabelStr != null) {
stLabel = ST_DEFINED;
hLabel = hLabelStr;
}
// Apply-To-Redirect-Ref header
hApplyToRedirectRefStr = req.getHeader(H_OVERWRITE);
if (hApplyToRedirectRefStr != null) {
stApplyToRedirectRef = ST_DEFINED;
if ("T".equalsIgnoreCase(hApplyToRedirectRefStr)) {
hApplyToRedirectRef = true;
}
else if ("F".equalsIgnoreCase(hApplyToRedirectRefStr)) {
hApplyToRedirectRef = false;
}
else {
stApplyToRedirectRef = ST_INVALID;
}
}
// Content-Type header
hContentTypeStr = req.getHeader(H_CONTENT_TYPE);
if (hContentTypeStr != null) {
stContentType = ST_DEFINED;
hContentType = hContentTypeStr;
}
}
private List extractLockTokens(String hStr) {
List result = new ArrayList();
int pos = hStr.indexOf(S_LOCK_TOKEN);
int endPos = -1;
int offset = S_LOCK_TOKEN.length();
String lockToken = null;
while (pos != -1) {
endPos = hStr.indexOf('>', pos + offset);
if (endPos == -1) {
lockToken = hStr;
endPos = hStr.length();
} else {
lockToken = hStr.substring(pos + offset, endPos);
}
//System.out.println("Lock Token found :-" + lockToken + "-");
slideToken.addLockToken(lockToken);
result.add( lockToken );
pos = hStr.indexOf(S_LOCK_TOKEN, endPos);
}
return result;
}
private int extractLockDuration(String hStr) {
int result;
int firstCommaPos = hStr.indexOf(',');
if (firstCommaPos != -1) {
hStr = hStr.substring(0, firstCommaPos);
}
if (hStr.startsWith("Second-")) {
result = Integer.parseInt(hStr.substring("Second-".length()));
} else {
if (hStr.equalsIgnoreCase("Infinite")) {
result = INFINITY;
} else {
result = Integer.parseInt(hStr);
}
}
return result;
}
}
}
The table below shows all metrics for AbstractWebdavMethod.java.




