content: /* 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] = "&nbsp;";
              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;
      }
  }
  