Merge branch 'develop' into update/JAL-3949_abstracted_logging_for_jalviewjs_and_sanity
[jalview.git] / src / jalview / ws / rest / RestJobThread.java
index 39f26ae..1cb1bc2 100644 (file)
@@ -1,12 +1,34 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.ws.rest;
 
+import java.util.Locale;
+
 import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.Annotation;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
@@ -16,6 +38,7 @@ import jalview.gui.WebserviceInfo;
 import jalview.io.NewickFile;
 import jalview.io.packed.JalviewDataset;
 import jalview.io.packed.JalviewDataset.AlignmentSet;
+import jalview.util.MessageManager;
 import jalview.ws.AWSThread;
 import jalview.ws.AWsJob;
 
@@ -23,11 +46,9 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map.Entry;
-import java.util.Vector;
 
 import org.apache.axis.transport.http.HTTPConstants;
 import org.apache.http.Header;
@@ -71,8 +92,8 @@ public class RestJobThread extends AWSThread
     {
       jobs = new RestJob[1];
       jobs[0] = new RestJob(0, this,
-              restClient._input.getVisibleAlignment(restClient.service
-                      .getGapCharacter()),
+              restClient._input.getVisibleAlignment(
+                      restClient.service.getGapCharacter()),
               restClient._input.getVisibleContigs());
       // need a function to get a range on a view/alignment and return both
       // annotation, groups and selection subsetted to just that region.
@@ -82,15 +103,14 @@ public class RestJobThread extends AWSThread
     {
       int[] viscontig = restClient._input.getVisibleContigs();
       AlignmentI[] viscontigals = restClient._input
-              .getVisibleContigAlignments(restClient.service
-                      .getGapCharacter());
+              .getVisibleContigAlignments(
+                      restClient.service.getGapCharacter());
       if (viscontigals != null && viscontigals.length > 0)
       {
         jobs = new RestJob[viscontigals.length];
         for (int j = 0; j < jobs.length; j++)
         {
-          int[] visc = new int[]
-          { viscontig[j * 2], viscontig[j * 2 + 1] };
+          int[] visc = new int[] { viscontig[j * 2], viscontig[j * 2 + 1] };
           if (j != 0)
           {
             jobs[j] = new RestJob(j, this, viscontigals[j], visc);
@@ -155,9 +175,13 @@ public class RestJobThread extends AWSThread
   private String getStage(Stage stg)
   {
     if (stg == Stage.SUBMIT)
+    {
       return "submitting ";
+    }
     if (stg == Stage.POLL)
+    {
       return "checking status of ";
+    }
 
     return (" being confused about ");
   }
@@ -177,6 +201,7 @@ public class RestJobThread extends AWSThread
   {
     String postUrl = rj.getPostUrl();
     doHttpReq(Stage.SUBMIT, rj, postUrl);
+    wsInfo.invalidate();
   }
 
   /**
@@ -210,8 +235,8 @@ public class RestJobThread extends AWSThread
       {
         if (input.getValue().validFor(rj))
         {
-          postentity.addPart(input.getKey(), input.getValue()
-                  .formatForInput(rj));
+          postentity.addPart(input.getKey(),
+                  input.getValue().formatForInput(rj));
         }
         else
         {
@@ -237,18 +262,22 @@ public class RestJobThread extends AWSThread
         response = httpclient.execute(request);
       } catch (ClientProtocolException he)
       {
-        rj.statMessage = "Web Protocol Exception when attempting to  "
-                + getStage(stg) + "Job. See Console output for details.";
+        rj.statMessage = "Web Protocol Exception when " + getStage(stg)
+                + "Job. <br>Problematic url was <a href=\""
+                + request.getURI() + "\">" + request.getURI()
+                + "</a><br>See Console output for details.";
         rj.setAllowedServerExceptions(0);// unrecoverable;
         rj.error = true;
-        Cache.log.fatal("Unexpected REST Job " + getStage(stg)
+        Cache.fatal("Unexpected REST Job " + getStage(stg)
                 + "exception for URL " + rj.rsd.postUrl);
         throw (he);
       } catch (IOException e)
       {
-        rj.statMessage = "IO Exception when attempting to  "
-                + getStage(stg) + "Job. See Console output for details.";
-        Cache.log.warn("IO Exception for REST Job " + getStage(stg)
+        rj.statMessage = "IO Exception when " + getStage(stg)
+                + "Job. <br>Problematic url was <a href=\""
+                + request.getURI() + "\">" + request.getURI()
+                + "</a><br>See Console output for details.";
+        Cache.warn("IO Exception for REST Job " + getStage(stg)
                 + "exception for URL " + rj.rsd.postUrl);
 
         throw (e);
@@ -257,15 +286,12 @@ public class RestJobThread extends AWSThread
       {
       case 200:
         rj.running = false;
-        Cache.log.debug("Processing result set.");
+        Cache.debug("Processing result set.");
         processResultSet(rj, response, request);
         break;
       case 202:
         rj.statMessage = "<br>Job submitted successfully. Results available at this URL:\n"
-                + "<a href="
-                + rj.getJobId()
-                + "\">"
-                + rj.getJobId()
+                + "<a href=" + rj.getJobId() + "\">" + rj.getJobId()
                 + "</a><br>";
         rj.running = true;
         break;
@@ -278,11 +304,9 @@ public class RestJobThread extends AWSThread
         {
           if (loc.length > 1)
           {
-            Cache.log
-                    .warn("Ignoring additional "
-                            + (loc.length - 1)
-                            + " location(s) provided in response header ( next one is '"
-                            + loc[1].getValue() + "' )");
+            Cache.warn("Ignoring additional " + (loc.length - 1)
+                    + " location(s) provided in response header ( next one is '"
+                    + loc[1].getValue() + "' )");
           }
           rj.setJobId(loc[0].getValue());
           rj.setSubmitted(true);
@@ -296,20 +320,27 @@ public class RestJobThread extends AWSThread
         rj.setSubjobComplete(true);
         rj.error = true;
         rj.running = false;
-        completeStatus(rj, response, "" + getStage(stg)
-                + "failed. Reason below:\n");
+        completeStatus(rj, response,
+                "" + getStage(stg) + "failed. Reason below:\n");
         break;
       default:
         // Some other response. Probably need to pop up the content in a window.
         // TODO: deal with all other HTTP response codes from server.
-        Cache.log.warn("Unhandled response status when " + getStage(stg)
+        Cache.warn("Unhandled response status when " + getStage(stg)
                 + "for " + postUrl + ": " + response.getStatusLine());
+        rj.error = true;
+        rj.setAllowedServerExceptions(0);
+        rj.setSubjobComplete(true);
+        rj.setSubmitted(true);
         try
         {
-          response.getEntity().consumeContent();
+          completeStatus(rj, response, "" + getStage(stg)
+                  + " resulted in an unexpected server response.<br/>Url concerned was <a href=\""
+                  + request.getURI() + "\">" + request.getURI()
+                  + "</a><br/>Filtered response content below:<br/>");
         } catch (IOException e)
         {
-          Cache.log.debug("IOException when consuming unhandled response",
+          Cache.debug("IOException when consuming unhandled response",
                   e);
         }
         ;
@@ -372,11 +403,11 @@ public class RestJobThread extends AWSThread
      */
     String f;
     StringBuffer content = new StringBuffer(f = EntityUtils.toString(en));
-    f = f.toLowerCase();
+    f = f.toLowerCase(Locale.ROOT);
     int body = f.indexOf("<body");
     if (body > -1)
     {
-      content.delete(0, f.indexOf(">", body));
+      content.delete(0, f.indexOf(">", body) + 1);
     }
     if (body > -1 && sb.length() > 0)
     {
@@ -404,11 +435,19 @@ public class RestJobThread extends AWSThread
     {
       System.err.println("Debug RestJob: Posting Job");
       doPost((RestJob) job);
+    } catch (NoValidInputDataException erex)
+    {
+      job.setSubjobComplete(true);
+      job.setSubmitted(true);
+      ((RestJob) job).statMessage = "<br>It looks like there was a problem with the data sent to the service :<br>"
+              + erex.getMessage() + "\n";
+      ((RestJob) job).error = true;
+
     } catch (Exception ex)
     {
       job.setSubjobComplete(true);
       job.setAllowedServerExceptions(-1);
-      Cache.log.error("Exception when trying to start Rest Job.", ex);
+      Cache.error("Exception when trying to start Rest Job.", ex);
     }
   }
 
@@ -417,7 +456,7 @@ public class RestJobThread extends AWSThread
   {
     // crazy users will see this message
     // TODO: finish this! and remove the message below!
-    Cache.log.warn("Rest job result parser is currently INCOMPLETE!");
+    Cache.warn("Rest job result parser is currently INCOMPLETE!");
     int validres = 0;
     for (RestJob rj : (RestJob[]) jobs)
     {
@@ -426,24 +465,30 @@ public class RestJobThread extends AWSThread
         String ln = null;
         try
         {
-          Cache.log.debug("Parsing data for job " + rj.getJobId());
+          Cache.debug("Parsing data for job " + rj.getJobId());
           rj.parseResultSet();
           if (rj.hasResults())
           {
             validres++;
           }
-          Cache.log.debug("Finished parsing data for job " + rj.getJobId());
+          Cache.debug("Finished parsing data for job " + rj.getJobId());
 
         } catch (Error ex)
         {
-          Cache.log.warn("Failed to finish parsing data for job "
-                  + rj.getJobId());
+          Cache.warn(
+                  "Failed to finish parsing data for job " + rj.getJobId());
           ex.printStackTrace();
         } catch (Exception ex)
         {
-          Cache.log.warn("Failed to finish parsing data for job "
-                  + rj.getJobId());
+          Cache.warn(
+                  "Failed to finish parsing data for job " + rj.getJobId());
           ex.printStackTrace();
+        } finally
+        {
+          rj.error = true;
+          rj.statMessage = "Error whilst parsing data for this job.<br>URL for job response is :<a href=\""
+                  + rj.resSet.getUrl() + "\">" + rj.resSet.getUrl()
+                  + "</a><br>";
         }
       }
     }
@@ -459,7 +504,8 @@ public class RestJobThread extends AWSThread
        */
       if (true)
       {
-        wsInfo.setViewResultsImmediatly(false);
+        // preserver current jalview behaviour
+        wsInfo.setViewResultsImmediatly(true);
       }
       else
       {
@@ -495,6 +541,8 @@ public class RestJobThread extends AWSThread
     else
     {
       // tell the user nothing was returned.
+      wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
+      wsInfo.setFinishedNoResults();
     }
   }
 
@@ -554,7 +602,7 @@ public class RestJobThread extends AWSThread
     // total number of distinct alignment sets generated by job set.
     int numAlSets = 0, als = 0;
     List<AlignmentI> destAls = new ArrayList<AlignmentI>();
-    List<jalview.datamodel.ColumnSelection> destColsel = new ArrayList<jalview.datamodel.ColumnSelection>();
+    List<jalview.datamodel.HiddenColumns> destColsel = new ArrayList<jalview.datamodel.HiddenColumns>();
     List<List<NewickFile>> trees = new ArrayList<List<NewickFile>>();
 
     do
@@ -660,7 +708,7 @@ public class RestJobThread extends AWSThread
           RestJob rj = (RestJob) jobs[nrj];
           int contigs[] = input.getVisibleContigs();
           AlignmentI destAl = null;
-          jalview.datamodel.ColumnSelection destCs = null;
+          jalview.datamodel.HiddenColumns destHCs = null;
           // Resolve destAl for this data.
           if (als == 0 && rj.isInputContextModified())
           {
@@ -676,10 +724,10 @@ public class RestJobThread extends AWSThread
               if (!restClient.isAlignmentModified() && merge)
               {
                 destAl = restClient.av.getAlignment();
-                destCs = restClient.av.getColumnSelection();
-                resultDest
-                        .add(restClient.isShowResultsInNewView() ? AddDataTo.newView
-                                : AddDataTo.currentView);
+                destHCs = restClient.av.getAlignment().getHiddenColumns();
+                resultDest.add(restClient.isShowResultsInNewView()
+                        ? AddDataTo.newView
+                        : AddDataTo.currentView);
                 destPanels.add(restClient.recoverAlignPanelForView());
               }
               else
@@ -687,15 +735,15 @@ public class RestJobThread extends AWSThread
                 newAlignment = true;
                 // recreate the input alignment data
                 Object[] idat = input
-                        .getAlignmentAndColumnSelection(gapCharacter);
+                        .getAlignmentAndHiddenColumns(gapCharacter);
                 destAl = new Alignment((SequenceI[]) idat[0]);
-                destCs = (ColumnSelection) idat[1];
+                destHCs = (HiddenColumns) idat[1];
                 resultDest.add(AddDataTo.newAlignment);
                 // but do not add to the alignment panel list - since we need to
                 // create a whole new alignFrame set.
               }
               destAls.add(destAl);
-              destColsel.add(destCs);
+              destColsel.add(destHCs);
             }
           }
           else
@@ -708,13 +756,13 @@ public class RestJobThread extends AWSThread
               {
                 // TODO: decide if multiple multiple alignments returned by
                 // non-vseparable services are allowed.
-                Cache.log
-                        .warn("dealing with multiple alignment products returned by non-vertically separable service.");
+                Cache.warn(
+                        "dealing with multiple alignment products returned by non-vertically separable service.");
               }
               // recover reference to last alignment created for this rest frame
               // ready for extension
               destAl = destAls.get(als);
-              destCs = destColsel.get(als);
+              destHCs = destColsel.get(als);
             }
             else
             {
@@ -743,14 +791,16 @@ public class RestJobThread extends AWSThread
                 newview = input.getUpdatedView(rseqs, orders, gapCharacter);
               }
               destAl = new Alignment((SequenceI[]) newview[0]);
-              destCs = (ColumnSelection) newview[1];
+              destHCs = (HiddenColumns) newview[1];
               newAlignment = true;
               // TODO create alignment from result data with propagated
               // references.
               destAls.add(destAl);
-              destColsel.add(destCs);
+              destColsel.add(destHCs);
               resultDest.add(AddDataTo.newAlignment);
-              throw new Error("Impl. Error! TODO: ");
+              throw new Error(
+                      MessageManager.getString("error.implementation_error")
+                              + "TODO: ");
             }
           }
           /**
@@ -795,7 +845,9 @@ public class RestJobThread extends AWSThread
                   visgrps.add(exsg);
                   exsg.setStartRes(sg.getStartRes() + contigs[ncnt]);
                   exsg.setEndRes(sg.getEndRes() + contigs[ncnt]);
-                } else {
+                }
+                else
+                {
                   recovered = true;
                 }
                 // now replace any references from the result set with
@@ -804,10 +856,8 @@ public class RestJobThread extends AWSThread
                 // TODO: cope with recovering hidden sequences from
                 // resultContext
                 {
-                  Vector sqs = sg.getSequences(null);
-                  for (int sqsi = 0, iSize = sqs.size(); sqsi < iSize; sqsi++)
+                  for (SequenceI oseq : sg.getSequences(null))
                   {
-                    SequenceI oseq = (SequenceI) sqs.get(sqsi);
                     SequenceI nseq = getNewSeq(oseq, rseqs[nrj],
                             ordermap[nrj], destAl);
                     if (nseq != null)
@@ -820,8 +870,8 @@ public class RestJobThread extends AWSThread
                     }
                     else
                     {
-                      Cache.log
-                              .warn("Couldn't resolve original sequence for new sequence.");
+                      Cache.warn(
+                              "Couldn't resolve original sequence for new sequence.");
                     }
                   }
                   if (sg.hasSeqrep())
@@ -843,8 +893,8 @@ public class RestJobThread extends AWSThread
                 {
                   // adjust boundaries of recovered group w.r.t. new group being
                   // merged on to original alignment.
-                  int start = sg.getStartRes() + contigs[ncnt], end = sg
-                          .getEndRes() + contigs[ncnt];
+                  int start = sg.getStartRes() + contigs[ncnt],
+                          end = sg.getEndRes() + contigs[ncnt];
                   if (start < exsg.getStartRes())
                   {
                     exsg.setStartRes(start);
@@ -898,8 +948,8 @@ public class RestJobThread extends AWSThread
                   grass = groupNames.get(alan[nrj][an].groupRef.getName());
                   if (grass == null)
                   {
-                    Cache.log
-                            .error("Couldn't relocate group referemce for group "
+                    Cache.error(
+                            "Couldn't relocate group referemce for group "
                                     + alan[nrj][an].groupRef.getName());
                   }
                 }
@@ -925,12 +975,15 @@ public class RestJobThread extends AWSThread
                   visan.sequenceRef = sqass;
                   visAlAn.add(visan);
                 }
-                if (contigs[ncnt]+alan[nrj][an].annotations.length>visan.annotations.length)
+                if (contigs[ncnt]
+                        + alan[nrj][an].annotations.length > visan.annotations.length)
                 {
                   // increase width of annotation row
-                  Annotation[] newannv = new Annotation[contigs[ncnt]+alan[nrj][an].annotations.length];
-                  System.arraycopy(visan.annotations, 0, newannv, 0, visan.annotations.length);
-                  visan.annotations=newannv;
+                  Annotation[] newannv = new Annotation[contigs[ncnt]
+                          + alan[nrj][an].annotations.length];
+                  System.arraycopy(visan.annotations, 0, newannv, 0,
+                          visan.annotations.length);
+                  visan.annotations = newannv;
                 }
                 // now copy local annotation data into correct position
                 System.arraycopy(alan[nrj][an].annotations, 0,
@@ -946,8 +999,8 @@ public class RestJobThread extends AWSThread
               {
                 // TODO: process each newick file, lifting over sequence refs to
                 // current alignment, if necessary.
-                Cache.log
-                        .error("Tree recovery from restjob not yet implemented.");
+                Cache.error(
+                        "Tree recovery from restjob not yet implemented.");
               }
             }
           }
@@ -983,10 +1036,11 @@ public class RestJobThread extends AWSThread
     for (AddDataTo action : resultDest)
     {
       AlignmentI destal;
-      ColumnSelection destcs;
-      String alTitle = restClient.service.details.Action + " using "
-              + restClient.service.details.Name + " on "
-              + " whatever you clicked on." + "(set " + als + ")";
+      HiddenColumns destcs;
+      String alTitle = MessageManager
+              .formatMessage("label.webservice_job_title_on", new String[]
+              { restClient.service.details.Action,
+                  restClient.service.details.Name, restClient.viewTitle });
       switch (action)
       {
       case newAlignment:
@@ -994,7 +1048,8 @@ public class RestJobThread extends AWSThread
         destcs = destColsel.get(als);
         destaf = new AlignFrame(destal, destcs, AlignFrame.DEFAULT_WIDTH,
                 AlignFrame.DEFAULT_HEIGHT);
-        PaintRefresher.Refresh(destaf, destaf.getViewport().getSequenceSetId());
+        PaintRefresher.Refresh(destaf,
+                destaf.getViewport().getSequenceSetId());
         // todo transfer any feature settings and colouring
         /*
          * destaf.getFeatureRenderer().transferSettings(this.featureSettings);
@@ -1014,12 +1069,14 @@ public class RestJobThread extends AWSThread
          * i++) { af.addSortByOrderMenuItem( WebServiceName + ((String)
          * names.get(i)) + " Ordering", (AlignmentOrder) alorders.get(i)); } } }
          */
