JAL-2089 patch broken merge to master for Release 2.10.0b1
[jalview.git] / src / jalview / ws / jws1 / 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.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.AlignmentOrder;
28 import jalview.datamodel.AlignmentView;
29 import jalview.datamodel.ColumnSelection;
30 import jalview.datamodel.SequenceI;
31 import jalview.gui.AlignFrame;
32 import jalview.gui.Desktop;
33 import jalview.gui.WebserviceInfo;
34 import jalview.util.MessageManager;
35 import jalview.ws.AWsJob;
36 import jalview.ws.JobStateSummary;
37 import jalview.ws.WSClientI;
38
39 import java.util.Hashtable;
40 import java.util.Vector;
41
42 import vamsas.objects.simple.MsaResult;
43
44 class MsaWSThread extends JWS1Thread implements WSClientI
45 {
46   boolean submitGaps = false; // pass sequences including gaps to alignment
47
48   // service
49
50   boolean preserveOrder = true; // and always store and recover sequence
51
52   // order
53
54   class MsaWSJob extends WSJob
55   {
56     // hold special input for this
57     vamsas.objects.simple.SequenceSet seqs = new vamsas.objects.simple.SequenceSet();
58
59     /**
60      * MsaWSJob
61      * 
62      * @param jobNum
63      *          int
64      * @param jobId
65      *          String
66      */
67     public MsaWSJob(int jobNum, SequenceI[] inSeqs)
68     {
69       this.jobnum = jobNum;
70       if (!prepareInput(inSeqs, 2))
71       {
72         submitted = true;
73         subjobComplete = true;
74         result = new MsaResult();
75         result.setFinished(true);
76         result.setStatus(MessageManager.getString("label.job_never_ran"));
77       }
78
79     }
80
81     Hashtable SeqNames = new Hashtable();
82
83     Vector emptySeqs = new Vector();
84
85     /**
86      * prepare input sequences for MsaWS service
87      * 
88      * @param seqs
89      *          jalview sequences to be prepared
90      * @param minlen
91      *          minimum number of residues required for this MsaWS service
92      * @return true if seqs contains sequences to be submitted to service.
93      */
94     private boolean prepareInput(SequenceI[] seqs, int minlen)
95     {
96       int nseqs = 0;
97       if (minlen < 0)
98       {
99         throw new Error(
100                 MessageManager
101                         .getString("error.implementation_error_minlen_must_be_greater_zero"));
102       }
103       for (int i = 0; i < seqs.length; i++)
104       {
105         if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
106         {
107           nseqs++;
108         }
109       }
110       boolean valid = nseqs > 1; // need at least two seqs
111       vamsas.objects.simple.Sequence[] seqarray = (valid) ? new vamsas.objects.simple.Sequence[nseqs]
112               : null;
113       for (int i = 0, n = 0; i < seqs.length; i++)
114       {
115
116         String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
117         // for
118         // any
119         // subjob
120         SeqNames.put(newname,
121                 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
122         if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
123         {
124           seqarray[n] = new vamsas.objects.simple.Sequence();
125           seqarray[n].setId(newname);
126           seqarray[n++].setSeq((submitGaps) ? seqs[i].getSequenceAsString()
127                   : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
128                           seqs[i].getSequenceAsString()));
129         }
130         else
131         {
132           String empty = null;
133           if (seqs[i].getEnd() >= seqs[i].getStart())
134           {
135             empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
136                     .extractGaps(jalview.util.Comparison.GapChars,
137                             seqs[i].getSequenceAsString());
138           }
139           emptySeqs.add(new String[] { newname, empty });
140         }
141       }
142       this.seqs = new vamsas.objects.simple.SequenceSet();
143       this.seqs.setSeqs(seqarray);
144       return valid;
145     }
146
147     /**
148      * 
149      * @return true if getAlignment will return a valid alignment result.
150      */
151     @Override
152     public boolean hasResults()
153     {
154       if (subjobComplete && result != null && result.isFinished()
155               && ((MsaResult) result).getMsa() != null
156               && ((MsaResult) result).getMsa().getSeqs() != null)
157       {
158         return true;
159       }
160       return false;
161     }
162
163     public Object[] getAlignment()
164     {
165
166       if (result != null && result.isFinished())
167       {
168         SequenceI[] alseqs = null;
169         char alseq_gapchar = '-';
170         int alseq_l = 0;
171         if (((MsaResult) result).getMsa() != null)
172         {
173           alseqs = getVamsasAlignment(((MsaResult) result).getMsa());
174           alseq_gapchar = ((MsaResult) result).getMsa().getGapchar()
175                   .charAt(0);
176           alseq_l = alseqs.length;
177         }
178         if (emptySeqs.size() > 0)
179         {
180           SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
181           // get width
182           int i, w = 0;
183           if (alseq_l > 0)
184           {
185             for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
186             {
187               if (w < alseqs[i].getLength())
188               {
189                 w = alseqs[i].getLength();
190               }
191               t_alseqs[i] = alseqs[i];
192               alseqs[i] = null;
193             }
194           }
195           // check that aligned width is at least as wide as emptySeqs width.
196           int ow = w, nw = w;
197           for (i = 0, w = emptySeqs.size(); i < w; i++)
198           {
199             String[] es = (String[]) emptySeqs.get(i);
200             if (es != null && es[1] != null)
201             {
202               int sw = es[1].length();
203               if (nw < sw)
204               {
205                 nw = sw;
206               }
207             }
208           }
209           // make a gapped string.
210           StringBuffer insbuff = new StringBuffer(w);
211           for (i = 0; i < nw; i++)
212           {
213             insbuff.append(alseq_gapchar);
214           }
215           if (ow < nw)
216           {
217             for (i = 0; i < alseq_l; i++)
218             {
219               int sw = t_alseqs[i].getLength();
220               if (nw > sw)
221               {
222                 // pad at end
223                 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
224                         + insbuff.substring(0, sw - nw));
225               }
226             }
227           }
228           for (i = 0, w = emptySeqs.size(); i < w; i++)
229           {
230             String[] es = (String[]) emptySeqs.get(i);
231             if (es[1] == null)
232             {
233               t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
234                       insbuff.toString(), 1, 0);
235             }
236             else
237             {
238               if (es[1].length() < nw)
239               {
240                 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
241                         es[0],
242                         es[1] + insbuff.substring(0, nw - es[1].length()),
243                         1, 1 + es[1].length());
244               }
245               else
246               {
247                 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
248                         es[0], es[1]);
249               }
250             }
251           }
252           alseqs = t_alseqs;
253         }
254         AlignmentOrder msaorder = new AlignmentOrder(alseqs);
255         // always recover the order - makes parseResult()'s life easier.
256         jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
257         // account for any missing sequences
258         jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
259         return new Object[] { alseqs, msaorder };
260       }
261       return null;
262     }
263
264     /**
265      * mark subjob as cancelled and set result object appropriatly
266      */
267     void cancel()
268     {
269       cancelled = true;
270       subjobComplete = true;
271       result = null;
272     }
273
274     /**
275      * 
276      * @return boolean true if job can be submitted.
277      */
278     @Override
279     public boolean hasValidInput()
280     {
281       if (seqs.getSeqs() != null)
282       {
283         return true;
284       }
285       return false;
286     }
287   }
288
289   String alTitle; // name which will be used to form new alignment window.
290
291   AlignmentI dataset; // dataset to which the new alignment will be
292
293   // associated.
294
295   ext.vamsas.MuscleWS server = null;
296
297   /**
298    * set basic options for this (group) of Msa jobs
299    * 
300    * @param subgaps
301    *          boolean
302    * @param presorder
303    *          boolean
304    */
305   MsaWSThread(ext.vamsas.MuscleWS server, String wsUrl,
306           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
307           AlignmentView alview, String wsname, boolean subgaps,
308           boolean presorder)
309   {
310     super(alFrame, wsinfo, alview, wsname, wsUrl);
311     this.server = server;
312     this.submitGaps = subgaps;
313     this.preserveOrder = presorder;
314   }
315
316   /**
317    * create one or more Msa jobs to align visible seuqences in _msa
318    * 
319    * @param title
320    *          String
321    * @param _msa
322    *          AlignmentView
323    * @param subgaps
324    *          boolean
325    * @param presorder
326    *          boolean
327    * @param seqset
328    *          Alignment
329    */
330   MsaWSThread(ext.vamsas.MuscleWS server, String wsUrl,
331           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
332           String wsname, String title, AlignmentView _msa, boolean subgaps,
333           boolean presorder, AlignmentI seqset)
334   {
335     this(server, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
336     OutputHeader = wsInfo.getProgressText();
337     alTitle = title;
338     dataset = seqset;
339
340     SequenceI[][] conmsa = _msa.getVisibleContigs('-');
341     if (conmsa != null)
342     {
343       int njobs = conmsa.length;
344       jobs = new MsaWSJob[njobs];
345       for (int j = 0; j < njobs; j++)
346       {
347         if (j != 0)
348         {
349           jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
350         }
351         else
352         {
353           jobs[j] = new MsaWSJob(0, conmsa[j]);
354         }
355         if (njobs > 0)
356         {
357           wsinfo.setProgressName("region " + jobs[j].getJobnum(),
358                   jobs[j].getJobnum());
359         }
360         wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
361       }
362     }
363   }
364
365   @Override
366   public boolean isCancellable()
367   {
368     return true;
369   }
370
371   @Override
372   public void cancelJob()
373   {
374     if (!jobComplete && jobs != null)
375     {
376       boolean cancelled = true;
377       for (int job = 0; job < jobs.length; job++)
378       {
379         if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
380         {
381           String cancelledMessage = "";
382           try
383           {
384             vamsas.objects.simple.WsJobId cancelledJob = server
385                     .cancel(jobs[job].getJobId());
386             if (cancelledJob.getStatus() == 2)
387             {
388               // CANCELLED_JOB
389               cancelledMessage = "Job cancelled.";
390               ((MsaWSJob) jobs[job]).cancel();
391               wsInfo.setStatus(jobs[job].getJobnum(),
392                       WebserviceInfo.STATE_CANCELLED_OK);
393             }
394             else if (cancelledJob.getStatus() == 3)
395             {
396               // VALID UNSTOPPABLE JOB
397               cancelledMessage += "Server cannot cancel this job. just close the window.\n";
398               cancelled = false;
399               // wsInfo.setStatus(jobs[job].jobnum,
400               // WebserviceInfo.STATE_RUNNING);
401             }
402
403             if (cancelledJob.getJobId() != null)
404             {
405               cancelledMessage += ("[" + cancelledJob.getJobId() + "]");
406             }
407
408             cancelledMessage += "\n";
409           } catch (Exception exc)
410           {
411             cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
412                     + exc + "\n");
413             Cache.log.warn(
414                     "Exception whilst cancelling " + jobs[job].getJobId(),
415                     exc);
416           }
417           wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
418                   + cancelledMessage + "\n");
419         }
420       }
421       if (cancelled)
422       {
423         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
424         jobComplete = true;
425       }
426       this.interrupt(); // kick thread to update job states.
427     }
428     else
429     {
430       if (!jobComplete)
431       {
432         wsInfo.setProgressText(OutputHeader
433                 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
434       }
435     }
436   }
437
438   @Override
439   public void pollJob(AWsJob job) throws Exception
440   {
441     ((MsaWSJob) job).result = server.getResult(((MsaWSJob) job).getJobId());
442   }
443
444   @Override
445   public void StartJob(AWsJob job)
446   {
447     if (!(job instanceof MsaWSJob))
448     {
449       throw new Error(MessageManager.formatMessage(
450               "error.implementation_error_msawbjob_called",
451               new String[] { job.getClass().toString() }));
452     }
453     MsaWSJob j = (MsaWSJob) job;
454     if (j.isSubmitted())
455     {
456       if (Cache.log.isDebugEnabled())
457       {
458         Cache.log.debug("Tried to submit an already submitted job "
459                 + j.getJobId());
460       }
461       return;
462     }
463     if (j.seqs.getSeqs() == null)
464     {
465       // special case - selection consisted entirely of empty sequences...
466       j.setSubmitted(true);
467       j.result = new MsaResult();
468       j.result.setFinished(true);
469       j.result.setStatus(MessageManager
470               .getString("label.empty_alignment_job"));
471       ((MsaResult) j.result).setMsa(null);
472     }
473     try
474     {
475       vamsas.objects.simple.WsJobId jobsubmit = server.align(j.seqs);
476
477       if ((jobsubmit != null) && (jobsubmit.getStatus() == 1))
478       {
479         j.setJobId(jobsubmit.getJobId());
480         j.setSubmitted(true);
481         j.setSubjobComplete(false);
482         // System.out.println(WsURL + " Job Id '" + jobId + "'");
483       }
484       else
485       {
486         if (jobsubmit == null)
487         {
488           throw new Exception(MessageManager.formatMessage(
489                   "exception.web_service_returned_null_try_later",
490                   new String[] { WsUrl }));
491         }
492
493         throw new Exception(jobsubmit.getJobId());
494       }
495     } catch (Exception e)
496     {
497       // TODO: JBPNote catch timeout or other fault types explicitly
498       // For unexpected errors
499       System.err
500               .println(WebServiceName
501                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
502                       + "When contacting Server:" + WsUrl + "\n"
503                       + e.toString() + "\n");
504       j.setAllowedServerExceptions(0);
505       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
506       wsInfo.setStatus(j.getJobnum(),
507               WebserviceInfo.STATE_STOPPED_SERVERERROR);
508       wsInfo.appendProgressText(j.getJobnum(), MessageManager
509               .getString("info.failed_to_submit_sequences_for_alignment"));
510
511       // e.printStackTrace(); // TODO: JBPNote DEBUG
512     }
513   }
514
515   private jalview.datamodel.Sequence[] getVamsasAlignment(
516           vamsas.objects.simple.Alignment valign)
517   {
518     // TODO: refactor to helper class for vamsas.objects.simple objects
519     vamsas.objects.simple.Sequence[] seqs = valign.getSeqs().getSeqs();
520     jalview.datamodel.Sequence[] msa = new jalview.datamodel.Sequence[seqs.length];
521
522     for (int i = 0, j = seqs.length; i < j; i++)
523     {
524       msa[i] = new jalview.datamodel.Sequence(seqs[i].getId(),
525               seqs[i].getSeq());
526     }
527
528     return msa;
529   }
530
531   @Override
532   public void parseResult()
533   {
534     int results = 0; // number of result sets received
535     JobStateSummary finalState = new JobStateSummary();
536     try
537     {
538       for (int j = 0; j < jobs.length; j++)
539       {
540         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
541         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
542                 && jobs[j].hasResults())
543         {
544           results++;
545           // if (Cache.log.isDebugEnabled())
546           // {
547           // System.out.println("Job lob for job "+jobs[j].getJobId()+":"+jobs[j].getJobnum());
548           // System.out.println(jobs[j].getStatus());
549           // }
550
551           vamsas.objects.simple.Alignment valign = ((MsaResult) ((MsaWSJob) jobs[j]).result)
552                   .getMsa();
553           if (valign != null)
554           {
555             wsInfo.appendProgressText(jobs[j].getJobnum(), MessageManager
556                     .getString("info.alignment_object_method_notes"));
557             String[] lines = valign.getMethod();
558             for (int line = 0; line < lines.length; line++)
559             {
560               wsInfo.appendProgressText(jobs[j].getJobnum(), lines[line]
561                       + "\n");
562             }
563             // JBPNote The returned files from a webservice could be
564             // hidden behind icons in the monitor window that,
565             // when clicked, pop up their corresponding data
566
567           }
568         }
569       }
570     } catch (Exception ex)
571     {
572
573       Cache.log.error("Unexpected exception when processing results for "
574               + alTitle, ex);
575       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
576     }
577     if (results > 0)
578     {
579       wsInfo.showResultsNewFrame
580               .addActionListener(new java.awt.event.ActionListener()
581               {
582                 @Override
583                 public void actionPerformed(java.awt.event.ActionEvent evt)
584                 {
585                   displayResults(true);
586                 }
587               });
588       wsInfo.mergeResults
589               .addActionListener(new java.awt.event.ActionListener()
590               {
591                 @Override
592                 public void actionPerformed(java.awt.event.ActionEvent evt)
593                 {
594                   displayResults(false);
595                 }
596               });
597       wsInfo.setResultsReady();
598     }
599     else
600     {
601       wsInfo.setFinishedNoResults();
602     }
603   }
604
605   void displayResults(boolean newFrame)
606   {
607     // view input or result data for each block
608     Vector alorders = new Vector();
609     SequenceI[][] results = new SequenceI[jobs.length][];
610     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
611     for (int j = 0; j < jobs.length; j++)
612     {
613       if (jobs[j].hasResults())
614       {
615         Object[] res = ((MsaWSJob) jobs[j]).getAlignment();
616         alorders.add(res[1]);
617         results[j] = (SequenceI[]) res[0];
618         orders[j] = (AlignmentOrder) res[1];
619
620         // SequenceI[] alignment = input.getUpdated
621       }
622       else
623       {
624         results[j] = null;
625       }
626     }
627     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
628     // trash references to original result data
629     for (int j = 0; j < jobs.length; j++)
630     {
631       results[j] = null;
632       orders[j] = null;
633     }
634     SequenceI[] alignment = (SequenceI[]) newview[0];
635     ColumnSelection columnselection = (ColumnSelection) newview[1];
636     Alignment al = new Alignment(alignment);
637     // TODO: add 'provenance' property to alignment from the method notes
638     // accompanying each subjob
639     if (dataset != null)
640     {
641       al.setDataset(dataset);
642     }
643
644     propagateDatasetMappings(al);
645     // JBNote- TODO: warn user if a block is input rather than aligned data ?
646
647     if (newFrame)
648     {
649       AlignFrame af = new AlignFrame(al, columnselection,
650               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
651
652       // initialise with same renderer settings as in parent alignframe.
653       af.getFeatureRenderer().transferSettings(this.featureSettings);
654       // update orders
655       if (alorders.size() > 0)
656       {
657         if (alorders.size() == 1)
658         {
659           af.addSortByOrderMenuItem(WebServiceName + " Ordering",
660                   (AlignmentOrder) alorders.get(0));
661         }
662         else
663         {
664           // construct a non-redundant ordering set
665           Vector names = new Vector();
666           for (int i = 0, l = alorders.size(); i < l; i++)
667           {
668             String orderName = new String(" Region " + i);
669             int j = i + 1;
670
671             while (j < l)
672             {
673               if (((AlignmentOrder) alorders.get(i)).equals((alorders
674                       .get(j))))
675               {
676                 alorders.remove(j);
677                 l--;
678                 orderName += "," + j;
679               }
680               else
681               {
682                 j++;
683               }
684             }
685
686             if (i == 0 && j == 1)
687             {
688               names.add(new String(""));
689             }
690             else
691             {
692               names.add(orderName);
693             }
694           }
695           for (int i = 0, l = alorders.size(); i < l; i++)
696           {
697             af.addSortByOrderMenuItem(
698                     WebServiceName + ((String) names.get(i)) + " Ordering",
699                     (AlignmentOrder) alorders.get(i));
700           }
701         }
702       }
703
704       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
705               AlignFrame.DEFAULT_HEIGHT);
706
707     }
708     else
709     {
710       System.out.println("MERGE WITH OLD FRAME");
711       // TODO: modify alignment in original frame, replacing old for new
712       // alignment using the commands.EditCommand model to ensure the update can
713       // be undone
714     }
715   }
716
717   @Override
718   public boolean canMergeResults()
719   {
720     return false;
721   }
722 }