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