JAL-974 JAL-975 try to cope with temporary server errors and timeouts
[jalview.git] / src / jalview / ws / jws2 / JabawsAlignCalcWorker.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.bin.Cache;
8 import jalview.datamodel.AlignmentAnnotation;
9 import jalview.datamodel.AlignmentI;
10 import jalview.datamodel.Annotation;
11 import jalview.datamodel.SequenceFeature;
12 import jalview.datamodel.SequenceI;
13 import jalview.gui.AlignFrame;
14 import jalview.gui.IProgressIndicator;
15 import jalview.workers.AlignCalcWorker;
16 import jalview.ws.jws2.dm.JabaWsParamSet;
17 import jalview.ws.jws2.jabaws2.Jws2Instance;
18 import jalview.ws.params.WsParamSetI;
19
20 import java.awt.Color;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26
27 import com.sun.xml.internal.ws.client.ClientTransportException;
28
29 import compbio.data.msa.SequenceAnnotation;
30 import compbio.data.sequence.FastaSequence;
31 import compbio.data.sequence.Score;
32 import compbio.data.sequence.ScoreManager;
33 import compbio.metadata.Argument;
34 import compbio.metadata.ChunkHolder;
35 import compbio.metadata.JobStatus;
36 import compbio.metadata.JobSubmissionException;
37 import compbio.metadata.Option;
38 import compbio.metadata.ResultNotAvailableException;
39 import compbio.metadata.WrongParameterException;
40
41 public abstract class JabawsAlignCalcWorker extends AlignCalcWorker
42 {
43   Jws2Instance service;
44   @SuppressWarnings("unchecked")
45   protected SequenceAnnotation aaservice;
46
47   protected ScoreManager scoremanager;
48
49   protected WsParamSetI preset;
50
51   protected List<Argument> arguments;
52
53   public JabawsAlignCalcWorker(AlignViewportI alignViewport,
54           AlignmentViewPanel alignPanel)
55   {
56     super(alignViewport, alignPanel);
57   }
58
59   IProgressIndicator guiProgress;
60
61   public JabawsAlignCalcWorker(Jws2Instance service, AlignFrame alignFrame,
62           WsParamSetI preset, List<Argument> paramset)
63   {
64     this(alignFrame.getCurrentView(), alignFrame.alignPanel);
65     this.guiProgress = alignFrame;
66     this.preset = preset;
67     this.arguments = paramset;
68     this.service = service;
69     aaservice = (SequenceAnnotation) service.service;
70
71   }
72
73   public WsParamSetI getPreset()
74   {
75     return preset;
76   }
77
78   public List<Argument> getArguments()
79   {
80     return arguments;
81   }
82
83   /**
84    * reconfigure and restart the AAConsClient. This method will spawn a new
85    * thread that will wait until any current jobs are finished, modify the
86    * parameters and restart the conservation calculation with the new values.
87    *
88    * @param newpreset
89    * @param newarguments
90    */
91   public void updateParameters(final WsParamSetI newpreset,
92           final List<Argument> newarguments)
93   {
94     preset = newpreset;
95     arguments = newarguments;
96     calcMan.startWorker(this);
97   }
98
99   public List<Option> getJabaArguments()
100   {
101     List<Option> newargs = new ArrayList<Option>();
102     if (preset != null && preset instanceof JabaWsParamSet)
103     {
104       newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
105     }
106     if (arguments != null && arguments.size() > 0)
107     {
108       for (Argument rg : arguments)
109       {
110         if (Option.class.isAssignableFrom(rg.getClass()))
111         {
112           newargs.add((Option) rg);
113         }
114       }
115     }
116     return newargs;
117   }
118
119   @Override
120   public void run()
121   {
122     if (aaservice == null)
123     {
124       return;
125     }
126     long progressId = -1;
127
128     int serverErrorsLeft = 3;
129
130     String rslt = "JOB NOT DEFINED";
131     StringBuffer msg=new StringBuffer();
132     try
133     {
134       if (checkDone())
135       {
136         return;
137       }
138       List<compbio.data.sequence.FastaSequence> seqs = getInputSequences(alignViewport
139               .getAlignment());
140
141       if (seqs == null)
142       {
143         calcMan.workerComplete(this);
144         return;
145       }
146
147       AlignmentAnnotation[] aa = alignViewport.getAlignment()
148               .getAlignmentAnnotation();
149       if (guiProgress != null)
150       {
151         guiProgress.setProgressBar("JABA " + getServiceActionText(),
152                 progressId = System.currentTimeMillis());
153       }
154       if (preset == null && arguments==null)
155       {
156         rslt = aaservice.analize(seqs);
157       }
158       else
159       {
160         try
161         {
162           rslt = aaservice.customAnalize(seqs, getJabaArguments());
163         } catch (WrongParameterException x)
164         {
165           throw new JobSubmissionException(
166                   "Invalid paremeter set. Check Jalview implementation.", x);
167
168         }
169       }
170       boolean finished = false;
171       long rpos = 0;
172       do
173       {
174         JobStatus status = aaservice.getJobStatus(rslt);
175         if (status.equals(JobStatus.FINISHED))
176         {
177           finished = true;
178         }
179         if (calcMan.isPending(this) && this instanceof AAConsClient)
180         {
181           finished = true;
182           // cancel this job and yield to the new job
183           try
184           {
185             if (aaservice.cancelJob(rslt))
186             {
187               System.err.println("Cancelled AACon job: " + rslt);
188             }
189             else
190             {
191               System.err.println("FAILED TO CANCELL AACon job: " + rslt);
192             }
193
194           } catch (Exception x)
195           {
196
197           }
198
199           return;
200         }
201         long cpos;
202         ChunkHolder stats = null;
203         do
204         {
205           cpos = rpos;
206           boolean retry = false;
207           do
208           {
209             try
210             {
211               stats = aaservice.pullExecStatistics(rslt, rpos);
212             }  catch (Exception x)
213             {
214
215               if (x.getMessage().contains(
216                       "Position in a file could not be negative!"))
217               {
218                 // squash index out of bounds exception- seems to happen for
219                 // disorder predictors which don't (apparently) produce any
220                 // progress information and JABA server throws an exception
221                 // because progress length is -1.
222                 stats = null;
223               }
224               else
225               {
226                 if (--serverErrorsLeft > 0)
227                 {
228                   retry = true;
229                   try {
230                     Thread.sleep(200);
231                   } catch (InterruptedException q) {};
232                 } else {
233                   throw x;
234                 }
235               }
236             }
237           } while (retry);
238           if (stats != null)
239           {
240             System.out.print(stats.getChunk());
241             msg.append(stats);
242             rpos = stats.getNextPosition();
243           }
244         } while (stats != null && rpos > cpos);
245
246         if (!finished && status.equals(JobStatus.FAILED))
247         {
248           try
249           {
250             Thread.sleep(200);
251           } catch (InterruptedException x)
252           {
253           }
254           ;
255         }
256       } while (!finished);
257       if (serverErrorsLeft>0)
258       {
259         try
260         {
261           Thread.sleep(200);
262         } catch (InterruptedException x)
263         {
264         }
265         ;
266         scoremanager = aaservice.getAnnotation(rslt);
267         if (scoremanager != null)
268         {
269           jalview.bin.Cache.log
270                   .debug("Updating result annotation from Job " + rslt
271                           + " at " + service.getUri());
272           updateResultAnnotation(true);
273         }
274       }
275     }
276
277     catch (JobSubmissionException x)
278     {
279
280       System.err.println("submission error with " + getServiceActionText()
281               + " :");
282       x.printStackTrace();
283       calcMan.workerCannotRun(this);
284     } catch (ResultNotAvailableException x)
285     {
286       System.err.println("collection error:\nJob ID: " + rslt);
287       x.printStackTrace();
288       calcMan.workerCannotRun(this);
289
290     } catch (OutOfMemoryError error)
291     {
292       calcMan.workerCannotRun(this);
293
294       // consensus = null;
295       // hconsensus = null;
296       ap.raiseOOMWarning(getServiceActionText(), error);
297     } catch (Exception x)
298     {
299       calcMan.workerCannotRun(this);
300
301       // consensus = null;
302       // hconsensus = null;
303       System.err
304               .println("Blacklisting worker due to unexpected exception:");
305       x.printStackTrace();
306     } finally
307     {
308
309       calcMan.workerComplete(this);
310       if (ap != null)
311       {
312         calcMan.workerComplete(this);
313         if (guiProgress != null && progressId!=-1)
314         {
315           guiProgress.setProgressBar("", progressId);
316         }
317         ap.paintAlignment(true);
318       }
319       if (msg.length()>0)
320       {
321         // TODO: stash message somewhere in annotation or alignment view.
322         // code below shows result in a text box popup
323         /* jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
324         cap.setText(msg.toString());
325         jalview.gui.Desktop.addInternalFrame(cap, "Job Status for "+getServiceActionText(), 600, 400); */
326       }
327     }
328
329   }
330
331   @Override
332   public void updateAnnotation()
333   {
334     updateResultAnnotation(false);
335   }
336
337   public abstract void updateResultAnnotation(boolean immediate);
338
339   public abstract String getServiceActionText();
340
341   boolean submitGaps = true;
342
343   boolean alignedSeqs = true;
344
345   boolean nucleotidesAllowed = false;
346
347   boolean proteinAllowed = false;
348
349   /**
350    * record sequences for mapping result back to afterwards
351    */
352   protected boolean bySequence = false;
353
354   Map<String, SequenceI> seqNames;
355   boolean[] gapMap;
356   int realw;
357   public List<FastaSequence> getInputSequences(AlignmentI alignment)
358   {
359     if (alignment == null || alignment.getWidth() <= 0
360             || alignment.getSequences() == null
361             // || (alignedSeqs && !alignment.isAligned() && !submitGaps)
362             || alignment.isNucleotide() ? !nucleotidesAllowed
363             : !proteinAllowed)
364     {
365       return null;
366     }
367     List<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
368
369     int minlen = 10;
370     int ln = -1;
371     if (bySequence)
372     {
373       seqNames = new HashMap<String, SequenceI>();
374     }
375     gapMap=new boolean[0];
376     for (SequenceI sq : ((List<SequenceI>) alignment.getSequences()))
377     {
378       if (sq.getEnd() - sq.getStart() > minlen - 1)
379       {
380         String newname = SeqsetUtils.unique_name(seqs.size() + 1);
381         // make new input sequence with or without gaps
382         if (seqNames != null)
383         {
384           seqNames.put(newname, sq);
385         }
386         FastaSequence seq;
387         if (submitGaps)
388         {
389           seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,sq.getSequenceAsString()));
390           if (gapMap==null || gapMap.length<seq.getSequence().length())
391           {
392             boolean[] tg=gapMap;
393             gapMap=new boolean[seq.getLength()];
394             System.arraycopy(tg, 0, gapMap, 0, tg.length);
395             for (int p=tg.length;p<gapMap.length;p++)
396             {
397               gapMap[p]=false; // init as a gap
398             }
399           }
400           for (int apos:sq.gapMap()) {
401             gapMap[apos]=true; // aligned.
402           }
403         } else {
404         seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
405                 AlignSeq
406                         .extractGaps(jalview.util.Comparison.GapChars,
407                                 sq.getSequenceAsString())));
408         }
409         if (seq.getSequence().length() > ln)
410         {
411           ln = seq.getSequence().length();
412         }
413       }
414     }
415     if (alignedSeqs && submitGaps)
416     {
417       realw = 0;
418       for (int i=0;i<gapMap.length;i++)
419       {
420         if (gapMap[i])
421         {
422           realw++;
423         }
424       }
425       // try real hard to return something submittable
426       // TODO: some of AAcons measures need a minimum of two or three amino
427       // acids at each position, and aacons doesn't gracefully degrade.
428       for (int p = 0; p < seqs.size(); p++)
429       {
430         FastaSequence sq = seqs.get(p);
431         int l = sq.getSequence().length();
432         // strip gapped columns
433         char[] padded = new char[realw],orig=sq.getSequence().toCharArray();
434         for (int i=0,pp=0;i<realw; pp++)
435         {
436           if (gapMap[pp])
437           {
438             if (orig.length>pp)
439             {
440               padded[i++]=orig[pp];
441             } else {
442               padded[i++]='-';
443             }       
444           }
445         }
446         seqs.set(p, new compbio.data.sequence.FastaSequence(sq.getId(),
447                   new String(padded)));
448       }
449     }
450     return seqs;
451   }
452
453   /**
454    * notify manager that we have started, and wait for a free calculation slot
455    *
456    * @return true if slot is obtained and work still valid, false if another
457    *         thread has done our work for us.
458    */
459   boolean checkDone()
460   {
461     calcMan.notifyStart(this);
462     ap.paintAlignment(false);
463     while (!calcMan.notifyWorking(this))
464     {
465       if (calcMan.isWorking(this))
466       {
467         return true;
468       }
469       try
470       {
471         if (ap != null)
472         {
473           ap.paintAlignment(false);
474         }
475
476         Thread.sleep(200);
477       } catch (Exception ex)
478       {
479         ex.printStackTrace();
480       }
481     }
482     if (alignViewport.isClosed())
483     {
484       abortAndDestroy();
485       return true;
486     }
487     return false;
488   }
489
490 }