Author: rhauch
Date: 2009-11-17 16:09:49 -0500 (Tue, 17 Nov 2009)
New Revision: 1325
Added:
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/CustomPropertiesFactory.java
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/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
Log:
DNA-553 Enhanced the file system connector so that it is much easier to define how
non-standard properties can be made to appear on nt:folder, nt:file, and nt:resource
nodes. By default, the file system connector behaves as it has in the past: it only uses
the standard properties defined by JCR for files, folders and content nodes.
There is a new CustomPropertiesFactory interface that defines several methods for getting
and setting custom properties. Simply implement this interface, and call
FileSystemSource.setCustomPropertiesFactory(...) with an instance of your implementation.
(Or, optionally subclass FileSystemSource, set the factory in the subclass'
constructor, and inherit all other functionality).
The connector uses this factory whenever it reads 'nt:folder', 'nt:file',
or 'nt:resource' nodes, and includes in these nodes the properties returned by the
factory. The factory methods take several arguments, including the ExecutionContext (which
can be used to obtain the PropertyFactory, value factories used to create property values,
and even the MIME type detector), the java.io.File object, the Location (which includes
the path), and other information. Hopefully this is sufficient to construct the necessary
custom properties.
Added:
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/CustomPropertiesFactory.java
===================================================================
---
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/CustomPropertiesFactory.java
(rev 0)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/CustomPropertiesFactory.java 2009-11-17
21:09:49 UTC (rev 1325)
@@ -0,0 +1,151 @@
+/*
+ * 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.connector.filesystem;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Map;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.JcrLexicon;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Property;
+
+/**
+ * A simple interface that allows an implementer to define additional properties for
"nt:folder", "nt:file", and "nt:resource"
+ * nodes created by the file system connector.
+ * <p>
+ * To use, supply the implementation to a {@link FileSystemSource} object (or register
the factory in a subclass of
+ * FileSystemSource). Implementations should be immutable because they are shared between
all the connections.
+ * </p>
+ */
+@Immutable
+public interface CustomPropertiesFactory extends Serializable {
+
+ /**
+ * Construct the custom properties that should be created for the supplied directory
that is to be treated as an "nt:folder".
+ * The resulting properties should not include the standard {@link
JcrLexicon#PRIMARY_TYPE} or {@link JcrLexicon#CREATED}
+ * properties, which are set automatically and will override any returned Property
with the same name.
+ *
+ * @param context the execution context; never null
+ * @param location the Location of the node, which always contains a {@link
Location#getPath() path}; never null
+ * @param directory the file system object; never null and {@link File#isDirectory()}
will always return true
+ * @return the custom properties; never null but possibly empty
+ */
+ Collection<Property> getDirectoryProperties( ExecutionContext context,
+ Location location,
+ File directory );
+
+ /**
+ * Construct the custom properties that should be created for the supplied file that
is to be treated as an "nt:resource",
+ * which is the node that contains the content-oriented properties and that is a
child of a "nt:file" node. The resulting
+ * properties should not include the standard {@link JcrLexicon#PRIMARY_TYPE}, {@link
JcrLexicon#LAST_MODIFIED}, or
+ * {@link JcrLexicon#DATA} properties, which are set automatically and will override
any returned Property with the same name.
+ *
+ * @param context the execution context; never null
+ * @param location the Location of the node, which always contains a {@link
Location#getPath() path}; never null
+ * @param file the file system object; never null and {@link File#isFile()} will
always return true
+ * @param mimeType the mime type for the file, as determined by the {@link
ExecutionContext#getMimeTypeDetector() MIME type
+ * detector}, or null if the MIME type could not be determined
+ * @return the custom properties; never null but possibly empty
+ */
+ Collection<Property> getResourceProperties( ExecutionContext context,
+ Location location,
+ File file,
+ String mimeType );
+
+ /**
+ * Construct the custom properties that should be created for the supplied file that
is to be treated as an "nt:file". The
+ * resulting properties should not include the standard {@link
JcrLexicon#PRIMARY_TYPE} or {@link JcrLexicon#CREATED}
+ * properties, which are set automatically and will override any returned Property
with the same name.
+ * <p>
+ * Although the connector does not automatically determine the MIME type for the
"nt:file" nodes, an implementation can
+ * determine the MIME type by using the context's {@link
ExecutionContext#getMimeTypeDetector() MIME type detector}. Note,
+ * however, that this may be an expensive operation, so it should be used only when
needed.
+ * </p>
+ *
+ * @param context the execution context; never null
+ * @param location the Location of the node, which always contains a {@link
Location#getPath() path}; never null
+ * @param file the file system object; never null and {@link File#isFile()} will
always return true
+ * @return the custom properties; never null but possibly empty
+ */
+ Collection<Property> getFileProperties( ExecutionContext context,
+ Location location,
+ File file );
+
+ /**
+ * Record the supplied properties as being set on the designated
"nt:folder" node.
+ *
+ * @param context the execution context; never null
+ * @param sourceName the name of the repository source; never null
+ * @param location the Location of the node, which always contains a {@link
Location#getPath() path}; never null
+ * @param file the file system object; never null, and both {@link File#exists()} and
{@link File#isDirectory()} will always
+ * return true
+ * @param properties the properties that are to be set
+ * @throws RepositorySourceException if any properties are invalid or cannot be set
on these nodes
+ */
+ void recordDirectoryProperties( ExecutionContext context,
+ String sourceName,
+ Location location,
+ File file,
+ Map<Name, Property> properties ) throws
RepositorySourceException;
+
+ /**
+ * Record the supplied properties as being set on the designated "nt:file"
node.
+ *
+ * @param context the execution context; never null
+ * @param sourceName the name of the repository source; never null
+ * @param location the Location of the node, which always contains a {@link
Location#getPath() path}; never null
+ * @param file the file system object; never null, and both {@link File#exists()} and
{@link File#isFile()} will always return
+ * true
+ * @param properties the properties that are to be set
+ * @throws RepositorySourceException if any properties are invalid or cannot be set
on these nodes
+ */
+ void recordFileProperties( ExecutionContext context,
+ String sourceName,
+ Location location,
+ File file,
+ Map<Name, Property> properties ) throws
RepositorySourceException;
+
+ /**
+ * Record the supplied properties as being set on the designated
"nt:resource" node.
+ *
+ * @param context the execution context; never null
+ * @param sourceName the name of the repository source; never null
+ * @param location the Location of the node, which always contains a {@link
Location#getPath() path}; never null
+ * @param file the file system object; never null, and both {@link File#exists()} and
{@link File#isFile()} will always return
+ * true
+ * @param properties the properties that are to be set
+ * @throws RepositorySourceException if any properties are invalid or cannot be set
on these nodes
+ */
+ void recordResourceProperties( ExecutionContext context,
+ String sourceName,
+ Location location,
+ File file,
+ Map<Name, Property> properties ) throws
RepositorySourceException;
+
+}
Property changes on:
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/CustomPropertiesFactory.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
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-11-17
18:47:00 UTC (rev 1324)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemConnection.java 2009-11-17
21:09:49 UTC (rev 1325)
@@ -52,6 +52,7 @@
private final int maxPathLength;
private final String workspaceRootPath;
private final boolean updatesAllowed;
+ private final CustomPropertiesFactory customPropertiesFactory;
FileSystemConnection( String sourceName,
String defaultWorkspaceName,
@@ -62,11 +63,13 @@
String workspaceRootPath,
int maxPathLength,
FilenameFilter filenameFilter,
- boolean updatesAllowed ) {
+ boolean updatesAllowed,
+ CustomPropertiesFactory customPropertiesFactory ) {
assert sourceName != null;
assert sourceName.trim().length() != 0;
assert availableWorkspaces != null;
assert rootNodeUuid != null;
+ assert customPropertiesFactory != null;
this.sourceName = sourceName;
this.defaultWorkspaceName = defaultWorkspaceName;
this.availableWorkspaces = availableWorkspaces;
@@ -77,6 +80,7 @@
this.maxPathLength = maxPathLength;
this.filenameFilter = filenameFilter;
this.updatesAllowed = updatesAllowed;
+ this.customPropertiesFactory = customPropertiesFactory;
}
/**
@@ -126,7 +130,8 @@
Request request ) throws RepositorySourceException {
RequestProcessor proc = new FileSystemRequestProcessor(sourceName,
defaultWorkspaceName, availableWorkspaces,
creatingWorkspacesAllowed,
rootNodeUuid, workspaceRootPath,
- maxPathLength, context,
filenameFilter, updatesAllowed);
+ maxPathLength, context,
filenameFilter, updatesAllowed,
+ customPropertiesFactory);
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-11-17
18:47:00 UTC (rev 1324)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemI18n.java 2009-11-17
21:09:49 UTC (rev 1325)
@@ -51,6 +51,7 @@
public static I18n sourceIsReadOnly;
public static I18n pathIsReadOnly;
public static I18n unableToCreateWorkspaces;
+ public static I18n errorSerializingCustomPropertiesFactory;
// Writable messages
public static I18n parentIsReadOnly;
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-11-17
18:47:00 UTC (rev 1324)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemRequestProcessor.java 2009-11-17
21:09:49 UTC (rev 1325)
@@ -30,8 +30,6 @@
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -42,7 +40,6 @@
import java.util.UUID;
import org.jboss.dna.common.i18n.I18n;
import org.jboss.dna.common.util.FileUtil;
-import org.jboss.dna.graph.DnaIntLexicon;
import org.jboss.dna.graph.DnaLexicon;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.JcrLexicon;
@@ -91,31 +88,6 @@
private static final String DEFAULT_MIME_TYPE = "application/octet";
- /**
- * Only certain properties are tolerated when writing content (dna:resource or
jcr:resource) nodes. These properties are
- * implicitly stored (primary type, data) or silently ignored (encoded, mimetype,
last modified). The silently ignored
- * properties must be accepted to stay compatible with the JCR specification.
- */
- private static final Set<Name> ALLOWABLE_PROPERTIES_FOR_CONTENT =
Collections.unmodifiableSet(new HashSet<Name>(
-
Arrays.asList(new Name[] {
-
JcrLexicon.PRIMARY_TYPE,
-
JcrLexicon.DATA,
-
JcrLexicon.ENCODED,
-
JcrLexicon.MIMETYPE,
-
JcrLexicon.LAST_MODIFIED,
-
JcrLexicon.UUID,
-
DnaIntLexicon.NODE_DEFINITON})));
- /**
- * Only certain properties are tolerated when writing files (nt:file) or folders
(nt:folder) nodes. These properties are
- * implicitly stored in the file or folder (primary type, created).
- */
- private static final Set<Name> ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER =
Collections.unmodifiableSet(new HashSet<Name>(
-
Arrays.asList(new Name[] {
-
JcrLexicon.PRIMARY_TYPE,
-
JcrLexicon.CREATED,
-
JcrLexicon.UUID,
-
DnaIntLexicon.NODE_DEFINITON})));
-
private final String defaultNamespaceUri;
private final Map<String, File> availableWorkspaces;
private final boolean creatingWorkspacesAllowed;
@@ -126,6 +98,7 @@
private final boolean updatesAllowed;
private final MimeTypeDetector mimeTypeDetector;
private final UUID rootNodeUuid;
+ private final CustomPropertiesFactory customPropertiesFactory;
/**
* @param sourceName
@@ -141,6 +114,8 @@
* @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
+ * @param customPropertiesFactory the factory that should be used to create custom
properties for "nt:folder", "nt:file", and
+ * "nt:resource" nodes
*/
protected FileSystemRequestProcessor( String sourceName,
String defaultWorkspaceName,
@@ -151,11 +126,13 @@
int maxPathLength,
ExecutionContext context,
FilenameFilter filenameFilter,
- boolean updatesAllowed ) {
+ boolean updatesAllowed,
+ CustomPropertiesFactory customPropertiesFactory
) {
super(sourceName, context, null);
assert defaultWorkspaceName != null;
assert availableWorkspaces != null;
assert rootNodeUuid != null;
+ assert customPropertiesFactory != null;
this.availableWorkspaces = availableWorkspaces;
this.creatingWorkspacesAllowed = creatingWorkspacesAllowed;
this.defaultNamespaceUri =
getExecutionContext().getNamespaceRegistry().getDefaultNamespaceUri();
@@ -165,6 +142,7 @@
this.defaultWorkspaceName = defaultWorkspaceName;
this.updatesAllowed = updatesAllowed;
this.mimeTypeDetector = context.getMimeTypeDetector();
+ this.customPropertiesFactory = customPropertiesFactory;
if (workspaceRootPath != null) {
this.workspaceRootPath = new File(workspaceRootPath);
@@ -279,24 +257,17 @@
return;
}
// Generate the properties for this File object ...
- DateTimeFactory dateFactory =
getExecutionContext().getValueFactories().getDateFactory();
+ final ExecutionContext context = getExecutionContext();
+ final DateTimeFactory dateFactory =
context.getValueFactories().getDateFactory();
// Note that we don't have 'created' timestamps, just last modified,
so we'll have to use them
if (file.isDirectory()) {
// Add properties for the directory ...
+ request.addProperties(customPropertiesFactory.getDirectoryProperties(context,
location, file));
request.addProperty(factory.create(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FOLDER));
request.addProperty(factory.create(JcrLexicon.CREATED,
dateFactory.create(file.lastModified())));
-
} else {
// It is a file, but ...
if (path.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
- // The request is to get properties of the "jcr:content" child
node ...
- // ... 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")));
-
// Discover the mime type ...
String mimeType = null;
InputStream contents = null;
@@ -319,11 +290,25 @@
}
}
+ // First add any custom properties ...
+
request.addProperties(customPropertiesFactory.getResourceProperties(context, location,
file, mimeType));
+
+ // The request is to get properties of the "jcr:content" child
node ...
+ // ... 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")));
+
// Now put the file's content into the "jcr:data" property
...
- BinaryFactory binaryFactory =
getExecutionContext().getValueFactories().getBinaryFactory();
+ BinaryFactory binaryFactory =
context.getValueFactories().getBinaryFactory();
request.addProperty(factory.create(JcrLexicon.DATA,
binaryFactory.create(file)));
} else {
+ // First add any custom properties ...
+ request.addProperties(customPropertiesFactory.getFileProperties(context,
location, file));
+
// The request is to get properties for the node representing the file
request.addProperty(factory.create(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE));
request.addProperty(factory.create(JcrLexicon.CREATED,
dateFactory.create(file.lastModified())));
@@ -367,8 +352,9 @@
Property primaryTypeProp = properties.get(JcrLexicon.PRIMARY_TYPE);
Name primaryType = primaryTypeProp == null ? null :
nameFactory().create(primaryTypeProp.getFirstValue());
+ Path newPath = pathFactory().create(parentPath, request.named());
+ Location actualLocation = Location.create(newPath);
if (JcrNtLexicon.FILE.equals(primaryType)) {
- ensureValidProperties(request.properties(),
ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
// The FILE node is represented by the existence of the file
if (!parent.canWrite()) {
@@ -410,8 +396,12 @@
ioe.getMessage()), ioe));
return;
}
+ customPropertiesFactory.recordFileProperties(getExecutionContext(),
+ getSourceName(),
+ actualLocation,
+ newFile,
+ properties);
} else if (JcrNtLexicon.RESOURCE.equals(primaryType) ||
DnaLexicon.RESOURCE.equals(primaryType)) {
- ensureValidProperties(request.properties(),
ALLOWABLE_PROPERTIES_FOR_CONTENT);
if (!JcrLexicon.CONTENT.equals(request.named())) {
I18n msg = FileSystemI18n.invalidNameForResource;
String nodeName = request.named().getString(registry);
@@ -526,9 +516,13 @@
}
}
}
+ customPropertiesFactory.recordResourceProperties(getExecutionContext(),
+ getSourceName(),
+ actualLocation,
+ newFile,
+ properties);
} else if (JcrNtLexicon.FOLDER.equals(primaryType) || primaryType == null) {
- ensureValidProperties(request.properties(),
ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
ensureValidPathLength(newFile);
if (!newFile.mkdir()) {
@@ -541,6 +535,11 @@
primaryType ==
null ? "null" : primaryType.getString(registry))));
return;
}
+ customPropertiesFactory.recordDirectoryProperties(getExecutionContext(),
+ getSourceName(),
+ actualLocation,
+ newFile,
+ properties);
} else {
// Set error and return
I18n msg = FileSystemI18n.unsupportedPrimaryType;
@@ -551,8 +550,7 @@
return;
}
- Path newPath = pathFactory().create(parentPath, request.named());
- request.setActualLocationOfNode(Location.create(newPath));
+ request.setActualLocationOfNode(actualLocation);
}
/**
@@ -564,8 +562,9 @@
public void process( UpdatePropertiesRequest request ) {
if (!updatesAllowed(request)) return;
+ Path path = request.on().getPath();
File workspace = getWorkspaceDirectory(request.inWorkspace());
- File target = getExistingFileFor(workspace, request.on().getPath(), request.on(),
request);
+ File target = getExistingFileFor(workspace, path, request.on(), request);
if (!target.exists()) {
// getExistingFile fills in the PathNotFoundException for non-existent files
@@ -573,13 +572,31 @@
return;
}
+ Location location = request.on();
if (target.isFile()) {
- ensureValidProperties(request.properties().values(),
ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
+ if (path.endsWith(JcrLexicon.CONTENT)) {
+ customPropertiesFactory.recordResourceProperties(getExecutionContext(),
+ getSourceName(),
+ location,
+ target,
+ request.properties());
+ } else {
+ customPropertiesFactory.recordFileProperties(getExecutionContext(),
+ getSourceName(),
+ location,
+ target,
+ request.properties());
+ }
} else {
- ensureValidProperties(request.properties().values(),
ALLOWABLE_PROPERTIES_FOR_CONTENT);
+ assert target.isDirectory();
+ customPropertiesFactory.recordDirectoryProperties(getExecutionContext(),
+ getSourceName(),
+ location,
+ target,
+ request.properties());
}
- request.setActualLocationOfNode(request.on());
+ request.setActualLocationOfNode(location);
}
/**
@@ -996,32 +1013,6 @@
return getExecutionContext().getValueFactories().getUuidFactory();
}
- /**
- * Checks that the collection of {@code properties} only contains properties with
allowable names.
- *
- * @param properties
- * @param validPropertyNames
- * @throws RepositorySourceException if {@code properties} contains a
- * @see #ALLOWABLE_PROPERTIES_FOR_CONTENT
- * @see #ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER
- */
- protected void ensureValidProperties( Collection<Property> properties,
- Set<Name> validPropertyNames ) {
- List<String> invalidNames = new LinkedList<String>();
- NamespaceRegistry registry = getExecutionContext().getNamespaceRegistry();
-
- for (Property property : properties) {
- if (!validPropertyNames.contains(property.getName())) {
- invalidNames.add(property.getName().getString(registry));
- }
- }
-
- if (!invalidNames.isEmpty()) {
- throw new RepositorySourceException(this.getSourceName(),
-
FileSystemI18n.invalidPropertyNames.text(invalidNames.toString()));
- }
- }
-
protected void ensureValidPathLength( File root ) {
ensureValidPathLength(root, 0);
}
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-11-17
18:47:00 UTC (rev 1324)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemSource.java 2009-11-17
21:09:49 UTC (rev 1325)
@@ -24,16 +24,28 @@
package org.jboss.dna.connector.filesystem;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Hashtable;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
+import javax.naming.BinaryRefAddr;
import javax.naming.Context;
import javax.naming.RefAddr;
import javax.naming.Reference;
@@ -45,12 +57,19 @@
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.DnaIntLexicon;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.JcrLexicon;
+import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.cache.CachePolicy;
import org.jboss.dna.graph.connector.RepositoryConnection;
import org.jboss.dna.graph.connector.RepositoryContext;
import org.jboss.dna.graph.connector.RepositorySource;
import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.NamespaceRegistry;
+import org.jboss.dna.graph.property.Property;
/**
* The {@link RepositorySource} for the connector that exposes an area of the local file
system as content in a repository. This
@@ -61,6 +80,12 @@
public class FileSystemSource implements RepositorySource, ObjectFactory {
/**
+ * An immutable {@link CustomPropertiesFactory} implementation that is used by
default when none is provided. Note that this
+ * implementation does restrict the properties that can be placed on file, folder and
resource nodes.
+ */
+ protected static CustomPropertiesFactory DEFAULT_PROPERTIES_FACTORY = new
StandardPropertiesFactory();
+
+ /**
* The first serialized version of this source. Version {@value} .
*/
private static final long serialVersionUID = 1L;
@@ -79,7 +104,7 @@
protected static final String ALLOW_CREATING_WORKSPACES =
"allowCreatingWorkspaces";
protected static final String MAX_PATH_LENGTH = "maxPathLength";
protected static final String EXCLUSION_PATTERN = "exclusionPattern";
- protected static final String ALLOW_UPDATES = "allowUpdates";
+ protected static final String CUSTOM_PROPERTY_FACTORY =
"customPropertyFactory";
/**
* This source supports events.
@@ -125,6 +150,7 @@
SUPPORTS_REFERENCES);
private transient CachePolicy cachePolicy;
private transient Map<String, File> availableWorkspaces;
+ private volatile CustomPropertiesFactory customPropertiesFactory;
/**
*
@@ -404,6 +430,24 @@
}
/**
+ * Get the factory that is used to create custom properties on "nt:folder",
"nt:file", and "nt:resource" nodes.
+ *
+ * @return the factory, or null if no custom properties are to be created
+ */
+ public synchronized CustomPropertiesFactory getCustomPropertiesFactory() {
+ return customPropertiesFactory;
+ }
+
+ /**
+ * Set the factory that is used to create custom properties on "nt:folder",
"nt:file", and "nt:resource" nodes.
+ *
+ * @param customPropertiesFactory the factory reference, or null if no custom
properties will be created
+ */
+ public synchronized void setCustomPropertiesFactory( CustomPropertiesFactory
customPropertiesFactory ) {
+ this.customPropertiesFactory = customPropertiesFactory;
+ }
+
+ /**
* {@inheritDoc}
*
* @see
org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext)
@@ -435,6 +479,18 @@
if (workspaceNames != null && workspaceNames.length != 0) {
ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES,
StringUtil.combineLines(workspaceNames)));
}
+ if (getCustomPropertiesFactory() != null) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CustomPropertiesFactory factory = getCustomPropertiesFactory();
+ try {
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(factory);
+ ref.add(new BinaryRefAddr(CUSTOM_PROPERTY_FACTORY, baos.toByteArray()));
+ } catch (IOException e) {
+ I18n msg = FileSystemI18n.errorSerializingCustomPropertiesFactory;
+ throw new RepositorySourceException(getName(),
msg.text(factory.getClass().getName(), getName()), e);
+ }
+ }
return ref;
}
@@ -446,7 +502,7 @@
Context nameCtx,
Hashtable<?, ?> environment ) throws Exception
{
if (obj instanceof Reference) {
- Map<String, String> values = new HashMap<String, String>();
+ Map<String, Object> values = new HashMap<String, Object>();
Reference ref = (Reference)obj;
Enumeration<?> en = ref.getAll();
while (en.hasMoreElements()) {
@@ -455,17 +511,28 @@
String key = subref.getType();
Object value = subref.getContent();
if (value != null) values.put(key, value.toString());
+ } else if (subref instanceof BinaryRefAddr) {
+ String key = subref.getType();
+ Object value = subref.getContent();
+ if (value instanceof byte[]) {
+ // Deserialize ...
+ ByteArrayInputStream bais = new
ByteArrayInputStream((byte[])value);
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ value = ois.readObject();
+ values.put(key, value);
+ }
}
}
- String sourceName = values.get(SOURCE_NAME);
- String cacheTtlInMillis = values.get(CACHE_TIME_TO_LIVE_IN_MILLISECONDS);
- String retryLimit = values.get(RETRY_LIMIT);
- String defaultWorkspace = values.get(DEFAULT_WORKSPACE);
- String createWorkspaces = values.get(ALLOW_CREATING_WORKSPACES);
- String exclusionPattern = values.get(DEFAULT_EXCLUSION_PATTERN);
- String maxPathLength = values.get(DEFAULT_MAX_PATH_LENGTH);
+ String sourceName = (String)values.get(SOURCE_NAME);
+ String cacheTtlInMillis =
(String)values.get(CACHE_TIME_TO_LIVE_IN_MILLISECONDS);
+ String retryLimit = (String)values.get(RETRY_LIMIT);
+ String defaultWorkspace = (String)values.get(DEFAULT_WORKSPACE);
+ String createWorkspaces = (String)values.get(ALLOW_CREATING_WORKSPACES);
+ String exclusionPattern = (String)values.get(EXCLUSION_PATTERN);
+ String maxPathLength = (String)values.get(DEFAULT_MAX_PATH_LENGTH);
+ Object customPropertiesFactory = values.get(CUSTOM_PROPERTY_FACTORY);
- String combinedWorkspaceNames = values.get(PREDEFINED_WORKSPACE_NAMES);
+ String combinedWorkspaceNames =
(String)values.get(PREDEFINED_WORKSPACE_NAMES);
String[] workspaceNames = null;
if (combinedWorkspaceNames != null) {
List<String> paths =
StringUtil.splitLines(combinedWorkspaceNames);
@@ -482,6 +549,7 @@
if (workspaceNames != null && workspaceNames.length != 0)
source.setPredefinedWorkspaceNames(workspaceNames);
if (exclusionPattern != null) source.setExclusionPattern(exclusionPattern);
if (maxPathLength != null)
source.setMaxPathLength(Integer.valueOf(maxPathLength));
+ if (customPropertiesFactory != null)
source.setCustomPropertiesFactory((CustomPropertiesFactory)customPropertiesFactory);
return source;
}
return null;
@@ -562,9 +630,10 @@
};
}
+ CustomPropertiesFactory propFactory = customPropertiesFactory != null ?
customPropertiesFactory : DEFAULT_PROPERTIES_FACTORY;
return new FileSystemConnection(name, defaultWorkspaceName, availableWorkspaces,
isCreatingWorkspacesAllowed(),
cachePolicy, rootNodeUuid, workspaceRootPath,
maxPathLength, filenameFilter,
- getSupportsUpdates());
+ getSupportsUpdates(), propFactory);
}
/**
@@ -576,6 +645,127 @@
this.availableWorkspaces = null;
}
+ protected static class StandardPropertiesFactory implements CustomPropertiesFactory
{
+ private static final long serialVersionUID = 1L;
+ private final Collection<Property> empty = Collections.emptyList();
+
+ /**
+ * Only certain properties are tolerated when writing content (dna:resource or
jcr:resource) nodes. These properties are
+ * implicitly stored (primary type, data) or silently ignored (encoded, mimetype,
last modified). The silently ignored
+ * properties must be accepted to stay compatible with the JCR specification.
+ */
+ private final Set<Name> ALLOWABLE_PROPERTIES_FOR_CONTENT =
Collections.unmodifiableSet(new HashSet<Name>(
+
Arrays.asList(new Name[] {
+
JcrLexicon.PRIMARY_TYPE,
+
JcrLexicon.DATA,
+
JcrLexicon.ENCODED,
+
JcrLexicon.MIMETYPE,
+
JcrLexicon.LAST_MODIFIED,
+
JcrLexicon.UUID,
+
DnaIntLexicon.NODE_DEFINITON})));
+ /**
+ * Only certain properties are tolerated when writing files (nt:file) or folders
(nt:folder) nodes. These properties are
+ * implicitly stored in the file or folder (primary type, created).
+ */
+ private final Set<Name> ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER =
Collections.unmodifiableSet(new HashSet<Name>(
+
Arrays.asList(new Name[] {
+
JcrLexicon.PRIMARY_TYPE,
+
JcrLexicon.CREATED,
+
JcrLexicon.UUID,
+
DnaIntLexicon.NODE_DEFINITON})));
+
+ public Collection<Property> getDirectoryProperties( ExecutionContext
context,
+ Location location,
+ File directory ) {
+ return empty;
+ }
+
+ public Collection<Property> getFileProperties( ExecutionContext context,
+ Location location,
+ File file ) {
+ return empty;
+ }
+
+ public Collection<Property> getResourceProperties( ExecutionContext
context,
+ Location location,
+ File file,
+ String mimeType ) {
+ return empty;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.connector.filesystem.CustomPropertiesFactory#recordDirectoryProperties(org.jboss.dna.graph.ExecutionContext,
+ * java.lang.String, org.jboss.dna.graph.Location, java.io.File,
java.util.Map)
+ */
+ public void recordDirectoryProperties( ExecutionContext context,
+ String sourceName,
+ Location location,
+ File file,
+ Map<Name, Property> properties )
throws RepositorySourceException {
+ ensureValidProperties(context, sourceName, properties.values(),
ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.connector.filesystem.CustomPropertiesFactory#recordFileProperties(org.jboss.dna.graph.ExecutionContext,
+ * java.lang.String, org.jboss.dna.graph.Location, java.io.File,
java.util.Map)
+ */
+ public void recordFileProperties( ExecutionContext context,
+ String sourceName,
+ Location location,
+ File file,
+ Map<Name, Property> properties ) throws
RepositorySourceException {
+ ensureValidProperties(context, sourceName, properties.values(),
ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.connector.filesystem.CustomPropertiesFactory#recordResourceProperties(org.jboss.dna.graph.ExecutionContext,
+ * java.lang.String, org.jboss.dna.graph.Location, java.io.File,
java.util.Map)
+ */
+ public void recordResourceProperties( ExecutionContext context,
+ String sourceName,
+ Location location,
+ File file,
+ Map<Name, Property> properties )
throws RepositorySourceException {
+ ensureValidProperties(context, sourceName, properties.values(),
ALLOWABLE_PROPERTIES_FOR_CONTENT);
+ }
+
+ /**
+ * Checks that the collection of {@code properties} only contains properties with
allowable names.
+ *
+ * @param context
+ * @param sourceName
+ * @param properties
+ * @param validPropertyNames
+ * @throws RepositorySourceException if {@code properties} contains a
+ * @see #ALLOWABLE_PROPERTIES_FOR_CONTENT
+ * @see #ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER
+ */
+ protected void ensureValidProperties( ExecutionContext context,
+ String sourceName,
+ Collection<Property> properties,
+ Set<Name> validPropertyNames ) {
+ List<String> invalidNames = new LinkedList<String>();
+ NamespaceRegistry registry = context.getNamespaceRegistry();
+
+ for (Property property : properties) {
+ if (!validPropertyNames.contains(property.getName())) {
+ invalidNames.add(property.getName().getString(registry));
+ }
+ }
+
+ if (!invalidNames.isEmpty()) {
+ throw new RepositorySourceException(sourceName,
FileSystemI18n.invalidPropertyNames.text(invalidNames.toString()));
+ }
+ }
+
+ }
+
@Immutable
/*package*/class FileSystemCachePolicy implements CachePolicy {
private static final long serialVersionUID = 1L;
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-11-17
18:47:00 UTC (rev 1324)
+++
trunk/extensions/dna-connector-filesystem/src/main/resources/org/jboss/dna/connector/filesystem/FileSystemI18n.properties 2009-11-17
21:09:49 UTC (rev 1325)
@@ -39,6 +39,7 @@
onlyTheDefaultNamespaceIsAllowed = {0} requires node names use the default namespace:
{1}
sourceIsReadOnly = The source "{0}" does not allow updates
pathIsReadOnly = The path "{0}" in workspace "{1}" in {2} cannot be
written to. See java.io.File\#canWrite().
+errorSerializingCustomPropertiesFactory = Error serializing a {0} instance owned by the
{1} FileSystemSource
# Writable tests
parentIsReadOnly = The parent node at path "{0}" in workspace "{1}"
in {2} cannot be written to. See java.io.File\#canWrite().