org.alfresco.module.vti.web.fp.PropfindMethod.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.module.vti.web.fp.PropfindMethod.java

Source

/*
 * #%L
 * Alfresco Sharepoint Protocol
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.module.vti.web.fp;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.vti.handler.alfresco.UrlHelper;
import org.alfresco.module.vti.handler.alfresco.VtiPathHelper;
import org.alfresco.module.vti.handler.alfresco.VtiUtils;
import org.alfresco.repo.webdav.LockInfo;
import org.alfresco.repo.webdav.WebDAV;
import org.alfresco.repo.webdav.WebDAVHelper;
import org.alfresco.repo.webdav.WebDAVMethod;
import org.alfresco.repo.webdav.WebDAVServerException;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.repository.datatype.TypeConverter;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.springframework.extensions.surf.util.URLDecoder;
import org.springframework.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;

/**
 * Class for handling PROPFIND method of the MS-WDVME protocol.
 * 
 * <p>MS Office client may send 3 variant of the PROPFIND method.
 *    <ul>
 *      <li>1. 'MS-Doclib' header with value '1' presents in request.</li>
 *      <li>2. 'Depth' header with value '0' presents in request.</li>
 *      <li>3. 'Depth' header with value 'infinity' presents in request.</li>
 *    </ul>
 *    All this possible situations are handled by this class. 
 * </p>
 * 
 * @author PavelYur
 *
 */
public class PropfindMethod extends WebDAVMethod {

    /** Logger */
    private static Log logger = LogFactory.getLog(PropfindMethod.class);

    private static final String HEADER_MS_DOCLIB = "MS-Doclib";
    private static final String XML_REPL = "repl";
    private static final int DEPTH_ZERO = 0;
    private static final int DEPTH_ONE = 1;
    private static final int DEPTH_INFINITY = 99;

    private HashMap<String, String> namespaceMap = new HashMap<String, String>();

    private int depth = DEPTH_INFINITY;
    private boolean containsCollblob = false;

    private String alfrescoContext;
    private UrlHelper urlHelper;
    private VtiPathHelper pathHelper;

    public PropfindMethod(VtiPathHelper pathHelper, UrlHelper urlHelper) {
        this.alfrescoContext = pathHelper.getAlfrescoContext();
        this.pathHelper = pathHelper;
        this.urlHelper = urlHelper;
        namespaceMap.put("urn:schemas-microsoft-com:office:office", "Office");
        namespaceMap.put("http://schemas.microsoft.com/repl/", "Repl");
        namespaceMap.put("urn:schemas-microsoft-com:", "Z");
    }

