JAL-653 typed map for feature colours in method signature
[jalview.git] / src / jalview / ws / jws1 / SeqSearchWSThread.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.AlignmentView;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.AlignFrame;
29 import jalview.gui.Desktop;
30 import jalview.gui.WebserviceInfo;
31 import jalview.io.NewickFile;
32 import jalview.util.MessageManager;
33 import jalview.ws.AWsJob;
34 import jalview.ws.JobStateSummary;
35 import jalview.ws.WSClientI;
36
37 import java.util.HashMap;
38 import java.util.Hashtable;
39 import java.util.Map;
40 import java.util.Vector;
41
42 import vamsas.objects.simple.MsaResult;
43 import vamsas.objects.simple.SeqSearchResult;
44
45 class SeqSearchWSThread extends JWS1Thread implements WSClientI
46 {
47   String dbs = null;
48
49   boolean profile = false;
50
51   class SeqSearchWSJob extends WSJob
52   {
53     // hold special input for this
54     vamsas.objects.simple.SequenceSet seqs = new vamsas.objects.simple.SequenceSet();
55
56     /**
57      * MsaWSJob
58      * 
59      * @param jobNum
60      *          int
61      * @param jobId
62      *          String
63      */
64     public SeqSearchWSJob(int jobNum, SequenceI[] inSeqs)
65     {
66       this.jobnum = jobNum;
67       if (!prepareInput(inSeqs, 2))
68       {
69         submitted = true;
70         subjobComplete = true;
71         result = new MsaResult();
72         result.setFinished(true);
73         result.setStatus(MessageManager.getString("label.job_never_ran"));
74       }
75
76     }
77
78     Hashtable SeqNames = new Hashtable();
79
80     Vector emptySeqs = new Vector();
81
82     /**
83      * prepare input sequences for 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(
97                 MessageManager
98                         .getString("error.implementation_error_minlen_must_be_greater_zero"));
99       }
100       for (int i = 0; i < seqs.length; i++)
101       {
102         if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
103         {
104           nseqs++;
105         }
106       }
107       boolean valid = nseqs >= 1; // need at least one sequence for valid input
108       // TODO: generalise
109       vamsas.objects.simple.Sequence[] seqarray = (valid) ? new vamsas.objects.simple.Sequence[nseqs]
110               : null;
111       boolean submitGaps = (nseqs == 1) ? false : true; // profile is submitted
112       // with gaps
113       for (int i = 0, n = 0; i < seqs.length; i++)
114       {
115
116         String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
117         // for
118         // any
119         // subjob
120         SeqNames.put(newname,
121                 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
122         if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
123         {
124           seqarray[n] = new vamsas.objects.simple.Sequence();
125           seqarray[n].setId(newname);
126           seqarray[n++].setSeq((submitGaps) ? seqs[i].getSequenceAsString()
127                   : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
128                           seqs[i].getSequenceAsString()));
129         }
130         else
131         {
132           String empty = null;
133           if (seqs[i].getEnd() >= seqs[i].getStart())
134           {
135             empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
136                     .extractGaps(jalview.util.Comparison.GapChars,
137                             seqs[i].getSequenceAsString());
138           }
139           emptySeqs.add(new String[] { newname, empty });
140         }
141       }
142       if (submitGaps)
143       {
144         // almost certainly have to remove gapped columns here
145       }
146       this.seqs = new vamsas.objects.simple.SequenceSet();
147       this.seqs.setSeqs(seqarray);
148       return valid;
149     }
150
151     /**
152      * 
153      * @return true if getAlignment will return a valid alignment result.
154      */
155     @Override
156     public boolean hasResults()
157     {
158       if (subjobComplete
159               && result != null
160               && result.isFinished()
161               && ((SeqSearchResult) result).getAlignment() != null
162               && ((SeqSearchResult) result).getAlignment().getSeqs() != null)
163       {
164         return true;
165       }
166       return false;
167     }
168
169     /**
170      * return sequence search results for display
171      * 
172      * @return null or { Alignment(+features and annotation), NewickFile)}
173      */
174     public Object[] getAlignment(Alignment dataset,
175             Map<String, Object> featureColours)
176     {
177
178       if (result != null && result.isFinished())
179       {
180         SequenceI[] alseqs = null;
181         // char alseq_gapchar = '-';
182         // int alseq_l = 0;
183         if (((SeqSearchResult) result).getAlignment() != null)
184         {
185           alseqs = getVamsasAlignment(((SeqSearchResult) result)
186                   .getAlignment());
187           // alseq_gapchar = ( (SeqSearchResult)
188           // result).getAlignment().getGapchar().charAt(0);
189           // alseq_l = alseqs.length;
190         }
191         /**
192          * what has to be done. 1 - annotate returned alignment with annotation
193          * file and sequence features file, and associate any tree-nodes. 2.
194          * connect alignment back to any associated dataset: 2.a. deuniquify
195          * recovers sequence information - but additionally, relocations must be
196          * made from the returned aligned sequence back to the dataset.
197          */
198         // construct annotated alignment as it would be done by the jalview
199         // applet
200         jalview.datamodel.Alignment al = new Alignment(alseqs);
201         // al.setDataset(dataset);
202         // make dataset
203         String inFile = null;
204         try
205         {
206           inFile = ((SeqSearchResult) result).getAnnotation();
207           if (inFile != null && inFile.length() > 0)
208           {
209             new jalview.io.AnnotationFile().readAnnotationFile(al, inFile,
210                     jalview.io.AppletFormatAdapter.PASTE);
211           }
212         } catch (Exception e)
213         {
214           System.err
215                   .println("Failed to parse the annotation file associated with the alignment.");
216           System.err.println(">>>EOF" + inFile + "\n<<<EOF\n");
217           e.printStackTrace(System.err);
218         }
219
220         try
221         {
222           inFile = ((SeqSearchResult) result).getFeatures();
223           if (inFile != null && inFile.length() > 0)
224           {
225             jalview.io.FeaturesFile ff = new jalview.io.FeaturesFile(
226                     inFile, jalview.io.AppletFormatAdapter.PASTE);
227             ff.parse(al, featureColours, false);
228           }
229         } catch (Exception e)
230         {
231           System.err
232                   .println("Failed to parse the Features file associated with the alignment.");
233           System.err.println(">>>EOF" + inFile + "\n<<<EOF\n");
234           e.printStackTrace(System.err);
235         }
236         jalview.io.NewickFile nf = null;
237         try
238         {
239           inFile = ((SeqSearchResult) result).getNewickTree();
240           if (inFile != null && inFile.length() > 0)
241           {
242             nf = new jalview.io.NewickFile(inFile,
243                     jalview.io.AppletFormatAdapter.PASTE);
244             if (!nf.isValid())
245             {
246               nf.close();
247               nf = null;
248             }
249           }
250         } catch (Exception e)
251         {
252           System.err
253                   .println("Failed to parse the treeFile associated with the alignment.");
254           System.err.println(">>>EOF" + inFile + "\n<<<EOF\n");
255           e.printStackTrace(System.err);
256         }
257
258         /*
259          * TODO: housekeeping w.r.t. recovery of dataset and annotation
260          * references for input sequences, and then dataset sequence creation
261          * for new sequences retrieved from service // finally, attempt to
262          * de-uniquify to recover input sequence identity, and try to map back
263          * onto dataset Note: this
264          * jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs, true); will
265          * NOT WORK - the returned alignment may contain multiple versions of
266          * the input sequence, each being a subsequence of the original.
267          * deuniquify also removes existing annotation and features added in the
268          * previous step... al.setDataset(dataset); // add in new sequences
269          * retrieved from sequence search which are not already in dataset. //
270          * trigger a 'fetchDBids' to annotate sequences with database ids...
271          */
272
273         return new Object[] { al, nf };
274       }
275       return null;
276     }
277
278     /**
279      * mark subjob as cancelled and set result object appropriatly
280      */
281     void cancel()
282     {
283       cancelled = true;
284       subjobComplete = true;
285       result = null;
286     }
287
288     /**
289      * 
290      * @return boolean true if job can be submitted.
291      */
292     @Override
293     public boolean hasValidInput()
294     {
295       if (seqs.getSeqs() != null)
296       {
297         return true;
298       }
299       return false;
300     }
301   }
302
303   String alTitle; // name which will be used to form new alignment window.
304
305   Alignment dataset; // dataset to which the new alignment will be
306
307   // associated.
308
309   ext.vamsas.SeqSearchI server = null;
310
311   private String dbArg;
312
313   /**
314    * set basic options for this (group) of Msa jobs
315    * 
316    * @param subgaps
317    *          boolean
318    * @param presorder
319    *          boolean
320    */
321   SeqSearchWSThread(ext.vamsas.SeqSearchI server, String wsUrl,
322           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
323           AlignmentView alview, String wsname, String db)
324   {
325     super(alFrame, wsinfo, alview, wsname, wsUrl);
326     this.server = server;
327     this.dbArg = db;
328   }
329
330   /**
331    * create one or more Msa jobs to align visible seuqences in _msa
332    * 
333    * @param title
334    *          String
335    * @param _msa
336    *          AlignmentView
337    * @param subgaps
338    *          boolean
339    * @param presorder
340    *          boolean
341    * @param seqset
342    *          Alignment
343    */
344   SeqSearchWSThread(ext.vamsas.SeqSearchI server, String wsUrl,
345           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
346           String wsname, String title, AlignmentView _msa, String db,
347           Alignment seqset)
348   {
349     this(server, wsUrl, wsinfo, alFrame, _msa, wsname, db);
350     OutputHeader = wsInfo.getProgressText();
351     alTitle = title;
352     dataset = seqset;
353
354     SequenceI[][] conmsa = _msa.getVisibleContigs('-');
355     if (conmsa != null)
356     {
357       int njobs = conmsa.length;
358       jobs = new SeqSearchWSJob[njobs];
359       for (int j = 0; j < njobs; j++)
360       {
361         if (j != 0)
362         {
363           jobs[j] = new SeqSearchWSJob(wsinfo.addJobPane(), conmsa[j]);
364         }
365         else
366         {
367           jobs[j] = new SeqSearchWSJob(0, conmsa[j]);
368         }
369         if (njobs > 0)
370         {
371           wsinfo.setProgressName("region " + jobs[j].getJobnum(),
372                   jobs[j].getJobnum());
373         }
374         wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
375       }
376     }
377   }
378
379   @Override
380   public boolean isCancellable()
381   {
382     return true;
383   }
384
385   @Override
386   public void cancelJob()
387   {
388     if (!jobComplete && jobs != null)
389     {
390       boolean cancelled = true;
391       for (int job = 0; job < jobs.length; job++)
392       {
393         if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
394         {
395           String cancelledMessage = "";
396           try
397           {
398             vamsas.objects.simple.WsJobId cancelledJob = server
399                     .cancel(jobs[job].getJobId());
400             if (cancelledJob.getStatus() == 2)
401             {
402               // CANCELLED_JOB
403               cancelledMessage = "Job cancelled.";
404               ((SeqSearchWSJob) jobs[job]).cancel();
405               wsInfo.setStatus(jobs[job].getJobnum(),
406                       WebserviceInfo.STATE_CANCELLED_OK);
407             }
408             else if (cancelledJob.getStatus() == 3)
409             {
410               // VALID UNSTOPPABLE JOB
411               cancelledMessage += "Server cannot cancel this job. just close the window.\n";
412               cancelled = false;
413               // wsInfo.setStatus(jobs[job].jobnum,
414               // WebserviceInfo.STATE_RUNNING);
415             }
416
417             if (cancelledJob.getJobId() != null)
418             {
419               cancelledMessage += ("[" + cancelledJob.getJobId() + "]");
420             }
421
422             cancelledMessage += "\n";
423           } catch (Exception exc)
424           {
425             cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
426                     + exc + "\n");
427             Cache.log.warn(
428                     "Exception whilst cancelling " + jobs[job].getJobId(),
429                     exc);
430           }
431           wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
432                   + cancelledMessage + "\n");
433         }
434       }
435       if (cancelled)
436       {
437         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
438         jobComplete = true;
439       }
440       this.interrupt(); // kick thread to update job states.
441     }
442     else
443     {
444       if (!jobComplete)
445       {
446         wsInfo.setProgressText(OutputHeader
447                 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
448       }
449     }
450   }
451
452   @Override
453   public void pollJob(AWsJob job) throws Exception
454   {
455     ((SeqSearchWSJob) job).result = server.getResult(((SeqSearchWSJob) job)
456             .getJobId());
457   }
458
459   @Override
460   public void StartJob(AWsJob job)
461   {
462     if (!(job instanceof SeqSearchWSJob))
463     {
464       throw new Error(MessageManager.formatMessage(
465               "error.implementation_error_msawbjob_called",
466               new String[] { job.getClass().toString() }));
467     }
468     SeqSearchWSJob j = (SeqSearchWSJob) job;
469     if (j.isSubmitted())
470     {
471       if (Cache.log.isDebugEnabled())
472       {
473         Cache.log.debug("Tried to submit an already submitted job "
474                 + j.getJobId());
475       }
476       return;
477     }
478     if (j.seqs.getSeqs() == null)
479     {
480       // special case - selection consisted entirely of empty sequences...
481       j.setSubmitted(true);
482       j.result = new MsaResult();
483       j.result.setFinished(true);
484       j.result.setStatus(MessageManager
485               .getString("label.empty_alignment_job"));
486       ((MsaResult) j.result).setMsa(null);
487     }
488     try
489     {
490       vamsas.objects.simple.WsJobId jobsubmit = server.search(
491               j.seqs.getSeqs()[0], dbArg);
492
493       if ((jobsubmit != null) && (jobsubmit.getStatus() == 1))
494       {
495         j.setJobId(jobsubmit.getJobId());
496         j.setSubmitted(true);
497         j.setSubjobComplete(false);
498         // System.out.println(WsURL + " Job Id '" + jobId + "'");
499       }
500       else
501       {
502         if (jobsubmit == null)
503         {
504           throw new Exception(MessageManager.formatMessage(
505                   "exception.web_service_returned_null_try_later",
506                   new String[] { WsUrl }));
507         }
508
509         throw new Exception(jobsubmit.getJobId());
510       }
511     } catch (Exception e)
512     {
513       // TODO: JBPNote catch timeout or other fault types explicitly
514       // For unexpected errors
515       System.err
516               .println(WebServiceName
517                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
518                       + "When contacting Server:" + WsUrl + "\n"
519                       + e.toString() + "\n");
520       j.setAllowedServerExceptions(0);
521       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
522       wsInfo.setStatus(j.getJobnum(),
523               WebserviceInfo.STATE_STOPPED_SERVERERROR);
524       wsInfo.appendProgressText(j.getJobnum(), MessageManager
525               .getString("info.failed_to_submit_sequences_for_alignment"));
526
527       // e.printStackTrace(); // TODO: JBPNote DEBUG
528     }
529   }
530
531   private jalview.datamodel.Sequence[] getVamsasAlignment(
532           vamsas.objects.simple.Alignment valign)
533   {
534     vamsas.objects.simple.Sequence[] seqs = valign.getSeqs().getSeqs();
535     jalview.datamodel.Sequence[] msa = new jalview.datamodel.Sequence[seqs.length];
536
537     for (int i = 0, j = seqs.length; i < j; i++)
538     {
539       msa[i] = new jalview.datamodel.Sequence(seqs[i].getId(),
540               seqs[i].getSeq());
541     }
542
543     return msa;
544   }
545
546   @Override
547   public void parseResult()
548   {
549     int results = 0; // number of result sets received
550     JobStateSummary finalState = new JobStateSummary();
551     try
552     {
553       for (int j = 0; j < jobs.length; j++)
554       {
555         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
556         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
557                 && jobs[j].hasResults())
558         {
559           results++;
560           vamsas.objects.simple.Alignment valign = ((SeqSearchResult) ((SeqSearchWSJob) jobs[j]).result)
561                   .getAlignment();
562           if (valign != null)
563           {
564             wsInfo.appendProgressText(jobs[j].getJobnum(), MessageManager
565                     .getString("info.alignment_object_method_notes"));
566             String[] lines = valign.getMethod();
567             for (int line = 0; line < lines.length; line++)
568             {
569               wsInfo.appendProgressText(jobs[j].getJobnum(), lines[line]
570                       + "\n");
571             }
572             // JBPNote The returned files from a webservice could be
573             // hidden behind icons in the monitor window that,
574             // when clicked, pop up their corresponding data
575           }
576         }
577       }
578     } catch (Exception ex)
579     {
580
581       Cache.log.error("Unexpected exception when processing results for "
582               + alTitle, ex);
583       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
584     }
585     if (results > 0)
586     {
587       wsInfo.showResultsNewFrame
588               .addActionListener(new java.awt.event.ActionListener()
589               {
590                 @Override
591                 public void actionPerformed(java.awt.event.ActionEvent evt)
592                 {
593                   displayResults(true);
594                 }
595               });
596       wsInfo.mergeResults
597               .addActionListener(new java.awt.event.ActionListener()
598               {
599                 @Override
600                 public void actionPerformed(java.awt.event.ActionEvent evt)
601                 {
602                   displayResults(false);
603                 }
604               });
605       wsInfo.setResultsReady();
606     }
607     else
608     {
609       wsInfo.setFinishedNoResults();
610     }
611   }
612
613   void displayResults(boolean newFrame)
614   {
615     if (!newFrame)
616     {
617       System.err.println("MERGE WITH OLD FRAME NOT IMPLEMENTED");
618       return;
619     }
620     // each subjob is an independent alignment for the moment
621     // Alignment al[] = new Alignment[jobs.length];
622     // NewickFile nf[] = new NewickFile[jobs.length];
623     for (int j = 0; j < jobs.length; j++)
624     {
625       Map<String, Object> featureColours = new HashMap<String, Object>();
626       Alignment al = null;
627       NewickFile nf = null;
628       if (jobs[j].hasResults())
629       {
630         Object[] res = ((SeqSearchWSJob) jobs[j]).getAlignment(dataset,
631                 featureColours);
632         if (res == null)
633         {
634           continue;
635         }
636         ;
637         al = (Alignment) res[0];
638         nf = (NewickFile) res[1];
639       }
640       else
641       {
642         al = null;
643         nf = null;
644         continue;
645       }
646       /*
647        * We can't map new alignment back with insertions from input's hidden
648        * regions until dataset mapping is sorted out... but basically it goes
649        * like this: 1. Merge each domain hit back onto the visible segments in
650        * the same way as a Jnet prediction is mapped back
651        * 
652        * Object[] newview = input.getUpdatedView(results, orders, getGapChar());
653        * // trash references to original result data for (int j = 0; j <
654        * jobs.length; j++) { results[j] = null; orders[j] = null; } SequenceI[]
655        * alignment = (SequenceI[]) newview[0]; ColumnSelection columnselection =
656        * (ColumnSelection) newview[1]; Alignment al = new Alignment(alignment);
657        * 
658        * if (dataset != null) { al.setDataset(dataset); }
659        * 
660        * propagateDatasetMappings(al); }
661        */
662
663       AlignFrame af = new AlignFrame(al,// columnselection,
664               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
665       if (nf != null)
666       {
667         af.ShowNewickTree(nf, MessageManager.formatMessage(
668                 "label.tree_from", new String[] { this.alTitle }));
669       }
670       // initialise with same renderer settings as in parent alignframe.
671       af.getFeatureRenderer().transferSettings(this.featureSettings);
672       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
673               AlignFrame.DEFAULT_HEIGHT);
674     }
675   }
676
677   @Override
678   public boolean canMergeResults()
679   {
680     return false;
681   }
682 }