Author: bcarothers
Date: 2009-08-05 10:45:38 -0400 (Wed, 05 Aug 2009)
New Revision: 1144
Added:
trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorWritableTest.java
Removed:
trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNotWritableTest.java
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/DnaIntLexicon.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml
trunk/dna-integration-tests/src/test/resources/tck/filesystem/repositoryOverlay.properties
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaIntLexicon.java
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-269 Add update support to the File System connector
Committed patch that implements a writable file system connector. The write capabilities
have some limits. Node ordering and same-name siblings are not supported. Namespaces for
paths (other than the default, empty namespace) are not permitted. Primary types other
than nt:folder, nt:file, dna:resource, and jcr:resource are not permitted. Files with
primary type jcr:resource are effectively converted to files of type dna:resource since
references are not permitted and nt:resource has mix:referenceable as a supertype.
The concept of a maximum path length (FileSystemSource#maxPathLength) was added to limit
the maximum length of the file path of any node that was created. This came about after I
wrote a deeply nested graph to my local filesystem and then found out that Windows
won't let me delete extremely long paths. I defaulted this value to 255 characters,
but some research is needed to determine the correct length for Windows and other
operating systems.
Because of all of the limitations (no arbitrary properties, no namespaces, no node
ordering, no SNS), the canonical writable connector tests don't really pass, so I
added a bespoke test suite for the writable capabilities of the connector. I tried to make
it as inclusive as I could, but other test cases are always welcome.
Also added patch from DNA-499 (Thanks, Torkjel!)
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/DnaIntLexicon.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/DnaIntLexicon.java 2009-08-05
13:13:29 UTC (rev 1143)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/DnaIntLexicon.java 2009-08-05
14:45:38 UTC (rev 1144)
@@ -23,7 +23,10 @@
*/
package org.jboss.dna.graph;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.basic.BasicName;
+
/**
* A lexicon of internal and implementation-specific information
*/
@@ -33,4 +36,7 @@
public static final String URI =
"http://www.jboss.org/dna/internal/1.0";
public static final String PREFIX = "dnaint";
}
+
+ public static final Name NODE_DEFINITON = new BasicName(Namespace.URI,
"nodeDefinition");
+
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2009-08-05 13:13:29 UTC
(rev 1143)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2009-08-05 14:45:38 UTC
(rev 1144)
@@ -4225,6 +4225,15 @@
* @return the interface for additional requests or actions
*/
Next as( Name newName );
+
+ /**
+ * Finish the request by specifying the name of the new child node. This method
indicates that the child should be added
+ * as a new node with the given name at the end of the parents children
+ *
+ * @param newName the new name
+ * @return the interface for additional requests or actions
+ */
+ Next as( String newName );
}
/**
@@ -6473,8 +6482,13 @@
return source.submit(workspaceName, from, intoWorkspaceName,
into, name, null, removeExisting);
}
};
+
}
+ public Into<WithUuids<T>> as( final String name ) {
+ return
as(context.getValueFactories().getNameFactory().create(name));
+ }
+
public Into<WithUuids<T>> as( final Segment segment ) {
return new CloneTargetAction<T>(afterConjunction(), source) {
@Override
Modified:
trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml
===================================================================
---
trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml 2009-08-05
13:13:29 UTC (rev 1143)
+++
trunk/dna-integration-tests/src/test/resources/tck/filesystem/configRepository.xml 2009-08-05
14:45:38 UTC (rev 1144)
@@ -41,6 +41,8 @@
dna:defaultWorkspaceName="defaultWorkspace"
dna:predefinedWorkspaceNames="otherWorkspace"
dna:creatingWorkspacesAllowed="false"
+ dna:updatesAllowed="true"
+ dna:exclusionPattern="\.svn"
/>
<!--
<dna:source jcr:name="Store"
dna:classname="org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource"
/>
Modified:
trunk/dna-integration-tests/src/test/resources/tck/filesystem/repositoryOverlay.properties
===================================================================
---
trunk/dna-integration-tests/src/test/resources/tck/filesystem/repositoryOverlay.properties 2009-08-05
13:13:29 UTC (rev 1143)
+++
trunk/dna-integration-tests/src/test/resources/tck/filesystem/repositoryOverlay.properties 2009-08-05
14:45:38 UTC (rev 1144)
@@ -3,6 +3,8 @@
javax.jcr.tck.dnaSkipImport=true
-javax.jcr.tck.nodetype=nt\:file
-javax.jcr.tck.nodetype2=nt\:file
+javax.jcr.tck.nodetype=nt\:folder
+javax.jcr.tck.nodetype2=nt\:folder
+javax.jcr.tck.propertyname1=jcr:created
+
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaIntLexicon.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaIntLexicon.java 2009-08-05 13:13:29
UTC (rev 1143)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaIntLexicon.java 2009-08-05 14:45:38
UTC (rev 1144)
@@ -30,6 +30,5 @@
* A lexicon of internal and implementation-specific information
*/
public class DnaIntLexicon extends org.jboss.dna.graph.DnaIntLexicon {
- public static final Name NODE_DEFINITON = new BasicName(Namespace.URI,
"nodeDefinition");
public static final Name MULTI_VALUED_PROPERTIES = new BasicName(Namespace.URI,
"multiValuedProperties");
}
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-08-05
13:13:29 UTC (rev 1143)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemConnection.java 2009-08-05
14:45:38 UTC (rev 1144)
@@ -51,6 +51,7 @@
private final boolean creatingWorkspacesAllowed;
private final FilenameFilter filenameFilter;
private final UUID rootNodeUuid;
+ private final int maxPathLength;
private final String workspaceRootPath;
private final boolean updatesAllowed;
@@ -61,6 +62,7 @@
CachePolicy cachePolicy,
UUID rootNodeUuid,
String workspaceRootPath,
+ int maxPathLength,
FilenameFilter filenameFilter,
boolean updatesAllowed ) {
assert sourceName != null;
@@ -74,6 +76,7 @@
this.cachePolicy = cachePolicy;
this.rootNodeUuid = rootNodeUuid;
this.workspaceRootPath = workspaceRootPath;
+ this.maxPathLength = maxPathLength;
this.filenameFilter = filenameFilter;
this.updatesAllowed = updatesAllowed;
}
@@ -125,7 +128,7 @@
Request request ) throws RepositorySourceException {
RequestProcessor proc = new FileSystemRequestProcessor(sourceName,
defaultWorkspaceName, availableWorkspaces,
creatingWorkspacesAllowed,
rootNodeUuid, workspaceRootPath,
- context, filenameFilter,
updatesAllowed);
+ maxPathLength, 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-08-05
13:13:29 UTC (rev 1143)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemI18n.java 2009-08-05
14:45:38 UTC (rev 1144)
@@ -46,9 +46,27 @@
public static I18n propertyIsRequired;
public static I18n locationInRequestMustHavePath;
public static I18n sameNameSiblingsAreNotAllowed;
+ public static I18n nodeOrderingNotSupported;
public static I18n onlyTheDefaultNamespaceIsAllowed;
public static I18n sourceIsReadOnly;
+ public static I18n pathIsReadOnly;
public static I18n unableToCreateWorkspaces;
+
+ // Writable messages
+ public static I18n parentIsReadOnly;
+ public static I18n fileAlreadyExists;
+ public static I18n couldNotCreateFile;
+ public static I18n unsupportedPrimaryType;
+ public static I18n invalidNameForResource;
+ public static I18n invalidPathForResource;
+ public static I18n invalidPropertyNames;
+ public static I18n couldNotWriteData;
+ public static I18n couldNotUpdateData;
+ public static I18n missingRequiredProperty;
+ public static I18n deleteFailed;
+ public static I18n copyFailed;
+ public static I18n getCanonicalPathFailed;
+ public static I18n maxPathLengthExceeded;
static {
try {
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-08-05
13:13:29 UTC (rev 1143)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemRequestProcessor.java 2009-08-05
14:45:38 UTC (rev 1144)
@@ -26,30 +26,44 @@
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
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;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Set;
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;
import org.jboss.dna.graph.JcrNtLexicon;
import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.NodeConflictBehavior;
import org.jboss.dna.graph.connector.RepositorySourceException;
import org.jboss.dna.graph.mimetype.MimeTypeDetector;
+import org.jboss.dna.graph.property.Binary;
import org.jboss.dna.graph.property.BinaryFactory;
import org.jboss.dna.graph.property.DateTimeFactory;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.NameFactory;
+import org.jboss.dna.graph.property.NamespaceRegistry;
import org.jboss.dna.graph.property.Path;
import org.jboss.dna.graph.property.PathFactory;
import org.jboss.dna.graph.property.PathNotFoundException;
+import org.jboss.dna.graph.property.Property;
import org.jboss.dna.graph.property.PropertyFactory;
+import org.jboss.dna.graph.property.UuidFactory;
+import org.jboss.dna.graph.property.Path.Segment;
import org.jboss.dna.graph.request.CloneBranchRequest;
import org.jboss.dna.graph.request.CloneWorkspaceRequest;
import org.jboss.dna.graph.request.CopyBranchRequest;
@@ -79,11 +93,37 @@
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;
private final String defaultWorkspaceName;
private final File workspaceRootPath;
+ private final int maxPathLength;
private final FilenameFilter filenameFilter;
private final boolean updatesAllowed;
private final MimeTypeDetector mimeTypeDetector;
@@ -99,6 +139,7 @@
* 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 maxPathLength the maximum absolute path length supported by this processor
* @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
@@ -109,6 +150,7 @@
boolean creatingWorkspacesAllowed,
UUID rootNodeUuid,
String workspaceRootPath,
+ int maxPathLength,
ExecutionContext context,
FilenameFilter filenameFilter,
boolean updatesAllowed ) {
@@ -120,6 +162,7 @@
this.creatingWorkspacesAllowed = creatingWorkspacesAllowed;
this.defaultNamespaceUri =
getExecutionContext().getNamespaceRegistry().getDefaultNamespaceUri();
this.rootNodeUuid = rootNodeUuid;
+ this.maxPathLength = maxPathLength;
this.filenameFilter = filenameFilter;
this.defaultWorkspaceName = defaultWorkspaceName;
this.updatesAllowed = updatesAllowed;
@@ -178,8 +221,9 @@
// Create a Location for each file and directory contained by the parent
directory ...
PathFactory pathFactory = pathFactory();
NameFactory nameFactory = nameFactory();
- for (String localName : parent.list(filenameFilter)) {
- Name childName = nameFactory.create(defaultNamespaceUri, localName);
+ for (File child : parent.listFiles(filenameFilter)) {
+ if (!child.canRead()) continue;
+ Name childName = nameFactory.create(defaultNamespaceUri,
child.getName());
Path childPath = pathFactory.create(parentPath, childName);
request.addChild(Location.create(childPath));
}
@@ -299,7 +343,213 @@
*/
@Override
public void process( CreateNodeRequest request ) {
- updatesAllowed(request);
+ if (!updatesAllowed(request)) return;
+
+ Path parentPath = getPathFor(request.under(), request);
+ if (parentPath == null) return;
+
+ File workspace = getWorkspaceDirectory(request.inWorkspace());
+ assert workspace != null;
+
+ File parent = getExistingFileFor(workspace, parentPath, request.under(),
request);
+ assert parent != null;
+
+ NamespaceRegistry registry = getExecutionContext().getNamespaceRegistry();
+ String newName = request.named().getString(registry);
+ File newFile = new File(parent, newName);
+
+ Map<Name, Property> properties = new HashMap<Name,
Property>(request.properties().size());
+ for (Property property : request.properties()) {
+ properties.put(property.getName(), property);
+ }
+
+ Property primaryTypeProp = properties.get(JcrLexicon.PRIMARY_TYPE);
+ Name primaryType = primaryTypeProp == null ? null :
nameFactory().create(primaryTypeProp.getFirstValue());
+
+ 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()) {
+ I18n msg = FileSystemI18n.parentIsReadOnly;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName())));
+ return;
+ }
+
+ try {
+ ensureValidPathLength(newFile);
+ boolean skipWrite = false;
+
+ if (newFile.exists()) {
+ if (request.conflictBehavior().equals(NodeConflictBehavior.APPEND))
{
+ I18n msg = FileSystemI18n.sameNameSiblingsAreNotAllowed;
+ throw new InvalidRequestException(msg.text(this.getSourceName(),
newName));
+ } else if
(request.conflictBehavior().equals(NodeConflictBehavior.DO_NOT_REPLACE)) {
+ skipWrite = true;
+ }
+ }
+
+ // Don't try to write if the node conflict behavior is
DO_NOT_REPLACE
+ if (!skipWrite) {
+ if (!newFile.createNewFile()) {
+ I18n msg = FileSystemI18n.fileAlreadyExists;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName())));
+ return;
+ }
+ }
+ } catch (IOException ioe) {
+ I18n msg = FileSystemI18n.couldNotCreateFile;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName(),
+
ioe.getMessage()), ioe));
+ return;
+ }
+ } 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);
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName(),
+
nodeName)));
+ return;
+ }
+
+ if (!parent.isFile()) {
+ I18n msg = FileSystemI18n.invalidPathForResource;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName())));
+ return;
+ }
+
+ if (!parent.canWrite()) {
+ I18n msg = FileSystemI18n.parentIsReadOnly;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName())));
+ return;
+ }
+
+ boolean skipWrite = false;
+
+ if (parent.exists()) {
+ if (request.conflictBehavior().equals(NodeConflictBehavior.APPEND)) {
+ I18n msg = FileSystemI18n.sameNameSiblingsAreNotAllowed;
+ throw new InvalidRequestException(msg.text(this.getSourceName(),
newName));
+ } else if
(request.conflictBehavior().equals(NodeConflictBehavior.DO_NOT_REPLACE)) {
+ // The content node logically maps to the file contents. If there are
file contents, don't replace them.
+ FileInputStream checkForContents = null;
+ try {
+ checkForContents = new FileInputStream(parent);
+ if (-1 != checkForContents.read()) skipWrite = true;
+
+ } catch (IOException ignore) {
+
+ } finally {
+ try {
+ checkForContents.close();
+ } catch (Exception ignore) {
+ }
+ }
+ skipWrite = true;
+ }
+ }
+
+ if (!skipWrite) {
+ // Copy over data into a temp file, then move it to the correct location
+ FileOutputStream fos = null;
+ try {
+ File temp = File.createTempFile("dna", null);
+ fos = new FileOutputStream(temp);
+
+ Property dataProp = properties.get(JcrLexicon.DATA);
+ if (dataProp == null) {
+ I18n msg = FileSystemI18n.missingRequiredProperty;
+ String dataPropName = JcrLexicon.DATA.getString(registry);
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName(),
+
dataPropName)));
+ return;
+ }
+
+ BinaryFactory binaryFactory =
getExecutionContext().getValueFactories().getBinaryFactory();
+ Binary binary =
binaryFactory.create(properties.get(JcrLexicon.DATA).getFirstValue());
+ InputStream is = binary.getStream();
+
+ final int BUFF_SIZE = 2 << 15;
+ byte[] buff = new byte[BUFF_SIZE];
+ int len;
+ while (-1 != (len = is.read(buff, 0, BUFF_SIZE))) {
+ fos.write(buff, 0, len);
+ }
+ fos.flush();
+ fos.close();
+ is.close();
+
+ if (!FileUtil.delete(parent)) {
+ I18n msg = FileSystemI18n.deleteFailed;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName())));
+ return;
+ }
+
+ if (!temp.renameTo(parent)) {
+ I18n msg = FileSystemI18n.couldNotUpdateData;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName())));
+ return;
+
+ }
+ } catch (IOException ioe) {
+ I18n msg = FileSystemI18n.couldNotWriteData;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName(),
+
ioe.getMessage()), ioe));
+ return;
+
+ } finally {
+ try {
+ if (fos != null) fos.close();
+ } catch (Exception ex) {
+ }
+ }
+ }
+
+ } else if (JcrNtLexicon.FOLDER.equals(primaryType) || primaryType == null) {
+ ensureValidProperties(request.properties(),
ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
+ ensureValidPathLength(newFile);
+
+ if (!newFile.mkdir()) {
+ I18n msg = FileSystemI18n.couldNotCreateFile;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(parent.getPath(),
+
request.inWorkspace(),
+
getSourceName(),
+
primaryType.getString(registry))));
+ return;
+ }
+ } else {
+ // Set error and return
+ I18n msg = FileSystemI18n.unsupportedPrimaryType;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(primaryType.getString(registry),
+
parent.getPath(),
+
request.inWorkspace(),
+
getSourceName())));
+ return;
+ }
+
+ Path newPath = pathFactory().create(parentPath, request.named());
+ request.setActualLocationOfNode(Location.create(newPath));
}
/**
@@ -309,7 +559,24 @@
*/
@Override
public void process( UpdatePropertiesRequest request ) {
- updatesAllowed(request);
+ if (!updatesAllowed(request)) return;
+
+ File workspace = getWorkspaceDirectory(request.inWorkspace());
+ File target = getExistingFileFor(workspace, request.on().getPath(), request.on(),
request);
+
+ if (!target.exists()) {
+ // getExistingFile fills in the PathNotFoundException for non-existent files
+ assert request.hasError();
+ return;
+ }
+
+ if (target.isFile()) {
+ ensureValidProperties(request.properties().values(),
ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
+ } else {
+ ensureValidProperties(request.properties().values(),
ALLOWABLE_PROPERTIES_FOR_CONTENT);
+ }
+
+ request.setActualLocationOfNode(request.on());
}
/**
@@ -319,7 +586,96 @@
*/
@Override
public void process( CopyBranchRequest request ) {
- updatesAllowed(request);
+ if (!updatesAllowed(request)) return;
+
+ File fromWorkspace = getWorkspaceDirectory(request.fromWorkspace());
+ File intoWorkspace = getWorkspaceDirectory(request.intoWorkspace());
+ Path fromPath = getPathFor(request.from(), request);
+ if (fromPath == null) return;
+ File from = getExistingFileFor(fromWorkspace, fromPath, request.from(),
request);
+
+ Path intoPath = getPathFor(request.into(), request);
+ if (intoPath == null) return;
+ File into = getExistingFileFor(intoWorkspace, intoPath, request.into(),
request);
+
+ NamespaceRegistry registry = getExecutionContext().getNamespaceRegistry();
+ Name desiredName = request.desiredName();
+ String fileName = desiredName != null ? desiredName.getString(registry) :
fromPath.getLastSegment().getString(registry);
+ File target = new File(into, fileName);
+ File tempInto = null;
+
+ Location actualFrom = null;
+ Location actualTo = null;
+ try {
+ actualFrom = locationFor(fromWorkspace, from);
+ actualTo = locationFor(intoWorkspace, target);
+ } catch (IOException ioe) {
+ throw new RepositorySourceException(this.getSourceName(),
FileSystemI18n.getCanonicalPathFailed.text(), ioe);
+ }
+
+ try {
+ int pathLenDelta = into.getCanonicalPath().length() -
from.getCanonicalFile().getParent().length();
+ if (pathLenDelta > 0) {
+ ensureValidPathLength(from, pathLenDelta);
+ }
+ } catch (IOException ioe) {
+ throw new RepositorySourceException(this.getSourceName(),
FileSystemI18n.getCanonicalPathFailed.text(), ioe);
+ }
+
+ if (target.exists() && from.isFile()) {
+ try {
+ tempInto = File.createTempFile("dna", null, into);
+ } catch (IOException ioe) {
+ throw new RepositorySourceException(this.getSourceName(),
+
FileSystemI18n.couldNotCreateFile.text("temporary file",
+
request.intoWorkspace(),
+
getSourceName(),
+
ioe.getMessage()), ioe);
+ }
+
+ try {
+ FileUtil.copy(from, tempInto);
+ } catch (IOException ioe) {
+ FileUtil.delete(tempInto);
+ throw new RepositorySourceException(this.getSourceName(),
FileSystemI18n.copyFailed.text(from.getPath(),
+
request.fromWorkspace(),
+
tempInto.getPath(),
+
request.intoWorkspace(),
+
getSourceName()), ioe);
+ }
+
+ // If everything worked, delete whatever was there and rename
+ if (target.exists()) {
+ if (!FileUtil.delete(target)) {
+ I18n msg = FileSystemI18n.deleteFailed;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(target.getPath(),
+
request.intoWorkspace(),
+
getSourceName())));
+ FileUtil.delete(tempInto);
+ return;
+ }
+ }
+
+ if (!tempInto.renameTo(target)) {
+ I18n msg = FileSystemI18n.couldNotUpdateData;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(target.getPath(),
+
request.intoWorkspace(),
+
getSourceName())));
+ FileUtil.delete(tempInto);
+ return;
+ }
+ } else {
+ if (!from.renameTo(target)) {
+ I18n msg = FileSystemI18n.couldNotUpdateData;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(target.getPath(),
+
request.intoWorkspace(),
+
getSourceName())));
+ return;
+ }
+
+ }
+ request.setActualLocations(actualFrom, actualTo);
+
}
/**
@@ -329,7 +685,19 @@
*/
@Override
public void process( CloneBranchRequest request ) {
- updatesAllowed(request);
+ if (!updatesAllowed(request)) return;
+
+ CopyBranchRequest copy = new CopyBranchRequest(request.from(),
request.fromWorkspace(), request.into(),
+ request.intoWorkspace(),
request.desiredName());
+
+ process(copy);
+
+ if (copy.hasError()) {
+ request.setError(copy.getError());
+ return;
+ }
+
+ request.setActualLocations(copy.getActualLocationBefore(),
copy.getActualLocationAfter());
}
/**
@@ -339,7 +707,30 @@
*/
@Override
public void process( DeleteBranchRequest request ) {
- updatesAllowed(request);
+ if (!updatesAllowed(request)) return;
+
+ File workspace = getWorkspaceDirectory(request.inWorkspace());
+ Path targetPath = getPathFor(request.at(), request);
+ if (targetPath == null) return;
+
+ File target = getExistingFileFor(workspace, targetPath, request.at(), request);
+ Location actual = null;
+
+ try {
+ actual = locationFor(workspace, target);
+ } catch (IOException ioe) {
+ throw new RepositorySourceException(this.getSourceName(),
FileSystemI18n.getCanonicalPathFailed.text(), ioe);
+ }
+
+ if (!FileUtil.delete(target)) {
+ request.setError(new RepositorySourceException(this.getSourceName(),
+
FileSystemI18n.deleteFailed.text(target.getPath(),
+
request.inWorkspace(),
+
getSourceName())));
+ return;
+ }
+
+ request.setActualLocationOfNode(actual);
}
/**
@@ -349,7 +740,56 @@
*/
@Override
public void process( MoveBranchRequest request ) {
- updatesAllowed(request);
+ if (!updatesAllowed(request)) return;
+
+ /* This connector does not support node ordering */
+ if (request.before() != null) {
+ throw new
InvalidRequestException(FileSystemI18n.nodeOrderingNotSupported.text(this.getSourceName()));
+ }
+
+ File workspace = getWorkspaceDirectory(request.inWorkspace());
+ Path fromPath = getPathFor(request.from(), request);
+ if (fromPath == null) return;
+ File from = getExistingFileFor(workspace, fromPath, request.from(), request);
+
+ Path intoPath = getPathFor(request.into(), request);
+ if (intoPath == null) return;
+ File into = getExistingFileFor(workspace, intoPath, request.into(), request);
+
+ NamespaceRegistry registry = getExecutionContext().getNamespaceRegistry();
+ Name desiredName = request.desiredName();
+ String fileName = desiredName != null ? desiredName.getString(registry) :
fromPath.getLastSegment().getString(registry);
+ File target = new File(into, fileName);
+
+ Location actualFrom = null;
+ Location actualTo = null;
+ try {
+ actualFrom = locationFor(workspace, from);
+ actualTo = locationFor(workspace, target);
+ } catch (IOException ioe) {
+ request.setError(new RepositorySourceException(this.getSourceName(),
FileSystemI18n.getCanonicalPathFailed.text()));
+ return;
+ }
+
+ try {
+ int pathLenDelta = into.getCanonicalPath().length() -
from.getCanonicalFile().getParent().length();
+ if (pathLenDelta > 0) {
+ ensureValidPathLength(from, pathLenDelta);
+ }
+ } catch (IOException ioe) {
+ request.setError(new RepositorySourceException(this.getSourceName(),
FileSystemI18n.getCanonicalPathFailed.text()));
+ return;
+ }
+
+ if (!from.renameTo(target)) {
+ I18n msg = FileSystemI18n.couldNotUpdateData;
+ request.setError(new RepositorySourceException(getSourceName(),
+ msg.text(target.getPath(),
workspace, getSourceName())));
+ return;
+ }
+
+ request.setActualLocations(actualFrom, actualTo);
+
}
/**
@@ -359,12 +799,46 @@
*/
@Override
public void process( RenameNodeRequest request ) {
- if (updatesAllowed(request)) super.process(request);
+ if (!updatesAllowed(request)) return;
+
+ super.process(request);
}
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest)
+ */
+ @Override
+ public void process( CloneWorkspaceRequest request ) {
+ if (!updatesAllowed(request)) return;
+
+ CreateWorkspaceRequest create = new
CreateWorkspaceRequest(request.desiredNameOfTargetWorkspace(),
+
request.targetConflictBehavior());
+ process(create);
+
+ if (create.hasError()) {
+ request.setError(create.getError());
+ return;
+ }
+
+ File fromWorkspace = getWorkspaceDirectory(request.nameOfWorkspaceToBeCloned());
+ assert fromWorkspace != null;
+ File toWorkspace = getWorkspaceDirectory(create.getActualWorkspaceName());
+ assert toWorkspace != null;
+
+ try {
+ FileUtil.copy(fromWorkspace, toWorkspace);
+ request.setActualWorkspaceName(create.getActualWorkspaceName());
+ request.setActualRootLocation(Location.create(pathFactory().createRootPath(),
this.rootNodeUuid));
+ } catch (IOException ioe) {
+ throw new RepositorySourceException(this.getSourceName(), ioe.getMessage());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.VerifyWorkspaceRequest)
*/
@Override
@@ -442,16 +916,6 @@
/**
* {@inheritDoc}
*
- * @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest)
- */
- @Override
- public void process( CloneWorkspaceRequest request ) {
- updatesAllowed(request);
- }
-
- /**
- * {@inheritDoc}
- *
* @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateWorkspaceRequest)
*/
@Override
@@ -464,7 +928,7 @@
}
// This doesn't create the directory representing the workspace (it must
already exist), but it will add
// the workspace name to the available names ...
- File directory = new File(workspaceName);
+ File directory = getWorkspaceDirectory(workspaceName);
if (directory.exists() && directory.isDirectory() &&
directory.canRead()) {
request.setActualWorkspaceName(getCanonicalWorkspaceName(directory));
request.setActualRootLocation(Location.create(pathFactory().createRootPath()));
@@ -503,6 +967,19 @@
return !request.hasError();
}
+ private UUID uuidFor( Location location ) {
+ if (location.getUuid() != null) return location.getUuid();
+ if (!location.hasIdProperties()) return null;
+
+ for (Property idProperty : location.getIdProperties()) {
+ if (JcrLexicon.UUID.equals(idProperty.getName())) {
+ return uuidFactory().create(idProperty.getFirstValue());
+ }
+ }
+
+ return null;
+ }
+
protected NameFactory nameFactory() {
return getExecutionContext().getValueFactories().getNameFactory();
}
@@ -511,19 +988,105 @@
return getExecutionContext().getValueFactories().getPathFactory();
}
+ protected UuidFactory uuidFactory() {
+ 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);
+ }
+
+ /**
+ * Recursively checks if any of the files in the tree rooted at {@code root} would
exceed the {@link #maxPathLength maximum
+ * path length for the processor} if their paths were {@code delta} characters
longer. If any files would exceed this length,
+ * a {@link RepositorySourceException} is thrown.
+ *
+ * @param root the root of the tree to check; may be a file or directory but may not
be null
+ * @param delta the change in the length of the path to check. Used to preemptively
check whether moving a file or directory
+ * to a new path would violate path length rules
+ * @throws RepositorySourceException if any files in the tree rooted at {@code root}
would exceed this {@link #maxPathLength
+ * the maximum path length for this processor}
+ */
+ protected void ensureValidPathLength( File root,
+ int delta ) {
+ try {
+ int len = root.getCanonicalPath().length();
+ if (len > maxPathLength - delta) {
+ String msg =
FileSystemI18n.maxPathLengthExceeded.text(this.maxPathLength,
+
this.getSourceName(),
+
root.getCanonicalPath());
+ throw new RepositorySourceException(this.getSourceName(), msg);
+ }
+
+ if (root.isDirectory()) {
+ for (File child : root.listFiles(filenameFilter)) {
+ ensureValidPathLength(child, delta);
+ }
+
+ }
+ } catch (IOException ioe) {
+ throw new RepositorySourceException(this.getSourceName(),
FileSystemI18n.getCanonicalPathFailed.text(), ioe);
+ }
+ }
+
+ protected Location locationFor( File workspaceRoot,
+ File path ) throws IOException {
+ assert workspaceRoot != null;
+ assert path != null;
+ assert path.getCanonicalPath().startsWith(workspaceRoot.getCanonicalPath());
+
+ String relativePath =
path.getCanonicalPath().substring(workspaceRoot.getCanonicalPath().length());
+ PathFactory pathFactory = pathFactory();
+ List<Segment> segments = new LinkedList<Segment>();
+
+ String sepString = File.separator.equals("\\") ? "\\\\" :
File.separator;
+ assert relativePath.charAt(0) == File.separatorChar;
+ for (String segment : relativePath.substring(1).split(sepString)) {
+ segments.add(pathFactory.createSegment(segment, 1));
+ }
+
+ return Location.create(pathFactory().createAbsolutePath(segments));
+ }
+
protected Path getPathFor( Location location,
Request request ) {
- Path path = location.getPath();
- if (location.getUuid() != null &&
rootNodeUuid.equals(location.getUuid())) {
+ if (location.hasPath()) return location.getPath();
+
+ UUID uuid = uuidFor(location);
+ if (rootNodeUuid.equals(uuid)) {
return pathFactory().createRootPath();
}
- if (path == null) {
- I18n msg = FileSystemI18n.locationInRequestMustHavePath;
- throw new RepositorySourceException(getSourceName(),
msg.text(getSourceName(), request));
- }
- return path;
+ I18n msg = FileSystemI18n.locationInRequestMustHavePath;
+ request.setError(new RepositorySourceException(getSourceName(),
msg.text(getSourceName(), request)));
+ return null;
}
protected File getWorkspaceDirectory( String workspaceName ) {
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-08-05
13:13:29 UTC (rev 1143)
+++
trunk/extensions/dna-connector-filesystem/src/main/java/org/jboss/dna/connector/filesystem/FileSystemSource.java 2009-08-05
14:45:38 UTC (rev 1144)
@@ -33,6 +33,7 @@
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.RefAddr;
import javax.naming.Reference;
@@ -78,6 +79,9 @@
protected static final String WORKSPACE_ROOT = "workspaceRootPath";
protected static final String PREDEFINED_WORKSPACE_NAMES =
"predefinedWorkspaceNames";
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";
/**
* This source supports events.
@@ -103,6 +107,8 @@
public static final int DEFAULT_RETRY_LIMIT = 0;
public static final int DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS = 60 * 5; // 5 minutes
+ public static final int DEFAULT_MAX_PATH_LENGTH = 255; // 255 for windows users
+ public static final String DEFAULT_EXCLUSION_PATTERN = null;
private volatile String name;
private volatile int retryLimit = DEFAULT_RETRY_LIMIT;
@@ -111,6 +117,8 @@
private volatile String workspaceRootPath;
private volatile String[] predefinedWorkspaces = new String[] {};
private volatile UUID rootNodeUuid = UUID.randomUUID();
+ private volatile int maxPathLength = DEFAULT_MAX_PATH_LENGTH;
+ private volatile String exclusionPattern = DEFAULT_EXCLUSION_PATTERN;
private volatile RepositorySourceCapabilities capabilities = new
RepositorySourceCapabilities(
SUPPORTS_SAME_NAME_SIBLINGS,
DEFAULT_SUPPORTS_UPDATES,
@@ -200,6 +208,25 @@
}
/**
+ * Get the regular expression that, if matched by a file or folder, indicates that
the file or folder should be ignored
+ *
+ * @return the regular expression that, if matched by a file or folder, indicates
that the file or folder should be ignored
+ */
+ public String getExclusionPattern() {
+ return exclusionPattern;
+ }
+
+ /**
+ * Sets the regular expression that, if matched by a file or folder, indicates that
the file or folder should be ignored
+ *
+ * @param exclusionPattern the regular expression that, if matched by a file or
folder, indicates that the file or folder
+ * should be ignored. If this pattern is {@code null}, no files will be
excluded.
+ */
+ public synchronized void setExclusionPattern( String exclusionPattern ) {
+ this.exclusionPattern = exclusionPattern;
+ }
+
+ /**
* 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
@@ -219,6 +246,31 @@
}
/**
+ * 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 int getMaxPathLength() {
+ return maxPathLength;
+ }
+
+ /**
+ * Set the maximum absolute path length supported by this source.
+ * <p>
+ * The length of any path is calculated relative to the file system root, NOT the
repository root. That is, if a workspace
+ * {@code foo} is mapped to the {@code /tmp/foo/bar} directory on the file system,
then the path {@code /node1/node2} in the
+ * {@code foo} workspace has an effective length of 23 for the purposes of the {@code
maxPathLength} calculation ({@code
+ * /tmp/foo/bar} has length 11, {@code /node1/node2} has length 12, 11 + 12 = 23).
+ * </p>
+ *
+ * @param maxPathLength the maximum absolute path length supported by this source;
must be non-negative
+ */
+ public synchronized void setMaxPathLength( int maxPathLength ) {
+ CheckArg.isNonNegative(maxPathLength, "maxPathLength");
+ this.maxPathLength = maxPathLength;
+ }
+
+ /**
* Get the name of the default workspace.
*
* @return the name of the workspace that should be used by default; never null
@@ -292,6 +344,29 @@
}
/**
+ * Get whether this source allows updates.
+ *
+ * @return true if this source allows updates by clients, or false if no updates are
allowed
+ * @see #setUpdatesAllowed(boolean)
+ */
+ public boolean areUpdatesAllowed() {
+ return capabilities.supportsUpdates();
+ }
+
+ /**
+ * Set whether this source allows updates to data within workspaces
+ *
+ * @param allowUpdates true if this source allows updates to data within workspaces
clients, or false if updates are not
+ * allowed.
+ * @see #areUpdatesAllowed()
+ */
+ public synchronized void setUpdatesAllowed( boolean allowUpdates ) {
+ capabilities = new
RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), allowUpdates,
+ capabilities.supportsEvents(),
capabilities.supportsCreatingWorkspaces(),
+
capabilities.supportsReferences());
+ }
+
+ /**
* {@inheritDoc}
*
* @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit()
@@ -356,6 +431,8 @@
ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDefaultWorkspaceName()));
ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES,
Boolean.toString(isCreatingWorkspacesAllowed())));
+ ref.add(new StringRefAddr(EXCLUSION_PATTERN, exclusionPattern));
+ ref.add(new StringRefAddr(MAX_PATH_LENGTH, String.valueOf(maxPathLength)));
String[] workspaceNames = getPredefinedWorkspaceNames();
if (workspaceNames != null && workspaceNames.length != 0) {
ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES,
StringUtil.combineLines(workspaceNames)));
@@ -387,6 +464,8 @@
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 combinedWorkspaceNames = values.get(PREDEFINED_WORKSPACE_NAMES);
String[] workspaceNames = null;
@@ -403,6 +482,8 @@
if (defaultWorkspace != null)
source.setDefaultWorkspaceName(defaultWorkspace);
if (createWorkspaces != null)
source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces));
if (workspaceNames != null && workspaceNames.length != 0)
source.setPredefinedWorkspaceNames(workspaceNames);
+ if (exclusionPattern != null) source.setExclusionPattern(exclusionPattern);
+ if (maxPathLength != null)
source.setMaxPathLength(Integer.valueOf(maxPathLength));
return source;
}
return null;
@@ -445,29 +526,48 @@
} else if (!file.canRead()) {
Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceCannotBeRead,
predefined, name);
}
-
-
+
this.availableWorkspaces.put(predefined, file);
}
}
- if (defaultWorkspaceName != null) {
+ if (defaultWorkspaceName != null) {
// Look for the file at this path ...
File file = new File(pathFor(defaultWorkspaceName));
if (!file.exists()) {
-
Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceDoesNotExist,
defaultWorkspaceName, name);
+
Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceDoesNotExist,
+ defaultWorkspaceName,
+ name);
} else if (!file.isDirectory()) {
-
Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceIsNotDirectory,
defaultWorkspaceName, name);
+
Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceIsNotDirectory,
+ defaultWorkspaceName,
+ name);
} else if (!file.canRead()) {
-
Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceCannotBeRead,
defaultWorkspaceName, name);
+
Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceCannotBeRead,
+ defaultWorkspaceName,
+ name);
}
-
-
+
this.availableWorkspaces.put(defaultWorkspaceName, file);
}
+
+ FilenameFilter filenameFilter = null;
+ if (exclusionPattern != null) {
+ final String filterPattern = exclusionPattern;
+ filenameFilter = new FilenameFilter() {
+ Pattern filter = Pattern.compile(filterPattern);
+
+ @Override
+ public boolean accept( File dir,
+ String name ) {
+ return !filter.matcher(name).matches();
+ }
+ };
+ }
return new FileSystemConnection(name, defaultWorkspaceName, availableWorkspaces,
isCreatingWorkspacesAllowed(),
- cachePolicy, rootNodeUuid, workspaceRootPath,
(FilenameFilter) null, getSupportsUpdates());
+ cachePolicy, rootNodeUuid, workspaceRootPath,
maxPathLength, filenameFilter,
+ 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-08-05
13:13:29 UTC (rev 1143)
+++
trunk/extensions/dna-connector-filesystem/src/main/resources/org/jboss/dna/connector/filesystem/FileSystemI18n.properties 2009-08-05
14:45:38 UTC (rev 1144)
@@ -35,6 +35,24 @@
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}
+nodeOrderingNotSupported = {0} does not support node ordering
onlyTheDefaultNamespaceIsAllowed = {0} requires node names use the default namespace:
{1}
-sourceIsReadOnly = {0} is a read-only source; no updates are allowed
+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().
+
+# Writable tests
+parentIsReadOnly = The parent node at path "{0}" in workspace "{1}"
in {2} cannot be written to. See java.io.File\#canWrite().
unableToCreateWorkspaces = {0} does not allow creating new workspaces (request was to
create "{1}")
+fileAlreadyExists = The path "{0}" in workspace "{1}" in {2} already
exists.
+couldNotCreateFile = Error creating the path "{0}" in workspace "{1}"
in {2}: {3}
+unsupportedPrimaryType = Primary type "{3}" for path "{0}" in
workspace "{1}" in {2} is not valid for the file system connector. Valid
primary types are nt\:file, nt\:folder, nt\:resource, and dna\:resouce.
+invalidNameForResource = Invalid node name "{3}" for node at path
"{0}" in workspace "{1}" in {2}. The name of nodes with primary type
nt:resource or dna:resource must be "jcr:content".
+invalidPathForResource = Invalid parent type for node at path "{0}" in
workspace "{1}" in {2}. The parent node for nodes with primary type nt:resource
or dna:resource must be of type nt:file.
+invalidPropertyNames = Attempt to set or update invalid property names: {0}
+couldNotWriteData = Error writing data to path "{0}" in workspace
"{1}" in {2}\: {3}
+couldNotUpdateData = Error moving temporary data file to path "{0}" in
workspace "{1}" in {2}
+missingRequiredProperty = Missing required property "{3}" at path
"{0}" in workspace "{1}" in {2}
+deleteFailed = Could not delete file at path "{0}" in workspace "{1}"
in {2}
+copyFailed = Could not copy file at path "{0}" in workspace "{1}" to
path "{2}" in workspace "{3}" in {4}
+getCanonicalPathFailed = Could not determine canonical path
+maxPathLengthExceeded = The maximum absolute path length ({0}) for source "{1}"
was exceeded by the node at: {2}
Deleted:
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-08-05
13:13:29 UTC (rev 1143)
+++
trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorNotWritableTest.java 2009-08-05
14:45:38 UTC (rev 1144)
@@ -1,64 +0,0 @@
-/*
- * 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 org.jboss.dna.graph.Graph;
-import org.jboss.dna.graph.connector.RepositorySource;
-import org.jboss.dna.graph.connector.test.NotWritableConnectorTest;
-
-/**
- * @author Randall Hauch
- */
-public class FileSystemConnectorNotWritableTest extends NotWritableConnectorTest {
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.graph.connector.test.AbstractConnectorTest#setUpSource()
- */
- @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[] predefinedWorkspaceNames = new String[] {path + "airplanes",
path + "cars"};
- FileSystemSource source = new FileSystemSource();
- source.setName("Test Repository");
- source.setPredefinedWorkspaceNames(predefinedWorkspaceNames);
- source.setDefaultWorkspaceName(predefinedWorkspaceNames[0]);
- source.setCreatingWorkspacesAllowed(false);
-
- return source;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.graph.connector.test.AbstractConnectorTest#initializeContent(org.jboss.dna.graph.Graph)
- */
- @Override
- protected void initializeContent( Graph graph ) {
- // No need to initialize any content ...
- }
-}
Copied:
trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorWritableTest.java
(from rev 1126,
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/FileSystemConnectorWritableTest.java
(rev 0)
+++
trunk/extensions/dna-connector-filesystem/src/test/java/org/jboss/dna/connector/filesystem/FileSystemConnectorWritableTest.java 2009-08-05
14:45:38 UTC (rev 1144)
@@ -0,0 +1,474 @@
+/*
+ * 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 static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import org.jboss.dna.common.util.FileUtil;
+import org.jboss.dna.graph.DnaLexicon;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.JcrLexicon;
+import org.jboss.dna.graph.JcrMixLexicon;
+import org.jboss.dna.graph.JcrNtLexicon;
+import org.jboss.dna.graph.connector.RepositorySource;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.connector.test.AbstractConnectorTest;
+import org.jboss.dna.graph.request.InvalidRequestException;
+import org.junit.Test;
+
+public class FileSystemConnectorWritableTest extends AbstractConnectorTest {
+
+ public static final String ARBITRARY_PROPERTIES_NOT_SUPPORTED = "This connector
does not support setting arbitrary properties";
+
+ private static final String REPO_PATH =
"./src/test/resources/repositories/";
+ private final String TEST_CONTENT = "Test content";
+
+ protected File testWorkspaceRoot;
+ protected File otherWorkspaceRoot;
+ protected File newWorkspaceRoot;
+
+ @Override
+ protected RepositorySource setUpSource() {
+ // Set the connection properties to be use the content of
"./src/test/resources/repositories" as a repository ...
+ String[] predefinedWorkspaceNames = new String[] {"test",
"otherWorkspace", "airplanes", "cars"};
+ FileSystemSource source = new FileSystemSource();
+ source.setName("Test Repository");
+ source.setWorkspaceRootPath(REPO_PATH);
+ source.setPredefinedWorkspaceNames(predefinedWorkspaceNames);
+ source.setDefaultWorkspaceName(predefinedWorkspaceNames[0]);
+ source.setCreatingWorkspacesAllowed(true);
+ source.setUpdatesAllowed(true);
+
+ testWorkspaceRoot = new File(REPO_PATH, "test");
+ testWorkspaceRoot.mkdir();
+
+ otherWorkspaceRoot = new File(REPO_PATH, "otherWorkspace");
+ otherWorkspaceRoot.mkdir();
+
+ newWorkspaceRoot = new File(REPO_PATH, "newWorkspace");
+ newWorkspaceRoot.mkdir();
+
+ return source;
+ }
+
+ @Override
+ protected void initializeContent( Graph graph ) {
+ // No setup required
+ }
+
+ @Override
+ public void afterEach() throws Exception {
+ FileUtil.delete(testWorkspaceRoot);
+ FileUtil.delete(otherWorkspaceRoot);
+ FileUtil.delete(newWorkspaceRoot);
+ super.afterEach();
+ }
+
+ @Test
+ public void shouldBeAbleToCreateFileWithContent() {
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldRespectConflictBehaviorOnCreate() {
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
"Should not overwrite".getBytes()).ifAbsent().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldBeAbleToCreateFileWithNoContent() {
+ graph.create("/testEmptyFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testEmptyFile");
+ assertThat(newFile.exists(), is(true));
+ assertThat(newFile.isFile(), is(true));
+ }
+
+ @Test
+ public void shouldBeAbleToCreateFolder() {
+ graph.create("/testFolder").orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFolder");
+ assertThat(newFile.exists(), is(true));
+ assertThat(newFile.isDirectory(), is(true));
+ }
+
+ @Test
+ public void shouldBeAbleToAddChildrenToFolder() {
+ graph.create("/testFolder").orReplace().and();
+
+ File newFolder = new File(testWorkspaceRoot, "testFolder");
+ assertThat(newFolder.exists(), is(true));
+ assertThat(newFolder.isDirectory(), is(true));
+
+ graph.create("/testFolder/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testfile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFolder/testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ }
+
+ @Test( expected = RepositorySourceException.class )
+ public void shouldNotBeAbleToCreateInvalidTypeForRepository() {
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.UNSTRUCTURED).orReplace().and();
+ }
+
+ @Test( expected = RepositorySourceException.class )
+ public void shouldNotBeAbleToSetArbitraryProperties() {
+ graph.create("/testFile").with(JcrLexicon.MIXIN_TYPES,
JcrMixLexicon.LOCKABLE).orReplace().and();
+ }
+
+ @Test
+ public void shouldBeAbleToCopyFile() {
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ graph.copy("/testFile").to("/copiedFile");
+ File copiedFile = new File(testWorkspaceRoot, "copiedFile");
+ assertContents(copiedFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldBeAbleToCopyFolder() {
+ graph.create("/testFolder").orReplace().and();
+ graph.create("/testFolder/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFolder/testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ graph.copy("/testFolder").to("/copiedFolder");
+ File copiedFolder = new File(testWorkspaceRoot, "copiedFolder");
+ assertTrue(copiedFolder.exists());
+ assertTrue(copiedFolder.isDirectory());
+
+ File copiedFile = new File(testWorkspaceRoot,
"copiedFolder/testFile");
+ assertContents(copiedFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldBeAbleToMoveFile() {
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ graph.create("/newFolder").orReplace().and();
+
+ graph.move("/testFile").into("/newFolder");
+ assertThat(newFile.exists(), is(false));
+
+ File copiedFile = new File(testWorkspaceRoot, "newFolder/testFile");
+ assertContents(copiedFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldBeAbleToMoveFolder() {
+ graph.create("/testFolder").orReplace().and();
+ graph.create("/testFolder/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFolder/testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ graph.create("/newFolder").orReplace().and();
+
+ graph.move("/testFolder").into("/newFolder");
+ assertThat(newFile.exists(), is(false));
+
+ File copiedFolder = new File(testWorkspaceRoot,
"newFolder/testFolder");
+ assertTrue(copiedFolder.exists());
+ assertTrue(copiedFolder.isDirectory());
+
+ File copiedFile = new File(testWorkspaceRoot,
"newFolder/testFolder/testFile");
+ assertContents(copiedFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldBeAbleToDeleteFolderWithContents() {
+ graph.create("/testFolder").orReplace().and();
+ graph.create("/testFolder/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFolder = new File(testWorkspaceRoot, "testFolder");
+ assertTrue(newFolder.exists());
+ assertTrue(newFolder.isDirectory());
+
+ File newFile = new File(testWorkspaceRoot, "testFolder/testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ graph.delete("/testFolder");
+
+ assertThat(newFolder.exists(), is(false));
+ }
+
+ @Test
+ public void shouldBeAbleToDeleteFile() {
+ graph.create("/testFolder").orReplace().and();
+ graph.create("/testFolder/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFolder = new File(testWorkspaceRoot, "testFolder");
+ assertTrue(newFolder.exists());
+ assertTrue(newFolder.isDirectory());
+
+ File newFile = new File(testWorkspaceRoot, "testFolder/testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ graph.delete("/testFolder/testFile");
+
+ assertTrue(newFolder.exists());
+ assertThat(newFile.exists(), is(false));
+ }
+
+ /**
+ * Since the FS connector does not support UUIDs (under the root node), all clones
are just copies (clone for
+ * non-referenceable nodes is a copy to the corresponding path).
+ */
+ @Test
+ public void shouldBeAbleToCloneFolder() {
+ graph.useWorkspace("otherWorkspace");
+ graph.create("/testFolder").orReplace().and();
+ graph.create("/testFolder/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(otherWorkspaceRoot, "testFolder/testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ graph.useWorkspace("test");
+
graph.clone("/testFolder").fromWorkspace("otherWorkspace").as("clonedFolder").into("/").failingIfAnyUuidsMatch();
+ File copiedFolder = new File(testWorkspaceRoot, "clonedFolder");
+ assertTrue(copiedFolder.exists());
+ assertTrue(copiedFolder.isDirectory());
+
+ File copiedFile = new File(testWorkspaceRoot,
"clonedFolder/testFile");
+ assertContents(copiedFile, TEST_CONTENT);
+ }
+
+ /**
+ * Since the FS connector does not support UUIDs (under the root node), all clones
are just copies (clone for
+ * non-referenceable nodes is a copy to the corresponding path).
+ */
+ @Test
+ public void shouldBeAbleToCloneFile() {
+ graph.useWorkspace("otherWorkspace");
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(otherWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ graph.useWorkspace("test");
+
graph.clone("/testFile").fromWorkspace("otherWorkspace").as("clonedFile").into("/").failingIfAnyUuidsMatch();
+ File copiedFile = new File(testWorkspaceRoot, "clonedFile");
+ assertContents(copiedFile, TEST_CONTENT);
+ }
+
+ @Test( expected = InvalidRequestException.class )
+ public void shouldNotBeAbleToReorderFolder() {
+ graph.create("/testFolder").orReplace().and();
+ graph.create("/testFolder2").orReplace().and();
+
+ File newFolder = new File(testWorkspaceRoot, "testFolder");
+ assertTrue(newFolder.exists());
+ assertTrue(newFolder.isDirectory());
+
+ File newFolder2 = new File(testWorkspaceRoot, "testFolder2");
+ assertTrue(newFolder2.exists());
+ assertTrue(newFolder2.isDirectory());
+
+ graph.move("/testFolder2").before("/testFolder");
+ }
+
+ @Test( expected = InvalidRequestException.class )
+ public void shouldNotBeAbleToReorderFile() {
+ graph.create("/testFolder").orReplace().and();
+ graph.create("/testFolder/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+ graph.create("/testFolder/testFile2").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testFile2/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFolder = new File(testWorkspaceRoot, "testFolder");
+ assertTrue(newFolder.exists());
+ assertTrue(newFolder.isDirectory());
+
+ File newFile = new File(testWorkspaceRoot, "testFolder/testFile");
+ assertContents(newFile, TEST_CONTENT);
+ File newFile2 = new File(testWorkspaceRoot, "testFolder/testFile2");
+ assertContents(newFile2, TEST_CONTENT);
+
+
graph.move("/testFolder/testFile2").before("/testFolder/testFile");
+ }
+
+ @Test
+ public void shouldBeAbleToRenameFolder() {
+ graph.create("/testFolder").orReplace().and();
+ graph.create("/testFolder/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+
graph.create("/testFolder/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFolder/testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+
graph.move("/testFolder").as("newFolder").into("/");
+ assertThat(newFile.exists(), is(false));
+
+ File copiedFolder = new File(testWorkspaceRoot, "newFolder");
+ assertTrue(copiedFolder.exists());
+ assertTrue(copiedFolder.isDirectory());
+
+ File copiedFile = new File(testWorkspaceRoot, "newFolder/testFile");
+ assertContents(copiedFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldBeAbleToRenameFile() {
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+
graph.move("/testFile").as("copiedFile").into("/");
+ assertThat(newFile.exists(), is(false));
+
+ File copiedFile = new File(testWorkspaceRoot, "copiedFile");
+ assertContents(copiedFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldBeAbleToCreateWorkspace() {
+ graph.createWorkspace().named("newWorkspace");
+
+ graph.useWorkspace("newWorkspace");
+
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(newWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+ }
+
+ @Test
+ public void shouldBeAbleToCloneWorkspace() {
+ graph.create("/testFile").with(JcrLexicon.PRIMARY_TYPE,
JcrNtLexicon.FILE).orReplace().and();
+ graph.create("/testFile/jcr:content").with(JcrLexicon.PRIMARY_TYPE,
DnaLexicon.RESOURCE).and(JcrLexicon.DATA,
+
TEST_CONTENT.getBytes()).orReplace().and();
+
+ File newFile = new File(testWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+
graph.createWorkspace().clonedFrom("test").named("newWorkspace");
+
+ newFile = new File(newWorkspaceRoot, "testFile");
+ assertContents(newFile, TEST_CONTENT);
+
+ }
+
+ @Test
+ public void shouldBeAbleToCreateDeepPath() {
+ String pathName = "";
+
+ for (int i = 0; i < 30; i++) {
+ pathName += "/test";
+ graph.create(pathName).orReplace().and();
+ }
+ }
+
+ @Test( expected = RepositorySourceException.class )
+ public void shouldNotBeAbleToCreateTooDeepPath() {
+ String pathName = "";
+
+ for (int i = 0; i < 100; i++) {
+ pathName += "/testFolder";
+ graph.create(pathName).orReplace().and();
+ }
+ }
+
+ protected void assertContents( File file,
+ String contents ) {
+ assertTrue(file.exists());
+ assertTrue(file.isFile());
+
+ StringBuilder buff = new StringBuilder();
+ final int BUFF_SIZE = 8192;
+ byte[] bytes = new byte[BUFF_SIZE];
+ int len;
+ FileInputStream fis = null;
+
+ try {
+ fis = new FileInputStream(file);
+
+ while (-1 != (len = fis.read(bytes, 0, BUFF_SIZE))) {
+ buff.append(new String(bytes, 0, len));
+ }
+
+ assertThat(buff.toString(), is(contents));
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ fail(ioe.getMessage());
+ return;
+ } finally {
+ try {
+ fis.close();
+ } catch (Exception ignore) {
+ }
+ }
+ }
+}