94e65aaf4040ceead90b9749a90ccf2259b76f15
[jalview.git] / src / jalview / ws / gui / MsaWSThread.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.gui;
22
23 import jalview.bin.Cache;
24 import jalview.bin.Console;
25 import jalview.datamodel.Alignment;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.AlignmentOrder;
28 import jalview.datamodel.AlignmentView;
29 import jalview.datamodel.HiddenColumns;
30 import jalview.datamodel.SequenceI;
31 import jalview.gui.AlignFrame;
32 import jalview.gui.Desktop;
33 import jalview.gui.SplitFrame;
34 import jalview.gui.WebserviceInfo;
35 import jalview.util.MessageManager;
36 import jalview.ws.AWSThread;
37 import jalview.ws.AWsJob;
38 import jalview.ws.JobStateSummary;
39 import jalview.ws.WSClientI;
40 import jalview.ws.api.CancellableI;
41 import jalview.ws.api.JobId;
42 import jalview.ws.api.MultipleSequenceAlignmentI;
43 import jalview.ws.gui.WsJob.JobState;
44 import jalview.ws.params.ArgumentI;
45 import jalview.ws.params.WsParamSetI;
46
47 import java.util.ArrayList;
48 import java.util.List;
49
50 import javax.swing.JInternalFrame;
51
52 public class MsaWSThread extends AWSThread implements WSClientI
53 {
54   boolean submitGaps = false; // pass sequences including gaps to alignment
55
56   // service
57
58   boolean preserveOrder = true; // and always store and recover sequence
59
60   // order
61
62
63   String alTitle; // name which will be used to form new alignment window.
64
65   AlignmentI dataset; // dataset to which the new alignment will be
66
67   // associated.
68
69   MultipleSequenceAlignmentI server = null;
70
71   /**
72    * set basic options for this (group) of Msa jobs
73    * 
74    * @param subgaps
75    *          boolean
76    * @param presorder
77    *          boolean
78    */
79   private MsaWSThread(MultipleSequenceAlignmentI server, String wsUrl,
80           WebserviceInfo wsinfo,
81           jalview.gui.AlignFrame alFrame, AlignmentView alview,
82           String wsname, boolean subgaps, boolean presorder)
83   {
84     super(alFrame, wsinfo, alview, wsname, wsUrl);
85     this.server = server;
86     this.submitGaps = subgaps;
87     this.preserveOrder = presorder;
88   }
89
90   /**
91    * create one or more Msa jobs to align visible sequences in _msa
92    * 
93    * @param title
94    *          String
95    * @param _msa
96    *          AlignmentView
97    * @param subgaps
98    *          boolean
99    * @param presorder
100    *          boolean
101    * @param seqset
102    *          Alignment
103    */
104   public MsaWSThread(MultipleSequenceAlignmentI server2, WsParamSetI preset,
105           List<ArgumentI> paramset,
106           String wsUrl, WebserviceInfo wsinfo,
107           jalview.gui.AlignFrame alFrame, String wsname, String title,
108           AlignmentView _msa, boolean subgaps, boolean presorder,
109           AlignmentI seqset)
110   {
111     this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
112     OutputHeader = wsInfo.getProgressText();
113     alTitle = title;
114     dataset = seqset;
115
116     SequenceI[][] conmsa = _msa.getVisibleContigs('-');
117     if (conmsa != null)
118     {
119       int nvalid = 0, njobs = conmsa.length;
120       jobs = new AWsJob[njobs];
121       for (int j = 0; j < njobs; j++)
122       {
123         if (j != 0)
124         {
125           jobs[j] = new MsaWSJob(this, wsinfo.addJobPane(), conmsa[j]);
126         }
127         else
128         {
129           jobs[j] = new MsaWSJob(this, 0, conmsa[j]);
130         }
131         if (jobs[j].hasValidInput())
132         {
133           nvalid++;
134         }
135         jobs[j].setPreset(preset);
136         jobs[j].setArguments(paramset);
137         ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
138         if (njobs > 0)
139         {
140           wsinfo.setProgressName("region " + jobs[j].getJobnum(),
141                   jobs[j].getJobnum());
142         }
143         wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
144       }
145       validInput = nvalid > 0;
146     }
147   }
148
149   boolean validInput = false;
150
151   /**
152    * 
153    * @return true if the thread will perform a calculation
154    */
155   public boolean hasValidInput()
156   {
157     return validInput;
158   }
159
160   @Override
161   public boolean isCancellable()
162   {
163     return server instanceof CancellableI;
164   }
165
166   @Override
167   public void cancelJob()
168   {
169     // TODO decide if when some jobs are not cancellable to shut down the thread
170     // anyhow ?
171     if (!jobComplete && jobs != null)
172     {
173       boolean cancelled = true;
174       for (int job = 0; job < jobs.length; job++)
175       {
176         if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
177         {
178           String cancelledMessage = "";
179           try
180           {
181             CancellableI service = (CancellableI) server;
182             boolean cancelledJob = service.cancel((WsJob) jobs[job]);
183             if (cancelledJob)
184             {
185               // CANCELLED_JOB
186               cancelledMessage = "Job cancelled.";
187               ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
188                                                // ugliness -
189               wsInfo.setStatus(jobs[job].getJobnum(),
190                       WebserviceInfo.STATE_CANCELLED_OK);
191             }
192             else
193             {
194               // VALID UNSTOPPABLE JOB
195               cancelledMessage += "Server cannot cancel this job. just close the window.\n";
196               cancelled = false;
197               // wsInfo.setStatus(jobs[job].jobnum,
198               // WebserviceInfo.STATE_RUNNING);
199             }
200           } catch (Exception exc)
201           {
202             cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
203                     + exc + "\n");
204             Console.warn(
205                     "Exception whilst cancelling " + jobs[job].getJobId(),
206                     exc);
207           }
208           wsInfo.setProgressText(jobs[job].getJobnum(),
209                   OutputHeader + cancelledMessage + "\n");
210         }
211         else
212         {
213           // if we hadn't submitted then just mark the job as cancelled.
214           jobs[job].setSubjobComplete(true);
215           wsInfo.setStatus(jobs[job].getJobnum(),
216                   WebserviceInfo.STATE_CANCELLED_OK);
217
218         }
219       }
220       if (cancelled)
221       {
222         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
223         jobComplete = true;
224       }
225       this.interrupt(); // kick thread to update job states.
226     }
227     else
228     {
229       if (!jobComplete)
230       {
231         wsInfo.setProgressText(OutputHeader
232                 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
233       }
234     }
235   }
236
237   @Override
238   public void pollJob(AWsJob job) throws Exception
239   {
240     // TODO: investigate if we still need to cast here in J1.6
241     MsaWSJob j = ((MsaWSJob) job);
242     // this is standard code, but since the interface doesn't comprise of a
243     // basic one that implements (getJobStatus, pullExecStatistics) we have to
244     // repeat the code for all jw2s services.
245     server.updateStatus(j);
246     server.updateJobProgress(j);
247   }
248
249   @Override
250   public void StartJob(AWsJob job)
251   {
252     Exception lex = null;
253     // boiler plate template
254     if (!(job instanceof MsaWSJob))
255     {
256       throw new Error(MessageManager.formatMessage(
257               "error.implementation_error_msawbjob_called", new String[]
258               { job.getClass().toString() }));
259     }
260     MsaWSJob j = (MsaWSJob) job;
261     if (j.isSubmitted())
262     {
263       if (Console.isDebugEnabled())
264       {
265         Console.debug(
266                 "Tried to submit an already submitted job " + j.getJobId());
267       }
268       return;
269     }
270     // end boilerplate
271
272     if (j.seqs == null || j.seqs.size() == 0)
273     {
274       // special case - selection consisted entirely of empty sequences...
275       j.setState(JobState.FINISHED);
276       j.setStatus(MessageManager.getString("label.empty_alignment_job"));
277     }
278     try
279     {
280       j.addInitialStatus(); // list the presets/parameters used for the job in
281                             // status
282       try
283       {
284         JobId jobHandle = server.align(j.seqs, j.getPreset(),
285                 j.getArguments());
286         if (jobHandle != null)
287         {
288           j.setJobHandle(jobHandle);
289         }
290
291       } catch (Throwable throwable)
292       {
293         if (!server.handleSubmitError(throwable, j, wsInfo))
294         {
295           if (throwable instanceof Exception)
296           {
297             throw ((Exception) throwable);
298           }
299           if (throwable instanceof Error)
300           {
301             throw ((Error) throwable);
302           }
303         }
304       }
305       ///// generic
306
307       if (j.getJobId() != null)
308       {
309         j.setSubmitted(true);
310         j.setSubjobComplete(false);
311         // System.out.println(WsURL + " Job Id '" + jobId + "'");
312         return;
313       }
314       else
315       {
316         throw new Exception(MessageManager.formatMessage(
317                 "exception.web_service_returned_null_try_later",
318                 new String[]
319                 { WsUrl }));
320       }
321     }
322     //// jabaws specific
323
324     //// generic
325     catch (Error e)
326     {
327       // For unexpected errors
328       System.err.println(WebServiceName
329               + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
330               + "When contacting Server:" + WsUrl + "\n");
331       e.printStackTrace(System.err);
332       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
333       wsInfo.setStatus(j.getJobnum(),
334               WebserviceInfo.STATE_STOPPED_SERVERERROR);
335     } catch (Exception e)
336     {
337       // For unexpected errors
338       System.err.println(WebServiceName
339               + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
340               + "When contacting Server:" + WsUrl + "\n");
341       e.printStackTrace(System.err);
342       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
343       wsInfo.setStatus(j.getJobnum(),
344               WebserviceInfo.STATE_STOPPED_SERVERERROR);
345     } finally
346     {
347       if (!j.isSubmitted())
348       {
349         // Boilerplate code here
350         // TODO: JBPNote catch timeout or other fault types explicitly
351
352         j.setAllowedServerExceptions(0);
353         wsInfo.appendProgressText(j.getJobnum(), MessageManager.getString(
354                 "info.failed_to_submit_sequences_for_alignment"));
355       }
356     }
357   }
358
359   @Override
360   public void parseResult()
361   {
362     long progbar = System.currentTimeMillis();
363     wsInfo.setProgressBar(
364             MessageManager.getString("status.collecting_job_results"),
365             progbar);
366     int results = 0; // number of result sets received
367     JobStateSummary finalState = new JobStateSummary();
368     try
369     {
370       for (int j = 0; j < jobs.length; j++)
371       {
372         MsaWSJob msjob = ((MsaWSJob) jobs[j]);
373         if (jobs[j].isFinished() && msjob.alignment == null)
374         {
375           int nunchanged = 3, nexcept = 3;
376           boolean jpchanged = false, jpex = false;
377           do
378           {
379             try
380             {
381               jpchanged = server.updateJobProgress(msjob);
382               jpex = false;
383               if (jpchanged)
384               {
385                 nexcept = 3;
386               }
387             } catch (Exception e)
388             {
389
390               Console.warn(
391                       "Exception when retrieving remaining Job progress data for job "
392                               + msjob.getJobId() + " on server " + WsUrl);
393               e.printStackTrace();
394               nexcept--;
395               nunchanged = 3;
396               // set flag remember that we've had an exception.
397               jpex = true;
398               jpchanged = false;
399             }
400             if (!jpchanged)
401             {
402               try
403               {
404                 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
405                                                   // experienced an exception.
406               } catch (Exception ex)
407               {
408               }
409               ;
410               nunchanged--;
411             }
412           } while (nunchanged > 0 && nexcept > 0);
413
414           if (Console.isDebugEnabled())
415           {
416             System.out.println("Job Execution file for job: "
417                     + msjob.getJobId() + " on server " + WsUrl);
418             System.out.println(msjob.getStatus());
419             System.out.println("*** End of status");
420
421           }
422           ///// jabaws specific(ish) Get Result from Server when available
423           try
424           {
425             msjob.alignment = server.getAlignmentFor(msjob.getJobHandle());
426           } catch (Exception e)
427           {
428             if (!server.handleCollectionException(e, msjob, wsInfo))
429             {
430               Console.error("Couldn't get Alignment for job.", e);
431               // TODO: Increment count and retry ?
432               msjob.setState(JobState.SERVERERROR);
433             }
434           }
435         }
436         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
437         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
438                 && jobs[j].hasResults())
439         {
440           results++;
441         }
442       }
443     } catch (Exception ex)
444     {
445
446       Console.error(
447               "Unexpected exception when processing results for " + alTitle,
448               ex);
449       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
450     }
451     if (results > 0)
452     {
453       wsInfo.showResultsNewFrame
454               .addActionListener(new java.awt.event.ActionListener()
455               {
456                 @Override
457                 public void actionPerformed(java.awt.event.ActionEvent evt)
458                 {
459                   displayResults(true);
460                 }
461               });
462       wsInfo.mergeResults
463               .addActionListener(new java.awt.event.ActionListener()
464               {
465                 @Override
466                 public void actionPerformed(java.awt.event.ActionEvent evt)
467                 {
468                   displayResults(false);
469                 }
470               });
471       wsInfo.setResultsReady();
472     }
473     else
474     {
475       wsInfo.setFinishedNoResults();
476     }
477     updateGlobalStatus(finalState);
478     wsInfo.setProgressBar(null, progbar);
479   }
480
481   /**
482    * Display alignment results in a new frame (or - not currently supported -
483    * added to an existing alignment).
484    * 
485    * @param newFrame
486    */
487   void displayResults(boolean newFrame)
488   {
489     // view input or result data for each block
490     List<AlignmentOrder> alorders = new ArrayList<>();
491     SequenceI[][] results = new SequenceI[jobs.length][];
492     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
493     String lastProgram = null;
494     MsaWSJob msjob;
495     for (int j = 0; j < jobs.length; j++)
496     {
497       if (jobs[j].hasResults())
498       {
499         msjob = (MsaWSJob) jobs[j];
500         Object[] res = msjob.getAlignment();
501         lastProgram = msjob.getAlignmentProgram();
502         alorders.add((AlignmentOrder) res[1]);
503         results[j] = (SequenceI[]) res[0];
504         orders[j] = (AlignmentOrder) res[1];
505
506         // SequenceI[] alignment = input.getUpdated
507       }
508       else
509       {
510         results[j] = null;
511       }
512     }
513     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
514     // trash references to original result data
515     for (int j = 0; j < jobs.length; j++)
516     {
517       results[j] = null;
518       orders[j] = null;
519     }
520     SequenceI[] alignment = (SequenceI[]) newview[0];
521     HiddenColumns hidden = (HiddenColumns) newview[1];
522     Alignment al = new Alignment(alignment);
523     // TODO: add 'provenance' property to alignment from the method notes
524     if (lastProgram != null)
525     {
526       al.setProperty("Alignment Program", lastProgram);
527     }
528     // accompanying each subjob
529     if (dataset != null)
530     {
531       al.setDataset(dataset);
532     }
533
534     propagateDatasetMappings(al);
535     // JBNote- TODO: warn user if a block is input rather than aligned data ?
536
537     if (newFrame)
538     {
539       displayInNewFrame(al, alorders, hidden);
540
541     }
542     else
543     {
544       // TODO 2.9.x feature
545       System.out.println("MERGE WITH OLD FRAME");
546       // TODO: modify alignment in original frame, replacing old for new
547       // alignment using the commands.EditCommand model to ensure the update can
548       // be undone
549     }
550   }
551
552   /**
553    * Display the alignment result in a new frame.
554    * 
555    * @param al
556    * @param alorders
557    * @param columnselection
558    */
559   protected void displayInNewFrame(AlignmentI al,
560           List<AlignmentOrder> alorders, HiddenColumns hidden)
561   {
562     AlignFrame af = new AlignFrame(al, hidden, AlignFrame.DEFAULT_WIDTH,
563             AlignFrame.DEFAULT_HEIGHT);
564
565     // initialise with same renderer settings as in parent alignframe.
566     af.getFeatureRenderer().transferSettings(this.featureSettings);
567
568     if (alorders.size() > 0)
569     {
570       addSortByMenuItems(af, alorders);
571     }
572
573     // TODO: refactor retrieve and show as new splitFrame as Desktop method
574
575     /*
576      * If alignment was requested from one half of a SplitFrame, show in a
577      * SplitFrame with the other pane similarly aligned.
578      */
579     AlignFrame requestedBy = getRequestingAlignFrame();
580     if (requestedBy != null && requestedBy.getSplitViewContainer() != null
581             && requestedBy.getSplitViewContainer()
582                     .getComplement(requestedBy) != null)
583     {
584       AlignmentI complement = requestedBy.getSplitViewContainer()
585               .getComplement(requestedBy);
586       String complementTitle = requestedBy.getSplitViewContainer()
587               .getComplementTitle(requestedBy);
588       // becomes null if the alignment window was closed before the alignment
589       // job finished.
590       AlignmentI copyComplement = new Alignment(complement);
591       // todo should this be done by copy constructor?
592       copyComplement.setGapCharacter(complement.getGapCharacter());
593       // share the same dataset (and the mappings it holds)
594       copyComplement.setDataset(complement.getDataset());
595       copyComplement.alignAs(al);
596       if (copyComplement.getHeight() > 0)
597       {
598         af.setTitle(alTitle);
599         AlignFrame af2 = new AlignFrame(copyComplement,
600                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
601         af2.setTitle(complementTitle);
602         String linkedTitle = MessageManager
603                 .getString("label.linked_view_title");
604         JInternalFrame splitFrame = new SplitFrame(
605                 al.isNucleotide() ? af : af2, al.isNucleotide() ? af2 : af);
606         Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
607         return;
608       }
609     }
610
611     /*
612      * Not from SplitFrame, or failed to created a complementary alignment
613      */
614     Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
615             AlignFrame.DEFAULT_HEIGHT);
616   }
617
618   /**
619    * Add sort order options to the AlignFrame menus.
620    * 
621    * @param af
622    * @param alorders
623    */
624   protected void addSortByMenuItems(AlignFrame af,
625           List<AlignmentOrder> alorders)
626   {
627     // update orders
628     if (alorders.size() == 1)
629     {
630       af.addSortByOrderMenuItem(WebServiceName + " Ordering",
631               alorders.get(0));
632     }
633     else
634     {
635       // construct a non-redundant ordering set
636       List<String> names = new ArrayList<>();
637       for (int i = 0, l = alorders.size(); i < l; i++)
638       {
639         String orderName = " Region " + i;
640         int j = i + 1;
641
642         while (j < l)
643         {
644           if (alorders.get(i).equals(alorders.get(j)))
645           {
646             alorders.remove(j);
647             l--;
648             orderName += "," + j;
649           }
650           else
651           {
652             j++;
653           }
654         }
655
656         if (i == 0 && j == 1)
657         {
658           names.add("");
659         }
660         else
661         {
662           names.add(orderName);
663         }
664       }
665       for (int i = 0, l = alorders.size(); i < l; i++)
666       {
667         af.addSortByOrderMenuItem(
668                 WebServiceName + (names.get(i)) + " Ordering",
669                 alorders.get(i));
670       }
671     }
672   }
673
674   @Override
675   public boolean canMergeResults()
676   {
677     return false;
678   }
679 }