FileContent.java
/*
* FileContent
*/
package gov.usgs.earthquake.product;
import gov.usgs.util.StreamUtils;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import java.util.regex.Pattern;
import javax.activation.MimetypesFileTypeMap;
/**
* Content stored in a file.
*/
public class FileContent extends AbstractContent {
/** Used to look up file types. */
private static MimetypesFileTypeMap SYSTEM_MIME_TYPES = new MimetypesFileTypeMap();
/** Explicit list of file extensions with standard mime types. */
private static Map<String, String> MIME_TYPES = new HashMap<String, String>();
static {
MIME_TYPES.put("atom", "application/atom+xml");
MIME_TYPES.put("css", "text/css");
MIME_TYPES.put("gif", "image/gif");
MIME_TYPES.put("gz", "application/gzip");
MIME_TYPES.put("html", "text/html");
MIME_TYPES.put("jpg", "image/jpeg");
MIME_TYPES.put("js", "text/javascript");
MIME_TYPES.put("json", "application/json");
MIME_TYPES.put("kml", "application/vnd.google-earth.kml+xml");
MIME_TYPES.put("kmz", "application/vnd.google-earth.kmz");
MIME_TYPES.put("pdf", "application/pdf");
MIME_TYPES.put("png", "image/png");
MIME_TYPES.put("ps", "application/postscript");
MIME_TYPES.put("txt", "text/plain");
MIME_TYPES.put("xml", "application/xml");
MIME_TYPES.put("zip", "application/zip");
}
/** The actual content. */
private File content;
/**
* Construct a new FileContent that does not use a nested path. same as new
* FileContent(file, file.getParentFile());
*
* @param file
* the source of content.
*/
public FileContent(final File file) {
this.content = file;
this.setLastModified(new Date(file.lastModified()));
this.setLength(file.length());
this.setContentType(getMimeType(file));
}
/**
* Construct a new FileContent from a URLContent for legacy products
*
* @param urlc
* the source of content.
* @throws URISyntaxException if error in URI
*/
public FileContent(final URLContent urlc) throws URISyntaxException {
super(urlc);
this.content = new File(urlc.getURL().toURI());
}
/**
* Convert a Content to a file backed content.
*
* The file written is new File(baseDirectory, content.getPath()).
*
* @param content
* the content that will be converted to a file.
* @param toWrite
* the file where content is written.
* @throws IOException
* if IO error occurs
*/
public FileContent(final Content content, final File toWrite)
throws IOException {
super(content);
// this file content object will use the newly created file
this.content = toWrite;
// make sure the parent directory exists
File parent = toWrite.getCanonicalFile().getParentFile();
if (!parent.isDirectory()) {
parent.mkdirs();
}
// save handle to stream to force it closed
OutputStream out = null;
InputStream in = null;
try {
in = content.getInputStream();
out = StreamUtils.getOutputStream(toWrite);
// write the file
StreamUtils.transferStream(in, out);
} finally {
// force the stream closed
StreamUtils.closeStream(in);
StreamUtils.closeStream(out);
}
// update modification date in filesystem
toWrite.setLastModified(content.getLastModified().getTime());
// verify file length
Long length = getLength();
if (length > 0 && !length.equals(toWrite.length())) {
throw new IOException("Written file length ("
+ toWrite.length()
+ ") does not match non-zero content length (" + length
+ ")");
}
// length may still be <= 0 if content was input stream.
setLength(toWrite.length());
}
/**
* @return an InputStream for the wrapped content.
*/
public InputStream getInputStream() throws IOException {
return StreamUtils.getInputStream(content);
}
/**
* @return the wrapped file.
*/
public File getFile() {
return content;
}
/**
* Search a directory for files. This is equivalent to
* getDirectoryContents(directory, directory).
*
* @param directory
* the directory to search.
* @return a map of relative paths to FileContent objects.
* @throws IOException if IO error occurs
*/
public static Map<String, FileContent> getDirectoryContents(
final File directory) throws IOException {
File absDirectory = directory.getCanonicalFile();
return getDirectoryContents(absDirectory, absDirectory);
}
/**
* Search a directory for files. The path to files relative to baseDirectory
* is used as a key in the returned map.
*
* @param directory
* the directory to search.
* @param baseDirectory
* the directory used to compute relative paths.
* @return a map of relative paths to FileContent objects.
* @throws IOException if IO error occurs
*/
public static Map<String, FileContent> getDirectoryContents(
final File directory, final File baseDirectory) throws IOException {
Map<String, FileContent> contents = new HashMap<String, FileContent>();
// compute the base path once, and escape the pattern being matched
String basePath = Pattern.quote(baseDirectory.getCanonicalPath()
+ File.separator);
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
// recurse into sub directory
contents.putAll(getDirectoryContents(file.getCanonicalFile(),
baseDirectory.getCanonicalFile()));
} else {
String path = file.getCanonicalPath().replaceAll(basePath, "");
contents.put(path, new FileContent(file));
}
}
return contents;
}
/**
* This implementation calls defaultGetMimeType, and exists so subclasses
* can override.
*
* @param file
* file to check.
* @return corresponding mime type.
*/
public String getMimeType(final File file) {
return defaultGetMimeType(file);
}
/**
* Check a local list of mime types, and fall back to MimetypeFileTypesMap
* if not specified.
*
* @param file
* file to check.
* @return corresponding mime type.
*/
protected static String defaultGetMimeType(final File file) {
String name = file.getName();
int index = name.lastIndexOf('.');
if (index != -1) {
String extension = name.substring(index + 1);
if (MIME_TYPES.containsKey(extension)) {
return MIME_TYPES.get(extension);
}
}
return SYSTEM_MIME_TYPES.getContentType(file);
}
/**
* Free any resources associated with this content.
*/
public void close() {
// nothing to free
}
}