[dna-commits] DNA SVN: r1126 - in trunk: dna-integration-tests/src/test/java/org/jboss/dna/test/integration and 9 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Thu Jul 23 21:15:02 EDT 2009


Author: bcarothers
Date: 2009-07-23 21:15:00 -0400 (Thu, 23 Jul 2009)
New Revision: 1126

Added:
   trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/FileSystemRepositoryTckTest.java
   trunk/dna-integration-tests/src/test/resources/tck/filesystem/
   trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml
   trunk/dna-integration-tests/src/test/resources/tck/filesystem/repositoryOverlay.properties
Modified:
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/DnaLexicon.java
   trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/AbstractRepositoryTckTest.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrValue.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java
   trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/DnaRepositoryStub.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/DnaTckTest.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
   trunk/dna-jcr/src/test/resources/security/tck_roles.properties
   trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemConnection.java
   trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemI18n.java
   trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemRequestProcessor.java
   trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemSource.java
   trunk/extensions/dna-connector-filesystem/src/main/resources/org/jboss/dna/connector/filesystem/FileSystemI18n.properties
   trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorCreateWorkspacesTest.java
   trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNoCreateWorkspaceTest.java
   trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNotWritableTest.java
   trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorReadableTest.java
Log:
DNA-476 Add integration test that uses the file system connector

Committed patch that implements a read-only TCK test based on a filesystem connector. This patch implements a few changes as described below:

- Minor JCR bug fixes around UUID properties for connectors that don't support UUID properties and binary handling in JcrValue and the document view exporter
- Minor tweaks to the TCK infrastructure code to support not importing test data into read-only repositories and having a test suite of just the read-only tests
- Less-minor tweaks to the filesystem connector to support a new way of specifying workspace names and locations as described in http://www.jboss.org/index.html?module=bb&op=viewtopic&t=158928. This caused a fair amount of code shuffling within the FS connector classes.

There's also a somewhat dubious change to the filesystem connector that causes the root folder to be returned as type dna:root instead of nt:folder. This is necessary if the root happens to be the root of a repository workspace, as it is in this test. This is not appropriate under some projection scenarios (e.g. /foo => /). This merits some more discussion and thought in the future.

It is also worth noting that our FS connector was modified to return a node with primary type nt:file and a child node of type dna:resource for each file instead of nt:file/nt:resource. We cannot support nt:resource from our FS connector because nt:resource extends mix:referenceable. Instead, we use a node type that is identical to nt:resource but without extending mix:referenceable.
[ Show ?\194?\187 ]
Brian Carothers - 23/Jul/09 09:12 PM Attached patch that implements a read-only TCK test based on a filesystem connector. This patch implements a few changes as described below: - Minor JCR bug fixes around UUID properties for connectors that don't support UUID properties and binary handling in JcrValue and the document view exporter - Minor tweaks to the TCK infrastructure code to support not importing test data into read-only repositories and having a test suite of just the read-only tests - Less-minor tweaks to the filesystem connector to support a new way of specifying workspace names and locations as described in http://www.jboss.org/index.html?module=bb&op=viewtopic&t=158928. This caused a fair amount of code shuffling within the FS connector classes. There's also a somewhat dubious change to the filesystem connector that causes the root folder to be returned as type dna:root instead of nt:folder. This is necessary if the root happens to be the root of a repository wor!
 kspace, as it is in this test. This is not appropriate under some projection scenarios (e.g. /foo => /). This merits some more discussion and thought in the future. It is also worth noting that our FS connector was modified to return a node with primary type nt:file and a child node of type dna:resource for each file instead of nt:file/nt:resource. We cannot support nt:resource from our FS connector because nt:resource extends mix:referenceable. Instead, we use a node type that is identical to nt:resource but without extending mix:referenceable.


Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/DnaLexicon.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/DnaLexicon.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/DnaLexicon.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -43,6 +43,8 @@
     public static final Name NAMESPACES = new BasicName(Namespace.URI, "namespaces");
     public static final Name PROJECTION_RULES = new BasicName(Namespace.URI, "projectionRules");
     public static final Name READ_ONLY = new BasicName(Namespace.URI, "readOnly");
+    public static final Name RESOURCE = new BasicName(Namespace.URI, "resource");
+    public static final Name ROOT = new BasicName(Namespace.URI, "root");
     public static final Name TIME_TO_EXPIRE = new BasicName(Namespace.URI, "timeToExpire");
     public static final Name NAMESPACE_URI = new BasicName(Namespace.URI, "uri");
 

Modified: trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/AbstractRepositoryTckTest.java
===================================================================
--- trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/AbstractRepositoryTckTest.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/AbstractRepositoryTckTest.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -53,8 +53,8 @@
     public static TestSuite readOnlyRepositorySuite( String name ) {
         TestSuite suite = new TestSuite("Tests for " + name + "(read-only)");
         suite.addTest(new ChangeRepositoryTestCase(name));
-        suite.addTest(JcrTckTest.suite());
-        suite.addTestSuite(DnaTckTest.class);
+        suite.addTest(JcrTckTest.readOnlySuite());
+        suite.addTest(DnaTckTest.readOnlySuite());
 
         return suite;
     }

Added: trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/FileSystemRepositoryTckTest.java
===================================================================
--- trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/FileSystemRepositoryTckTest.java	                        (rev 0)
+++ trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/FileSystemRepositoryTckTest.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -0,0 +1,33 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.test.integration;
+
+import junit.framework.Test;
+
+public class FileSystemRepositoryTckTest {
+
+    public static Test suite() {
+        return AbstractRepositoryTckTest.readOnlyRepositorySuite("filesystem");
+    }
+}


