Skip to content

Commit

Permalink
MODE-1206 Add Ability to Specify Temporary Storage Area on File Syste…
Browse files Browse the repository at this point in the history
…m Connector

Added a new property, temporaryStoragePath, to FileSystemSource and then modified FileSystemWorkspace.putNode to use this directory instead of just creating a pending file.  All tests pass.
  • Loading branch information
bcarothers-xx authored and rhauch committed Jun 30, 2011
1 parent e5ea0d0 commit 1999011
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@
<entry>Optional property that, if used, defines the number of times that any single operation on a &RepositoryConnection; to this source should be retried
following a communication failure. The default value is '0'.</entry>
</row>
<row>
<entry>temporaryStoragePath</entry>
<entry><para>Optional property that allows administrators to specify a specific location for the file system connector's temporary
storage. When writing file content, this connector first writes the content to a file in the temporary storage area. After
that write succeeds in full, the temporary file is moved to its final location in the workspace. This extra step is taken
so that an error or failure while writing the file does not cause corruption in the target file.
</para>
<para>
However, if the temporary storage area is located on a different file system than the file that will ultimately be written
to, the rename operation cannot be used and a separate file copy must occur. Therefore,
administrators should set the value of this path to some path in the same file system as the
workspace root path as specified by the <code>workspaceRootPath</code> property.
</para>
</entry>
</row>
<row>
<entry>updatesAllowed</entry>
<entry>Determines whether the content in the file system can be updated ("true"), or if the content may only be read ("false").
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public final class FileSystemI18n {
public static I18n customPropertiesFactoryPropertyDescription;
public static I18n customPropertiesFactoryPropertyLabel;
public static I18n customPropertiesFactoryPropertyCategory;
public static I18n temporaryStoragePathPropertyDescription;
public static I18n temporaryStoragePathPropertyLabel;
public static I18n temporaryStoragePathPropertyCategory;

// Writable messages
public static I18n parentIsReadOnly;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ protected File getWorkspaceDirectory( String workspaceName ) {
return directory;
}

/**
* @return the directory in which pending files should be created
*/
File pendingFileDir() {
return new File(source.getTemporaryStoragePath());
}

@Override
public FileSystemTransaction startTransaction( ExecutionContext context,
boolean readonly ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public class FileSystemSource extends AbstractNodeCachingRepositorySource<Path,
*/
private static final long serialVersionUID = 1L;

private static final String JAVA_TEMP_DIR = System.getProperty("java.io.tmpdir");

/**
* The initial {@link #getDefaultWorkspaceName() name of the default workspace} is "{@value} ", unless otherwise specified.
*/
Expand All @@ -95,6 +97,7 @@ public class FileSystemSource extends AbstractNodeCachingRepositorySource<Path,
protected static final String EAGER_FILE_LOADING = "eagerFileLoading";
protected static final String DETERMINE_MIME_TYPE_USING_CONTENT = "determineMimeTypeUsingContent";
protected static final String EXTRA_PROPERTIES = "extraProperties";
protected static final String TEMPORARY_STORAGE_PATH = "temporaryStoragePath";

/**
* This source supports events.
Expand Down Expand Up @@ -133,6 +136,12 @@ public class FileSystemSource extends AbstractNodeCachingRepositorySource<Path,
*/
public static final boolean DEFAULT_DETERMINE_MIME_TYPE_USING_CONTENT = false;

/**
* The default path to the temporary storage area, the default value uses the directory specified by the {@code
* java.io.tmpdir} system property.
*/
public static final String DEFAULT_TEMPORARY_STORAGE_PATH = JAVA_TEMP_DIR;

public static final int DEFAULT_MAX_PATH_LENGTH = 255; // 255 for windows users
public static final String DEFAULT_EXCLUSION_PATTERN = null;
public static final String DEFAULT_INCLUSION_PATTERN = null;
Expand Down Expand Up @@ -185,6 +194,11 @@ public class FileSystemSource extends AbstractNodeCachingRepositorySource<Path,
@Category( i18n = FileSystemI18n.class, value = "extraPropertiesPropertyCategory" )
private volatile String extraProperties = DEFAULT_EXTRA_PROPERTIES;

@Description( i18n = FileSystemI18n.class, value = "temporaryStoragePathPropertyDescription" )
@Label( i18n = FileSystemI18n.class, value = "temporaryStoragePathPropertyLabel" )
@Category( i18n = FileSystemI18n.class, value = "temporaryStoragePathPropertyCategory" )
private volatile String temporaryStoragePath = DEFAULT_TEMPORARY_STORAGE_PATH;

private volatile FilenameFilter filenameFilter = DEFAULT_FILENAME_FILTER;
private volatile InclusionExclusionFilenameFilter inclusionExclusionFilenameFilter = new InclusionExclusionFilenameFilter();

Expand Down Expand Up @@ -630,6 +644,38 @@ public void setEagerFileLoading( boolean eagerFileLoading ) {
this.eagerFileLoading = eagerFileLoading;
}

/**
* Optional setting that allows administrators to specify a specific location for the file system connector's temporary
* storage. When writing file content, this connector first writes the content to a file in the temporary storage area. After
* that write succeeds in full, the temporary file is moved to its final location in the workspace. This extra step is taken
* so that an error or failure while writing the file does not cause corruption in the target file.
* <p/>
* However, if the temporary storage area is located on a different file system than the file that will ultimately be written
* to, {@link File#renameTo(File) the rename operation} cannot be used and a separate file copy must occur. Therefore,
* administrators should set the value of this path to some path in the same file system as {@link #getWorkspaceRootPath() the
* workspace root path}.
* <p/>
* The default value of this property is the value of {@code System.getProperty("java.io.tmpdir")}.
*
* @return the directory that should be used for temporary storage; never null
*/
public String getTemporaryStoragePath() {
return temporaryStoragePath;
}

/**
* Sets the new value for the temporary storage path.
*
* @param temporaryStoragePath the new value for the temporary storage path; null indicates that the directory specified by
* {@code java.io.tmpdir} system property should be used
*/
public void setTemporaryStoragePath( String temporaryStoragePath ) {
if (temporaryStoragePath == null) {
temporaryStoragePath = JAVA_TEMP_DIR;
}
this.temporaryStoragePath = temporaryStoragePath;
}

/**
* {@inheritDoc}
*
Expand Down Expand Up @@ -664,6 +710,9 @@ public synchronized Reference getReference() {
if (filenameFilter != null) {
ref.add(new StringRefAddr(FILENAME_FILTER, filenameFilter.getClass().getName()));
}
if (this.temporaryStoragePath != null) {
ref.add(new StringRefAddr(TEMPORARY_STORAGE_PATH, this.temporaryStoragePath));
}
ref.add(new StringRefAddr(EAGER_FILE_LOADING, Boolean.toString(isEagerFileLoading())));
ref.add(new StringRefAddr(DETERMINE_MIME_TYPE_USING_CONTENT, Boolean.toString(isContentUsedToDetermineMimeType())));

Expand Down Expand Up @@ -707,6 +756,7 @@ public Object getObjectInstance( Object obj,
String extraPropertiesBehavior = (String)values.get(EXTRA_PROPERTIES);
String eagerFileLoading = (String)values.get(EAGER_FILE_LOADING);
String useContentForMimeType = (String)values.get(DETERMINE_MIME_TYPE_USING_CONTENT);
String temporaryStoragePath = (String)values.get(TEMPORARY_STORAGE_PATH);
Object defaultCachePolicy = values.get(CACHE_POLICY);
Object nodeCachePolicy = values.get(NODE_CACHE_POLICY);

Expand All @@ -733,6 +783,7 @@ public Object getObjectInstance( Object obj,
if (customPropertiesFactoryClassName != null) source.setCustomPropertiesFactory(customPropertiesFactoryClassName);
if (eagerFileLoading != null) source.setEagerFileLoading(Boolean.parseBoolean(eagerFileLoading));
if (useContentForMimeType != null) source.setContentUsedToDetermineMimeType(Boolean.parseBoolean(useContentForMimeType));
if (temporaryStoragePath != null) source.setTemporaryStoragePath(temporaryStoragePath);
return source;
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.modeshape.common.i18n.I18n;
import org.modeshape.common.util.FileUtil;
Expand Down Expand Up @@ -59,7 +60,7 @@ class FileSystemWorkspace extends PathWorkspace<PathNode> implements NodeCaching
JcrNtLexicon.FILE, JcrNtLexicon.RESOURCE, ModeShapeLexicon.RESOURCE}));

private final FileSystemSource source;
private final FileSystemRepository repository;
protected final FileSystemRepository repository;
private final ExecutionContext context;
private final File workspaceRoot;
private final boolean eagerLoading;
Expand Down Expand Up @@ -137,8 +138,8 @@ public NodeCache<Path, PathNode> getCache() {
return cache;
}

private void moveFile( File originalFileOrDirectory,
File newFileOrDirectory ) {
protected void moveFile( File originalFileOrDirectory,
File newFileOrDirectory ) {
if (originalFileOrDirectory.renameTo(newFileOrDirectory)) return;

/*
Expand All @@ -151,8 +152,8 @@ private void moveFile( File originalFileOrDirectory,
FileUtil.delete(originalFileOrDirectory);
} catch (IOException ioe) {
throw new RepositorySourceException(FileSystemI18n.couldNotCopyData.text(source.getName(),
originalFileOrDirectory.getAbsolutePath(),
newFileOrDirectory.getAbsolutePath()), ioe);
originalFileOrDirectory.getAbsolutePath(),
newFileOrDirectory.getAbsolutePath()), ioe);
}

}
Expand Down Expand Up @@ -321,44 +322,33 @@ public PathNode putNode( PathNode node ) {
}

// Copy over data into a temp file, then move it to the correct location
FileOutputStream fos = null;
try {
File temp = File.createTempFile("modeshape", null);
fos = new FileOutputStream(temp);

Property dataProp = properties.get(JcrLexicon.DATA);
BinaryFactory binaryFactory = context.getValueFactories().getBinaryFactory();
Binary binary = null;
if (dataProp == null) {
// There is no content, so make empty content ...
binary = binaryFactory.create(new byte[] {});
dataProp = context.getPropertyFactory().create(JcrLexicon.DATA, new Object[] {binary});
} else {
// Must read the value ...
binary = binaryFactory.create(properties.get(JcrLexicon.DATA).getFirstValue());
}

IoUtil.write(binary.getStream(), fos);
Property dataProp = properties.get(JcrLexicon.DATA);
BinaryFactory binaryFactory = context.getValueFactories().getBinaryFactory();
Binary binary = null;
if (dataProp == null) {
// There is no content, so make empty content ...
binary = binaryFactory.create(new byte[] {});
dataProp = context.getPropertyFactory().create(JcrLexicon.DATA, new Object[] {binary});
} else {
// Must read the value ...
binary = binaryFactory.create(properties.get(JcrLexicon.DATA).getFirstValue());
}

if (!FileUtil.delete(parentFile)) {
I18n msg = FileSystemI18n.deleteFailed;
throw new RepositorySourceException(source.getName(), msg.text(parentPath, getName(), source.getName()));
}
try {

moveFile(temp, parentFile);
PendingFile temp = new PendingFile(parentFile);
temp.write(binary);
temp.commit();
} catch (IOException ioe) {
I18n msg = FileSystemI18n.couldNotWriteData;
throw new RepositorySourceException(source.getName(), msg.text(parentPath,
getName(),
source.getName(),
ioe.getMessage()), ioe);

} finally {
try {
if (fos != null) fos.close();
} catch (Exception ex) {
}
}

customPropertiesFactory.recordResourceProperties(context,
source.getName(),
Location.create(parentPath),
Expand Down Expand Up @@ -830,4 +820,68 @@ protected boolean primaryTypeIs( Property property,
protected Name nameValueFor( Property property ) {
return nameFactory.create(property.getFirstValue());
}

/**
* Representation of a file that is being written to the repository in a transaction that has not yet been fully committed.
*/
class PendingFile {
private File targetFile;
private File pendingFile;

PendingFile( File targetFile ) {
this.targetFile = targetFile;
this.pendingFile = new File(repository.pendingFileDir(), UUID.randomUUID().toString());
}

/**
* Writes the content of the given {@link Binary binary object} into the temporary file
*
* @param binary the object to write; may not be null
* @return the number of bytes written
* @throws IOException if the binary data cannot be read or written
*/
public long write( Binary binary ) throws IOException {
final int BUFF_SIZE = 1024 * 4;
byte[] buff = new byte[BUFF_SIZE];

InputStream in = binary.getStream();
FileOutputStream out = new FileOutputStream(pendingFile);

try {
long count = 0;
int chunk = 0;
while (-1 != (chunk = in.read(buff, 0, buff.length))) {
out.write(buff, 0, chunk);
count += chunk;
}
return count;
} finally {
try {
in.close();
} catch (Exception ignore) {
}
try {
if (out != null) out.close();
} catch (IOException ignore) {
}
}

}

/**
* Moves the pending file into its final location.
*
* @see FileSystemWorkspace#moveFile(File, File)
*/
public void commit() {
moveFile(pendingFile, targetFile);
}

// A future step is to split the file system transaction into prepare and commit/rollback stages
// public void rollback() {
// pendingFile.delete();
// }

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ pathForWorkspaceCannotBeRead = The path "{1}" for the workspace "{2}" for the fi
workspaceRootPathPropertyDescription = Optional property that specifies a path on the local file system to the parent folder where all workspaces are found. The workspace name will be appended to this path to determine the absolute path for a particular workspace. If no value (or a null value) is specified, the source will use the name of the workspace as a relative path from the current working directory (as defined by new File(".").\nAs an example, if a value is specified for this property, the workspace named "default/foo" will be found using 'new File(workspaceRootPath, "default/foo")'. If, however, this property is not set, then the path for that workspace will be found with 'new File(".", "default/foo")'.
workspaceRootPathPropertyLabel = Path to the Workspaces
workspaceRootPathPropertyCategory = Workspaces
temporaryStoragePathPropertyDescription = Optional property that specifies a path on the local file system to the directory where temporary files will be created. The default value is system temporary directory as specified by the java.io.tmpdir system property.
temporaryStoragePathPropertyLabel = Path to the Temporary Storage Area
temporaryStoragePathPropertyCategory = Workspaces
defaultWorkspaceNamePropertyDescription = Optional property that defines the name for the workspace that will be used by default if none is specified. The default value is an empty string.
defaultWorkspaceNamePropertyLabel = Default Workspace Name
defaultWorkspaceNamePropertyCategory = Workspaces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class FileSystemConnectorWritableTest extends AbstractConnectorTest {

private static final String REPO_PATH = "./target/repositories/";
private static final String REPO_SOURCE_PATH = "./src/test/resources/repositories/";
private static final String TEMP_STORAGE_PATH = "./target/tmp";
private final String TEST_CONTENT = "Test content";

protected File testWorkspaceRoot;
Expand All @@ -68,6 +69,9 @@ protected RepositorySource setUpSource() throws Exception {
FileUtil.delete(scratchDirectory);
FileUtil.copy(sourceRepo, scratchDirectory);

File tempStorage = new File(TEMP_STORAGE_PATH);
tempStorage.mkdirs();

// 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"};
source = new FileSystemSource();
Expand All @@ -79,6 +83,7 @@ protected RepositorySource setUpSource() throws Exception {
source.setUpdatesAllowed(true);
source.setExclusionPattern("\\.svn");
source.setInclusionPattern(".+");
source.setTemporaryStoragePath(TEMP_STORAGE_PATH);

testWorkspaceRoot = new File(REPO_PATH, "test");
testWorkspaceRoot.mkdir();
Expand Down
Loading

0 comments on commit 1999011

Please sign in to comment.