From b9dfa9023b22777dc4df8880d51b24d351aaa21f Mon Sep 17 00:00:00 2001
From: jeb228
Date: Thu, 20 May 2010 20:49:42 +0000
Subject: [PATCH] NIHVIVO-160 Start coding the back-end
---
.../FileAlreadyExistsException.java | 38 ++++
.../webapp/utils/filestorage/FileStorage.java | 108 ++++++++++
.../utils/filestorage/FileStorageHelper.java | 10 +
.../utils/filestorage/FileStorageImpl.java | 192 ++++++++++++++++++
.../utils/filestorage/package-info.java | 100 +++++++++
5 files changed, 448 insertions(+)
create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileAlreadyExistsException.java
create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorage.java
create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelper.java
create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImpl.java
create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/package-info.java
diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileAlreadyExistsException.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileAlreadyExistsException.java
new file mode 100644
index 000000000..7bd7a3c75
--- /dev/null
+++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileAlreadyExistsException.java
@@ -0,0 +1,38 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
+
+import java.io.IOException;
+
+/**
+ * Indicates that a file already exists with the specified ID, but with a
+ * different filename from the one specified.
+ */
+public class FileAlreadyExistsException extends IOException {
+ private final String id;
+ private final String existingFilename;
+ private final String requestedFilename;
+
+ public FileAlreadyExistsException(String id, String existingFilename,
+ String requestedFilename) {
+ super("File with a different name already exists at this ID: '" + id
+ + "', requested filename: '" + requestedFilename
+ + "', existing filename: '" + existingFilename + "'");
+ this.id = id;
+ this.existingFilename = existingFilename;
+ this.requestedFilename = requestedFilename;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getExistingFilename() {
+ return existingFilename;
+ }
+
+ public String getRequestedFilename() {
+ return requestedFilename;
+ }
+
+}
diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorage.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorage.java
new file mode 100644
index 000000000..7abd05cf3
--- /dev/null
+++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorage.java
@@ -0,0 +1,108 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import edu.cornell.mannlib.vitro.webapp.ConfigurationProperties;
+
+/**
+ * The "interface" for the File Storage system. All methods are abstract except
+ * for the factory method.
+ */
+public abstract class FileStorage {
+ /**
+ * If this system property is set, it will be taken as the name of the
+ * implementing class.
+ */
+ public static final String PROPERTY_IMPLEMETATION_CLASSNAME = FileStorage.class
+ .getName();
+
+ /**
+ * The default implementation will use this key to ask
+ * {@link ConfigurationProperties} for the file storage base directory.
+ */
+ public static final String PROPERTY_FILE_STORAGE_BASE_DIR = "upload.directory";
+
+ /**
+ * The default implementation will use this key to ask
+ * {@link ConfigurationProperties} for the default URI namespace.
+ */
+ public static final String PROPERTY_DEFAULT_NAMESPACE = "Vitro.defaultNamespace";
+
+ /**
+ *
+ * Get an instance of {@link FileStorage}. By default, this will be an
+ * instance of {@link FileStorageImpl}.
+ *
+ *
+ * If the System Property named by
+ * {#SYSTEM_PROPERTY_IMPLEMETATION_CLASSNAME} is set, it must contain the
+ * name of the implementation class, which must be a sub-class of
+ * {@link FileStorage}, and must have a public, no-argument constructor.
+ *
+ */
+ public static FileStorage getInstance() {
+ String className = System.getProperty(PROPERTY_IMPLEMETATION_CLASSNAME);
+ if (className == null) {
+ return new FileStorageImpl();
+ }
+
+ try {
+ Class> clazz = Class.forName(className);
+ Object instance = clazz.newInstance();
+ return FileStorage.class.cast(instance);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException(
+ "Can't create a FileStorage instance", e);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException(
+ "Can't create a FileStorage instance", e);
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException(
+ "Can't create a FileStorage instance", e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(
+ "Can't create a FileStorage instance", e);
+ }
+ }
+
+ /**
+ * Store the bytes from this stream as a file with the specified ID and
+ * filename. If the file already exists, it is over-written.
+ *
+ * @throws FileAlreadyExistsException
+ * if a file already exists with this ID but with a different
+ * filename.
+ *
+ */
+ public abstract void createFile(String id, String filename,
+ InputStream bytes) throws FileAlreadyExistsException, IOException;
+
+ /**
+ * If a file exists with this ID, get its name.
+ *
+ * @return The name of the file (un-encoded) if it exists, or
+ * null
if it does not.
+ */
+ public abstract String getFilename(String id) throws IOException;
+
+ /**
+ * Get the contents of the file with this ID and this filename.
+ *
+ * @throws FileNotFoundException
+ * if there is no file that matches this ID and filename.
+ */
+ public abstract byte[] getfile(String id, String filename)
+ throws FileNotFoundException, IOException;
+
+ /**
+ * If a file exists with this ID, it will be deleted, regardless of the file
+ * name. If no such file exists, no action is taken, no exception is thrown.
+ *
+ * @return true if a file existed, false
otherwise.
+ */
+ public abstract boolean deleteFile(String id) throws IOException;
+}
diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelper.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelper.java
new file mode 100644
index 000000000..6060496c6
--- /dev/null
+++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageHelper.java
@@ -0,0 +1,10 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
+
+/**
+ * TODO
+ */
+public class FileStorageHelper {
+
+}
diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImpl.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImpl.java
new file mode 100644
index 000000000..5ec7d243d
--- /dev/null
+++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/FileStorageImpl.java
@@ -0,0 +1,192 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Collections;
+
+import edu.cornell.mannlib.vitro.webapp.ConfigurationProperties;
+
+/**
+ * The default implementation of {@link FileStorage}.
+ */
+public class FileStorageImpl extends FileStorage {
+
+ // static FileStorage getInstance()
+ // gets baseDir and namespaces from ConfigurationProperties
+ // gets instance class from system properties
+ // throws IllegalStateException if requires properties are missing
+ // throws IOException
+ //
+ // void createFile(String id, String filename, InputStream bytes)
+ // stores the bytes as a file with this name under this id
+ // if the file already exists, over-writes it
+ // throws FileAlreadyExistsException if a file already exists under this id
+ // with a different name
+ // throws IOException
+ //
+ // String getfilename(String id)
+ // returns the name of the file at this ID, or null if there is none.
+ // throws IOException
+ //
+ // byte[] getFile(String id, String filename)
+ // gets the bytes from the file
+ // throws FileNotFoundException if the file does not exist
+ // throws IOException
+ //
+ // boolean deleteFile(String id)
+ // removes the file at this id, returns true
+ // if no such file, takes no action, returns false
+ // throws IOException
+ //
+ // FileStorageImpl
+ //
+
+ /**
+ * Use the configuration properties to create an instance.
+ *
+ * @throws IllegalArgumentException
+ * if the configuration property for the base directory is
+ * missing, or if it doesn't point to an existing, writeable
+ * directory.
+ * @throws IllegalArgumentException
+ * if the configuration property for the default namespace is
+ * missing, or if it isn't in the expected form.
+ */
+ FileStorageImpl() {
+ this(figureBaseDir(), figureFileNamespace());
+ }
+
+ // package-level constructor(File baseDir, Collection namespaces),
+ // gets properties from arguments
+ // if baseDir is not initialized with file_storage_root and
+ // file_storage_prefixMap, do it.
+ // otherwise check for correctness and consistency
+ // throws IllegalStateException if partially initialized
+ // throws IllegalStateException if already initialized and namespaces don't
+ // match
+
+ /**
+ * Use the arguments to create an instance. If the base directory is empty,
+ * initialize it. Otherwise, check that it was initialized to the same
+ * namespaces.
+ *
+ * @throws IllegalArgumentException
+ * if the configuration property doesn't point to an existing,
+ * writeable directory.
+ */
+ FileStorageImpl(File baseDir, Collection namespaces) {
+ if (baseDir == null) {
+ throw new NullPointerException("baseDir may not be null.");
+ }
+ if (namespaces == null) {
+ throw new NullPointerException("namespaces may not be null.");
+ }
+ if (!baseDir.exists()) {
+ throw new IllegalArgumentException(
+ "File upload directory does not exist: '"
+ + baseDir.getPath() + "'");
+ }
+ if (!baseDir.isDirectory()) {
+ throw new IllegalArgumentException(
+ "File upload directory is not a directory: '"
+ + baseDir.getPath() + "'");
+ }
+ if (!baseDir.canWrite()) {
+ throw new IllegalArgumentException(
+ "File upload directory is not writeable: '"
+ + baseDir.getPath() + "'");
+ }
+
+ }
+
+ /**
+ * Get the configuration property for the file storage base directory, and
+ * check that it points to an existing, writeable directory.
+ */
+ private static File figureBaseDir() {
+ String baseDirPath = ConfigurationProperties
+ .getProperty(PROPERTY_FILE_STORAGE_BASE_DIR);
+ if (baseDirPath == null) {
+ throw new IllegalArgumentException(
+ "Configuration properties must contain a value for '"
+ + PROPERTY_FILE_STORAGE_BASE_DIR + "'");
+ }
+ return new File(baseDirPath);
+ }
+
+ /**
+ * Get the configuration property for the default namespace, and derive the
+ * file namespace from it. The default namespace is assumed to be in this
+ * form: http://vivo.mydomain.edu/individual/
+ *
+ * @returns the file namespace is assumed to be in this form:
+ * http://vivo.mydomain.edu/file/
+ */
+ private static Collection figureFileNamespace() {
+ String defaultNamespace = ConfigurationProperties
+ .getProperty(PROPERTY_DEFAULT_NAMESPACE);
+ if (defaultNamespace == null) {
+ throw new IllegalArgumentException(
+ "Configuration properties must contain a value for '"
+ + PROPERTY_DEFAULT_NAMESPACE + "'");
+ }
+
+ String defaultSuffix = "/individual/";
+ String fileSuffix = "/file/";
+
+ if (!defaultNamespace.endsWith(defaultSuffix)) {
+ throw new IllegalArgumentException(
+ "Default namespace does not match the expected form: '"
+ + defaultNamespace + "'");
+ }
+
+ int hostLength = defaultNamespace.length() - defaultSuffix.length();
+ String fileNamespace = defaultNamespace.substring(0, hostLength)
+ + fileSuffix;
+ return Collections.singleton(fileNamespace);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void createFile(String id, String filename, InputStream bytes)
+ throws FileAlreadyExistsException, IOException {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("FileStorage.createFile() not implemented.");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean deleteFile(String id) throws IOException {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("FileStorage.deleteFile() not implemented.");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getFilename(String id) throws IOException {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("FileStorage.getFilename() not implemented.");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public byte[] getfile(String id, String filename)
+ throws FileNotFoundException, IOException {
+ // TODO Auto-generated method stub
+ throw new RuntimeException("FileStorage.getfile() not implemented.");
+ }
+
+}
diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/package-info.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/package-info.java
new file mode 100644
index 000000000..157d6a660
--- /dev/null
+++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/filestorage/package-info.java
@@ -0,0 +1,100 @@
+/**
+ *
+ * The code in this package implements the Vitro file-storage system.
+ *
+ *
+ * Relationship to PairTree
+ *
+ *
+ * The system incorporates a number of ideas from the PairTree specification,
+ *
+ * -
+ * The basic pairtree algorithm –
+ * mapping an encoded identifier string into a filesystem directory path.
+ *
+ * -
+ * Identifier string cleaning –
+ * encoding identifiers in a two-step process so that all illegal
+ * characters are eliminated, but some commonly-used
+ * illegal characters are handled by simple substitution.
+ * Actually, it becomes a three-step process because namespaces are
+ * invoved.
+ *
+ *
+ * but is different in several respects:
+ *
+ * -
+ * Each “object” will consist only of a single file,
+ * causing the entire issue of object encapsulation to be moot.
+ *
+ * -
+ * Filenames will be cleaned in the same manner as identifiers,
+ * guarding against illegal characters in filenames.
+ *
+ * -
+ * Character encoding will include backslashes,
+ * for compatibility with Windows.
+ *
+ * -
+ * Character encoding will include tildes, to allow for "namespaces".
+ *
+ * -
+ * A namespace/prefix capability will be used to shorten file paths,
+ * but with more flexibility than the prefix algorithm given in the specification.
+ *
+ *
+ *
+ *
+ * Directory structure
+ *
+ *
+ * A typical structure would look like this:
+ *
+ * + basedir
+ * |
+ * +--+ file_storage_namespaces.properties
+ * |
+ * +--+ file_storage_root
+ *
+ * The file_storage_root
directory contains the subdirectories
+ * that implement the encoded IDs, and the final directory for each ID will
+ * contain a single file that corresponds to that ID.
+ *
+ *
+ * Namespaces
+ *
+ *
+ * To reduce the length of the file paths, the system will can be initialized
+ * to recognize certain sets of characters (loosely termed "namespaces") and
+ * to replace them with a given prefix and separator character during ID
+ * encoding.
+ *
+ *
+ * For example, the sytem might be initialized with a "namespace" of
+ * "http://vivo.mydomain.edu/file/". If that is the only namespace, it will
+ * be internally assigned a prefix of "a", so a URI like this:
+ *
http://vivo.mydomain.edu/file/n3424/myPhoto.jpg
+ * would be converted to this:
+ * a~n3424/myPhoto.jpg
+ *
+ *
+ * ID encoding
+ *
+ *
+ *
+ *
+ * Filename encoding
+ *
+ *
+ * The name of the file is encoded as needed to guard against illegal
+ * characters for the filesystem, but in practice we expect little encoding
+ * to be required, since few files are named with the special characters.
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+package edu.cornell.mannlib.vitro.webapp.utils.filestorage;