Property changes on: trunk/dna-integration-tests/src/test/java/org/jboss/dna/test/integration/FileSystemRepositoryTckTest.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Added: trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml
===================================================================
--- trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml	                        (rev 0)
+++ trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml	2009-07-24 01:15:00 UTC (rev 1126)
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ JBoss DNA (http://www.jboss.org/dna)
+  ~
+  ~ See the COPYRIGHT.txt file distributed with this work for information
+  ~ regarding copyright ownership.  Some portions may be licensed
+  ~ to Red Hat, Inc. under one or more contributor license agreements.
+  ~ See the AUTHORS.txt file in the distribution for a full listing of 
+  ~ individual contributors.
+  ~
+  ~ JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+  ~ is licensed to you under the terms of the GNU Lesser General Public License as
+  ~ published by the Free Software Foundation; either version 2.1 of
+  ~ the License, or (at your option) any later version.
+  ~
+  ~ JBoss DNA is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+  ~ or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
+  ~ for more details.
+  ~
+  ~ You should have received a copy of the GNU Lesser General Public License
+  ~ along with this distribution; if not, write to:
+  ~ Free Software Foundation, Inc.
+  ~ 51 Franklin Street, Fifth Floor
+  ~ Boston, MA  02110-1301  USA
+  -->
+<configuration xmlns:dna="http://www.jboss.org/dna/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0">
+    <!-- 
+    Define the sources for the content.  These sources are directly accessible using the DNA-specific Graph API.
+    In fact, this is how the DNA JCR implementation works.  You can think of these as being similar to
+    JDBC DataSource objects, except that they expose graph content via the Graph API instead of records via SQL or JDBC. 
+    -->
+    <dna:sources jcr:primaryType="nt:unstructured">
+        <!-- 
+        The 'JCR' repository is a file system source with a single, default workspace (though others could be created, too).
+        In a production release, the dna:rootNodeUuid would be specified here as well to ensure stability over restarts.  
+        Since we don't specify a rootNodeUuid, a random UUID will be generated each time that the corresponding test is run.
+        -->
+        <dna:source jcr:name="Store" dna:classname="org.jboss.dna.connector.filesystem.FileSystemSource"
+        	dna:workspaceRootPath="./src/test/resources/tck/filesystem"
+        	dna:defaultWorkspaceName="defaultWorkspace"
+        	dna:predefinedWorkspaceNames="otherWorkspace"
+        	dna:creatingWorkspacesAllowed="false"
+        	/>    
+        <!-- 
+        <dna:source jcr:name="Store" dna:classname="org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource" />
+         -->	
+    </dna:sources>
+    <!-- 
+    Define the mime type detectors. This is an optional section.  By default, each engine will use the 
+    MIME type detector that uses filename extensions.  So we wouldn't need to define the same detector again,
+    but this is how you'd define another extension.
+    -->
+    <dna:mimeTypeDetectors>
+        <dna:mimeTypeDetector jcr:name="Detector">
+            <dna:description>Standard extension-based MIME type detector</dna:description>        
+            <!-- 
+            Specify the implementation class (required), as a child element or attribute on parent element.
+            -->
+            <dna:classname>org.jboss.dna.graph.mimetype.ExtensionBasedMimeTypeDetector</dna:classname>
+            <!-- 
+            Specify the classpath (optional) as an ordered list of 'names', where each name is significant to 
+            the classpath factory.  For example, a name could be an OSGI identifier or a Maven coordinate,
+            depending upon the classpath factory being used.  If there is only one 'name' in the classpath,
+            it may be specified as an attribute on the 'mimeTypeDetector' element.  If there is more than one
+            'name', then they must be specified as child 'classpath' elements. Blank or empty values are ignored. 
+             -->
+            <dna:classpath></dna:classpath>
+        </dna:mimeTypeDetector>
+    </dna:mimeTypeDetectors>
+    <!-- 
+    Define the JCR repositories 
+    -->
+    <dna:repositories>
+        <!-- 
+        Define a JCR repository that accesses the 'JCR' source directly.
+        This of course is optional, since we could access the same content through 'JCR'.
+        -->
+        <dna:repository jcr:name="Test Repository Source">
+            <!-- Specify the source that should be used for the repository -->
+            <dna:source>Store</dna:source>
+            <!-- Define the options for the JCR repository, using camelcase version of JcrRepository.Option names
+-->
+            <dna:options jcr:primaryType="dna:options">
+                <jaasLoginConfigName jcr:primaryType="dna:option" dna:value="dna-jcr"/>
+                <projectNodeTypes jcr:primaryType="dna:option" dna:value="false"/>
+            </dna:options>
+            <!-- Define any namespaces for this repository, other than those already defined by JCR or DNA
+-->
+            <namespaces jcr:primaryType="dna:namespaces">
+                <dnatest jcr:primaryType="dna:namespace" dna:uri="http://jboss.org/dna/test/1.0"/>
+            </namespaces>
+        </dna:repository>
+    </dna:repositories>
+</configuration>


Property changes on: trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml
___________________________________________________________________
Name: svn:keywords
   + Id Revision

Added: trunk/dna-integration-tests/src/test/resources/tck/filesystem/repositoryOverlay.properties
===================================================================
--- trunk/dna-integration-tests/src/test/resources/tck/filesystem/repositoryOverlay.properties	                        (rev 0)
+++ trunk/dna-integration-tests/src/test/resources/tck/filesystem/repositoryOverlay.properties	2009-07-24 01:15:00 UTC (rev 1126)
@@ -0,0 +1,8 @@
+# Placeholder for any overlaid properties for this repo configuration
+
+javax.jcr.tck.dnaSkipImport=true
+
+
+javax.jcr.tck.nodetype=nt\:file
+javax.jcr.tck.nodetype2=nt\:file
+

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -35,7 +35,6 @@
     public static final Name NAMESPACE = new BasicName(Namespace.URI, "namespace");
     public static final Name NODE_TYPES = new BasicName(Namespace.URI, "nodeTypes");
     public static final Name REPOSITORIES = new BasicName(Namespace.URI, "repositories");
-    public static final Name ROOT = new BasicName(Namespace.URI, "root");
     public static final Name SYSTEM = new BasicName(Namespace.URI, "system");
     public static final Name URI = new BasicName(Namespace.URI, "uri");
 }

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -23,6 +23,7 @@
  */
 package org.jboss.dna.jcr;
 
+import java.io.IOException;
 import java.io.OutputStream;
 import java.text.CharacterIterator;
 import java.text.StringCharacterIterator;
@@ -39,6 +40,7 @@
 import net.jcip.annotations.NotThreadSafe;
 import org.jboss.dna.common.text.TextEncoder;
 import org.jboss.dna.common.text.XmlNameEncoder;
+import org.jboss.dna.common.util.Base64;
 import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.property.Name;
 import org.jboss.dna.graph.property.ValueFactories;
