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