NIHVIVO-160 Complete the first iteration of coding and testing - especially avoidance of Windows reserved words.

This commit is contained in:
jeb228 2010-05-25 20:33:32 +00:00
parent 60958400f1
commit a16985ccaa
6 changed files with 381 additions and 79 deletions

View file

@ -24,11 +24,21 @@ public interface FileStorage {
*/
String PROPERTY_DEFAULT_NAMESPACE = "Vitro.defaultNamespace";
/**
* The name of the root directory, within the base directory.
*/
public static final String FILE_STORAGE_ROOT = "file_storage_root";
/**
* The name of the file in the base directory that holds the namespace map.
*/
public static final String FILE_STORAGE_NAMESPACES_PROPERTIES = "file_storage_namespaces.properties";
/**
* How often to we insert path separator characters?
*/
int SHORTY_LENGTH = 3;
/**
* Store the bytes from this stream as a file with the specified ID and
* filename. If the file already exists, it is over-written.

View file

@ -42,6 +42,15 @@ public class FileStorageHelper {
public static final char[] NAME_SINGLE_CHARACTER_TARGETS = new char[] {
'=', '+' };
/**
* Windows reserves these names (case-insensitive), so they can't be used
* for directories or files.
*/
public static final String[] WINDOWS_RESERVED_NAMES = new String[] { "CON",
"PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5",
"COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4",
"LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
/**
* Encode the filename as needed to guard against illegal characters.
*
@ -49,8 +58,9 @@ public class FileStorageHelper {
*/
public static String encodeName(String filename) {
String hexed = addHexEncoding(filename);
return addSingleCharacterConversions(hexed,
String cleaned = addSingleCharacterConversions(hexed,
NAME_SINGLE_CHARACTER_SOURCES, NAME_SINGLE_CHARACTER_TARGETS);
return excludeWindowsReservedNames(cleaned);
}
/**
@ -123,17 +133,47 @@ public class FileStorageHelper {
return c;
}
/**
* If a requested filename, after cleaning, is one of the Windows reserved
* words, add a tilde in front.
*/
private static String excludeWindowsReservedNames(String cleanedName) {
for (String word : WINDOWS_RESERVED_NAMES) {
if (word.equalsIgnoreCase(cleanedName)) {
return '~' + cleanedName;
}
}
return cleanedName;
}
/**
* Restore the filename to its original form, removing the encoding.
*
* @see edu.cornell.mannlib.vitro.webapp.utils.filestorage
*/
public static String decodeName(String coded) {
String hexed = removeSingleCharacterConversions(coded,
public static String decodeName(String stored) {
String unexcluded = unexcludeWindowsReservedNames(stored);
String hexed = removeSingleCharacterConversions(unexcluded,
NAME_SINGLE_CHARACTER_SOURCES, NAME_SINGLE_CHARACTER_TARGETS);
return removeHexEncoding(hexed);
}
/**
* If the stored filename was a tilde followed by a Windows reserved word,
* strip the tilde.
*/
private static String unexcludeWindowsReservedNames(String stored) {
if (stored.startsWith("~")) {
String remainder = stored.substring(1);
for (String word : WINDOWS_RESERVED_NAMES) {
if (word.equalsIgnoreCase(remainder)) {
return remainder;
}
}
}
return stored;
}
/**
* Convert common single-character substitutions back to their original
* values.
@ -186,7 +226,7 @@ public class FileStorageHelper {
* Translate the object ID to a relative directory path. A recognized
* namespace is translated to its prefix, and illegal characters are
* encoded. The resulting string is broken up into 3-character directory
* names (or less).
* names (or less). Windows reserved words are prefixed with tilde.
*
* @see edu.cornell.mannlib.vitro.webapp.utils.filestorage
*/
@ -206,7 +246,11 @@ public class FileStorageHelper {
String cleaned = addSingleCharacterConversions(hexed,
PATH_SINGLE_CHARACTER_SOURCES, PATH_SINGLE_CHARACTER_TARGETS);
String prefixed = applyPrefixChar(prefix, cleaned);
return insertPathDelimiters(prefixed);
String brokenUp = insertPathDelimiters(prefixed);
String result = excludeWindowsWordsFromPath(brokenUp);
LOG.debug("id2Path: id='" + id + "', namespaces='" + namespacesMap
+ "', path='" + result + "'");
return result;
}
/**
@ -232,9 +276,31 @@ public class FileStorageHelper {
}
path.append(prefixed.charAt(i));
}
LOG.debug("Insert path delimiters to '" + prefixed + "' giving '"
+ path + "'");
return path.toString();
}
/**
* Check each part in the path, and if it is a Windows reserved word, add a
* tilde. This only applies to the relative path.
*/
private static String excludeWindowsWordsFromPath(String rawPath) {
String path = rawPath.replace(File.separatorChar, '/');
String[] parts = path.split("/");
StringBuilder newPath = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
String part = excludeWindowsReservedNames(parts[i]);
if (i > 0) {
newPath.append(File.separatorChar);
}
newPath.append(part);
}
return newPath.toString();
}
/**
* Translate the object ID and the file storage root directory into a full
* path to the directory that would represent that ID.

View file

@ -14,6 +14,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -69,10 +70,10 @@ public class FileStorageImpl implements FileStorage {
checkNamespacesValid(namespaces);
this.baseDir = baseDir;
this.rootDir = new File(this.baseDir, "file_storage_root");
this.rootDir = new File(this.baseDir, FILE_STORAGE_ROOT);
this.namespaceFile = new File(baseDir,
"file_storage_namespaces.properties");
FILE_STORAGE_NAMESPACES_PROPERTIES);
if (rootDir.exists() && namespaceFile.exists()) {
this.namespacesMap = confirmNamespaces(namespaces);
@ -80,15 +81,15 @@ public class FileStorageImpl implements FileStorage {
this.namespacesMap = mapNamespaces(namespaces);
initializeStorage();
} else if (rootDir.exists()) {
throw new IllegalStateException(
"Storage directory '' has been partially initialized. '"
+ rootDir.getPath() + "' exists, but '"
+ namespaceFile.getPath() + "' does not.");
throw new IllegalStateException("Storage directory '"
+ baseDir.getPath() + "' has been partially initialized. '"
+ FILE_STORAGE_ROOT + "' exists, but '"
+ FILE_STORAGE_NAMESPACES_PROPERTIES + "' does not.");
} else {
throw new IllegalStateException(
"Storage directory '' has been partially initialized. '"
+ namespaceFile.getPath() + "' exists, but '"
+ rootDir.getPath() + "' does not.");
throw new IllegalStateException("Storage directory '"
+ baseDir.getPath() + "' has been partially initialized. '"
+ FILE_STORAGE_NAMESPACES_PROPERTIES + "' exists, but '"
+ FILE_STORAGE_ROOT + "' does not.");
}
}
@ -222,9 +223,11 @@ public class FileStorageImpl implements FileStorage {
private Map<Character, String> confirmNamespaces(
Collection<String> namespaces) throws IOException {
Map<Character, String> map;
Reader reader = null;
try {
reader = new FileReader(this.namespaceFile);
Properties props = new Properties();
props.load(new FileReader(this.namespaceFile));
props.load(reader);
map = new HashMap<Character, String>();
for (Object key : props.keySet()) {
char keyChar = key.toString().charAt(0);
@ -232,6 +235,14 @@ public class FileStorageImpl implements FileStorage {
}
} catch (Exception e) {
throw new IOException("Problem loading the namespace file.");
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Set<String> requestedNamespaces = new HashSet<String>(namespaces);
@ -248,12 +259,29 @@ public class FileStorageImpl implements FileStorage {
return map;
}
// ----------------------------------------------------------------------
// package access methods -- used in unit tests.
// ----------------------------------------------------------------------
File getBaseDir() {
return this.baseDir;
}
Map<Character, String> getNamespaces() {
return this.namespacesMap;
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
/**
* {@inheritDoc}
*
* <p>
* Before creating the file, we may need to create one or more parent
* directories to put it in.
* </p>
*/
@Override
public void createFile(String id, String filename, InputStream bytes)
@ -266,6 +294,16 @@ public class FileStorageImpl implements FileStorage {
File file = FileStorageHelper.getFullPath(this.rootDir, id, filename,
this.namespacesMap);
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
if (!parent.exists()) {
throw new IOException(
"Failed to create parent directories for file with ID '"
+ id + "', file location '" + file + "'");
}
}
OutputStream out = null;
try {

View file

@ -134,8 +134,17 @@
* </li>
* <li>
* <strong>Path breakdown</strong> -
* Finally, path separator characters are inserted after every third
* character in the processed ID string.
* Path separator characters are inserted after every third character
* in the processed ID string.
* </li>
* <li>
* <strong>Exclusion of reserved Windows filenames</strong> -
* Windows will not permit certain specific filename or directory names,
* so if any part of the path would be equal to one of those reserved
* names, it is prefixed with a tilde. The reserved names are:
* CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8,
* COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
* And remember, Windows is case-insensitive.
* </li>
* </ul>
* Examples:
@ -161,7 +170,8 @@
*
* <p>
* The encoding process is the same as the "rare character encoding" and
* "common character encoding" steps used for ID encoding.
* "common character encoding" steps used for ID encoding, except that
* periods are not encoded.
* </p>
*/