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