You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2012/02/13 19:20:26 UTC

svn commit: r1243636 - in /commons/proper/vfs/trunk: ./ core/ core/src/test/java/org/apache/commons/vfs2/provider/test/ core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/ core/src/test/java/org/apache/commons/vfs2/provider/zip/test/ core/...

Author: ggregory
Date: Mon Feb 13 18:20:26 2012
New Revision: 1243636

URL: http://svn.apache.org/viewvc?rev=1243636&view=rev
Log:
[VFS-392] Build tests WebDAV file system with an embedded WebDAV server (Apache Jackrabbit).

Added:
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JackrabbitMain.java
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JcrUtils.java
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/zip/test/BugReport.java
Modified:
    commons/proper/vfs/trunk/core/   (props changed)
    commons/proper/vfs/trunk/core/pom.xml
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/test/JunctionProviderConfig.java
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/WebdavProviderTestCase.java
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/AbstractProviderTestConfig.java
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ContentTests.java
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderReadTests.java
    commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderTestConfig.java
    commons/proper/vfs/trunk/pom.xml

Propchange: commons/proper/vfs/trunk/core/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Mon Feb 13 18:20:26 2012
@@ -3,3 +3,4 @@ target
 .*
 maven-eclipse.xml
 bin
+jackrabbit

Modified: commons/proper/vfs/trunk/core/pom.xml
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/pom.xml?rev=1243636&r1=1243635&r2=1243636&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/pom.xml (original)
+++ commons/proper/vfs/trunk/core/pom.xml Mon Feb 13 18:20:26 2012
@@ -119,6 +119,16 @@
       <artifactId>httpcore-nio</artifactId>
       <scope>test</scope>
     </dependency>
+    <!-- Test WebDAV with Apache Jackrabbit-->
+    <dependency> 
+      <groupId>javax.jcr</groupId> 
+      <artifactId>jcr</artifactId> 
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-standalone</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <properties>
@@ -221,13 +231,13 @@
         <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
-          <version>1.5.6</version>
+          <version>1.5.5</version>
           <scope>test</scope>
         </dependency>
         <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-log4j12</artifactId>
-          <version>1.5.6</version>
+          <version>1.5.5</version>
           <scope>test</scope>
         </dependency>
       </dependencies>

Modified: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/test/JunctionProviderConfig.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/test/JunctionProviderConfig.java?rev=1243636&r1=1243635&r2=1243636&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/test/JunctionProviderConfig.java (original)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/test/JunctionProviderConfig.java Mon Feb 13 18:20:26 2012
@@ -73,4 +73,9 @@ public class JunctionProviderConfig
 
         return newFs.resolveFile(junctionPoint);
     }
+
+    public boolean isFileSystemRootAccessible()
+    {
+        return true;
+    }
 }

