NIHVIVO-160 Start coding the back-end
This commit is contained in:
parent
5ed46b631b
commit
b9dfa9023b
5 changed files with 448 additions and 0 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Get an instance of {@link FileStorage}. By default, this will be an
|
||||
* instance of {@link FileStorageImpl}.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
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
|
||||
* <code>null</code> 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 <code>true<code> if a file existed, <code>false</code> otherwise.
|
||||
*/
|
||||
public abstract boolean deleteFile(String id) throws IOException;
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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<String> 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<String> 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: <code>http://vivo.mydomain.edu/individual/</code>
|
||||
*
|
||||
* @returns the file namespace is assumed to be in this form:
|
||||
* <code>http://vivo.mydomain.edu/file/</code>
|
||||
*/
|
||||
private static Collection<String> 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.");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* <p>
|
||||
* The code in this package implements the Vitro file-storage system.
|
||||
* </p>
|
||||
*
|
||||
* <h1>Relationship to PairTree</h1>
|
||||
*
|
||||
* <p>
|
||||
* The system incorporates a number of ideas from the PairTree specification,
|
||||
* <ul>
|
||||
* <li>
|
||||
* The basic pairtree algorithm –
|
||||
* mapping an encoded identifier string into a filesystem directory path.
|
||||
* </li>
|
||||
* <li>
|
||||
* 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.
|
||||
* </li>
|
||||
* </ul>
|
||||
* but is different in several respects:
|
||||
* <ul>
|
||||
* <li>
|
||||
* Each “object” will consist only of a single file,
|
||||
* causing the entire issue of object encapsulation to be moot.
|
||||
* </li>
|
||||
* <li>
|
||||
* Filenames will be cleaned in the same manner as identifiers,
|
||||
* guarding against illegal characters in filenames.
|
||||
* </li>
|
||||
* <li>
|
||||
* Character encoding will include backslashes,
|
||||
* for compatibility with Windows.
|
||||
* </li>
|
||||
* <li>
|
||||
* Character encoding will include tildes, to allow for "namespaces".
|
||||
* </li>
|
||||
* <li>
|
||||
* A namespace/prefix capability will be used to shorten file paths,
|
||||
* but with more flexibility than the prefix algorithm given in the specification.
|
||||
* </li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <h1>Directory structure</h1>
|
||||
*
|
||||
* <p>
|
||||
* A typical structure would look like this:
|
||||
* <pre>
|
||||
* + basedir
|
||||
* |
|
||||
* +--+ file_storage_namespaces.properties
|
||||
* |
|
||||
* +--+ file_storage_root
|
||||
* </pre>
|
||||
* The <code>file_storage_root</code> 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.
|
||||
* </p>
|
||||
*
|
||||
* <h1>Namespaces</h1>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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:
|
||||
* <pre>http://vivo.mydomain.edu/file/n3424/myPhoto.jpg</pre>
|
||||
* would be converted to this:
|
||||
* <pre>a~n3424/myPhoto.jpg</pre>
|
||||
* </p>
|
||||
*
|
||||
* <h1>ID encoding</h1>
|
||||
*
|
||||
* <p>
|
||||
* </p>
|
||||
*
|
||||
* <h1>Filename encoding</h1>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* <p></p>
|
||||
* <p></p>
|
||||
* <p></p>
|
||||
* <p></p>
|
||||
*/
|
||||
|
||||
package edu.cornell.mannlib.vitro.webapp.utils.filestorage;
|
Loading…
Add table
Reference in a new issue