/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.misc;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* This class defines a servlet that makes it possible for a client to request
* a file from the Web server, and have the Web server distribute a different
* file for each request. The individual files to distribute should exist in
* a specified location on the filesystem and should be named with the same base
* name followed by a period and a sequentially-incrementing number starting at
* 1. For example, if a request for the file "myfile" should return one of five
* files, then the files to be retrieved should be named "myfile.1", "myfile.2",
* "myfile.3", "myfile.4", and "myfile.5".
*
*
* @author Neil A. Wilson
*/
public class GetFile
extends HttpServlet
{
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = -8475709072466939471L;
/**
* The name of the servlet initialization parameter that specifies the
* directory in which to look for the requested files.
*/
public static final String SERVLET_INIT_PARAM_FILE_DIRECTORY =
"file_directory";
/**
* The name of the servlet parameter that specifies the file to retrieve.
*/
public static final String SERVLET_PARAM_FILENAME = "file";
/**
* The name of the servlet parameter that specifies the access mode that
* should be used when retrieving the files.
*/
public static final String SERVLET_PARAM_ACCESS_MODE = "mode";
/**
* The access mode that indicates that the files should be retrieved in
* sequential order.
*/
public static final String ACCESS_MODE_SEQUENTIAL = "sequential";
/**
* The access mode that indicates that the files should be retrieved in
* random order.
*/
public static final String ACCESS_MODE_RANDOM = "random";
/**
* The access mode that indicates that information about the specified file
* should be removed from the file data cache.
*/
public static final String ACCESS_MODE_REMOVE = "remove";
// Indicates whether this servlet has been initialized.
private static boolean initialized;
// The cache used for this servlet.
private static HashMap<String,GetFileCacheItem> fileDataCache;
// The mutex used to provide threadsafe access to the cache.
private static final Object cacheMutex = new Object();
// The path to the directory in which the files served by this servlet reside.
private String fileDirectory;
// The reason that this servlet is unavailable.
private static String unavailableReason;
/**
* Performs the necessary initialization for this servlet.
*/
@Override()
public void init()
{
// Create the file data cache.
fileDataCache = new HashMap<String,GetFileCacheItem>();
// Prepare for the worst, but hope for the best
initialized = false;
unavailableReason = "Unknown error during servlet initialization.";
// See if a file directory has been specified. If so, then use it.
fileDirectory = "";
ServletConfig servletConfig = getServletConfig();
if (servletConfig != null)
{
String dir =
servletConfig.getInitParameter(SERVLET_INIT_PARAM_FILE_DIRECTORY);
if (dir == null)
{
unavailableReason = "No file directory has been specified. Please " +
"specify a value for the " +
SERVLET_INIT_PARAM_FILE_DIRECTORY +
" servlet initialization parameter.";
return;
}
else
{
try
{
File dirFile = new File(dir);
if (dirFile.exists())
{
if (dirFile.isDirectory())
{
fileDirectory = dir;
initialized = true;
return;
}
else
{
unavailableReason = "Specified file directory \"" + dir +
"\" exists but is not a directory";
return;
}
}
else
{
unavailableReason = "Specified file directory \"" + dir +
"\" does not exist or could not be accessed.";
return;
}
}
catch (Exception e)
{
unavailableReason = "Unable to find or access the file directory \"" +
dir + "\" -- " + e;
return;
}
}
}
}
/**
* Processes the servlet request and returns the appropriate file.
*
* @param request Information about the request received from the client.
* @param response Information about the response to send back to the
* client.
*
* @throws IOException If a problem occurs while sending the response back
* to the client.
*/
@Override()
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException
{
// If the servlet was not properly initialized, then return an error.
if (! initialized)
{
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("The servlet initialization did not " +
"complete properly: " + unavailableReason);
return;
}
// Get the base name of the file to retrieve.
String filename = request.getParameter(SERVLET_PARAM_FILENAME);
if ((filename == null) || (filename.length() == 0))
{
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("Missing required parameter " +
SERVLET_PARAM_FILENAME);
return;
}
// Determine the access mode to use for the file.
boolean sequential = true;
String modeStr = request.getParameter(SERVLET_PARAM_ACCESS_MODE);
if ((modeStr != null) && (modeStr.length() > 0))
{
if (modeStr.equalsIgnoreCase(ACCESS_MODE_RANDOM))
{
sequential = false;
}
else if (modeStr.equalsIgnoreCase(ACCESS_MODE_REMOVE))
{
synchronized (cacheMutex)
{
Object removedObject = fileDataCache.remove(filename);
if (removedObject == null)
{
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("No information about file \"" +
filename +
"\" exists in the file data cache.");
}
else
{
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write("Information about file \"" + filename +
"\" has been removed from the cache.");
}
return;
}
}
else if (! modeStr.equalsIgnoreCase(ACCESS_MODE_SEQUENTIAL))
{
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("Invalid access mode specified -- must be " +
"either " + ACCESS_MODE_SEQUENTIAL +
" or " + ACCESS_MODE_RANDOM);
return;
}
}
// See if the file exists in the cache. If so, then get the cached info. If
// not, then see if the file actually exists on the filesystem.
GetFileCacheItem cacheItem;
synchronized (cacheMutex)
{
cacheItem = fileDataCache.get(filename);
if (cacheItem == null)
{
cacheItem = createCacheItem(filename);
}
}
// If the cache item is null, then return a "not found" response.
if (cacheItem == null)
{
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("Could not find file \"" + filename + '"');
return;
}
// Determine the actual filename to use and then try to get the file data.
String fileToRetrieve;
if (sequential)
{
fileToRetrieve = cacheItem.nextFileName();
}
else
{
fileToRetrieve = cacheItem.randomFileName();
}
sendFile(response, fileToRetrieve);
}
/**
* Creates a cache item with information about the specified file. If the
* file does not exist, then it will return null.
*
* @param filename The name of the file to be cached.
*
* @return The cache item with information about the specified file, or
* <CODE>null</CODE> if the file does not exist.
*/
public GetFileCacheItem createCacheItem(String filename)
{
// First, see if the filename has any forward or backward slashes. If so,
// then reject it without checking, because it's an attempt to go outside
// the file directory.
if ((filename.indexOf('/') >= 0) || (filename.indexOf('\\') >= 0))
{
return null;
}
// Next, see if the file exists with a name of ".1". If it does not exist
// or is not accessible, then return null;
File actualFile = new File(fileDirectory + '/' + filename + ".1");
if ((! actualFile.exists()) || (! actualFile.isFile()))
{
return null;
}
// OK. At least one file exists. Find out how many there are.
int numFiles = 1;
while (true)
{
File testFile = new File(fileDirectory + '/' + filename + '.' +
(numFiles + 1));
if (testFile.exists() && testFile.isFile())
{
numFiles++;
}
else
{
break;
}
}
// Now we know how many files there are, so create a cache item, put it in
// the cache, and return it to the caller.
GetFileCacheItem cacheItem = new GetFileCacheItem(filename, numFiles);
fileDataCache.put(filename, cacheItem);
return cacheItem;
}
/**
* Sends the contents of the specified file back to the client.
*
* @param response The response object to use to send the data back
* to the client.
* @param fileToRetrieve The name of the actual file to retrieve.
*
* @throws IOException If a problem occurs while trying to send the file
* data to the client.
*/
public void sendFile(HttpServletResponse response, String fileToRetrieve)
throws IOException
{
byte[] buffer = new byte[4096];
BufferedInputStream inputStream =
new BufferedInputStream(new FileInputStream(fileDirectory + '/' +
fileToRetrieve));
BufferedOutputStream outputStream =
new BufferedOutputStream(response.getOutputStream());
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) >= 0)
{
outputStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outputStream.flush();
outputStream.close();
}
}