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