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