Commit Diff


commit - /dev/null
commit + 1bbed3956b1d921c893322d595688ad003e70940
blob - /dev/null
blob + d645695673349e3947e8e5ae42332d0ac3164cd7 (mode 644)
--- /dev/null
+++ LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
blob - /dev/null
blob + 7028744f430a3e18a81939ea3f859c9b5e991eb1 (mode 644)
--- /dev/null
+++ NOTICE
@@ -0,0 +1,20 @@
+=========================================================================
+==  NOTICE file corresponding to section 4(d) of the Apache License,   ==
+==  Version 2.0.                                                       ==
+=========================================================================
+
+Moxo S3 DAV Proxy Server
+Copyright 2007 Matthias L. Jugel.
+
+This product includes software developed at
+
+  Many Ideas (for WebDAV) taken from s3DAV (c) Pierre Carion
+  http://www.carion.org/s3dav/
+
+  The Apache Software Foundation (http://www.apache.org/).
+  MetaStuff, Ltd. (DOM4J, http://dom4j.org).
+  Mort Bay Consulting (Jetty, http://jetty.mortbay.org).
+  James Murty (jetst3, http://http://jets3t.s3.amazonaws.com)
+
+
+
blob - /dev/null
blob + 709526aee6deb2cbb9880883eca5aaf026160cc9 (mode 644)
--- /dev/null
+++ README
@@ -0,0 +1,39 @@
+Moxo S3 DAV Proxy Server
+Copyright 2007 Matthias L. Jugel. See LICENSE for details.
+http://thinkberg.com/
+
+This is a first go on two issues:
+
+a) a WebDAV server based on apache-commons-vfs
+b) an Amazon S3 provider backend for apache-commons-vfs
+
+The WebDAV server is semi-complete in a sense that it works well for most tests in the
+listmus test suite except for property handling which is virtually non-existing.
+
+The VFS backend is started and provides read-only access. I have managed to browse through
+my test backup on S3 using MacOSX own Finder DAV client.
+
+INFO:
+
+Subversion: http://thinkberg.com/svn/moxo/trunk
+
+The maven build process is not yet fully completed, so to get this running a little bit
+of tweaking is necessary, unless you use IntelliJ IDEA and maven idea:idea to create
+an IDEA project file.
+
+To run either the MoxoJettyRunner or the MoxoTest you need to include the src/main/resources
+directory in your classpath. Also copy the file moxo.template.properties and edit it to
+include your Amazon S3 access information as well as the bucket to use. Right now the bucket
+must already exist and contain files uploaded using the Uploader or Synchronize from Jets3t.
+
+Define the system property "moxo.properties" to point to your moxo.properties file:
+
+java -cp ... -Dmoxo.properties=moxo.properties com.thinkberg.moxo.MoxoJettyRunner
+
+TODO:
+
+- Create an executable JAR with all required libraries. The Main is already prepared to do
+  that but I have not yet fully understood how to get maven to package the jars right next
+  to the compiled classes.
+- Add write access to the VFS backend.
+- Much more ...
blob - /dev/null
blob + d645695673349e3947e8e5ae42332d0ac3164cd7 (mode 644)
--- /dev/null
+++ licenses/APACHE-LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
blob - /dev/null
blob + a90650a4bc01ea8bc40312e8c70e7759c5806857 (mode 644)
--- /dev/null
+++ licenses/DOM4J-BSD.txt
@@ -0,0 +1,40 @@
+Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.

+

+Redistribution and use of this software and associated documentation

+("Software"), with or without modification, are permitted provided

+that the following conditions are met:

+

+1. Redistributions of source code must retain copyright

+   statements and notices.  Redistributions must also contain a

+   copy of this document.

+ 

+2. Redistributions in binary form must reproduce the

+   above copyright notice, this list of conditions and the

+   following disclaimer in the documentation and/or other

+   materials provided with the distribution.

+ 

+3. The name "DOM4J" must not be used to endorse or promote

+   products derived from this Software without prior written

+   permission of MetaStuff, Ltd.  For written permission,

+   please contact dom4j-info@metastuff.com.

+ 

+4. Products derived from this Software may not be called "DOM4J"

+   nor may "DOM4J" appear in their names without prior written

+   permission of MetaStuff, Ltd. DOM4J is a registered

+   trademark of MetaStuff, Ltd.

+ 

+5. Due credit should be given to the DOM4J Project - 

+   http://www.dom4j.org

+ 

+THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS

+``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT

+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND

+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL

+METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,

+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES

+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR

+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)

+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,

+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)

+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED

+OF THE POSSIBILITY OF SUCH DAMAGE.

