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