JAL-1645 Version-Rel Version 2.9 Year-Rel 2015 Licensing glob
[jalview.git] / src / jalview / ws / jws1 / SeqSearchWSThread.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9)
3  * Copyright (C) 2015 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.Hashtable;
38 import java.util.Vector;
39
40 import vamsas.objects.simple.MsaResult;
41 import vamsas.objects.simple.SeqSearchResult;
42
43 class SeqSearchWSThread extends JWS1Thread implements WSClientI
44 {
45   String dbs = null;
46
47   boolean profile = false;
48
49   class SeqSearchWSJob extends WSJob
50   {
51     // hold special input for this
52     vamsas.objects.simple.SequenceSet seqs = new vamsas.objects.simple.SequenceSet();
53
54     /**
55      * MsaWSJob
56      * 
57      * @param jobNum
58      *          int
59      * @param jobId
60      *          String
61      */
62     public SeqSearchWSJob(int jobNum, SequenceI[] inSeqs)
63     {
64       this.jobnum = jobNum;
65       if (!prepareInput(inSeqs, 2))
66       {
67         submitted = true;
68         subjobComplete = true;
69         result = new MsaResult();
70         result.setFinished(true);
71         result.setStatus(MessageManager.getString("label.job_never_ran"));
72       }
73
74     }
75
76     Hashtable SeqNames = new Hashtable();
77
78     Vector emptySeqs = new Vector();
79
80     /**
81      * prepare input sequences for service
82      * 
83      * @param seqs
84      *          jalview sequences to be prepared
85      * @param minlen
86      *          minimum number of residues required for this MsaWS service
87      * @return true if seqs contains sequences to be submitted to service.
88      */
89     private boolean prepareInput(SequenceI[] seqs, int minlen)
90     {
91       int nseqs = 0;
92       if (minlen < 0)
93       {
94         throw new Error(
95                 MessageManager
96                         .getString("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) ? new vamsas.objects.simple.Sequence[nseqs]
108               : null;
109       boolean submitGaps = (nseqs == 1) ? false : true; // profile is submitted
110       // with gaps
111       for (int i = 0, n = 0; i < seqs.length; i++)
112       {
113
114         String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
115         // for
116         // any
117         // subjob
118         SeqNames.put(newname,
119                 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
120         if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
121         {
122           seqarray[n] = new vamsas.objects.simple.Sequence();
123           seqarray[n].setId(newname);
124           seqarray[n++].setSeq((submitGaps) ? seqs[i].getSequenceAsString()
125                   : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
126                           seqs[i].getSequenceAsString()));
127         }
128         else
129         {
130           String empty = null;
131           if (seqs[i].getEnd() >= seqs[i].getStart())
132           {
133             empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
134                     .extractGaps(jalview.util.Comparison.GapChars,
135                             seqs[i].getSequenceAsString());
136           }
137           emptySeqs.add(new String[] { newname, empty });
138         }
139       }
140       if (submitGaps)
141       {
142         // almost certainly have to remove gapped columns here
143       }
144       this.seqs = new vamsas.objects.simple.SequenceSet();
145       this.seqs.setSeqs(seqarray);
146       return valid;
147     }
148
149     /**
150      * 
151      * @return true if getAlignment will return a valid alignment result.
152      */
153     public boolean hasResults()
154     {
155       if (subjobComplete
156               && result != null
157               && result.isFinished()
158               && ((SeqSearchResult) result).getAlignment() != null
159               && ((SeqSearchResult) result).getAlignment().getSeqs() != null)
160       {
161         return true;
162       }
163       return false;
164     }
165
166     /**
167      * return sequence search results for display
168      * 
169      * @return null or { Alignment(+features and annotation), NewickFile)}
170      */
171     public Object[] getAlignment(Alignment dataset, Hashtable featureColours)
172     {
173
174       if (result != null && result.isFinished())
175       {
176         SequenceI[] alseqs = null;
177         // char alseq_gapchar = '-';
178         // int alseq_l = 0;
179         if (((SeqSearchResult) result).getAlignment() != null)
180         {
181           alseqs = getVamsasAlignment(((SeqSearchResult) result)
182                   .getAlignment());
183           // alseq_gapchar = ( (SeqSearchResult)
184           // result).getAlignment().getGapchar().charAt(0);
185           // alseq_l = alseqs.length;
186         }
187         /**
188          * what has to be done. 1 - annotate returned alignment with annotation
189          * file and sequence features file, and associate any tree-nodes. 2.
190          * connect alignment back to any associated dataset: 2.a. deuniquify
191          * recovers sequence information - but additionally, relocations must be
192          * made from the returned aligned sequence back to the dataset.
193          */
194         // construct annotated alignment as it would be done by the jalview
195         // applet
196         jalview.datamodel.Alignment al = new Alignment(alseqs);
197         // al.setDataset(dataset);
198         // make dataset
199         String inFile = null;
200         try
201         {
202           inFile = ((SeqSearchResult) result).getAnnotation();
203           if (inFile != null && inFile.length() > 0)
204           {
205             new jalview.io.AnnotationFile().readAnnotationFile(al, inFile,
206                     jalview.io.AppletFormatAdapter.PASTE);
207           }
208         } catch (Exception e)
209         {
210           System.err
211                   .println("Failed to parse the annotation file associated with the alignment.");
212           System.err.println(">>>EOF" + inFile + "\n<<<EOF\n");
213           e.printStackTrace(System.err);
214         }
215
216         try
217         {
218           inFile = ((SeqSearchResult) result).getFeatures();
219           if (inFile != null && inFile.length() > 0)
220           {
221             jalview.io.FeaturesFile ff = new jalview.io.FeaturesFile(
222                     inFile, jalview.io.AppletFormatAdapter.PASTE);
223             ff.parse(al, featureColours, false);
224           }
225         } catch (Exception e)
226         {
227           System.err
228                   .println("Failed to parse the Features file associated with the alignment.");
229           System.err.println(">>>EOF" + inFile + "\n<<<EOF\n");
230           e.printStackTrace(System.err);
231         }
232         jalview.io.NewickFile nf = null;
233         try
234         {
235           inFile = ((SeqSearchResult) result).getNewickTree();
236           if (inFile != null && inFile.length() > 0)
237           {
238             nf = new jalview.io.NewickFile(inFile,
239                     jalview.io.AppletFormatAdapter.PASTE);
240             if (!nf.isValid())
241             {
242               nf.close();
243               nf = null;
244             }
245           }
246         } catch (Exception e)
247         {
248           System.err
249                   .println("Failed to parse the treeFile associated with the alignment.");
250           System.err.println(">>>EOF" + inFile + "\n<<<EOF\n");
251           e.printStackTrace(System.err);
252         }
253
254         /*
255          * TODO: housekeeping w.r.t. recovery of dataset and annotation
256          * references for input sequences, and then dataset sequence creation
257          * for new sequences retrieved from service // finally, attempt to
258          * de-uniquify to recover input sequence identity, and try to map back
259          * onto dataset Note: this
260          * jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs, true); will
261          * NOT WORK - the returned alignment may contain multiple versions of
262          * the input sequence, each being a subsequence of the original.
263          * deuniquify also removes existing annotation and features added in the
264          * previous step... al.setDataset(dataset); // add in new sequences
265          * retrieved from sequence search which are not already in dataset. //
266          * trigger a 'fetchDBids' to annotate sequences with database ids...
267          */
268
269         return new Object[] { al, nf };
270       }
271       return null;
272     }
273
274     /**
275      * mark subjob as cancelled and set result object appropriatly
276      */
277     void cancel()
278     {
279       cancelled = true;
280       subjobComplete = true;
281       result = null;
282     }
283
284     /**
285      * 
286      * @return boolean true if job can be submitted.
287      */
288     public boolean hasValidInput()
289     {
290       if (seqs.getSeqs() != null)
291       {
292         return true;
293       }
294       return false;
295     }
296   }
297
298   String alTitle; // name which will be used to form new alignment window.
299
300   Alignment dataset; // dataset to which the new alignment will be
301
302   // associated.
303
304   ext.vamsas.SeqSearchI server = null;
305
306   private String dbArg;
307
308   /**
309    * set basic options for this (group) of Msa jobs
310    * 
311    * @param subgaps
312    *          boolean
313    * @param presorder
314    *          boolean
315    */
316   SeqSearchWSThread(ext.vamsas.SeqSearchI server, String wsUrl,
317           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
318           AlignmentView alview, String wsname, String db)
319   {
320     super(alFrame, wsinfo, alview, wsname, wsUrl);
321     this.server = server;
322     this.dbArg = db;
323   }
324
325   /**
326    * create one or more Msa jobs to align visible seuqences in _msa
327    * 
328    * @param title
329    *          String
330    * @param _msa
331    *          AlignmentView
332    * @param subgaps
333    *          boolean
334    * @param presorder
335    *          boolean
336    * @param seqset
337    *          Alignment
338    */
339   SeqSearchWSThread(ext.vamsas.SeqSearchI server, String wsUrl,
340           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
341           String wsname, String title, AlignmentView _msa, String db,
342           Alignment seqset)
343   {
344     this(server, wsUrl, wsinfo, alFrame, _msa, wsname, db);
345     OutputHeader = wsInfo.getProgressText();
346     alTitle = title;
347     dataset = seqset;
348
349     SequenceI[][] conmsa = _msa.getVisibleContigs('-');
350     if (conmsa != null)
351     {
352       int njobs = conmsa.length;
353       jobs = new SeqSearchWSJob[njobs];
354       for (int j = 0; j < njobs; j++)
355       {
356         if (j != 0)
357         {
358           jobs[j] = new SeqSearchWSJob(wsinfo.addJobPane(), conmsa[j]);
359         }
360         else
361         {
362           jobs[j] = new SeqSearchWSJob(0, conmsa[j]);
363         }
364         if (njobs > 0)
365         {
366           wsinfo.setProgressName("region " + jobs[j].getJobnum(),
367                   jobs[j].getJobnum());
368         }
369         wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
370       }
371     }
372   }
373
374   public boolean isCancellable()
375   {
376     return true;
377   }
378
379   public void cancelJob()
380   {
381     if (!jobComplete && jobs != null)
382     {
383       boolean cancelled = true;
384       for (int job = 0; job < jobs.length; job++)
385       {
386         if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
387         {
388           String cancelledMessage = "";
389           try
390           {
391             vamsas.objects.simple.WsJobId cancelledJob = server
392                     .cancel(jobs[job].getJobId());
393             if (cancelledJob.getStatus() == 2)
394             {
395               // CANCELLED_JOB
396               cancelledMessage = "Job cancelled.";
397               ((SeqSearchWSJob) jobs[job]).cancel();
398               wsInfo.setStatus(jobs[job].getJobnum(),
399                       WebserviceInfo.STATE_CANCELLED_OK);
400             }
401             else if (cancelledJob.getStatus() == 3)
402             {
403               // VALID UNSTOPPABLE JOB
404               cancelledMessage += "Server cannot cancel this job. just close the window.\n";
405               cancelled = false;
406               // wsInfo.setStatus(jobs[job].jobnum,
407               // WebserviceInfo.STATE_RUNNING);
408             }
409
410             if (cancelledJob.getJobId() != null)
411             {
412               cancelledMessage += ("[" + cancelledJob.getJobId() + "]");
413             }
414
415             cancelledMessage += "\n";
416           } catch (Exception exc)
417           {
418             cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
419                     + exc + "\n");
420             Cache.log.warn(
421                     "Exception whilst cancelling " + jobs[job].getJobId(),
422                     exc);
423           }
424           wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
425                   + cancelledMessage + "\n");
426         }
427       }
428       if (cancelled)
429       {
430         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
431         jobComplete = true;
432       }
433       this.interrupt(); // kick thread to update job states.
434     }
435     else
436     {
437       if (!jobComplete)
438       {
439         wsInfo.setProgressText(OutputHeader
440                 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
441       }
442     }
443   }
444
445   public void pollJob(AWsJob job) throws Exception
446   {
447     ((SeqSearchWSJob) job).result = server.getResult(((SeqSearchWSJob) job)
448             .getJobId());
449   }
450
451   public void StartJob(AWsJob job)
452   {
453     if (!(job instanceof SeqSearchWSJob))
454     {
455       throw new Error(MessageManager.formatMessage(
456               "error.implementation_error_msawbjob_called",
457               new String[] { job.getClass().toString() }));
458     }
459     SeqSearchWSJob j = (SeqSearchWSJob) job;
460     if (j.isSubmitted())
461     {
462       if (Cache.log.isDebugEnabled())
463       {
464         Cache.log.debug("Tried to submit an already submitted job "
465                 + j.getJobId());
466       }
467       return;
468     }
469     if (j.seqs.getSeqs() == null)
470     {
471       // special case - selection consisted entirely of empty sequences...
472       j.setSubmitted(true);
473       j.result = new MsaResult();
474       j.result.setFinished(true);
475       j.result.setStatus(MessageManager
476               .getString("label.empty_alignment_job"));
477       ((MsaResult) j.result).setMsa(null);
478     }
479     try
480     {
481       vamsas.objects.simple.WsJobId jobsubmit = server.search(
482               j.seqs.getSeqs()[0], dbArg);
483
484       if ((jobsubmit != null) && (jobsubmit.getStatus() == 1))
485       {
486         j.setJobId(jobsubmit.getJobId());
487         j.setSubmitted(true);
488         j.setSubjobComplete(false);
489         // System.out.println(WsURL + " Job Id '" + jobId + "'");
490       }
491       else
492       {
493         if (jobsubmit == null)
494         {
495           throw new Exception(MessageManager.formatMessage(
496                   "exception.web_service_returned_null_try_later",
497                   new String[] { WsUrl }));
498         }
499
500         throw new Exception(jobsubmit.getJobId());
501       }
502     } catch (Exception e)
503     {
504       // TODO: JBPNote catch timeout or other fault types explicitly
505       // For unexpected errors
506       System.err
507               .println(WebServiceName
508                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
509                       + "When contacting Server:" + WsUrl + "\n"
510                       + e.toString() + "\n");
511       j.setAllowedServerExceptions(0);
512       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
513       wsInfo.setStatus(j.getJobnum(),
514               WebserviceInfo.STATE_STOPPED_SERVERERROR);
515       wsInfo.appendProgressText(j.getJobnum(), MessageManager
516               .getString("info.failed_to_submit_sequences_for_alignment"));
517
518       // e.printStackTrace(); // TODO: JBPNote DEBUG
519     }
520   }
521
522   private jalview.datamodel.Sequence[] getVamsasAlignment(
523           vamsas.objects.simple.Alignment valign)
524   {
525     vamsas.objects.simple.Sequence[] seqs = valign.getSeqs().getSeqs();
526     jalview.datamodel.Sequence[] msa = new jalview.datamodel.Sequence[seqs.length];
527
528     for (int i = 0, j = seqs.length; i < j; i++)
529     {
530       msa[i] = new jalview.datamodel.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(), MessageManager
555                     .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(
656                 "label.tree_from", new String[] { this.alTitle }));
657       }
658       // initialise with same renderer settings as in parent alignframe.
659       af.getFeatureRenderer().transferSettings(this.featureSettings);
660       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
661               AlignFrame.DEFAULT_HEIGHT);
662     }
663   }
664
665   public boolean canMergeResults()
666   {
667     return false;
668   }
669 }