    /**
     * <p>
     *  Handle PROPFIND method of the MS-WDVME protocol.
     *  <ul>
     *      <li>1. If MS-Doclib header with value '1' presents in request it check for library existing and return
     *             the MS-Doclib header in response with value of the valid URL that is point to the library that 
     *             resource belongs to.
     *      </li>
     *      <li>
     *          2. If Depth header in request has a value '0' returns properties for the requested resource.
     *      </li>
     *      <li>
     *          3. If Depth header in request has a value 'infinity' (requested resources should be collection) 
     *             returns properties for the requested resource and all resources that are stored in requested 
     *             collection. 
     *      </li>
     *  </ul>
     *  In case if requested resource was not found then HTTP 404 status is sent to the client.
     * </p>
     */
    @Override
    protected void executeImpl() throws WebDAVServerException, Exception {
        String msDoclib = m_request.getHeader(HEADER_MS_DOCLIB);

        if (msDoclib != null && msDoclib.equals("1")) {
            try {
                getDAVHelper().getParentNodeForPath(getRootNodeRef(), m_strPath);
            } catch (FileNotFoundException e) {
                m_response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
            // TODO: this shouldn't assume the requested URL is the one exposed to the outside world
            // (may be behind proxy). Change to parse properly.
            String docLibHref = URLDecoder
                    .decode(m_request.getRequestURL().substring(0, m_request.getRequestURL().lastIndexOf("/")));
            m_response.setHeader(HEADER_MS_DOCLIB, docLibHref);
            return;
        }

        m_response.setStatus(WebDAV.WEBDAV_SC_MULTI_STATUS);

        if (logger.isDebugEnabled()) {
            logger.debug("processing PROPFIND request with uri: " + m_request.getRequestURI());
        }

        FileInfo pathNodeInfo = null;

        // Check that the path exists
        pathNodeInfo = pathHelper.resolvePathFileInfo(m_strPath);

        if (pathNodeInfo == null) {
            // The path is not valid - send a 404 error back to the client
            m_response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // Note the null check, as root node may be null in cloud.
        if (pathNodeInfo.getNodeRef() != null && getFileFolderService().isHidden(pathNodeInfo.getNodeRef())) {
            // ALF-17662, the path is hidden - send a 404 error back to the client
            m_response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // Set the response content type
        m_response.setContentType(WebDAV.XML_CONTENT_TYPE);

        // Create multistatus response
        XMLWriter xml = createXMLWriter();

        xml.startDocument();

        String nsdec = generateNamespaceDeclarations(namespaceMap);
        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_MULTI_STATUS + nsdec, WebDAV.XML_NS_MULTI_STATUS + nsdec,
                getDAVHelper().getNullAttributes());

        if (containsCollblob) {
            xml.startElement("http://schemas.microsoft.com/repl/", "repl", "Repl:repl",
                    getDAVHelper().getNullAttributes());
            xml.startElement("http://schemas.microsoft.com/repl/", "collblob", "Repl:collblob",
                    getDAVHelper().getNullAttributes());
            xml.write(VtiUtils.formatPropfindDate(new Date()));
            xml.endElement("http://schemas.microsoft.com/repl/", "collblob", "Repl:collblob");
            xml.endElement("http://schemas.microsoft.com/repl/", "repl", "Repl:repl");
        }

        xml.write("\n");

        // Create the path for the current location in the tree
        StringBuilder baseBuild = new StringBuilder(256);
        baseBuild.append("");
        if (baseBuild.length() == 0 || baseBuild.charAt(baseBuild.length() - 1) != WebDAVHelper.PathSeperatorChar) {
            baseBuild.append(WebDAVHelper.PathSeperatorChar);
        }
        String basePath = baseBuild.toString();

        // Output the response for the root node, depth zero
        try {
            generateResponseForNode(xml, pathNodeInfo, basePath);
        } catch (Exception e) {
            m_response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // If additional levels are required and the root node is a folder then recurse to the required
        // level and output node details a level at a time
        if (depth != DEPTH_ZERO && pathNodeInfo.isFolder()) {
            // Create the initial list of nodes to report
            List<FileInfo> nodeInfos = new ArrayList<FileInfo>(10);
            nodeInfos.add(pathNodeInfo);

            int curDepth = WebDAV.DEPTH_1;

            // List of next level of nodes to report
            List<FileInfo> nextNodeInfos = null;
            if (depth > WebDAV.DEPTH_1) {
                nextNodeInfos = new ArrayList<FileInfo>(10);
            }

            // Loop reporting each level of nodes to the requested depth
            while (curDepth <= depth && nodeInfos != null) {
                // Clear out the next level of nodes, if required
                if (nextNodeInfos != null) {
                    nextNodeInfos.clear();
                }

                // Output the current level of node(s), the node list should
                // only contain folder nodes

                for (FileInfo curNodeInfo : nodeInfos) {
                    // Get the list of child nodes for the current node
                    List<FileInfo> childNodeInfos = getDAVHelper().getChildren(curNodeInfo);

                    // can skip the current node if it doesn't have children
                    if (childNodeInfos.size() == 0) {
                        continue;
                    }

                    // Output the child node details
                    // Generate the base path for the current parent node

                    baseBuild.setLength(0);
                    try {
                        String pathSnippet = null;
                        if ((pathNodeInfo.getNodeRef() == null) && (curNodeInfo.getNodeRef() == null)) {
                            pathSnippet = "/";
                        } else {
                            pathSnippet = getDAVHelper().getPathFromNode(pathNodeInfo.getNodeRef(),
                                    curNodeInfo.getNodeRef());
                        }

                        baseBuild.append(pathSnippet);
                    } catch (FileNotFoundException e) {
                        // move to the next node
                        continue;
                    }

                    int curBaseLen = baseBuild.length();

                    // Output the child node details
                    for (FileInfo curChildInfo : childNodeInfos) {
                        // ALF-17662, do not show link nodes and hidden documents
                        // Note the null check, as node may be null in cloud.
                        final boolean isHidden = curChildInfo.getNodeRef() != null
                                && getFileFolderService().isHidden(curChildInfo.getNodeRef());
                        if (curChildInfo.isLink() == false && !isHidden) {
                            // Build the path for the current child node
                            baseBuild.setLength(curBaseLen);

                            baseBuild.append(WebDAVHelper.PathSeperatorChar + curChildInfo.getName());

                            // Output the current child node details
                            try {
                                generateResponseForNode(xml, curChildInfo, baseBuild.toString());
                            } catch (Exception e) {
                                m_response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                                return;
                            }

                            // If the child is a folder add it to the list of next level nodes
                            if (nextNodeInfos != null && curChildInfo.isFolder()) {
                                nextNodeInfos.add(curChildInfo);
                            }
                        }
                    }
                }

                // Update the current tree depth
                curDepth++;

                // Move the next level of nodes to the current node list
                nodeInfos.clear();
                if (nextNodeInfos != null) {
                    nodeInfos.addAll(nextNodeInfos);
                }
            }
        }

        // Close the outer XML element
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_MULTI_STATUS, WebDAV.XML_NS_MULTI_STATUS);

        // Send remaining data
        flushXML(xml);
    }

    /**
     * Generates the required response XML for the current node
     * 
     * @param xml XMLWriter
     * @param nodeInfo FileInfo
     * @param path String
     */
    protected void generateResponseForNode(XMLWriter xml, FileInfo nodeInfo, String path) throws Exception {
        boolean isFolder = nodeInfo.isFolder();

        // Output the response block for the current node
        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_RESPONSE, WebDAV.XML_NS_RESPONSE,
                getDAVHelper().getNullAttributes());

        path = URLDecoder.decode(m_request.getRequestURI()).replaceFirst(alfrescoContext, "") + path;
        path = path.replaceAll("//", "/");

        // Build the href string for the current node
        String relativeUrl = getDAVHelper().getURLForPath(m_request, path, isFolder);
        String strHRef = urlHelper.getExternalURLHostOnly() + relativeUrl;

        if (nodeInfo.isFolder()) {
            strHRef = strHRef.substring(0, strHRef.length() - 1);
        }

        strHRef = strHRef.endsWith("/") ? strHRef.substring(0, strHRef.length() - 1) : strHRef;

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_HREF, WebDAV.XML_NS_HREF, getDAVHelper().getNullAttributes());
        xml.write(strHRef);
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_HREF, WebDAV.XML_NS_HREF);