Added: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JackrabbitMain.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JackrabbitMain.java?rev=1243636&view=auto
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JackrabbitMain.java (added)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JackrabbitMain.java Mon Feb 13 18:20:26 2012
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.vfs2.provider.webdav.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.servlet.jackrabbit.JackrabbitRepositoryServlet;
+import org.apache.jackrabbit.standalone.Main;
+import org.apache.log4j.FileAppender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.mortbay.jetty.Connector;
+import org.mortbay.jetty.NCSARequestLog;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.bio.SocketConnector;
+import org.mortbay.jetty.handler.RequestLogHandler;
+import org.mortbay.jetty.servlet.ServletHolder;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+/**
+ * Manages a Jackrabbit server instance.
+ * 
+ * Copied and minimally changed from Jackrabbit's Main class in 1.5.2 to add a shutdown method.
+ * 
+ * @since 2.1
+ */
+class JackrabbitMain
+{
+
+    /**
+     * @param args
+     */
+    public static void main(String[] args) throws Exception
+    {
+        new JackrabbitMain(args).run();
+    }
+
+    private final Options options = new Options();
+
+    private final CommandLine command;
+
+    private final RequestLogHandler accessLog = new RequestLogHandler();
+
+    private final WebAppContext webapp = new WebAppContext();
+
+    private final Connector connector = new SocketConnector();
+
+    private final Server server = new Server();
+
+    public JackrabbitMain(String[] args) throws ParseException
+    {
+        options.addOption("?", "help", false, "print this message");
+        options.addOption("n", "notice", false, "print copyright notices");
+        options.addOption("l", "license", false, "print license information");
+
+        options.addOption("q", "quiet", false, "disable console output");
+        options.addOption("d", "debug", false, "enable debug logging");
+
+        options.addOption("h", "host", true, "IP address of the HTTP server");
+        options.addOption("p", "port", true, "TCP port of the HTTP server (8080)");
+        options.addOption("f", "file", true, "location of this jar file");
+        options.addOption("r", "repo", true, "repository directory (jackrabbit)");
+        options.addOption("c", "conf", true, "repository configuration file");
+
+        command = new GnuParser().parse(options, args);
+    }
+
+    private void copyToOutput(String resource) throws IOException
+    {
+        InputStream stream = JackrabbitMain.class.getResourceAsStream(resource);
+        try
+        {
+            IOUtils.copy(stream, System.out);
+        } finally
+        {
+            stream.close();
+        }
+    }
+
+    private void message(String message)
+    {
+        if (!command.hasOption("quiet"))
+        {
+            System.out.println(message);
+        }
+    }
+
+    private void prepareAccessLog(File log)
+    {
+        NCSARequestLog ncsa = new NCSARequestLog(new File(log, "access.log.yyyy_mm_dd").getPath());
+        ncsa.setFilenameDateFormat("yyyy-MM-dd");
+        accessLog.setRequestLog(ncsa);
+    }
+
+    private void prepareConnector()
+    {
+        String port = command.getOptionValue("port", "8080");
+        connector.setPort(Integer.parseInt(port));
+        String host = command.getOptionValue("host");
+        if (host != null)
+        {
+            connector.setHost(host);
+        }
+    }
+
+    private void prepareServerLog(File log) throws IOException
+    {
+        Layout layout = new PatternLayout("%d{dd.MM.yyyy HH:mm:ss} *%-5p* %c{1}: %m%n");
+
+        Logger jackrabbitLog = Logger.getRootLogger();
+        jackrabbitLog.addAppender(new FileAppender(layout, new File(log, "jackrabbit.log").getPath()));
+
+        Logger jettyLog = Logger.getLogger("org.mortbay.log");
+        jettyLog.addAppender(new FileAppender(layout, new File(log, "jetty.log").getPath()));
+        jettyLog.setAdditivity(false);
+
+        // if (command.hasOption("debug"))
+        // {
+        // jackrabbitLog.setLevel(Level.DEBUG);
+        // jettyLog.setLevel(Level.DEBUG);
+        // } else
+        // {
+        // jackrabbitLog.setLevel(Level.INFO);
+        // jettyLog.setLevel(Level.INFO);
+        // }
+        //
+        System.setProperty("derby.stream.error.file", new File(log, "derby.log").getPath());
+    }
+
+    private void prepareShutdown()
+    {
+        Runtime.getRuntime().addShutdownHook(new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    shutdown();
+                } catch (Exception e)
+                {
+                    e.printStackTrace();
+                }
+            }
+
+        });
+    }
+
+    private void prepareWebapp(File file, File repository, File tmp)
+    {
+        webapp.setContextPath("/");
+        webapp.setWar(file.getPath());
+        webapp.setExtractWAR(false);
+        webapp.setTempDirectory(tmp);
+
+        ServletHolder servlet = new ServletHolder(JackrabbitRepositoryServlet.class);
+        servlet.setInitOrder(1);
+        servlet.setInitParameter("repository.home", repository.getPath());
+        String conf = command.getOptionValue("conf");
+        if (conf != null)
+        {
+            servlet.setInitParameter("repository.config", conf);
+        }
+        webapp.addServlet(servlet, "/repository.properties");
+    }
+
+    public void run() throws Exception
+    {
+        String defaultFile = "jackrabbit-standalone.jar";
+        URL location = Main.class.getProtectionDomain().getCodeSource().getLocation();
+        if (location != null && "file".equals(location.getProtocol()))
+        {
+            File file = new File(location.getPath());
+            if (file.isFile())
+            {
+                defaultFile = location.getPath();
+            }
+        }
+        File file = new File(command.getOptionValue("file", defaultFile));
+
+        if (command.hasOption("help"))
+        {
+            HelpFormatter formatter = new HelpFormatter();
+            formatter.printHelp("java -jar " + file.getName(), options, true);
+        } else if (command.hasOption("notice"))
+        {
+            copyToOutput("/META-INF/NOTICE.txt");
+        } else if (command.hasOption("license"))
+        {
+            copyToOutput("/META-INF/LICENSE.txt");
+        } else
+        {
+            message("Welcome to Apache Jackrabbit!");
+            message("-------------------------------");
+
+            File repository = new File(command.getOptionValue("repo", "jackrabbit"));
+            message("Using repository directory " + repository);
+            repository.mkdirs();
+            File tmp = new File(repository, "tmp");
+            tmp.mkdir();
+            File log = new File(repository, "log");
+            log.mkdir();
+
+            message("Writing log messages to " + log);
+            prepareServerLog(log);
+
+            message("Starting the server...");
+            prepareWebapp(file, repository, tmp);
+            accessLog.setHandler(webapp);
+            prepareAccessLog(log);
+            server.setHandler(accessLog);
+            prepareConnector();
+            server.addConnector(connector);
+            prepareShutdown();
+
+            try
+            {
+                server.start();
+
+                String host = connector.getHost();
+                if (host == null)
+                {
+                    host = "localhost";
+                }
+                message("Apache Jackrabbit is now running at " + "http://" + host + ":" + connector.getPort() + "/");
+            } catch (Throwable t)
+            {
+                System.err.println("Unable to start the server: " + t.getMessage());
+                System.exit(1);
+            }
+        }
+    }
+
+    public void shutdown() throws Exception, InterruptedException
+    {
+        message("Shutting down the server...");
+        server.stop();
+        server.join();
+        message("-------------------------------");
+        message("Goodbye from Apache Jackrabbit!");
+    }
+
+}

