/* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://www.sun.com/cddl/cddl.html or
* install_dir/legal/LICENSE
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at install_dir/legal/LICENSE.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id$
*
* Copyright 2005-2009 Sun Microsystems Inc. All Rights Reserved
*/
package com.sun.faban.harness.webclient;
import com.sun.faban.common.SortableTableModel;
import com.sun.faban.harness.ParamRepository;
import com.sun.faban.harness.common.BenchmarkDescription;
import com.sun.faban.harness.common.Config;
import com.sun.faban.harness.common.RunId;
import com.sun.faban.harness.util.FileHelper;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.sun.faban.harness.util.FileHelper.*;
/**
* Controller handling actions from the result list screen.
*/
public class ResultAction {
private static Logger logger =
Logger.getLogger(ResultAction.class.getName());
private SimpleDateFormat dateFormat = new SimpleDateFormat(
"EEE MM/dd/yy HH:mm:ss z");
/**
* Checks for actions the user asked to perform and take the appropriate
* action.
* @param request The request
* @param response The response
* @return The name of the jsp view to forward this request
* @throws IOException If there are problems reading or writing data
* @throws ParseException Error parsing input
*/
public String takeAction(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ParseException {
String process = request.getParameter("process");
if ("Compare".equals(process))
return editAnalysis(process, request, response);
if ("Average".equals(process))
return editAnalysis(process, request, response);
if ("Archive".equals(process))
return editArchive(request, response);
if ("Delete".equals(process))
return deleteResults(request, response);
return null;
}
/**
* The model object for the EditArchive screen view.
* This is according to the MVC pattern.
*/
public class EditArchiveModel implements Serializable {
/** The display header. */
public String head;
/** The run id of the runs to archive. */
public String[] runIds;
/** The set of duplicate runs. */
public Set<String> duplicates;
/** The list of run result objects. */
public RunResult[] results;
}
String editArchive(HttpServletRequest request,
HttpServletResponse response) throws IOException,
FileNotFoundException, ParseException {
String[] runIds = request.getParameterValues("select");
if (runIds == null || runIds.length < 1) {
String msg;
msg = "Select at least one runs to archive.";
response.getOutputStream().println(msg);
response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
return null;
}
EditArchiveModel model = new EditArchiveModel();
model.runIds = runIds;
model.duplicates = checkArchivedRuns(runIds);
if (Config.repositoryURLs != null &&
Config.repositoryURLs.length > 1)
model.head = "Repositories";
else
model.head = "Repository";
model.results = new RunResult[runIds.length];
for (int i = 0; i < runIds.length; i++) {
model.results[i] = RunResult.getInstance(new RunId(runIds[i]));
}
// We use request attributes as not to reflect session state.
request.setAttribute("editarchive.model", model);
return "/edit_archive.jsp";
}
/**
* Obtains the tags list for each profile.
* @param req
* @param resp
* @throws java.io.IOException
*/
public void profileTagList (HttpServletRequest req,
HttpServletResponse resp) throws IOException {
String profile = req.getParameter("profileselected");
File tagsFile = new File(Config.PROFILES_DIR + profile + "/tags");
String tagsForProfile = "";
if(tagsFile.exists() && tagsFile.length()>0){
tagsForProfile = FileHelper.readContentFromFile(tagsFile).trim();
}
Writer w = resp.getWriter();
w.write(tagsForProfile);
w.flush();
w.close();
}
/*private Set<String> checkArchivedRuns(String[] runIds) throws IOException{
StringBuilder b = new StringBuilder();
b.append("/controller/uploader/check_runs");
int endPath = b.length();
for (String runId : runIds)
b.append("&select=").append(runId);
b.setCharAt(endPath, '?');
HttpURLConnection c = (HttpURLConnection) request.openConnection();
if (c.getResponseCode() != 404){
existingRuns.add(runId);
}
HashSet<String> existingRuns = new HashSet<String>();
for (String runId : runIds){
for (URL repository : Config.repositoryURLs) {
URL request = new URL(repository, "/output/"+
Config.FABAN_HOST+"."+runId);
URLConnection c = request.openConnection();
int len = c.getContentLength();
if (len < 0){
existingRuns.add(runId);
}
}
}
return existingRuns;
}*/
private String editResultInfo(String runID)
throws FileNotFoundException, IOException {
RunId runId = new RunId(runID);
String ts = null;
String[] status = new String[2];
File file = new File(Config.OUT_DIR + runID + '/' + Config.RESULT_INFO);
RandomAccessFile rf = new RandomAccessFile(file, "rwd");
long size = rf.length();
byte[] buffer = new byte[(int) size];
rf.readFully(buffer);
String content = new String(buffer, 0, (int) size );
int idx = content.indexOf('\t');
if (idx != -1) {
status[0] = content.substring(0, idx).trim();
status[1] = content.substring(++idx).trim();
} else {
status[0] = content.trim();
int lastIdxln = status[0].lastIndexOf("\n");
if(lastIdxln != -1)
status[0] = status[0].substring(0, lastIdxln-1);
}
if (status[1] != null) {
ts = status[1];
}else{
String paramFileName = runId.getResultDir().getAbsolutePath() +
File.separator + "run.xml";
File paramFile = new File(paramFileName);
long dt = paramFile.lastModified();
ts = dateFormat.format(new Date(dt));
rf.seek(rf.length());
rf.writeBytes('\t' + ts.trim());
}
rf.close();
return ts;
}
private Set<String> checkArchivedRuns(String[] runIds) throws
FileNotFoundException, IOException {
HashSet<String> existingRuns = new HashSet<String>();
String[] runIdTimeStamps = new String[runIds.length];
for (int r=0; r< runIds.length ; r++){
String runId = runIds[r];
runIdTimeStamps[r] = editResultInfo(runId);
}
for (URL repository : Config.repositoryURLs) {
URL repos = new URL(repository, "/controller/uploader/check_runs");
PostMethod post = new PostMethod(repos.toString());
post.addParameter("host",Config.FABAN_HOST);
for (String runId : runIds){
post.addParameter("runId",runId);
}
for (String ts : runIdTimeStamps){
post.addParameter("ts",ts);
}
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams().
setConnectionTimeout(5000);
int status = client.executeMethod(post);
if (status != HttpStatus.SC_OK)
logger.info("SC_OK not ok");
String response = post.getResponseBodyAsString();
StringTokenizer t = new StringTokenizer(response.trim(),"\n");
while (t.hasMoreTokens()) {
existingRuns.add(t.nextToken().trim());
}
}
return existingRuns;
}
/**
* The model object for the EditAnalysis screen view.
* This is according to the MVC pattern.
*/
public static class EditAnalysisModel implements Serializable {
/** The header, usually the process type. */
public String head;
/** The type string in all lowercase. */
public String type;
/** The string representation of the list of runs to analyze. */
public String runList;
/** The analysis name. */
public String name;
/** The run ids to analyze. */
public String[] runIds;
}
String editAnalysis(String process, HttpServletRequest request,
HttpServletResponse response)
throws IOException {
EditAnalysisModel model = new EditAnalysisModel();
model.head = process;
model.type = process.toLowerCase();
model.runIds = request.getParameterValues("select");
if (model.runIds == null || model.runIds.length < 2) {
String msg;
msg = "Select at least 2 runs to " + model.type + ".";
response.getOutputStream().println(msg);
response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
return null;
}
StringBuilder runList = new StringBuilder();
for (String runId : model.runIds)
runList.append(runId).append(", ");
runList.setLength(runList.length() - 2); //Strip off the last comma
model.runList = runList.toString();
model.name = RunAnalyzer.suggestName(RunAnalyzer.Type.COMPARE,
model.runIds);
request.setAttribute("editanalysis.model", model);
return "/edit_analysis.jsp";
}
/**
* This method is responsible for analyzing the runs.
* @param request
* @param response
* @return string
* @throws java.io.IOException
*/
public String analyze(HttpServletRequest request,
HttpServletResponse response) throws IOException {
EditAnalysisModel model = new EditAnalysisModel();
model.name = request.getParameter("output");
model.type = request.getParameter("type");
RunAnalyzer.Type type;
if ("compare".equals(model.type)) {
type = RunAnalyzer.Type.COMPARE;
} else if ("average".equals(model.type)) {
type = RunAnalyzer.Type.AVERAGE;
} else {
String msg = "Invalid analysis: " + model.name;
response.getWriter().println(msg);
logger.severe(msg);
response.sendError(HttpServletResponse.SC_CONFLICT, msg);
return null;
}
model.runIds = request.getParameterValues("select");
boolean analyze = false;
boolean redirect = false;
if (RunAnalyzer.exists(model.name)) {
String replace = request.getParameter("replace");
if (replace == null) {
request.setAttribute("editanalysis.model", model);
return "/confirm_analysis.jsp";
} else if ("Replace".equals(replace)) {
analyze = true;
redirect = true;
} else {
redirect = true;
}
} else {
analyze = true;
redirect = true;
}
if (analyze)
try {
RunAnalyzer.clear(model.name);
UserEnv usrEnv = (UserEnv) request.getSession().
getAttribute("usrEnv");
new RunAnalyzer().analyze(type, model.runIds, model.name,
usrEnv.getUser());
} catch (IOException e) {
String msg = e.getMessage();
response.getWriter().println(msg);
logger.log(Level.SEVERE, msg, e);
response.sendError(HttpServletResponse.SC_CONFLICT, msg);
return null;
}
if (redirect)
//response.sendRedirect("/analysis/" + model.name + "/index.html");
response.sendRedirect("/controller/view/xan_view/analysis/" +
model.name + "/" + model.type + ".xan");
return null;
}
/**
* This method is responsible for archiving the runs to the repository.
* @param request
* @param response
* @return String
* @throws java.io.IOException
* @throws java.io.FileNotFoundException
* @throws java.text.ParseException
* @throws java.lang.ClassNotFoundException
*/
public String archive(HttpServletRequest request,
HttpServletResponse response) throws IOException,
FileNotFoundException, ParseException, ClassNotFoundException {
//Reading values from request
String[] duplicateIds = request.getParameterValues("duplicates");
String[] replaceIds = request.getParameterValues("replace");
String[] runIds = request.getParameterValues("select");
String submitAction = request.getParameter("process");
HashSet<String> modelDuplicates = new HashSet<String>();
HashSet<String> replaceSet = new HashSet<String>();
HashSet<File> uploadSet = new HashSet<File>();
HashSet<String> uploadedRuns = new HashSet<String>();
HashSet<String> duplicateSet = new HashSet<String>();
if(replaceIds != null) {
for(String replaceId : replaceIds){
replaceSet.add(replaceId);
}
}
EditArchiveModel model = new EditArchiveModel();
model.runIds = runIds;
if (duplicateIds != null) {
for (String duplicateId : duplicateIds){
modelDuplicates.add(duplicateId);
}
}
model.duplicates = modelDuplicates;
if (Config.repositoryURLs != null &&
Config.repositoryURLs.length > 1)
model.head = "Repositories";
else
model.head = "Repository";
model.results = new RunResult[runIds.length];
for (int i = 0; i < runIds.length; i++) {
model.results[i] = RunResult.getInstance(new RunId(runIds[i]));
}
if (submitAction.equals("Archive")) {
for (int i = 0; i < model.runIds.length; i++) {
String runId = model.runIds[i];
if (model.duplicates.contains(runId)) {
if (replaceIds != null) {
if (replaceSet.contains(runId)) {
prepareUpload(request, model.results[i],
uploadedRuns, uploadSet);
} else { // Description or tags got changed, replace anyway...
if (!model.results[i].description.equals(request.
getParameter(runId + "_description")) ||
!model.results[i].tags.toString().equals(request.
getParameter(runId + "_tags"))) {
replaceSet.add(runId);
prepareUpload(request, model.results[i],
uploadedRuns, uploadSet);
}
}
} else { // Single run, description changed, replace anyway.
if (!model.results[i].description.equals(
request.getParameter(runId + "_description")) ||
!model.results[i].tags.toString().equals(request.
getParameter(runId + "_tags"))) {
replaceSet.add(runId);
prepareUpload(request, model.results[i],
uploadedRuns, uploadSet);
}
}
} else {
prepareUpload(request, model.results[i],
uploadedRuns, uploadSet);
}
}
}
duplicateSet = uploadRuns(runIds, uploadSet,replaceSet);
request.setAttribute("archive.model", model);
request.setAttribute("uploadedRuns", uploadedRuns);
request.setAttribute("duplicateRuns", duplicateSet);
return "/archive_results.jsp";
}
public String deleteResults(HttpServletRequest request,
HttpServletResponse response) throws IOException {
String[] runIds = request.getParameterValues("select");
if (runIds != null) {
TagEngine tagEngine;
try {
tagEngine = TagEngine.getInstance();
} catch (ClassNotFoundException ex) {
logger.log(Level.SEVERE, "Cannot find tag engine class", ex);
throw new IOException("Cannot find tag engine class", ex);
}
for (String r : runIds) {
RunResult runResult = RunResult.getInstance(new RunId(r));
runResult.delete(r);
tagEngine.removeRun(r);
tagEngine.save();
}
}
HttpSession session = request.getSession();
UserEnv usrEnv = (UserEnv) session.getAttribute("usrEnv");
if (usrEnv == null) {
usrEnv = new UserEnv();
session.setAttribute("usrEnv", usrEnv);
}
SortableTableModel resultTable = RunResult.getResultTable(usrEnv.getSubject(), 5, "DESCENDING");
String feedURL = "/controller/results/feed";
request.setAttribute("feedURL", feedURL );
request.setAttribute("table.model", resultTable);
return "/resultlist.jsp";
}
@SuppressWarnings("empty-statement")
private void prepareUpload(HttpServletRequest request, RunResult result,
HashSet<String> uploadedRuns, HashSet<File> uploadSet)
throws IOException, ClassNotFoundException {
String runId = result.runId.toString();
StringBuilder formattedTags = new StringBuilder();
File runTagFile = new File(Config.OUT_DIR + runId + "/META-INF/tags");
String tags = request.getParameter(runId + "_tags").trim();
TagEngine te = TagEngine.getInstance();
if (tags != null && !"".equals(tags)) {
StringTokenizer t = new StringTokenizer(tags," \n,");
ArrayList<String> tagList = new ArrayList<String>(t.countTokens());
while (t.hasMoreTokens()) {
String nextT = t.nextToken().trim();
if( nextT != null && !"".equals(nextT) ){
formattedTags.append(nextT + "\n");
tagList.add(nextT);
}
}
FileHelper.writeContentToFile(formattedTags.toString(), runTagFile);
result.tags = tagList.toArray(new String[tagList.size()]);
boolean tagsChanged = true;
if (result.tags == null && result.tags.length == 0) {
if (tags == null || "".equals(tags)) {
tagsChanged = false;
}
}
if (tagsChanged) {
te.add(runId, result.tags);
te.save();
}
} else {
runTagFile.delete();
result.tags = new String[1];
result.tags[0] = " ";
te.add(runId, new String[0]);
te.save();
}
result.description = request.getParameter(runId + "_description");
editXML(result);
uploadedRuns.add(runId);
uploadSet.add(jarUpRun(runId));
}
/**
* Edit run.xml file.
* @param result The run result object to edit
*/
public static void editXML(RunResult result){
try {
File resultDir = result.runId.getResultDir();
String shortName = result.runId.getBenchName();
BenchmarkDescription desc = BenchmarkDescription.readDescription(
shortName, resultDir.getAbsolutePath());
String paramFileName = resultDir.getAbsolutePath() + File.separator
+ desc.configFileName;
ParamRepository param = new ParamRepository(paramFileName, false);
param.setParameter("fa:runConfig/fh:description",
result.description);
param.save();
} catch (Exception ex) {
Logger.getLogger(ResultAction.class.getName()).
log(Level.SEVERE, null, ex);
}
}
/**
* Jar up the run by runId.
* @param runId
* @return File
* @throws IOException
*/
private File jarUpRun(String runId) throws IOException{
String[] files = new File(Config.OUT_DIR, runId).list();
File jarFile = new File(Config.TMP_DIR, runId + ".jar");
jar(Config.OUT_DIR + runId, files, jarFile.getAbsolutePath());
return jarFile;
//return new File(Config.TMP_DIR, "test.jar");
}
/**
* This method is responsible for uploading the runs to repository.
* @param uploadSet
* @param replaceSet
* @return HashSet
* @throws java.io.IOException
*/
public static HashSet<String> uploadRuns(String[] runIds,
HashSet<File> uploadSet,
HashSet<String> replaceSet)
throws IOException {
// 3. Upload the run
HashSet<String> duplicates = new HashSet<String>();
// Prepare run id set for cross checking.
HashSet<String> runIdSet = new HashSet<String>(runIds.length);
for (String runId : runIds) {
runIdSet.add(runId);
}
// Prepare the parts for the request.
ArrayList<Part> params = new ArrayList<Part>();
params.add(new StringPart("host", Config.FABAN_HOST));
for (String replaceId : replaceSet) {
params.add(new StringPart("replace", replaceId));
}
for (File jarFile : uploadSet) {
params.add(new FilePart("jarfile", jarFile));
}
Part[] parts = new Part[params.size()];
parts = params.toArray(parts);
// Send the request for each reposotory.
for (URL repository : Config.repositoryURLs) {
URL repos = new URL(repository, "/controller/uploader/upload_runs");
PostMethod post = new PostMethod(repos.toString());
post.setRequestEntity(
new MultipartRequestEntity(parts, post.getParams()));
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams().
setConnectionTimeout(5000);
int status = client.executeMethod(post);
if (status == HttpStatus.SC_FORBIDDEN)
logger.warning("Server denied permission to upload run !");
else if (status == HttpStatus.SC_NOT_ACCEPTABLE)
logger.warning("Run origin error!");
else if (status != HttpStatus.SC_CREATED)
logger.warning("Server responded with status code " +
status + ". Status code 201 (SC_CREATED) expected.");
for (File jarFile : uploadSet) {
jarFile.delete();
}
String response = post.getResponseBodyAsString();
if (status == HttpStatus.SC_CREATED) {
StringTokenizer t = new StringTokenizer(response.trim(),"\n");
while (t.hasMoreTokens()) {
String duplicateRun = t.nextToken().trim();
if (duplicateRun.length() > 0)
duplicates.add(duplicateRun.trim());
}
for (Iterator<String> iter = duplicates.iterator(); iter.hasNext();) {
String runId = iter.next();
if (!runIdSet.contains(runId)) {
logger.warning("Unexpected archive response from " +
repos + ": " + runId);
iter.remove();
}
}
} else {
logger.warning("Message from repository: " + response);
}
}
return duplicates;
}
}