Java tutorial
/* * $Header: /var/chroot/cvs/cvs/factsheetDesigner/extern/jakarta-slide-server-src-2.1-iPlus Edit/src/webdav/server/org/apache/slide/webdav/method/AbstractWebdavMethod.java,v 1.2 2006-01-22 22:55:20 peter-cvs Exp $ * $Revision: 1.2 $ * $Date: 2006-01-22 22:55:20 $ * * ==================================================================== * * 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.Iterator; import java.util.List; import java.util.Locale; import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.transaction.SystemException; import javax.transaction.Transaction; import org.apache.commons.transaction.locking.GenericLock; 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.NodeProperty.NamespaceCache; import org.apache.slide.lock.Lock; import org.apache.slide.lock.NodeLock; import org.apache.slide.macro.Macro; import org.apache.slide.search.Search; import org.apache.slide.security.Security; 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.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.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.a.Document; import org.jdom.a.Element; import org.jdom.a.JDOMException; import org.jdom.a.Namespace; import org.jdom.a.input.SAXBuilder; import org.jdom.a.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>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(); // 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) }; // create global read/write lock to allow for deadlock-free access private static final MultiLevelLock GLOBAL_LOCK = new GenericLock("global", 2, new PrintWriterLogger(new PrintWriter(System.out), LOG_CHANNEL, false)); // ----------------------------------------------------- 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; /** * Slide token. */ protected SlideToken slideToken; /** * 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(); // -------------------------------------------------- Static Initialization // static { // // // Load the MD5 helper used to calculate signatures. // try { // md5Helper = MessageDigest.getInstance("MD5"); // } catch (NoSuchAlgorithmException e) { // System.out.println(e.toString()); // throw new IllegalStateException(); // } // } // ----------------------------------------------------------- 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)) { try { String name = slideToken.getCredentialsToken().getPrincipal().getName(); slideToken.setCredentialsToken(new CredentialsToken(name.toLowerCase())); } catch (NullPointerException e) { System.err.println("User principle not found"); throw e; } } 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); 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); } if (this instanceof ReadMethod) { assureGlobalReadLock(); } else if (this instanceof WriteMethod) { assureGlobalWriteLock(); } globalLockObtained = true; } // Was this call made to finalize a transaction? boolean isEndofTransactionxUnlock = false; if (this instanceof UnlockMethod) { UnlockMethod meth = (UnlockMethod) this; if (meth.getCommand() != UnlockMethod.NO_TRANSACTION) isEndofTransactionxUnlock = true; } /* * Check for object existence and cleanup locks only if we're not * unlocking as part of finalizing a 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 it the * current transaction. */ if (!isEndofTransactionxUnlock) { 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, config, req, resp); 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 } } executeRequest(); 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); 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 { if (!slideToken.isExternalTransaction() && transactionIsStarted) { // Something went wrong, we are here and the TA is still open try { token.rollback(); } catch (Exception e) { // TODO e.printStackTrace(); } } 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) { // TODO Auto-generated catch block e.printStackTrace(); } } if (globalLockObtained) { releaseGlobalLock(); } } } // --------------------------------------------------------- Public Methods /** * 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; } // ------------------------------------------------------ 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(P_PARENT_SET); 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 // destinationUri = WebdavUtils.decodeURL(WebdavUtils.fixTomcatURL(destinationUri, "ISO-8859-1")); uri = WebdavUtils.decodeURL(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.a.output.Format format = org.jdom.a.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)); } /** * 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")); } protected void assureGlobalReadLock() { if (isSequentialRead()) { try { GLOBAL_LOCK.acquire(this, 1, true, true, Long.MAX_VALUE); } catch (InterruptedException e) { } } } protected void assureGlobalWriteLock() { if (isSequentialWrite()) { try { GLOBAL_LOCK.acquire(this, 2, true, true, Long.MAX_VALUE); } catch (InterruptedException e) { } } } protected void releaseGlobalLock() { GLOBAL_LOCK.release(this); } /** * 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; // 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; // 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; 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 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 String 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; } // 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; } } }