JAL-2103 extracted result processing code to allow mocking
[jalview.git] / src / jalview / ws / jws1 / JPredThread.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.ws.jws1;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.bin.Cache;
25 import jalview.datamodel.Alignment;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.AlignmentView;
28 import jalview.datamodel.ColumnSelection;
29 import jalview.datamodel.SequenceI;
30 import jalview.gui.AlignFrame;
31 import jalview.gui.Desktop;
32 import jalview.gui.WebserviceInfo;
33 import jalview.io.FileParse;
34 import jalview.io.FormatAdapter;
35 import jalview.util.Comparison;
36 import jalview.util.MessageManager;
37 import jalview.ws.AWsJob;
38 import jalview.ws.JobStateSummary;
39 import jalview.ws.WSClientI;
40
41 import java.io.IOException;
42 import java.util.Hashtable;
43
44 import vamsas.objects.simple.JpredResult;
45
46 class JPredThread extends JWS1Thread implements WSClientI
47 {
48   // TODO: put mapping between JPredJob input and input data here -
49   // JNetAnnotation adding is done after result parsing.
50   class JPredJob extends WSJob
51   {
52     // TODO: make JPredJob deal only with what was sent to and received from a
53     // JNet service
54     int[] predMap = null; // mapping from sequence(i) to the original
55
56     // sequence(predMap[i]) being predicted on
57
58     vamsas.objects.simple.Sequence sequence;
59
60     vamsas.objects.simple.Msfalignment msa;
61
62     java.util.Hashtable SequenceInfo = null;
63
64     /**
65      * 
66      * @return true if getResultSet will return a valid alignment and prediction
67      *         result.
68      */
69     @Override
70     public boolean hasResults()
71     {
72       if (subjobComplete && result != null && result.isFinished()
73               && ((JpredResult) result).getPredfile() != null
74               && ((JpredResult) result).getAligfile() != null)
75       {
76         return true;
77       }
78       return false;
79     }
80
81     @Override
82     public boolean hasValidInput()
83     {
84       if (sequence != null)
85       {
86         return true;
87       }
88       return false;
89     }
90
91     /**
92      * 
93      * @return null or Object[] { annotated alignment for this prediction,
94      *         ColumnSelection for this prediction} or null if no results
95      *         available.
96      * @throws Exception
97      */
98     public Object[] getResultSet() throws Exception
99     {
100       if (result == null || !result.isFinished())
101       {
102         return null;
103       }
104
105       JpredResult result = (JpredResult) this.result;
106
107       jalview.bin.Cache.log.debug("Parsing output from JNet job.");
108       Object[] resview = JPredWSUtils.processJnetResult(currentView, input,
109               getGapChar(), SequenceInfo, this.msa != null,
110               this.predMap,
111               result.getPredfile(), result.getAligfile(),
112               getFullAlignmentSource());
113       return resview; // Alignment, ColumnSelection
114     }
115
116     public FileParse getFullAlignmentSource() throws IOException
117     {
118       // locate full alignment
119       // http://www.compbio.dundee.ac.uk/jpred/results/jp_GuygEzV/jp_GuygEzV.full_MSA.fasta
120       String jobid = getJobId().substring(getJobId().lastIndexOf("/") + 1);
121       String job_alignment = "http://www.compbio.dundee.ac.uk/jpred/results/"
122               + jobid + "/" + jobid + ".full_MSA.fasta";
123       return new FileParse(job_alignment, FormatAdapter.URL);
124     }
125
126     public JPredJob(Hashtable SequenceInfo, SequenceI seq, int[] delMap)
127     {
128       super();
129       this.predMap = delMap;
130       String sq = AlignSeq.extractGaps(Comparison.GapChars,
131               seq.getSequenceAsString());
132       if (sq.length() >= 20)
133       {
134         this.SequenceInfo = SequenceInfo;
135         sequence = new vamsas.objects.simple.Sequence();
136         sequence.setId(seq.getName());
137         sequence.setSeq(sq);
138       }
139       else
140       {
141         errorMessage = "Sequence is too short to predict with JPred - need at least 20 amino acids.";
142       }
143     }
144
145     public JPredJob(Hashtable SequenceInfo, SequenceI[] msf, int[] delMap)
146     {
147       this(SequenceInfo, msf[0], delMap);
148       if (sequence != null)
149       {
150         if (msf.length > 1)
151         {
152           msa = new vamsas.objects.simple.Msfalignment();
153           jalview.io.PileUpfile pileup = new jalview.io.PileUpfile();
154           msa.setMsf(pileup.print(msf));
155         }
156       }
157     }
158
159     String errorMessage = "";
160
161     public String getValidationMessages()
162     {
163       return errorMessage + "\n";
164     }
165   }
166
167   ext.vamsas.Jpred server;
168
169   String altitle = "";
170
171   JPredThread(WebserviceInfo wsinfo, String altitle,
172           ext.vamsas.Jpred server, String wsurl, AlignmentView alview,
173           AlignFrame alframe)
174   {
175     super(alframe, wsinfo, alview, wsurl);
176     this.altitle = altitle;
177     this.server = server;
178   }
179
180   JPredThread(WebserviceInfo wsinfo, String altitle,
181           ext.vamsas.Jpred server, String wsurl, Hashtable SequenceInfo,
182           SequenceI seq, int[] delMap, AlignmentView alview,
183           AlignFrame alframe)
184   {
185     this(wsinfo, altitle, server, wsurl, alview, alframe);
186     JPredJob job = new JPredJob(SequenceInfo, seq, delMap);
187     if (job.hasValidInput())
188     {
189       OutputHeader = wsInfo.getProgressText();
190       jobs = new WSJob[] { job };
191       job.setJobnum(0);
192     }
193     else
194     {
195       wsInfo.appendProgressText(job.getValidationMessages());
196     }
197   }
198
199   JPredThread(WebserviceInfo wsinfo, String altitle,
200           ext.vamsas.Jpred server, Hashtable SequenceInfo, SequenceI[] msf,
201           int[] delMap, AlignmentView alview, AlignFrame alframe,
202           String wsurl)
203   {
204     this(wsinfo, altitle, server, wsurl, alview, alframe);
205     JPredJob job = new JPredJob(SequenceInfo, msf, delMap);
206     if (job.hasValidInput())
207     {
208       jobs = new WSJob[] { job };
209       OutputHeader = wsInfo.getProgressText();
210       job.setJobnum(0);
211     }
212     else
213     {
214       wsInfo.appendProgressText(job.getValidationMessages());
215     }
216   }
217
218   @Override
219   public void StartJob(AWsJob j)
220   {
221     if (!(j instanceof JPredJob))
222     {
223       throw new Error(MessageManager.formatMessage(
224               "error.implementation_error_startjob_called",
225               new String[] { j.getClass().toString() }));
226     }
227     try
228     {
229       JPredJob job = (JPredJob) j;
230       if (job.msa != null)
231       {
232         job.setJobId(server.predictOnMsa(job.msa));
233       }
234       else if (job.sequence != null)
235       {
236         job.setJobId(server.predict(job.sequence)); // debug like : job.jobId =
237         // "/jobs/www-jpred/jp_Yatat29";//
238       }
239
240       if (job.getJobId() != null)
241       {
242         if (job.getJobId().startsWith("Broken"))
243         {
244           job.result = new JpredResult();
245           job.result.setInvalid(true);
246           job.result.setStatus(MessageManager.formatMessage(
247                   "label.submission_params", new String[] { job.getJobId()
248                           .toString() }));
249           throw new Exception(job.getJobId());
250         }
251         else
252         {
253           job.setSubmitted(true);
254           job.setSubjobComplete(false);
255           Cache.log.info(WsUrl + " Job Id '" + job.getJobId() + "'");
256         }
257       }
258       else
259       {
260         throw new Exception(
261                 MessageManager
262                         .getString("exception.server_timeout_try_later"));
263       }
264     } catch (Exception e)
265     {
266       // kill the whole job.
267       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
268       if (e.getMessage().indexOf("Exception") > -1)
269       {
270         wsInfo.setStatus(j.getJobnum(),
271                 WebserviceInfo.STATE_STOPPED_SERVERERROR);
272         wsInfo.setProgressText(
273                 j.getJobnum(),
274                 "Failed to submit the prediction. (Just close the window)\n"
275                         + "It is most likely that there is a problem with the server.\n");
276         System.err
277                 .println("JPredWS Client: Failed to submit the prediction. Quite possibly because of a server error - see below)\n"
278                         + e.getMessage() + "\n");
279
280         jalview.bin.Cache.log.warn("Server Exception", e);
281       }
282       else
283       {
284         wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
285         // JBPNote - this could be a popup informing the user of the problem.
286         wsInfo.appendProgressText(j.getJobnum(), MessageManager
287                 .formatMessage(
288                         "info.failed_to_submit_prediction",
289                         new String[] { e.getMessage(),
290                             wsInfo.getProgressText() }));
291
292         jalview.bin.Cache.log.debug(
293                 "Failed Submission of job " + j.getJobnum(), e);
294
295       }
296       j.setAllowedServerExceptions(-1);
297       j.setSubjobComplete(true);
298     }
299   }
300
301   @Override
302   public void parseResult()
303   {
304     int results = 0; // number of result sets received
305     JobStateSummary finalState = new JobStateSummary();
306     try
307     {
308       for (int j = 0; j < jobs.length; j++)
309       {
310         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
311         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
312                 && jobs[j].hasResults())
313         {
314           results++;
315         }
316       }
317     } catch (Exception ex)
318     {
319
320       Cache.log.error("Unexpected exception when processing results for "
321               + altitle, ex);
322       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
323     }
324     if (results > 0)
325     {
326       wsInfo.showResultsNewFrame
327               .addActionListener(new java.awt.event.ActionListener()
328               {
329                 @Override
330                 public void actionPerformed(java.awt.event.ActionEvent evt)
331                 {
332                   displayResults(true);
333                 }
334               });
335       wsInfo.mergeResults
336               .addActionListener(new java.awt.event.ActionListener()
337               {
338                 @Override
339                 public void actionPerformed(java.awt.event.ActionEvent evt)
340                 {
341                   displayResults(false);
342                 }
343               });
344       wsInfo.setResultsReady();
345     }
346     else
347     {
348       wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
349       wsInfo.appendInfoText("No jobs ran.");
350       wsInfo.setFinishedNoResults();
351     }
352   }
353
354   void displayResults(boolean newWindow)
355   {
356     // TODO: cope with multiple subjobs.
357     if (jobs != null)
358     {
359       Object[] res = null;
360       boolean msa = false;
361       for (int jn = 0; jn < jobs.length; jn++)
362       {
363         Object[] jobres = null;
364         JPredJob j = (JPredJob) jobs[jn];
365
366         if (j.hasResults())
367         {
368           // hack - we only deal with all single seuqence predictions or all
369           // profile predictions
370           msa = (j.msa != null) ? true : msa;
371           try
372           {
373             jalview.bin.Cache.log.debug("Parsing output of job " + jn);
374             jobres = j.getResultSet();
375             jalview.bin.Cache.log.debug("Finished parsing output.");
376             if (jobs.length == 1)
377             {
378               res = jobres;
379             }
380             else
381             {
382               // do merge with other job results
383               throw new Error(
384                       MessageManager
385                               .getString("error.multiple_jnet_subjob_merge_not_implemented"));
386             }
387           } catch (Exception e)
388           {
389             jalview.bin.Cache.log.error(
390                     "JNet Client: JPred Annotation Parse Error", e);
391             wsInfo.setStatus(j.getJobnum(),
392                     WebserviceInfo.STATE_STOPPED_ERROR);
393             wsInfo.appendProgressText(j.getJobnum(), MessageManager
394                     .formatMessage("info.invalid_jnet_job_result_data",
395                             new String[] { OutputHeader.toString(),
396                                 j.result.getStatus(), e.getMessage() }));
397             j.result.setBroken(true);
398           }
399         }
400       }
401
402       if (res != null)
403       {
404         if (newWindow)
405         {
406           AlignFrame af;
407           ((AlignmentI) res[0]).setSeqrep(((AlignmentI) res[0])
408                   .getSequenceAt(0));
409           if (input == null)
410           {
411             if (res[1] != null)
412             {
413               af = new AlignFrame((Alignment) res[0],
414                       (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,
415                       AlignFrame.DEFAULT_HEIGHT);
416             }
417             else
418             {
419               af = new AlignFrame((Alignment) res[0],
420                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
421             }
422           }
423           else
424           {
425             /*
426              * java.lang.Object[] alandcolsel =
427              * input.getAlignmentAndColumnSelection
428              * (alignFrame.getViewport().getGapCharacter()); if
429              * (((SequenceI[])alandcolsel[0])[0].getLength()!=res.getWidth()) {
430              * if (msa) { throw new Error("Implementation Error! ColumnSelection
431              * from input alignment will not map to result alignment!"); } } if
432              * (!msa) { // update hidden regions to account for loss of gaps in
433              * profile. - if any // gapMap returns insert list, interpreted as
434              * delete list by pruneDeletions //((ColumnSelection)
435              * alandcolsel[1]).pruneDeletions(ShiftList.parseMap(((SequenceI[])
436              * alandcolsel[0])[0].gapMap())); }
437              */
438
439             af = new AlignFrame((Alignment) res[0],
440                     (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,
441                     AlignFrame.DEFAULT_HEIGHT);
442           }
443           Desktop.addInternalFrame(af, altitle, AlignFrame.DEFAULT_WIDTH,
444                   AlignFrame.DEFAULT_HEIGHT);
445         }
446         else
447         {
448           Cache.log.info("Append results onto existing alignment.");
449         }
450       }
451     }
452   }
453
454   @Override
455   public void pollJob(AWsJob job) throws Exception
456   {
457     ((JPredJob) job).result = server.getresult(job.getJobId());
458   }
459
460   @Override
461   public boolean isCancellable()
462   {
463     return false;
464   }
465
466   @Override
467   public void cancelJob()
468   {
469     throw new Error(MessageManager.getString("error.implementation_error"));
470   }
471
472   @Override
473   public boolean canMergeResults()
474   {
475     return false;
476   }
477
478 }