/*
* 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.admin;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUpload;
import com.slamd.common.Constants;
import com.slamd.job.JobClass;
import com.slamd.server.SLAMDServer;
import com.slamd.server.SLAMDServerException;
/**
* This class defines methods for dealing with SLAMD job packs. A job pack is a
* JAR file containing one or more SLAMD jobs and supporting files that may be
* uploaded as a group and automatically registered with the SLAMD server.
*
*
* @author Neil A. Wilson
*/
public class JobPack
{
// The list of fields in the multipart form content.
private List fieldList;
// Information about the request from the client, including the file data.
private RequestInfo requestInfo;
// The SLAMD server with which to register the jobs.
private SLAMDServer slamdServer;
// The path to the directory in which the job classes should be placed.
private String jobClassDirectory;
// The path to the job pack file on the server's filesystem.
private String filePath;
/**
* Creates a new job pack definition with the provided request.
*
* @param requestInfo Information about the request from the client,
* including the file data.
*/
public JobPack(RequestInfo requestInfo)
{
this.requestInfo = requestInfo;
filePath = null;
fieldList = requestInfo.multipartFieldList;
slamdServer = AdminServlet.slamdServer;
jobClassDirectory = AdminServlet.classPath;
}
/**
* Creates a new job pack definition from a file on the server's filesystem.
*
* @param requestInfo Information about the request from the client.
* @param filePath The path to the job pack file on the server's
* filesystem.
*/
public JobPack(RequestInfo requestInfo, String filePath)
{
this.requestInfo = requestInfo;
this.filePath = filePath;
slamdServer = AdminServlet.slamdServer;
jobClassDirectory = AdminServlet.classPath;
}
/**
* Extracts the contents of the job pack and registers the included jobs with
* the SLAMD server.
*
* @throws SLAMDServerException If a problem occurs while processing the job
* pack JAR file.
*/
public void processJobPack()
throws SLAMDServerException
{
byte[] fileData = null;
File tempFile = null;
String fileName = null;
String separator = System.getProperty("file.separator");
if (filePath == null)
{
// First, get the request and ensure it is multipart content.
HttpServletRequest request = requestInfo.request;
if (! FileUpload.isMultipartContent(request))
{
throw new SLAMDServerException("Request does not contain multipart " +
"content");
}
// Iterate through the request fields to get to the file data.
Iterator iterator = fieldList.iterator();
while (iterator.hasNext())
{
FileItem fileItem = (FileItem) iterator.next();
String fieldName = fileItem.getFieldName();
if (fieldName.equals(Constants.SERVLET_PARAM_JOB_PACK_FILE))
{
fileData = fileItem.get();
fileName = fileItem.getName();
}
}
// Make sure that a file was actually uploaded.
if (fileData == null)
{
throw new SLAMDServerException("No file data was found in the " +
"request.");
}
// Write the JAR file data to a temp file, since that's the only way we
// can parse it.
if (separator == null)
{
separator = "/";
}
tempFile = new File(jobClassDirectory + separator + fileName);
try
{
FileOutputStream outputStream = new FileOutputStream(tempFile);
outputStream.write(fileData);
outputStream.flush();
outputStream.close();
}
catch (IOException ioe)
{
try
{
tempFile.delete();
} catch (Exception e) {}
ioe.printStackTrace();
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
throw new SLAMDServerException("I/O error writing temporary JAR " +
"file: " + ioe, ioe);
}
}
else
{
tempFile = new File(filePath);
if ((! tempFile.exists()) || (! tempFile.isFile()))
{
throw new SLAMDServerException("Specified job pack file \"" + filePath +
"\" does not exist");
}
try
{
fileName = tempFile.getName();
int fileLength = (int) tempFile.length();
fileData = new byte[fileLength];
FileInputStream inputStream = new FileInputStream(tempFile);
int bytesRead = 0;
while (bytesRead < fileLength)
{
bytesRead += inputStream.read(fileData, bytesRead,
fileLength-bytesRead);
}
inputStream.close();
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new SLAMDServerException("Error reading job pack file \"" +
filePath + "\" -- " + e, e);
}
}
StringBuilder htmlBody = requestInfo.htmlBody;
// Parse the jar file
JarFile jarFile = null;
Manifest manifest = null;
Enumeration jarEntries = null;
try
{
jarFile = new JarFile(tempFile, true);
manifest = jarFile.getManifest();
jarEntries = jarFile.entries();
}
catch (IOException ioe)
{
try
{
if (filePath == null)
{
tempFile.delete();
}
} catch (Exception e) {}
ioe.printStackTrace();
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
throw new SLAMDServerException("Unable to parse the JAR file: " + ioe,
ioe);
}
ArrayList<String> dirList = new ArrayList<String>();
ArrayList<String> fileNameList = new ArrayList<String>();
ArrayList<byte[]> fileDataList = new ArrayList<byte[]>();
while (jarEntries.hasMoreElements())
{
JarEntry jarEntry = (JarEntry) jarEntries.nextElement();
String entryName = jarEntry.getName();
if (jarEntry.isDirectory())
{
dirList.add(entryName);
}
else
{
try
{
int entrySize = (int) jarEntry.getSize();
byte[] entryData = new byte[entrySize];
InputStream inputStream = jarFile.getInputStream(jarEntry);
extractFileData(inputStream, entryData);
fileNameList.add(entryName);
fileDataList.add(entryData);
}
catch (IOException ioe)
{
try
{
jarFile.close();
if (filePath == null)
{
tempFile.delete();
}
} catch (Exception e) {}
ioe.printStackTrace();
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
throw new SLAMDServerException("I/O error parsing JAR entry " +
entryName + " -- " + ioe, ioe);
}
catch (SLAMDServerException sse)
{
try
{
jarFile.close();
if (filePath == null)
{
tempFile.delete();
}
} catch (Exception e) {}
sse.printStackTrace();
throw sse;
}
}
}
// If we have gotten here, then we have read all the data from the JAR file.
// Delete the temporary file to prevent possible (although unlikely)
// conflicts with data contained in the JAR.
try
{
jarFile.close();
if (filePath == null)
{
tempFile.delete();
}
} catch (Exception e) {}
// Create the directory structure specified in the JAR file.
if (! dirList.isEmpty())
{
htmlBody.append("<B>Created the following directories</B>" +
Constants.EOL);
htmlBody.append("<BR>" + Constants.EOL);
htmlBody.append("<UL>" + Constants.EOL);
for (int i=0; i < dirList.size(); i++)
{
File dirFile = new File(jobClassDirectory + separator + dirList.get(i));
try
{
dirFile.mkdirs();
htmlBody.append(" <LI>" + dirFile.getAbsolutePath() + "</LI>" +
Constants.EOL);
}
catch (Exception e)
{
htmlBody.append("</UL>" + Constants.EOL);
e.printStackTrace();
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new SLAMDServerException("Unable to create directory \"" +
dirFile.getAbsolutePath() + " -- " +
e, e);
}
}
htmlBody.append("</UL>" + Constants.EOL);
htmlBody.append("<BR><BR>" + Constants.EOL);
}
// Write all the files to disk. If we have gotten this far, then there
// should not be any failures, but if there are, then we will have to
// leave things in a "dirty" state.
if (! fileNameList.isEmpty())
{
htmlBody.append("<B>Created the following files</B>" +
Constants.EOL);
htmlBody.append("<BR>" + Constants.EOL);
htmlBody.append("<UL>" + Constants.EOL);
for (int i=0; i < fileNameList.size(); i++)
{
File dataFile = new File(jobClassDirectory + separator +
fileNameList.get(i));
try
{
// Make sure the parent directory exists.
dataFile.getParentFile().mkdirs();
} catch (Exception e) {}
try
{
FileOutputStream outputStream = new FileOutputStream(dataFile);
outputStream.write(fileDataList.get(i));
outputStream.flush();
outputStream.close();
htmlBody.append(" <LI>" + dataFile.getAbsolutePath() + "</LI>" +
Constants.EOL);
}
catch (IOException ioe)
{
htmlBody.append("</UL>" + Constants.EOL);
ioe.printStackTrace();
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
throw new SLAMDServerException("Unable to write file " +
dataFile.getAbsolutePath() + ioe, ioe);
}
}
htmlBody.append("</UL>" + Constants.EOL);
htmlBody.append("<BR><BR>" + Constants.EOL);
}
// Finally, parse the manifest to get the names of the classes that should
// be registered with the SLAMD server.
Attributes manifestAttributes = manifest.getMainAttributes();
Attributes.Name key =
new Attributes.Name(Constants.JOB_PACK_MANIFEST_REGISTER_JOBS_ATTR);
String registerClassesStr = (String) manifestAttributes.get(key);
if ((registerClassesStr == null) || (registerClassesStr.length() == 0))
{
htmlBody.append("<B>No job classes registered</B>" + Constants.EOL);
}
else
{
ArrayList<String> successList = new ArrayList<String>();
ArrayList<String> failureList = new ArrayList<String>();
StringTokenizer tokenizer = new StringTokenizer(registerClassesStr,
", \t\r\n");
while (tokenizer.hasMoreTokens())
{
String className = tokenizer.nextToken();
try
{
JobClass jobClass = slamdServer.loadJobClass(className);
slamdServer.addJobClass(jobClass);
successList.add(className);
}
catch (Exception e)
{
failureList.add(className + ": " + e);
}
}
if (! successList.isEmpty())
{
htmlBody.append("<B>Registered Job Classes</B>" + Constants.EOL);
htmlBody.append("<UL>" + Constants.EOL);
for (int i=0; i < successList.size(); i++)
{
htmlBody.append(" <LI>" + successList.get(i) + "</LI>" +
Constants.EOL);
}
htmlBody.append("</UL>" + Constants.EOL);
htmlBody.append("<BR><BR>" + Constants.EOL);
}
if (! failureList.isEmpty())
{
htmlBody.append("<B>Unable to Register Job Classes</B>" +
Constants.EOL);
htmlBody.append("<UL>" + Constants.EOL);
for (int i=0; i < failureList.size(); i++)
{
htmlBody.append(" <LI>" + failureList.get(i) + "</LI>" +
Constants.EOL);
}
htmlBody.append("</UL>" + Constants.EOL);
htmlBody.append("<BR><BR>" + Constants.EOL);
}
}
}
/**
* Reads the contents of the provided input stream into the given data array.
* It will continue reading until the data array has been filled.
*
* @param inputStream The input stream from which to read the data.
* @param dataArray The array into which the data should be placed after
* it is read.
*
* @throws IOException If an I/O problem occurs while reading the
* data from the input stream.
* @throws SLAMDServerException If a problem occurs while trying to fill the
* array with the data.
*/
private void extractFileData(InputStream inputStream, byte[] dataArray)
throws IOException, SLAMDServerException
{
int bytesRead = inputStream.read(dataArray);
if (bytesRead == dataArray.length)
{
return;
}
else if (bytesRead < 0)
{
throw new SLAMDServerException("Unexpectedly reached the end of the " +
"JAR entry input stream");
}
while (bytesRead < dataArray.length)
{
int bytesRemaining = dataArray.length - bytesRead;
int moreBytesRead = inputStream.read(dataArray, bytesRead,
bytesRemaining);
if (moreBytesRead < 0)
{
throw new SLAMDServerException("Unexpectedly reached the end of the " +
"JAR entry input stream");
}
bytesRead += moreBytesRead;
}
}
}