@@ -56,6 +58,8 @@
 @NotThreadSafe
 class JcrDocumentViewExporter extends AbstractJcrExporter {
 
+    private static final int ENCODE_BUFFER_SIZE = 2 << 15;
+
     private static final TextEncoder VALUE_ENCODER = new JcrDocumentViewExporter.JcrDocumentViewPropertyEncoder();
 
     JcrDocumentViewExporter( JcrSession session ) {
@@ -99,14 +103,19 @@
         while (properties.hasNext()) {
             Property prop = properties.nextProperty();
 
+            Name propName = ((AbstractJcrProperty)prop).name();
+
+            String localPropName = getPrefixedName(propName);
+
             if (skipBinary && PropertyType.BINARY == prop.getType()) {
+                atts.addAttribute(propName.getNamespaceUri(),
+                                  propName.getLocalName(),
+                                  localPropName,
+                                  PropertyType.nameFromValue(prop.getType()),
+                                  "");
                 continue;
             }
 
-            Name propName = ((AbstractJcrProperty)prop).name();
-
-            String localPropName = getPrefixedName(propName);
-
             Value value;
             if (prop instanceof JcrSingleValueProperty) {
                 value = prop.getValue();
@@ -115,11 +124,31 @@
                 // 6.4.2.5
                 value = prop.getValues()[0];
             }
+
+            String valueAsString;
+            if (PropertyType.BINARY == prop.getType()) {
+                StringBuffer buff = new StringBuffer(ENCODE_BUFFER_SIZE);
+                try {
+                    Base64.InputStream is = new Base64.InputStream(value.getStream(), Base64.ENCODE);
+
+                    byte[] bytes = new byte[ENCODE_BUFFER_SIZE];
+                    int len;
+                    while (-1 != (len = is.read(bytes, 0, ENCODE_BUFFER_SIZE))) {
+                        buff.append(new String(bytes, 0, len));
+                    }
+                } catch (IOException ioe) {
+                    throw new RepositoryException(ioe);
+                }
+                valueAsString = buff.toString();
+            } else {
+                valueAsString = VALUE_ENCODER.encode(value.getString());
+            }
+
             atts.addAttribute(propName.getNamespaceUri(),
                               propName.getLocalName(),
                               localPropName,
                               PropertyType.nameFromValue(prop.getType()),
-                              VALUE_ENCODER.encode(value.getString()));
+                              valueAsString);
         }
 
         Name name;
@@ -148,8 +177,10 @@
      * Indicates whether the current node is an XML text node as per section 6.4.2.3 of the JCR 1.0 specification. XML text nodes
      * are nodes that have the name &quot;jcr:xmltext&quot; and only one property (besides the mandatory
      * &quot;jcr:primaryType&quot;). The property must have a property name of &quot;jcr:xmlcharacters&quot;, a type of
-     * <code>String</code>, and does not have multiple values.<p/> In practice, this is handled in DNA by making XML text nodes
-     * have a type of &quot;dna:xmltext&quot;, which enforces these property characteristics.
+     * <code>String</code>, and does not have multiple values.
+     * <p/>
+     * In practice, this is handled in DNA by making XML text nodes have a type of &quot;dna:xmltext&quot;, which enforces these
+     * property characteristics.
      * 
      * @param node the node to test
      * @return whether this node is a special xml text node
@@ -224,6 +255,7 @@
      * underscore characters that might otherwise suggest an encoding, as defined in {@link XmlNameEncoder}.
      */
     protected static class JcrDocumentViewPropertyEncoder extends XmlNameEncoder {
+
         private static final Set<Character> MAPPED_CHARACTERS;
 
         static {

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrValue.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrValue.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrValue.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -56,6 +56,7 @@
     private final ValueFactories valueFactories;
     private final int type;
     private final Object value;
+    private InputStream asStream = null;
 
     JcrValue( ValueFactories valueFactories,
               SessionCache sessionCache,
@@ -187,10 +188,12 @@
             throw new IllegalStateException(JcrI18n.nonInputStreamConsumed.text());
         }
         try {
-            Binary binary = valueFactories.getBinaryFactory().create(value);
-            InputStream convertedValue = new SelfClosingInputStream(binary);
-            state = State.INPUT_STREAM_CONSUMED;
-            return convertedValue;
+            if (asStream == null) {
+                Binary binary = valueFactories.getBinaryFactory().create(value);
+                asStream = new SelfClosingInputStream(binary);
+                state = State.INPUT_STREAM_CONSUMED;
+            }
+            return asStream;
         } catch (RuntimeException error) {
             throw createValueFormatException(InputStream.class);
         }

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -2067,43 +2067,7 @@
                     uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid);
                 }
             }
-            if (uuidProperty == null) {
-                org.jboss.dna.graph.session.GraphSession.PropertyInfo<JcrPropertyPayload> uuidInfo = node.getProperty(JcrLexicon.UUID);
-                if (uuidInfo != null) {
-                    uuidProperty = uuidInfo.getProperty();
-                }
-                if (uuidProperty != null) {
-                    // Grab the first 'good' UUID value ...
-                    for (Object uuidValue : uuidProperty) {
-                        try {
-                            uuid = factories.getUuidFactory().create(uuidValue);
-                            break;
-                        } catch (ValueFormatException e) {
-                            // Ignore; just continue with the next property value
-                        }
-                    }
-                }
-                if (uuid == null) {
-                    // Look for the DNA UUID property ...
-                    uuidInfo = node.getProperty(DnaLexicon.UUID);
-                    if (uuidInfo != null) {
-                        uuidProperty = uuidInfo.getProperty();
-                    }
-                    if (uuidProperty != null) {
-                        // Grab the first 'good' UUID value ...
-                        for (Object uuidValue : uuidProperty) {
-                            try {
-                                uuid = factories.getUuidFactory().create(uuidValue);
-                                break;
-                            } catch (ValueFormatException e) {
-                                // Ignore; just continue with the next property value
-                            }
-                        }
-                    }
-                }
-            }
-            if (uuid == null) uuid = UUID.randomUUID();
-            if (uuidProperty == null) uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid);
+            if (uuid != null && uuidProperty == null) uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid);
 
             // Look for the primary type of the node ...
             Map<Name, Property> graphProperties = persistentNode.getPropertiesByName();

Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd	2009-07-24 01:15:00 UTC (rev 1126)
@@ -53,3 +53,8 @@
 + jcr:system (dna:system) = dna:system autocreated mandatory protected ignore
 + * (nt:base) = nt:unstructured multiple version
 