blob - /dev/null
blob + 366af8a561343bd5d1b259929b35c50840a49bae (mode 644)
--- /dev/null
+++ pom.xml
@@ -0,0 +1,71 @@
+<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>
+  <artifactId>moxo</artifactId>
+  <packaging>jar</packaging>
+  <version>1.0-SNAPSHOT</version>
+  <name>Moxo S3 DAV Proxy</name>
+  <url>http://thinkberg.com</url>
+  <repositories>
+    <repository>
+      <id>codehaus-m2-repository</id>
+      <name>Codehaus Maven 2.x Repository</name>
+      <url>http://repository.codehaus.org</url>
+    </repository>
+  </repositories>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>jets3t</groupId>
+      <artifactId>jets3t</artifactId>
+      <version>0.5.0</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-httpclient</groupId>
+      <artifactId>commons-httpclient</artifactId>
+      <version>3.0.1</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-vfs</groupId>
+      <artifactId>commons-vfs</artifactId>
+      <version>1.0</version>
+    </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>
+    </dependency>
+    <dependency>
+      <groupId>jaxen</groupId>
+      <artifactId>jaxen</artifactId>
+      <version>1.1</version>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifest>
+              <mainClass>com.thinkberg.moxo.MoxoJettyRunner</mainClass>
+              <addClasspath>true</addClasspath>
+            </manifest>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
blob - /dev/null
blob + 81f9dc3075c006734e4d4a76f76f86d0023776df (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/Main.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.Policy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+/**
+ * The launcher is responsible for extracting referenced libraries from the jar and
+ * setting up the classpath.
+ *
+ * @author Matthias L. Jugel
+ */
+public class Main {
+  private final static URL location = Main.class.getProtectionDomain().getCodeSource().getLocation();
+
+  @SuppressWarnings({"RedundantArrayCreation"})
+  public static void main(String args[])
+          throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+    ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
+    if (null == parentClassLoader) {
+      parentClassLoader = Main.class.getClassLoader();
+    }
+    if (null == parentClassLoader) {
+      parentClassLoader = ClassLoader.getSystemClassLoader();
+    }
+    URLClassLoader classLoader = new URLClassLoader(initClassPath(), parentClassLoader);
+    Thread.currentThread().setContextClassLoader(classLoader);
+
+    System.setSecurityManager(null);
+
+    try {
+      Policy.getPolicy().refresh();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    Class mainClass = classLoader.loadClass("com.thinkberg.moxo.MoxoJettyRunner");
+    final Method main = mainClass.getDeclaredMethod("main", new Class[]{String[].class});
+    main.invoke(null, new Object[]{args});
+  }
+
+  /**
+   * Read the jar manifest and add class-path entries to the classpath.
+   *
+   * @return the classpath
+   */
+  private static URL[] initClassPath() {
+    List<URL> urlArray = new ArrayList<URL>();
+    InputStream manifestIn = null;
+    InputStream jarIn = null;
+    try {
+      manifestIn = location.openStream();
+      JarInputStream launcherJarIs = new JarInputStream(manifestIn);
+      StringBuffer classPath = new StringBuffer(location.getFile());
+      List<URL> classpathList = new ArrayList<URL>(urlArray);
+      JarEntry jarEntry;
+      while (null != (jarEntry = launcherJarIs.getNextJarEntry())) {
+        if (!jarEntry.isDirectory() && jarEntry.getName().endsWith(".jar")) {
+          try {
+            URL classPathEntry = getResourceUrl(jarEntry.getName());
+            if (!classpathList.contains(classPathEntry)) {
+              urlArray.add(classPathEntry);
+              classPath.append(File.pathSeparatorChar);
+              classPath.append(classPathEntry.getFile());
+            }
+          } catch (IOException e) {
+            System.err.println("ignored '" + jarEntry.getName() + "'");
+          }
+        }
+      }
+
+      System.setProperty("java.class.path", classPath.toString());
+    } catch (IOException e) {
+      e.printStackTrace();
+    } finally {
+      try {
+        manifestIn.close();
+      } catch (Throwable ignore) {
+        // ignore errors
+      }
+      try {
+        jarIn.close();
+      } catch (Throwable ignore) {
+        // ignore errors
+      }
+    }
+    return urlArray.toArray(new URL[0]);
+  }
+
+  /**
+   * Get URL.
+   *
+   * @param resource resource name/path
+   * @return the url pointing to the resource
+   * @throws IOException if the resource cannot be accessed
+   */
+  private static URL getResourceUrl(String resource) throws IOException {
+    File directoryBase = new File(location.getFile()).getParentFile();
+    File file = new File(resource);
+    if (file.isAbsolute() && file.exists()) {
+      return file.toURL();
+    }
+    file = new File(directoryBase, resource);
+    if (file.exists()) {
+      return file.toURL();
+    }
+
+    URL resourceURL = Main.class.getResource("/" + resource);
+    if (null != resourceURL) {
+      return extract(resourceURL);
+    }
+
+    throw new MalformedURLException(resource);
+  }
+
+  /**
+   * Extract file from launcher jar to be able to access is via classpath.
+   *
+   * @param resource the jar resource to be extracted
+   * @return a url pointing to the new file
+   * @throws IOException if the extraction was not possible
+   */
+  private static URL extract(URL resource) throws IOException {
+    File f = File.createTempFile("launcher_", ".jar");
+    f.deleteOnExit();
+    if (f.getParentFile() != null) {
+      f.getParentFile().mkdirs();
+    }
+    InputStream is = new BufferedInputStream(resource.openStream());
+    FileOutputStream os = new FileOutputStream(f);
+    byte[] arr = new byte[8192];
+    for (int i = 0; i >= 0; i = is.read(arr)) {
+      os.write(arr, 0, i);
+    }
+    is.close();
+    os.close();
+    return f.toURL();
+  }
+}
\ No newline at end of file
blob - /dev/null
blob + e1d96cf755ad2bafbd99a230101d870629bf7423 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/MoxoJettyRunner.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo;
+
+import org.mortbay.jetty.Server;
+import org.mortbay.xml.XmlConfiguration;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * The Java Webserver starter uses jetty.
+ *
+ * @author Matthias L. Jugel
+ */
+public class MoxoJettyRunner {
+  private static final String CONF_JETTY_XML = "jetty.xml";
+
+  public static void main(String[] args) {
+    System.out.println("Moxo S3 DAV Proxy (c) 2007 Matthias L. Jugel");
+
+    // set encoding of the JVM and make sure Jetty decodes URIs correctly
+    System.setProperty("file.encoding", "UTF-8");
+    System.setProperty("org.mortbay.util.URI.charset", "UTF-8");
+
+    try {
+      Server server = new Server();
+      XmlConfiguration xmlConfiguration = new XmlConfiguration(getResource(CONF_JETTY_XML));
+      xmlConfiguration.configure(server);
+      server.start();
+      server.join();
+    } catch (Exception e) {
+      System.err.println("Can't start server: " + e.getMessage());
+      System.exit(1);
+    }
+  }
+
+  @SuppressWarnings({"SameParameterValue"})
+  private static URL getResource(String resource) {
+    URL url = MoxoJettyRunner.class.getResource("/" + resource);
+    if (null == url) {
+      try {
+        url = new File(resource).toURL();
+        System.err.println("Loading configuration from file: " + url.toExternalForm());
+      } catch (MalformedURLException e) {
+        // ignore ...
+      }
+    }
+    return url;
+  }
+}
blob - /dev/null
blob + 0e0225a25f66bc7037f99212080eb30f5bf7e944 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/ResourceManager.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo;
+
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileSystemManager;
+import org.apache.commons.vfs.VFS;
+import org.jets3t.service.Jets3tProperties;
+
+/**
+ * The resource manager is responsible for providing a virtual file system root.
+ *
+ * @author Matthias L. Jugel
+ */
+public class ResourceManager {
+  private static FileObject root;
+
+  static {
+    try {
+      String propertiesFileName = System.getProperty("moxo.properties", "moxo.properties");
+      Jets3tProperties properties = Jets3tProperties.getInstance(propertiesFileName);
+      FileSystemManager fsm = VFS.getManager();
+      FileObject rootObject = fsm.resolveFile("s3://" + properties.getStringProperty("bucket", null));
+      root = fsm.createVirtualFileSystem(rootObject);
+      System.err.println("Created virtual file system: " + rootObject);
+    } catch (FileSystemException e) {
+      System.err.println("Can't create virtual filesystem: " + e.getMessage());
+      e.printStackTrace();
+    }
+  }
+
+  public static FileObject getFileObject(String path) throws FileSystemException {
+    return root.resolveFile(path);
+  }
+}
blob - /dev/null
blob + 34da23d2a591b3326dc3d78d18ae9a335ad6b4ca (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/CopyHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.vfs.DepthFileSelector;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class CopyHandler extends CopyMoveBase {
+  protected void copyOrMove(FileObject object, FileObject target, int depth) throws FileSystemException {
+    target.copyFrom(object, new DepthFileSelector(depth));
+  }
+}
blob - /dev/null
blob + b6e185d27e3e58d31107a6a92117d79beee64682 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/CopyMoveBase.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import com.thinkberg.moxo.dav.lock.LockException;
+import com.thinkberg.moxo.dav.lock.LockManager;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public abstract class CopyMoveBase extends WebdavHandler {
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    boolean overwrite = getOverwrite(request);
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+    FileObject targetObject = getDestination(request);
+
+    try {
+      // check that we can write the target
+      LockManager.getInstance().checkCondition(targetObject, getIf(request));
+      // if we move, check that we can actually write on the source
+      if ("MOVE".equals(request.getMethod())) {
+        LockManager.getInstance().checkCondition(object, getIf(request));
+      }
+    } catch (LockException e) {
+      if (e.getLocks() != null) {
+        response.sendError(SC_LOCKED);
+      } else {
+        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+      }
+      return;
+    }
+
+
+    if (null == targetObject) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+
+    if (object.equals(targetObject)) {
+      response.sendError(HttpServletResponse.SC_FORBIDDEN);
+      return;
+    }
+
+    if (targetObject.exists()) {
+      if (!overwrite) {
+        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+        return;
+      }
+      response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+    } else {
+      FileObject targetParent = targetObject.getParent();
+      if (!targetParent.exists() ||
+              !FileType.FOLDER.equals(targetParent.getType())) {
+        response.sendError(HttpServletResponse.SC_CONFLICT);
+      }
+      response.setStatus(HttpServletResponse.SC_CREATED);
+    }
+
+    copyOrMove(object, targetObject, getDepth(request));
+  }
+
+  protected abstract void copyOrMove(FileObject object, FileObject target, int depth) throws FileSystemException;
+
+}
blob - /dev/null
blob + 3533537d1b03790bb012ae0befe0c5539f1330a5 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/DeleteHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import com.thinkberg.moxo.dav.lock.LockException;
+import com.thinkberg.moxo.dav.lock.LockManager;
+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;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class DeleteHandler extends WebdavHandler {
+
+  private final static FileSelector ALL_FILES_SELECTOR = new FileSelector() {
+    public boolean includeFile(FileSelectInfo fileSelectInfo) throws Exception {
+      return true;
+    }
+
+    public boolean traverseDescendents(FileSelectInfo fileSelectInfo) throws Exception {
+      return true;
+    }
+  };
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+    if (request instanceof Request) {
+      String fragment = ((Request) request).getUri().getFragment();
+      if (fragment != null) {
+        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+        return;
+      }
+    }
+
+    try {
+      LockManager.getInstance().checkCondition(object, getIf(request));
+    } catch (LockException e) {
+      if (e.getLocks() != null) {
+        response.sendError(SC_LOCKED);
+      } else {
+        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+      }
+      return;
+    }
+
+    if (object.exists()) {
+      if (object.delete(ALL_FILES_SELECTOR) > 0) {
+        response.setStatus(HttpServletResponse.SC_OK);
+      } else {
+        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+      }
+    } else {
+      response.sendError(HttpServletResponse.SC_NOT_FOUND);
+    }
+  }
+}
blob - /dev/null
blob + bcfd04dc1bfcf5fd118934e8a0108b8e9f592b13 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/GetHandler.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import org.apache.commons.vfs.FileContent;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class GetHandler extends WebdavHandler {
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+
+    if (object.exists()) {
+      if (FileType.FOLDER.equals(object.getType())) {
+        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+        return;
+      }
+
+      setHeader(response, object.getContent());
+
+      InputStream is = object.getContent().getInputStream();
+      OutputStream os = response.getOutputStream();
+      Util.copyStream(is, os);
+      is.close();
+    } else {
+      response.sendError(HttpServletResponse.SC_NOT_FOUND);
+    }
+  }
+
+  void setHeader(HttpServletResponse response, FileContent content) throws FileSystemException {
+    response.setHeader("Last-Modified", Util.getDateString(content.getLastModifiedTime()));
+    response.setHeader("Content-Type", content.getContentInfo().getContentType());
+  }
+
+
+}
blob - /dev/null
blob + 51fd09bff59cddb5c4212b9d20ddb56a28d8c5d3 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/HeadHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class HeadHandler extends GetHandler {
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+
+    if (object.exists()) {
+      if (FileType.FOLDER.equals(object.getType())) {
+        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+      } else {
+        setHeader(response, object.getContent());
+      }
+    } else {
+      response.sendError(HttpServletResponse.SC_NOT_FOUND);
+    }
+  }
+}
blob - /dev/null
blob + 8e7e95dc2594e7ce20d4a8e2afd6b1d6729fb992 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/LockHandler.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import com.thinkberg.moxo.dav.lock.Lock;
+import com.thinkberg.moxo.dav.lock.LockConflictException;
+import com.thinkberg.moxo.dav.lock.LockException;
+import com.thinkberg.moxo.dav.lock.LockManager;
+import org.apache.commons.vfs.FileObject;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Node;
+import org.dom4j.io.SAXReader;
+import org.dom4j.io.XMLWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Iterator;
+
+/**
+ * Handle WebDAV LOCK requests.
+ *
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class LockHandler extends WebdavHandler {
+  private static final String TAG_LOCKSCOPE = "lockscope";
+  private static final String TAG_LOCKTYPE = "locktype";
+  private static final String TAG_OWNER = "owner";
+  private static final String TAG_HREF = "href";
+  private static final String TAG_PROP = "prop";
+  private static final String TAG_LOCKDISCOVERY = "lockdiscovery";
+
+  private static final String HEADER_LOCK_TOKEN = "Lock-Token";
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+
+    try {
+      Lock lock = LockManager.getInstance().checkCondition(object, getIf(request));
+      if (lock != null) {
+        sendLockAcquiredResponse(response, lock);
+        return;
+      }
+    } catch (LockException e) {
+      // handle locks below
+    }
+
+    try {
+      SAXReader saxReader = new SAXReader();
+      Document lockInfo = saxReader.read(request.getInputStream());
+      //log(lockInfo);
+
+      Element rootEl = lockInfo.getRootElement();
+      String lockScope = null, lockType = null;
+      Object owner = null;
+      Iterator elIt = rootEl.elementIterator();
+      while (elIt.hasNext()) {
+        Element el = (Element) elIt.next();
+        if (TAG_LOCKSCOPE.equals(el.getName())) {
+          lockScope = el.selectSingleNode("*").getName();
+        } else if (TAG_LOCKTYPE.equals(el.getName())) {
+          lockType = el.selectSingleNode("*").getName();
+        } else if (TAG_OWNER.equals(el.getName())) {
+          // TODO correctly handle owner
+          Node subEl = el.selectSingleNode("*");
+          if (subEl != null && TAG_HREF.equals(subEl.getName())) {
+            owner = new URL(el.selectSingleNode("*").getText());
+          } else {
+            owner = el.getText();
+          }
+        }
+      }
+
+      log("LOCK(" + lockType + ", " + lockScope + ", " + owner + ")");
+
+      Lock requestedLock = new Lock(object, lockType, lockScope, owner, getDepth(request), getTimeout(request));
+      try {
+        LockManager.getInstance().acquireLock(requestedLock);
+        sendLockAcquiredResponse(response, requestedLock);
+      } catch (LockConflictException e) {
+        response.sendError(SC_LOCKED);
+      } catch (IllegalArgumentException e) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      }
+    } catch (DocumentException e) {
+      e.printStackTrace();
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+    }
+  }
+
+  private void sendLockAcquiredResponse(HttpServletResponse response, Lock lock) throws IOException {
+    response.setContentType("text/xml");
+    response.setCharacterEncoding("UTF-8");
+    response.setHeader(HEADER_LOCK_TOKEN, "<" + lock.getToken() + ">");
+
+    Document propDoc = DocumentHelper.createDocument();
+    Element propEl = propDoc.addElement(TAG_PROP, "DAV:");
+    Element lockdiscoveryEl = propEl.addElement(TAG_LOCKDISCOVERY);
+
+    lock.serializeToXml(lockdiscoveryEl);
+
+    XMLWriter xmlWriter = new XMLWriter(response.getWriter());
+    xmlWriter.write(propDoc);
+    log(propDoc);
+  }
+}
blob - /dev/null
blob + c7bd42b7da7d33d12157b1d0f6dac1a96b20dfbb (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/MkColHandler.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import com.thinkberg.moxo.dav.lock.LockException;
+import com.thinkberg.moxo.dav.lock.LockManager;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class MkColHandler extends WebdavHandler {
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    if (request.getReader().readLine() != null) {
+      response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
+      return;
+    }
+
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+
+    try {
+      LockManager.getInstance().checkCondition(object, getIf(request));
+    } catch (LockException e) {
+      if (e.getLocks() != null) {
+        response.sendError(SC_LOCKED);
+      } else {
+        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+      }
+      return;
+    }
+
+    if (object.exists()) {
+      response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+      return;
+    }
+
+    if (!object.getParent().exists() || !FileType.FOLDER.equals(object.getParent().getType())) {
+      response.sendError(HttpServletResponse.SC_CONFLICT);
+      return;
+    }
+
+    try {
+      object.createFolder();
+      response.setStatus(HttpServletResponse.SC_CREATED);
+    } catch (FileSystemException e) {
+      response.sendError(HttpServletResponse.SC_FORBIDDEN);
+    }
+  }
+}
blob - /dev/null
blob + 5cb1a045ff6f0fc46e8991898548660850d8a10a (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/MoveHandler.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.vfs.DepthFileSelector;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+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());
+  }
+}
blob - /dev/null
blob + fb5876098ccd381b67aa1aa2065a55e2ee93d7af (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/OptionsHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class OptionsHandler extends WebdavHandler {
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    response.setHeader("DAV", "1, 2");
+
+    String path = request.getPathInfo();
+    StringBuffer options = new StringBuffer();
+    FileObject object = ResourceManager.getFileObject(path);
+    if (object.exists()) {
+      options.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE, COPY, MOVE, LOCK, UNLOCK, PROPFIND");
+      if (FileType.FOLDER.equals(object.getType())) {
+        options.append(", PUT");
+      }
+    } else {
+      options.append("OPTIONS, MKCOL, PUT, LOCK");
+    }
+    response.setHeader("Allow", options.toString());
+
+    // see: http://www-128.ibm.com/developerworks/rational/library/2089.html
+    response.setHeader("MS-Author-Via", "DAV");
+  }
+}
blob - /dev/null
blob + e0038189d0aaef0103b1f32ab0a53ea3a228e09e (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/PostHandler.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class PostHandler extends WebdavHandler {
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+  }
+}
blob - /dev/null
blob + 169c09204e05f26f192ab23f7ad567443352bb55 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/PropFindHandler.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import com.thinkberg.moxo.dav.data.DavResource;
+import com.thinkberg.moxo.dav.data.DavResourceFactory;
+import com.thinkberg.moxo.vfs.DepthFileSelector;
+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 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.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Matthias L. Jugel
+ * @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";
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    SAXReader saxReader = new SAXReader();
+    try {
+      Document propDoc = saxReader.read(request.getInputStream());
+      // log(propDoc);
+      Element propFindEl = propDoc.getRootElement();
+      Element propEl = (Element) propFindEl.elementIterator().next();
+      String propElName = propEl.getName();
+
+      List<String> requestedProperties = new ArrayList<String>();
+      boolean ignoreValues = false;
+      if (TAG_PROP.equals(propElName)) {
+        for (Object id : propEl.elements()) {
+          requestedProperties.add(((Element) id).getName());
+        }
+      } else if (TAG_ALLPROP.equals(propElName)) {
+        requestedProperties = DavResource.ALL_PROPERTIES;
+      } else if (TAG_PROPNAMES.equals(propElName)) {
+        requestedProperties = DavResource.ALL_PROPERTIES;
+        ignoreValues = true;
+      }
+
+      FileObject object = ResourceManager.getFileObject(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);
+        //log(multiStatusResponse);
+
+        // write the actual response
+        XMLWriter writer = new XMLWriter(response.getWriter(), OutputFormat.createCompactFormat());
+        writer.write(multiStatusResponse);
+        writer.flush();
+        writer.close();
+
+      } else {
+        log("!! " + object.getName().getPath() + " NOT FOUND");
+        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+      }
+    } catch (DocumentException e) {
+      log("!! inavlid request: " + e.getMessage());
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+    }
+  }
+
+  private Document getMultiStatusRespons(FileObject object,
+                                         List<String> requestedProperties,
+                                         URL baseUrl,
+                                         int depth,
+                                         boolean ignoreValues) throws FileSystemException {
+    Document propDoc = DocumentHelper.createDocument();
+    propDoc.setXMLEncoding("UTF-8");
+
+    Element multiStatus = propDoc.addElement(TAG_MULTISTATUS, "DAV:");
+    FileObject[] children = object.findFiles(new DepthFileSelector(depth));
+    for (FileObject child : children) {
+      Element responseEl = multiStatus.addElement(TAG_RESPONSE);
+      try {
+        URL url = new URL(baseUrl, URLEncoder.encode(child.getName().getPath(), "UTF-8"));
+        log("!! " + url);
+        responseEl.addElement(TAG_HREF).addText(url.toExternalForm());
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+      DavResource resource = DavResourceFactory.getInstance().getDavResource(child);
+      resource.setIgnoreValues(ignoreValues);
+      resource.serializeToXml(responseEl, requestedProperties);
+    }
+    return propDoc;
+  }
+}
blob - /dev/null
blob + 9848117271155f8ff4227471f202c991479dffba (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/PropPatchHandler.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import com.thinkberg.moxo.dav.data.DavResource;
+import com.thinkberg.moxo.dav.lock.LockException;
+import com.thinkberg.moxo.dav.lock.LockManager;
+import org.apache.commons.vfs.FileObject;
+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.IOException;
+import java.net.URL;
+import java.util.List;
+
+/**
+ * Handle PROPPATCH requests. This currently a dummy only and will return a
+ * forbidden status for any attempt to modify or remove a property.
+ *
+ * @author Matthias L. Jugel
+ */
+public class PropPatchHandler extends WebdavHandler {
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+
+    try {
+      LockManager.getInstance().checkCondition(object, getIf(request));
+    } catch (LockException e) {
+      if (e.getLocks() != null) {
+        response.sendError(SC_LOCKED);
+      } else {
+        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+      }
+      return;
+    }
+
+    SAXReader saxReader = new SAXReader();
+    try {
+      Document propDoc = saxReader.read(request.getInputStream());
+//      log(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("!! " + 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 el = (Element) elObject;
+          if ("set".equals(el.getName())) {
+            for (Object propObject : el.elements()) {
+              setProperty(propEl, object, (Element) propObject);
+            }
+          } 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);
+
+        // write the actual response
+        XMLWriter writer = new XMLWriter(response.getWriter(), OutputFormat.createCompactFormat());
+        writer.write(resultDoc);
+        writer.flush();
+        writer.close();
+      } else {
+        log("!! " + object.getName().getPath() + " NOT FOUND");
+        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+      }
+    } catch (DocumentException e) {
+      log("!! inavlid 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();
+      }
+      root.add(propEl.detach());
+    }
+  }
+
+  private void removeProperty(Element root, FileObject object, Element el) {
+    setProperty(root, object, el);
+  }
+
+
+}
blob - /dev/null
blob + 0299d52204489160b361b989b9f0e74e73180407 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/PutHandler.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import com.thinkberg.moxo.dav.lock.LockException;
+import com.thinkberg.moxo.dav.lock.LockManager;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class PutHandler extends WebdavHandler {
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+
+    try {
+      LockManager.getInstance().checkCondition(object, getIf(request));
+    } catch (LockException e) {
+      if (e.getLocks() != null) {
+        response.sendError(SC_LOCKED);
+      } else {
+        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+      }
+      return;
+    }
+
+    // it is forbidden to write data on a folder
+    if (object.exists() && FileType.FOLDER.equals(object.getType())) {
+      response.sendError(HttpServletResponse.SC_FORBIDDEN);
+      return;
+    }
+
+    FileObject parent = object.getParent();
+    if (!parent.exists()) {
+      response.sendError(HttpServletResponse.SC_FORBIDDEN);
+      return;
+    }
+
+    if (!FileType.FOLDER.equals(parent.getType())) {
+      response.sendError(HttpServletResponse.SC_CONFLICT);
+      return;
+    }
+
+    InputStream is = request.getInputStream();
+    OutputStream os = object.getContent().getOutputStream();
+    log("copied " + Util.copyStream(is, os) + " bytes");
+    os.close();
+
+    response.setStatus(HttpServletResponse.SC_CREATED);
+  }
+}
blob - /dev/null
blob + 7e66661da892e8d3514f76702c740496e62ced5d (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/URLEncoder.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import java.io.UnsupportedEncodingException;
+import java.util.BitSet;
+
+/**
+ * Encode a URL but leave some special characters in plain text.
+ *
+ * @author Matthias L. Jugel
+ */
+class URLEncoder {
+
+  private static BitSet keepPlain;
+
+  static {
+    keepPlain = new BitSet(256);
+    int i;
+    for (i = 'a'; i <= 'z'; i++) {
+      keepPlain.set(i);
+    }
+    for (i = 'A'; i <= 'Z'; i++) {
+      keepPlain.set(i);
+    }
+    for (i = '0'; i <= '9'; i++) {
+      keepPlain.set(i);
+    }
+    keepPlain.set('+');
+    keepPlain.set('-');
+    keepPlain.set('_');
+    keepPlain.set('.');
+    keepPlain.set('*');
+    keepPlain.set('/');
+    keepPlain.set(':');
+  }
+
+
+  @SuppressWarnings({"SameParameterValue"})
+  public static String encode(String s, String enc) throws UnsupportedEncodingException {
+    byte[] buf = s.getBytes(enc);
+    StringBuffer result = new StringBuffer();
+    for (byte aBuf : buf) {
+      int c = (int) aBuf;
+      if (keepPlain.get(c & 0xFF)) {
+        result.append((char) c);
+      } else {
+        result.append('%').append(Integer.toHexString(c & 0xFF).toUpperCase());
+      }
+    }
+    return result.toString();
+  }
+}
blob - /dev/null
blob + c9b1dbfa79cede571c23e5fbce3af2951df4c49e (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/UnlockHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+import com.thinkberg.moxo.dav.lock.LockManager;
+import org.apache.commons.vfs.FileObject;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class UnlockHandler extends WebdavHandler {
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    FileObject object = ResourceManager.getFileObject(request.getPathInfo());
+    String lockTokenHeader = request.getHeader("Lock-Token");
+    String lockToken = lockTokenHeader.substring(1, lockTokenHeader.length() - 1);
+    log("UNLOCK(" + lockToken + ")");
+
+    if (LockManager.getInstance().releaseLock(object, lockToken)) {
+      response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+    } else {
+      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+    }
+  }
+}
blob - /dev/null
blob + 1729f3d7d238c8ce6d25dae22834c23c3f734ea3 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/Util.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class Util {
+
+  private static final SimpleDateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+
+  public static String getDateString(long time) {
+    return httpDateFormat.format(new Date(time));
+  }
+
+//  public static String getISODateString(long time) {
+//    return "";
+//  }
+
+
+  public static int copyStream(InputStream is, OutputStream os) throws IOException {
+    byte[] buffer = new byte[4096];
+    int bytesRead, bytesCount = 0;
+    while ((bytesRead = is.read(buffer)) != -1) {
+      os.write(buffer, 0, bytesRead);
+      bytesCount += bytesRead;
+    }
+    os.flush();
+
+    return bytesCount;
+  }
+}
blob - /dev/null
blob + c9780e5ce9c328ffad20e67ea417ac5ce93ae6e7 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/WebdavHandler.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.ResourceManager;
+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.HttpServlet;
+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;
+import java.util.Arrays;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public abstract class WebdavHandler {
+  static final int SC_LOCKED = 423;
+  static final int SC_MULTI_STATUS = 207;
+
+  private HttpServlet servlet;
+
+  public void setServlet(HttpServlet servlet) {
+    this.servlet = servlet;
+  }
+
+  public abstract void service(HttpServletRequest request, HttpServletResponse response) throws IOException;
+
+  void log(Node element) {
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    try {
+      XMLWriter xmlWriter = new XMLWriter(bos, OutputFormat.createPrettyPrint());
+      xmlWriter.write(element);
+      System.out.print(bos.toString());
+    } catch (IOException e) {
+      servlet.log("!! " + e.getMessage());
+    }
+  }
+
+  void log(String message) {
+    servlet.log("## " + message);
+  }
+
+
+  static URL getBaseUrl(HttpServletRequest request) {
+    try {
+      String requestUrl = request.getRequestURL().toString();
+      String requestUri = request.getRequestURI();
+      String requestUrlBase = requestUrl.substring(0, requestUrl.length() - requestUri.length());
+      return new URL(requestUrlBase);
+    } catch (MalformedURLException e) {
+      // ignore ...
+    }
+    return null;
+  }
+
+
+  /**
+   * Get the depth header value. This value defines how operations
+   * like propfind, move, copy etc. handle collections. A depth value
+   * of 0 will only return the current collection, 1 will return
+   * children too and infinity will recursively operate.
+   *
+   * @param request the servlet request
+   * @return the depth value as 0, 1 or Integer.MAX_VALUE;
+   */
+  int getDepth(HttpServletRequest request) {
+    String depth = request.getHeader("Depth");
+    int depthValue;
+
+    if (null == depth || "infinity".equalsIgnoreCase(depth)) {
+      depthValue = Integer.MAX_VALUE;
+    } else {
+      depthValue = Integer.parseInt(depth);
+    }
+
+    log("Depth: " + depthValue);
+    return depthValue;
+  }
+
+  /**
+   * Get the overwrite header value, whether to overwrite destination
+   * objects or collections or not.
+   *
+   * @param request the servlet request
+   * @return true or false
+   */
+  boolean getOverwrite(HttpServletRequest request) {
+    String overwrite = request.getHeader("Overwrite");
+    boolean overwriteValue = overwrite == null || "T".equals(overwrite);
+    log("Overwrite: " + overwriteValue);
+    return overwriteValue;
+  }
+
+  /**
+   * Get the destination object or collection. The destination header contains
+   * a URL to the destination which is returned as a file object.
+   *
+   * @param request the servlet request
+   * @return the file object of the destination
+   * @throws FileSystemException   if the filesystem cannot create a file object
+   * @throws MalformedURLException if the url is misformatted
+   */
+  FileObject getDestination(HttpServletRequest request) throws FileSystemException, MalformedURLException {
+    String targetUrlStr = request.getHeader("Destination");
+    FileObject targetObject = null;
+    if (null != targetUrlStr) {
+      URL target = new URL(targetUrlStr);
+      targetObject = ResourceManager.getFileObject(target.getPath());
+      log("Destination: " + targetObject.getName().getPath());
+    }
+
+    return targetObject;
+  }
+
+  /**
+   * Get the if header.
+   *
+   * @param request the request
+   * @return the value if the If: header.
+   */
+  String getIf(HttpServletRequest request) {
+    return request.getHeader("If");
+  }
+
+  /**
+   * Get and parse the timeout header value.
+   *
+   * @param request the request
+   * @return the timeout
+   */
+  long getTimeout(HttpServletRequest request) {
+    String timeout = request.getHeader("Timeout");
+    if (null != timeout) {
+      String[] timeoutValues = timeout.split(",[ ]*");
+      log(Arrays.asList(timeoutValues).toString());
+      if ("infinity".equalsIgnoreCase(timeoutValues[0])) {
+        return -1;
+      } else {
+        return Integer.parseInt(timeoutValues[0].replaceAll("Second-", ""));
+      }
+    }
+    return -1;
+  }
+}
blob - /dev/null
blob + 4356ce41848ea62de23b8a036e7aea950df89046 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/data/AbstractDavResource.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.data;
+
+import org.dom4j.Element;
+
+import java.util.HashSet;
+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_403 = "HTTP/1.1 403 Forbidden";
+
+  private static final String TAG_PROPSTAT = "propstat";
+  private static final String TAG_PROP = "prop";
+  private static final String TAG_STATUS = "status";
+
+  @SuppressWarnings({"UnusedReturnValue"})
+  public Element serializeToXml(Element root, List<String> requestedProperties) {
+    Element propStatEl = root.addElement(TAG_PROPSTAT);
+    Element propEl = propStatEl.addElement(TAG_PROP);
+
+    Set<String> missingProperties = new HashSet<String>();
+    for (String propertyName : requestedProperties) {
+      if (!addPropertyValue(propEl, propertyName)) {
+        missingProperties.add(propertyName);
+      }
+    }
+    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);
+      }
+      propStatEl.addElement(TAG_STATUS).addText(STATUS_404);
+    }
+
+    return root;
+  }
+
+  protected abstract boolean addPropertyValue(Element root, String propertyName);
+}
blob - /dev/null
blob + 918da1c04a6de60ad3be17615e2e920da8a2975b (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/data/DavCollection.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.data;
+
+import org.apache.commons.vfs.FileObject;
+import org.dom4j.Element;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class DavCollection extends DavResource {
+  public static final String COLLECTION = "collection";
+
+  public DavCollection(FileObject object) {
+    super(object);
+  }
+
+
+  public DavCollection(FileObject object, boolean ignoreValues) {
+    super(object, ignoreValues);
+  }
+
+  protected boolean addResourceTypeProperty(Element root) {
+    root.addElement(PROP_RESOURCETYPE).addElement(COLLECTION);
+    return true;
+  }
+
+  /**
+   * Ignore content language for collections.
+   *
+   * @param root the prop element to add to
+   * @return true, even though nothing is added
+   */
+  protected boolean addGetContentLanguageProperty(Element root) {
+    return true;
+  }
+
+  /**
+   * Ignore content length for collections.
+   *
+   * @param root the prop element to add to
+   * @return true, even though nothing is added
+   */
+  protected boolean addGetContentLengthProperty(Element root) {
+    return true;
+  }
+
+  /**
+   * Ignore content type for collections.
+   *
+   * @param root the prop element to add to
+   * @return true, even though nothing is added
+   */
+  protected boolean addGetContentTypeProperty(Element root) {
+    return true;
+  }
+
+  protected boolean addQuotaProperty(Element root) {
+    root.addElement(PROP_QUOTA).addText("1000000");
+    return true;
+  }
+
+  protected boolean addQuotaUsedProperty(Element root) {
+    // TODO add correct handling of used quota
+    root.addElement(PROP_QUOTA_USED).addText("0");
+    return true;
+  }
+
+  protected boolean addQuotaAvailableBytesProperty(Element root) {
+    root.addElement(PROP_QUOTA_AVAILABLE_BYTES).addText("1000000");
+    return true;
+  }
+
+  protected boolean addQuotaUsedBytesProperty(Element root) {
+    // TODO add correct handling of used quota
+    root.addElement(PROP_QUOTA_USED_BYTES).addText("0");
+    return true;
+  }
+}
blob - /dev/null
blob + 220a25349e7c8e941c9ae4944459052216833000 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/data/DavResource.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.data;
+
+import com.thinkberg.moxo.dav.Util;
+import com.thinkberg.moxo.dav.lock.Lock;
+import com.thinkberg.moxo.dav.lock.LockManager;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.dom4j.Element;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+@SuppressWarnings({"SameReturnValue"})
+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
+  );
+
+  private final FileObject object;
+  private boolean ignoreValues = false;
+
+  public DavResource(FileObject object) {
+    this(object, false);
+  }
+
+
+  public DavResource(FileObject object, boolean ignoreValues) {
+    this.object = object;
+    this.ignoreValues = ignoreValues;
+
+  }
+
+  /**
+   * 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
+   * @return true for successful addition and false for missing data
+   */
+  protected boolean addPropertyValue(Element root, String propertyName) {
+    if (PROP_CREATION_DATE.equals(propertyName)) {
+      return addCreationDateProperty(root);
+    } else if (PROP_DISPLAY_NAME.equals(propertyName)) {
+      return addGetDisplayNameProperty(root);
+    } else if (PROP_GET_CONTENT_LANGUAGE.equals(propertyName)) {
+      return addGetContentLanguageProperty(root);
+    } else if (PROP_GET_CONTENT_LENGTH.equals(propertyName)) {
+      return addGetContentLengthProperty(root);
+    } else if (PROP_GET_CONTENT_TYPE.equals(propertyName)) {
+      return addGetContentTypeProperty(root);
+    } else if (PROP_GET_ETAG.equals(propertyName)) {
+      return addGetETagProperty(root);
+    } else if (PROP_GET_LAST_MODIFIED.equals(propertyName)) {
+      return addGetLastModifiedProperty(root);
+    } else if (PROP_LOCK_DISCOVERY.equals(propertyName)) {
+      return addLockDiscoveryProperty(root);
+    } else if (PROP_RESOURCETYPE.equals(propertyName)) {
+      return addResourceTypeProperty(root);
+    } else if (PROP_SOURCE.equals(propertyName)) {
+      return addSourceProperty(root);
+    } else if (PROP_SUPPORTED_LOCK.equals(propertyName)) {
+      return addSupportedLockProperty(root);
+    } else {
+      // handle non-standard properties (keep a little separate)
+      if (PROP_QUOTA.equals(propertyName)) {
+        return addQuotaProperty(root);
+      } else if (PROP_QUOTA_USED.equals(propertyName)) {
+        return addQuotaUsedProperty(root);
+      } else if (PROP_QUOTA_AVAILABLE_BYTES.equals(propertyName)) {
+        return addQuotaAvailableBytesProperty(root);
+      } else if (PROP_QUOTA_USED_BYTES.equals(propertyName)) {
+        return addQuotaUsedBytesProperty(root);
+      }
+    }
+
+    return false;
+  }
+
+  @SuppressWarnings({"WeakerAccess", "UnusedParameters"})
+  protected boolean addCreationDateProperty(Element root) {
+    return false;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addGetDisplayNameProperty(Element root) {
+    Element el = root.addElement(PROP_DISPLAY_NAME);
+    if (!ignoreValues) {
+      el.addCDATA(object.getName().getBaseName());
+    }
+    return true;
+  }
+
+  @SuppressWarnings({"WeakerAccess", "UnusedParameters"})
+  protected boolean addGetContentLanguageProperty(Element root) {
+    return false;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addGetContentLengthProperty(Element root) {
+    try {
+      Element el = root.addElement(PROP_GET_CONTENT_LENGTH);
+      if (!ignoreValues) {
+        el.addText("" + object.getContent().getSize());
+      }
+      return true;
+    } catch (FileSystemException e) {
+      e.printStackTrace();
+      return false;
+    }
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addGetContentTypeProperty(Element root) {
+    try {
+      String contentType = object.getContent().getContentInfo().getContentType();
+      if (null == contentType || "".equals(contentType)) {
+        return false;
+      }
+
+      Element el = root.addElement(PROP_GET_CONTENT_TYPE);
+      if (!ignoreValues) {
+        el.addText(contentType);
+      }
+      return true;
+    } catch (FileSystemException e) {
+      e.printStackTrace();
+      return false;
+    }
+  }
+
+  @SuppressWarnings({"WeakerAccess", "UnusedParameters"})
+  protected boolean addGetETagProperty(Element root) {
+    return false;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addGetLastModifiedProperty(Element root) {
+    try {
+      Element el = root.addElement(PROP_GET_LAST_MODIFIED);
+      if (!ignoreValues) {
+        el.addText(Util.getDateString(object.getContent().getLastModifiedTime()));
+      }
+      return true;
+    } catch (FileSystemException e) {
+      e.printStackTrace();
+      return false;
+    }
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addLockDiscoveryProperty(Element root) {
+    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) {
+            lock.serializeToXml(lockdiscoveryEl);
+          }
+        }
+      }
+      return true;
+    } catch (FileSystemException e) {
+      e.printStackTrace();
+      root.remove(lockdiscoveryEl);
+      return false;
+    }
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addResourceTypeProperty(Element root) {
+    root.addElement(PROP_RESOURCETYPE);
+    return true;
+  }
+
+  @SuppressWarnings({"WeakerAccess", "UnusedParameters"})
+  protected boolean addSourceProperty(Element root) {
+    return false;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addSupportedLockProperty(Element root) {
+    Element supportedlockEl = root.addElement(PROP_SUPPORTED_LOCK);
+    if (!ignoreValues) {
+      Element exclLockentryEl = supportedlockEl.addElement("lockentry");
+      exclLockentryEl.addElement("lockscope").addElement("exclusive");
+      exclLockentryEl.addElement("locktype").addElement("write");
+      Element sharedLockentryEl = supportedlockEl.addElement("lockentry");
+      sharedLockentryEl.addElement("lockscope").addElement("shared");
+      sharedLockentryEl.addElement("locktype").addElement("write");
+    }
+
+    return true;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addQuotaProperty(Element root) {
+    return false;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addQuotaUsedProperty(Element root) {
+    return false;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addQuotaAvailableBytesProperty(Element root) {
+    return false;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected boolean addQuotaUsedBytesProperty(Element root) {
+    return false;
+  }
+}
blob - /dev/null
blob + 8d66d4881885922677820797ffae246c27b96ef9 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/data/DavResourceFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.data;
+
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class DavResourceFactory {
+  private static DavResourceFactory instance;
+
+  public static DavResourceFactory getInstance() {
+    if (null == instance) {
+      instance = new DavResourceFactory();
+    }
+    return instance;
+  }
+
+  private DavResourceFactory() {
+
+  }
+
+  public DavResource getDavResource(FileObject object) throws FileSystemException {
+    if (FileType.FOLDER.equals(object.getType())) {
+      return new DavCollection(object);
+    } else {
+      return new DavResource(object);
+    }
+  }
+}
blob - /dev/null
blob + 44f4c9aaa4abefb4b0565650ccaa2960ed875225 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/lock/Lock.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.lock;
+
+import org.apache.commons.vfs.FileObject;
+import org.dom4j.Element;
+
+import java.net.URL;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class Lock {
+  public final static String SHARED = "shared";
+  public final static String EXCLUSIVE = "exclusive";
+
+  public final static String WRITE = "write";
+
+  private final FileObject object;
+  private final String type;
+  private final String scope;
+  private final Object owner;
+  private final int depth;
+  private final long timeout;
+  private final String token;
+
+
+  public Lock(FileObject object, String type, String scope, Object owner,
+              int depth, long timeout) {
+    this.object = object;
+    this.type = type;
+    this.scope = scope;
+    this.owner = owner;
+    this.depth = depth;
+    this.timeout = timeout;
+
+    this.token = "opaquelocktoken:" + Integer.toHexString(object.hashCode());
+  }
+
+  public FileObject getObject() {
+    return object;
+  }
+
+  public String getType() {
+    return type;
+  }
+
+  public String getScope() {
+    return scope;
+  }
+
+  public Object getOwner() {
+    return owner;
+  }
+
+  public int getDepth() {
+    return depth;
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  public String getDepthValue() {
+    switch (depth) {
+      case 0:
+        return "0";
+      case 1:
+        return "1";
+      default:
+        return "Infinity";
+    }
+  }
+
+  @SuppressWarnings({"WeakerAccess"})
+  public String getTimeout() {
+    if (timeout == -1) {
+      return "Infinity";
+    }
+    return "Second-" + timeout;
+  }
+
+  public String getToken() {
+    return this.token;
+  }
+
+  /**
+   * Create an XML serialized version of the lock by adding an activelock tag
+   * with the locks properties to the root element provided.
+   *
+   * @param root the root element to add the activelock to
+   * @return the root element
+   */
+  @SuppressWarnings({"UnusedReturnValue"})
+  public Element serializeToXml(Element root) {
+    Element activelockEl = root.addElement("activelock");
+    activelockEl.addElement("locktype").addElement(getType());
+    activelockEl.addElement("lockscope").addElement(getScope());
+    activelockEl.addElement("depth").addText(getDepthValue());
+    // TODO handle owner correctly
+    if (getOwner() instanceof URL) {
+      activelockEl.addElement("owner").addElement("href")
+              .addText(((URL) getOwner()).toExternalForm());
+    } else {
+      activelockEl.addElement("owner").addText((String) getOwner());
+    }
+    activelockEl.addElement("timeout").addText(getTimeout());
+    activelockEl.addElement("locktoken")
+            .addElement("href").addText(getToken());
+
+    return root;
+  }
+
+  /**
+   * There can only be one lock per object, thus compare the file objects.
+   *
+   * @param other the other lock to compare to
+   * @return whether this lock is for the same file object
+   */
+  public boolean equals(Object other) {
+    return object.equals(((Lock) other).object);
+  }
+
+
+  public String toString() {
+    return new StringBuffer().append("Lock[")
+            .append(object).append(",")
+            .append(type).append(",")
+            .append(scope).append("]").toString();
+  }
+}
+
blob - /dev/null
blob + 0944f6914361ef87067e9e0739796e73448509e2 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/lock/LockConditionFailedException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.lock;
+
+import java.util.List;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class LockConditionFailedException extends LockException {
+  private String condition = null;
+
+  public LockConditionFailedException(List<Lock> locks) {
+    super(locks);
+  }
+
+  public LockConditionFailedException(List<Lock> locks, String condition) {
+    super(locks);
+    this.condition = condition;
+
+  }
+
+  public String getCondition() {
+    return condition;
+  }
+}
blob - /dev/null
blob + bc0d2328671d1f91e7b57dc24d33acd8d01e849f (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/lock/LockConflictException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.lock;
+
+import java.util.List;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class LockConflictException extends LockException {
+  public LockConflictException(List<Lock> locks) {
+    super(locks);
+  }
+}
blob - /dev/null
blob + 937cae1dfcf4c4ed1d768feb4e82ebf20cff4a46 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/lock/LockException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.lock;
+
+import java.util.List;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class LockException extends Exception {
+  private final List<Lock> locks;
+
+  public LockException(List<Lock> locks) {
+    super();
+    this.locks = locks;
+  }
+
+  public List<Lock> getLocks() {
+    return locks;
+  }
+}
blob - /dev/null
blob + 3a78f6d504fbd03a7ef5615a5b68b6a02b64931e (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/dav/lock/LockManager.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.dav.lock;
+
+import com.thinkberg.moxo.vfs.DepthFileSelector;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSelectInfo;
+import org.apache.commons.vfs.FileSystemException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The lock manager is responsible for exclusive and shared write locks on the
+ * DAV server. It is used to acquire a lock, release a lock, discover existing
+ * locks or check conditions. The lock manager is a singleton.
+ *
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class LockManager {
+  private static LockManager instance = null;
+
+  /**
+   * Get an instance of the lock manager.
+   *
+   * @return the lock manager
+   */
+  public static LockManager getInstance() {
+    if (null == instance) {
+      instance = new LockManager();
+    }
+
+    return instance;
+  }
+
+  private final Map<FileObject, List<Lock>> lockMap;
+
+  /**
+   * The lock manager is a singleton and cannot be instantiated directly.
+   */
+  private LockManager() {
+    lockMap = new HashMap<FileObject, List<Lock>>();
+  }
+
+  /**
+   * Acquire a lock. This will first check for conflicts and throws exceptions if
+   * there are existing locks or for some reason the lock could not be acquired.
+   *
+   * @param lock the lock to acquire
+   * @throws LockConflictException if an existing lock has priority
+   * @throws FileSystemException   if the file object and its path cannot be accessed
+   */
+  public void acquireLock(Lock lock) throws LockConflictException, FileSystemException {
+    checkConflicts(lock);
+    addLock(lock);
+  }
+
+  /**
+   * Release a lock on a file object with a given lock token. Releeases the lock if
+   * if one exists and if the lock token is valid for the found lock.
+   *
+   * @param object the file object we want to unlock
+   * @param token  the lock token associated with the file object
+   * @return true if the lock has been released, false if not
+   */
+  public boolean releaseLock(FileObject object, String token) {
+    List<Lock> locks = lockMap.get(object);
+    if (null != locks) {
+      for (Lock lock : locks) {
+        if (lock.getToken().equals(token)) {
+          locks.remove(lock);
+          return true;
+        }
+      }
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Discover locks for a given file object. This will find locks for the object
+   * itself and parent path locks with a depth that reaches the file object.
+   *
+   * @param object the file object to find locks for
+   * @return the locks that are found for this file object
+   * @throws FileSystemException if the file object or its parents cannot be accessed
+   */
+  public List<Lock> discoverLock(FileObject object) throws FileSystemException {
+    FileObject parent = object;
+    while (parent != null) {
+      List<Lock> parentLocks = lockMap.get(parent);
+      if (parentLocks != null && !parentLocks.isEmpty()) {
+        return parentLocks;
+      }
+      parent = parent.getParent();
+    }
+
+    return null;
+  }
+
+  /**
+   * Check a condition for a file object. The condition check looks for locks on the
+   * given file object and will throw exceptions if the condition does not meet the
+   * lock requirements (i.e. lock token in the condition differes from the token in
+   * the discovered lock) or no condition exists if a lock was discovered. If no lock
+   * was discovered but a condition exists a lock condition exception will be thrown.
+   *
+   * @param object the file object in question
+   * @param ifCond the condition to check
+   * @return the lock found for the given if condition and the object
+   * @throws FileSystemException          if the object or path cannot be accessed
+   * @throws LockConflictException        if there is a a lock but no condition
+   * @throws LockConditionFailedException if the condition and lock does not match
+   */
+  public Lock checkCondition(FileObject object, String ifCond)
+          throws FileSystemException, LockConflictException, LockConditionFailedException {
+    List<Lock> locks = discoverLock(object);
+    if (null != locks && !locks.isEmpty()) {
+      // if there is no condition but a lock, this must fail
+      if (null == ifCond) {
+        throw new LockConflictException(locks);
+      }
+
+      // simple check whether the token is in the condition (TODO: check for NOT)
+      for (Lock lock : locks) {
+        if (ifCond.indexOf("<" + lock.getToken() + ">") != -1) {
+          return lock;
+        }
+      }
+      throw new LockConditionFailedException(locks);
+    } else if (null != ifCond) {
+      // no lock but a condition must fail too
+      throw new LockConditionFailedException(null);
+    }
+
+    return null;
+  }
+
+
+  /**
+   * Add a lock to the list of shared locks of a given object.
+   *
+   * @param lock the lock to add
+   */
+  private void addLock(Lock lock) {
+    FileObject object = lock.getObject();
+    List<Lock> locks = lockMap.get(object);
+    if (null == locks) {
+      locks = new ArrayList<Lock>();
+      lockMap.put(object, locks);
+    }
+    locks.add(lock);
+  }
+
+  /**
+   * Check whether a lock conflicts with already existing locks up and down the path.
+   * First we go up the path to check for parent locks that may include the file object
+   * and the go down the directory tree (if depth requires it) to check locks that
+   * will conflict.
+   *
+   * @param requestedLock the lock requested
+   * @throws LockConflictException if a conflicting lock was found
+   * @throws FileSystemException   if the file object or path cannot be accessed
+   */
+  private void checkConflicts(final Lock requestedLock) throws LockConflictException, FileSystemException {
+    // find locks in the parent path
+    FileObject parent = requestedLock.getObject();
+    while (parent != null) {
+      List<Lock> parentLocks = lockMap.get(parent);
+      if (parentLocks != null && !parentLocks.isEmpty()) {
+        for (Lock parentLock : parentLocks) {
+          if (Lock.EXCLUSIVE.equals(requestedLock.getScope()) || Lock.EXCLUSIVE.equals(parentLock.getScope())) {
+            throw new LockConflictException(parentLocks);
+          }
+        }
+      }
+      parent = parent.getParent();
+    }
+
+    // look for locks down the path (if depth requests it)
+    if (requestedLock.getDepth() != 0 && requestedLock.getObject().getChildren().length > 0) {
+      requestedLock.getObject().findFiles(new DepthFileSelector(1, requestedLock.getDepth()) {
+        public boolean includeFile(FileSelectInfo fileSelectInfo) throws Exception {
+          List<Lock> childLocks = lockMap.get(fileSelectInfo.getFile());
+          for (Lock childLock : childLocks) {
+            if (Lock.EXCLUSIVE.equals(requestedLock.getScope()) || Lock.EXCLUSIVE.equals(childLock.getScope())) {
+              throw new LockConflictException(childLocks);
+            }
+          }
+          return false;
+        }
+      }, false, new ArrayList());
+    }
+  }
+}
blob - /dev/null
blob + 5d8f96a1cb04ca9f1dfd9b2d00daa1eb695d0e06 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/s3/S3Connector.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.s3;
+
+import org.apache.commons.vfs.FileSystemException;
+import org.jets3t.service.Jets3tProperties;
+import org.jets3t.service.S3Service;
+import org.jets3t.service.S3ServiceException;
+import org.jets3t.service.impl.rest.httpclient.RestS3Service;
+import org.jets3t.service.model.S3Bucket;
+import org.jets3t.service.security.AWSCredentials;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3Connector {
+  private static final String APPLICATION_DESCRIPTION = "S3 VFS Connector/1.0";
+
+  private static S3Connector instance;
+
+  /**
+   * Get an instance of the S3Connector which is initialized and authenticated to the
+   * Amazon S3 Service.
+   *
+   * @return an S3 connector
+   * @throws FileSystemException if connection or authentication fails
+   */
+  public static S3Connector getInstance() throws FileSystemException {
+    if (null == instance) {
+      instance = new S3Connector();
+    }
+    return instance;
+  }
+
+  private S3Service service;
+
+  /**
+   * Initialize Amazon S3.
+   *
+   * @throws FileSystemException if S3 can't be initialized
+   */
+  private S3Connector() throws FileSystemException {
+    String propertiesFileName = System.getProperty("moxo.properties", "moxo.properties");
+    Jets3tProperties properties = Jets3tProperties.getInstance(propertiesFileName);
+
+    if (!properties.isLoaded()) {
+      throw new FileSystemException("can't find S3 configuration: " + propertiesFileName);
+    }
+
+    AWSCredentials awsCredentials = new AWSCredentials(
+            properties.getStringProperty("accesskey", null),
+            properties.getStringProperty("secretkey", null));
+
+
+    try {
+      service = new RestS3Service(awsCredentials, APPLICATION_DESCRIPTION, null);
+    } catch (S3ServiceException e) {
+      throw new FileSystemException("can't initialize S3 Service", e);
+    }
+  }
+
+  /**
+   * Get a virtual file system root corresponding to a bucket.
+   *
+   * @param bucket the bucket that contains the filesystem
+   * @return the S3 root
+   * @throws FileSystemException if the bucket is not found
+   */
+  public S3VfsRoot getRoot(String bucket) throws FileSystemException {
+    try {
+      if (service.isBucketAccessible(bucket)) {
+        return new S3VfsRootImpl(service, new S3Bucket(bucket));
+      }
+      throw new FileSystemException("vsf.provider.vfs/cant-access.error");
+    } catch (S3ServiceException e) {
+      throw new FileSystemException(e);
+    }
+  }
+}
blob - /dev/null
blob + b647ccf5ddd6dc7bd616ac1e91ecafb4480ede53 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/s3/S3VfsObject.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.s3;
+
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+
+import java.io.InputStream;
+
+/**
+ * This interface describes methods to access S3 Objects whether files or folders.
+ *
+ * @author Matthias L. Jugel
+ */
+public interface S3VfsObject {
+  /**
+   * The base name of the S3 Object.
+   *
+   * @return the base name without path
+   */
+  public String getName();
+
+  /**
+   * The absolute path of the S3 Object.
+   *
+   * @return the absolute path
+   */
+  public String getPath();
+
+  /**
+   * The type of the S3 Object. May be file, folder or imaginary.
+   *
+   * @return the file type
+   * @see org.apache.commons.vfs.FileType#FILE
+   * @see org.apache.commons.vfs.FileType#FOLDER
+   * @see org.apache.commons.vfs.FileType#IMAGINARY
+   */
+  public FileType getType();
+
+  /**
+   * Get the last modified time. The value is undefined if this is an imaginary file.
+   *
+   * @return the last modified time in milliseconds
+   */
+  public long getLastModified();
+
+  /**
+   * Get the content length. The value may be 0 for imaginary files.
+   *
+   * @return the content length in bytes
+   */
+  public long getContentLength();
+
+  /**
+   * Get the actual content MIME type. May be null for imaginary files.
+   *
+   * @return the MIME type
+   */
+  public String getContentType();
+
+  /**
+   * Get the input stream to read data from the object or null if this file is imaginary
+   *
+   * @return the input stream
+   * @throws FileSystemException if the S3 object cannot be accessed
+   */
+  public InputStream getInputStream() throws FileSystemException;
+
+  /**
+   * List all children names relative to this S3 Object. It is assumed that this is
+   * only called on folders. Non-Folders may return results but there is no defined
+   * behaviour in such a case.
+   *
+   * @return the list of children names (without path)
+   * @throws FileSystemException if the object cannot be accessed
+   */
+  public String[] getChildren() throws FileSystemException;
+}
blob - /dev/null
blob + 413839eee68c4c031e4a9e55a9ad6c96d64f13c8 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/s3/S3VfsObjectImpl.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.s3;
+
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+import org.jets3t.service.S3Service;
+import org.jets3t.service.S3ServiceException;
+import org.jets3t.service.model.S3Bucket;
+import org.jets3t.service.model.S3Object;
+import org.jets3t.service.utils.Mimetypes;
+
+import java.io.InputStream;
+
+/**
+ * Implementation of the virtual S3 file system object using the Jets3t library.
+ *
+ * @author Matthias L. Jugel
+ */
+public class S3VfsObjectImpl implements S3VfsObject {
+  private final S3Service service;
+  private final S3Bucket bucket;
+  private S3Object object;
+
+  /**
+   * Create a new S3 Object wrapper for the virtual filesystem. If an object exists its details
+   * are loaded and a virtual object is created for non-existing files. If data is requested that
+   * is not part of the details (HEAD) the real object is loaded on demand.
+   *
+   * @param service the S3 service used for retrieving the object
+   * @param bucket  the bucket the object is located in
+   * @param path    the full path to the S3 object (the key)
+   * @throws FileSystemException if there is a problem accessing the object
+   */
+  @SuppressWarnings({"RedundantThrows"})
+  public S3VfsObjectImpl(S3Service service, S3Bucket bucket, String path) throws FileSystemException {
+    this.service = service;
+    this.bucket = bucket;
+    // check object and load details or create a virtual object
+    String s3Path = makeS3Path(path);
+    try {
+      object = service.getObjectDetails(bucket, s3Path);
+    } catch (S3ServiceException e) {
+      object = new S3Object(bucket, s3Path);
+    }
+  }
+
+  /**
+   * Get the name of this object.
+   *
+   * @return the base name of the object
+   */
+  public String getName() {
+    String key = object.getKey();
+    if ("".equals(key)) {
+      return "/";
+    }
+
+    int lastSlash = key.lastIndexOf("/");
+    if (lastSlash == -1) {
+      return key;
+    } else {
+      return key.substring(lastSlash + 1);
+    }
+  }
+
+  /**
+   * The file type as provided by commons-vfs.
+   *
+   * @return the file type
+   * @see org.apache.commons.vfs.FileType#FILE
+   * @see org.apache.commons.vfs.FileType#FOLDER
+   * @see org.apache.commons.vfs.FileType#IMAGINARY
+   */
+  public FileType getType() {
+    if (null == object.getContentType()) {
+      return FileType.IMAGINARY;
+    }
+
+    String key = object.getKey();
+    String contentType = object.getContentType();
+    if ("".equals(key) || "/".equals(key) || Mimetypes.MIMETYPE_JETS3T_DIRECTORY.equals(contentType)) {
+      return FileType.FOLDER;
+    }
+
+    return FileType.FILE;
+  }
+
+  /**
+   * Get the full path of the object as an absolute path (including heading /).
+   *
+   * @return the full path
+   */
+  public String getPath() {
+    return makeAbsolutePath(object.getKey());
+  }
+
+  /**
+   * Get the last modified time of the object. The result is undefined if this object does not exist.
+   *
+   * @return the last modified time
+   */
+  public long getLastModified() {
+    return object.getLastModifiedDate().getTime();
+  }
+
+  /**
+   * Get the content length of the object. The result is 0 for non-existing objects.
+   *
+   * @return the content length
+   */
+  public long getContentLength() {
+    return object.getContentLength();
+  }
+
+  /**
+   * Get the actual content type as a MIME type. The result is undefined if this object does not exist.
+   *
+   * @return the mime type
+   */
+  public String getContentType() {
+    return object.getContentType();
+  }
+
+  /**
+   * Get all children names relative to this object. It is assumed that
+   * this method is called for directories only. The returned names are
+   * not absolute names, but rather relative base names.
+   *
+   * @return the list of children names
+   * @throws FileSystemException if the object cannot be accessed
+   */
+  public String[] getChildren() throws FileSystemException {
+    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, "/");
+      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;
+    } catch (S3ServiceException e) {
+      throw new FileSystemException(e);
+    }
+  }
+
+  /**
+   * Get the input stream to read data from the object. Returns null for non-existing objects.
+   * Calling this method causes the object to be reloaded if it has not yet been fully
+   * retrieved from S3.
+   *
+   * @return the input stream
+   * @throws FileSystemException if the input stream cannot be accessed
+   */
+  public InputStream getInputStream() throws FileSystemException {
+    try {
+      InputStream is = object.getDataInputStream();
+      if (null == is) {
+        object = service.getObject(bucket, object.getKey());
+        is = object.getDataInputStream();
+      }
+      return is;
+    } catch (S3ServiceException e) {
+      throw new FileSystemException(e);
+    }
+  }
+
+  // Utility methods
+
+  /**
+   * Make an absolute path out of an S3 (Jets3t path) which does not contain
+   * a slash. If the key is "SomeDirectory/afile.txt" it will become
+   * "/SomeDirectory/afile.txt". All files will be handled with their full
+   * key name so this cannot be confused with relative file names which are
+   * handled by commons-vfs.
+   *
+   * @param key the objects key
+   * @return the absolute path name
+   */
+  private String makeAbsolutePath(String key) {
+    return "/" + key;
+  }
+
+  /**
+   * Create an S3 path (the key) from a commons-vfs path. This simply
+   * strips the slash from the beginning if it exists. We assume that the
+   * path is always absolute, so a missing slash at the beginning is
+   * simply ignored.
+   *
+   * @param path the absolute file path (with or without heading slash)
+   * @return the S3 object key
+   */
+  private String makeS3Path(String path) {
+    if ("".equals(path)) {
+      return path;
+    } else {
+      return path.substring(1);
+    }
+  }
+}
blob - /dev/null
blob + 6594366a8e657a91f2c102756d9a35b42e138610 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/s3/S3VfsRoot.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.s3;
+
+import org.apache.commons.vfs.FileSystemException;
+
+/**
+ * Virtual Root of an S3 file system. Implement this interface to be able to get
+ * objects from Amazon S3. The root should be bound to a bucket that contains the
+ * virtual file system. All calls to S3VfsRoot#getObject should return a virtual
+ * object within this file system.
+ *
+ * @author Matthias L. Jugel
+ */
+public interface S3VfsRoot {
+  /**
+   * Get a file object relative to this S3 root. A roo
+   *
+   * @param path the path of the file (the absolute path)
+   * @return the virtual S3 file object
+   * @throws FileSystemException if the object cannot be accessed
+   */
+  public S3VfsObject getObject(String path) throws FileSystemException;
+}
blob - /dev/null
blob + 435dc12b13ce00f347f7278cb86e82f7d44dc975 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/s3/S3VfsRootImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.s3;
+
+import org.apache.commons.vfs.FileSystemException;
+import org.jets3t.service.S3Service;
+import org.jets3t.service.model.S3Bucket;
+
+/**
+ * Virtual Root of an S3 file system using the Jets3t library. The root
+ * stores the service and bucket to create file objects found in the
+ * bucket.
+ *
+ * @author Matthias L. Jugel
+ */
+public class S3VfsRootImpl implements S3VfsRoot {
+  private final S3Service service;
+  private final S3Bucket bucket;
+
+  /**
+   * Create a new bucket file system root.
+   *
+   * @param service the S3 service to use
+   * @param bucket  the S3 bucket this root is bound to
+   */
+  public S3VfsRootImpl(S3Service service, S3Bucket bucket) {
+    this.service = service;
+    this.bucket = bucket;
+  }
+
+  /**
+   * Create a new S3VfsObject using the given path. The object may not exist
+   * in the bucket and will then return an object that has an imaginary file type.
+   *
+   * @param path the absolute path to the file
+   * @return the virtual S3 object representing the file
+   * @throws FileSystemException if the file cannot be accessed
+   * @see com.thinkberg.moxo.s3.S3VfsObject
+   */
+  public S3VfsObject getObject(String path) throws FileSystemException {
+    return new S3VfsObjectImpl(service, bucket, path);
+  }
+}
blob - /dev/null
blob + ee85960287fcb8693e420e89092915792c5c13e3 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/servlet/MoxoS3WebdavServlet.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.servlet;
+
+import com.thinkberg.moxo.dav.CopyHandler;
+import com.thinkberg.moxo.dav.DeleteHandler;
+import com.thinkberg.moxo.dav.GetHandler;
+import com.thinkberg.moxo.dav.HeadHandler;
+import com.thinkberg.moxo.dav.LockHandler;
+import com.thinkberg.moxo.dav.MkColHandler;
+import com.thinkberg.moxo.dav.MoveHandler;
+import com.thinkberg.moxo.dav.OptionsHandler;
+import com.thinkberg.moxo.dav.PostHandler;
+import com.thinkberg.moxo.dav.PropFindHandler;
+import com.thinkberg.moxo.dav.PropPatchHandler;
+import com.thinkberg.moxo.dav.PutHandler;
+import com.thinkberg.moxo.dav.UnlockHandler;
+import com.thinkberg.moxo.dav.WebdavHandler;
+import org.mortbay.jetty.Response;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class MoxoS3WebdavServlet extends HttpServlet {
+  private final Map<String, WebdavHandler> handlers = new HashMap<String, WebdavHandler>();
+
+  public MoxoS3WebdavServlet() {
+    handlers.put("COPY", new CopyHandler());
+    handlers.put("DELETE", new DeleteHandler());
+    handlers.put("GET", new GetHandler());
+    handlers.put("HEAD", new HeadHandler());
+    handlers.put("LOCK", new LockHandler());
+    handlers.put("MKCOL", new MkColHandler());
+    handlers.put("MOVE", new MoveHandler());
+    handlers.put("OPTIONS", new OptionsHandler());
+    handlers.put("POST", new PostHandler());
+    handlers.put("PROPFIND", new PropFindHandler());
+    handlers.put("PROPPATCH", new PropPatchHandler());
+    handlers.put("PUT", new PutHandler());
+    handlers.put("UNLOCK", new UnlockHandler());
+
+    for (WebdavHandler handler : handlers.values()) {
+      handler.setServlet(this);
+    }
+  }
+
+  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+    String method = request.getMethod();
+    log(">> " + request.getMethod() + " " + request.getPathInfo());
+    if (request.getHeader("X-Litmus") != null) {
+      log("!! " + request.getHeader("X-Litmus"));
+    }
+    if (handlers.containsKey(method)) {
+      handlers.get(method).service(request, response);
+    } else {
+      response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
+    }
+    Response jettyResponse = ((Response) response);
+    String reason = jettyResponse.getReason();
+    log("<< " + jettyResponse.getStatus() + (reason != null ? ": " + reason : ""));
+  }
+}
blob - /dev/null
blob + c955d451826291a42e1b6aac6197ae8efee01db4 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/vfs/DepthFileSelector.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.vfs;
+
+import org.apache.commons.vfs.FileSelectInfo;
+import org.apache.commons.vfs.FileSelector;
+
+/**
+ * A file selector that operates depth of the directory structure and will
+ * select all files up to and including the depth given in the constructor.
+ *
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class DepthFileSelector implements FileSelector {
+  private final int maxDepth;
+  private final int minDepth;
+
+  /**
+   * Create a file selector that will select ALL files.
+   */
+  public DepthFileSelector() {
+    this(0, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Create a file selector that will select all files up to and including
+   * the directory depth.
+   *
+   * @param depth the maximum depth
+   */
+  public DepthFileSelector(int depth) {
+    this(0, depth);
+  }
+
+  @SuppressWarnings({"SameParameterValue"})
+  public DepthFileSelector(int min, int max) {
+    minDepth = min;
+    maxDepth = max;
+  }
+
+  public boolean includeFile(FileSelectInfo fileSelectInfo) throws Exception {
+    int depth = fileSelectInfo.getDepth();
+    return depth >= minDepth && depth <= maxDepth;
+  }
+
+  public boolean traverseDescendents(FileSelectInfo fileSelectInfo) throws Exception {
+    return fileSelectInfo.getDepth() < maxDepth;
+  }
+}
blob - /dev/null
blob + 5768fc6965485ff468abf9e84001279acbb70358 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/vfs/S3FileName.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.vfs;
+
+import org.apache.commons.vfs.FileType;
+import org.apache.commons.vfs.provider.local.LocalFileName;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3FileName extends LocalFileName {
+  @SuppressWarnings({"WeakerAccess"})
+  protected S3FileName(final String scheme, final String rootFile, final String path, final FileType type) {
+    super(scheme, rootFile, path, type);
+  }
+}
blob - /dev/null
blob + 9c5d272ffd3f18b369af876efa3fa7762060d730 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/vfs/S3FileNameParser.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.vfs;
+
+import org.apache.commons.vfs.FileName;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+import org.apache.commons.vfs.provider.AbstractFileNameParser;
+import org.apache.commons.vfs.provider.UriParser;
+import org.apache.commons.vfs.provider.VfsComponentContext;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3FileNameParser extends AbstractFileNameParser {
+  private static final S3FileNameParser instance = new S3FileNameParser();
+
+  public static S3FileNameParser getInstance() {
+    return instance;
+  }
+
+  private S3FileNameParser() {
+
+  }
+
+
+  public FileName parseUri(final VfsComponentContext context, final FileName base, final String filename) throws FileSystemException {
+    StringBuffer name = new StringBuffer();
+
+    String scheme = UriParser.extractScheme(filename, name);
+    UriParser.canonicalizePath(name, 0, name.length(), this);
+
+    UriParser.fixSeparators(name);
+
+    // Normalise the path
+    FileType fileType = UriParser.normalisePath(name);
+
+    // Extract the root prefix
+    final String bucketName = UriParser.extractFirstElement(name);
+
+
+    return new S3FileName(scheme, bucketName, name.toString(), fileType);
+  }
+
+}
blob - /dev/null
blob + 7e14e4183a36cc376545891551023afd1a7e67ba (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/vfs/S3FileObject.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.vfs;
+
+import com.thinkberg.moxo.s3.S3VfsObject;
+import org.apache.commons.vfs.FileName;
+import org.apache.commons.vfs.FileType;
+import org.apache.commons.vfs.provider.AbstractFileObject;
+
+import java.io.InputStream;
+
+/**
+ * A VFS wrapper for the S3 file object. All requests are proxied through to the actual
+ * data object that contains an implementation of the virtual S3 Object.
+ *
+ * @author Matthias L. Jugel
+ * @see com.thinkberg.moxo.s3.S3VfsObject
+ */
+public class S3FileObject extends AbstractFileObject {
+
+  private final S3VfsObject s3Object;
+
+  /**
+   * Create a new S3 file object with the given virtual S3 object as data backend.
+   *
+   * @param fileName   the file name of the current file in the virtual filesystem
+   * @param fileSystem the filesystem used (@see S3FileSystem)
+   * @param s3Object   the actual data object
+   */
+  @SuppressWarnings({"WeakerAccess"})
+  protected S3FileObject(FileName fileName, S3FileSystem fileSystem, S3VfsObject s3Object) {
+    super(fileName, fileSystem);
+    this.s3Object = s3Object;
+  }
+
+  protected FileType doGetType() throws Exception {
+    return s3Object.getType();
+  }
+
+  protected String[] doListChildren() throws Exception {
+    return s3Object.getChildren();
+  }
+
+  protected long doGetContentSize() throws Exception {
+    return s3Object.getContentLength();
+  }
+
+
+  protected long doGetLastModifiedTime() throws Exception {
+    return s3Object.getLastModified();
+  }
+
+  protected InputStream doGetInputStream() throws Exception {
+    return s3Object.getInputStream();
+  }
+
+}
blob - /dev/null
blob + c0fa0a50a3b224fdf9528bfdc917c8ad756523de (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/vfs/S3FileProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.vfs;
+
+import com.thinkberg.moxo.s3.S3Connector;
+import com.thinkberg.moxo.s3.S3VfsRoot;
+import org.apache.commons.vfs.Capability;
+import org.apache.commons.vfs.FileName;
+import org.apache.commons.vfs.FileSystem;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileSystemOptions;
+import org.apache.commons.vfs.provider.AbstractOriginatingFileProvider;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3FileProvider extends AbstractOriginatingFileProvider {
+
+  public final static Collection capabilities = Collections.unmodifiableCollection(Arrays.asList(
+/*
+          Capability.CREATE,
+          Capability.DELETE,
+          Capability.RENAME,
+*/
+Capability.GET_TYPE,
+Capability.GET_LAST_MODIFIED,
+/*
+          Capability.SET_LAST_MODIFIED_FILE,
+          Capability.SET_LAST_MODIFIED_FOLDER,
+*/
+Capability.LIST_CHILDREN,
+Capability.READ_CONTENT,
+Capability.URI/*,
+
+          Capability.WRITE_CONTENT,
+          Capability.APPEND_CONTENT,
+          Capability.RANDOM_ACCESS_READ,
+          Capability.RANDOM_ACCESS_WRITE
+*/
+  ));
+
+
+  public S3FileProvider() {
+    super();
+    setFileNameParser(S3FileNameParser.getInstance());
+  }
+
+  protected FileSystem doCreateFileSystem(FileName fileName, FileSystemOptions fileSystemOptions) throws FileSystemException {
+    S3FileName s3FileName = (S3FileName) fileName;
+    String s3BucketId = s3FileName.getRootFile();
+    S3VfsRoot s3VfsRoot = S3Connector.getInstance().getRoot(s3BucketId);
+    return new S3FileSystem(s3FileName, fileSystemOptions, s3VfsRoot);
+  }
+
+  public Collection getCapabilities() {
+    return capabilities;
+  }
+}
blob - /dev/null
blob + b93b1b2b2e973615eca2965d99c3ab752bfd73c3 (mode 644)
--- /dev/null
+++ src/main/java/com/thinkberg/moxo/vfs/S3FileSystem.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.vfs;
+
+import com.thinkberg.moxo.s3.S3VfsRoot;
+import org.apache.commons.vfs.FileName;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystem;
+import org.apache.commons.vfs.FileSystemOptions;
+import org.apache.commons.vfs.provider.AbstractFileSystem;
+
+import java.util.Collection;
+
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3FileSystem extends AbstractFileSystem implements FileSystem {
+  private final S3VfsRoot s3VfsRoot;
+
+  @SuppressWarnings({"WeakerAccess"})
+  protected S3FileSystem(S3FileName fileName, FileSystemOptions fileSystemOptions, S3VfsRoot s3VfsRoot) {
+    super(fileName, null, fileSystemOptions);
+    this.s3VfsRoot = s3VfsRoot;
+  }
+
+  @SuppressWarnings({"unchecked"})
+  protected void addCapabilities(Collection caps) {
+    caps.addAll(S3FileProvider.capabilities);
+  }
+
+  protected FileObject createFile(FileName fileName) throws Exception {
+    return new S3FileObject(fileName, this, s3VfsRoot.getObject(fileName.getPathDecoded()));
+  }
+
+}
blob - /dev/null
blob + 686c14998f8bbad319b7f2e07f193ecb66f3cbb8 (mode 644)
--- /dev/null
+++ src/main/resources/META-INF/vfs-providers.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright 2007 Matthias L. Jugel.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~        http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<providers>
+
+  <provider class-name="com.thinkberg.moxo.vfs.S3FileProvider">
+    <scheme name="s3"/>
+    <if-available class-name="org.jets3t.service.S3Service"/>
+  </provider>
+
+</providers>
blob - /dev/null
blob + ae39e881510681e8eb8b08d6c28510f4eec11b1d (mode 644)
--- /dev/null
+++ src/main/resources/jetty.xml
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Server                                      -->
+<!--                                                                 -->
+<!-- Documentation of this file format can be found at:              -->
+<!-- http://docs.codehaus.org/display/JETTY/jetty.xml                -->
+<!--                                                                 -->
+<!-- =============================================================== -->
+
+
+<Configure id="Server" class="org.mortbay.jetty.Server">
+
+  <!-- =========================================================== -->
+  <!-- Server Thread Pool                                          -->
+  <!-- =========================================================== -->
+  <Set name="ThreadPool">
+    <!-- Default bounded blocking threadpool
+    -->
+    <New class="org.mortbay.thread.BoundedThreadPool">
+      <Set name="minThreads">10</Set>
+      <Set name="lowThreads">50</Set>
+      <Set name="maxThreads">250</Set>
+    </New>
+
+    <!-- Optional Java 5 bounded threadpool with job queue
+    <New class="org.mortbay.thread.concurrent.ThreadPool">
+      <Arg type="int">0</Arg>
+      <Set name="corePoolSize">10</Set>
+      <Set name="maximumPoolSize">250</Set>
+    </New>
+    -->
+  </Set>
+
+
+  <!-- =========================================================== -->
+  <!-- Set connectors                                              -->
+  <!-- =========================================================== -->
+  <!-- One of each type!                                           -->
+  <!-- =========================================================== -->
+
+  <!-- Use this connector for many frequently idle connections
+       and for threadless continuations.
+  -->
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.mortbay.jetty.nio.SelectChannelConnector">
+        <Set name="port">
+          <SystemProperty name="jetty.port" default="8080"/>
+        </Set>
+        <Set name="maxIdleTime">30000</Set>
+        <Set name="Acceptors">2</Set>
+        <Set name="confidentialPort">8443</Set>
+      </New>
+    </Arg>
+  </Call>
+
+
+  <!-- Use this connector if NIO is not available.
+  <Call name="addConnector">
+    <Arg>
+        <New class="org.mortbay.jetty.bio.SocketConnector">
+          <Set name="port">8081</Set>
+          <Set name="maxIdleTime">50000</Set>
+          <Set name="lowResourceMaxIdleTime">1500</Set>
+        </New>
+    </Arg>
+  </Call>
+  -->
+
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <!-- To add a HTTPS SSL listener                                     -->
+  <!-- see jetty-ssl.xml to add an ssl connector. use                  -->
+  <!-- java -jar start.jar etc/jetty.xml etc/jetty-ssl.xml             -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+  <!-- =========================================================== -->
+  <!-- Set up global session ID manager                            -->
+  <!-- =========================================================== -->
+  <!--
+  <Set name="sessionIdManager">
+    <New class="org.mortbay.jetty.servlet.HashSessionIdManager">
+      <Set name="workerName">node1</Set>
+    </New>
+  </Set>
+  -->
+
+  <!-- =========================================================== -->
+  <!-- Set handler Collection Structure                            -->
+  <!-- =========================================================== -->
+  <Set name="handler">
+    <New id="Handlers" class="org.mortbay.jetty.handler.HandlerCollection">
+      <Set name="handlers">
+        <Array type="org.mortbay.jetty.Handler">
+          <Item>
+            <New id="Contexts" class="org.mortbay.jetty.handler.ContextHandlerCollection"/>
+          </Item>
+          <Item>
+            <New id="DefaultHandler" class="org.mortbay.jetty.handler.DefaultHandler"/>
+          </Item>
+          <Item>
+            <New id="RequestLog" class="org.mortbay.jetty.handler.RequestLogHandler"/>
+          </Item>
+        </Array>
+      </Set>
+    </New>
+  </Set>
+
+  <!-- Set the handler for our web dav servlet -->
+  <Set name="handler">
+    <New id="WebDAVServletContext" class="org.mortbay.jetty.servlet.Context">
+      <Set name="contextPath">/</Set>
+      <Set name="resourceBase">
+        <SystemProperty name="jetty.docroot" default="."/>
+      </Set>
+      <Call name="addServlet">
+        <Arg>com.thinkberg.moxo.servlet.MoxoS3WebdavServlet</Arg>
+        <Arg>/*</Arg>
+      </Call>
+    </New>
+  </Set>
+
+
+  <!-- =========================================================== -->
+  <!-- Configure Authentication Realms                             -->
+  <!-- Realms may be configured for the entire server here, or     -->
+  <!-- they can be configured for a specific web app in a context  -->
+  <!-- configuration (see $(jetty.home)/contexts/test.xml for an   -->
+  <!-- example).                                                   -->
+  <!-- =========================================================== -->
+  <Set name="UserRealms">
+    <Array type="org.mortbay.jetty.security.UserRealm">
+      <!--
+           <Item>
+             <New class="org.mortbay.jetty.security.HashUserRealm">
+               <Set name="name">Test Realm</Set>
+               <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
+             </New>
+           </Item>
+     -->
+    </Array>
+  </Set>
+
+  <!-- =========================================================== -->
+  <!-- Configure Request Log                                       -->
+  <!-- Request logs  may be configured for the entire server here, -->
+  <!-- or they can be configured for a specific web app in a       -->
+  <!-- contexts configuration (see $(jetty.home)/contexts/test.xml -->
+  <!-- for an example).                                            -->
+  <!-- =========================================================== -->
+  <Ref id="RequestLog">
+    <Set name="requestLog">
+      <New id="RequestLogImpl" class="org.mortbay.jetty.NCSARequestLog">
+        <Arg>
+          <SystemProperty name="jetty.logs" default="./logs"/>
+          /yyyy_mm_dd.request.log
+        </Arg>
+        <Set name="retainDays">90</Set>
+        <Set name="append">true</Set>
+        <Set name="extended">false</Set>
+        <Set name="LogTimeZone">GMT</Set>
+      </New>
+    </Set>
+  </Ref>
+
+  <!-- =========================================================== -->
+  <!-- extra options                                               -->
+  <!-- =========================================================== -->
+  <Set name="stopAtShutdown">true</Set>
+  <!-- ensure/prevent Server: header being sent to browsers        -->
+  <Set name="sendServerVersion">true</Set>
+
+</Configure>
blob - /dev/null
blob + 063bbb02000bd53c285e6e92b4a9669d148ed99d (mode 644)
--- /dev/null
+++ src/main/resources/moxo.template.properties
@@ -0,0 +1,38 @@
+#
+# Copyright 2007 Matthias L. Jugel.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#####################################
+# VFS provider application properties
+#
+# This file must be available on the 
+# classpath when the VFS is used
+####################################
+
+# AWS Access Key (required)
+accesskey=<your AWS access key here>
+
+# AWS Secret Key (required)
+secretkey=<your AWS secret key here>
+
+# Access Control List setting to apply to uploads, must be one of: PRIVATE, PUBLIC_READ, PUBLIC_READ_WRITE
+# The ACL setting defaults to PRIVATE if this setting is missing.
+acl=PRIVATE
+
+# Password used when encrypting/decrypting files. Only required when the --crypto setting is used.
+password=<some encryption password here>
+
+# the bucket that contains the file system
+bucket=<the filesystem bucket>
\ No newline at end of file
blob - /dev/null
blob + d30eb9df05dee7f0339cc282835d00c601283bd2 (mode 644)
--- /dev/null
+++ src/test/java/com/thinkberg/moxo/MoxoTest.java
@@ -0,0 +1,54 @@
+package com.thinkberg.moxo;
+
+import com.thinkberg.moxo.dav.DavLockManagerTest;
+import com.thinkberg.moxo.dav.DavResourceTest;
+import com.thinkberg.moxo.s3.S3WrapperTest;
+import com.thinkberg.moxo.vfs.S3FileNameTest;
+import com.thinkberg.moxo.vfs.S3FileProviderTest;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.jets3t.service.Jets3tProperties;
+
+/**
+ * Unit tests for the Moxo S3 Amazon Proxy server.
+ *
+ * @author Matthias L. Jugel
+ */
+public class MoxoTest extends TestCase {
+  /**
+   * The complete Moxo Test suite.
+   *
+   * @param name name of the test case
+   */
+  public MoxoTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Add all known tests to the suite.
+   *
+   * @return the suite of tests being tested
+   */
+  public static Test suite() {
+    TestSuite s = new TestSuite();
+
+    s.addTestSuite(DavResourceTest.class);
+    s.addTestSuite(DavLockManagerTest.class);
+
+    String propertiesFileName = System.getProperty("moxo.properties", "moxo.properties");
+    Jets3tProperties properties = Jets3tProperties.getInstance(propertiesFileName);
+
+    String bucketId = properties.getStringProperty("bucket", null);
+    if (null != bucketId) {
+      s.addTestSuite(S3WrapperTest.class);
+
+      s.addTestSuite(S3FileNameTest.class);
+      s.addTestSuite(S3FileProviderTest.class);
+    }
+
+    return s;
+  }
+
+
+}
blob - /dev/null
blob + 111a1dbd66bf028541af96c6cc04d9feef45c600 (mode 644)
--- /dev/null
+++ src/test/java/com/thinkberg/moxo/S3TestCase.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo;
+
+import junit.framework.TestCase;
+import org.jets3t.service.Jets3tProperties;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3TestCase extends TestCase {
+  protected final static String BUCKETID;
+  protected static final String ROOT;
+
+  static {
+    String propertiesFileName = System.getProperty("moxo.properties", "moxo.properties");
+    Jets3tProperties properties = Jets3tProperties.getInstance(propertiesFileName);
+
+    BUCKETID = properties.getStringProperty("bucket", null);
+    ROOT = "s3://" + BUCKETID;
+
+  }
+}
blob - /dev/null
blob + 2fa2574ff79738bae97230feeef528a7ff9781d9 (mode 644)
--- /dev/null
+++ src/test/java/com/thinkberg/moxo/dav/DavLockManagerTest.java
@@ -0,0 +1,48 @@
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.dav.lock.Lock;
+import com.thinkberg.moxo.dav.lock.LockConflictException;
+import com.thinkberg.moxo.dav.lock.LockManager;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class DavLockManagerTest extends DavTestCase {
+  private final String OWNER_STR = "testowner";
+
+  public DavLockManagerTest() {
+    super();
+  }
+
+  public void testAcquireSharedFileLock() {
+    Lock sharedLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+    try {
+      LockManager.getInstance().acquireLock(sharedLock);
+    } catch (Exception e) {
+      assertNull(e.getMessage(), e);
+    }
+  }
+
+  public void testAcquireDoubleSharedFileLock() {
+    Lock sharedLock1 = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+    Lock sharedLock2 = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR + "1", 0, 3600);
+    try {
+      LockManager.getInstance().acquireLock(sharedLock1);
+      LockManager.getInstance().acquireLock(sharedLock2);
+    } catch (Exception e) {
+      assertNull(e.getMessage(), e);
+    }
+  }
+
+  public void testAcquireExclusiveLock() {
+    Lock sharedLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+    Lock exclusiveLock = new Lock(aFile, Lock.WRITE, Lock.EXCLUSIVE, OWNER_STR, 0, 3600);
+    try {
+      LockManager.getInstance().acquireLock(sharedLock);
+      LockManager.getInstance().acquireLock(exclusiveLock);
+      assertTrue("acquireLock() should fail", false);
+    } catch (Exception e) {
+      assertEquals(LockConflictException.class, e.getClass());
+    }
+  }
+}
blob - /dev/null
blob + 66f3e2ed8c1495e69dd6a684bf30ad748976367b (mode 644)
--- /dev/null
+++ src/test/java/com/thinkberg/moxo/dav/DavResourceTest.java
@@ -0,0 +1,54 @@
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.dav.data.DavCollection;
+import com.thinkberg.moxo.dav.data.DavResource;
+import org.apache.commons.vfs.FileSystemException;
+import org.dom4j.Element;
+
+import java.io.IOException;
+
+/**
+ * Test case for the DAV resource wrapper. Checks that resources are serialized
+ * correctly.
+ *
+ * @author Matthias L. Jugel
+ */
+public class DavResourceTest extends DavTestCase {
+  public void testFileCreationDateIsNull() throws FileSystemException {
+    Element root = serializeDavResource(aFile, DavResource.PROP_CREATION_DATE);
+    assertNull(selectExistingProperty(root, DavResource.PROP_CREATION_DATE));
+  }
+
+  public void testFileCreationDateIsMissing() throws IOException {
+    Element root = serializeDavResource(aFile, DavResource.PROP_CREATION_DATE);
+    assertEquals(DavResource.PROP_CREATION_DATE,
+                 selectMissingPropertyName(root, DavResource.PROP_CREATION_DATE));
+  }
+
+  public void testFileDisplayNameWithValue() throws FileSystemException {
+    testPropertyValue(aFile, DavResource.PROP_DISPLAY_NAME, aFile.getName().getBaseName());
+  }
+
+  public void testFileDisplayNameWithoutValue() throws FileSystemException {
+    testPropertyNoValue(aFile, DavResource.PROP_DISPLAY_NAME);
+  }
+
+  public void testFileResourceTypeNotMissing() throws FileSystemException {
+    Element root = serializeDavResource(aFile, DavResource.PROP_RESOURCETYPE);
+    assertNull(selectMissingProperty(root, DavResource.PROP_RESOURCETYPE));
+  }
+
+  public void testDirectoryResourceTypeNotMissing() throws FileSystemException {
+    Element root = serializeDavResource(aDirectory, DavResource.PROP_RESOURCETYPE);
+    assertNull(selectMissingProperty(root, DavResource.PROP_RESOURCETYPE));
+  }
+
+  public void testFileResourceType() throws FileSystemException {
+    testPropertyNoValue(aFile, DavResource.PROP_RESOURCETYPE);
+  }
+
+  public void testDirectoryResourceType() throws FileSystemException {
+    Element root = serializeDavResource(aDirectory, DavResource.PROP_RESOURCETYPE);
+    assertNotNull(selectExistingProperty(root, DavResource.PROP_RESOURCETYPE).selectSingleNode(DavCollection.COLLECTION));
+  }
+}
blob - /dev/null
blob + 0a640610fa6e6e3de857f625d04981a4eaa3f1d3 (mode 644)
--- /dev/null
+++ src/test/java/com/thinkberg/moxo/dav/DavTestCase.java
@@ -0,0 +1,83 @@
+package com.thinkberg.moxo.dav;
+
+import com.thinkberg.moxo.dav.data.DavResource;
+import com.thinkberg.moxo.dav.data.DavResourceFactory;
+import junit.framework.TestCase;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileSystemManager;
+import org.apache.commons.vfs.VFS;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Node;
+
+import java.util.Arrays;
+
+/**
+ * Helper class for DAV tests.
+ *
+ * @author Matthias L. Jugel
+ */
+public class DavTestCase extends TestCase {
+  private static final String PROP_EXISTS = "propstat[status='HTTP/1.1 200 OK']/prop/";
+  private static final String PROP_MISSING = "propstat[status='HTTP/1.1 404 Not Found']/prop/";
+  private static final String EMPTY = "";
+
+  FileObject aFile;
+  FileObject aDirectory;
+
+
+  protected void setUp() throws Exception {
+    super.setUp();
+    FileSystemManager fsm = VFS.getManager();
+    FileObject fsRoot = fsm.createVirtualFileSystem(fsm.resolveFile("ram:/"));
+    aFile = fsRoot.resolveFile("/file.txt");
+    aFile.delete();
+    aFile.createFile();
+    aDirectory = fsRoot.resolveFile("/folder");
+    aDirectory.delete();
+    aDirectory.createFolder();
+  }
+
+  @SuppressWarnings({"SameParameterValue"})
+  protected void testPropertyValue(FileObject object, String propertyName, String propertyValue) throws FileSystemException {
+    Element root = serializeDavResource(object, propertyName);
+    assertEquals(propertyValue, selectExistingPropertyValue(root, propertyName));
+  }
+
+  protected void testPropertyNoValue(FileObject object, String propertyName) throws FileSystemException {
+    Element root = serializeDavResource(object, propertyName, true);
+    assertEquals(EMPTY, selectExistingPropertyValue(root, propertyName));
+  }
+
+  Node selectExistingProperty(Element root, String propertyName) {
+    return root.selectSingleNode(PROP_EXISTS + propertyName);
+  }
+
+  Node selectMissingProperty(Element root, String propertyName) {
+    return root.selectSingleNode(PROP_MISSING + propertyName);
+  }
+
+  @SuppressWarnings({"SameParameterValue"})
+  String selectMissingPropertyName(Element root, String propertyName) {
+    return selectMissingProperty(root, propertyName).getName();
+  }
+
+  private String selectExistingPropertyValue(Element root, String propertyName) {
+    return selectExistingProperty(root, propertyName).getText();
+  }
+
+  Element serializeDavResource(FileObject object, String propertyName) throws FileSystemException {
+    return serializeDavResource(object, propertyName, false);
+  }
+
+  private Element serializeDavResource(FileObject object, String propertyName, boolean ignoreValues) throws FileSystemException {
+    Element root = DocumentHelper.createElement("root");
+    DavResourceFactory factory = DavResourceFactory.getInstance();
+    DavResource davResource = factory.getDavResource(object);
+    davResource.setIgnoreValues(ignoreValues);
+    davResource.serializeToXml(root, Arrays.asList(propertyName));
+    return root;
+  }
+
+}
blob - /dev/null
blob + 4daf32ab4820a8f9afbb63a5bb033383d7855fbe (mode 644)
--- /dev/null
+++ src/test/java/com/thinkberg/moxo/s3/S3WrapperTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.s3;
+
+import junit.framework.TestCase;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+import org.jets3t.service.Jets3tProperties;
+
+import java.io.InputStream;
+
+import com.thinkberg.moxo.S3TestCase;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3WrapperTest extends S3TestCase {
+  public void testCreateConnection() throws FileSystemException {
+    assertNotNull(S3Connector.getInstance());
+  }
+
+  public void testGetS3BucketRoot() throws FileSystemException {
+    assertNotNull(S3Connector.getInstance().getRoot(BUCKETID));
+  }
+
+  public void testGetS3BucketRootMissing() throws FileSystemException {
+    try {
+      assertNull(S3Connector.getInstance().getRoot(BUCKETID+".NOTEXISTING"));
+    } catch (FileSystemException e) {
+      assertNotNull(e);
+    }
+  }
+
+  public void testGetRootFolder() throws FileSystemException {
+    S3VfsRoot root = S3Connector.getInstance().getRoot(BUCKETID);
+    S3VfsObject slashObject = root.getObject("/");
+    assertEquals("/", slashObject.getName());
+  }
+
+  public void testRootFolderTypeIsCorrect() throws FileSystemException {
+    S3VfsRoot root = S3Connector.getInstance().getRoot(BUCKETID);
+    S3VfsObject slashObject = root.getObject("/");
+    assertEquals(FileType.FOLDER, slashObject.getType());
+  }
+
+  public void testRootFolderListing() throws FileSystemException {
+    S3VfsRoot root = S3Connector.getInstance().getRoot(BUCKETID);
+    S3VfsObject slashObject = root.getObject("/");
+    assertEquals(1, slashObject.getChildren().length);
+  }
+
+  public void testFolderTypeIsCorrect() throws FileSystemException {
+    S3VfsRoot root = S3Connector.getInstance().getRoot(BUCKETID);
+    S3VfsObject folderObject = root.getObject("/Sites");
+    assertEquals(FileType.FOLDER, folderObject.getType());
+  }
+
+  public void testFileTypeIsCorrect() throws FileSystemException {
+    S3VfsRoot root = S3Connector.getInstance().getRoot(BUCKETID);
+    S3VfsObject fileObject = root.getObject("/Sites/Sites/index.html");
+    assertEquals(FileType.FILE, fileObject.getType());
+  }
+
+  public void testFolderSize() throws FileSystemException {
+    S3VfsRoot root = S3Connector.getInstance().getRoot(BUCKETID);
+    S3VfsObject folderObject = root.getObject("/Sites/Sites/images");
+    assertEquals(3, folderObject.getChildren().length);
+  }
+
+  public void testGetFile() throws FileSystemException {
+    S3VfsRoot root = S3Connector.getInstance().getRoot(BUCKETID);
+    S3VfsObject fileObject = root.getObject("/Sites/Sites/images/macosxlogo.gif");
+    assertNotNull(fileObject.getInputStream());
+  }
+
+  public void testGetMissingFileIsImaginary() throws FileSystemException {
+    S3VfsRoot root = S3Connector.getInstance().getRoot(BUCKETID);
+    S3VfsObject fileObject = root.getObject("/notexisting.txt");
+    assertEquals(FileType.IMAGINARY, fileObject.getType());
+  }
+}
blob - /dev/null
blob + 91cc77a793bd5a99291d9625ec74622b90b9102d (mode 644)
--- /dev/null
+++ src/test/java/com/thinkberg/moxo/vfs/S3FileNameTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.vfs;
+
+import junit.framework.TestCase;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileName;
+import org.jets3t.service.Jets3tProperties;
+import com.thinkberg.moxo.S3TestCase;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3FileNameTest extends S3TestCase {
+  public void testGetBucketFromUri() throws FileSystemException {
+    String uri = ROOT + "/junk.txt";
+    FileName fileName = S3FileNameParser.getInstance().parseUri(null, null, uri);
+    assertEquals(BUCKETID, ((S3FileName)fileName).getRootFile());
+  }
+
+  public void testGetRootFolderFromUri() throws FileSystemException {
+    String path = "/myfolder";
+    String uri = ROOT + path;
+    FileName fileName = S3FileNameParser.getInstance().parseUri(null, null, uri);
+    assertEquals(path, fileName.getPath());
+  }
+}
blob - /dev/null
blob + 6fddf9948fa98da70f3c6f8d3e6268191d6ee83d (mode 644)
--- /dev/null
+++ src/test/java/com/thinkberg/moxo/vfs/S3FileProviderTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.thinkberg.moxo.vfs;
+
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileSystemOptions;
+import org.apache.commons.vfs.FileSystemManager;
+import org.apache.commons.vfs.VFS;
+import org.apache.commons.vfs.FileType;
+import org.apache.commons.vfs.impl.DefaultFileSystemManager;
+import org.jets3t.service.Jets3tProperties;
+import junit.framework.TestCase;
+import com.thinkberg.moxo.S3TestCase;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class S3FileProviderTest extends S3TestCase {
+  public void testDoCreateFileSystem() throws FileSystemException {
+    FileObject object = VFS.getManager().resolveFile(ROOT);
+    assertEquals(BUCKETID, ((S3FileName)object.getName()).getRootFile());
+  }
+
+  public void testRootDirectoryIsFolder() throws FileSystemException {
+    FileObject object = VFS.getManager().resolveFile(ROOT);
+    assertEquals(FileType.FOLDER, object.getType());
+  }
+
+  public void testGetDirectory() throws FileSystemException {
+    FileObject object = VFS.getManager().resolveFile(ROOT + "/Sites");
+    assertEquals(FileType.FOLDER, object.getType());
+  }
+
+  public void testGetDirectoryListing() throws FileSystemException {
+    FileObject object = VFS.getManager().resolveFile(ROOT + "/Sites/Sites/images");
+    FileObject[] files = object.findFiles(new DepthFileSelector(1));
+    assertEquals(4, files.length);
+  }
+
+  public void testMissingFile() throws FileSystemException {
+    FileObject object = VFS.getManager().resolveFile(ROOT + "/nonexisting.txt");
+    assertFalse(object.exists());
+  }
+}