commit 1bbed3956b1d921c893322d595688ad003e70940 from: leo date: Fri Feb 16 22:55:58 2007 UTC Moxo S3 DAV Proxy 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 @@ + + 4.0.0 + com.thinkberg.moxo + moxo + jar + 1.0-SNAPSHOT + Moxo S3 DAV Proxy + http://thinkberg.com + + + codehaus-m2-repository + Codehaus Maven 2.x Repository + http://repository.codehaus.org + + + + + junit + junit + 3.8.1 + test + + + jets3t + jets3t + 0.5.0 + + + commons-httpclient + commons-httpclient + 3.0.1 + + + commons-vfs + commons-vfs + 1.0 + + + org.mortbay.jetty + jetty + 6.1.1 + + + dom4j + dom4j + 1.6.1 + + + jaxen + jaxen + 1.1 + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + com.thinkberg.moxo.MoxoJettyRunner + true + + + + + + + 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 urlArray = new ArrayList(); + InputStream manifestIn = null; + InputStream jarIn = null; + try { + manifestIn = location.openStream(); + JarInputStream launcherJarIs = new JarInputStream(manifestIn); + StringBuffer classPath = new StringBuffer(location.getFile()); + List classpathList = new ArrayList(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 requestedProperties = new ArrayList(); + 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 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 requestedProperties) { + Element propStatEl = root.addElement(TAG_PROPSTAT); + Element propEl = propStatEl.addElement(TAG_PROP); + + Set missingProperties = new HashSet(); + 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 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 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 locks) { + super(locks); + } + + public LockConditionFailedException(List 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 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 locks; + + public LockException(List locks) { + super(); + this.locks = locks; + } + + public List 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> lockMap; + + /** + * The lock manager is a singleton and cannot be instantiated directly. + */ + private LockManager() { + lockMap = new HashMap>(); + } + + /** + * 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 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 discoverLock(FileObject object) throws FileSystemException { + FileObject parent = object; + while (parent != null) { + List 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 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 locks = lockMap.get(object); + if (null == locks) { + locks = new ArrayList(); + 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 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 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 handlers = new HashMap(); + + 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 @@ + + + + + + + + + + blob - /dev/null blob + ae39e881510681e8eb8b08d6c28510f4eec11b1d (mode 644) --- /dev/null +++ src/main/resources/jetty.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + 10 + 50 + 250 + + + + + + + + + + + + + + + + + + + + 30000 + 2 + 8443 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + com.thinkberg.moxo.servlet.MoxoS3WebdavServlet + /* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /yyyy_mm_dd.request.log + + 90 + true + false + GMT + + + + + + + + true + + true + + 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= + +# AWS Secret Key (required) +secretkey= + +# 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= + +# the bucket that contains the file system +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()); + } +}