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