Added: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JcrUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JcrUtils.java?rev=1243636&view=auto
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JcrUtils.java (added)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/JcrUtils.java Mon Feb 13 18:20:26 2012
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+// COPIED FROM JACKRABBIT 2.4.0
+
+package org.apache.commons.vfs2.provider.webdav.test;
+
+import java.io.InputStream;
+import java.util.Calendar;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+/**
+ * Collection of static utility methods for use with the JCR 1.0 API and Apache Jackrabbit 1.5.2.
+ * 
+ * Copied, adapted and pruned down from Jackrabbit 2.4.0.
+ * 
+ * @since 2.1
+ */
+class JcrUtils
+{
+
+    private static final String NodeType_NT_RESOURCE = "nt:resource";
+
+    private static final String Node_JCR_CONTENT = "jcr:content";
+
+    private static final String NodeType_NT_FOLDER = "nt:folder";
+
+    private static final String NodeType_NT_FILE = "nt:file";
+
+    private static final String Property_JCR_MIMETYPE = "jcr:mimeType";
+
+    private static final String Property_JCR_ENCODING = "jcr:encoding";
+
+    private static final String Property_JCR_LAST_MODIFIED = "jcr:lastModified";
+
+    private static final String Property_JCR_DATA = "jcr:data";
+
+    /**
+     * Returns the named child of the given node, creating it as an nt:folder node if it does not already exist. The caller is expected to
+     * take care of saving or discarding any transient changes.
+     * <p>
+     * Note that the type of the returned node is <em>not</em> guaranteed to match nt:folder in case the node already existed. The caller
+     * can use an explicit {@link Node#isNodeType(String)} check if needed, or simply use a data-first approach and not worry about the node
+     * type until a constraint violation is encountered.
+     * 
+     * @param parent
+     *            parent node
+     * @param name
+     *            name of the child node
+     * @return the child node
+     * @throws RepositoryException
+     *             if the child node can not be accessed or created
+     */
+    public static Node getOrAddFolder(Node parent, String name) throws RepositoryException
+    {
+        return getOrAddNode(parent, name, NodeType_NT_FOLDER);
+    }
+
+    /**
+     * Returns the named child of the given node, creating the child if it does not already exist. If the child node gets added, then it is
+     * created with the given node type. The caller is expected to take care of saving or discarding any transient changes.
+     * 
+     * @see Node#getNode(String)
+     * @see Node#addNode(String, String)
+     * @see Node#isNodeType(String)
+     * @param parent
+     *            parent node
+     * @param name
+     *            name of the child node
+     * @param type
+     *            type of the child node, ignored if the child already exists
+     * @return the child node
+     * @throws RepositoryException
+     *             if the child node can not be accessed or created
+     */
+    public static Node getOrAddNode(Node parent, String name, String type) throws RepositoryException
+    {
+        if (parent.hasNode(name))
+        {
+            return parent.getNode(name);
+        } else
+        {
+            return parent.addNode(name, type);
+        }
+    }
+
+    /**
+     * Creates or updates the named child of the given node. If the child does not already exist, then it is created using the nt:file node
+     * type. This file child node is returned from this method.
+     * <p>
+     * If the file node does not already contain a jcr:content child, then one is created using the nt:resource node type. The following
+     * properties are set on the jcr:content node:
+     * <dl>
+     * <dt>jcr:mimeType</dt>
+     * <dd>media type</dd>
+     * <dt>jcr:encoding (optional)</dt>
+     * <dd>charset parameter of the media type, if any</dd>
+     * <dt>jcr:lastModified</dt>
+     * <dd>current time</dd>
+     * <dt>jcr:data</dt>
+     * <dd>binary content</dd>
+     * </dl>
+     * <p>
+     * Note that the types of the returned node or the jcr:content child are <em>not</em> guaranteed to match nt:file and nt:resource in
+     * case the nodes already existed. The caller can use an explicit {@link Node#isNodeType(String)} check if needed, or simply use a
+     * data-first approach and not worry about the node type until a constraint violation is encountered.
+     * <p>
+     * The given binary content stream is closed by this method.
+     * 
+     * @param parent
+     *            parent node
+     * @param name
+     *            name of the file
+     * @param mime
+     *            media type of the file
+     * @param data
+     *            binary content of the file
+     * @return the child node
+     * @throws RepositoryException
+     *             if the child node can not be created or updated
+     */
+    public static Node putFile(Node parent, String name, String mime, InputStream data) throws RepositoryException
+    {
+        return putFile(parent, name, mime, data, Calendar.getInstance());
+    }
+
+    /**
+     * Creates or updates the named child of the given node. If the child does not already exist, then it is created using the nt:file node
+     * type. This file child node is returned from this method.
+     * <p>
+     * If the file node does not already contain a jcr:content child, then one is created using the nt:resource node type. The following
+     * properties are set on the jcr:content node:
+     * <dl>
+     * <dt>jcr:mimeType</dt>
+     * <dd>media type</dd>
+     * <dt>jcr:encoding (optional)</dt>
+     * <dd>charset parameter of the media type, if any</dd>
+     * <dt>jcr:lastModified</dt>
+     * <dd>date of last modification</dd>
+     * <dt>jcr:data</dt>
+     * <dd>binary content</dd>
+     * </dl>
+     * <p>
+     * Note that the types of the returned node or the jcr:content child are <em>not</em> guaranteed to match nt:file and nt:resource in
+     * case the nodes already existed. The caller can use an explicit {@link Node#isNodeType(String)} check if needed, or simply use a
+     * data-first approach and not worry about the node type until a constraint violation is encountered.
+     * <p>
+     * The given binary content stream is closed by this method.
+     * 
+     * @param parent
+     *            parent node
+     * @param name
+     *            name of the file
+     * @param mime
+     *            media type of the file
+     * @param data
+     *            binary content of the file
+     * @param date
+     *            date of last modification
+     * @return the child node
+     * @throws RepositoryException
+     *             if the child node can not be created or updated
+     */
+    public static Node putFile(Node parent, String name, String mime, InputStream data, Calendar date)
+            throws RepositoryException
+    {
+        Value binary = parent.getSession().getValueFactory().createValue(data);
+        try
+        {
+            Node file = getOrAddNode(parent, name, NodeType_NT_FILE);
+            Node content = getOrAddNode(file, Node_JCR_CONTENT, NodeType_NT_RESOURCE);
+
+            content.setProperty(Property_JCR_MIMETYPE, mime);
+            String[] parameters = mime.split(";");
+            for (int i = 1; i < parameters.length; i++)
+            {
+                int equals = parameters[i].indexOf('=');
+                if (equals != -1)
+                {
+                    String parameter = parameters[i].substring(0, equals);
+                    if ("charset".equalsIgnoreCase(parameter.trim()))
+                    {
+                        content.setProperty(Property_JCR_ENCODING, parameters[i].substring(equals + 1).trim());
+                    }
+                }
+            }
+
+            content.setProperty(Property_JCR_LAST_MODIFIED, date);
+            content.setProperty(Property_JCR_DATA, binary);
+            return file;
+        } finally
+        {
+            // JCR 2.0 API:
+            // binary.dispose();
+        }
+    }
+
+    /**
+     * Private constructor to prevent instantiation of this class.
+     */
+    private JcrUtils()
+    {
+    }
+}