+[dna:resource] > nt:base
+- jcr:data (binary) primary mandatory
+- jcr:encoding (string) copy
+- jcr:lastModified (date) mandatory ignore
+- jcr:mimeType (string) copy mandatory

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/DnaRepositoryStub.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/DnaRepositoryStub.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/DnaRepositoryStub.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -39,6 +39,9 @@
  * Concrete implementation of {@link RepositoryStub} based on DNA-specific configuration.
  */
 public class DnaRepositoryStub extends RepositoryStub {
+
+    public static final String DNA_SKIP_IMPORT = "javax.jcr.tck.dnaSkipImport";
+    
     private static final String REPOSITORY_SOURCE_NAME = "Test Repository Source";
 
     private static String currentConfigurationName = "default";
@@ -46,7 +49,6 @@
     private Properties configProps;
     private String repositoryConfigurationName;
     private JcrRepository repository;
-    
 
     static {
 
@@ -69,7 +71,7 @@
 
     private void configureRepository() {
         repositoryConfigurationName = currentConfigurationName;
-        
+
         // Create the in-memory (DNA) repository
         JcrConfiguration configuration = new JcrConfiguration();
         try {
@@ -94,7 +96,6 @@
         JcrEngine engine = configuration.build();
         engine.start();
 
-        // Problems problems = engine.getRepositoryService().getStartupProblems();
         Problems problems = engine.getProblems();
         // Print all of the problems from the engine configuration ...
         for (Problem problem : problems) {
@@ -110,24 +111,27 @@
         try {
             repository = engine.getRepository(REPOSITORY_SOURCE_NAME);
 
-            // Set up some sample nodes in the graph to match the expected test configuration
-            Graph graph = Graph.create(repository.getRepositorySourceName(),
-                                       engine.getRepositoryConnectionFactory(),
-                                       executionContext);
-            Path destinationPath = executionContext.getValueFactories().getPathFactory().createRootPath();
+            // This needs to check configProps directly to avoid an infinite loop
+            String skipImport = (String)configProps.get(DNA_SKIP_IMPORT);
+            if (!Boolean.valueOf(skipImport)) {
 
-            InputStream xmlStream = getClass().getResourceAsStream("/tck/repositoryForTckTests.xml");
-            graph.importXmlFrom(xmlStream).into(destinationPath);
+                // Set up some sample nodes in the graph to match the expected test configuration
+                Graph graph = Graph.create(repository.getRepositorySourceName(),
+                                           engine.getRepositoryConnectionFactory(),
+                                           executionContext);
+                Path destinationPath = executionContext.getValueFactories().getPathFactory().createRootPath();
 
-            graph.createWorkspace().named("otherWorkspace");
+                InputStream xmlStream = getClass().getResourceAsStream("/tck/repositoryForTckTests.xml");
+                graph.importXmlFrom(xmlStream).into(destinationPath);
 
+                graph.createWorkspace().named("otherWorkspace");
+            }
         } catch (Exception ex) {
             // The TCK tries to quash this exception. Print it out to be more obvious.
             ex.printStackTrace();
             throw new IllegalStateException("Failed to initialize the repository with text content.", ex);
         }
 
-
     }
 
     public static void setCurrentConfigurationName( String newConfigName ) {

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/DnaTckTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/DnaTckTest.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/DnaTckTest.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -13,6 +13,8 @@
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
 import javax.jcr.nodetype.ConstraintViolationException;
+import junit.framework.Test;
+import junit.framework.TestSuite;
 import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.jboss.dna.jcr.nodetype.NodeTypeTemplate;
 
@@ -23,6 +25,25 @@
 
     Session session;
 
+    public DnaTckTest( String testName ) {
+        super();
+
+        this.setName(testName);
+        this.isReadOnly = true;
+    }
+    
+    public static Test readOnlySuite() {
+        TestSuite suite = new TestSuite("DNA JCR API tests");
+
+        suite.addTest(new DnaTckTest("testShouldAllowAdminSessionToRead"));
+        suite.addTest(new DnaTckTest("testShouldAllowReadOnlySessionToRead"));
+        suite.addTest(new DnaTckTest("testShouldAllowReadWriteSessionToRead"));
+        suite.addTest(new DnaTckTest("testShouldNotSeeWorkspacesWithoutReadPermission"));
+        suite.addTest(new DnaTckTest("testShouldMapReadRolesToWorkspacesWhenSpecified"));
+
+        return suite;
+    }
+    
     @Override
     protected void tearDown() throws Exception {
         try {
@@ -254,11 +275,35 @@
      * 
      * @throws Exception
      */
-    public void testShouldMapRolesToWorkspacesWhenSpecified() throws Exception {
+    public void testShouldMapReadRolesToWorkspacesWhenSpecified() throws Exception {
         Credentials creds = new SimpleCredentials("defaultonly", "defaultonly".toCharArray());
         session = helper.getRepository().login(creds);
 
         testRead(session);
+
+        session.logout();
+
+        session = helper.getRepository().login(creds, "otherWorkspace");
+        testRead(session);
+        try {
+            testWrite(session);
+            fail("User 'defaultuser' should not have write access to 'otherWorkspace'");
+        } catch (AccessDeniedException expected) {
+        }
+        session.logout();
+    }
+
+    /**
+     * User defaultuser is configured to have readwrite in "otherWorkspace" and readonly in the default workspace. This test makes
+     * sure both work.
+     * 
+     * @throws Exception
+     */
+    public void testShouldMapWriteRolesToWorkspacesWhenSpecified() throws Exception {
+        Credentials creds = new SimpleCredentials("defaultonly", "defaultonly".toCharArray());
+        session = helper.getRepository().login(creds);
+
+        testRead(session);
         testWrite(session);
 
         session.logout();

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -107,6 +107,23 @@
     }
 
     /**
+     * Wrapper for read-only tests
+     * 
+     * @return a new instance of {@link JCRTestSuite}.
+     */
+    public static Test readOnlySuite() {
+        // Uncomment this to execute all tests
+        // return new JCRTestSuite();
+
+        // Or uncomment the following lines to execute the different sets/suites of tests ...
+        TestSuite suite = new TestSuite("JCR 1.0 API tests");
+
+        suite.addTest(new LevelOneFeatureTests());
+
+        return suite;
+    }
+
+    /**
      * Test suite that includes the Level 1 JCR TCK API tests from the Jackrabbit project.
      */
     private static class LevelOneFeatureTests extends TestSuite {

Modified: trunk/dna-jcr/src/test/resources/security/tck_roles.properties
===================================================================
--- trunk/dna-jcr/src/test/resources/security/tck_roles.properties	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/dna-jcr/src/test/resources/security/tck_roles.properties	2009-07-24 01:15:00 UTC (rev 1126)
@@ -2,6 +2,6 @@
 superuser=admin
 readwrite=readwrite
 readonly=readonly
-# default workspace name is the empty string
-defaultonly=readwrite.Store.default,readonly.Store.otherWorkspace
+# defaultWorkspace is the default workspace name for the filesystem TCK test
+defaultonly=readwrite.Store.default,readonly.Store.otherWorkspace,readwrite.Store.defaultWorkspace
 noaccess=readonly.Store.otherWorkspace

Modified: trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemConnection.java
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemConnection.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemConnection.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -25,7 +25,8 @@
 
 import java.io.File;
 import java.io.FilenameFilter;
-import java.util.Set;
+import java.util.Map;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import javax.transaction.xa.XAResource;
 import org.jboss.dna.graph.ExecutionContext;
@@ -44,28 +45,35 @@
 public class FileSystemConnection implements RepositoryConnection {
 
     private final String sourceName;
-    private final File defaultWorkspace;
+    private final String defaultWorkspaceName;
     private final CachePolicy cachePolicy;
-    private final Set<String> availableWorkspaceNames;
+    private final Map<String, File> availableWorkspaces;
     private final boolean creatingWorkspacesAllowed;
     private final FilenameFilter filenameFilter;
+    private final UUID rootNodeUuid;
+    private final String workspaceRootPath;
     private final boolean updatesAllowed;
 
     FileSystemConnection( String sourceName,
-                          File defaultWorkspace,
-                          Set<String> availableWorkspaceNames,
+                          String defaultWorkspaceName,
+                          Map<String, File> availableWorkspaces,
                           boolean creatingWorkspacesAllowed,
                           CachePolicy cachePolicy,
+                          UUID rootNodeUuid,
+                          String workspaceRootPath,
                           FilenameFilter filenameFilter,
                           boolean updatesAllowed ) {
         assert sourceName != null;
         assert sourceName.trim().length() != 0;
-        assert availableWorkspaceNames != null;
+        assert availableWorkspaces != null;
+        assert rootNodeUuid != null;
         this.sourceName = sourceName;
-        this.defaultWorkspace = defaultWorkspace;
-        this.availableWorkspaceNames = availableWorkspaceNames;
+        this.defaultWorkspaceName = defaultWorkspaceName;
+        this.availableWorkspaces = availableWorkspaces;
         this.creatingWorkspacesAllowed = creatingWorkspacesAllowed;
         this.cachePolicy = cachePolicy;
+        this.rootNodeUuid = rootNodeUuid;
+        this.workspaceRootPath = workspaceRootPath;
         this.filenameFilter = filenameFilter;
         this.updatesAllowed = updatesAllowed;
     }
@@ -115,8 +123,9 @@
      */
     public void execute( ExecutionContext context,
                          Request request ) throws RepositorySourceException {
-        RequestProcessor proc = new FileSystemRequestProcessor(sourceName, defaultWorkspace, availableWorkspaceNames,
-                                                               creatingWorkspacesAllowed, context, filenameFilter, updatesAllowed);
+        RequestProcessor proc = new FileSystemRequestProcessor(sourceName, defaultWorkspaceName, availableWorkspaces,
+                                                               creatingWorkspacesAllowed, rootNodeUuid, workspaceRootPath,
+                                                               context, filenameFilter, updatesAllowed);
         try {
             proc.process(request);
         } finally {

Modified: trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemI18n.java
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemI18n.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemI18n.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -40,6 +40,9 @@
     public static I18n pathForPredefinedWorkspaceDoesNotExist;
     public static I18n pathForPredefinedWorkspaceIsNotDirectory;
     public static I18n pathForPredefinedWorkspaceCannotBeRead;
+    public static I18n pathForWorkspaceRootDoesNotExist;
+    public static I18n pathForWorkspaceRootIsNotDirectory;
+    public static I18n pathForWorkspaceRootCannotBeRead;
     public static I18n propertyIsRequired;
     public static I18n locationInRequestMustHavePath;
     public static I18n sameNameSiblingsAreNotAllowed;

Modified: trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemRequestProcessor.java
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemRequestProcessor.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemRequestProcessor.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -31,8 +31,11 @@
 import java.io.InputStream;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.graph.DnaLexicon;
 import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.JcrLexicon;
 import org.jboss.dna.graph.JcrNtLexicon;
@@ -77,43 +80,68 @@
     private static final String DEFAULT_MIME_TYPE = "application/octet";
 
     private final String defaultNamespaceUri;
-    private final Set<String> availableWorkspaceNames;
+    private final Map<String, File> availableWorkspaces;
     private final boolean creatingWorkspacesAllowed;
-    private final File defaultWorkspace;
+    private final String defaultWorkspaceName;
+    private final File workspaceRootPath;
     private final FilenameFilter filenameFilter;
     private final boolean updatesAllowed;
     private final MimeTypeDetector mimeTypeDetector;
+    private final UUID rootNodeUuid;
 
     /**
      * @param sourceName
-     * @param defaultWorkspace
-     * @param availableWorkspaceNames
+     * @param defaultWorkspaceName
+     * @param availableWorkspaces
      * @param creatingWorkspacesAllowed
      * @param context
+     * @param rootNodeUuid the UUID for the root node in this workspace; may be null. If not specified, a random UUID will be
+     *        generated each time that the repository is started.
+     * @param workspaceRootPath the path to the workspace root directory; may be null. If specified, all workspace names will be
+     *        treated as relative paths from this directory.
      * @param filenameFilter the filename filter to use to restrict the allowable nodes, or null if all files/directories are to
      *        be exposed by this connector
      * @param updatesAllowed true if this connector supports updating the file system, or false if the connector is readonly
      */
     protected FileSystemRequestProcessor( String sourceName,
-                                          File defaultWorkspace,
-                                          Set<String> availableWorkspaceNames,
+                                          String defaultWorkspaceName,
+                                          Map<String, File> availableWorkspaces,
                                           boolean creatingWorkspacesAllowed,
+                                          UUID rootNodeUuid,
+                                          String workspaceRootPath,
                                           ExecutionContext context,
                                           FilenameFilter filenameFilter,
                                           boolean updatesAllowed ) {
         super(sourceName, context, null);
-        assert defaultWorkspace != null;
-        assert defaultWorkspace.exists();
-        assert defaultWorkspace.canRead();
-        assert defaultWorkspace.isDirectory();
-        assert availableWorkspaceNames != null;
-        this.availableWorkspaceNames = availableWorkspaceNames;
+        assert defaultWorkspaceName != null;
+        assert availableWorkspaces != null;
+        assert rootNodeUuid != null;
+        this.availableWorkspaces = availableWorkspaces;
         this.creatingWorkspacesAllowed = creatingWorkspacesAllowed;
         this.defaultNamespaceUri = getExecutionContext().getNamespaceRegistry().getDefaultNamespaceUri();
+        this.rootNodeUuid = rootNodeUuid;
         this.filenameFilter = filenameFilter;
-        this.defaultWorkspace = defaultWorkspace;
+        this.defaultWorkspaceName = defaultWorkspaceName;
         this.updatesAllowed = updatesAllowed;
         this.mimeTypeDetector = context.getMimeTypeDetector();
+
+        if (workspaceRootPath != null) {
+            this.workspaceRootPath = new File(workspaceRootPath);
+            if (!this.workspaceRootPath.exists()) {
+                throw new IllegalStateException(FileSystemI18n.pathForWorkspaceRootDoesNotExist.text(workspaceRootPath,
+                                                                                                     sourceName));
+            }
+            if (!this.workspaceRootPath.isDirectory()) {
+                throw new IllegalStateException(FileSystemI18n.pathForWorkspaceRootIsNotDirectory.text(workspaceRootPath,
+                                                                                                       sourceName));
+            }
+            if (!this.workspaceRootPath.canRead()) {
+                throw new IllegalStateException(FileSystemI18n.pathForWorkspaceRootCannotBeRead.text(workspaceRootPath,
+                                                                                                     sourceName));
+            }
+        } else {
+            this.workspaceRootPath = null;
+        }
     }
 
     /**
@@ -134,6 +162,11 @@
         // Find the existing file for the parent ...
         Location location = request.of();
         Path parentPath = getPathFor(location, request);
+
+        if (parentPath.isRoot()) {
+            if (!location.hasPath()) location = location.with(parentPath);
+        }
+
         File parent = getExistingFileFor(workspaceRoot, parentPath, location, request);
         if (parent == null) {
             // An error was set on the request
@@ -180,13 +213,20 @@
             return;
         }
 
+        PropertyFactory factory = getExecutionContext().getPropertyFactory();
+
         // Find the existing file for the parent ...
         Location location = request.at();
         Path path = getPathFor(location, request);
         if (path.isRoot()) {
-            // There are no properties on the root ...
+            // Root nodes can be requested by UUID, path, or both
+            if (!location.hasPath()) location = location.with(path);
+            if (location.getUuid() == null) location = location.with(rootNodeUuid);
+
+            request.addProperty(factory.create(JcrLexicon.PRIMARY_TYPE, DnaLexicon.ROOT));
             request.setActualLocationOfNode(location);
             setCacheableInfo(request);
+
             return;
         }
 
@@ -197,7 +237,6 @@
             return;
         }
         // Generate the properties for this File object ...
-        PropertyFactory factory = getExecutionContext().getPropertyFactory();
         DateTimeFactory dateFactory = getExecutionContext().getValueFactories().getDateFactory();
         // Note that we don't have 'created' timestamps, just last modified, so we'll have to use them
         if (file.isDirectory()) {
@@ -209,7 +248,9 @@
             // It is a file, but ...
             if (path.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
                 // The request is to get properties of the "jcr:content" child node ...
-                request.addProperty(factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.RESOURCE));
+                // ... use the dna:resource node type. This is the same as nt:resource, but is not referenceable
+                // since we cannot assume that we control all access to this file and can track its movements
+                request.addProperty(factory.create(JcrLexicon.PRIMARY_TYPE, DnaLexicon.RESOURCE));
                 request.addProperty(factory.create(JcrLexicon.LAST_MODIFIED, dateFactory.create(file.lastModified())));
                 // Don't really know the encoding, either ...
                 // request.addProperty(factory.create(JcrLexicon.ENCODED, stringFactory.create("UTF-8")));
@@ -330,23 +371,19 @@
     public void process( VerifyWorkspaceRequest request ) {
         // If the request contains a null name, then we use the default ...
         String workspaceName = request.workspaceName();
-        if (workspaceName == null) workspaceName = getCanonicalWorkspaceName(defaultWorkspace);
+        if (workspaceName == null) workspaceName = defaultWorkspaceName;
 
         if (!this.creatingWorkspacesAllowed) {
             // Then the workspace name must be one of the available names ...
+
             boolean found = false;
-            for (String available : this.availableWorkspaceNames) {
+            for (String available : this.availableWorkspaces.keySet()) {
                 if (workspaceName.equals(available)) {
                     found = true;
                     break;
                 }
-                File directory = new File(available);
-                if (directory.exists() && directory.isDirectory() && directory.canRead()
-                    && getCanonicalWorkspaceName(directory).equals(workspaceName)) {
-                    found = true;
-                    break;
-                }
             }
+
             if (!found) {
                 request.setError(new InvalidWorkspaceException(FileSystemI18n.workspaceDoesNotExist.text(workspaceName)));
                 return;
@@ -354,9 +391,9 @@
             // We know it is an available workspace, so just continue ...
         }
         // Verify that there is a directory at the path given by the workspace name ...
-        File directory = new File(workspaceName);
+        File directory = availableWorkspaces.get(workspaceName);
         if (directory.exists() && directory.isDirectory() && directory.canRead()) {
-            request.setActualWorkspaceName(getCanonicalWorkspaceName(directory));
+            request.setActualWorkspaceName(workspaceName);
             request.setActualRootLocation(Location.create(pathFactory().createRootPath()));
         } else {
             request.setError(new InvalidWorkspaceException(FileSystemI18n.workspaceDoesNotExist.text(workspaceName)));
@@ -372,12 +409,13 @@
     public void process( GetWorkspacesRequest request ) {
         // Return the set of available workspace names, even if new workspaces can be created ...
         Set<String> names = new HashSet<String>();
-        for (String name : this.availableWorkspaceNames) {
-            File directory = new File(name);
+        for (Map.Entry<String, File> entry : this.availableWorkspaces.entrySet()) {
+            File directory = entry.getValue();
             if (directory.exists() && directory.isDirectory() && directory.canRead()) {
-                names.add(getCanonicalWorkspaceName(directory));
+                names.add(entry.getKey());
             }
         }
+
         request.setAvailableWorkspaceNames(Collections.unmodifiableSet(names));
     }
 
@@ -389,6 +427,12 @@
      */
     protected String getCanonicalWorkspaceName( File directory ) {
         try {
+            if (this.workspaceRootPath != null) {
+                String directoryCanonicalPath = directory.getCanonicalPath();
+                String rootCanonicalPath = workspaceRootPath.getCanonicalPath();
+                assert directoryCanonicalPath.startsWith(rootCanonicalPath);
+                return directoryCanonicalPath.substring(rootCanonicalPath.length() + 1);
+            }
             return directory.getCanonicalPath();
         } catch (IOException e) {
             return directory.getAbsolutePath();
@@ -424,7 +468,7 @@
         if (directory.exists() && directory.isDirectory() && directory.canRead()) {
             request.setActualWorkspaceName(getCanonicalWorkspaceName(directory));
             request.setActualRootLocation(Location.create(pathFactory().createRootPath()));
-            availableWorkspaceNames.add(workspaceName);
+            availableWorkspaces.put(workspaceName, directory);
             recordChange(request);
         } else {
             request.setError(new InvalidWorkspaceException(FileSystemI18n.workspaceDoesNotExist.text(workspaceName)));
@@ -444,7 +488,7 @@
             request.setError(new InvalidRequestException(msg));
         }
         // This doesn't delete the file/directory; rather, it just remove the workspace from the available set ...
-        if (!this.availableWorkspaceNames.remove(workspaceName)) {
+        if (this.availableWorkspaces.remove(workspaceName) == null) {
             request.setError(new InvalidWorkspaceException(FileSystemI18n.workspaceDoesNotExist.text(workspaceName)));
         } else {
             request.setActualRootLocation(Location.create(pathFactory().createRootPath()));
@@ -470,6 +514,11 @@
     protected Path getPathFor( Location location,
                                Request request ) {
         Path path = location.getPath();
+
+        if (location.getUuid() != null && rootNodeUuid.equals(location.getUuid())) {
+            return pathFactory().createRootPath();
+        }
+
         if (path == null) {
             I18n msg = FileSystemI18n.locationInRequestMustHavePath;
             throw new RepositorySourceException(getSourceName(), msg.text(getSourceName(), request));
@@ -478,13 +527,11 @@
     }
 
     protected File getWorkspaceDirectory( String workspaceName ) {
-        File workspace = defaultWorkspace;
-        if (workspaceName != null) {
-            File directory = new File(workspaceName);
-            if (directory.exists() && directory.isDirectory() && directory.canRead()) workspace = directory;
-            else return null;
-        }
-        return workspace;
+        if (workspaceName == null) workspaceName = defaultWorkspaceName;
+
+        File directory = this.workspaceRootPath == null ? new File(workspaceName) : new File(workspaceRootPath, workspaceName);
+        if (directory.exists() && directory.isDirectory() && directory.canRead()) return directory;
+        return null;
     }
 
     /**

Modified: trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemSource.java
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemSource.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemSource.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -31,7 +31,8 @@
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 import javax.naming.Context;
 import javax.naming.RefAddr;
 import javax.naming.Reference;
@@ -40,6 +41,7 @@
 import net.jcip.annotations.Immutable;
 import net.jcip.annotations.ThreadSafe;
 import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.util.CheckArg;
 import org.jboss.dna.common.util.Logger;
 import org.jboss.dna.common.util.StringUtil;
 import org.jboss.dna.graph.cache.CachePolicy;
@@ -64,10 +66,16 @@
      */
     private static final long serialVersionUID = 1L;
 
+    /**
+     * The initial {@link #getDefaultWorkspaceName() name of the default workspace} is "{@value} ", unless otherwise specified.
+     */
+    public static final String DEFAULT_NAME_OF_DEFAULT_WORKSPACE = "default";
+
     protected static final String SOURCE_NAME = "sourceName";
     protected static final String CACHE_TIME_TO_LIVE_IN_MILLISECONDS = "cacheTimeToLiveInMilliseconds";
     protected static final String RETRY_LIMIT = "retryLimit";
     protected static final String DEFAULT_WORKSPACE = "defaultWorkspace";
+    protected static final String WORKSPACE_ROOT = "workspaceRootPath";
     protected static final String PREDEFINED_WORKSPACE_NAMES = "predefinedWorkspaceNames";
     protected static final String ALLOW_CREATING_WORKSPACES = "allowCreatingWorkspaces";
 
@@ -84,7 +92,7 @@
      */
     protected static final boolean DEFAULT_SUPPORTS_CREATING_WORKSPACES = true;
     /**
-     * This source does not support udpates by default, but each instance may be configured to be read-only or updateable}.
+     * This source does not support updates by default, but each instance may be configured to be read-only or updateable}.
      */
     public static final boolean DEFAULT_SUPPORTS_UPDATES = false;
 
@@ -99,8 +107,10 @@
     private volatile String name;
     private volatile int retryLimit = DEFAULT_RETRY_LIMIT;
     private volatile int cacheTimeToLiveInMilliseconds = DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS * 1000;
-    private volatile String defaultWorkspace;
+    private volatile String defaultWorkspaceName = DEFAULT_NAME_OF_DEFAULT_WORKSPACE;
+    private volatile String workspaceRootPath;
     private volatile String[] predefinedWorkspaces = new String[] {};
+    private volatile UUID rootNodeUuid = UUID.randomUUID();
     private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(
                                                                                                   SUPPORTS_SAME_NAME_SIBLINGS,
                                                                                                   DEFAULT_SUPPORTS_UPDATES,
@@ -108,7 +118,7 @@
                                                                                                   DEFAULT_SUPPORTS_CREATING_WORKSPACES,
                                                                                                   SUPPORTS_REFERENCES);
     private transient CachePolicy cachePolicy;
-    private transient CopyOnWriteArraySet<String> availableWorkspaceNames;
+    private transient Map<String, File> availableWorkspaces;
 
     /**
      * 
@@ -170,30 +180,64 @@
     // }
 
     /**
-     * Get the file system path to the existing directory that should be used for the default workspace. If the default is
-     * specified as a null String or is not a valid and resolvable path, this source will consider the default to be the current
-     * working directory of this virtual machine, as defined by the <code>new File(".")</code>.
+     * Get the relative root directory for the workspaces. If this property is set, workspaces can be given as relative paths from
+     * this directory and all workspace paths must be ancestors of this path.
      * 
-     * @return the file system path to the directory representing the default workspace, or null if the default should be the
-     *         current working directory
+     * @return the root directory for workspaces
      */
-    public String getDirectoryForDefaultWorkspace() {
-        return defaultWorkspace;
+    public String getWorkspaceRootPath() {
+        return workspaceRootPath;
     }
 
     /**
-     * Set the file system path to the existing directory that should be used for the default workspace. If the default is
-     * specified as a null String or is not a valid and resolvable path, this source will consider the default to be the current
-     * working directory of this virtual machine, as defined by the <code>new File(".")</code>.
+     * Sets the relative root directory for workspaces
      * 
-     * @param pathToDirectoryForDefaultWorkspace the valid and resolvable file system path to the directory representing the
-     *        default workspace, or null if the current working directory should be used as the default workspace
+     * @param workspaceRootPath the relative root directory for workspaces. If this value is non-null, all workspace paths will be
+     *        treated as paths relative to this directory
      */
-    public synchronized void setDirectoryForDefaultWorkspace( String pathToDirectoryForDefaultWorkspace ) {
-        this.defaultWorkspace = pathToDirectoryForDefaultWorkspace;
+    public synchronized void setWorkspaceRootPath( String workspaceRootPath ) {
+        this.workspaceRootPath = workspaceRootPath;
     }
 
     /**
+     * Get the UUID that is used for the root node of each workspace
+     * 
+     * @return the UUID that is used for the root node of each workspace
+     */
+    public UUID getRootNodeUuid() {
+        return rootNodeUuid;
+    }
+
+    /**
+     * Set the {@code jcr:uuid} property of the root node in each workspace to the given value.
+     * 
+     * @param rootNodeUuid the UUID to use for the root nodes of all workspaces
+     */
+    public synchronized void setRootNodeUuid( String rootNodeUuid ) {
+        CheckArg.isNotNull(rootNodeUuid, "rootNodeUuid");
+        this.rootNodeUuid = UUID.fromString(rootNodeUuid);
+    }
+
+    /**
+     * Get the name of the default workspace.
+     * 
+     * @return the name of the workspace that should be used by default; never null
+     */
+    public String getDefaultWorkspaceName() {
+        return defaultWorkspaceName;
+    }
+
+    /**
+     * Set the name of the workspace that should be used when clients don't specify a workspace.
+     * 
+     * @param nameOfDefaultWorkspace the name of the workspace that should be used by default, or null if the
+     *        {@link #DEFAULT_NAME_OF_DEFAULT_WORKSPACE default name} should be used
+     */
+    public synchronized void setDefaultWorkspaceName( String nameOfDefaultWorkspace ) {
+        this.defaultWorkspaceName = nameOfDefaultWorkspace != null ? nameOfDefaultWorkspace : DEFAULT_NAME_OF_DEFAULT_WORKSPACE;
+    }
+
+    /**
      * Gets the names of the workspaces that are available when this source is created. Each workspace name corresponds to a path
      * to a directory on the file system.
      * 
@@ -310,7 +354,7 @@
         }
         ref.add(new StringRefAddr(CACHE_TIME_TO_LIVE_IN_MILLISECONDS, Integer.toString(getCacheTimeToLiveInMilliseconds())));
         ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
-        ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDirectoryForDefaultWorkspace()));
+        ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDefaultWorkspaceName()));
         ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed())));
         String[] workspaceNames = getPredefinedWorkspaceNames();
         if (workspaceNames != null && workspaceNames.length != 0) {
@@ -356,7 +400,7 @@
             if (sourceName != null) source.setName(sourceName);
             if (cacheTtlInMillis != null) source.setCacheTimeToLiveInMilliseconds(Integer.parseInt(cacheTtlInMillis));
             if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
-            if (defaultWorkspace != null) source.setDirectoryForDefaultWorkspace(defaultWorkspace);
+            if (defaultWorkspace != null) source.setDefaultWorkspaceName(defaultWorkspace);
             if (createWorkspaces != null) source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces));
             if (workspaceNames != null && workspaceNames.length != 0) source.setPredefinedWorkspaceNames(workspaceNames);
             return source;
@@ -364,6 +408,18 @@
         return null;
     }
 
+    private String pathFor( String workspaceName ) {
+        String path = workspaceName;
+        if (this.workspaceRootPath != null) {
+            if (this.workspaceRootPath.charAt(workspaceRootPath.length() - 1) == File.separatorChar) {
+                path = this.workspaceRootPath + workspaceName;
+            }
+            path = this.workspaceRootPath + File.separatorChar + workspaceName;
+        }
+
+        return path;
+    }
+
     /**
      * {@inheritDoc}
      * 
@@ -376,54 +432,42 @@
             throw new RepositorySourceException(getName(), msg.text("name"));
         }
 
-        boolean reportWarnings = false;
-        if (this.availableWorkspaceNames == null) {
+        if (this.availableWorkspaces == null) {
             // Set up the predefined workspace names ...
-            this.availableWorkspaceNames = new CopyOnWriteArraySet<String>();
+            this.availableWorkspaces = new ConcurrentHashMap<String, File>();
             for (String predefined : this.predefinedWorkspaces) {
-                this.availableWorkspaceNames.add(predefined);
-            }
-
-            // Report the warnings for non-existant predefined workspaces
-            reportWarnings = true;
-            for (String path : this.availableWorkspaceNames) {
                 // Look for the file at this path ...
-                File file = new File(path);
+                File file = new File(pathFor(predefined));
                 if (!file.exists()) {
-                    Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceDoesNotExist, path, name);
+                    Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceDoesNotExist, predefined, name);
                 } else if (!file.isDirectory()) {
-                    Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceIsNotDirectory, path, name);
+                    Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceIsNotDirectory, predefined, name);
                 } else if (!file.canRead()) {
-                    Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceCannotBeRead, path, name);
+                    Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceCannotBeRead, predefined, name);
                 }
+                
+                
+                this.availableWorkspaces.put(predefined, file);
             }
         }
 
-        FilenameFilter filenameFilter = null;
-        boolean supportsUpdates = getSupportsUpdates();
-        File defaultWorkspace = new File(".");
-        String path = getDirectoryForDefaultWorkspace();
-        if (path != null) {
+        if (defaultWorkspaceName != null) { 
             // Look for the file at this path ...
-            File file = new File(path);
-            I18n warning = null;
+            File file = new File(pathFor(defaultWorkspaceName));
             if (!file.exists()) {
-                warning = FileSystemI18n.pathForDefaultWorkspaceDoesNotExist;
+                Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceDoesNotExist, defaultWorkspaceName, name);
             } else if (!file.isDirectory()) {
-                warning = FileSystemI18n.pathForDefaultWorkspaceIsNotDirectory;
+                Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceIsNotDirectory, defaultWorkspaceName, name);
             } else if (!file.canRead()) {
-                warning = FileSystemI18n.pathForDefaultWorkspaceCannotBeRead;
-            } else {
-                // good to use!
-                defaultWorkspace = file;
+                Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceCannotBeRead, defaultWorkspaceName, name);
             }
-            if (reportWarnings && warning != null) {
-                Logger.getLogger(getClass()).warn(warning, path, name);
-            }
+            
+            
+            this.availableWorkspaces.put(defaultWorkspaceName, file);
         }
-        this.availableWorkspaceNames.add(defaultWorkspace.getPath());
-        return new FileSystemConnection(name, defaultWorkspace, availableWorkspaceNames, isCreatingWorkspacesAllowed(),
-                                        cachePolicy, filenameFilter, supportsUpdates);
+        
+        return new FileSystemConnection(name, defaultWorkspaceName, availableWorkspaces, isCreatingWorkspacesAllowed(),
+                                        cachePolicy, rootNodeUuid, workspaceRootPath, (FilenameFilter) null, getSupportsUpdates());
     }
 
     @Immutable

Modified: trunk/extensions/dna-connector-filesystem/src/main/resources/org/jboss/dna/connector/filesystem/FileSystemI18n.properties
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/main/resources/org/jboss/dna/connector/filesystem/FileSystemI18n.properties	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/main/resources/org/jboss/dna/connector/filesystem/FileSystemI18n.properties	2009-07-24 01:15:00 UTC (rev 1126)
@@ -29,6 +29,9 @@
 pathForPredefinedWorkspaceDoesNotExist = The path "{0}" for the predefined workspace for the file system source "{1}" does not represent an existing directory
 pathForPredefinedWorkspaceIsNotDirectory = The path "{0}" for the predefined workspace for the file system source "{1}" is actually a path to an existing file
 pathForPredefinedWorkspaceCannotBeRead = The path "{0}" for the predefined workspace for the file system source "{1}" cannot be read
+pathForWorkspaceRootDoesNotExist = The path "{0}" for the predefined workspace for the file system source "{1}" does not represent an existing directory
+pathForWorkspaceRootIsNotDirectory = The path "{0}" for the predefined workspace for the file system source "{1}" is actually a path to an existing file
+pathForWorkspaceRootCannotBeRead = The path "{0}" for the predefined workspace for the file system source "{1}" cannot be read
 propertyIsRequired = The {0} property is required but has no value
 locationInRequestMustHavePath = {0} requires a path in the request: {1}
 sameNameSiblingsAreNotAllowed = {0} does not allow same name siblings on nodes: {1}

Modified: trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorCreateWorkspacesTest.java
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorCreateWorkspacesTest.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorCreateWorkspacesTest.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -25,7 +25,6 @@
 
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertThat;
-import java.io.File;
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
@@ -56,7 +55,7 @@
         FileSystemSource source = new FileSystemSource();
         source.setName("Test Repository");
         source.setPredefinedWorkspaceNames(predefinedWorkspaceNames);
-        source.setDirectoryForDefaultWorkspace(predefinedWorkspaceNames[0]);
+        source.setDefaultWorkspaceName(predefinedWorkspaceNames[0]);
         source.setCreatingWorkspacesAllowed(true);
 
         return source;
@@ -101,11 +100,8 @@
             workspaceNames.add(workspace.getName());
         }
         // The actual names should be the absolute paths to the directories representing the root ...
-        String absolutePathToRepositories = new File("./src/test/resources/repositories/").getCanonicalPath();
+        String absolutePathToRepositories = "./src/test/resources/repositories/";
         
-        // getCanonicalPath strips the trailing separator character, so we need to re-added it for each case
-        absolutePathToRepositories += File.separatorChar;	
-        
         assertThat(workspaceNames.remove(absolutePathToRepositories + "airplanes"), is(true));
         assertThat(workspaceNames.remove(absolutePathToRepositories + "cars"), is(true));
         assertThat(workspaceNames.isEmpty(), is(true));

Modified: trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNoCreateWorkspaceTest.java
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNoCreateWorkspaceTest.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNoCreateWorkspaceTest.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -49,7 +49,7 @@
         FileSystemSource source = new FileSystemSource();
         source.setName("Test Repository");
         source.setPredefinedWorkspaceNames(predefinedWorkspaceNames);
-        source.setDirectoryForDefaultWorkspace(predefinedWorkspaceNames[0]);
+        source.setDefaultWorkspaceName(predefinedWorkspaceNames[0]);
         source.setCreatingWorkspacesAllowed(false);
 
         return source;

Modified: trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNotWritableTest.java
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNotWritableTest.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNotWritableTest.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -46,7 +46,7 @@
         FileSystemSource source = new FileSystemSource();
         source.setName("Test Repository");
         source.setPredefinedWorkspaceNames(predefinedWorkspaceNames);
-        source.setDirectoryForDefaultWorkspace(predefinedWorkspaceNames[0]);
+        source.setDefaultWorkspaceName(predefinedWorkspaceNames[0]);
         source.setCreatingWorkspacesAllowed(false);
 
         return source;

Modified: trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorReadableTest.java
===================================================================
--- trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorReadableTest.java	2009-07-22 22:41:00 UTC (rev 1125)
+++ trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorReadableTest.java	2009-07-24 01:15:00 UTC (rev 1126)
@@ -26,7 +26,6 @@
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsNull.notNullValue;
 import static org.junit.Assert.assertThat;
-import java.io.File;
 import java.util.List;
 import org.jboss.dna.graph.Graph;
 import org.jboss.dna.graph.JcrLexicon;
@@ -50,13 +49,13 @@
     @Override
     protected RepositorySource setUpSource() {
         // Set the connection properties to be use the content of "./src/test/resources/repositories" as a repository ...
-        String path = new File(".").getAbsolutePath() + "/src/test/resources/repositories/";
+        String path = "./src/test/resources/repositories/";
         String[] predefinedWorkspaceNames = new String[] {path + "airplanes", path + "cars"};
         FileSystemSource source = new FileSystemSource();
         source.setName("Test Repository");
         source.setPredefinedWorkspaceNames(predefinedWorkspaceNames);
-        source.setDirectoryForDefaultWorkspace(predefinedWorkspaceNames[0]);
-        source.setCreatingWorkspacesAllowed(false);
+        source.setDefaultWorkspaceName(predefinedWorkspaceNames[0]);
+        source.setCreatingWorkspacesAllowed(true);
 
         return source;
     }



More information about the dna-commits mailing list