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