Commit Diff


commit - 557565a3485f3bceaaedfceaed9c90915eb457d4
commit + 17f2d4cbb747e0c9349d8500acf02d15a620c00f
blob - 4d645e1afa7c6fd288e8da760d89064436692577
blob + 514a1b9f2955d0a9825b8c0d0ddbb9a1d0a3e359
--- modules/vfs.s3/pom.xml
+++ modules/vfs.s3/pom.xml
@@ -17,13 +17,8 @@
 
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>moxo</artifactId>
-        <groupId>com.thinkberg.moxo</groupId>
-        <version>1.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
-    </parent>
     <modelVersion>4.0.0</modelVersion>
+    <groupId>com.thinkberg</groupId>
     <artifactId>vfs.s3</artifactId>
     <version>1.0-SNAPSHOT</version>
     <name>thinkberg.com S3 VFS provider</name>
@@ -69,11 +64,21 @@
         </resources>
         <plugins>
             <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <verbose>true</verbose>
+                    <fork>true</fork>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
                     <includes>
-                        <include>**/tests/*.java</include>
+                        <include>**/tests/S3FileNameTest.java</include>
+                        <include>**/tests/S3FileProviderTest.java</include>
                     </includes>
                 </configuration>
             </plugin>
blob - ff8178919c5992604e3c2cd499dda4b4f2804614
blob + 3353936723407d8aaf9b55fb8cc0cec4601cf2e3
--- modules/vfs.s3/src/main/java/com/thinkberg/vfs/s3/jets3t/Jets3tFileObject.java
+++ modules/vfs.s3/src/main/java/com/thinkberg/vfs/s3/jets3t/Jets3tFileObject.java
@@ -18,9 +18,7 @@ package com.thinkberg.vfs.s3.jets3t;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.commons.vfs.FileName;
-import org.apache.commons.vfs.FileObject;
-import org.apache.commons.vfs.FileType;
+import org.apache.commons.vfs.*;
 import org.apache.commons.vfs.provider.AbstractFileObject;
 import org.apache.commons.vfs.util.MonitorOutputStream;
 import org.jets3t.service.Constants;
@@ -34,7 +32,10 @@ import java.io.*;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
 import java.nio.channels.ReadableByteChannel;
