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