9298fb5996d69e66e9c227949476d998cfbf34f6
[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         Console.error("failed to send the job to the alignment server", throwable);
294         if (!server.handleSubmitError(throwable, j, wsInfo))
295         {
296           if (throwable instanceof Exception)
297           {
298             throw ((Exception) throwable);
299           }
300           if (throwable instanceof Error)
301           {
302             throw ((Error) throwable);
303           }
304         }
305       }
306       ///// generic
307
308       if (j.getJobId() != null)
309       {
310         j.setSubmitted(true);
311         j.setSubjobComplete(false);
312         // System.out.println(WsURL + " Job Id '" + jobId + "'");
313         return;
314       }
315       else
316       {
317         throw new Exception(MessageManager.formatMessage(
318                 "exception.web_service_returned_null_try_later",
319                 new String[]
320                 { WsUrl }));
321       }
322     }
323     //// jabaws specific
324
325     //// generic
326     catch (Error e)
327     {
328       // For unexpected errors
329       System.err.println(WebServiceName
330               + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
331               + "When contacting Server:" + WsUrl + "\n");
332       e.printStackTrace(System.err);
333       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
334       wsInfo.setStatus(j.getJobnum(),
335               WebserviceInfo.STATE_STOPPED_SERVERERROR);
336     } catch (Exception e)
337     {
338       // For unexpected errors
339       System.err.println(WebServiceName
340               + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
341               + "When contacting Server:" + WsUrl + "\n");
342       e.printStackTrace(System.err);
343       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
344       wsInfo.setStatus(j.getJobnum(),
345               WebserviceInfo.STATE_STOPPED_SERVERERROR);
346     } finally
347     {
348       if (!j.isSubmitted())
349       {
350         // Boilerplate code here
351         // TODO: JBPNote catch timeout or other fault types explicitly
352
353         j.setAllowedServerExceptions(0);
354         wsInfo.appendProgressText(j.getJobnum(), MessageManager.getString(
355                 "info.failed_to_submit_sequences_for_alignment"));
356       }
357     }
358   }
359
360   @Override
361   public void parseResult()
362   {
363     long progbar = System.currentTimeMillis();
364     wsInfo.setProgressBar(
365             MessageManager.getString("status.collecting_job_results"),
366             progbar);
367     int results = 0; // number of result sets received
368     JobStateSummary finalState = new JobStateSummary();
369     try
370     {
371       for (int j = 0; j < jobs.length; j++)
372       {
373         MsaWSJob msjob = ((MsaWSJob) jobs[j]);
374         if (jobs[j].isFinished() && msjob.alignment == null)
375         {
376           int nunchanged = 3, nexcept = 3;
377           boolean jpchanged = false, jpex = false;
378           do
379           {
380             try
381             {
382               jpchanged = server.updateJobProgress(msjob);
383               jpex = false;
384               if (jpchanged)
385               {
386                 nexcept = 3;
387               }
388             } catch (Exception e)
389             {
390
391               Console.warn(
392                       "Exception when retrieving remaining Job progress data for job "
393                               + msjob.getJobId() + " on server " + WsUrl);
394               e.printStackTrace();
395               nexcept--;
396               nunchanged = 3;
397               // set flag remember that we've had an exception.
398               jpex = true;
399               jpchanged = false;
400             }
401             if (!jpchanged)
402             {
403               try
404               {
405                 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
406                                                   // experienced an exception.
407               } catch (Exception ex)
408               {
409               }
410               ;
411               nunchanged--;
412             }
413           } while (nunchanged > 0 && nexcept > 0);
414
415           if (Console.isDebugEnabled())
416           {
417             System.out.println("Job Execution file for job: "
418                     + msjob.getJobId() + " on server " + WsUrl);
419             System.out.println(msjob.getStatus());
420             System.out.println("*** End of status");
421
422           }
423           ///// jabaws specific(ish) Get Result from Server when available
424           try
425           {
426             msjob.alignment = server.getAlignmentFor(msjob.getJobHandle());
427           } catch (Exception e)
428           {
429             if (!server.handleCollectionException(e, msjob, wsInfo))
430             {
431               Console.error("Couldn't get Alignment for job.", e);
432               // TODO: Increment count and retry ?
433               msjob.setState(JobState.SERVERERROR);
434             }
435           }
436         }
437         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
438         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
439                 && jobs[j].hasResults())
440         {
441           results++;
442         }
443       }
444     } catch (Exception ex)
445     {
446
447       Console.error(
448               "Unexpected exception when processing results for " + alTitle,
449               ex);
450       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
451     }
452     if (results > 0)
453     {
454       wsInfo.showResultsNewFrame
455               .addActionListener(new java.awt.event.ActionListener()
456               {
457                 @Override
458                 public void actionPerformed(java.awt.event.ActionEvent evt)
459                 {
460                   displayResults(true);
461                 }
462               });
463       wsInfo.mergeResults
464               .addActionListener(new java.awt.event.ActionListener()
465               {
466                 @Override
467                 public void actionPerformed(java.awt.event.ActionEvent evt)
468                 {
469                   displayResults(false);
470                 }
471               });
472       wsInfo.setResultsReady();
473     }
474     else
475     {
476       wsInfo.setFinishedNoResults();
477     }
478     updateGlobalStatus(finalState);
479     wsInfo.setProgressBar(null, progbar);
480   }
481
482   /**
483    * Display alignment results in a new frame (or - not currently supported -
484    * added to an existing alignment).
485    * 
486    * @param newFrame
487    */
488   void displayResults(boolean newFrame)
489   {
490     // view input or result data for each block
491     List<AlignmentOrder> alorders = new ArrayList<>();
492     SequenceI[][] results = new SequenceI[jobs.length][];
493     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
494     String lastProgram = null;
495     MsaWSJob msjob;
496     for (int j = 0; j < jobs.length; j++)
497     {
498       if (jobs[j].hasResults())
499       {
500         msjob = (MsaWSJob) jobs[j];
501         Object[] res = msjob.getAlignment();
502         lastProgram = msjob.getAlignmentProgram();
503         alorders.add((AlignmentOrder) res[1]);
504         results[j] = (SequenceI[]) res[0];
505         orders[j] = (AlignmentOrder) res[1];
506
507         // SequenceI[] alignment = input.getUpdated
508       }
509       else
510       {
511         results[j] = null;
512       }
513     }
514     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
515     // trash references to original result data
516     for (int j = 0; j < jobs.length; j++)
517     {
518       results[j] = null;
519       orders[j] = null;
520     }
521     SequenceI[] alignment = (SequenceI[]) newview[0];
522     HiddenColumns hidden = (HiddenColumns) newview[1];
523     Alignment al = new Alignment(alignment);
524     // TODO: add 'provenance' property to alignment from the method notes
525     if (lastProgram != null)
526     {
527       al.setProperty("Alignment Program", lastProgram);
528     }
529     // accompanying each subjob
530     if (dataset != null)
531     {
532       al.setDataset(dataset);
533     }
534
535     propagateDatasetMappings(al);
536     // JBNote- TODO: warn user if a block is input rather than aligned data ?
537
538     if (newFrame)
539     {
540       displayInNewFrame(al, alorders, hidden);
541
542     }
543     else
544     {
545       // TODO 2.9.x feature
546       System.out.println("MERGE WITH OLD FRAME");
547       // TODO: modify alignment in original frame, replacing old for new
548       // alignment using the commands.EditCommand model to ensure the update can
549       // be undone
550     }
551   }
552
553   /**
554    * Display the alignment result in a new frame.
555    * 
556    * @param al
557    * @param alorders
558    * @param columnselection
559    */
560   protected void displayInNewFrame(AlignmentI al,
561           List<AlignmentOrder> alorders, HiddenColumns hidden)
562   {
563     AlignFrame af = new AlignFrame(al, hidden, AlignFrame.DEFAULT_WIDTH,
564             AlignFrame.DEFAULT_HEIGHT);
565
566     // initialise with same renderer settings as in parent alignframe.
567     af.getFeatureRenderer().transferSettings(this.featureSettings);
568
569     if (alorders.size() > 0)
570     {
571       addSortByMenuItems(af, alorders);
572     }
573
574     // TODO: refactor retrieve and show as new splitFrame as Desktop method
575
576     /*
577      * If alignment was requested from one half of a SplitFrame, show in a
578      * SplitFrame with the other pane similarly aligned.
579      */
580     AlignFrame requestedBy = getRequestingAlignFrame();
581     if (requestedBy != null && requestedBy.getSplitViewContainer() != null
582             && requestedBy.getSplitViewContainer()
583                     .getComplement(requestedBy) != null)
584     {
585       AlignmentI complement = requestedBy.getSplitViewContainer()
586               .getComplement(requestedBy);
587       String complementTitle = requestedBy.getSplitViewContainer()
588               .getComplementTitle(requestedBy);
589       // becomes null if the alignment window was closed before the alignment
590       // job finished.
591       AlignmentI copyComplement = new Alignment(complement);
592       // todo should this be done by copy constructor?
593       copyComplement.setGapCharacter(complement.getGapCharacter());
594       // share the same dataset (and the mappings it holds)
595       copyComplement.setDataset(complement.getDataset());
596       copyComplement.alignAs(al);
597       if (copyComplement.getHeight() > 0)
598       {
599         af.setTitle(alTitle);
600         AlignFrame af2 = new AlignFrame(copyComplement,
601                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
602         af2.setTitle(complementTitle);
603         String linkedTitle = MessageManager
604                 .getString("label.linked_view_title");
605         JInternalFrame splitFrame = new SplitFrame(
606                 al.isNucleotide() ? af : af2, al.isNucleotide() ? af2 : af);
607         Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
608         return;
609       }
610     }
611
612     /*
613      * Not from SplitFrame, or failed to created a complementary alignment
614      */
615     Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
616             AlignFrame.DEFAULT_HEIGHT);
617   }
618
619   /**
620    * Add sort order options to the AlignFrame menus.
621    * 
622    * @param af
623    * @param alorders
624    */
625   protected void addSortByMenuItems(AlignFrame af,
626           List<AlignmentOrder> alorders)
627   {
628     // update orders
629     if (alorders.size() == 1)
630     {
631       af.addSortByOrderMenuItem(WebServiceName + " Ordering",
632               alorders.get(0));
633     }
634     else
635     {
636       // construct a non-redundant ordering set
637       List<String> names = new ArrayList<>();
638       for (int i = 0, l = alorders.size(); i < l; i++)
639       {
640         String orderName = " Region " + i;
641         int j = i + 1;
642
643         while (j < l)
644         {
645           if (alorders.get(i).equals(alorders.get(j)))
646           {
647             alorders.remove(j);
648             l--;
649             orderName += "," + j;
650           }
651           else
652           {
653             j++;
654           }
655         }
656
657         if (i == 0 && j == 1)
658         {
659           names.add("");
660         }
661         else
662         {
663           names.add(orderName);
664         }
665       }
666       for (int i = 0, l = alorders.size(); i < l; i++)
667       {
668         af.addSortByOrderMenuItem(
669                 WebServiceName + (names.get(i)) + " Ordering",
670                 alorders.get(i));
671       }
672     }
673   }
674
675   @Override
676   public boolean canMergeResults()
677   {
678     return false;
679   }
680 }