+import java.util.Arrays;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 
 /**
@@ -121,10 +122,46 @@ public class Jets3tFileObject extends AbstractFileObje
     attached = false;
   }
 
-  protected void doRename(FileObject newfile) throws Exception {
-    super.doRename(newfile);
+  protected void doRename(FileObject targetFileObject) throws Exception {
+    String bucketId = bucket.getName();
+    S3Object targetObject = ((Jets3tFileObject) targetFileObject).object;
+
+    LOG.debug(String.format("move object '%s' to '%s'", getS3Key(), targetObject.getKey()));
+
+    // if this is a folder, then rename all children of the current folder too
+    if (FileType.FOLDER.equals(getType())) {
+      String path = object.getKey();
+      // make sure we add a '/' slash at the end to find children
+      if (!"".equals(path)) {
+        path = path + "/";
+      }
+
+      try {
+        S3Object[] children = service.listObjects(bucket, path, null);
+        LOG.debug(children);
+        String targetName = targetObject.getKey();
+        for (S3Object child : children) {
+          String targetChildName = child.getKey();
+          targetChildName = targetName + targetChildName.substring(object.getKey().length());
+          service.renameObject(bucketId, child.getKey(), new S3Object(bucket, targetChildName));
+        }
+      } catch (S3ServiceException e) {
+        throw new FileSystemException(String.format("can't move children of '%s' to '%s'", object.getKey(), targetObject.getKey()), e);
+      }
+    }
+
+    try {
+      service.renameObject(bucket.getName(), object.getKey(), ((Jets3tFileObject) targetFileObject).object);
+    } catch (S3ServiceException e) {
+      throw new FileSystemException("can't rename  object", e);
+    }
   }
 
+  @Override
+  public void copyFrom(FileObject file, FileSelector selector) throws FileSystemException {
+    super.copyFrom(file, selector);
+  }
+
   protected void doCreateFolder() throws Exception {
     if (!Mimetypes.MIMETYPE_JETS3T_DIRECTORY.equals(object.getContentType())) {
       object.setContentType(Mimetypes.MIMETYPE_JETS3T_DIRECTORY);
@@ -144,6 +181,7 @@ public class Jets3tFileObject extends AbstractFileObje
 
   protected void doSetLastModifiedTime(final long modtime) throws Exception {
     object.addMetadata(Constants.REST_METADATA_PREFIX + VFS_LAST_MODIFIED_TIME, modtime);
+    service.updateObjectMetadata(bucket.getName(), object);
   }
 
   protected InputStream doGetInputStream() throws Exception {
@@ -172,6 +210,7 @@ public class Jets3tFileObject extends AbstractFileObje
       protected void onClose() throws IOException {
         try {
           LOG.debug(String.format("sending '%s' to storage (cached=%b)", object.getKey(), cacheFile));
+          LOG.debug(object);
           if (cacheFile != null) {
             FileChannel cacheFc = getCacheFile().getChannel();
             object.setContentLength(cacheFc.size());
@@ -198,29 +237,56 @@ public class Jets3tFileObject extends AbstractFileObje
     return FileType.FILE;
   }
 
-  protected String[] doListChildren() throws Exception {
+  protected String[] doListChildren() throws FileSystemException {
     String path = object.getKey();
     // make sure we add a '/' slash at the end to find children
     if (!"".equals(path)) {
       path = path + "/";
     }
 
-    S3Object[] children = service.listObjects(bucket, path, "/");
-    String[] childrenNames = new String[children.length];
-    for (int i = 0; i < children.length; i++) {
-      if (!children[i].getKey().equals(path)) {
-        // strip path from name (leave only base name)
-        childrenNames[i] = children[i].getKey().replaceAll("[^/]*//*", "");
+    try {
+      S3Object[] children = service.listObjects(bucket, path, "/");
+      LOG.debug(Arrays.asList(children));
+      LOG.debug(Arrays.asList(children));
+      String[] childrenNames = new String[children.length];
+      for (int i = 0; i < children.length; i++) {
+        if (!children[i].getKey().equals(path)) {
+          // strip path from name (leave only base name)
+          childrenNames[i] = children[i].getKey().replaceAll("[^/]*//*", "");
+        }
       }
-    }
 
-    return childrenNames;
+      return childrenNames;
+    } catch (S3ServiceException e) {
+      throw new FileSystemException(String.format("can't list children of '%s'", path), e);
+    }
   }
 
   protected long doGetContentSize() throws Exception {
     return object.getContentLength();
   }
 
+  @SuppressWarnings("unchecked")
+  protected Map doGetAttributes() throws Exception {
+    Map metaData = object.getModifiableMetadata();
+    Map attributes = new HashMap<Object, Object>(metaData.size());
+    for (Object key : metaData.keySet()) {
+      if (((String) key).startsWith(Constants.REST_METADATA_PREFIX)) {
+        attributes.put(((String) key).substring(Constants.REST_METADATA_PREFIX.length()), metaData.get(key));
+      } else {
+        attributes.put(key, metaData.get(key));
+      }
+    }
+    LOG.debug(String.format("%s[%s]", object.getKey(), attributes));
+    return attributes;
+  }
+
+  @SuppressWarnings("unchecked")
+  protected void doSetAttribute(String attrName, Object value) throws Exception {
+    object.addMetadata(Constants.REST_METADATA_PREFIX + attrName, value);
+    service.updateObjectMetadata(bucket.getName(), object);
+  }
+
   // Utility methods
   /**
    * Create an S3 key from a commons-vfs path. This simply
blob - 4ab104f040636ab7b267aed201ad98af0049f444
blob + 9e5648fcc4e4e7515289b475da7bb2992a230de9
--- modules/vfs.s3/src/test/java/com/thinkberg/vfs/s3/tests/S3FileProviderTest.java
+++ modules/vfs.s3/src/test/java/com/thinkberg/vfs/s3/tests/S3FileProviderTest.java
@@ -42,6 +42,9 @@ public class S3FileProviderTest extends S3TestCase {
   };
   private static final String FOLDER = "/directory";
   private static final String FILE = FOLDER + "/newfile.txt";
+  private static final String FILE_NON_EXISTING = "/nonexisting.txt";
+  private static final String ATTR_TESTKEY = "TESTKEY";
+  protected static final String ATTR_TESTVALUE = "TESTVALUE";
 
   static {
     LogFactory.getLog(S3FileProviderTest.class).debug("initializing ...");
@@ -106,12 +109,22 @@ public class S3FileProviderTest extends S3TestCase {
 
   public void testGetFolderListing() throws FileSystemException {
     FileObject object = ROOT.resolveFile(FOLDER);
-    FileObject[] files = object.findFiles(new DepthFileSelector(1));
-    assertEquals(3, files.length);
+    assertEquals(2, object.getChildren().length);
   }
 
+  public void testGetFolderListingIsShallow() throws FileSystemException {
+    FileObject object = ROOT.resolveFile(FOLDER);
+    FileObject subFolder = object.resolveFile("subdir");
+    subFolder.createFolder();
+    subFolder.resolveFile("subfile.0").createFile();
+    subFolder.resolveFile("subfile.1").createFile();
+    subFolder.resolveFile("subfile.2").createFile();
+
+    assertEquals(3, object.getChildren().length);
+  }
+
   public void testMissingFile() throws FileSystemException {
-    FileObject object = ROOT.resolveFile("/nonexisting.txt");
+    FileObject object = ROOT.resolveFile(FILE_NON_EXISTING);
     assertFalse(object.exists());
   }
 
@@ -137,7 +150,42 @@ public class S3FileProviderTest extends S3TestCase {
     assertFalse(object.exists());
   }
 
-  public void testCopyFolder() throws FileSystemException {
+  public void testCopyFile() throws FileSystemException {
+    FileObject srcObject = ROOT.resolveFile(FILE);
+    srcObject.createFile();
+    assertTrue("source object should exist", srcObject.exists());
+    FileObject dstObject = ROOT.resolveFile(FILE + ".dst");
+    assertFalse("destination should not exist", dstObject.exists());
+    dstObject.copyFrom(srcObject, ALL_FILE_SELECTOR);
+    assertTrue("destination should exist after copy", dstObject.exists());
+
+    srcObject.delete();
+    dstObject.delete();
+  }
+
+  public void testCopyFileWithAttribute() throws FileSystemException {
+    if (!BUCKETID.startsWith("s3:")) {
+      FileObject srcObject = ROOT.resolveFile(FILE);
+      srcObject.createFile();
+      srcObject.getContent().setAttribute(ATTR_TESTKEY, ATTR_TESTVALUE);
+      assertTrue("source object should exist", srcObject.exists());
+      assertEquals("source object attribute missing",
+                   ATTR_TESTVALUE, srcObject.getContent().getAttribute(ATTR_TESTKEY));
+      FileObject dstObject = ROOT.resolveFile(FILE + ".dst");
+      assertFalse("destination should not exist", dstObject.exists());
+      dstObject.copyFrom(srcObject, ALL_FILE_SELECTOR);
+      assertTrue("destination should exist after copy", dstObject.exists());
+      assertEquals("destination object attribute missing",
+                   ATTR_TESTVALUE, dstObject.getContent().getAttribute(ATTR_TESTKEY));
+
+      srcObject.delete();
+      dstObject.delete();
+    } else {
+      LogFactory.getLog(S3FileProviderTest.class).info(String.format("ignoring property test for '%s'", ROOT));
+    }
+  }
+
+  public void testCopyShallowFolder() throws FileSystemException {
     FileObject origFolder = ROOT.resolveFile(FOLDER);
     origFolder.createFolder();
 
@@ -145,17 +193,17 @@ public class S3FileProviderTest extends S3TestCase {
     origFolder.resolveFile("file.1").createFile();
     origFolder.resolveFile("file.2").createFile();
 
-    FileObject[] origFiles = origFolder.findFiles(new DepthFileSelector(1));
-    assertEquals(4, origFiles.length);
+    assertEquals(3, origFolder.getChildren().length);
 
     FileObject destFolder = ROOT.resolveFile(FOLDER + "_dest");
     assertFalse(destFolder.exists());
     destFolder.copyFrom(origFolder, new DepthFileSelector(1));
     assertTrue(destFolder.exists());
 
+    assertEquals(3, destFolder.getChildren().length);
+
+    FileObject[] origFiles = origFolder.findFiles(new DepthFileSelector(1));
     FileObject[] destFiles = destFolder.findFiles(new DepthFileSelector(1));
-    assertEquals(4, destFiles.length);
-
     for (int i = 0; i < origFiles.length; i++) {
       assertEquals(origFiles[i].getName().getRelativeName(origFolder.getName()),
                    destFiles[i].getName().getRelativeName(destFolder.getName()));
@@ -168,10 +216,67 @@ public class S3FileProviderTest extends S3TestCase {
     assertFalse(destFolder.exists());
   }
 
-//  public void testMoveFolder() throws FileSystemException {
-//
-//  }
+  public void testMoveShallowFolder() throws FileSystemException {
+    FileObject origFolder = ROOT.resolveFile(FOLDER);
+    origFolder.delete(ALL_FILE_SELECTOR);
+    origFolder.createFolder();
 
+    origFolder.resolveFile("file.0").createFile();
+    origFolder.resolveFile("file.1").createFile();
+    origFolder.resolveFile("file.2").createFile();
+
+    assertEquals(3, origFolder.getChildren().length);
+
+    FileObject destFolder = ROOT.resolveFile(FOLDER + "_dest");
+    destFolder.delete(ALL_FILE_SELECTOR);
+    assertFalse(destFolder.exists());
+
+    origFolder.moveTo(destFolder);
+    assertFalse(origFolder.exists());
+    assertTrue(destFolder.exists());
+
+    assertEquals(3, destFolder.getChildren().length);
+
+    destFolder.delete(ALL_FILE_SELECTOR);
+
+    assertFalse(origFolder.exists());
+    assertFalse(destFolder.exists());
+  }
+
+  public void testMoveDeepFolder() throws FileSystemException {
+    FileObject origFolder = ROOT.resolveFile(FOLDER);
+    origFolder.delete(ALL_FILE_SELECTOR);
+    origFolder.createFolder();
+
+    origFolder.resolveFile("file.0").createFile();
+    origFolder.resolveFile("file.1").createFile();
+    origFolder.resolveFile("file.2").createFile();
+    origFolder.resolveFile("subfolder").createFolder();
+    origFolder.resolveFile("subfolder").resolveFile("subfile.0").createFile();
+    origFolder.resolveFile("subfolder").resolveFile("subfile.1").createFile();
+    origFolder.resolveFile("subfolder").resolveFile("subfile.2").createFile();
+
+
+    FileObject[] origFiles = origFolder.findFiles(ALL_FILE_SELECTOR);
+    assertEquals(8, origFiles.length);
+
+    FileObject destFolder = ROOT.resolveFile(FOLDER + "_dest");
+    destFolder.delete(ALL_FILE_SELECTOR);
+    assertFalse(destFolder.exists());
+
+    origFolder.moveTo(destFolder);
+    assertFalse(origFolder.exists());
+    assertTrue(destFolder.exists());
+
+    FileObject[] destFiles = destFolder.findFiles(ALL_FILE_SELECTOR);
+    assertEquals(8, destFiles.length);
+
+    destFolder.delete(ALL_FILE_SELECTOR);
+
+    assertFalse(origFolder.exists());
+    assertFalse(destFolder.exists());
+  }
+
   public void testCloseFileSystem() throws FileSystemException {
     VFS.getManager().closeFileSystem(ROOT.getFileSystem());
   }
blob - c592b4c0538d04698f16be3c7f792e4cf706d0f8
blob + ab1a74fa06b6c50787d3d4de5596e04bba672562
--- modules/webdav/pom.xml
+++ modules/webdav/pom.xml
@@ -17,13 +17,8 @@
 
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>moxo</artifactId>
-        <groupId>com.thinkberg.moxo</groupId>
-        <version>1.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
-    </parent>
     <modelVersion>4.0.0</modelVersion>
+    <groupId>com.thinkberg</groupId>
     <artifactId>webdav</artifactId>
     <version>1.0-SNAPSHOT</version>
     <name>thinkberg.com WebDAV</name>
@@ -48,7 +43,21 @@
             <artifactId>commons-codec</artifactId>
             <version>1.3</version>
         </dependency>
-
+        <dependency>
+            <groupId>dom4j</groupId>
+            <artifactId>dom4j</artifactId>
+            <version>1.6.1</version>
+        </dependency>
+        <dependency>
+            <groupId>jaxen</groupId>
+            <artifactId>jaxen</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+        </dependency>
     </dependencies>
     <build>
         <resources>
@@ -58,6 +67,15 @@
         </resources>
         <plugins>
             <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <verbose>true</verbose>
+                    <fork>true</fork>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
blob - 09b60a7b33e721815fda022a1e7167429e306ca8
blob + bc0c6eff3013f0bb36bc5e4f530ddb7d5d520ba1
--- modules/webdav/src/main/java/com/thinkberg/webdav/DeleteHandler.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/DeleteHandler.java
@@ -24,11 +24,12 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.commons.vfs.FileObject;
 import org.apache.commons.vfs.FileSelectInfo;
 import org.apache.commons.vfs.FileSelector;
-import org.mortbay.jetty.Request;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.text.ParseException;
 
 /**
@@ -50,12 +51,15 @@ public class DeleteHandler extends WebdavHandler {
 
   public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
     FileObject object = VFSBackend.resolveFile(request.getPathInfo());
-    if (request instanceof Request) {
-      String fragment = ((Request) request).getUri().getFragment();
+
+    try {
+      String fragment = new URI(request.getRequestURI()).getFragment();
       if (fragment != null) {
         response.sendError(HttpServletResponse.SC_FORBIDDEN);
         return;
       }
+    } catch (URISyntaxException e) {
+      throw new IOException(e.getMessage());
     }
 
     try {
blob - 204d06d06be8367f5bb86da172606b16b3c525ab
blob + c47185b358b6ad5571018d737169ae5e2aa40ad1
--- modules/webdav/src/main/java/com/thinkberg/webdav/LockHandler.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/LockHandler.java
@@ -147,7 +147,7 @@ public class LockHandler extends WebdavHandler {
     logXml(propDoc);
   }
 
-  private void logXml(Node element) {
+  protected void logXml(Node element) {
     ByteArrayOutputStream bos = new ByteArrayOutputStream();
     try {
       XMLWriter xmlWriter = new XMLWriter(bos, OutputFormat.createPrettyPrint());
blob - 21fb7f4fd07568da5160aadb3563b7b18d3ed5f1
blob + 5635c52c1fce56d483cbf7f66f81e52dad5c6c75
--- modules/webdav/src/main/java/com/thinkberg/webdav/MoveHandler.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/MoveHandler.java
@@ -16,7 +16,6 @@
 
 package com.thinkberg.webdav;
 
-import com.thinkberg.webdav.vfs.DepthFileSelector;
 import org.apache.commons.vfs.FileObject;
 import org.apache.commons.vfs.FileSystemException;
 
@@ -26,7 +25,6 @@ import org.apache.commons.vfs.FileSystemException;
  */
 public class MoveHandler extends CopyMoveBase {
   protected void copyOrMove(FileObject object, FileObject target, int depth) throws FileSystemException {
-    target.copyFrom(object, new DepthFileSelector(depth));
-    object.delete(new DepthFileSelector());
+    object.moveTo(target);
   }
 }
