Merge branch 'develop' into bug/JAL-4059_update_swingJS_for_JalviewJS_2_11_2_and_2_11_3
[jalview.git] / src / jalview / ws / jws2 / AbstractJabaCalcWorker.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.jws2;
22
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import compbio.data.sequence.FastaSequence;
29 import compbio.metadata.Argument;
30 import compbio.metadata.ChunkHolder;
31 import compbio.metadata.JobStatus;
32 import compbio.metadata.JobSubmissionException;
33 import compbio.metadata.Option;
34 import compbio.metadata.ResultNotAvailableException;
35 import jalview.analysis.AlignSeq;
36 import jalview.analysis.SeqsetUtils;
37 import jalview.api.AlignViewportI;
38 import jalview.api.AlignmentViewPanel;
39 import jalview.bin.Console;
40 import jalview.datamodel.AlignmentAnnotation;
41 import jalview.datamodel.AlignmentI;
42 import jalview.datamodel.AnnotatedCollectionI;
43 import jalview.datamodel.SequenceI;
44 import jalview.gui.AlignFrame;
45 import jalview.gui.IProgressIndicator;
46 import jalview.gui.IProgressIndicatorHandler;
47 import jalview.schemes.ResidueProperties;
48 import jalview.util.IdUtils;
49 import jalview.util.IdUtils.IdType;
50 import jalview.workers.AlignCalcWorker;
51 import jalview.ws.jws2.dm.AAConSettings;
52 import jalview.ws.jws2.dm.JabaWsParamSet;
53 import jalview.ws.jws2.jabaws2.Jws2Instance;
54 import jalview.ws.params.WsParamSetI;
55
56 public abstract class AbstractJabaCalcWorker extends AlignCalcWorker
57 {
58
59   protected Jws2Instance service;
60
61   protected WsParamSetI preset;
62
63   protected List<Argument> arguments;
64
65   protected IProgressIndicator guiProgress;
66
67   protected boolean submitGaps = true;
68
69   /**
70    * by default, we filter out non-standard residues before submission
71    */
72   protected boolean filterNonStandardResidues = true;
73
74   /**
75    * Recover any existing parameters for this service
76    */
77   protected void initViewportParams()
78   {
79     if (getCalcId() != null)
80     {
81       ((jalview.gui.AlignViewport) alignViewport).setCalcIdSettingsFor(
82               getCalcId(),
83               new AAConSettings(true, service, this.preset,
84                       (arguments != null)
85                               ? JabaParamStore.getJwsArgsfromJaba(arguments)
86                               : null),
87               true);
88     }
89   }
90
91   /**
92    * 
93    * @return null or a string used to recover all annotation generated by this
94    *         worker
95    */
96   public abstract String getCalcId();
97
98   public WsParamSetI getPreset()
99   {
100     return preset;
101   }
102
103   public List<Argument> getArguments()
104   {
105     return arguments;
106   }
107
108   /**
109    * reconfigure and restart the AAConClient. This method will spawn a new
110    * thread that will wait until any current jobs are finished, modify the
111    * parameters and restart the conservation calculation with the new values.
112    * 
113    * @param newpreset
114    * @param newarguments
115    */
116   public void updateParameters(final WsParamSetI newpreset,
117           final List<Argument> newarguments)
118   {
119     preset = newpreset;
120     arguments = newarguments;
121     calcMan.startWorker(this);
122     initViewportParams();
123   }
124
125   public List<Option> getJabaArguments()
126   {
127     List<Option> newargs = new ArrayList<>();
128     if (preset != null && preset instanceof JabaWsParamSet)
129     {
130       newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
131     }
132     if (arguments != null && arguments.size() > 0)
133     {
134       for (Argument rg : arguments)
135       {
136         if (Option.class.isAssignableFrom(rg.getClass()))
137         {
138           newargs.add((Option) rg);
139         }
140       }
141     }
142     return newargs;
143   }
144
145   protected boolean alignedSeqs = true;
146
147   protected boolean nucleotidesAllowed = false;
148
149   protected boolean proteinAllowed = false;
150
151   /**
152    * record sequences for mapping result back to afterwards
153    */
154   protected boolean bySequence = false;
155
156   protected Map<String, SequenceI> seqNames;
157
158   protected boolean[] gapMap;
159
160   int realw;
161
162   protected int start;
163
164   int end;
165
166   public AbstractJabaCalcWorker(AlignViewportI alignViewport,
167           AlignmentViewPanel alignPanel)
168   {
169     super(alignViewport, alignPanel);
170   }
171
172   public AbstractJabaCalcWorker(Jws2Instance service, AlignFrame alignFrame,
173           WsParamSetI preset, List<Argument> paramset)
174   {
175     this(alignFrame.getCurrentView(), alignFrame.alignPanel);
176     this.guiProgress = alignFrame;
177     this.preset = preset;
178     this.arguments = paramset;
179     this.service = service;
180   }
181
182   /**
183    * 
184    * @return true if the submission thread should attempt to submit data
185    */
186   abstract boolean hasService();
187
188   volatile String rslt = "JOB NOT DEFINED";
189
190   @Override
191   public void run()
192   {
193     if (!hasService())
194     {
195       return;
196     }
197     long progressId = -1;
198
199     int serverErrorsLeft = 3;
200
201     StringBuffer msg = new StringBuffer();
202     try
203     {
204       if (checkDone())
205       {
206         return;
207       }
208       List<compbio.data.sequence.FastaSequence> seqs = getInputSequences(
209               alignViewport.getAlignment(),
210               bySequence ? alignViewport.getSelectionGroup() : null);
211
212       if (seqs == null || !checkValidInputSeqs(true, seqs))
213       {
214         calcMan.workerComplete(this);
215         return;
216       }
217
218       AlignmentAnnotation[] aa = alignViewport.getAlignment()
219               .getAlignmentAnnotation();
220       if (guiProgress != null)
221       {
222         guiProgress.setProgressBar("JABA " + getServiceActionText(),
223                 progressId = IdUtils.newId(IdType.PROGRESS));
224       }
225       rslt = submitToService(seqs);
226       if (guiProgress != null)
227       {
228         guiProgress.registerHandler(progressId,
229                 new IProgressIndicatorHandler()
230                 {
231
232                   @Override
233                   public boolean cancelActivity(long id)
234                   {
235                     cancelCurrentJob();
236                     return true;
237                   }
238
239                   @Override
240                   public boolean canCancel()
241                   {
242                     return true;
243                   }
244                 });
245       }
246       boolean finished = false;
247       long rpos = 0;
248       do
249       {
250         JobStatus status = getJobStatus(rslt);
251         if (status.equals(JobStatus.FINISHED))
252         {
253           finished = true;
254         }
255         if (calcMan.isPending(this) && isInteractiveUpdate())
256         {
257           finished = true;
258           // cancel this job and yield to the new job
259           try
260           {
261             if (cancelJob(rslt))
262             {
263               jalview.bin.Console
264                       .errPrintln("Cancelled AACon job: " + rslt);
265             }
266             else
267             {
268               jalview.bin.Console
269                       .errPrintln("FAILED TO CANCEL AACon job: " + rslt);
270             }
271
272           } catch (Exception x)
273           {
274
275           }
276           rslt = "CANCELLED JOB";
277           return;
278         }
279         long cpos;
280         ChunkHolder stats = null;
281         do
282         {
283           cpos = rpos;
284           boolean retry = false;
285           do
286           {
287             try
288             {
289               stats = pullExecStatistics(rslt, rpos);
290             } catch (Exception x)
291             {
292
293               if (x.getMessage().contains(
294                       "Position in a file could not be negative!"))
295               {
296                 // squash index out of bounds exception- seems to happen for
297                 // disorder predictors which don't (apparently) produce any
298                 // progress information and JABA server throws an exception
299                 // because progress length is -1.
300                 stats = null;
301               }
302               else
303               {
304                 if (--serverErrorsLeft > 0)
305                 {
306                   retry = true;
307                   try
308                   {
309                     Thread.sleep(200);
310                   } catch (InterruptedException q)
311                   {
312                   }
313                   ;
314                 }
315                 else
316                 {
317                   throw x;
318                 }
319               }
320             }
321           } while (retry);
322           if (stats != null)
323           {
324             System.out.print(stats.getChunk());
325             msg.append(stats);
326             rpos = stats.getNextPosition();
327           }
328         } while (stats != null && rpos > cpos);
329
330         if (!finished && status.equals(JobStatus.FAILED))
331         {
332           try
333           {
334             Thread.sleep(200);
335           } catch (InterruptedException x)
336           {
337           }
338           ;
339         }
340       } while (!finished);
341       if (serverErrorsLeft > 0)
342       {
343         try
344         {
345           Thread.sleep(200);
346         } catch (InterruptedException x)
347         {
348         }
349         if (collectAnnotationResultsFor(rslt))
350         {
351           Console.debug("Updating result annotation from Job " + rslt
352                   + " at " + service.getUri());
353           updateResultAnnotation(true);
354           ap.adjustAnnotationHeight();
355         }
356       }
357     }
358
359     catch (JobSubmissionException x)
360     {
361
362       jalview.bin.Console.errPrintln(
363               "submission error with " + getServiceActionText() + " :");
364       x.printStackTrace();
365       calcMan.disableWorker(this);
366     } catch (ResultNotAvailableException x)
367     {
368       jalview.bin.Console.errPrintln("collection error:\nJob ID: " + rslt);
369       x.printStackTrace();
370       calcMan.disableWorker(this);
371
372     } catch (OutOfMemoryError error)
373     {
374       calcMan.disableWorker(this);
375
376       // consensus = null;
377       // hconsensus = null;
378       ap.raiseOOMWarning(getServiceActionText(), error);
379     } catch (Exception x)
380     {
381       calcMan.disableWorker(this);
382
383       // consensus = null;
384       // hconsensus = null;
385       System.err
386               .println("Blacklisting worker due to unexpected exception:");
387       x.printStackTrace();
388     } finally
389     {
390
391       calcMan.workerComplete(this);
392       if (ap != null)
393       {
394         calcMan.workerComplete(this);
395         if (guiProgress != null && progressId != -1)
396         {
397           guiProgress.setProgressBar("", progressId);
398         }
399         // TODO: may not need to paintAlignment again !
400         ap.paintAlignment(false, false);
401       }
402       if (msg.length() > 0)
403       {
404         // TODO: stash message somewhere in annotation or alignment view.
405         // code below shows result in a text box popup
406         /*
407          * jalview.gui.CutAndPasteTransfer cap = new
408          * jalview.gui.CutAndPasteTransfer(); cap.setText(msg.toString());
409          * jalview.gui.Desktop.addInternalFrame(cap,
410          * "Job Status for "+getServiceActionText(), 600, 400);
411          */
412       }
413     }
414
415   }
416
417   /**
418    * validate input for dynamic/non-dynamic update context
419    * 
420    * @param dynamic
421    * @param seqs
422    * @return true if input is valid
423    */
424   abstract boolean checkValidInputSeqs(boolean dynamic,
425           List<FastaSequence> seqs);
426
427   abstract String submitToService(
428           List<compbio.data.sequence.FastaSequence> seqs)
429           throws JobSubmissionException;
430
431   abstract boolean cancelJob(String rslt) throws Exception;
432
433   abstract JobStatus getJobStatus(String rslt) throws Exception;
434
435   abstract ChunkHolder pullExecStatistics(String rslt, long rpos);
436
437   abstract boolean collectAnnotationResultsFor(String rslt)
438           throws ResultNotAvailableException;
439
440   public void cancelCurrentJob()
441   {
442     try
443     {
444       String id = rslt;
445       if (cancelJob(rslt))
446       {
447         jalview.bin.Console.errPrintln("Cancelled job " + id);
448       }
449       else
450       {
451         jalview.bin.Console
452                 .errPrintln("Job " + id + " couldn't be cancelled.");
453       }
454     } catch (Exception q)
455     {
456       q.printStackTrace();
457     }
458   }
459
460   /**
461    * Interactive updating. Analysis calculations that work on the currently
462    * displayed alignment data should cancel existing jobs when the input data
463    * has changed.
464    * 
465    * @return true if a running job should be cancelled because new input data is
466    *         available for analysis
467    */
468   abstract boolean isInteractiveUpdate();
469
470   public List<FastaSequence> getInputSequences(AlignmentI alignment,
471           AnnotatedCollectionI inputSeqs)
472   {
473     if (alignment == null || alignment.getWidth() <= 0
474             || alignment.getSequences() == null || alignment.isNucleotide()
475                     ? !nucleotidesAllowed
476                     : !proteinAllowed)
477     {
478       return null;
479     }
480     if (inputSeqs == null || inputSeqs.getWidth() <= 0
481             || inputSeqs.getSequences() == null
482             || inputSeqs.getSequences().size() < 1)
483     {
484       inputSeqs = alignment;
485     }
486
487     List<compbio.data.sequence.FastaSequence> seqs = new ArrayList<>();
488
489     int minlen = 10;
490     int ln = -1;
491     if (bySequence)
492     {
493       seqNames = new HashMap<>();
494     }
495     gapMap = new boolean[0];
496     start = inputSeqs.getStartRes();
497     end = inputSeqs.getEndRes();
498
499     for (SequenceI sq : (inputSeqs.getSequences()))
500     {
501       if (bySequence
502               ? sq.findPosition(end + 1)
503                       - sq.findPosition(start + 1) > minlen - 1
504               : sq.getEnd() - sq.getStart() > minlen - 1)
505       {
506         String newname = SeqsetUtils.unique_name(seqs.size() + 1);
507         // make new input sequence with or without gaps
508         if (seqNames != null)
509         {
510           seqNames.put(newname, sq);
511         }
512         FastaSequence seq;
513         if (submitGaps)
514         {
515           seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
516                   sq.getSequenceAsString()));
517           if (gapMap == null || gapMap.length < seq.getSequence().length())
518           {
519             boolean[] tg = gapMap;
520             gapMap = new boolean[seq.getLength()];
521             System.arraycopy(tg, 0, gapMap, 0, tg.length);
522             for (int p = tg.length; p < gapMap.length; p++)
523             {
524               gapMap[p] = false; // init as a gap
525             }
526           }
527           for (int apos : sq.gapMap())
528           {
529             char sqc = sq.getCharAt(apos);
530             if (!filterNonStandardResidues
531                     || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
532                             : ResidueProperties.nucleotideIndex[sqc] < 5))
533             {
534               gapMap[apos] = true; // aligned and real amino acid residue
535             }
536             ;
537           }
538         }
539         else
540         {
541           seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
542                   AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
543                           sq.getSequenceAsString(start, end + 1))));
544         }
545         if (seq.getSequence().length() > ln)
546         {
547           ln = seq.getSequence().length();
548         }
549       }
550     }
551     if (alignedSeqs && submitGaps)
552     {
553       realw = 0;
554       for (int i = 0; i < gapMap.length; i++)
555       {
556         if (gapMap[i])
557         {
558           realw++;
559         }
560       }
561       // try real hard to return something submittable
562       // TODO: some of AAcon measures need a minimum of two or three amino
563       // acids at each position, and AAcon doesn't gracefully degrade.
564       for (int p = 0; p < seqs.size(); p++)
565       {
566         FastaSequence sq = seqs.get(p);
567         int l = sq.getSequence().length();
568         // strip gapped columns
569         char[] padded = new char[realw],
570                 orig = sq.getSequence().toCharArray();
571         for (int i = 0, pp = 0; i < realw; pp++)
572         {
573           if (gapMap[pp])
574           {
575             if (orig.length > pp)
576             {
577               padded[i++] = orig[pp];
578             }
579             else
580             {
581               padded[i++] = '-';
582             }
583           }
584         }
585         seqs.set(p, new compbio.data.sequence.FastaSequence(sq.getId(),
586                 new String(padded)));
587       }
588     }
589     return seqs;
590   }
591
592   @Override
593   public void updateAnnotation()
594   {
595     updateResultAnnotation(false);
596   }
597
598   public abstract void updateResultAnnotation(boolean immediate);
599
600   public abstract String getServiceActionText();
601
602   /**
603    * notify manager that we have started, and wait for a free calculation slot
604    * 
605    * @return true if slot is obtained and work still valid, false if another
606    *         thread has done our work for us.
607    */
608   protected boolean checkDone()
609   {
610     calcMan.notifyStart(this);
611     ap.paintAlignment(false, false);
612     while (!calcMan.notifyWorking(this))
613     {
614       if (calcMan.isWorking(this))
615       {
616         return true;
617       }
618       try
619       {
620         if (ap != null)
621         {
622           ap.paintAlignment(false, false);
623         }
624
625         Thread.sleep(200);
626       } catch (Exception ex)
627       {
628         ex.printStackTrace();
629       }
630     }
631     if (alignViewport.isClosed())
632     {
633       abortAndDestroy();
634       return true;
635     }
636     return false;
637   }
638
639   protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
640   {
641     List<AlignmentAnnotation> our = ourAnnots;
642     ourAnnots = ourAnnot;
643     AlignmentI alignment = alignViewport.getAlignment();
644     if (our != null)
645     {
646       if (our.size() > 0)
647       {
648         for (AlignmentAnnotation an : our)
649         {
650           if (!ourAnnots.contains(an))
651           {
652             // remove the old annotation
653             alignment.deleteAnnotation(an);
654           }
655         }
656       }
657       our.clear();
658
659       ap.adjustAnnotationHeight();
660     }
661   }
662
663 }