org.alfresco.repo.webdav.PropFindMethod.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.webdav.PropFindMethod.java

Source

/*
 * #%L
 * Alfresco Remote API
 * %%
 * 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.repo.webdav;

import java.io.IOException;
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.repo.SessionUser;
import org.alfresco.repo.webdav.auth.AuthenticationFilter;
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.InvalidQNameException;
import org.alfresco.service.namespace.QName;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
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.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * Implements the WebDAV PROPFIND method
 * 
 * @author Gavin Cornwell
 */
public class PropFindMethod extends WebDAVMethod {
    // Request types
    protected static final int GET_ALL_PROPS = 0;
    protected static final int GET_NAMED_PROPS = 1;
    protected static final int FIND_PROPS = 2;

    // Find request type
    protected int m_mode = GET_ALL_PROPS;

    // Requested properties
    protected ArrayList<WebDAVProperty> m_properties = null;

    // Available namespaces list
    protected HashMap<String, String> m_namespaces = null;

    /**
     * Default constructor
     */
    public PropFindMethod() {
        m_namespaces = new HashMap<String, String>();
    }

    /**
     * Return the find mode
     * 
     * @return int
     */
    public final int getMode() {
        return m_mode;
    }

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

        parseDepthHeader();

    }

    /**
     * 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();
            Node node = null;

            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(WebDAV.XML_ALLPROP)) {
                        m_mode = GET_ALL_PROPS;
                    } else if (currentNode.getNodeName().endsWith(WebDAV.XML_PROP)) {
                        m_mode = GET_NAMED_PROPS;
                        node = currentNode;
                    } else if (currentNode.getNodeName().endsWith(WebDAV.XML_PROPNAME)) {
                        m_mode = FIND_PROPS;
                    }

                    break;
                }
            }

            if (m_mode == GET_NAMED_PROPS) {
                m_properties = new ArrayList<WebDAVProperty>();
                childList = node.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:
                        m_properties.add(createProperty(currentNode));
                        break;
                    }
                }
            }
        }
    }

    /**
     * @return          Returns <tt>true</tt> always
     */
    @Override
    protected boolean isReadOnly() {
        return true;
    }

    /**
     * Execute the main WebDAV request processing
     * 
     * @exception WebDAVServerException
     */
    protected void executeImpl() throws WebDAVServerException, Exception {
        m_response.setStatus(WebDAV.WEBDAV_SC_MULTI_STATUS);

        FileInfo pathNodeInfo = null;
        try {
            // Check that the path exists
            pathNodeInfo = getDAVHelper().getNodeForPath(getRootNodeRef(), m_strPath);
        } catch (FileNotFoundException e) {
            // The path is not valid - send a 404 error back to the client
            throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND);
        }

        // A node hidden during a 'shuffle' operation - send a 404 error back to the client, as some Mac clients need this
        // Note the null check, as root node may be null in cloud.
        if (pathNodeInfo.getNodeRef() != null && getFileFolderService().isHidden(pathNodeInfo.getNodeRef())) {
            throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND);
        }

        // Set the response content type

        m_response.setContentType(WebDAV.XML_CONTENT_TYPE);

        // Create multistatus response

        XMLWriter xml = createXMLWriter();

        xml.startDocument();

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

        // Create the path for the current location in the tree
        StringBuilder baseBuild = new StringBuilder(256);
        baseBuild.append(getPath());
        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
        generateResponseForNode(xml, pathNodeInfo, basePath);

        // 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 (getDepth() != WebDAV.DEPTH_0 && 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;

            // Save the base path length
            int baseLen = baseBuild.length();

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

            // Loop reporting each level of nodes to the requested depth
            while (curDepth <= getDepth() && 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(baseLen);
                    try {
                        String pathSnippet = null;
                        if ((pathNodeInfo.getNodeRef() == null) && (curNodeInfo.getNodeRef() == null)) {
                            // TODO review - note: can be null in case of Thor
                            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) {
                        // Build the path for the current child node
                        baseBuild.setLength(curBaseLen);

                        baseBuild.append(curChildInfo.getName());

                        // Output the current child node details
                        generateResponseForNode(xml, curChildInfo, baseBuild.toString());

                        // 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 = 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);
    }

    @Override
    protected OutputFormat getXMLOutputFormat() {
        String userAgent = m_request.getHeader("User-Agent");
        return ((null != userAgent) && userAgent.toLowerCase().startsWith("microsoft-webdav-miniredir/5.1."))
                ? OutputFormat.createCompactFormat()
                : super.getXMLOutputFormat();

    }

    /**
     * Creates a WebDAVProperty from the given XML node
     */
    protected WebDAVProperty createProperty(Node node) {
        WebDAVProperty property = null;

        String strName = node.getLocalName();
        String strNamespaceUri = node.getNamespaceURI();

        if (WebDAV.DEFAULT_NAMESPACE_URI.equals(strNamespaceUri)) {
            property = new WebDAVProperty(strName);
        } else {
            property = new WebDAVProperty(strName, strNamespaceUri, getNamespaceName(strNamespaceUri));
        }

        return property;
    }

    /**
     * Retrieves the namespace name for the given namespace URI, one is
     * generated if it doesn't exist
     */
    private String getNamespaceName(String strNamespaceUri) {
        if (strNamespaceUri == null) {
            return null;
        }
        String strNamespaceName = m_namespaces.get(strNamespaceUri);
        if (strNamespaceName == null) {
            strNamespaceName = "ns" + m_namespaces.size();
            m_namespaces.put(strNamespaceUri, strNamespaceName);
        }

        return strNamespaceName;
    }

    /**
     * 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());

        // Build the href string for the current node
        String strHRef = getURLForPath(m_request, path, isFolder);

        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);

        switch (m_mode) {
        case GET_NAMED_PROPS:
            generateNamedPropertiesResponse(xml, nodeInfo, isFolder);
            break;
        case GET_ALL_PROPS:
            generateAllPropertiesResponse(xml, nodeInfo, isFolder);
            break;
        case FIND_PROPS:
            generateFindPropertiesResponse(xml, nodeInfo, isFolder);
            break;
        }

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

    /**
     * Generates the XML response for a PROPFIND request that asks for a
     * specific set of properties
     * 
     * @param xml XMLWriter
     * @param nodeInfo FileInfo
     * @param isDir boolean
     */
    private void generateNamedPropertiesResponse(XMLWriter xml, FileInfo nodeInfo, boolean isDir) throws Exception {
        // Get the properties for the node
        Map<QName, Serializable> props = nodeInfo.getProperties();
        Map<QName, String> deadProperties = null;

        // 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);

        ArrayList<WebDAVProperty> propertiesNotFound = new ArrayList<WebDAVProperty>();

        TypeConverter typeConv = DefaultTypeConverter.INSTANCE;

        // Loop through the requested property list
        for (WebDAVProperty property : m_properties) {
            // Get the requested property details

            String propName = property.getName();
            String propNamespaceUri = property.getNamespaceUri();

            // Check if the property is a standard WebDAV property

            Object davValue = null;

            if (WebDAV.DEFAULT_NAMESPACE_URI.equals(propNamespaceUri)) {
                // Check if the client is requesting lock information
                if (propName.equals(WebDAV.XML_LOCK_DISCOVERY)) // && metaData.isLocked())
                {
                    generateLockDiscoveryResponse(xml, nodeInfo, isDir);
                } else if (propName.equals(WebDAV.XML_SUPPORTED_LOCK)) {
                    // Output the supported lock types
                    writeLockTypes(xml);
                }

                // Check if the client is requesting the resource type

                else if (propName.equals(WebDAV.XML_RESOURCE_TYPE)) {
                    // 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);
                } else if (propName.equals(WebDAV.XML_DISPLAYNAME)) {
                    // Get the node name
                    if (getRootNodeRef().equals(nodeInfo.getNodeRef())) {
                        // Output an empty name for the root node
                        xml.write(DocumentHelper.createElement(WebDAV.XML_NS_SOURCE));
                    } else {
                        // Get the node name
                        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=" + nodeInfo.getNodeRef());
                            }
                            xml.write(name);
                        }
                        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_DISPLAYNAME, WebDAV.XML_NS_DISPLAYNAME);
                    }
                } else if (propName.equals(WebDAV.XML_SOURCE)) {
                    // NOTE: source is always a no content element in our
                    // implementation

                    xml.write(DocumentHelper.createElement(WebDAV.XML_NS_SOURCE));
                } else if (propName.equals(WebDAV.XML_GET_LAST_MODIFIED)) {
                    // Get the modifed date/time

                    davValue = WebDAV.getDAVPropertyValue(props, WebDAV.XML_GET_LAST_MODIFIED);

                    // 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(WebDAV.formatModifiedDate(typeConv.convert(Date.class, davValue)));
                    xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_LAST_MODIFIED, WebDAV.XML_NS_GET_LAST_MODIFIED);
                } else if (propName.equals(WebDAV.XML_GET_CONTENT_LANGUAGE) && !isDir) {
                    // Get the content language
                    // TODO:
                    // Output the content language
                    xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_LANGUAGE,
                            WebDAV.XML_NS_GET_CONTENT_LANGUAGE, nullAttr);
                    // TODO:
                    xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_LANGUAGE,
                            WebDAV.XML_NS_GET_CONTENT_LANGUAGE);
                } else if (propName.equals(WebDAV.XML_GET_CONTENT_TYPE) && !isDir) {
                    // Get the content type
                    davValue = WebDAV.getDAVPropertyValue(props, WebDAV.XML_GET_CONTENT_TYPE);

                    // Output the content type
                    xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_TYPE, WebDAV.XML_NS_GET_CONTENT_TYPE,
                            nullAttr);
                    if (davValue != null)
                        xml.write(typeConv.convert(String.class, davValue));
                    xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_TYPE, WebDAV.XML_NS_GET_CONTENT_TYPE);
                } else if (propName.equals(WebDAV.XML_GET_ETAG) && !isDir) {
                    // Output the etag

                    xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_ETAG, WebDAV.XML_NS_GET_ETAG, nullAttr);
                    xml.write(getDAVHelper().makeETag(nodeInfo));
                    xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_ETAG, WebDAV.XML_NS_GET_ETAG);
                } else if (propName.equals(WebDAV.XML_GET_CONTENT_LENGTH)) {
                    // Get the content length, if it's not a folder
                    long len = 0;

                    if (!isDir) {
                        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);
                } else if (propName.equals(WebDAV.XML_CREATION_DATE)) {
                    // 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(WebDAV.formatCreationDate(typeConv.convert(Date.class, davValue)));
                    xml.endElement(WebDAV.DAV_NS, WebDAV.XML_CREATION_DATE, WebDAV.XML_NS_CREATION_DATE);
                } else if (propName.equals(WebDAV.XML_ALF_AUTHTICKET)) {
                    // Get the users authentication ticket

                    SessionUser davUser = (SessionUser) m_request.getSession()
                            .getAttribute(AuthenticationFilter.AUTHENTICATION_USER);

                    xml.startElement(WebDAV.DAV_NS, WebDAV.XML_ALF_AUTHTICKET, WebDAV.XML_NS_ALF_AUTHTICKET,
                            nullAttr);
                    if (davUser != null)
                        xml.write(davUser.getTicket());
                    xml.endElement(WebDAV.DAV_NS, WebDAV.XML_ALF_AUTHTICKET, WebDAV.XML_NS_ALF_AUTHTICKET);
                } else {
                    // Could not map the requested property to an Alfresco property
                    if (property.getName().equals(WebDAV.XML_HREF) == false)
                        propertiesNotFound.add(property);
                }
            } else {
                // Look in the custom properties

                //                String qualifiedName = propNamespaceUri + WebDAV.NAMESPACE_SEPARATOR + propName;

                String value = (String) nodeInfo.getProperties().get(property.createQName());
                if (value == null) {
                    if (deadProperties == null) {
                        deadProperties = loadDeadProperties(nodeInfo.getNodeRef());
                    }
                    value = deadProperties.get(property.createQName());
                }

                if (value == null) {
                    propertiesNotFound.add(property);
                } else {
                    if (property.hasNamespaceName()) {
                        xml.startElement(property.getNamespaceName(), property.getName(),
                                property.getNamespaceName() + WebDAV.NAMESPACE_SEPARATOR + property.getName(),
                                nullAttr);
                        xml.write(value);
                        xml.endElement(property.getNamespaceName(), property.getName(),
                                property.getNamespaceName() + WebDAV.NAMESPACE_SEPARATOR + property.getName());
                    } else {
                        xml.startElement("", property.getName(), property.getName(), nullAttr);
                        xml.write(value);
                        xml.endElement("", property.getName(), property.getName());
                    }
                }

            }
        }

        // Close off the successful part of the response

        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP);

        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.endElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT);

        // If some of the requested properties were not found return another
        // status section

        if (propertiesNotFound.size() > 0) {
            // Start the second status section

            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);

            // Loop through the list of properties that were not found

            for (WebDAVProperty property : propertiesNotFound) {
                // Output the property not found status block

                String propName = property.getName();
                String propNamespaceName = property.getNamespaceName();
                String propQName = propName;
                if (propNamespaceName != null && propNamespaceName.length() > 0)
                    propQName = propNamespaceName + ":" + propName;

                xml.write(DocumentHelper.createElement(propQName));
            }

            // Close the unsuccessful part of the response

            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP);

            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_STATUS, WebDAV.XML_NS_STATUS, nullAttr);
            xml.write(WebDAV.HTTP1_1 + " " + HttpServletResponse.SC_NOT_FOUND + " " + WebDAV.SC_NOT_FOUND_DESC);
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_STATUS, WebDAV.XML_NS_STATUS);

            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT);
        }
    }

    /**
     * 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

        Map<QName, Serializable> props = nodeInfo.getProperties();

        // 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);

        // Generate a lock status report, if locked

        generateLockDiscoveryResponse(xml, nodeInfo, isDir);

        // Output the supported lock types

        writeLockTypes(xml);

        // 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);

        // Get the node name

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

        TypeConverter typeConv = DefaultTypeConverter.INSTANCE;

        // 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="
                        + nodeInfo.getNodeRef());
            }
            xml.write(name);
        }
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_DISPLAYNAME, WebDAV.XML_NS_DISPLAYNAME);

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

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

        // 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(WebDAV.formatCreationDate(typeConv.convert(Date.class, davValue)));
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_CREATION_DATE, WebDAV.XML_NS_CREATION_DATE);

        // Get the modifed date/time

        davValue = WebDAV.getDAVPropertyValue(props, WebDAV.XML_GET_LAST_MODIFIED);

        // 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(WebDAV.formatModifiedDate(typeConv.convert(Date.class, davValue)));
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_LAST_MODIFIED, WebDAV.XML_NS_GET_LAST_MODIFIED);

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

        if (isDir == false) {
            // Get the content language

            // TODO:
            // Output the content language

            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_LANGUAGE, WebDAV.XML_NS_GET_CONTENT_LANGUAGE,
                    nullAttr);
            // TODO:
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_LANGUAGE, WebDAV.XML_NS_GET_CONTENT_LANGUAGE);

            // Get the content type
            davValue = WebDAV.getDAVPropertyValue(props, WebDAV.XML_GET_CONTENT_TYPE);

            // Output the content type
            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_TYPE, WebDAV.XML_NS_GET_CONTENT_TYPE, nullAttr);
            if (davValue != null)
                xml.write(typeConv.convert(String.class, davValue));
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_CONTENT_TYPE, WebDAV.XML_NS_GET_CONTENT_TYPE);

            // Output the etag

            xml.startElement(WebDAV.DAV_NS, WebDAV.XML_GET_ETAG, WebDAV.XML_NS_GET_ETAG, nullAttr);
            xml.write(getDAVHelper().makeETag(nodeInfo));
            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_GET_ETAG, WebDAV.XML_NS_GET_ETAG);
        }

        // Get the content length, if it's not a folder

        long len = 0;

        if (isDir == false) {
            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

        SessionUser davUser = (SessionUser) m_request.getSession()
                .getAttribute(AuthenticationFilter.AUTHENTICATION_USER);

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_ALF_AUTHTICKET, WebDAV.XML_NS_ALF_AUTHTICKET, nullAttr);
        if (davUser != null)
            xml.write(davUser.getTicket());
        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_ALF_AUTHTICKET, WebDAV.XML_NS_ALF_AUTHTICKET);

        // Close off the response

        xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP);

        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.endElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT);
    }

    /**
     * Generates the XML response for a PROPFIND request that asks for a list of
     * all known properties
     * 
     * @param xml XMLWriter
     * @param nodeInfo FileInfo
     * @param isDir boolean
     */
    protected void generateFindPropertiesResponse(XMLWriter xml, FileInfo nodeInfo, boolean isDir) {
        try {
            // 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);

            // Output the well-known properties

            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_LOCK_DISCOVERY));
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_SUPPORTED_LOCK));
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_RESOURCE_TYPE));
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_DISPLAYNAME));
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_GET_LAST_MODIFIED));
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_GET_CONTENT_LENGTH));
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_CREATION_DATE));
            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_GET_ETAG));

            if (isDir) {
                xml.write(DocumentHelper.createElement(WebDAV.XML_NS_GET_CONTENT_LANGUAGE));
                xml.write(DocumentHelper.createElement(WebDAV.XML_NS_GET_CONTENT_TYPE));
            }

            // Output the custom properties

            xml.write(DocumentHelper.createElement(WebDAV.XML_NS_ALF_AUTHTICKET));

            // Close off the response

            xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP);

            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.endElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT);
        } catch (Exception ex) {
            // Convert to a runtime exception

            throw new AlfrescoRuntimeException("XML processing error", ex);
        }
    }

    /**
     * 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 {
        // Output the lock status response
        LockInfo lockInfo = getNodeLockInfo(nodeInfo);
        if (lockInfo.isLocked() && !lockInfo.isExpired()) {
            generateLockDiscoveryXML(xml, nodeInfo, lockInfo);
        }
    }

    /**
     * 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);

            // Output exclusive lock
            // Shared locks are not supported, as they cannot be supported by the LockService (relevant to ALF-16449).
            writeLock(xml, WebDAV.XML_NS_EXCLUSIVE);

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

    /**
     * Loads all dead properties persisted on the node
     * 
     * @param nodeRef NodeRef
     * @return the map of all dead properties
     */
    @SuppressWarnings("unchecked")
    protected Map<QName, String> loadDeadProperties(NodeRef nodeRef) {
        Map<QName, String> result;

        List<String> deadProperties = (List<String>) getNodeService().getProperty(nodeRef,
                ContentModel.PROP_DEAD_PROPERTIES);

        if (deadProperties != null) {
            result = new HashMap<QName, String>(deadProperties.size() * 2);

            for (String deadProperty : deadProperties) {
                int last = deadProperty.length() - 1;
                int pos = deadProperty.indexOf(QName.NAMESPACE_END);
                if (pos == -1 || pos == last) {
                    continue;
                }
                pos = deadProperty.indexOf(':', pos + 1);
                if (pos == -1 || pos == last) {
                    continue;
                }
                try {
                    result.put(QName.createQName(deadProperty.substring(0, pos)), deadProperty.substring(pos + 1));
                } catch (InvalidQNameException e) {
                    // Skip and continue
                }
            }
        } else {
            result = new HashMap<QName, String>(7);
        }

        return result;
    }

    /**
     * Persists dead properties for specified resource
     * 
     * @param nodeRef specified resource
     * @param deadProperties the properties to persist
     */
    protected void persistDeadProperties(NodeRef nodeRef, Map<QName, String> deadProperties) {
        List<String> listToPersist = new ArrayList<String>(deadProperties.size());

        for (Map.Entry<QName, String> entry : deadProperties.entrySet()) {
            listToPersist.add(entry.getKey().toString() + ':' + entry.getValue());
        }

        getNodeService().setProperty(nodeRef, ContentModel.PROP_DEAD_PROPERTIES, (Serializable) listToPersist);
    }

    /**
     * Output the lockentry element of the specified type
     * @param xml XMLWriter
     * @param lockType lock type containing namespace. Can be WebDAV.XML_NS_EXCLUSIVE or WebDAV.XML_NS_SHARED
     * @throws SAXException
     * @throws IOException
     */
    private void writeLock(XMLWriter xml, String lockType) throws SAXException, IOException {
        AttributesImpl nullAttr = getDAVHelper().getNullAttributes();

        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_ENTRY, WebDAV.XML_NS_LOCK_ENTRY, nullAttr);
        xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_SCOPE, WebDAV.XML_NS_LOCK_SCOPE, nullAttr);
        xml.write(DocumentHelper.createElement(lockType));
        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, WebDAV.XML_LOCK_ENTRY, WebDAV.XML_NS_LOCK_ENTRY);
    }
}