Modified: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/WebdavProviderTestCase.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/WebdavProviderTestCase.java?rev=1243636&r1=1243635&r2=1243636&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/WebdavProviderTestCase.java (original)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/webdav/test/WebdavProviderTestCase.java Mon Feb 13 18:20:26 2012
@@ -16,8 +16,24 @@
  */
 package org.apache.commons.vfs2.provider.webdav.test;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+
 import junit.framework.Test;
 
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSystemManager;
 import org.apache.commons.vfs2.FileSystemOptions;
@@ -27,53 +43,327 @@ import org.apache.commons.vfs2.provider.
 import org.apache.commons.vfs2.provider.webdav.WebdavFileSystemConfigBuilder;
 import org.apache.commons.vfs2.test.AbstractProviderTestConfig;
 import org.apache.commons.vfs2.test.ProviderTestSuite;
+import org.apache.commons.vfs2.util.FreeSocketPortUtil;
+import org.apache.jackrabbit.core.TransientRepository;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
 
 /**
  * Test cases for the WebDAV provider.
- *
+ * 
  * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
  */
-public class WebdavProviderTestCase
-    extends AbstractProviderTestConfig
+public class WebdavProviderTestCase extends AbstractProviderTestConfig
 {
+    private static final char[] PASSWORD = new char[0];
+
+    private static final String USER_ID = "admin";
+
+    private static int SocketPort;
+
     private static final String TEST_URI = "test.webdav.uri";
-    public static Test suite() throws Exception
+
+    private static JackrabbitMain JrMain;
+
+    /**
+     * Use %40 for @ in URLs
+     */
+    private static String ConnectionUri;
+
+    private static File RepoDirectory;
+
+    private static boolean DEBUG = Boolean.getBoolean("WebdavProviderTestCase.Debug");
+
+    public static File createTempDirectory() throws IOException
+    {
+        final File tempFile;
+
+        tempFile = File.createTempFile("WebdavProviderTestCase_", Long.toString(System.nanoTime()));
+
+        if (!tempFile.delete())
+        {
+            throw new IOException("Could not delete temp file: " + tempFile.getAbsolutePath());
+        }
+
+        if (!tempFile.mkdir())
+        {
+            throw new IOException("Could not create temp directory: " + tempFile.getAbsolutePath());
+        }
+
+        return tempFile;
+    }
+
+    private static void dump(File repoDirectory) throws Exception
+    {
+        TransientRepository repository = getTransientRepository(repoDirectory);
+        try
+        {
+            final Session session = getSession(repository);
+            message("Root node dump:");
+            dump(session.getRootNode());
+            session.logout();
+        } finally
+        {
+            repository.shutdown();
+        }
+    }
+
+    /** Recursively outputs the contents of the given node. */
+    private static void dump(Node node) throws RepositoryException
     {
-        if (System.getProperty(TEST_URI) != null)
+        // First output the node path
+        message(node.getPath());
+        // Skip the virtual (and large!) jcr:system subtree
+        if (node.getName().equals("jcr:system"))
+        {
+            return;
+        }
+
+        if (node.getName().equals("jcr:content"))
+        {
+            return;
+        }
+
+        // Then output the properties
+        PropertyIterator properties = node.getProperties();
+        while (properties.hasNext())
+        {
+            Property property = properties.nextProperty();
+            if (property.getDefinition().isMultiple())
+            {
+                // A multi-valued property, print all values
+                Value[] values = property.getValues();
+                for (Value value : values)
+                {
+                    message(property.getPath() + " = " + value.getString());
+                }
+            } else
+            {
+                // A single-valued property
+                message(property.getPath() + " = " + property.getString());
+            }
+        }
+
+        // Finally output all the child nodes recursively
+        NodeIterator nodes = node.getNodes();
+        while (nodes.hasNext())
         {
-            ProviderTestSuite suite = new WebdavProviderTestSuite(new WebdavProviderTestCase());
-            suite.addTests(WebdavVersioningTests.class);
-            return suite;
+            dump(nodes.nextNode());
         }
-        else
+    }
+
+    private static Session getSession(TransientRepository repository) throws RepositoryException
+    {
+        return repository.login(new SimpleCredentials(USER_ID, PASSWORD));
+    }
+
+    private static String getSystemTestUriOverride()
+    {
+        return System.getProperty(TEST_URI);
+    }
+
+    private static TransientRepository getTransientRepository(File repoDirectory) throws IOException
+    {
+        // Jackrabbit 1.6:
+        // TransientRepository repository = new TransientRepository(repoDirectory);
+        // Jackrabbit 1.5.2:
+        return new TransientRepository(new File(repoDirectory, "repository.xml").toString(), repoDirectory.toString());
+    }
+
+    private static void importFiles(File repoDirectory, File sourceDir) throws Exception
+    {
+        TransientRepository repository = getTransientRepository(repoDirectory);
+        try
         {
-            return notConfigured(WebdavProviderTestCase.class);
+            final Session session = getSession(repository);
+            importFiles(session.getRootNode(), sourceDir);
+            session.save();
+            session.logout();
+        } finally
+        {
+            repository.shutdown();
+        }
+    }
+
+    private static void importFiles(final Node parent, final File sourceDir) throws RepositoryException, IOException
+    {
+        final File[] files = sourceDir.listFiles();
+        for (File file : files)
+        {
+            if (file.isFile())
+            {
+                final InputStream data = new FileInputStream(file);
+                try
+                {
+                    message("Importing file " + file);
+                    JcrUtils.putFile(parent, file.getName(), "application/octet-stream", data);
+                } finally
+                {
+                    data.close();
+                }
+            } else if (file.isDirectory())
+            {
+                message("Importing folder " + file);
+                final Node folder = JcrUtils.getOrAddFolder(parent, file.getName());
+                importFiles(folder, file);
+            }
+        }
+    }
+
+    private static void message(IOException e)
+    {
+        if (DEBUG)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    private static void message(String string)
+    {
+        if (DEBUG)
+        {
+            System.out.println(string);
         }
     }
 
     /**
-     * Prepares the file system manager.
+     * Creates and starts an embedded Apache WebDAV Server (Jackrabbit).
+     * 
+     * @throws Exception
      */
-    @Override
-    public void prepare(final DefaultFileSystemManager manager)
-        throws Exception
+    private static void setUpClass() throws Exception
     {
-        manager.addProvider("webdav", new WebdavFileProvider());
-        manager.addProvider("tmp", new TemporaryFileProvider());
+        // Create temp dir for repo
+        RepoDirectory = createTempDirectory();
+        message("Created temp directory " + RepoDirectory);
+        // Populate repo
+        importFiles(RepoDirectory, new File(getTestDirectory()));
+        dump(RepoDirectory);
+        // Start server with temp repo
+        startJackrabbit(RepoDirectory);
+        message("Returned from org.apache.jackrabbit.standalone.Main " + SocketPort);
+    }
+
+    /**
+     * Starts an embedded Apache Jackrabbit server.
+     * 
+     * @param repoDirectory
+     * @throws Exception
+     */
+    private static void startJackrabbit(File repoDirectory) throws Exception
+    {
+        boolean quiet = false;
+        if (!DEBUG)
+        {
+            Logger.getLogger("org.apache.jackrabbit").setLevel(Level.WARN);
+            Logger.getLogger("org.apache.commons.httpclient").setLevel(Level.ERROR);
+            Logger.getLogger("org.apache.commons.vfs2").setLevel(Level.WARN);
+            Logger.getLogger("org.mortbay").setLevel(Level.WARN);
+            quiet = true;
+        }
+        JrMain = new JackrabbitMain(new String[]
+        { "--port", Integer.toString(SocketPort), "--repo", repoDirectory.toString(), quiet ? "--quiet" : "" });
+        JrMain.run();
+    }
+
+    public static Test suite() throws Exception
+    {
+        return new ProviderTestSuite(new WebdavProviderTestCase())
+        {
+            @Override
+            protected void setUp() throws Exception
+            {
+                if (getSystemTestUriOverride() == null)
+                {
+                    setUpClass();
+                }
+                super.setUp();
+            }
+
+            @Override
+            protected void tearDown() throws Exception
+            {
+                tearDownClass();
+                super.tearDown();
+            }
+        };
+    }
+
+    /**
+     * Tears down resources for this test case.
+     * <ol>
+     * <li>Shuts down the embedded Jackrabbit</li>
+     * <li>Extra clean up for org.apache.commons.httpclient.MultiThreadedHttpConnectionManager</li>
+     * <li>Remove temporary repository directory.</li>
+     * </ol>
+     * Stops the embedded Apache WebDAV Server.
+     * 
+     * @throws Exception
+     * @throws
+     */
+    private static void tearDownClass() throws Exception
+    {
+        // Main JR shutdown
+        JrMain.shutdown();
+        // WARN logged because one thread is still there, so clean up explicitly.
+        MultiThreadedHttpConnectionManager.shutdownAll();
+        // Remove repo dir
+        try
+        {
+            message("Deleting temp directory " + RepoDirectory);
+            FileUtils.deleteDirectory(RepoDirectory);
+        } catch (IOException e)
+        {
+            message(e);
+            if (RepoDirectory.exists())
+            {
+                message("Directory will be deleted on VM exit " + RepoDirectory);
+                RepoDirectory.deleteOnExit();
+            }
+        }
+    }
+
+    public WebdavProviderTestCase() throws IOException
+    {
+        SocketPort = FreeSocketPortUtil.findFreeLocalPort();
+        message("FreeSocketPortUtil.findFreeLocalPort() = " + SocketPort);
+        // Use %40 for @ in a URL
+        // Any user id and password will do with the default Jackrabbit set up.
+        ConnectionUri = String.format("webdav://%s@localhost:%d/repository/default", USER_ID, SocketPort);
     }
 
     /**
      * Returns the base folder for tests.
      */
     @Override
-    public FileObject getBaseTestFolder(final FileSystemManager manager)
-        throws Exception
+    public FileObject getBaseTestFolder(final FileSystemManager manager) throws Exception
     {
-        WebdavFileSystemConfigBuilder builder =
-            (WebdavFileSystemConfigBuilder)manager.getFileSystemConfigBuilder("webdav");
-        final String uri = System.getProperty(TEST_URI);
+        String uri = getSystemTestUriOverride();
+        if (uri == null)
+        {
+            uri = ConnectionUri;
+        }
+        WebdavFileSystemConfigBuilder builder = (WebdavFileSystemConfigBuilder) manager
+                .getFileSystemConfigBuilder("webdav");
         FileSystemOptions opts = new FileSystemOptions();
         builder.setRootURI(opts, uri);
         return manager.resolveFile(uri, opts);
     }
+
+    @Override
+    public boolean isFileSystemRootAccessible()
+    {
+        return false;
+    }
+
+    /**
+     * Prepares the file system manager.
+     */
+    @Override
+    public void prepare(final DefaultFileSystemManager manager) throws Exception
+    {
+        manager.addProvider("webdav", new WebdavFileProvider());
+        manager.addProvider("tmp", new TemporaryFileProvider());
+    }
+
 }

Added: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/zip/test/BugReport.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/zip/test/BugReport.java?rev=1243636&view=auto
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/zip/test/BugReport.java (added)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/zip/test/BugReport.java Mon Feb 13 18:20:26 2012
@@ -0,0 +1,76 @@
+package org.apache.commons.vfs2.provider.zip.test;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.commons.vfs2.FileContent;
+
+import org.apache.commons.vfs2.FileObject;
+
+import org.apache.commons.vfs2.FileSystemManager;
+
+import org.apache.commons.vfs2.VFS;
+
+import java.io.ByteArrayOutputStream;
+
+import java.io.InputStream;
+
+/**
+ * 
+ * Demonstrate a bug in commons vfs2:
+ * 
+ * Closing a zip file object doesn't release the underlying file lock.
+ * 
+ * 
+ * 
+ * Discovered on:
+ * 
+ * Windows 7 pro SP1
+ * 
+ * Java 1.6.0_20-b02 64-bit
+ * 
+ * VFS maven version: org.apache.commons:commons-vfs2:2.0
+ */
+
+public class BugReport
+{
+
+    public static void main(String args[]) throws Exception
+    {
+
+        String zipFile = "zip:C:\\some-file.zip!some-zip-entry.xml";
+
+        FileSystemManager vfs = VFS.getManager();
+
+        FileObject file = vfs.resolveFile(zipFile);
+
+        FileContent content = file.getContent();
+
+        InputStream is = content.getInputStream(); // actually locks the file
+
+        // Optionally consume the input stream.
+
+        // This implicitly call close() on the input stream when finish reading.
+
+        // See DefaultFileContent.FileContentInputStream class and MonitorInputStream.read()
+
+        // But either way, the zip file is still locked.
+
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+        IOUtils.copy(is, buffer);
+
+        // The following expected to unlock the file but they don't
+
+        IOUtils.closeQuietly(is);
+
+        content.close();
+
+        file.close(); // doesn't close the embedded ZipFileSystem which takes a file lock.
+
+        // Only this releases file lock.
+
+        vfs.closeFileSystem(file.getFileSystem());
+
+    }
+
+}

Modified: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/AbstractProviderTestConfig.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/AbstractProviderTestConfig.java?rev=1243636&r1=1243635&r2=1243636&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/AbstractProviderTestConfig.java (original)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/AbstractProviderTestConfig.java Mon Feb 13 18:20:26 2012
@@ -55,4 +55,10 @@ public abstract class AbstractProviderTe
 
         return cache;
     }