        generateAllPropertiesResponse(xml, nodeInfo, isFolder);

        // Close off the response element
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_RESPONSE, WebDAV.XML_NS_RESPONSE);
        xml.write("\n");
    }

    /**
     * Generates the XML response for a PROPFIND request that asks for all known
     * properties
     * 
     * @param xml XMLWriter
     * @param nodeInfo FileInfo
     * @param isDir boolean
     */
    protected void generateAllPropertiesResponse(XMLWriter xml, FileInfo nodeInfo, boolean isDir) throws Exception {
        // Get the properties for the node

        NodeRef node = nodeInfo.getNodeRef();

        Map<QName, Serializable> props = nodeInfo.getProperties();
        TypeConverter typeConv = DefaultTypeConverter.INSTANCE;

        NodeRef workingCopy = null;
        if (node != null) {
            workingCopy = getDAVHelper().getServiceRegistry().getCheckOutCheckInService().getWorkingCopy(node);
        }

        Map<QName, Serializable> workingCopyProps = null;
        if (workingCopy != null) {
            String workingCopyOwner = getDAVHelper().getNodeService()
                    .getProperty(workingCopy, ContentModel.PROP_WORKING_COPY_OWNER).toString();
            if (workingCopyOwner
                    .equals(getDAVHelper().getServiceRegistry().getAuthenticationService().getCurrentUserName())) {
                workingCopyProps = getDAVHelper().getNodeService().getProperties(workingCopy);
            }
        }

        // Output the start of the properties element

        Attributes nullAttr = getDAVHelper().getNullAttributes();

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT, nullAttr);
        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP, nullAttr);

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_RESOURCE_TYPE, WebDAV.XML_RESOURCE_TYPE, nullAttr);
        if (isDir) {
            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_COLLECTION, WebDAV.XML_COLLECTION, nullAttr);
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_COLLECTION, WebDAV.XML_COLLECTION);
        }
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_RESOURCE_TYPE, WebDAV.XML_RESOURCE_TYPE);

        // Get the node name

        Object davValue = WebDAV.getDAVPropertyValue(props, WebDAV.XML_DISPLAYNAME);

        // Output the node name

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_DISPLAYNAME, WebDAV.XML_NS_DISPLAYNAME, nullAttr);
        if (davValue != null) {
            String name = typeConv.convert(String.class, davValue);
            if (name == null || name.length() == 0) {
                logger.error("WebDAV name is null, value=" + davValue.getClass().getName() + ", node=" + node);
            }
            xml.write(name);
        }
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_DISPLAYNAME, WebDAV.XML_NS_DISPLAYNAME);

        // Generate a lock status report, if locked
        if (workingCopy != null
                && (VtiUtils.isMacClientRequest(m_request) || VtiUtils.isOffice2010ClientRequest(m_request))) {
            // Office 2008/2011 for Mac assumes that checkouted node is locked node
            generateFakeLockDiscoveryResponseForWorkingCopy(xml, workingCopy, isDir);
        } else {
            generateLockDiscoveryResponse(xml, nodeInfo, isDir);
        }

        // Output the supported lock types

        if (!isDir) {
            writeLockTypes(xml);
        }

        if (isDir) {
            xml.startElement(WebDAV.DAV_NS, "isFolder", WebDAV.DAV_NS + ":isFolder", nullAttr);
            xml.write("t");
            xml.endElement(WebDAV.DAV_NS, "isFolder", WebDAV.DAV_NS + ":isFolder");
            xml.startElement(WebDAV.DAV_NS, "iscollection", WebDAV.DAV_NS + ":iscollection", nullAttr);
            xml.write("1");
            xml.endElement(WebDAV.DAV_NS, "iscollection", WebDAV.DAV_NS + ":iscollection");
            xml.startElement(WebDAV.DAV_NS, "ishidden", WebDAV.DAV_NS + ":ishidden", nullAttr);
            xml.write("0");
            xml.endElement(WebDAV.DAV_NS, "ishidden", WebDAV.DAV_NS + ":ishidden");
            xml.startElement(WebDAV.DAV_NS, "getcontenttype", WebDAV.DAV_NS + ":getcontenttype", nullAttr);
            xml.write("application/octet-stream");
            xml.endElement(WebDAV.DAV_NS, "getcontenttype", WebDAV.DAV_NS + ":getcontenttype");
            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_LENGTH, WebDAV.XML_NS_GET_CONTENT_LENGTH,
                    nullAttr);
            xml.write("0");
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_LENGTH, WebDAV.XML_NS_GET_CONTENT_LENGTH);

            // If the node is a folder then return as a collection type

            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_RESOURCE_TYPE, WebDAV.XML_NS_RESOURCE_TYPE, nullAttr);
            if (isDir)
                xml.write(DocumentHelper.createElement(WebDAV.XML_NS_COLLECTION));
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_RESOURCE_TYPE, WebDAV.XML_NS_RESOURCE_TYPE);

            xml.startElement("Repl", "authoritative-directory", "Repl:authoritative-directory", nullAttr);
            xml.write("t");
            xml.endElement("Repl", "authoritative-directory", "Repl:authoritative-directory");
        }

        // Output the source
        //
        // NOTE: source is always a no content element in our implementation

        //xml.write(DocumentHelper.createElement(WebDAV.XML_NS_SOURCE));        

        // Get the modifed date/time

        if (workingCopyProps != null) {
            davValue = WebDAV.getDAVPropertyValue(workingCopyProps, WebDAV.XML_GET_LAST_MODIFIED);
        } else {
            davValue = WebDAV.getDAVPropertyValue(props, WebDAV.XML_GET_LAST_MODIFIED);
        }

        Date lastModified = (Date) davValue;

        // Output the last modified date of the node

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_LAST_MODIFIED, WebDAV.XML_NS_GET_LAST_MODIFIED, nullAttr);
        if (davValue != null)
            xml.write(VtiUtils.formatPropfindDate(typeConv.convert(Date.class, davValue)));
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_LAST_MODIFIED, WebDAV.XML_NS_GET_LAST_MODIFIED);

        // Get the creation date
        davValue = WebDAV.getDAVPropertyValue(props, WebDAV.XML_CREATION_DATE);

        // Output the creation date

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_CREATION_DATE, WebDAV.XML_NS_CREATION_DATE, nullAttr);
        if (davValue != null)
            xml.write(VtiUtils.formatPropfindDate(typeConv.convert(Date.class, davValue)));
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_CREATION_DATE, WebDAV.XML_NS_CREATION_DATE);

        // For a file node output the content language and content type

        if (isDir == false) {
            long len = 0;

            ContentData contentData = (ContentData) props.get(ContentModel.PROP_CONTENT);
            if (contentData != null)
                len = contentData.getSize();

            // Output the content length

            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_LENGTH, WebDAV.XML_NS_GET_CONTENT_LENGTH,
                    nullAttr);
            xml.write("" + len);
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_LENGTH, WebDAV.XML_NS_GET_CONTENT_LENGTH);

        }

        // Print out all the custom properties
        String guid = "";
        if (node != null) {
            guid = node.getId().toUpperCase();
        }

        xml.startElement("Repl", "repl-uid", "Repl:repl-uid", nullAttr);
        if (StringUtils.hasText(guid)) {
            xml.write(VtiUtils.constructRid(guid));
        }
        xml.endElement("Repl", "repl-uid", "Repl:repl-uid");

        xml.startElement("Repl", "resourcetag", "Repl:resourcetag", nullAttr);
        if (lastModified != null) {
            xml.write(VtiUtils.constructResourceTag(guid, lastModified));
        }
        xml.endElement("Repl", "resourcetag", "Repl:resourcetag");

        // Output the etag        
        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_ETAG, WebDAV.XML_NS_GET_ETAG, nullAttr);
        if (lastModified != null) {
            xml.write(VtiUtils.constructETag(guid, lastModified));
        }
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_ETAG, WebDAV.XML_NS_GET_ETAG);

        if (!isDir) {
            String modifiedBy = props.get(ContentModel.PROP_MODIFIER).toString();
            xml.startElement("Office", "modifiedby", "Office:modifiedby", nullAttr);
            xml.write(modifiedBy);
            xml.endElement("Office", "modifiedby", "Office:modifiedby");

            // Office 2011 for Mac special property
            xml.startElement(WebDAV.DAV_NS, "getmodifiedby", WebDAV.DAV_NS_PREFIX + "getmodifiedby", nullAttr);
            xml.write(modifiedBy);
            xml.endElement(WebDAV.DAV_NS, "getmodifiedby", WebDAV.DAV_NS_PREFIX + "getmodifiedby");

        }
        // Close off the response

        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP);
        xml.write("\n");

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_STATUS, WebDAV.XML_NS_STATUS, nullAttr);
        xml.write(WebDAV.HTTP1_1 + " " + HttpServletResponse.SC_OK + " " + WebDAV.SC_OK_DESC);
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_STATUS, WebDAV.XML_NS_STATUS);
        xml.write("\n");

        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT);
        xml.write("\n");
    }

    /**
     * Generates the XML response snippet showing the lock information for the
     * given path
     * 
     * @param xml XMLWriter
     * @param nodeInfo FileInfo
     * @param isDir boolean
     */
    protected void generateLockDiscoveryResponse(XMLWriter xml, FileInfo nodeInfo, boolean isDir) throws Exception {
        // Get the lock status for the node

        LockInfo lockInfo = getNodeLockInfo(nodeInfo);

        // Output the lock status response    
        if (lockInfo.isLocked()) {
            generateLockDiscoveryXML(xml, nodeInfo, lockInfo);
        } else {
            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_DISCOVERY, WebDAV.XML_NS_LOCK_DISCOVERY,
                    getDAVHelper().getNullAttributes());
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_DISCOVERY, WebDAV.XML_NS_LOCK_DISCOVERY);

            if (isDir) {
                xml.startElement(WebDAV.DAV_NS, WebDAV.XML_SUPPORTED_LOCK, WebDAV.XML_NS_SUPPORTED_LOCK,
                        getDAVHelper().getNullAttributes());
                xml.endElement(WebDAV.DAV_NS, WebDAV.XML_SUPPORTED_LOCK, WebDAV.XML_NS_SUPPORTED_LOCK);
            }
        }
    }

    /**
     * Generates the XML response snippet showing the fake lock information for the given path
     * 
     * @param xml XMLWriter
     * @param node NodeRef
     * @param isDir boolean
     */
    protected void generateFakeLockDiscoveryResponseForWorkingCopy(XMLWriter xml, NodeRef node, boolean isDir)
            throws Exception {

        Attributes nullAttr = getDAVHelper().getNullAttributes();
        String ns = WebDAV.DAV_NS;
        if (node != null) {
            String owner = (String) getNodeService().getProperty(node, ContentModel.PROP_WORKING_COPY_OWNER);
            String lockTocken = WebDAV.makeLockToken(node, owner);
            // Output the XML response

            xml.startElement(ns, WebDAV.XML_LOCK_DISCOVERY, WebDAV.XML_NS_LOCK_DISCOVERY, nullAttr);
            xml.startElement(ns, WebDAV.XML_ACTIVE_LOCK, WebDAV.XML_NS_ACTIVE_LOCK, nullAttr);

            xml.startElement(ns, WebDAV.XML_LOCK_TYPE, WebDAV.XML_NS_LOCK_TYPE, nullAttr);
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_WRITE));
            xml.endElement(ns, WebDAV.XML_LOCK_TYPE, WebDAV.XML_NS_LOCK_TYPE);

            xml.startElement(ns, WebDAV.XML_LOCK_SCOPE, WebDAV.XML_NS_LOCK_SCOPE, nullAttr);
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_EXCLUSIVE));
            xml.endElement(ns, WebDAV.XML_LOCK_SCOPE, WebDAV.XML_NS_LOCK_SCOPE);

            // NOTE: We only support one level of lock at the moment

            xml.startElement(ns, WebDAV.XML_DEPTH, WebDAV.XML_NS_DEPTH, nullAttr);
            xml.write("0");
            xml.endElement(ns, WebDAV.XML_DEPTH, WebDAV.XML_NS_DEPTH);

            xml.startElement(ns, WebDAV.XML_OWNER, WebDAV.XML_NS_OWNER, nullAttr);
            xml.write(owner);
            xml.endElement(ns, WebDAV.XML_OWNER, WebDAV.XML_NS_OWNER);

            xml.startElement(ns, WebDAV.XML_TIMEOUT, WebDAV.XML_NS_TIMEOUT, nullAttr);

            xml.write(WebDAV.SECOND + "604800");

            xml.endElement(ns, WebDAV.XML_TIMEOUT, WebDAV.XML_NS_TIMEOUT);

            xml.startElement(ns, WebDAV.XML_LOCK_TOKEN, WebDAV.XML_NS_LOCK_TOKEN, nullAttr);
            xml.startElement(ns, WebDAV.XML_HREF, WebDAV.XML_NS_HREF, nullAttr);

            xml.write(lockTocken);

            xml.endElement(ns, WebDAV.XML_HREF, WebDAV.XML_NS_HREF);
            xml.endElement(ns, WebDAV.XML_LOCK_TOKEN, WebDAV.XML_NS_LOCK_TOKEN);

            xml.endElement(ns, WebDAV.XML_ACTIVE_LOCK, WebDAV.XML_NS_ACTIVE_LOCK);
            xml.endElement(ns, WebDAV.XML_LOCK_DISCOVERY, WebDAV.XML_NS_LOCK_DISCOVERY);
        }
    }

    /**
     * Output the supported lock types XML element
     * 
     * @param xml XMLWriter
     */
    protected void writeLockTypes(XMLWriter xml) {
        try {
            AttributesImpl nullAttr = getDAVHelper().getNullAttributes();

            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_SUPPORTED_LOCK, WebDAV.XML_NS_SUPPORTED_LOCK, nullAttr);

            xml.startElement(WebDAV.DAV_NS, "lockentry", "D:lockentry", nullAttr);

            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_SCOPE, WebDAV.XML_NS_LOCK_SCOPE, nullAttr);
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_EXCLUSIVE));
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_SCOPE, WebDAV.XML_NS_LOCK_SCOPE);

            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_TYPE, WebDAV.XML_NS_LOCK_TYPE, nullAttr);
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_WRITE));
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_TYPE, WebDAV.XML_NS_LOCK_TYPE);

            xml.endElement(WebDAV.DAV_NS, "lockentry", "D:lockentry");

            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_SUPPORTED_LOCK, WebDAV.XML_NS_SUPPORTED_LOCK);
        } catch (Exception ex) {
            throw new AlfrescoRuntimeException("XML write error", ex);
        }
    }

    /**
     * Parse the request headers
     * 
     * @exception WebDAVServerException
     */
    protected void parseRequestHeaders() throws WebDAVServerException {
        // Store the Depth header as this is used by several WebDAV methods

        String strDepth = m_request.getHeader(WebDAV.HEADER_DEPTH);
        if (strDepth != null && strDepth.length() > 0) {
            if (strDepth.equals(WebDAV.ZERO)) {
                depth = DEPTH_ZERO;
            } else if (strDepth.equals(WebDAV.ONE)) {
                depth = DEPTH_ONE;
            } else {
                depth = DEPTH_INFINITY;
            }
        }
    }

    /**
     * Parse the request body
     * 
     * @exception WebDAVServerException
     */
    protected void parseRequestBody() throws WebDAVServerException {
        Document body = getRequestBodyAsDocument();
        if (body != null) {
            Element rootElement = body.getDocumentElement();
            NodeList childList = rootElement.getChildNodes();

            for (int i = 0; i < childList.getLength(); i++) {
                Node currentNode = childList.item(i);
                switch (currentNode.getNodeType()) {
                case Node.TEXT_NODE:
                    break;
                case Node.ELEMENT_NODE:
                    if (currentNode.getNodeName().endsWith(XML_REPL)) {
                        containsCollblob = true;
                    }

                    break;
                }
            }
        }
    }

    @Override
    protected OutputFormat getXMLOutputFormat() {
        OutputFormat outputFormat = new OutputFormat();
        outputFormat.setNewLineAfterDeclaration(false);
        outputFormat.setNewlines(false);
        outputFormat.setIndent(false);
        return outputFormat;
    }
}