/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 The eXist Project * http://exist-db.org * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * $Id$ */ package org.exist.http.webdav.methods; import org.exist.EXistException; import org.exist.collections.Collection; import org.exist.dom.DocumentImpl; import org.exist.dom.DocumentMetadata; import org.exist.dom.LockToken; import org.exist.dom.QName; import org.exist.http.webdav.WebDAV; import org.exist.http.webdav.WebDAVUtil; import org.exist.security.Permission; import org.exist.security.User; import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.storage.lock.Lock; import org.exist.util.LockException; import org.exist.util.MimeType; import org.exist.util.serializer.SAXSerializer; import org.exist.util.serializer.SerializerPool; import org.exist.xmldb.XmldbURI; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; /** * Implements the WebDAV PROPFIND method. * * @author wolf */ public class Propfind extends AbstractWebDAVMethod { // search types private final static int FIND_ALL_PROPERTIES = 0; private final static int FIND_BY_PROPERTY = 1; private final static int FIND_PROPERTY_NAMES = 2; private final static SimpleDateFormat creationDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); private final static SimpleDateFormat modificationDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); static { creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); modificationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); } // default prefix for the webdav namespace private final static String PREFIX = "D"; // constants for known properties private final static QName DISPLAY_NAME_PROP = new QName("displayname", WebDAV.DAV_NS, PREFIX); private final static QName CREATION_DATE_PROP = new QName("creationdate", WebDAV.DAV_NS, PREFIX); private final static QName RESOURCE_TYPE_PROP = new QName("resourcetype", WebDAV.DAV_NS, PREFIX); private final static QName CONTENT_TYPE_PROP = new QName("getcontenttype", WebDAV.DAV_NS, PREFIX); private final static QName CONTENT_LENGTH_PROP = new QName("getcontentlength", WebDAV.DAV_NS, PREFIX); private final static QName LAST_MODIFIED_PROP = new QName("getlastmodified", WebDAV.DAV_NS, PREFIX); private final static QName SUPPORTED_LOCK_PROP = new QName("supportedlock", WebDAV.DAV_NS, PREFIX); private final static QName EXCLUSIVE_LOCK_PROP = new QName("exclusive", WebDAV.DAV_NS, PREFIX); private final static QName WRITE_LOCK_PROP = new QName("write", WebDAV.DAV_NS, PREFIX); private final static QName ETAG_PROP = new QName("etag", WebDAV.DAV_NS, PREFIX); private final static QName STATUS_PROP = new QName("status", WebDAV.DAV_NS, PREFIX); private final static QName COLLECTION_PROP = new QName("collection", WebDAV.DAV_NS, PREFIX); private final static QName LOCK_DISCOVERY_PROP = new QName("lockdiscovery", WebDAV.DAV_NS, PREFIX); private final static QName ACTIVELOCK_PROP = new QName("activelock", WebDAV.DAV_NS, PREFIX); private final static QName LOCKTYPE_PROP = new QName("activelock", WebDAV.DAV_NS, PREFIX); private final static QName LOCK_SCOPE_PROP = new QName("lockscope", WebDAV.DAV_NS, PREFIX); private final static QName LOCK_DEPTH_PROP = new QName("depth", WebDAV.DAV_NS, PREFIX); private final static QName LOCK_OWNER_PROP = new QName("owner", WebDAV.DAV_NS, PREFIX); private final static QName LOCK_TIMEOUT_PROP = new QName("timeout", WebDAV.DAV_NS, PREFIX); private final static QName LOCK_TOKEN_PROP = new QName("locktocken", WebDAV.DAV_NS, PREFIX); private final static QName[] DEFAULT_COLLECTION_PROPS = { DISPLAY_NAME_PROP, RESOURCE_TYPE_PROP, CREATION_DATE_PROP, LAST_MODIFIED_PROP }; private final static QName[] DEFAULT_RESOURCE_PROPS = { DISPLAY_NAME_PROP, RESOURCE_TYPE_PROP, CREATION_DATE_PROP, LAST_MODIFIED_PROP, CONTENT_TYPE_PROP, CONTENT_LENGTH_PROP, SUPPORTED_LOCK_PROP, LOCK_DISCOVERY_PROP }; public Propfind(BrokerPool pool) { super(pool); } public void process(User user, HttpServletRequest request, HttpServletResponse response, XmldbURI path) throws ServletException, IOException { DBBroker broker = null; Collection collection = null; DocumentImpl resource = null; try { broker = pool.get(user); // open the collection or resource specified in the path collection = broker.openCollection(path, Lock.READ_LOCK); if(collection == null) { ///TODO : use dedicated function in XmldbURI // no collection found: check for a resource XmldbURI docUri = path.lastSegment(); XmldbURI collUri = path.removeLastSegment(); collection = broker.openCollection(collUri, Lock.READ_LOCK); if(collection == null) { LOG.debug("No resource or collection found for path: " + path); response.sendError(HttpServletResponse.SC_NOT_FOUND, NOT_FOUND_ERR); return; } resource = collection.getDocumentWithLock(broker, docUri, Lock.READ_LOCK); if(resource == null) { LOG.debug("No resource found for path: " + path); response.sendError(HttpServletResponse.SC_NOT_FOUND, NOT_FOUND_ERR); return; } } if(!collection.getPermissions().validate(user, Permission.READ)) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } //TODO : release collection lock here ? //Take care however : collection is still used below // parse the request contents DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); docFactory.setNamespaceAware(true); DocumentBuilder docBuilder; try { docBuilder = docFactory.newDocumentBuilder(); } catch (ParserConfigurationException e1) { throw new ServletException(WebDAVUtil.XML_CONFIGURATION_ERR, e1); } Document doc = WebDAVUtil.parseRequestContent(request, response, docBuilder); int type = FIND_ALL_PROPERTIES; DAVProperties searchedProperties = new DAVProperties(); // LOG.debug("input:\n"+xmlToString(doc)); if(doc != null) { Element propfind = doc.getDocumentElement(); if(!(propfind.getLocalName().equals("propfind") && propfind.getNamespaceURI().equals(WebDAV.DAV_NS))) { LOG.debug(WebDAVUtil.UNEXPECTED_ELEMENT_ERR + propfind.getNodeName()); response.sendError(HttpServletResponse.SC_BAD_REQUEST, WebDAVUtil.UNEXPECTED_ELEMENT_ERR + propfind.getNodeName()); return; } NodeList childNodes = propfind.getChildNodes(); for(int i = 0; i < childNodes.getLength(); i++) { Node currentNode = childNodes.item(i); if(currentNode.getNodeType() == Node.ELEMENT_NODE) { if(currentNode.getNamespaceURI().equals(WebDAV.DAV_NS)) { if(currentNode.getLocalName().equals("prop")) { type = FIND_BY_PROPERTY; getPropertyNames(currentNode, searchedProperties); } if(currentNode.getLocalName().equals("allprop")) type = FIND_ALL_PROPERTIES; if(currentNode.getLocalName().equals("propname")) type = FIND_PROPERTY_NAMES; } else { // Found an unknown element: return with 400 Bad Request LOG.debug("Unexpected child: " + currentNode.getNodeName()); response.sendError(HttpServletResponse.SC_BAD_REQUEST, WebDAVUtil.UNEXPECTED_ELEMENT_ERR + currentNode.getNodeName()); return; } } } } // write response String servletPath = getServletPath(request); int depth = getDepth(request); StringWriter os = new StringWriter(); SAXSerializer serializer = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class); try { serializer.setOutput(os, WebDAV.OUTPUT_PROPERTIES); AttributesImpl attrs = new AttributesImpl(); serializer.startDocument(); serializer.startPrefixMapping(PREFIX, WebDAV.DAV_NS); serializer.startElement(WebDAV.DAV_NS, "multistatus", "D:multistatus", attrs); if(type == FIND_ALL_PROPERTIES || type == FIND_BY_PROPERTY) { if(resource != null) writeResourceProperties(user, searchedProperties, type, collection, resource, serializer, servletPath); else writeCollectionProperties(user, broker, searchedProperties, type, collection, serializer, servletPath, depth, 0); } else if(type == FIND_PROPERTY_NAMES) writePropertyNames(collection, resource, serializer, servletPath); serializer.endElement(WebDAV.DAV_NS, "multistatus", "D:multistatus"); serializer.endPrefixMapping(PREFIX); serializer.endDocument(); } catch (SAXException e) { throw new ServletException("Exception while writing multistatus response: " + e.getMessage(), e); } finally { SerializerPool.getInstance().returnObject(serializer); } String content = os.toString(); // LOG.debug("response:\n" + content); writeResponse(response, content); } catch (EXistException e) { throw new ServletException(e.getMessage(), e); } catch (LockException e) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } finally { pool.release(broker); if(resource != null) resource.getUpdateLock().release(Lock.READ_LOCK); if(collection != null) collection.release(Lock.READ_LOCK); } } private void writeCollectionProperties(User user, DBBroker broker, DAVProperties searchedProperties, int type, Collection collection, SAXSerializer serializer, String servletPath, int maxDepth, int currentDepth) throws SAXException { if(!collection.getPermissions().validate(user, Permission.READ)) return; AttributesImpl attrs = new AttributesImpl(); searchedProperties.reset(); serializer.startElement(WebDAV.DAV_NS, "response", "D:response", attrs); // write D:href serializer.startElement(WebDAV.DAV_NS, "href", "D:href", attrs); serializer.characters(servletPath + collection.getURI()); serializer.endElement(WebDAV.DAV_NS, "href", "D:href"); serializer.startElement(WebDAV.DAV_NS, "propstat", "D:propstat", attrs); serializer.startElement(WebDAV.DAV_NS, "prop", "D:prop", attrs); if(shouldIncludeProperty(type, searchedProperties, DISPLAY_NAME_PROP)) { // write D:displayname String displayName = collection.getURI().lastSegment().toString(); writeSimpleElement(DISPLAY_NAME_PROP, displayName, serializer); } if(shouldIncludeProperty(type, searchedProperties, RESOURCE_TYPE_PROP)) { serializer.startElement(WebDAV.DAV_NS, "resourcetype", "D:resourcetype", attrs); writeEmptyElement(COLLECTION_PROP, serializer); serializer.endElement(WebDAV.DAV_NS, "resourcetype", "D:resourcetype"); } if(shouldIncludeProperty(type, searchedProperties, CREATION_DATE_PROP)) { long created = collection.getCreationTime(); writeSimpleElement(CREATION_DATE_PROP, creationDateFormat.format(new Date(created)), serializer); } if(shouldIncludeProperty(type, searchedProperties, LAST_MODIFIED_PROP)) { // for collections, the last modification date is the same as the creation date long created = collection.getCreationTime(); writeSimpleElement(LAST_MODIFIED_PROP, modificationDateFormat.format(new Date(created)), serializer); } serializer.endElement(WebDAV.DAV_NS, "prop", "D:prop"); writeSimpleElement(STATUS_PROP, "HTTP/1.1 200 OK", serializer); serializer.endElement(WebDAV.DAV_NS, "propstat", "D:propstat"); if(type == FIND_BY_PROPERTY) { List unvisited = searchedProperties.unvisitedProperties(); if(unvisited.size() > 0) { // there were unsupported properties. Report these to the client. serializer.startElement(WebDAV.DAV_NS, "propstat", "D:propstat", attrs); serializer.startElement(WebDAV.DAV_NS, "prop", "D:prop", attrs); for(Iterator i = unvisited.iterator(); i.hasNext(); ) { writeEmptyElement((QName)i.next(), serializer); } serializer.endElement(WebDAV.DAV_NS, "prop", "D:prop"); writeSimpleElement(STATUS_PROP, "HTTP/1.1 404 Not Found", serializer); serializer.endElement(WebDAV.DAV_NS, "propstat", "D:propstat"); } } serializer.endElement(WebDAV.DAV_NS, "response", "D:response"); if(currentDepth++ < maxDepth) { if(collection.getDocumentCount() > 0) { for(Iterator i = collection.iterator(broker); i.hasNext(); ) { DocumentImpl doc = (DocumentImpl)i.next(); try { doc.getUpdateLock().acquire(Lock.READ_LOCK); writeResourceProperties(user, searchedProperties, type, collection, doc, serializer, servletPath); } catch (LockException e) { LOG.debug("Failed to acquire lock on document " + doc.getURI()); } finally { doc.getUpdateLock().release(Lock.READ_LOCK); } } } if(collection.getChildCollectionCount() > 0) { for(Iterator i = collection.collectionIterator(); i.hasNext(); ) { XmldbURI child = (XmldbURI)i.next(); Collection childCollection = null; try { childCollection = broker.openCollection(collection.getURI().append(child), Lock.READ_LOCK); if(childCollection != null) writeCollectionProperties(user, broker, searchedProperties, type, childCollection, serializer, servletPath, maxDepth, currentDepth); } catch (Exception e) { //Doh ! } finally { if(childCollection != null) childCollection.release(Lock.READ_LOCK); } } } } } private void writeResourceProperties(User user, DAVProperties searchedProperties, int type, Collection collection, DocumentImpl resource, SAXSerializer serializer, String servletPath) throws SAXException { if(!resource.getPermissions().validate(user, Permission.READ)) return; DocumentMetadata metadata = resource.getMetadata(); AttributesImpl attrs = new AttributesImpl(); searchedProperties.reset(); serializer.startElement(WebDAV.DAV_NS, "response", "D:response", attrs); // write D:href serializer.startElement(WebDAV.DAV_NS, "href", "D:href", attrs); serializer.characters(servletPath + collection.getURI().append(resource.getFileURI()).toString()); serializer.endElement(WebDAV.DAV_NS, "href", "D:href"); serializer.startElement(WebDAV.DAV_NS, "propstat", "D:propstat", attrs); serializer.startElement(WebDAV.DAV_NS, "prop", "D:prop", attrs); if(shouldIncludeProperty(type, searchedProperties, DISPLAY_NAME_PROP)) { // write D:displayname writeSimpleElement(DISPLAY_NAME_PROP, resource.getFileURI().toString(), serializer); } if(shouldIncludeProperty(type, searchedProperties, RESOURCE_TYPE_PROP)) { writeEmptyElement(RESOURCE_TYPE_PROP, serializer); } if(shouldIncludeProperty(type, searchedProperties, CREATION_DATE_PROP)) { long created = metadata.getCreated(); writeSimpleElement(CREATION_DATE_PROP, creationDateFormat.format(new Date(created)), serializer); } if(shouldIncludeProperty(type, searchedProperties, LAST_MODIFIED_PROP)) { long modified = metadata.getLastModified(); writeSimpleElement(LAST_MODIFIED_PROP, modificationDateFormat.format(new Date(modified)), serializer); } if(shouldIncludeProperty(type, searchedProperties, CONTENT_LENGTH_PROP)) { writeSimpleElement(CONTENT_LENGTH_PROP, Long.toString(resource.getContentLength()), serializer); } if(shouldIncludeProperty(type, searchedProperties, CONTENT_TYPE_PROP)) { writeSimpleElement(CONTENT_TYPE_PROP, metadata.getMimeType(), serializer); } if(shouldIncludeProperty(type, searchedProperties, SUPPORTED_LOCK_PROP)) { serializer.startElement(WebDAV.DAV_NS, "supportedlock", "D:supportedlock", attrs); serializer.startElement(WebDAV.DAV_NS, "lockentry", "D:lockentry", attrs); serializer.startElement(WebDAV.DAV_NS, "lockscope", "D:lockscope", attrs); writeEmptyElement(EXCLUSIVE_LOCK_PROP, serializer); serializer.endElement(WebDAV.DAV_NS, "lockscope", "D:lockscope"); serializer.startElement(WebDAV.DAV_NS, "locktype", "D:locktype", attrs); writeEmptyElement(WRITE_LOCK_PROP, serializer); serializer.endElement(WebDAV.DAV_NS, "locktype", "D:locktype"); serializer.endElement(WebDAV.DAV_NS, "lockentry", "D:lockentry"); serializer.endElement(WebDAV.DAV_NS, "supportedlock", "D:supportedlock"); } if(shouldIncludeProperty(type, searchedProperties, LOCK_DISCOVERY_PROP)){ DocumentMetadata meta = resource.getMetadata(); LockToken token = null; if( meta!=null ){ token = meta.getLockToken(); } else { LOG.info("No Document meta data"); } serializer.startElement(WebDAV.DAV_NS, "lockdiscovery", "D:lockdiscovery", attrs); if(token==null){ // LOG.debug("No lock token"); } else { serializer.startElement(WebDAV.DAV_NS, "activelock", "D:activelock", attrs); String lockType; switch(token.getType()){ case LockToken.LOCK_TYPE_WRITE : lockType="write"; break; default : lockType="none"; } writeSimpleElement(LOCKTYPE_PROP, lockType, serializer); String lockScope; switch(token.getScope()){ case LockToken.LOCK_SCOPE_EXCLUSIVE : lockScope="exclusive"; break; case LockToken.LOCK_SCOPE_SHARED : lockScope="shared"; break; default : lockScope="none"; } writeSimpleElement(LOCK_SCOPE_PROP, lockScope, serializer); String lockDepth; switch(token.getDepth()){ case LockToken.LOCK_DEPTH_INFINIY : lockDepth="Infinity"; break; case LockToken.LOCK_DEPTH_1: lockDepth="1"; break; case LockToken.LOCK_DEPTH_0: lockDepth="0"; break; default : lockDepth="none"; } writeSimpleElement(LOCK_DEPTH_PROP, lockDepth, serializer); writeSimpleElement(LOCK_OWNER_PROP, token.getOwner(), serializer); writeSimpleElement(LOCK_TIMEOUT_PROP, ""+token.getTimeOut(), serializer); serializer.startElement(WebDAV.DAV_NS, "locktoken", "D:locktoken", attrs); serializer.startElement(WebDAV.DAV_NS, "href", "D:href", attrs); serializer.characters( "opaquelocktoken:"+token.getOpaqueLockToken() ); serializer.endElement(WebDAV.DAV_NS, "href", "D:href"); serializer.endElement(WebDAV.DAV_NS, "locktoken", "D:locktoken"); serializer.endElement(WebDAV.DAV_NS, "activelock", "D:activelock"); } serializer.endElement(WebDAV.DAV_NS, "lockdiscovery", "D:lockdiscovery"); } serializer.endElement(WebDAV.DAV_NS, "prop", "D:prop"); writeSimpleElement(STATUS_PROP, "HTTP/1.1 200 OK", serializer); serializer.endElement(WebDAV.DAV_NS, "propstat", "D:propstat"); if(type == FIND_BY_PROPERTY) { List unvisited = searchedProperties.unvisitedProperties(); if(unvisited.size() > 0) { // there were unsupported properties. Report these to the client. serializer.startElement(WebDAV.DAV_NS, "propstat", "D:propstat", attrs); serializer.startElement(WebDAV.DAV_NS, "prop", "D:prop", attrs); for(Iterator i = unvisited.iterator(); i.hasNext(); ) { writeEmptyElement((QName)i.next(), serializer); } serializer.endElement(WebDAV.DAV_NS, "prop", "D:prop"); writeSimpleElement(STATUS_PROP, "HTTP/1.1 404 Not Found", serializer); serializer.endElement(WebDAV.DAV_NS, "propstat", "D:propstat"); } } serializer.endElement(WebDAV.DAV_NS, "response", "D:response"); } private void writePropertyNames(Collection collection, DocumentImpl resource, SAXSerializer serializer, String servletPath) throws SAXException { AttributesImpl attrs = new AttributesImpl(); serializer.startElement(WebDAV.DAV_NS, "response", "D:response", attrs); // write D:href serializer.startElement(WebDAV.DAV_NS, "href", "D:href", attrs); ///TODO : use dedicated function in XmldbURI String href = servletPath + (resource != null ? collection.getURI().toString() + "/" + resource.getFileURI() : collection.getURI().toString()); serializer.characters(href); serializer.endElement(WebDAV.DAV_NS, "href", "D:href"); serializer.startElement(WebDAV.DAV_NS, "propstat", "D:propstat", attrs); serializer.startElement(WebDAV.DAV_NS, "prop", "D:prop", attrs); QName[] defaults = resource == null ? DEFAULT_COLLECTION_PROPS : DEFAULT_RESOURCE_PROPS; for(int i = 0; i < defaults.length; i++) { writeEmptyElement(defaults[i], serializer); } serializer.endElement(WebDAV.DAV_NS, "prop", "D:prop"); writeSimpleElement(STATUS_PROP, "HTTP/1.1 200 OK", serializer); serializer.endElement(WebDAV.DAV_NS, "propstat", "D:propstat"); serializer.endElement(WebDAV.DAV_NS, "response", "D:response"); } private boolean shouldIncludeProperty(int type, DAVProperties properties, QName name) { if(type == FIND_ALL_PROPERTIES) return true; return properties.includeProperty(name); } private void writeEmptyElement(QName qname, SAXSerializer serializer) throws SAXException { serializer.startElement(WebDAV.DAV_NS, qname.getLocalName(), qname.getStringValue(), new AttributesImpl()); serializer.endElement(WebDAV.DAV_NS, qname.getLocalName(), qname.getStringValue()); } private void writeSimpleElement(QName element, String content, SAXSerializer serializer) throws SAXException { serializer.startElement(WebDAV.DAV_NS, element.getLocalName(), element.getStringValue(), new AttributesImpl()); serializer.characters(content); serializer.endElement(WebDAV.DAV_NS, element.getLocalName(), element.getStringValue()); } private void writeResponse(HttpServletResponse response, String content) throws IOException { response.setStatus(WebDAV.SC_MULTI_STATUS); response.setContentType(MimeType.XML_CONTENT_TYPE.getName()); byte[] data = content.getBytes("UTF-8"); response.setContentLength(data.length); OutputStream os = response.getOutputStream(); os.write(data); os.flush(); } private String getServletPath(HttpServletRequest request) { String servletPath = request.getContextPath(); if(servletPath.endsWith("/")) { servletPath = servletPath.substring(0, servletPath.length() - 1); } servletPath += request.getServletPath(); if(servletPath.endsWith("/")) { servletPath = servletPath.substring(0, servletPath.length() - 1); } return servletPath; } protected int getDepth(HttpServletRequest req) { int depth = 1; // Depth: infitiy String depthStr = req.getHeader("Depth"); if (depthStr != null && depthStr.equals("0")) { depth = 0; } return depth; } private void getPropertyNames(Node propNode, DAVProperties properties) { NodeList childList = propNode.getChildNodes(); for(int i = 0; i < childList.getLength(); i++) { Node currentNode = childList.item(i); if(currentNode.getNodeType() == Node.ELEMENT_NODE) { properties.add(currentNode); } } } public static String xmlToString(Node node) { try { Source source = new DOMSource(node); StringWriter stringWriter = new StringWriter(); Result result = new StreamResult(stringWriter); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.transform(source, result); return stringWriter.getBuffer().toString(); } catch (TransformerConfigurationException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } return null; } private static class Visited { boolean visited = false; boolean isVisited() { return visited; } void setVisited(boolean visit) { visited = visit; } } private static class DAVProperties extends HashMap { DAVProperties() { super(); } void add(Node node) { QName qname = new QName(node.getLocalName(), node.getNamespaceURI()); if(node.getNamespaceURI().equals(WebDAV.DAV_NS)) qname.setPrefix(PREFIX); else qname.setPrefix(node.getPrefix()); if(!containsKey(qname)) put(qname, new Visited()); } boolean includeProperty(QName property) { Visited visited = (Visited)get(property); if(visited == null) return false; boolean include = !visited.isVisited(); visited.setVisited(true); return include; } List unvisitedProperties() { List list = new ArrayList(5); for(Iterator i = entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry)i.next(); if(!((Visited)entry.getValue()).visited) list.add(entry.getKey()); } return list; } void reset() { for(Iterator i = values().iterator(); i.hasNext(); ) { Visited visited = (Visited)i.next(); visited.setVisited(false); } } } }