blob - 00a7383b8d339f22c7242ad22205a99bfaec87cf
blob + d7233be643552cc949f8535d2bdc4aade8257493
--- modules/webdav/src/main/java/com/thinkberg/webdav/PropFindHandler.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/PropFindHandler.java
@@ -24,17 +24,19 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.vfs.FileObject;
 import org.apache.commons.vfs.FileSystemException;
-import org.dom4j.*;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
 import org.dom4j.io.OutputFormat;
 import org.dom4j.io.SAXReader;
 import org.dom4j.io.XMLWriter;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.URL;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -42,26 +44,19 @@ import java.util.List;
  * @version $Id$
  */
 public class PropFindHandler extends WebdavHandler {
-  private static final String TAG_PROP = "prop";
-  private static final String TAG_ALLPROP = "allprop";
-  private static final String TAG_PROPNAMES = "propnames";
   private static final String TAG_MULTISTATUS = "multistatus";
   private static final String TAG_HREF = "href";
   private static final String TAG_RESPONSE = "response";
+
+  private static final String TAG_ALLPROP = "allprop";
+  private static final String TAG_PROPNAMES = "propnames";
+  private static final String TAG_PROP = "prop";
+
+  private static final List<String> VALID_PROPFIND_TAGS = Arrays.asList(
+          TAG_ALLPROP, TAG_PROPNAMES, TAG_PROP
+  );
   private static final Log LOG = LogFactory.getLog(PropFindHandler.class);
 
-  void logXml(Node element) {
-    ByteArrayOutputStream bos = new ByteArrayOutputStream();
-    try {
-      XMLWriter xmlWriter = new XMLWriter(bos, OutputFormat.createPrettyPrint());
-      xmlWriter.write(element);
-      LOG.debug(bos.toString());
-    } catch (IOException e) {
-      LOG.error(e.getMessage());
-    }
-  }
-
-
   public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
     SAXReader saxReader = new SAXReader();
     try {
@@ -69,59 +64,46 @@ public class PropFindHandler extends WebdavHandler {
       logXml(propDoc);
 
       Element propFindEl = propDoc.getRootElement();
-      Element propEl = (Element) propFindEl.elementIterator().next();
-      String propElName = propEl.getName();
+      for (Object propElObject : propFindEl.elements()) {
+        Element propEl = (Element) propElObject;
+        if (VALID_PROPFIND_TAGS.contains(propEl.getName())) {
+          FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+          if (object.exists()) {
+            // respond as XML encoded multi status
+            response.setContentType("text/xml");
+            response.setCharacterEncoding("UTF-8");
+            response.setStatus(SC_MULTI_STATUS);
 
-      List<String> requestedProperties = new ArrayList<String>();
-      boolean ignoreValues = false;
-      if (TAG_PROP.equals(propElName)) {
-        for (Object id : propEl.elements()) {
-          requestedProperties.add(((Element) id).getName());
+            Document multiStatusResponse =
+                    getMultiStatusResponse(object,
+                                           propEl,
+                                           getBaseUrl(request),
+                                           getDepth(request));
+            logXml(multiStatusResponse);
+
+            // write the actual response
+            XMLWriter writer = new XMLWriter(response.getWriter(), OutputFormat.createCompactFormat());
+            writer.write(multiStatusResponse);
+            writer.flush();
+            writer.close();
+
+          } else {
+            LOG.error(object.getName().getPath() + " NOT FOUND");
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+          }
+          break;
         }
-      } else if (TAG_ALLPROP.equals(propElName)) {
-        requestedProperties = DavResource.ALL_PROPERTIES;
-      } else if (TAG_PROPNAMES.equals(propElName)) {
-        requestedProperties = DavResource.ALL_PROPERTIES;
-        ignoreValues = true;
       }
-
-      FileObject object = VFSBackend.resolveFile(request.getPathInfo());
-      if (object.exists()) {
-        // respond as XML encoded multi status
-        response.setContentType("text/xml");
-        response.setCharacterEncoding("UTF-8");
-        response.setStatus(SC_MULTI_STATUS);
-
-        Document multiStatusResponse =
-                getMultiStatusRespons(object,
-                                      requestedProperties,
-                                      getBaseUrl(request),
-                                      getDepth(request),
-                                      ignoreValues);
-        logXml(multiStatusResponse);
-
-        // write the actual response
-        XMLWriter writer = new XMLWriter(response.getWriter(), OutputFormat.createCompactFormat());
-        writer.write(multiStatusResponse);
-        writer.flush();
-        writer.close();
-
-      } else {
-        LOG.error(object.getName().getPath() + " NOT FOUND");
-        response.sendError(HttpServletResponse.SC_NOT_FOUND);
-      }
     } catch (DocumentException e) {
       LOG.error("invalid request: " + e.getMessage());
       response.sendError(HttpServletResponse.SC_BAD_REQUEST);
     }
   }
 
-  @SuppressWarnings({"ConstantConditions"})
-  private Document getMultiStatusRespons(FileObject object,
-                                         List<String> requestedProperties,
-                                         URL baseUrl,
-                                         int depth,
-                                         boolean ignoreValues) throws FileSystemException {
+  private Document getMultiStatusResponse(FileObject object,
+                                          Element propEl,
+                                          URL baseUrl,
+                                          int depth) throws FileSystemException {
     Document propDoc = DocumentHelper.createDocument();
     propDoc.setXMLEncoding("UTF-8");
 
@@ -131,14 +113,12 @@ public class PropFindHandler extends WebdavHandler {
       Element responseEl = multiStatus.addElement(TAG_RESPONSE);
       try {
         URL url = new URL(baseUrl, URLEncoder.encode(child.getName().getPath(), "UTF-8"));
-        LOG.debug(url);
         responseEl.addElement(TAG_HREF).addText(url.toExternalForm());
       } catch (Exception e) {
-        e.printStackTrace();
+        LOG.error("can't set href in response", e);
       }
       DavResource resource = DavResourceFactory.getInstance().getDavResource(child);
-      resource.setIgnoreValues(ignoreValues);
-      resource.serializeToXml(responseEl, requestedProperties);
+      resource.getPropertyValues(responseEl, propEl);
     }
     return propDoc;
   }
blob - 07ffae65a4113a2f5526d060bb9abd511fc50a5a
blob + 50f915eb7b1c5158cd909b49560628c9b14048a8
--- modules/webdav/src/main/java/com/thinkberg/webdav/PropPatchHandler.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/PropPatchHandler.java
@@ -16,13 +16,16 @@
 
 package com.thinkberg.webdav;
 
+import com.thinkberg.webdav.data.AbstractDavResource;
 import com.thinkberg.webdav.data.DavResource;
+import com.thinkberg.webdav.data.DavResourceFactory;
 import com.thinkberg.webdav.lock.LockException;
 import com.thinkberg.webdav.lock.LockManager;
 import com.thinkberg.webdav.vfs.VFSBackend;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
 import org.dom4j.Document;
 import org.dom4j.DocumentException;
 import org.dom4j.DocumentHelper;
@@ -36,6 +39,7 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.net.URL;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -47,6 +51,10 @@ import java.util.List;
 public class PropPatchHandler extends WebdavHandler {
   private static final Log LOG = LogFactory.getLog(PropPatchHandler.class);
 
+  private static final String TAG_MULTISTATUS = "multistatus";
+  private static final String TAG_HREF = "href";
+  private static final String TAG_RESPONSE = "response";
+
   public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
     FileObject object = VFSBackend.resolveFile(request.getPathInfo());
 
@@ -63,76 +71,66 @@ public class PropPatchHandler extends WebdavHandler {
       return;
     }
 
-    SAXReader saxReader = new SAXReader();
-    try {
-      Document propDoc = saxReader.read(request.getInputStream());
-//      log(propDoc);
+    if (object.exists()) {
+      SAXReader saxReader = new SAXReader();
+      try {
+        Document propDoc = saxReader.read(request.getInputStream());
+        logXml(propDoc);
 
-      response.setContentType("text/xml");
-      response.setCharacterEncoding("UTF-8");
-      response.setStatus(SC_MULTI_STATUS);
-
-      if (object.exists()) {
-        Document resultDoc = DocumentHelper.createDocument();
-        Element multiStatusResponse = resultDoc.addElement("multistatus", "DAV:");
-        Element responseEl = multiStatusResponse.addElement("response");
-        try {
-          URL url = new URL(getBaseUrl(request), URLEncoder.encode(object.getName().getPath(), "UTF-8"));
-          LOG.debug(url);
-          responseEl.addElement("href").addText(url.toExternalForm());
-        } catch (Exception e) {
-          e.printStackTrace();
-        }
-
-        Element propstatEl = responseEl.addElement("propstat");
-        Element propEl = propstatEl.addElement("prop");
-
-        Element propertyUpdateEl = propDoc.getRootElement();
-        for (Object elObject : propertyUpdateEl.elements()) {
+        Element propUpdateEl = propDoc.getRootElement();
+        List<Element> requestedProperties = new ArrayList<Element>();
+        for (Object elObject : propUpdateEl.elements()) {
           Element el = (Element) elObject;
-          if ("set".equals(el.getName())) {
-            for (Object propObject : el.elements()) {
-              setProperty(propEl, object, (Element) propObject);
+          String command = el.getName();
+          if (AbstractDavResource.TAG_PROP_SET.equals(command) || AbstractDavResource.TAG_PROP_REMOVE.equals(command)) {
+            for (Object propElObject : el.elements()) {
+              for (Object propNameElObject : ((Element) propElObject).elements()) {
+                Element propNameEl = (Element) propNameElObject;
+                requestedProperties.add(propNameEl);
+              }
             }
-          } else if ("remove".equals(el.getName())) {
-            for (Object propObject : el.elements()) {
-              removeProperty(propEl, object, (Element) propObject);
-            }
           }
         }
-        propstatEl.addElement("status").addText(DavResource.STATUS_403);
 
-//        log(resultDoc);
+        // respond as XML encoded multi status
+        response.setContentType("text/xml");
+        response.setCharacterEncoding("UTF-8");
+        response.setStatus(SC_MULTI_STATUS);
 
+        Document multiStatusResponse = getMultiStatusResponse(object, requestedProperties, getBaseUrl(request));
+
+        logXml(multiStatusResponse);
+
         // write the actual response
         XMLWriter writer = new XMLWriter(response.getWriter(), OutputFormat.createCompactFormat());
-        writer.write(resultDoc);
+        writer.write(multiStatusResponse);
         writer.flush();
         writer.close();
-      } else {
-        LOG.error(object.getName().getPath() + " NOT FOUND");
-        response.sendError(HttpServletResponse.SC_NOT_FOUND);
-      }
-    } catch (DocumentException e) {
-      LOG.error("invalid request: " + e.getMessage());
-      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-    }
-  }
 
-  private void setProperty(Element root, FileObject object, Element el) {
-    List propList = el.elements();
-    for (Object propElObject : propList) {
-      Element propEl = (Element) propElObject;
-      for (int i = 0; i < propEl.nodeCount(); i++) {
-        propEl.node(i).detach();
+      } catch (DocumentException e) {
+        LOG.error("invalid request: " + e.getMessage());
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
       }
-      root.add(propEl.detach());
+    } else {
+      LOG.error(object.getName().getPath() + " NOT FOUND");
+      response.sendError(HttpServletResponse.SC_NOT_FOUND);
     }
   }
 
-  private void removeProperty(Element root, FileObject object, Element el) {
-    setProperty(root, object, el);
-  }
+  private Document getMultiStatusResponse(FileObject object, List<Element> requestedProperties, URL baseUrl) throws FileSystemException {
+    Document propDoc = DocumentHelper.createDocument();
+    propDoc.setXMLEncoding("UTF-8");
 
-
+    Element multiStatus = propDoc.addElement(TAG_MULTISTATUS, "DAV:");
+    Element responseEl = multiStatus.addElement(TAG_RESPONSE);
+    try {
+      URL url = new URL(baseUrl, URLEncoder.encode(object.getName().getPath(), "UTF-8"));
+      responseEl.addElement(TAG_HREF).addText(url.toExternalForm());
+    } catch (Exception e) {
+      LOG.error("can't set HREF tag in response", e);
+    }
+    DavResource resource = DavResourceFactory.getInstance().getDavResource(object);
+    resource.setPropertyValues(responseEl, requestedProperties);
+    return propDoc;
+  }
 }
blob - bca6db1498389193b487c1a5c50753dda989f4c5
blob + c4c58177f10fdcc3fa24ad733291fa87a7f918e5
--- modules/webdav/src/main/java/com/thinkberg/webdav/WebdavHandler.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/WebdavHandler.java
@@ -21,9 +21,13 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.vfs.FileObject;
 import org.apache.commons.vfs.FileSystemException;
+import org.dom4j.Node;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.XMLWriter;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -147,4 +151,15 @@ public abstract class WebdavHandler {
     }
     return -1;
   }
+
+  void logXml(Node element) {
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    try {
+      XMLWriter xmlWriter = new XMLWriter(bos, OutputFormat.createPrettyPrint());
+      xmlWriter.write(element);
+      LogFactory.getLog(this.getClass()).debug(bos.toString());
+    } catch (IOException e) {
+      LogFactory.getLog(this.getClass()).error(e.getMessage());
+    }
+  }
 }
blob - 953b5f534b2c224ad5eb65cd370f36d6232bca2d
blob + 693bd57dff62a318333b2d882a4f4668698b55d2
--- modules/webdav/src/main/java/com/thinkberg/webdav/data/AbstractDavResource.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/data/AbstractDavResource.java
@@ -16,50 +16,196 @@
 
 package com.thinkberg.webdav.data;
 
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileContent;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
 import org.dom4j.Element;
+import org.dom4j.Node;
 
-import java.util.HashSet;
+import java.util.Arrays;
 import java.util.List;
-import java.util.Set;
 
 /**
  * @author Matthias L. Jugel
  * @version $Id$
  */
 public abstract class AbstractDavResource {
-  private static final String STATUS_200 = "HTTP/1.1 200 OK";
-  private static final String STATUS_404 = "HTTP/1.1 404 Not Found";
+  public static final String STATUS_200 = "HTTP/1.1 200 OK";
+  public static final String STATUS_404 = "HTTP/1.1 404 Not Found";
   public static final String STATUS_403 = "HTTP/1.1 403 Forbidden";
+  public static final String STATUS_422 = "HTTP/1.1 422 Unprocessable Entity";
 
-  private static final String TAG_PROPSTAT = "propstat";
-  private static final String TAG_PROP = "prop";
-  private static final String TAG_STATUS = "status";
+  public static final String TAG_ALLPROP = "allprop";
+  public static final String TAG_PROPNAMES = "propnames";
 
-  public Element serializeToXml(Element root, List<String> requestedProperties) {
-    Element propStatEl = root.addElement(TAG_PROPSTAT);
-    Element propEl = propStatEl.addElement(TAG_PROP);
+  public static final String TAG_PROPSTAT = "propstat";
+  public static final String TAG_PROP = "prop";
+  public static final String TAG_STATUS = "status";
+  public static final String TAG_PROP_SET = "set";
+  public static final String TAG_PROP_REMOVE = "remove";
 
-    Set<String> missingProperties = new HashSet<String>();
-    for (String propertyName : requestedProperties) {
-      if (!addPropertyValue(propEl, propertyName)) {
-        missingProperties.add(propertyName);
+  // @see http://www.webdav.org/specs/rfc2518.html#dav.properties
+  public static final String PROP_CREATION_DATE = "creationdate";
+  public static final String PROP_DISPLAY_NAME = "displayname";
+  public static final String PROP_GET_CONTENT_LANGUAGE = "getcontentlanguage";
+  public static final String PROP_GET_CONTENT_LENGTH = "getcontentlength";
+  public static final String PROP_GET_CONTENT_TYPE = "getcontenttype";
+  public static final String PROP_GET_ETAG = "getetag";
+  public static final String PROP_GET_LAST_MODIFIED = "getlastmodified";
+  public static final String PROP_LOCK_DISCOVERY = "lockdiscovery";
+  public static final String PROP_RESOURCETYPE = "resourcetype";
+  public static final String PROP_SOURCE = "source";
+  public static final String PROP_SUPPORTED_LOCK = "supportedlock";
+
+  // non-standard properties
+  static final String PROP_QUOTA = "quota";
+  static final String PROP_QUOTA_USED = "quotaused";
+  static final String PROP_QUOTA_AVAILABLE_BYTES = "quota-available-bytes";
+  static final String PROP_QUOTA_USED_BYTES = "quota-used-bytes";
+
+  // list of standard supported properties (for allprop/propname)
+  public static final List<String> ALL_PROPERTIES = Arrays.asList(
+          PROP_CREATION_DATE,
+          PROP_DISPLAY_NAME,
+          PROP_GET_CONTENT_LANGUAGE,
+          PROP_GET_CONTENT_LENGTH,
+          PROP_GET_CONTENT_TYPE,
+          PROP_GET_ETAG,
+          PROP_GET_LAST_MODIFIED,
+          PROP_LOCK_DISCOVERY,
+          PROP_RESOURCETYPE,
+          PROP_SOURCE,
+          PROP_SUPPORTED_LOCK
+  );
+
+  protected final FileObject object;
+
+  public AbstractDavResource(FileObject object) {
+    this.object = object;
+  }
+
+  public Element setPropertyValues(Element root, List<Element> requestedProperties) {
+    // initialize the <propstat> element for 200
+    Element okPropStatEl = root.addElement(TAG_PROPSTAT);
+    Element okPropEl = okPropStatEl.addElement(TAG_PROP);
+
+    // initialize the <propstat> element for 422
+    Element failPropStatEl = root.addElement(TAG_PROPSTAT);
+    Element failPropEl = failPropStatEl.addElement(TAG_PROP);
+
+    // go through the properties and try to set/remove them,
+    // if it fails, add to the failed list
+    for (Node propertyEl : requestedProperties) {
+      if (!setPropertyValue(okPropEl, (Element) propertyEl)) {
+        failPropEl.addElement(((Element) propertyEl).getQName());
       }
     }
-    propStatEl.addElement(TAG_STATUS).addText(STATUS_200);
 
-    // add missing properties status
-    if (missingProperties.size() > 0) {
-      propStatEl = root.addElement(TAG_PROPSTAT);
-      propEl = propStatEl.addElement(TAG_PROP);
-      for (String el : missingProperties) {
-        propEl.addElement(el);
+    // only add the OK section, if there is content
+    if (okPropEl.elements().size() > 0) {
+      okPropStatEl.addElement(TAG_STATUS).addText(STATUS_200);
+    } else {
+      okPropStatEl.detach();
+    }
+
+    // only add the failed section, if there is content
+    if (failPropEl.elements().size() > 0) {
+      failPropStatEl.addElement(TAG_STATUS).addText(STATUS_422);
+    } else {
+      failPropStatEl.detach();
+    }
+
+    return root;
+  }
+
+  public Element getPropertyValues(Element root, Element propertyEl) {
+    // initialize the <propstat> for 200
+    Element okPropStatEl = root.addElement(TAG_PROPSTAT);
+    Element okPropEl = okPropStatEl.addElement(TAG_PROP);
+
+    // initialize the <propstat> element for 404
+    Element failPropStatEl = root.addElement(TAG_PROPSTAT);
+    Element failPropEl = failPropStatEl.addElement(TAG_PROP);
+
+    if (TAG_ALLPROP.equalsIgnoreCase(propertyEl.getName()) ||
+        TAG_PROPNAMES.equalsIgnoreCase(propertyEl.getName())) {
+      boolean ignoreValue = TAG_PROPNAMES.equalsIgnoreCase(propertyEl.getName());
+
+      // get all known standard properties
+      for (String propName : ALL_PROPERTIES) {
+        if (!getPropertyValue(okPropEl, propName, ignoreValue)) {
+          failPropEl.addElement(propName);
+        }
       }
-      propStatEl.addElement(TAG_STATUS).addText(STATUS_404);
+
+      // additionally try to add all the custom properties
+      try {
+        FileContent objectContent = object.getContent();
+        for (String attributeName : objectContent.getAttributeNames()) {
+          if (!getPropertyValue(okPropEl, attributeName, ignoreValue)) {
+            failPropEl.addElement(attributeName);
+          }
+        }
+      } catch (FileSystemException e) {
+        LogFactory.getLog(getClass()).error(String.format("can't read attribute properties from '%s'",
+                                                          object.getName()), e);
+      }
+    } else {
+      List requestedProperties = propertyEl.elements();
+      for (Object propertyElObject : requestedProperties) {
+        Element propEl = (Element) propertyElObject;
+        final String nameSpace = propEl.getNamespaceURI();
+        if (!getPropertyValue(okPropEl, getFQName(nameSpace, propEl.getQualifiedName()), false)) {
+          failPropEl.addElement(propEl.getQName());
+        }
+      }
     }
 
+    // only add the OK section, if there is content
+    if (okPropEl.elements().size() > 0) {
+      okPropStatEl.addElement(TAG_STATUS).addText(STATUS_200);
+    } else {
+      okPropStatEl.detach();
+    }
+
+    // only add the failed section, if there is content
+    if (failPropEl.elements().size() > 0) {
+      failPropStatEl.addElement(TAG_STATUS).addText(STATUS_404);
+    } else {
+      failPropStatEl.detach();
+    }
+
+
     return root;
   }
 
-  @SuppressWarnings({"BooleanMethodIsAlwaysInverted"})
-  protected abstract boolean addPropertyValue(Element root, String propertyName);
+  protected String getFQName(String nameSpace, String name) {
+    String prefix = "";
+    if (!"DAV:".equals(nameSpace) && null != nameSpace && !"".equals(nameSpace)) {
+      prefix = new String(Base64.encodeBase64(nameSpace.getBytes()));
+    }
+    return String.format("%s%s", prefix, name);
+  }
+
+  /**
+   * Set the property and its value. Returns false if the property cannot be processed.
+   *
+   * @param root       the response stat element
+   * @param propertyEl the property element to set
+   * @return false if this property cannot be set
+   */
+  protected abstract boolean setPropertyValue(Element root, Element propertyEl);
+
+  /**
+   * Get the property value and append it to the xml document (root). If this method
+   * returns false, the property does not exist.
+   *
+   * @param root         the root element to add the property name (and possible value to)
+   * @param propertyName the property name to read
+   * @param ignoreValue  ignore the value and just add the name
+   * @return whether the property exists
+   */
+  protected abstract boolean getPropertyValue(Element root, String propertyName, boolean ignoreValue);
 }
blob - 0e47f326c1df304cd650f3a542497617366517b4
blob + 809b8be1ec0c49b3121f5f5ed7b841cd070a4543
--- modules/webdav/src/main/java/com/thinkberg/webdav/data/DavCollection.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/data/DavCollection.java
@@ -30,12 +30,7 @@ public class DavCollection extends DavResource {
     super(object);
   }
 
-
-  public DavCollection(FileObject object, boolean ignoreValues) {
-    super(object, ignoreValues);
-  }
-
-  protected boolean addResourceTypeProperty(Element root) {
+  protected boolean addResourceTypeProperty(Element root, boolean ignoreValue) {
     root.addElement(PROP_RESOURCETYPE).addElement(COLLECTION);
     return true;
   }
@@ -46,7 +41,7 @@ public class DavCollection extends DavResource {
    * @param root the prop element to add to
    * @return true, even though nothing is added
    */
-  protected boolean addGetContentLanguageProperty(Element root) {
+  protected boolean addGetContentLanguageProperty(Element root, boolean ignoreValue) {
     return true;
   }
 
@@ -56,7 +51,7 @@ public class DavCollection extends DavResource {
    * @param root the prop element to add to
    * @return true, even though nothing is added
    */
-  protected boolean addGetContentLengthProperty(Element root) {
+  protected boolean addGetContentLengthProperty(Element root, boolean ignoreValue) {
     return true;
   }
 
@@ -66,27 +61,27 @@ public class DavCollection extends DavResource {
    * @param root the prop element to add to
    * @return true, even though nothing is added
    */
-  protected boolean addGetContentTypeProperty(Element root) {
+  protected boolean addGetContentTypeProperty(Element root, boolean ignoreValue) {
     return true;
   }
 
-  protected boolean addQuotaProperty(Element root) {
+  protected boolean addQuotaProperty(Element root, boolean ignoreValue) {
     root.addElement(PROP_QUOTA).addText("" + Long.MAX_VALUE);
     return true;
   }
 
-  protected boolean addQuotaUsedProperty(Element root) {
+  protected boolean addQuotaUsedProperty(Element root, boolean ignoreValue) {
     // TODO add correct handling of used quota
     root.addElement(PROP_QUOTA_USED).addText("0");
     return true;
   }
 
-  protected boolean addQuotaAvailableBytesProperty(Element root) {
+  protected boolean addQuotaAvailableBytesProperty(Element root, boolean ignoreValue) {
     root.addElement(PROP_QUOTA_AVAILABLE_BYTES).addText(Long.toHexString(Long.MAX_VALUE));
     return true;
   }
 
-  protected boolean addQuotaUsedBytesProperty(Element root) {
+  protected boolean addQuotaUsedBytesProperty(Element root, boolean ignoreValue) {
     // TODO add correct handling of used quota
     root.addElement(PROP_QUOTA_USED_BYTES).addText("0");
     return true;
blob - 3d01502a97a50c6eb672345d40fd43c6eee5b1e7
blob + cca7886e245ede67ecb82ca0d3dfb3bde4a06852
--- modules/webdav/src/main/java/com/thinkberg/webdav/data/DavResource.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/data/DavResource.java
@@ -19,11 +19,17 @@ package com.thinkberg.webdav.data;
 import com.thinkberg.webdav.Util;
 import com.thinkberg.webdav.lock.Lock;
 import com.thinkberg.webdav.lock.LockManager;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileContent;
 import org.apache.commons.vfs.FileObject;
 import org.apache.commons.vfs.FileSystemException;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
 import org.dom4j.Element;
 
-import java.util.Arrays;
+import java.io.IOException;
+import java.io.StringWriter;
 import java.util.List;
 
 /**
@@ -32,131 +38,135 @@ import java.util.List;
  */
 public class DavResource extends AbstractDavResource {
 
-  // @see http://www.webdav.org/specs/rfc2518.html#dav.properties
-  public static final String PROP_CREATION_DATE = "creationdate";
-  public static final String PROP_DISPLAY_NAME = "displayname";
-  private static final String PROP_GET_CONTENT_LANGUAGE = "getcontentlanguage";
-  private static final String PROP_GET_CONTENT_LENGTH = "getcontentlength";
-  private static final String PROP_GET_CONTENT_TYPE = "getcontenttype";
-  private static final String PROP_GET_ETAG = "getetag";
-  private static final String PROP_GET_LAST_MODIFIED = "getlastmodified";
-  private static final String PROP_LOCK_DISCOVERY = "lockdiscovery";
-  public static final String PROP_RESOURCETYPE = "resourcetype";
-  private static final String PROP_SOURCE = "source";
-  private static final String PROP_SUPPORTED_LOCK = "supportedlock";
-
-  // non-standard properties
-  static final String PROP_QUOTA = "quota";
-  static final String PROP_QUOTA_USED = "quotaused";
-  static final String PROP_QUOTA_AVAILABLE_BYTES = "quota-available-bytes";
-  static final String PROP_QUOTA_USED_BYTES = "quota-used-bytes";
-
-  // list of standard supported properties (for allprop/propname)
-  public static final List<String> ALL_PROPERTIES = Arrays.asList(
-          PROP_CREATION_DATE,
-          PROP_DISPLAY_NAME,
-          PROP_GET_CONTENT_LANGUAGE,
-          PROP_GET_CONTENT_LENGTH,
-          PROP_GET_CONTENT_TYPE,
-          PROP_GET_ETAG,
-          PROP_GET_LAST_MODIFIED,
-          PROP_LOCK_DISCOVERY,
-          PROP_RESOURCETYPE,
-          PROP_SOURCE,
-          PROP_SUPPORTED_LOCK
-  );
-
-  protected final FileObject object;
-  protected boolean ignoreValues = false;
-
   public DavResource(FileObject object) {
-    this(object, false);
+    super(object);
   }
 
+  protected boolean setPropertyValue(Element root, Element propertyEl) {
+    LogFactory.getLog(getClass()).debug(String.format("[%s].set('%s')", object.getName(), propertyEl.asXML()));
 
-  public DavResource(FileObject object, boolean ignoreValues) {
-    this.object = object;
-    this.ignoreValues = ignoreValues;
-
+    if (!ALL_PROPERTIES.contains(propertyEl.getName())) {
+      final String nameSpace = propertyEl.getNamespaceURI();
+      final String attributeName = getFQName(nameSpace, propertyEl.getQualifiedName());
+      try {
+        FileContent objectContent = object.getContent();
+        final String command = propertyEl.getParent().getParent().getName();
+        if (TAG_PROP_SET.equals(command)) {
+          StringWriter propertyValueWriter = new StringWriter();
+          propertyEl.write(propertyValueWriter);
+          propertyValueWriter.close();
+          objectContent.setAttribute(attributeName, propertyValueWriter.getBuffer().toString());
+        } else if (TAG_PROP_REMOVE.equals(command)) {
+          objectContent.setAttribute(attributeName, null);
+        }
+        root.addElement(propertyEl.getQName());
+        return true;
+      } catch (IOException e) {
+        LogFactory.getLog(getClass()).error(String.format("can't store attribute property '%s' = '%s'",
+                                                          attributeName,
+                                                          propertyEl.asXML()), e);
+      }
+    }
+    return false;
   }
 
   /**
-   * Ignore values
-   *
-   * @param ignoreValues true if the serialized xml should not contain values
-   */
-  public void setIgnoreValues(boolean ignoreValues) {
-    this.ignoreValues = ignoreValues;
-  }
-
-  /**
    * Add the value for a given property to the result document. If the value
    * is missing or can not be added for some reason it will return false to
    * indicate a missing property.
    *
    * @param root         the root element for the result document fragment
-   * @param propertyName the property name to add
+   * @param propertyName the property name to query
    * @return true for successful addition and false for missing data
    */
-  protected boolean addPropertyValue(Element root, String propertyName) {
+  protected boolean getPropertyValue(Element root, String propertyName, boolean ignoreValue) {
+    LogFactory.getLog(getClass()).debug(String.format("[%s].get('%s')", object.getName(), propertyName));
     if (PROP_CREATION_DATE.equals(propertyName)) {
-      return addCreationDateProperty(root);
+      return addCreationDateProperty(root, ignoreValue);
     } else if (PROP_DISPLAY_NAME.equals(propertyName)) {
-      return addGetDisplayNameProperty(root);
+      return addGetDisplayNameProperty(root, ignoreValue);
     } else if (PROP_GET_CONTENT_LANGUAGE.equals(propertyName)) {
-      return addGetContentLanguageProperty(root);
+      return addGetContentLanguageProperty(root, ignoreValue);
     } else if (PROP_GET_CONTENT_LENGTH.equals(propertyName)) {
-      return addGetContentLengthProperty(root);
+      return addGetContentLengthProperty(root, ignoreValue);
     } else if (PROP_GET_CONTENT_TYPE.equals(propertyName)) {
-      return addGetContentTypeProperty(root);
+      return addGetContentTypeProperty(root, ignoreValue);
     } else if (PROP_GET_ETAG.equals(propertyName)) {
-      return addGetETagProperty(root);
+      return addGetETagProperty(root, ignoreValue);
     } else if (PROP_GET_LAST_MODIFIED.equals(propertyName)) {
-      return addGetLastModifiedProperty(root);
+      return addGetLastModifiedProperty(root, ignoreValue);
     } else if (PROP_LOCK_DISCOVERY.equals(propertyName)) {
-      return addLockDiscoveryProperty(root);
+      return addLockDiscoveryProperty(root, ignoreValue);
     } else if (PROP_RESOURCETYPE.equals(propertyName)) {
-      return addResourceTypeProperty(root);
+      return addResourceTypeProperty(root, ignoreValue);
     } else if (PROP_SOURCE.equals(propertyName)) {
-      return addSourceProperty(root);
+      return addSourceProperty(root, ignoreValue);
     } else if (PROP_SUPPORTED_LOCK.equals(propertyName)) {
-      return addSupportedLockProperty(root);
+      return addSupportedLockProperty(root, ignoreValue);
     } else {
       // handle non-standard properties (keep a little separate)
       if (PROP_QUOTA.equals(propertyName)) {
-        return addQuotaProperty(root);
+        return addQuotaProperty(root, ignoreValue);
       } else if (PROP_QUOTA_USED.equals(propertyName)) {
-        return addQuotaUsedProperty(root);
+        return addQuotaUsedProperty(root, ignoreValue);
       } else if (PROP_QUOTA_AVAILABLE_BYTES.equals(propertyName)) {
-        return addQuotaAvailableBytesProperty(root);
+        return addQuotaAvailableBytesProperty(root, ignoreValue);
       } else if (PROP_QUOTA_USED_BYTES.equals(propertyName)) {
-        return addQuotaUsedBytesProperty(root);
+        return addQuotaUsedBytesProperty(root, ignoreValue);
+      } else {
+        try {
+          Object propertyValue = object.getContent().getAttribute(propertyName);
+          if (null != propertyValue) {
+            if (((String) propertyValue).startsWith("<")) {
+              try {
+                Document property = DocumentHelper.parseText((String) propertyValue);
+                if (ignoreValue) {
+                  property.clearContent();
+                }
+                root.add(property.getRootElement().detach());
+                return true;
+              } catch (DocumentException e) {
+                LogFactory.getLog(getClass()).error("property value unparsable", e);
+                return false;
+              }
+            } else {
+              Element el = root.addElement(propertyName);
+              if (!ignoreValue) {
+                el.addText((String) propertyValue);
+              }
+              return true;
+            }
+
+          }
+        } catch (FileSystemException e) {
+          LogFactory.getLog(this.getClass()).error(String.format("property '%s' is not supported", propertyName), e);
+        }
       }
     }
 
     return false;
   }
 
-  protected boolean addCreationDateProperty(Element root) {
+  protected boolean addCreationDateProperty(Element root, boolean ignoreValue) {
     return false;
   }
 
-  protected boolean addGetDisplayNameProperty(Element root) {
+  protected boolean addGetDisplayNameProperty(Element root, boolean ignoreValue) {
     Element el = root.addElement(PROP_DISPLAY_NAME);
-    if (!ignoreValues) {
+    if (!ignoreValue) {
       el.addCDATA(object.getName().getBaseName());
     }
     return true;
   }
 
-  protected boolean addGetContentLanguageProperty(Element root) {
+  protected boolean addGetContentLanguageProperty(Element root, boolean ignoreValue) {
     return false;
   }
 
-  protected boolean addGetContentLengthProperty(Element root) {
+  protected boolean addGetContentLengthProperty(Element root, boolean ignoreValue) {
     try {
       Element el = root.addElement(PROP_GET_CONTENT_LENGTH);
-      if (!ignoreValues) {
+      if (!ignoreValue) {
         el.addText("" + object.getContent().getSize());
       }
       return true;
@@ -166,7 +176,7 @@ public class DavResource extends AbstractDavResource {
     }
   }
 
-  protected boolean addGetContentTypeProperty(Element root) {
+  protected boolean addGetContentTypeProperty(Element root, boolean ignoreValue) {
     try {
       String contentType = object.getContent().getContentInfo().getContentType();
       if (null == contentType || "".equals(contentType)) {
@@ -174,7 +184,7 @@ public class DavResource extends AbstractDavResource {
       }
 
       Element el = root.addElement(PROP_GET_CONTENT_TYPE);
-      if (!ignoreValues) {
+      if (!ignoreValue) {
         el.addText(contentType);
       }
       return true;
@@ -184,14 +194,14 @@ public class DavResource extends AbstractDavResource {
     }
   }
 
-  protected boolean addGetETagProperty(Element root) {
+  protected boolean addGetETagProperty(Element root, boolean ignoreValue) {
     return false;
   }
 
-  protected boolean addGetLastModifiedProperty(Element root) {
+  protected boolean addGetLastModifiedProperty(Element root, boolean ignoreValue) {
     try {
       Element el = root.addElement(PROP_GET_LAST_MODIFIED);
-      if (!ignoreValues) {
+      if (!ignoreValue) {
         el.addText(Util.getDateString(object.getContent().getLastModifiedTime()));
       }
       return true;
@@ -201,37 +211,37 @@ public class DavResource extends AbstractDavResource {
     }
   }
 
-  protected boolean addLockDiscoveryProperty(Element root) {
+  protected boolean addLockDiscoveryProperty(Element root, boolean ignoreValue) {
     Element lockdiscoveryEl = root.addElement(PROP_LOCK_DISCOVERY);
     try {
       List<Lock> locks = LockManager.getInstance().discoverLock(object);
       if (locks != null && !locks.isEmpty()) {
         for (Lock lock : locks) {
-          if (lock != null && !ignoreValues) {
+          if (lock != null && !ignoreValue) {
             lock.serializeToXml(lockdiscoveryEl);
           }
         }
       }
       return true;
     } catch (FileSystemException e) {
-      e.printStackTrace();
       root.remove(lockdiscoveryEl);
+      e.printStackTrace();
       return false;
     }
   }
 
-  protected boolean addResourceTypeProperty(Element root) {
+  protected boolean addResourceTypeProperty(Element root, boolean ignoreValue) {
     root.addElement(PROP_RESOURCETYPE);
     return true;
   }
 
-  protected boolean addSourceProperty(Element root) {
+  protected boolean addSourceProperty(Element root, boolean ignoreValue) {
     return false;
   }
 
-  protected boolean addSupportedLockProperty(Element root) {
+  protected boolean addSupportedLockProperty(Element root, boolean ignoreValue) {
     Element supportedlockEl = root.addElement(PROP_SUPPORTED_LOCK);
-    if (!ignoreValues) {
+    if (!ignoreValue) {
       Element exclLockentryEl = supportedlockEl.addElement("lockentry");
       exclLockentryEl.addElement("lockscope").addElement("exclusive");
       exclLockentryEl.addElement("locktype").addElement("write");
@@ -243,19 +253,19 @@ public class DavResource extends AbstractDavResource {
     return true;
   }
 
-  protected boolean addQuotaProperty(Element root) {
+  protected boolean addQuotaProperty(Element root, boolean ignoreValue) {
     return false;
   }
 
-  protected boolean addQuotaUsedProperty(Element root) {
+  protected boolean addQuotaUsedProperty(Element root, boolean ignoreValue) {
     return false;
   }
 
-  protected boolean addQuotaAvailableBytesProperty(Element root) {
+  protected boolean addQuotaAvailableBytesProperty(Element root, boolean ignoreValue) {
     return false;
   }
 
-  protected boolean addQuotaUsedBytesProperty(Element root) {
+  protected boolean addQuotaUsedBytesProperty(Element root, boolean ignoreValue) {
     return false;
   }
 }
blob - 0bd27718adde8f76ebb9bf18691ac0317fb46631
blob + 93266e8ffdd305cc0bad778b0ea41e45055fa1b7
--- modules/webdav/src/main/java/com/thinkberg/webdav/servlet/MoxoWebDAVServlet.java
+++ modules/webdav/src/main/java/com/thinkberg/webdav/servlet/MoxoWebDAVServlet.java
@@ -24,7 +24,6 @@ import org.apache.commons.vfs.FileSystemException;
 import org.apache.commons.vfs.FileSystemOptions;
 import org.apache.commons.vfs.auth.StaticUserAuthenticator;
 import org.apache.commons.vfs.impl.DefaultFileSystemConfigBuilder;
-import org.mortbay.jetty.Response;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
@@ -113,8 +112,6 @@ public class MoxoWebDAVServlet extends HttpServlet {
     } else {
       response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
     }
-    Response jettyResponse = ((Response) response);
-    String reason = jettyResponse.getReason();
-    LOG.debug(String.format("<< %s (%d%s)", request.getMethod(), jettyResponse.getStatus(), reason != null ? ": " + reason : ""));
+    LOG.debug(String.format("<< %s (%s)", request.getMethod(), response.toString().replaceAll("[\\r\\n]+", "")));
   }
 }
blob - 2389b60f777cec922576a63c3d4ad792dd4073da
blob + 8f0bdb3b66beb18d36e1200ac5a043a0182ada4f
--- modules/webdav/src/test/java/com/thinkberg/webdav/DavTestCase.java
+++ modules/webdav/src/test/java/com/thinkberg/webdav/DavTestCase.java
@@ -11,8 +11,6 @@ import org.dom4j.DocumentHelper;
 import org.dom4j.Element;
 import org.dom4j.Node;
 
-import java.util.Arrays;
-
 /**
  * Helper class for DAV tests.
  *
@@ -73,8 +71,12 @@ public class DavTestCase extends TestCase {
     Element root = DocumentHelper.createElement("root");
     DavResourceFactory factory = DavResourceFactory.getInstance();
     DavResource davResource = factory.getDavResource(object);
-    davResource.setIgnoreValues(ignoreValues);
-    davResource.serializeToXml(root, Arrays.asList(propertyName));
+
+    Element testPropertyEl = (Element) root.addElement("prop").detach();
+    testPropertyEl.addElement(propertyName);
+
+    davResource.getPropertyValues(root, testPropertyEl);
+
     return root;
   }
 
blob - 06c1c46a60a4facb13fe413c6195580c5b6a3282
blob + 3a0fa1b9ecede2c113b5c0fca9111d6041b9e615
--- pom.xml
+++ pom.xml
@@ -1,38 +1,32 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
-    <groupId>com.thinkberg.moxo</groupId>
+    <groupId>com.thinkberg</groupId>
     <artifactId>moxo</artifactId>
     <packaging>pom</packaging>
     <version>1.0-SNAPSHOT</version>
+    <name>Moxo S3 DAV Proxy</name>
     <modules>
         <module>modules/webdav</module>
         <module>modules/vfs.s3</module>
     </modules>
-    <name>Moxo S3 DAV Proxy</name>
     <url>http://thinkberg.com</url>
     <dependencies>
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>3.8.1</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.mortbay.jetty</groupId>
             <artifactId>jetty</artifactId>
             <version>6.1.1</version>
         </dependency>
         <dependency>
-            <groupId>dom4j</groupId>
-            <artifactId>dom4j</artifactId>
-            <version>1.6.1</version>
+            <groupId>com.thinkberg</groupId>
+            <artifactId>webdav</artifactId>
+            <version>1.0-SNAPSHOT</version>
         </dependency>
-        <!--<dependency>-->
-        <!--<groupId>jaxen</groupId>-->
-        <!--<artifactId>jaxen</artifactId>-->
-        <!--<version>1.1</version>-->
-        <!--</dependency>-->
+        <dependency>
+            <groupId>com.thinkberg</groupId>
+            <artifactId>vfs.s3</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
     </dependencies>
     <build>
         <resources>
@@ -96,14 +90,25 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
-                <configuration>
-                    <archive>
-                        <manifest>
-                            <mainClass>com.thinkberg.moxo.Main</mainClass>
-                            <addClasspath>true</addClasspath>
-                        </manifest>
-                    </archive>
-                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                        <configuration>
+                            <archive>
+                                <manifest>
+                                    <mainClass>com.thinkberg.moxo.Main</mainClass>
+                                    <addClasspath>true</addClasspath>
+                                </manifest>
+                            </archive>
+                            <includes>
+                                <include>**/moxo/*</include>
+                            </includes>
+                        </configuration>
+                    </execution>
+                </executions>
             </plugin>
         </plugins>
     </build>
blob - 5b43b0aee4de7daf1be61d57c1bd4be286fef0fd
blob + 3048290ec50e533b9e6bc29c272b59405f156dad
--- src/main/resources/simplelog.properties
+++ src/main/resources/simplelog.properties
@@ -15,4 +15,5 @@
 #
 
 org.apache.commons.logging.simplelog.defaultlog=error
-org.apache.commons.logging.simplelog.log.com.thinkberg.moxo=debug
\ No newline at end of file
+org.apache.commons.logging.simplelog.log.com.thinkberg.vfs.s3=debug
+org.apache.commons.logging.simplelog.log.com.thinkberg.webdav=debug
\ No newline at end of file