diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorage.java
index a9faebcbc..6cb49129a 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorage.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorage.java
@@ -17,6 +17,12 @@ public interface FileStorage {
* {@link ConfigurationProperties} for the file storage base directory.
*/
String PROPERTY_FILE_STORAGE_BASE_DIR = "upload.directory";
+
+ /**
+ * The default implementation will use this key to ask
+ * {@link ConfigurationProperties} for the maximum permissible file size.
+ */
+ String PROPERTY_FILE_MAXIMUM_SIZE = "file.maximum.size";
/**
* The default implementation will use this key to ask
@@ -24,6 +30,11 @@ public interface FileStorage {
*/
String PROPERTY_DEFAULT_NAMESPACE = "Vitro.defaultNamespace";
+ /**
+ * 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.
@@ -50,7 +61,7 @@ public interface FileStorage {
* @throws FileNotFoundException
* if there is no file that matches this ID and filename.
*/
- byte[] getfile(String id, String filename) throws FileNotFoundException,
+ byte[] getFile(String id, String filename) throws FileNotFoundException,
IOException;
/**
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelper.java
index c05d42bf1..0fe4897cf 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelper.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelper.java
@@ -2,59 +2,305 @@
package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
+import static edu.cornell.mannlib.vitro.webapp.utils.filestorage.FileStorage.SHORTY_LENGTH;
+
import java.io.File;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.log4j.Logger;
/**
- * TODO
+ * A collection of utility routines used by the file storage system. Routines
+ * exist to:
+ *
+ * - encode filenames for safe storage
+ * - decode filenames to their original values
+ * - convert an ID (with namespaces) to a path, relative to the root directory
+ *
+ * - convert an ID (with namespaces) to an absolute path
+ * - convert an ID (with namespaces) and a filename to a full path for storing
+ * the file
+ * - parse the string that specifies the maximum size of an uploaded file
+ *
*/
public class FileStorageHelper {
+ private static final Logger LOG = Logger.getLogger(FileStorageHelper.class);
+
+ public static final char HEX_ESCAPE_CHAR = '^';
+
+ public static final String HEX_ENCODE_SOURCES = "\"*+,<=>?^|\\~";
+
+ public static final char[] PATH_SINGLE_CHARACTER_SOURCES = new char[] {
+ '/', ':', '.' };
+ public static final char[] PATH_SINGLE_CHARACTER_TARGETS = new char[] {
+ '=', '+', ',' };
+
+ /** Same as for path, except that a period is not translated. */
+ public static final char[] NAME_SINGLE_CHARACTER_SOURCES = new char[] {
+ '/', ':' };
+ /** Same as for path, except that a period is not translated. */
+ public static final char[] NAME_SINGLE_CHARACTER_TARGETS = new char[] {
+ '=', '+' };
/**
- * @param id
- * @return
- */
- public static String id2Path(String id) {
- // TODO Auto-generated method stub
- throw new RuntimeException("FileStorageHelper.id2Path() not implemented.");
- }
-
- /**
- * @param filename
- * @return
+ * Encode the filename as needed to guard against illegal characters.
+ *
+ * @see edu.cornell.mannlib.vitro.webapp.utils.filestorage
*/
public static String encodeName(String filename) {
- // TODO Auto-generated method stub
- throw new RuntimeException("FileStorageHelper.encodeName() not implemented.");
+ String hexed = addHexEncoding(filename);
+ return addSingleCharacterConversions(hexed,
+ NAME_SINGLE_CHARACTER_SOURCES, NAME_SINGLE_CHARACTER_TARGETS);
}
/**
- * @param rootDir
- * @param id
- * @param filename
- * @return
+ * Encode special characters to hex sequences.
*/
- public static File getFullPath(File rootDir, String id, String filename) {
- // TODO Auto-generated method stub
- throw new RuntimeException("FileStorageHelper.getFullPath() not implemented.");
+ private static String addHexEncoding(String clear) {
+ for (int i = 0; i < clear.length(); i++) {
+ char c = clear.charAt(i);
+ if (c > 255) {
+ throw new InvalidCharacterException(c, i, clear);
+ }
+ }
+
+ StringBuilder result = new StringBuilder();
+
+ for (int i = 0; i < clear.length(); i++) {
+ result.append(hexEncodeCharacter(clear.charAt(i)));
+ }
+
+ LOG.debug("Add hex encodings to '" + clear + "' giving '" + result
+ + "'");
+ return result.toString();
}
/**
- * @param rootDir
- * @param id
- * @return
+ * Create a string holding either the character or its hex-encoding.
*/
- public static File getPathToIdDirectory(File rootDir, String id) {
- // TODO Auto-generated method stub
- throw new RuntimeException("FileStorageHelper.getPathToIdDirectory() not implemented.");
+ private static String hexEncodeCharacter(char c) {
+ if ((c < 0x21) || (c > 0x7e) || (HEX_ENCODE_SOURCES.indexOf(c) >= 0)) {
+ return new StringBuilder().append(HEX_ESCAPE_CHAR).append(
+ toHexDigit(c / 16)).append(toHexDigit(c % 16)).toString();
+
+ } else {
+ return Character.toString(c);
+ }
}
/**
- * @param name
- * @return
+ * Return the correct hex character for this integer value.
*/
- public static String decodeName(String name) {
- // TODO Auto-generated method stub
- throw new RuntimeException("FileStorageHelper.decodeName() not implemented.");
+ private static char toHexDigit(int i) {
+ return "0123456789abcdef".charAt(i);
+ }
+
+ /**
+ * Perform common single-character substitutions.
+ */
+ private static String addSingleCharacterConversions(String encoded,
+ char[] sources, char[] targets) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < encoded.length(); i++) {
+ char c = encoded.charAt(i);
+ result.append(translateSingleCharacter(c, sources, targets));
+ }
+ LOG.debug("Add single character conversions to '" + encoded
+ + "' giving '" + result + "'");
+ return result.toString();
+ }
+
+ /**
+ * If a character found in the "from" set, return its corresponding
+ * character from the "to" set. Otherwise, return the character itself.
+ */
+ private static char translateSingleCharacter(char c, char[] from, char[] to) {
+ for (int j = 0; j < from.length; j++) {
+ if (c == from[j]) {
+ return to[j];
+ }
+ }
+ return c;
+ }
+
+ /**
+ * 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,
+ NAME_SINGLE_CHARACTER_SOURCES, NAME_SINGLE_CHARACTER_TARGETS);
+ return removeHexEncoding(hexed);
+ }
+
+ /**
+ * Convert common single-character substitutions back to their original
+ * values.
+ */
+ private static String removeSingleCharacterConversions(String cleaned,
+ char[] sources, char[] targets) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < cleaned.length(); i++) {
+ char c = cleaned.charAt(i);
+ result.append(translateSingleCharacter(c, targets, sources));
+ }
+ LOG.debug("Remove single character conversions from '" + cleaned
+ + "' giving '" + result + "'");
+ return result.toString();
+ }
+
+ /**
+ * Convert hex-encoded characters back to their original values.
+ */
+ private static String removeHexEncoding(String encoded) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < encoded.length(); i++) {
+ char c = encoded.charAt(i);
+ if (c == HEX_ESCAPE_CHAR) {
+ try {
+ if (i + 2 >= encoded.length()) {
+ throw new InvalidPathException(
+ "Invalid hex encoding in path: '" + encoded
+ + "'");
+ }
+ String hexChars = encoded.substring(i + 1, i + 3);
+ int value = Integer.parseInt(hexChars, 16);
+ result.append((char) value);
+ i += 2;
+ } catch (NumberFormatException e) {
+ throw new InvalidPathException(
+ "Invalid hex encoding in path: '" + encoded + "'",
+ e);
+ }
+ } else {
+ result.append(c);
+ }
+ }
+ LOG.debug("Remove hex encodings from '" + encoded + "' giving '"
+ + result + "'");
+ return result.toString();
+ }
+
+ /**
+ * 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).
+ *
+ * @see edu.cornell.mannlib.vitro.webapp.utils.filestorage
+ */
+ public static String id2Path(String id, Map namespacesMap) {
+ char prefix = 0;
+ String localName = id;
+ for (Entry entry : namespacesMap.entrySet()) {
+ String namespace = entry.getValue();
+ if (id.startsWith(namespace)) {
+ prefix = entry.getKey();
+ localName = id.substring(namespace.length());
+ break;
+ }
+ }
+
+ String hexed = addHexEncoding(localName);
+ String cleaned = addSingleCharacterConversions(hexed,
+ PATH_SINGLE_CHARACTER_SOURCES, PATH_SINGLE_CHARACTER_TARGETS);
+ String prefixed = applyPrefixChar(prefix, cleaned);
+ return insertPathDelimiters(prefixed);
+ }
+
+ /**
+ * Now that the cleaning is complete, add the prefix if there is one.
+ */
+ private static String applyPrefixChar(char prefix, String cleaned) {
+ if (prefix == 0) {
+ return cleaned;
+ } else {
+ return prefix + "~" + cleaned;
+ }
+ }
+
+ /**
+ * Add path delimiters as needed to turn the cleaned prefixed string into a
+ * relative path.
+ */
+ private static String insertPathDelimiters(String prefixed) {
+ StringBuilder path = new StringBuilder();
+ for (int i = 0; i < prefixed.length(); i++) {
+ if ((i % SHORTY_LENGTH == 0) && (i > 0)) {
+ path.append(File.separatorChar);
+ }
+ path.append(prefixed.charAt(i));
+ }
+ return path.toString();
+ }
+
+ /**
+ * Translate the object ID and the file storage root directory into a full
+ * path to the directory that would represent that ID.
+ *
+ * @see edu.cornell.mannlib.vitro.webapp.utils.filestorage
+ */
+ public static File getPathToIdDirectory(String id,
+ Map namespacesMap, File rootDir) {
+ return new File(rootDir, id2Path(id, namespacesMap));
+ }
+
+ /**
+ * Translate the object ID, the file storage root directory and the filename
+ * into a full path to where the file would be stored.
+ *
+ * @see edu.cornell.mannlib.vitro.webapp.utils.filestorage
+ */
+ public static File getFullPath(File rootDir, String id, String filename,
+ Map namespacesMap) {
+ return new File(getPathToIdDirectory(id, namespacesMap, rootDir),
+ encodeName(filename));
+ }
+
+ /**
+ * Translate the configuration property for maximum file size from a
+ * String
to a long
.
+ *
+ * The string must be represent a positive integer, optionally followed by
+ * "K", "M", or "G" (to indicate kilobytes, megabytes, or gigabytes).
+ */
+ public static long parseMaximumFileSize(String fileSizeString) {
+ long factor = 1L;
+ String integerString;
+ int shorter = fileSizeString.length() - 1;
+ if (fileSizeString.endsWith("K")) {
+ factor = 1024L;
+ integerString = fileSizeString.substring(0, shorter);
+ } else if (fileSizeString.endsWith("M")) {
+ factor = 1024L * 1024L;
+ integerString = fileSizeString.substring(0, shorter);
+ } else if (fileSizeString.endsWith("G")) {
+ factor = 1024L * 1024L * 1024L;
+ integerString = fileSizeString.substring(0, shorter);
+ } else {
+ integerString = fileSizeString;
+ }
+
+ long value = 0;
+ try {
+ value = Long.parseLong(integerString);
+
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "Maximum file size is invalid: '" + fileSizeString
+ + "'. Must be a positive integer, "
+ + "optionally followed by 'K', 'M', or 'G'");
+ }
+
+ if (value <= 0L) {
+ throw new IllegalArgumentException(
+ "Maximum file size must be more than 0: '" + fileSizeString
+ + "'");
+ }
+
+ return value * factor;
}
}
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImpl.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImpl.java
index 98787606c..7a85d03d8 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImpl.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImpl.java
@@ -4,8 +4,10 @@ package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
@@ -32,6 +34,7 @@ public class FileStorageImpl implements FileStorage {
private final File baseDir;
private final File rootDir;
private final File namespaceFile;
+ private final long maximumFileSize;
private final Map namespacesMap;
// ----------------------------------------------------------------------
@@ -50,7 +53,7 @@ public class FileStorageImpl implements FileStorage {
* missing, or if it isn't in the expected form.
*/
FileStorageImpl() throws IOException {
- this(figureBaseDir(), figureFileNamespace());
+ this(figureBaseDir(), figureFileNamespace(), figureMaximumFileSize());
}
/**
@@ -62,17 +65,20 @@ public class FileStorageImpl implements FileStorage {
* if the configuration property doesn't point to an existing,
* writeable directory.
*/
- FileStorageImpl(File baseDir, Collection namespaces)
- throws IOException {
+ FileStorageImpl(File baseDir, Collection namespaces,
+ long maximumFileSize) throws IOException {
checkBaseDirValid(baseDir);
checkNamespacesValid(namespaces);
+ checkMaximumFileSizeValid(maximumFileSize);
this.baseDir = baseDir;
- this.rootDir = new File(baseDir, "file_storage_root");
+ this.rootDir = new File(this.baseDir, "file_storage_root");
this.namespaceFile = new File(baseDir,
"file_storage_namespaces.properties");
+ this.maximumFileSize = maximumFileSize;
+
if (rootDir.exists() && namespaceFile.exists()) {
this.namespacesMap = confirmNamespaces(namespaces);
} else if (!rootDir.exists() && !namespaceFile.exists()) {
@@ -91,6 +97,13 @@ public class FileStorageImpl implements FileStorage {
}
}
+ private void checkMaximumFileSizeValid(long maximumFileSize) {
+ if (maximumFileSize < 0) {
+ throw new IllegalArgumentException(
+ "Maximum file size may not be negative.");
+ }
+ }
+
private void checkNamespacesValid(Collection namespaces) {
if (namespaces == null) {
throw new NullPointerException("namespaces may not be null.");
@@ -172,6 +185,23 @@ public class FileStorageImpl implements FileStorage {
return Collections.singleton(fileNamespace);
}
+ /**
+ * Get the configuration property for the maximum file size and translate it
+ * into a long integer. It must be a positive integer, optionally followed
+ * by "K", "M", or "G" (to indicate kilobytes, megabytes, or gigabytes).
+ */
+ private static long figureMaximumFileSize() {
+ String fileSizeString = ConfigurationProperties
+ .getProperty(PROPERTY_FILE_MAXIMUM_SIZE);
+ if (fileSizeString == null) {
+ throw new IllegalArgumentException(
+ "Configuration properties must contain a value for '"
+ + PROPERTY_FILE_MAXIMUM_SIZE + "'");
+ }
+
+ return FileStorageHelper.parseMaximumFileSize(fileSizeString);
+ }
+
/**
* Assign arbitrary prefixes to these namespaces.
*/
@@ -263,7 +293,8 @@ public class FileStorageImpl implements FileStorage {
}
- File file = FileStorageHelper.getFullPath(this.rootDir, id, filename);
+ File file = FileStorageHelper.getFullPath(this.rootDir, id, filename,
+ this.namespacesMap);
OutputStream out = null;
try {
@@ -297,7 +328,7 @@ public class FileStorageImpl implements FileStorage {
}
File file = FileStorageHelper.getFullPath(this.rootDir, id,
- existingFilename);
+ existingFilename, this.namespacesMap);
file.delete();
if (file.exists()) {
@@ -317,7 +348,8 @@ public class FileStorageImpl implements FileStorage {
*/
@Override
public String getFilename(String id) throws IOException {
- File dir = FileStorageHelper.getPathToIdDirectory(this.rootDir, id);
+ File dir = FileStorageHelper.getPathToIdDirectory(id,
+ this.namespacesMap, this.rootDir);
if ((!dir.exists()) || (!dir.isDirectory())) {
return null;
@@ -344,16 +376,44 @@ public class FileStorageImpl implements FileStorage {
/**
* {@inheritDoc}
+ *
+ * @throws IOException
+ * if the file is larger than the maximum allowable size.
*/
@Override
- public byte[] getfile(String id, String filename)
- throws FileNotFoundException, IOException {
- // gets the bytes from the file
- // throws FileNotFoundException if the file does not exist
- // throws IOException
+ public byte[] getFile(String id, String filename) throws IOException {
- // TODO Auto-generated method stub
- throw new RuntimeException("FileStorage.getfile() not implemented.");
+ File file = FileStorageHelper.getFullPath(this.rootDir, id, filename,
+ this.namespacesMap);
+
+ if (!file.exists()) {
+ throw new FileNotFoundException("No file exists with ID '" + id
+ + "', file location '" + file + "'");
+ }
+
+ InputStream in = null;
+ try {
+ in = new BufferedInputStream(new FileInputStream(file));
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[4096];
+ int howMany;
+ while (-1 != (howMany = in.read(buffer))) {
+ if (bytes.size() > this.maximumFileSize) {
+ throw new IOException("File is too large at this ID: '"
+ + id + "', file location '" + file + "'");
+ }
+ bytes.write(buffer, 0, howMany);
+ }
+ bytes.close();
+ return bytes.toByteArray();
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
}
-
}
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/InvalidCharacterException.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/InvalidCharacterException.java
new file mode 100644
index 000000000..7e1cf2cd4
--- /dev/null
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/InvalidCharacterException.java
@@ -0,0 +1,27 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
+
+/**
+ * Indicates that an object ID contains an invalid character.
+ */
+public class InvalidCharacterException extends RuntimeException {
+ private final char invalid;
+ private final int position;
+ private final String context;
+
+
+ public InvalidCharacterException(char invalid, int position, String context) {
+ this.invalid = invalid;
+ this.position = position;
+ this.context = context;
+ }
+
+
+ @Override
+ public String getMessage() {
+ return String.format(
+ "Invalid character '%1$c'(0x%1$x) at position %2$d in '%3$s'",
+ (int)invalid, position, context);
+ }
+}
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/InvalidPathException.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/InvalidPathException.java
new file mode 100644
index 000000000..05138a30e
--- /dev/null
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/InvalidPathException.java
@@ -0,0 +1,27 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
+
+/**
+ * Indicates a PairTree path ("ppath" or "relative path") that is not correctly
+ * formed, and cannot be converted to an object ID.
+ */
+public class InvalidPathException extends RuntimeException {
+
+ public InvalidPathException() {
+ super();
+ }
+
+ public InvalidPathException(String message) {
+ super(message);
+ }
+
+ public InvalidPathException(Throwable cause) {
+ super(cause);
+ }
+
+ public InvalidPathException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/package-info.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/package-info.java
index 377839531..e1c1ddfa7 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/package-info.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/filestorage/package-info.java
@@ -44,6 +44,9 @@
* A namespace/prefix capability will be used to shorten file paths,
* but with more flexibility than the prefix algorithm given in the specification.
*
+ *
+ * "shorty" directory names may be up to 3 characters long, not 2.
+ *
*
*
*
@@ -91,8 +94,61 @@
*
*
* ID encoding
- *
+ *
*
+ * This is a multi-step process:
+ *
+ * -
+ * Namespace recognition -
+ * If the ID begins with a recognized namespace, then that namespace is
+ * stripped from the ID, and the prefix associated with that namespace
+ * is set aside for later in the process.
+ *
+ * -
+ * Rare character encoding -
+ * Illegal characters are translated to their hexadecimal equivalents,
+ * as are some rarely used characters which will be given other
+ * purposes later in the process. The translated characters include any
+ * octet outside of the visible ASCII range (21-7e), and these additional
+ * characters:
+ *
" * + , < = > ? ^ | \ ~
+ * The hexadecimal encoding consists of a caret followed by 2 hex digits,
+ * e.g.: ^7C
+ *
+ * -
+ * Common character encoding -
+ * To keep the file paths short and readable, characters that are used
+ * commonly in IDs but may be illegal in the file system are translated
+ * to a single, lesser-used character.
+ *
+ * - / becomes =
+ * - : becomes +
+ * - . becomes ,
+ *
+ *
+ * -
+ * Prefixing -
+ * If a namespace was recognized on the ID in the first step, the
+ * associated prefix letter will be prepended to the string, with a
+ * tilde separator.
+ *
+ * -
+ * Path breakdown -
+ * Finally, path separator characters are inserted after every third
+ * character in the processed ID string.
+ *
+ *
+ * Examples:
+ *
ark:/13030/xt12t3
becomes
+ * ark/+=1/303/0=x/t12/t3
+ *
http://n2t.info/urn:nbn:se:kb:repos-1
becomes
+ * htt/p+=/=n2/t,i/nfo/=ur/n+n/bn+/se+/kb+/rep/os-/1
+ *
what-the-*@?#!^!~?
becomes
+ * wha/t-t/he-/^2a/@^3/f#!/^5e/!^7/e^3/f
+ *
http://vivo.myDomain.edu/file/n3424
with namespace
+ * http://vivo.myDomain.edu/file/
and prefix
+ * a
becomes
+ * a~n/342/4
*
*
* Filename encoding
@@ -103,6 +159,10 @@
* to be required, since few files are named with the special characters.
*
*
+ *
+ * The encoding process is the same as the "rare character encoding" and
+ * "common character encoding" steps used for ID encoding.
+ *
*/
package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageFactoryTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageFactoryTest.java
index 578084ab8..5fafa7375 100644
--- a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageFactoryTest.java
+++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageFactoryTest.java
@@ -3,6 +3,7 @@
package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
import static edu.cornell.mannlib.vitro.webapp.utils.filestorage.FileStorageFactory.PROPERTY_IMPLEMETATION_CLASSNAME;
+import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -28,7 +29,8 @@ import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
import edu.cornell.mannlib.vitro.webapp.ConfigurationProperties;
/**
- * TODO
+ * This just checks the interaction between the configuration properties, the
+ * system properties, and the implementation of {@link FileStorage}.
*/
public class FileStorageFactoryTest extends AbstractTestClass {
private static final String configProperties = "#mock config properties file\n";
@@ -62,7 +64,7 @@ public class FileStorageFactoryTest extends AbstractTestClass {
@Test
public void createDefaultImplementation() throws IOException {
setConfigurationProperties(tempDir.getPath(),
- "http://vivo.myDomain.edu/individual/");
+ "http://vivo.myDomain.edu/individual/", "50M");
FileStorage fs = FileStorageFactory.getFileStorage();
assertEquals("implementation class", FileStorageImpl.class, fs
.getClass());
@@ -80,13 +82,20 @@ public class FileStorageFactoryTest extends AbstractTestClass {
@Test(expected = IllegalArgumentException.class)
public void baseDirectoryDoesntExist() throws IOException {
setConfigurationProperties("/bogus/Directory",
- "http://vivo.myDomain.edu/individual/");
+ "http://vivo.myDomain.edu/individual/", "50M");
FileStorageFactory.getFileStorage();
}
@Test(expected = IllegalArgumentException.class)
public void defaultNamespaceIsBogus() throws IOException {
- setConfigurationProperties(tempDir.getPath(), "namespace");
+ setConfigurationProperties(tempDir.getPath(), "namespace", "50M");
+ FileStorageFactory.getFileStorage();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void invalidMaximumFileSize() throws IOException {
+ setConfigurationProperties(tempDir.getPath(),
+ "http://vivo.myDomain.edu/individual/", "50X");
FileStorageFactory.getFileStorage();
}
@@ -115,10 +124,11 @@ public class FileStorageFactoryTest extends AbstractTestClass {
// ----------------------------------------------------------------------
private void setConfigurationProperties(String baseDir,
- String defaultNamespace) {
+ String defaultNamespace, String maxFileSize) {
Map map = new HashMap();
- map.put("upload.directory", baseDir);
- map.put("Vitro.defaultNamespace", defaultNamespace);
+ map.put(FileStorage.PROPERTY_FILE_STORAGE_BASE_DIR, baseDir);
+ map.put(FileStorage.PROPERTY_DEFAULT_NAMESPACE, defaultNamespace);
+ map.put(FileStorage.PROPERTY_FILE_MAXIMUM_SIZE, maxFileSize);
try {
Field f = ConfigurationProperties.class.getDeclaredField("theMap");
@@ -147,7 +157,7 @@ public class FileStorageFactoryTest extends AbstractTestClass {
return "filename";
}
- public byte[] getfile(String id, String filename)
+ public byte[] getFile(String id, String filename)
throws FileNotFoundException, IOException {
return new byte[0];
}
@@ -171,7 +181,7 @@ public class FileStorageFactoryTest extends AbstractTestClass {
return "filename";
}
- public byte[] getfile(String id, String filename)
+ public byte[] getFile(String id, String filename)
throws FileNotFoundException, IOException {
return new byte[0];
}
diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelperTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelperTest.java
new file mode 100644
index 000000000..d75350dd9
--- /dev/null
+++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelperTest.java
@@ -0,0 +1,239 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+/**
+ *
+ */
+public class FileStorageHelperTest {
+ private static String RAW_NAME_1 = "simpleName";
+ private static String ENCODED_NAME_1 = "simpleName";
+ private static String RAW_NAME_2 = "common:/Chars.pdf";
+ private static String ENCODED_NAME_2 = "common+=Chars.pdf";
+ private static String RAW_NAME_3 = "rare\"+~chars";
+ private static String ENCODED_NAME_3 = "rare^22^2b^7echars";
+ private static String RAW_NAME_4 = "combination+ EMPTY_NAMESPACES = Collections
+ .emptyMap();
+ private static Map NAMESPACES = initPrefixMap();
+
+ private static Map initPrefixMap() {
+ Map map = new HashMap();
+ map.put('a', "junk");
+ map.put('b', "http://vivo.myDomain.edu/file/");
+ return map;
+ }
+
+ // ----------------------------------------------------------------------
+ // encodeName
+ // ----------------------------------------------------------------------
+
+ @Test
+ public void encodeName1() {
+ assertNameEncoding(RAW_NAME_1, ENCODED_NAME_1);
+ }
+
+ @Test
+ public void encodeName2() {
+ assertNameEncoding(RAW_NAME_2, ENCODED_NAME_2);
+ }
+
+ @Test
+ public void encodeName3() {
+ assertNameEncoding(RAW_NAME_3, ENCODED_NAME_3);
+ }
+
+ @Test
+ public void encodeName4() {
+ assertNameEncoding(RAW_NAME_4, ENCODED_NAME_4);
+ }
+
+ @Test
+ public void encodeName5() {
+ assertNameEncoding(RAW_NAME_5, ENCODED_NAME_5);
+ }
+
+ @Test(expected = InvalidCharacterException.class)
+ public void encodeName6() {
+ FileStorageHelper.encodeName(RAW_NAME_6);
+ }
+
+ private void assertNameEncoding(String rawName, String expected) {
+ String encoded = FileStorageHelper.encodeName(rawName);
+ assertEquals("encoded name", expected, encoded);
+ }
+
+ // ----------------------------------------------------------------------
+ // decodeName
+ // ----------------------------------------------------------------------
+
+ @Test
+ public void decodeName1() {
+ assertNameDecoding(ENCODED_NAME_1, RAW_NAME_1);
+ }
+
+ @Test
+ public void decodeName2() {
+ assertNameDecoding(ENCODED_NAME_2, RAW_NAME_2);
+ }
+
+ @Test
+ public void decodeName3() {
+ assertNameDecoding(ENCODED_NAME_3, RAW_NAME_3);
+ }
+
+ @Test
+ public void decodeName4() {
+ assertNameDecoding(ENCODED_NAME_4, RAW_NAME_4);
+ }
+
+ @Test
+ public void decodeName5() {
+ assertNameDecoding(ENCODED_NAME_5, RAW_NAME_5);
+ }
+
+ private void assertNameDecoding(String encodedName, String expected) {
+ String decoded = FileStorageHelper.decodeName(encodedName);
+ assertEquals("decodedName", expected, decoded);
+ }
+
+ // ----------------------------------------------------------------------
+ // idToPath
+ // ----------------------------------------------------------------------
+
+ @Test
+ public void idToPath1() {
+ assertIdToPath(ID_1, EMPTY_NAMESPACES, RELATIVE_PATH_1);
+ }
+
+ @Test
+ public void idToPath2() {
+ assertIdToPath(ID_2, EMPTY_NAMESPACES, RELATIVE_PATH_2);
+ }
+
+ @Test
+ public void idToPath3() {
+ assertIdToPath(ID_3, EMPTY_NAMESPACES, RELATIVE_PATH_3);
+ }
+
+ @Test
+ public void idToPath3WithNamespace() {
+ assertIdToPath(ID_3, NAMESPACES, RELATIVE_PREFIXED_PATH_3);
+ }
+
+ private void assertIdToPath(String id, Map namespaces,
+ String expected) {
+ String adjustedExpected = expected.replace('/', File.separatorChar);
+ String relativePath = FileStorageHelper.id2Path(id, namespaces);
+ assertEquals("idToPath", adjustedExpected, relativePath);
+ }
+
+ // ----------------------------------------------------------------------
+ // getPathToIdDirectory
+ // ----------------------------------------------------------------------
+
+ @Test
+ public void getPathToIdDirectory1() {
+ assertPathToIdDirectory(ID_1, EMPTY_NAMESPACES, ROOT_DIR_1,
+ ABSOLUTE_PATH_1);
+ }
+
+ @Test
+ public void getPathToIdDirectory2() {
+ assertPathToIdDirectory(ID_1, EMPTY_NAMESPACES, ROOT_DIR_2,
+ ABSOLUTE_PATH_2);
+ }
+
+ private void assertPathToIdDirectory(String id,
+ Map namespaces, File rootDir, File expected) {
+ File actual = FileStorageHelper.getPathToIdDirectory(id, namespaces,
+ rootDir);
+ File adjustedExpected = new File(expected.getPath().replace('/',
+ File.separatorChar));
+ assertEquals("pathToIdDirectory", adjustedExpected, actual);
+ }
+
+ // ----------------------------------------------------------------------
+ // getFullPath
+ // ----------------------------------------------------------------------
+
+ @Test
+ public void getFullPath() {
+ File actual = FileStorageHelper.getFullPath(FULL_ROOT, FULL_ID,
+ FULL_NAME, NAMESPACES);
+ assertEquals("fullPath", FULL_RESULT_PATH, actual);
+ }
+
+ // ----------------------------------------------------------------------
+ // parseMaximumFileSize
+ // ----------------------------------------------------------------------
+
+ @Test
+ public void parseMaximumFileSizeBare() {
+ long size = FileStorageHelper.parseMaximumFileSize("1467898");
+ assertEquals("", 1467898, size);
+ }
+
+ @Test
+ public void parseMaximumFileSizeWithSuffixes() {
+ long size = FileStorageHelper.parseMaximumFileSize("152K");
+ assertEquals("", 152L * 1024L, size);
+
+ size = FileStorageHelper.parseMaximumFileSize("47M");
+ assertEquals("", 47L * 1024L * 1024L, size);
+
+ size = FileStorageHelper.parseMaximumFileSize("3G");
+ assertEquals("", 3L * 1024L * 1024L * 1024L, size);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseMaximumFileSizeInvalidSuffix() {
+ FileStorageHelper.parseMaximumFileSize("152X");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseMaximumFileSizeNegativeNumber() {
+ FileStorageHelper.parseMaximumFileSize("-3K");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseMaximumFileSizeEmbeddedBadCharacter() {
+ FileStorageHelper.parseMaximumFileSize("1G52K");
+ }
+
+}
diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImplTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImplTest.java
index 91dad1f32..701ff00ca 100644
--- a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImplTest.java
+++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImplTest.java
@@ -2,16 +2,18 @@
package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
-import static org.junit.Assert.*;
+import static org.junit.Assert.fail;
import org.junit.Ignore;
import org.junit.Test;
+import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
+
/**
* Test the FileStorage methods. The zero-argument constructor was tested in
* {@link FileStorageFactoryTest}.
*/
-public class FileStorageImplTest {
+public class FileStorageImplTest extends AbstractTestClass {
@Ignore
@Test
public void baseDirDoesntExist() {
@@ -48,4 +50,69 @@ public class FileStorageImplTest {
fail("initializedNamespacesDontMatch not implemented");
}
+ @Ignore
+ @Test
+ public void createFileOriginal() {
+ fail("createFileOriginal not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void createFileOverwrite() {
+ fail("createFileOverwrite not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void createFileConflictingName() {
+ fail("createFileConflictingName not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void getFilenameExists() {
+ fail("getFilenameExists not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void getFilenameDoesntExist() {
+ fail("getFilenameDoesntExist not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void getFilenameMultipleFiles() {
+ fail("getFilenameMultipleFiles not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void getFileFound() {
+ fail("getFilenameFound not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void getFileNotFound() {
+ fail("getFileNotFound not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void getFileTooLarge() {
+ fail("getFileTooLarge not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void deleteFileExists() {
+ fail("deleteFileExists not implemented");
+ }
+
+ @Ignore
+ @Test
+ public void deleteFileDoesntExist() {
+ fail("deleteFileDoesntExist not implemented");
+ }
}