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