+        // TODO: modify this and previous alignment's title if many alignments
+        // have been returned.
         Desktop.addInternalFrame(destaf, alTitle, AlignFrame.DEFAULT_WIDTH,
                 AlignFrame.DEFAULT_HEIGHT);
 
         break;
       case newView:
-
+        // TODO: determine title for view
         break;
       case currentView:
         break;
@@ -1120,8 +1177,8 @@ public class RestJobThread extends AWSThread
           {
             if (start + width < end)
             {
-              blocks[c][s] = sequenceIs[s].getSubSequence(start, start
-                      + width);
+              blocks[c][s] = sequenceIs[s].getSubSequence(start,
+                      start + width);
             }
             else
             {
@@ -1193,6 +1250,8 @@ public class RestJobThread extends AWSThread
    */
   public boolean isValid()
   {
+    ArrayList<String> _warnings = new ArrayList<String>();
+    boolean validt = true;
     if (jobs != null)
     {
       for (RestJob rj : (RestJob[]) jobs)
@@ -1201,14 +1260,48 @@ public class RestJobThread extends AWSThread
         {
           // invalid input for this job
           System.err.println("Job " + rj.getJobnum()
-                  + " has invalid input.");
-          return false;
+                  + " has invalid input. ( " + rj.getStatus() + ")");
+          if (rj.hasStatus() && !_warnings.contains(rj.getStatus()))
+          {
+            _warnings.add(rj.getStatus());
+          }
+          validt = false;
         }
       }
-      return true;
     }
+    if (!validt)
+    {
+      warnings = "";
+      for (String st : _warnings)
+      {
+        if (warnings.length() > 0)
+        {
+          warnings += "\n";
+        }
+        warnings += st;
+
+      }
+    }
+    return validt;
+  }
+
+  protected String warnings;
+
+  public boolean hasWarnings()
+  {
     // TODO Auto-generated method stub
-    return false;
+    return warnings != null && warnings.length() > 0;
+  }
+
+  /**
+   * get any informative messages about why the job thread couldn't start.
+   * 
+   * @return
+   */
+  public String getWarnings()
+  {
+    return isValid() ? "Job can be started. No warnings."
+            : hasWarnings() ? warnings : "";
   }
 
 }