+    
+    public boolean isFileSystemRootAccessible()
+    {
+        return true;
+    }
+
 }

Modified: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ContentTests.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ContentTests.java?rev=1243636&r1=1243635&r2=1243636&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ContentTests.java (original)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ContentTests.java Mon Feb 13 18:20:26 2012
@@ -99,6 +99,10 @@ public class ContentTests
      */
     public void testRootURI() throws FileSystemException
     {
+        if (!this.getProviderConfig().isFileSystemRootAccessible())
+        {
+            return;
+        }
         FileSystem fs = getReadFolder().getFileSystem();
         String uri = fs.getRootURI();
         testRoot(getManager().resolveFile(uri));
@@ -108,7 +112,11 @@ public class ContentTests
      * Tests root of file system exists.
      */
     public void testRootAPI() throws FileSystemException
-    {
+    {        
+        if (!this.getProviderConfig().isFileSystemRootAccessible())
+        {
+            return;
+        }
         FileSystem fs = getReadFolder().getFileSystem();
         testRoot(fs.getRoot());
     }

Modified: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderReadTests.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderReadTests.java?rev=1243636&r1=1243635&r2=1243636&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderReadTests.java (original)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderReadTests.java Mon Feb 13 18:20:26 2012
@@ -30,12 +30,11 @@ import org.apache.commons.vfs2.FileType;
 
 /**
  * Read-only test cases for file providers.
- *
+ * 
  * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
  * @todo Test getLastModified(), getAttribute()
  */
-public class ProviderReadTests
-    extends AbstractProviderTestCase
+public class ProviderReadTests extends AbstractProviderTestCase
 {
     /**
      * Returns the capabilities required by the tests of this test case.
@@ -44,16 +43,11 @@ public class ProviderReadTests
     protected Capability[] getRequiredCaps()
     {
         return new Capability[]
-        {
-            Capability.GET_TYPE,
-            Capability.LIST_CHILDREN,
-            Capability.READ_CONTENT
-        };
+        { Capability.GET_TYPE, Capability.LIST_CHILDREN, Capability.READ_CONTENT };
     }
 
     /**
-     * Walks the base folder structure, asserting it contains exactly the
-     * expected files and folders.
+     * Walks the base folder structure, asserting it contains exactly the expected files and folders.
      */
     public void testStructure() throws Exception
     {
@@ -62,12 +56,9 @@ public class ProviderReadTests
     }
 
     /**
-     * Walks a folder structure, asserting it contains exactly the
-     * expected files and folders.
+     * Walks a folder structure, asserting it contains exactly the expected files and folders.
      */
-    protected void assertSameStructure(final FileObject folder,
-                                       final FileInfo expected)
-        throws Exception
+    protected void assertSameStructure(final FileObject folder, final FileInfo expected) throws Exception
     {
         // Setup the structure
         final List<FileInfo> queueExpected = new ArrayList<FileInfo>();
@@ -97,7 +88,7 @@ public class ProviderReadTests
             int length = children.length;
             if (info.children.size() != children.length)
             {
-                for (int i=0; i < children.length; ++i)
+                for (int i = 0; i < children.length; ++i)
                 {
                     if (children[i].getName().getBaseName().startsWith("."))
                     {
@@ -155,6 +146,10 @@ public class ProviderReadTests
      */
     public void testRoot() throws FileSystemException
     {
+        if (!this.getProviderConfig().isFileSystemRootAccessible())
+        {
+            return;
+        }
         FileSystem fs = getReadFolder().getFileSystem();
         String uri = fs.getRootURI();
         final FileObject file = getManager().resolveFile(uri);
@@ -173,7 +168,8 @@ public class ProviderReadTests
         final FileObject[] actualFiles = getReadFolder().findFiles(selector);
         Arrays.sort(actualFiles);
         FileObject prevActualFile = actualFiles[0];
-        for (FileObject actualFile : actualFiles) {
+        for (FileObject actualFile : actualFiles)
+        {
             assertTrue(prevActualFile.toString().compareTo(actualFile.toString()) <= 0);
             prevActualFile = actualFile;
         }
@@ -208,8 +204,7 @@ public class ProviderReadTests
         {
             folder.getContent().getInputStream();
             fail();
-        }
-        catch (FileSystemException e)
+        } catch (FileSystemException e)
         {
             assertSameMessage("vfs.provider/read-not-file.error", folder, e);
         }
@@ -233,8 +228,7 @@ public class ProviderReadTests
             folder.exists();
             folder.getType();
             folder.getChildren();
-        }
-        finally
+        } finally
         {
             instr.close();
         }

Modified: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderTestConfig.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderTestConfig.java?rev=1243636&r1=1243635&r2=1243636&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderTestConfig.java (original)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/test/ProviderTestConfig.java Mon Feb 13 18:20:26 2012
@@ -23,11 +23,10 @@ import org.apache.commons.vfs2.impl.Defa
 
 /**
  * Test configuration for a file system.
- *
+ * 
  * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
  */
-public interface ProviderTestConfig
-{
+public interface ProviderTestConfig {
     /**
      * Returns a DefaultFileSystemManager instance (or subclass instance).
      */
@@ -39,8 +38,7 @@ public interface ProviderTestConfig
     void prepare(DefaultFileSystemManager manager) throws Exception;
 
     /**
-     * Returns the base folder for tests.  This folder must exist, and contain
-     * the following structure:
+     * Returns the base folder for tests. This folder must exist, and contain the following structure:
      * <ul>
      * <li>/read-tests
      * <li>/write-tests
@@ -52,4 +50,13 @@ public interface ProviderTestConfig
      * Returns the filesCache implementation used for tests.
      */
     FilesCache getFilesCache();
+
+    /**
+     * Whether or not the root of test file system is accessible.
+     * 
+     * For example, with the default Jackrabbit (WebDAV) server, the root is not accessible, but deeper paths are OK.
+     * 
+     * @return Whether or not the root of test file system is accessible.
+     */
+    boolean isFileSystemRootAccessible();
 }

Modified: commons/proper/vfs/trunk/pom.xml
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/pom.xml?rev=1243636&r1=1243635&r2=1243636&view=diff
==============================================================================
--- commons/proper/vfs/trunk/pom.xml (original)
+++ commons/proper/vfs/trunk/pom.xml Mon Feb 13 18:20:26 2012
@@ -17,9 +17,7 @@
    limitations under the License.
   -->
 
-<project
-  xmlns="http://maven.apache.org/POM/4.0.0"
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <parent>
     <groupId>org.apache.commons</groupId>
@@ -370,13 +368,13 @@
       <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
-        <version>1.6.3</version>
+        <version>1.5.5</version>
         <scope>test</scope>
       </dependency>
       <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-log4j12</artifactId>
-        <version>1.6.3</version>
+        <version>1.5.5</version>
         <scope>test</scope>
       </dependency>
       <!-- Test SFTP with Apache SHHd Server (MINA) -->
@@ -405,6 +403,25 @@
         <version>4.1.4</version>
         <scope>test</scope>
       </dependency>
+      <!-- Test WebDAV with Apache Jackrabbit -->
+      <dependency> 
+        <groupId>javax.jcr</groupId> 
+        <artifactId>jcr</artifactId> 
+        <version>1.0</version> 
+      </dependency>
+      <dependency>
+        <groupId>org.apache.jackrabbit</groupId>
+        <artifactId>jackrabbit-standalone</artifactId>
+        <version>1.5.2</version>
+        <scope>test</scope>
+        <exclusions>
+          <exclusion>
+            <!-- Exclude Derby due to a sealing violation -->
+            <groupId>org.apache.derby</groupId>
+            <artifactId>derby</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
@@ -458,8 +475,8 @@
     </site>
   </distributionManagement>
 
-  <!-- Releasing VFS as a multi-module build with binary artifacts is somewhat painful. This profile hooks into the commons-parent and the apache-pom 
-    to get the build done and then uses the assembly to package it up. -->
+  <!-- Releasing VFS as a multi-module build with binary artifacts is somewhat painful. This profile hooks into the commons-parent 
+    and the apache-pom to get the build done and then uses the assembly to package it up. -->
   <profiles>
     <profile>
       <id>include-sandbox</id>