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