f5021e68657b73f31023314d223edc2120aa57e6
[jalview.git] / src / jalview / ws / jws2 / JabawsAlignCalcWorker.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ws.jws2;
19
20 import jalview.analysis.AlignSeq;
21 import jalview.analysis.SeqsetUtils;
22 import jalview.api.AlignViewportI;
23 import jalview.api.AlignmentViewPanel;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.Annotation;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.AlignFrame;
29 import jalview.gui.IProgressIndicator;
30 import jalview.workers.AlignCalcWorker;
31 import jalview.ws.jws2.dm.JabaWsParamSet;
32 import jalview.ws.jws2.jabaws2.Jws2Instance;
33 import jalview.ws.params.WsParamSetI;
34
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40
41 import compbio.data.msa.SequenceAnnotation;
42 import compbio.data.sequence.FastaSequence;
43 import compbio.data.sequence.Score;
44 import compbio.data.sequence.ScoreManager;
45 import compbio.metadata.Argument;
46 import compbio.metadata.ChunkHolder;
47 import compbio.metadata.JobStatus;
48 import compbio.metadata.JobSubmissionException;
49 import compbio.metadata.Option;
50 import compbio.metadata.ResultNotAvailableException;
51 import compbio.metadata.WrongParameterException;
52
53 public abstract class JabawsAlignCalcWorker extends AlignCalcWorker
54 {
55   Jws2Instance service;
56
57   @SuppressWarnings("unchecked")
58   protected SequenceAnnotation aaservice;
59
60   protected ScoreManager scoremanager;
61
62   protected WsParamSetI preset;
63
64   protected List<Argument> arguments;
65
66   public JabawsAlignCalcWorker(AlignViewportI alignViewport,
67           AlignmentViewPanel alignPanel)
68   {
69     super(alignViewport, alignPanel);
70   }
71
72   IProgressIndicator guiProgress;
73
74   public JabawsAlignCalcWorker(Jws2Instance service, AlignFrame alignFrame,
75           WsParamSetI preset, List<Argument> paramset)
76   {
77     this(alignFrame.getCurrentView(), alignFrame.alignPanel);
78     this.guiProgress = alignFrame;
79     this.preset = preset;
80     this.arguments = paramset;
81     this.service = service;
82     aaservice = (SequenceAnnotation) service.service;
83
84   }
85
86   public WsParamSetI getPreset()
87   {
88     return preset;
89   }
90
91   public List<Argument> getArguments()
92   {
93     return arguments;
94   }
95
96   /**
97    * reconfigure and restart the AAConClient. This method will spawn a new
98    * thread that will wait until any current jobs are finished, modify the
99    * parameters and restart the conservation calculation with the new values.
100    * 
101    * @param newpreset
102    * @param newarguments
103    */
104   public void updateParameters(final WsParamSetI newpreset,
105           final List<Argument> newarguments)
106   {
107     preset = newpreset;
108     arguments = newarguments;
109     calcMan.startWorker(this);
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     int serverErrorsLeft = 3;
142
143     String rslt = "JOB NOT DEFINED";
144     StringBuffer msg = new StringBuffer();
145     try
146     {
147       if (checkDone())
148       {
149         return;
150       }
151       List<compbio.data.sequence.FastaSequence> seqs = getInputSequences(alignViewport
152               .getAlignment());
153
154       if (seqs == null)
155       {
156         calcMan.workerComplete(this);
157         return;
158       }
159
160       AlignmentAnnotation[] aa = alignViewport.getAlignment()
161               .getAlignmentAnnotation();
162       if (guiProgress != null)
163       {
164         guiProgress.setProgressBar("JABA " + getServiceActionText(),
165                 progressId = System.currentTimeMillis());
166       }
167       if (preset == null && arguments == null)
168       {
169         rslt = aaservice.analize(seqs);
170       }
171       else
172       {
173         try
174         {
175           rslt = aaservice.customAnalize(seqs, getJabaArguments());
176         } catch (WrongParameterException x)
177         {
178           throw new JobSubmissionException(
179                   "Invalid paremeter set. Check Jalview implementation.", x);
180
181         }
182       }
183       boolean finished = false;
184       long rpos = 0;
185       do
186       {
187         JobStatus status = aaservice.getJobStatus(rslt);
188         if (status.equals(JobStatus.FINISHED))
189         {
190           finished = true;
191         }
192         if (calcMan.isPending(this) && this instanceof AAConClient)
193         {
194           finished = true;
195           // cancel this job and yield to the new job
196           try
197           {
198             if (aaservice.cancelJob(rslt))
199             {
200               System.err.println("Cancelled AACon job: " + rslt);
201             }
202             else
203             {
204               System.err.println("FAILED TO CANCEL AACon job: " + rslt);
205             }
206
207           } catch (Exception x)
208           {
209
210           }
211
212           return;
213         }
214         long cpos;
215         ChunkHolder stats = null;
216         do
217         {
218           cpos = rpos;
219           boolean retry = false;
220           do
221           {
222             try
223             {
224               stats = aaservice.pullExecStatistics(rslt, rpos);
225             } catch (Exception x)
226             {
227
228               if (x.getMessage().contains(
229                       "Position in a file could not be negative!"))
230               {
231                 // squash index out of bounds exception- seems to happen for
232                 // disorder predictors which don't (apparently) produce any
233                 // progress information and JABA server throws an exception
234                 // because progress length is -1.
235                 stats = null;
236               }
237               else
238               {
239                 if (--serverErrorsLeft > 0)
240                 {
241                   retry = true;
242                   try
243                   {
244                     Thread.sleep(200);
245                   } catch (InterruptedException q)
246                   {
247                   }
248                   ;
249                 }
250                 else
251                 {
252                   throw x;
253                 }
254               }
255             }
256           } while (retry);
257           if (stats != null)
258           {
259             System.out.print(stats.getChunk());
260             msg.append(stats);
261             rpos = stats.getNextPosition();
262           }
263         } while (stats != null && rpos > cpos);
264
265         if (!finished && status.equals(JobStatus.FAILED))
266         {
267           try
268           {
269             Thread.sleep(200);
270           } catch (InterruptedException x)
271           {
272           }
273           ;
274         }
275       } while (!finished);
276       if (serverErrorsLeft > 0)
277       {
278         try
279         {
280           Thread.sleep(200);
281         } catch (InterruptedException x)
282         {
283         }
284         ;
285         scoremanager = aaservice.getAnnotation(rslt);
286         if (scoremanager != null)
287         {
288           jalview.bin.Cache.log
289                   .debug("Updating result annotation from Job " + rslt
290                           + " at " + service.getUri());
291           updateResultAnnotation(true);
292           ap.adjustAnnotationHeight();
293         }
294       }
295     }
296
297     catch (JobSubmissionException x)
298     {
299
300       System.err.println("submission error with " + getServiceActionText()
301               + " :");
302       x.printStackTrace();
303       calcMan.workerCannotRun(this);
304     } catch (ResultNotAvailableException x)
305     {
306       System.err.println("collection error:\nJob ID: " + rslt);
307       x.printStackTrace();
308       calcMan.workerCannotRun(this);
309
310     } catch (OutOfMemoryError error)
311     {
312       calcMan.workerCannotRun(this);
313
314       // consensus = null;
315       // hconsensus = null;
316       ap.raiseOOMWarning(getServiceActionText(), error);
317     } catch (Exception x)
318     {
319       calcMan.workerCannotRun(this);
320
321       // consensus = null;
322       // hconsensus = null;
323       System.err
324               .println("Blacklisting worker due to unexpected exception:");
325       x.printStackTrace();
326     } finally
327     {
328
329       calcMan.workerComplete(this);
330       if (ap != null)
331       {
332         calcMan.workerComplete(this);
333         if (guiProgress != null && progressId != -1)
334         {
335           guiProgress.setProgressBar("", progressId);
336         }
337         ap.paintAlignment(true);
338       }
339       if (msg.length() > 0)
340       {
341         // TODO: stash message somewhere in annotation or alignment view.
342         // code below shows result in a text box popup
343         /*
344          * jalview.gui.CutAndPasteTransfer cap = new
345          * jalview.gui.CutAndPasteTransfer(); cap.setText(msg.toString());
346          * jalview.gui.Desktop.addInternalFrame(cap,
347          * "Job Status for "+getServiceActionText(), 600, 400);
348          */
349       }
350     }
351
352   }
353
354   @Override
355   public void updateAnnotation()
356   {
357     updateResultAnnotation(false);
358   }
359
360   public abstract void updateResultAnnotation(boolean immediate);
361
362   public abstract String getServiceActionText();
363
364   boolean submitGaps = true;
365
366   boolean alignedSeqs = true;
367
368   boolean nucleotidesAllowed = false;
369
370   boolean proteinAllowed = false;
371
372   /**
373    * record sequences for mapping result back to afterwards
374    */
375   protected boolean bySequence = false;
376
377   Map<String, SequenceI> seqNames;
378
379   boolean[] gapMap;
380
381   int realw;
382
383   public List<FastaSequence> getInputSequences(AlignmentI alignment)
384   {
385     if (alignment == null || alignment.getWidth() <= 0
386             || alignment.getSequences() == null
387             // || (alignedSeqs && !alignment.isAligned() && !submitGaps)
388             || alignment.isNucleotide() ? !nucleotidesAllowed
389             : !proteinAllowed)
390     {
391       return null;
392     }
393     List<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
394
395     int minlen = 10;
396     int ln = -1;
397     if (bySequence)
398     {
399       seqNames = new HashMap<String, SequenceI>();
400     }
401     gapMap = new boolean[0];
402     for (SequenceI sq : ((List<SequenceI>) alignment.getSequences()))
403     {
404       if (sq.getEnd() - sq.getStart() > minlen - 1)
405       {
406         String newname = SeqsetUtils.unique_name(seqs.size() + 1);
407         // make new input sequence with or without gaps
408         if (seqNames != null)
409         {
410           seqNames.put(newname, sq);
411         }
412         FastaSequence seq;
413         if (submitGaps)
414         {
415           seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
416                   sq.getSequenceAsString()));
417           if (gapMap == null || gapMap.length < seq.getSequence().length())
418           {
419             boolean[] tg = gapMap;
420             gapMap = new boolean[seq.getLength()];
421             System.arraycopy(tg, 0, gapMap, 0, tg.length);
422             for (int p = tg.length; p < gapMap.length; p++)
423             {
424               gapMap[p] = false; // init as a gap
425             }
426           }
427           for (int apos : sq.gapMap())
428           {
429             gapMap[apos] = true; // aligned.
430           }
431         }
432         else
433         {
434           seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
435                   AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
436                           sq.getSequenceAsString())));
437         }
438         if (seq.getSequence().length() > ln)
439         {
440           ln = seq.getSequence().length();
441         }
442       }
443     }
444     if (alignedSeqs && submitGaps)
445     {
446       realw = 0;
447       for (int i = 0; i < gapMap.length; i++)
448       {
449         if (gapMap[i])
450         {
451           realw++;
452         }
453       }
454       // try real hard to return something submittable
455       // TODO: some of AAcon measures need a minimum of two or three amino
456       // acids at each position, and AAcon doesn't gracefully degrade.
457       for (int p = 0; p < seqs.size(); p++)
458       {
459         FastaSequence sq = seqs.get(p);
460         int l = sq.getSequence().length();
461         // strip gapped columns
462         char[] padded = new char[realw], orig = sq.getSequence()
463                 .toCharArray();
464         for (int i = 0, pp = 0; i < realw; pp++)
465         {
466           if (gapMap[pp])
467           {
468             if (orig.length > pp)
469             {
470               padded[i++] = orig[pp];
471             }
472             else
473             {
474               padded[i++] = '-';
475             }
476           }
477         }
478         seqs.set(p, new compbio.data.sequence.FastaSequence(sq.getId(),
479                 new String(padded)));
480       }
481     }
482     return seqs;
483   }
484
485   /**
486    * notify manager that we have started, and wait for a free calculation slot
487    * 
488    * @return true if slot is obtained and work still valid, false if another
489    *         thread has done our work for us.
490    */
491   boolean checkDone()
492   {
493     calcMan.notifyStart(this);
494     ap.paintAlignment(false);
495     while (!calcMan.notifyWorking(this))
496     {
497       if (calcMan.isWorking(this))
498       {
499         return true;
500       }
501       try
502       {
503         if (ap != null)
504         {
505           ap.paintAlignment(false);
506         }
507
508         Thread.sleep(200);
509       } catch (Exception ex)
510       {
511         ex.printStackTrace();
512       }
513     }
514     if (alignViewport.isClosed())
515     {
516       abortAndDestroy();
517       return true;
518     }
519     return false;
520   }
521
522   protected void createAnnotationRowsForScores(
523           List<AlignmentAnnotation> ourAnnot, String calcId, int alWidth,
524           Score scr)
525   {
526     // simple annotation row
527     AlignmentAnnotation annotation = alignViewport.getAlignment()
528             .findOrCreateAnnotation(scr.getMethod(), calcId, true, null,
529                     null);
530     if (alWidth == gapMap.length) // scr.getScores().size())
531     {
532       constructAnnotationFromScore(annotation, 0, alWidth, scr);
533       ourAnnot.add(annotation);
534     }
535   }
536
537   protected AlignmentAnnotation createAnnotationRowsForScores(
538           List<AlignmentAnnotation> ourAnnot, String typeName,
539           String calcId, SequenceI dseq, int base, Score scr)
540   {
541     System.out.println("Creating annotation on dseq:" + dseq.getStart()
542             + " base is " + base + " and length=" + dseq.getLength()
543             + " == " + scr.getScores().size());
544     // AlignmentAnnotation annotation = new AlignmentAnnotation(
545     // scr.getMethod(), typeName, new Annotation[]
546     // {}, 0, -1, AlignmentAnnotation.LINE_GRAPH);
547     // annotation.setCalcId(calcId);
548     AlignmentAnnotation annotation = alignViewport.getAlignment()
549             .findOrCreateAnnotation(typeName, calcId, false, dseq, null);
550     constructAnnotationFromScore(annotation, 0, dseq.getLength(), scr);
551     annotation.createSequenceMapping(dseq, base, false);
552     annotation.adjustForAlignment();
553     dseq.addAlignmentAnnotation(annotation);
554     ourAnnot.add(annotation);
555     return annotation;
556   }
557
558   private void constructAnnotationFromScore(AlignmentAnnotation annotation,
559           int base, int alWidth, Score scr)
560   {
561     Annotation[] elm = new Annotation[alWidth];
562     Iterator<Float> vals = scr.getScores().iterator();
563     float m = 0f, x = 0f;
564     for (int i = 0; vals.hasNext(); i++)
565     {
566       float val = vals.next().floatValue();
567       if (i == 0)
568       {
569         m = val;
570         x = val;
571       }
572       else
573       {
574         if (m > val)
575         {
576           m = val;
577         }
578         ;
579         if (x < val)
580         {
581           x = val;
582         }
583       }
584       // if we're at a gapped column then skip to next ungapped position
585       if (gapMap != null && gapMap.length > 0)
586       {
587         while (!gapMap[i])
588         {
589           elm[i++] = new Annotation("", "", ' ', Float.NaN);
590         }
591       }
592       elm[i] = new Annotation("", "" + val, ' ', val);
593     }
594
595     annotation.annotations = elm;
596     annotation.belowAlignment = true;
597     if (x < 0)
598     {
599       x = 0;
600     }
601     x += (x - m) * 0.1;
602     annotation.graphMax = x;
603     annotation.graphMin = m;
604     annotation.validateRangeAndDisplay();
605   }
606
607   protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
608   {
609     List<AlignmentAnnotation> our = ourAnnots;
610     ourAnnots = ourAnnot;
611     AlignmentI alignment = alignViewport.getAlignment();
612     if (our != null)
613     {
614       if (our.size() > 0)
615       {
616         for (AlignmentAnnotation an : our)
617         {
618           if (!ourAnnots.contains(an))
619           {
620             // remove the old annotation
621             alignment.deleteAnnotation(an);
622           }
623         }
624       }
625       our.clear();
626
627       ap.adjustAnnotationHeight();
628     }
629   }
630 }