JAL-3949 Complete new abstracted logging framework in jalview.log. Updated log calls...
[jalview.git] / src / jalview / ws / jws1 / JPredThread.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.jws1;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.analysis.SeqsetUtils;
25 import jalview.bin.Cache;
26 import jalview.datamodel.Alignment;
27 import jalview.datamodel.AlignmentAnnotation;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.AlignmentView;
30 import jalview.datamodel.HiddenColumns;
31 import jalview.datamodel.SequenceI;
32 import jalview.gui.AlignFrame;
33 import jalview.gui.Desktop;
34 import jalview.gui.WebserviceInfo;
35 import jalview.io.DataSourceType;
36 import jalview.io.FileFormatI;
37 import jalview.io.FormatAdapter;
38 import jalview.io.IdentifyFile;
39 import jalview.io.JPredFile;
40 import jalview.io.JnetAnnotationMaker;
41 import jalview.io.PileUpfile;
42 import jalview.util.Comparison;
43 import jalview.util.MessageManager;
44 import jalview.ws.AWsJob;
45 import jalview.ws.JobStateSummary;
46 import jalview.ws.WSClientI;
47
48 import java.util.Hashtable;
49 import java.util.List;
50
51 import vamsas.objects.simple.JpredResult;
52
53 class JPredThread extends JWS1Thread implements WSClientI
54 {
55   // TODO: put mapping between JPredJob input and input data here -
56   // JNetAnnotation adding is done after result parsing.
57   class JPredJob extends WSJob
58   {
59     // TODO: make JPredJob deal only with what was sent to and received from a
60     // JNet service
61     int[] predMap = null; // mapping from sequence(i) to the original
62
63     // sequence(predMap[i]) being predicted on
64
65     vamsas.objects.simple.Sequence sequence;
66
67     vamsas.objects.simple.Msfalignment msa;
68
69     java.util.Hashtable SequenceInfo = null;
70
71     int msaIndex = 0; // the position of the original sequence in the array of
72
73     // Sequences in the input object that this job holds a
74     // prediction for
75
76     /**
77      * 
78      * @return true if getResultSet will return a valid alignment and prediction
79      *         result.
80      */
81     @Override
82     public boolean hasResults()
83     {
84       if (subjobComplete && result != null && result.isFinished()
85               && ((JpredResult) result).getPredfile() != null
86               && ((JpredResult) result).getAligfile() != null)
87       {
88         return true;
89       }
90       return false;
91     }
92
93     @Override
94     public boolean hasValidInput()
95     {
96       if (sequence != null)
97       {
98         return true;
99       }
100       return false;
101     }
102
103     /**
104      * 
105      * @return null or Object[] { annotated alignment for this prediction,
106      *         ColumnSelection for this prediction} or null if no results
107      *         available.
108      * @throws Exception
109      */
110     public Object[] getResultSet() throws Exception
111     {
112       if (result == null || !result.isFinished())
113       {
114         return null;
115       }
116       AlignmentI al = null;
117       HiddenColumns alhidden = null;
118       int FirstSeq = -1; // the position of the query sequence in Alignment al
119
120       JpredResult result = (JpredResult) this.result;
121
122       Cache.debug("Parsing output from JNet job.");
123       // JPredFile prediction = new JPredFile("C:/JalviewX/files/jpred.txt",
124       // "File");
125       JPredFile prediction = new JPredFile(result.getPredfile(),
126               DataSourceType.PASTE);
127       SequenceI[] preds = prediction.getSeqsAsArray();
128       Cache.debug("Got prediction profile.");
129
130       if ((this.msa != null) && (result.getAligfile() != null))
131       {
132         Cache.debug("Getting associated alignment.");
133         // we ignore the returned alignment if we only predicted on a single
134         // sequence
135         FileFormatI format = new IdentifyFile()
136                 .identify(result.getAligfile(), DataSourceType.PASTE);
137
138         if (format != null)
139         {
140           SequenceI sqs[];
141           if (predMap != null)
142           {
143             Object[] alandcolsel = input
144                     .getAlignmentAndHiddenColumns(getGapChar());
145             sqs = (SequenceI[]) alandcolsel[0];
146             al = new Alignment(sqs);
147             alhidden = (HiddenColumns) alandcolsel[1];
148           }
149           else
150           {
151             al = new FormatAdapter().readFile(result.getAligfile(),
152                     DataSourceType.PASTE, format);
153             sqs = new SequenceI[al.getHeight()];
154
155             for (int i = 0, j = al.getHeight(); i < j; i++)
156             {
157               sqs[i] = al.getSequenceAt(i);
158             }
159             if (!SeqsetUtils.deuniquify(SequenceInfo, sqs))
160             {
161               throw (new Exception(MessageManager.getString(
162                       "exception.couldnt_recover_sequence_properties_for_alignment")));
163             }
164           }
165           FirstSeq = 0;
166           if (currentView.getDataset() != null)
167           {
168             al.setDataset(currentView.getDataset());
169
170           }
171           else
172           {
173             al.setDataset(null);
174           }
175           JnetAnnotationMaker.add_annotation(prediction, al, FirstSeq,
176                   false, predMap);
177
178         }
179         else
180         {
181           throw (new Exception(MessageManager.formatMessage(
182                   "exception.unknown_format_for_file", new String[]
183                   { "", result.getAligfile() })));
184         }
185       }
186       else
187       {
188         al = new Alignment(preds);
189         FirstSeq = prediction.getQuerySeqPosition();
190         if (predMap != null)
191         {
192           char gc = getGapChar();
193           SequenceI[] sqs = (SequenceI[]) input
194                   .getAlignmentAndHiddenColumns(gc)[0];
195           if (this.msaIndex >= sqs.length)
196           {
197             throw new Error(MessageManager.getString(
198                     "error.implementation_error_invalid_msa_index_for_job"));
199           }
200
201           // ///
202           // Uses RemoveGapsCommand
203           // ///
204           new jalview.commands.RemoveGapsCommand(
205                   MessageManager.getString("label.remove_gaps"),
206                   new SequenceI[]
207                   { sqs[msaIndex] }, currentView);
208
209           SequenceI profileseq = al.getSequenceAt(FirstSeq);
210           profileseq.setSequence(sqs[msaIndex].getSequenceAsString());
211         }
212
213         if (!jalview.analysis.SeqsetUtils.SeqCharacterUnhash(
214                 al.getSequenceAt(FirstSeq), SequenceInfo))
215         {
216           throw (new Exception(MessageManager.getString(
217                   "exception.couldnt_recover_sequence_props_for_jnet_query")));
218         }
219         else
220         {
221           if (currentView.getDataset() != null)
222           {
223             al.setDataset(currentView.getDataset());
224
225           }
226           else
227           {
228             al.setDataset(null);
229           }
230           jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,
231                   FirstSeq, true, predMap);
232           SequenceI profileseq = al.getSequenceAt(0); // this includes any gaps.
233           alignToProfileSeq(al, profileseq);
234           if (predMap != null)
235           {
236             // Adjust input view for gaps
237             // propagate insertions into profile
238             alhidden = al.propagateInsertions(profileseq, input);
239           }
240         }
241       }
242       // transfer to dataset
243       for (AlignmentAnnotation alant : al.getAlignmentAnnotation())
244       {
245         if (alant.sequenceRef != null)
246         {
247           replaceAnnotationOnAlignmentWith(alant, alant.label,
248                   "jalview.jws1.Jpred" + (this.msa == null ? "" : "MSA"),
249                   alant.sequenceRef);
250         }
251       }
252       return new Object[] { al, alhidden }; // , FirstSeq, noMsa};
253     }
254
255     /**
256      * copied from JabawsCalcWorker
257      * 
258      * @param newAnnot
259      * @param typeName
260      * @param calcId
261      * @param aSeq
262      */
263     protected void replaceAnnotationOnAlignmentWith(
264             AlignmentAnnotation newAnnot, String typeName, String calcId,
265             SequenceI aSeq)
266     {
267       SequenceI dsseq = aSeq.getDatasetSequence();
268       while (dsseq.getDatasetSequence() != null)
269       {
270         dsseq = dsseq.getDatasetSequence();
271       }
272       // look for same annotation on dataset and lift this one over
273       List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
274               typeName);
275       if (dsan != null && dsan.size() > 0)
276       {
277         for (AlignmentAnnotation dssan : dsan)
278         {
279           dsseq.removeAlignmentAnnotation(dssan);
280         }
281       }
282       AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
283       dsseq.addAlignmentAnnotation(dssan);
284       dssan.adjustForAlignment();
285     }
286
287     /**
288      * Given an alignment where all other sequences except profileseq are
289      * aligned to the ungapped profileseq, insert gaps in the other sequences to
290      * realign them with the residues in profileseq
291      * 
292      * @param al
293      * @param profileseq
294      */
295     private void alignToProfileSeq(AlignmentI al, SequenceI profileseq)
296     {
297       char gc = al.getGapCharacter();
298       int[] gapMap = profileseq.gapMap();
299       // insert gaps into profile
300       for (int lp = 0, r = 0; r < gapMap.length; r++)
301       {
302         if (gapMap[r] - lp > 1)
303         {
304           StringBuffer sb = new StringBuffer();
305           for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
306           {
307             sb.append(gc);
308           }
309           for (int s = 1, ns = al.getHeight(); s < ns; s++)
310           {
311             String sq = al.getSequenceAt(s).getSequenceAsString();
312             int diff = gapMap[r] - sq.length();
313             if (diff > 0)
314             {
315               // pad gaps
316               sq = sq + sb;
317               while ((diff = gapMap[r] - sq.length()) > 0)
318               {
319                 sq = sq + ((diff >= sb.length()) ? sb.toString()
320                         : sb.substring(0, diff));
321               }
322               al.getSequenceAt(s).setSequence(sq);
323             }
324             else
325             {
326               al.getSequenceAt(s).setSequence(sq.substring(0, gapMap[r])
327                       + sb.toString() + sq.substring(gapMap[r]));
328             }
329           }
330         }
331         lp = gapMap[r];
332       }
333     }
334
335     public JPredJob(Hashtable SequenceInfo, SequenceI seq, int[] delMap)
336     {
337       super();
338       this.predMap = delMap;
339       String sq = AlignSeq.extractGaps(Comparison.GapChars,
340               seq.getSequenceAsString());
341       if (sq.length() >= 20)
342       {
343         this.SequenceInfo = SequenceInfo;
344         sequence = new vamsas.objects.simple.Sequence();
345         sequence.setId(seq.getName());
346         sequence.setSeq(sq);
347       }
348       else
349       {
350         errorMessage = "Sequence is too short to predict with JPred - need at least 20 amino acids.";
351       }
352     }
353
354     public JPredJob(Hashtable SequenceInfo, SequenceI[] msf, int[] delMap)
355     {
356       this(SequenceInfo, msf[0], delMap);
357       if (sequence != null)
358       {
359         if (msf.length > 1)
360         {
361           msa = new vamsas.objects.simple.Msfalignment();
362           PileUpfile pileup = new PileUpfile();
363           msa.setMsf(pileup.print(msf, true));
364         }
365       }
366     }
367
368     String errorMessage = "";
369
370     public String getValidationMessages()
371     {
372       return errorMessage + "\n";
373     }
374   }
375
376   ext.vamsas.Jpred server;
377
378   String altitle = "";
379
380   JPredThread(WebserviceInfo wsinfo, String altitle,
381           ext.vamsas.Jpred server, String wsurl, AlignmentView alview,
382           AlignFrame alframe)
383   {
384     super(alframe, wsinfo, alview, wsurl);
385     this.altitle = altitle;
386     this.server = server;
387   }
388
389   JPredThread(WebserviceInfo wsinfo, String altitle,
390           ext.vamsas.Jpred server, String wsurl, Hashtable SequenceInfo,
391           SequenceI seq, int[] delMap, AlignmentView alview,
392           AlignFrame alframe)
393   {
394     this(wsinfo, altitle, server, wsurl, alview, alframe);
395     JPredJob job = new JPredJob(SequenceInfo, seq, delMap);
396     if (job.hasValidInput())
397     {
398       OutputHeader = wsInfo.getProgressText();
399       jobs = new WSJob[] { job };
400       job.setJobnum(0);
401     }
402     else
403     {
404       wsInfo.appendProgressText(job.getValidationMessages());
405     }
406   }
407
408   JPredThread(WebserviceInfo wsinfo, String altitle,
409           ext.vamsas.Jpred server, Hashtable SequenceInfo, SequenceI[] msf,
410           int[] delMap, AlignmentView alview, AlignFrame alframe,
411           String wsurl)
412   {
413     this(wsinfo, altitle, server, wsurl, alview, alframe);
414     JPredJob job = new JPredJob(SequenceInfo, msf, delMap);
415     if (job.hasValidInput())
416     {
417       jobs = new WSJob[] { job };
418       OutputHeader = wsInfo.getProgressText();
419       job.setJobnum(0);
420     }
421     else
422     {
423       wsInfo.appendProgressText(job.getValidationMessages());
424     }
425   }
426
427   @Override
428   public void StartJob(AWsJob j)
429   {
430     if (!(j instanceof JPredJob))
431     {
432       throw new Error(MessageManager.formatMessage(
433               "error.implementation_error_startjob_called", new String[]
434               { j.getClass().toString() }));
435     }
436     try
437     {
438       JPredJob job = (JPredJob) j;
439       if (job.msa != null)
440       {
441         job.setJobId(server.predictOnMsa(job.msa));
442       }
443       else if (job.sequence != null)
444       {
445         job.setJobId(server.predict(job.sequence)); // debug like : job.jobId =
446         // "/jobs/www-jpred/jp_Yatat29";//
447       }
448
449       if (job.getJobId() != null)
450       {
451         if (job.getJobId().startsWith("Broken"))
452         {
453           job.result = new JpredResult();
454           job.result.setInvalid(true);
455           job.result.setStatus(MessageManager
456                   .formatMessage("label.submission_params", new String[]
457                   { job.getJobId().toString() }));
458           throw new Exception(job.getJobId());
459         }
460         else
461         {
462           job.setSubmitted(true);
463           job.setSubjobComplete(false);
464           Cache.info(WsUrl + " Job Id '" + job.getJobId() + "'");
465         }
466       }
467       else
468       {
469         throw new Exception(MessageManager
470                 .getString("exception.server_timeout_try_later"));
471       }
472     } catch (Exception e)
473     {
474       // kill the whole job.
475       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
476       if (e.getMessage().indexOf("Exception") > -1)
477       {
478         wsInfo.setStatus(j.getJobnum(),
479                 WebserviceInfo.STATE_STOPPED_SERVERERROR);
480         wsInfo.setProgressText(j.getJobnum(),
481                 "Failed to submit the prediction. (Just close the window)\n"
482                         + "It is most likely that there is a problem with the server.\n");
483         System.err.println(
484                 "JPredWS Client: Failed to submit the prediction. Quite possibly because of a server error - see below)\n"
485                         + e.getMessage() + "\n");
486
487         Cache.warn("Server Exception", e);
488       }
489       else
490       {
491         wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
492         // JBPNote - this could be a popup informing the user of the problem.
493         wsInfo.appendProgressText(j.getJobnum(),
494                 MessageManager.formatMessage(
495                         "info.failed_to_submit_prediction", new String[]
496                         { e.getMessage(), wsInfo.getProgressText() }));
497
498         Cache.debug("Failed Submission of job " + j.getJobnum(), e);
499
500       }
501       j.setAllowedServerExceptions(-1);
502       j.setSubjobComplete(true);
503     }
504   }
505
506   @Override
507   public void parseResult()
508   {
509     int results = 0; // number of result sets received
510     JobStateSummary finalState = new JobStateSummary();
511     try
512     {
513       for (int j = 0; j < jobs.length; j++)
514       {
515         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
516         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
517                 && jobs[j].hasResults())
518         {
519           results++;
520         }
521       }
522     } catch (Exception ex)
523     {
524
525       Cache.error(
526               "Unexpected exception when processing results for " + altitle,
527               ex);
528       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
529     }
530     if (results > 0)
531     {
532       wsInfo.showResultsNewFrame
533               .addActionListener(new java.awt.event.ActionListener()
534               {
535                 @Override
536                 public void actionPerformed(java.awt.event.ActionEvent evt)
537                 {
538                   displayResults(true);
539                 }
540               });
541       wsInfo.mergeResults
542               .addActionListener(new java.awt.event.ActionListener()
543               {
544                 @Override
545                 public void actionPerformed(java.awt.event.ActionEvent evt)
546                 {
547                   displayResults(false);
548                 }
549               });
550       wsInfo.setResultsReady();
551     }
552     else
553     {
554       wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
555       wsInfo.appendInfoText("No jobs ran.");
556       wsInfo.setFinishedNoResults();
557     }
558   }
559
560   void displayResults(boolean newWindow)
561   {
562     // TODO: cope with multiple subjobs.
563     if (jobs != null)
564     {
565       Object[] res = null;
566       boolean msa = false;
567       for (int jn = 0; jn < jobs.length; jn++)
568       {
569         Object[] jobres = null;
570         JPredJob j = (JPredJob) jobs[jn];
571
572         if (j.hasResults())
573         {
574           // hack - we only deal with all single seuqence predictions or all
575           // profile predictions
576           msa = (j.msa != null) ? true : msa;
577           try
578           {
579             Cache.debug("Parsing output of job " + jn);
580             jobres = j.getResultSet();
581             Cache.debug("Finished parsing output.");
582             if (jobs.length == 1)
583             {
584               res = jobres;
585             }
586             else
587             {
588               // do merge with other job results
589               throw new Error(MessageManager.getString(
590                       "error.multiple_jnet_subjob_merge_not_implemented"));
591             }
592           } catch (Exception e)
593           {
594             Cache.error("JNet Client: JPred Annotation Parse Error", e);
595             wsInfo.setStatus(j.getJobnum(),
596                     WebserviceInfo.STATE_STOPPED_ERROR);
597             wsInfo.appendProgressText(j.getJobnum(),
598                     MessageManager.formatMessage(
599                             "info.invalid_jnet_job_result_data",
600                             new String[]
601                             { OutputHeader.toString(), j.result.getStatus(),
602                                 e.getMessage() }));
603             j.result.setBroken(true);
604           }
605         }
606       }
607
608       if (res != null)
609       {
610         if (newWindow)
611         {
612           AlignFrame af;
613           ((AlignmentI) res[0])
614                   .setSeqrep(((AlignmentI) res[0]).getSequenceAt(0));
615           if (input == null)
616           {
617             if (res[1] != null)
618             {
619               af = new AlignFrame((Alignment) res[0],
620                       (HiddenColumns) res[1], AlignFrame.DEFAULT_WIDTH,
621                       AlignFrame.DEFAULT_HEIGHT);
622             }
623             else
624             {
625               af = new AlignFrame((Alignment) res[0],
626                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
627             }
628           }
629           else
630           {
631             /*
632              * java.lang.Object[] alandcolsel =
633              * input.getAlignmentAndColumnSelection
634              * (alignFrame.getViewport().getGapCharacter()); if
635              * (((SequenceI[])alandcolsel[0])[0].getLength()!=res.getWidth()) {
636              * if (msa) { throw new Error("Implementation Error! ColumnSelection
637              * from input alignment will not map to result alignment!"); } } if
638              * (!msa) { // update hidden regions to account for loss of gaps in
639              * profile. - if any // gapMap returns insert list, interpreted as
640              * delete list by pruneDeletions //((ColumnSelection)
641              * alandcolsel[1]).pruneDeletions(ShiftList.parseMap(((SequenceI[])
642              * alandcolsel[0])[0].gapMap())); }
643              */
644
645             af = new AlignFrame((Alignment) res[0], (HiddenColumns) res[1],
646                     AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
647           }
648           Desktop.addInternalFrame(af, altitle, AlignFrame.DEFAULT_WIDTH,
649                   AlignFrame.DEFAULT_HEIGHT);
650         }
651         else
652         {
653           Cache.info("Append results onto existing alignment.");
654         }
655       }
656     }
657   }
658
659   @Override
660   public void pollJob(AWsJob job) throws Exception
661   {
662     ((JPredJob) job).result = server.getresult(job.getJobId());
663   }
664
665   @Override
666   public boolean isCancellable()
667   {
668     return false;
669   }
670
671   @Override
672   public void cancelJob()
673   {
674     throw new Error(MessageManager.getString("error.implementation_error"));
675   }
676
677   @Override
678   public boolean canMergeResults()
679   {
680     return false;
681   }
682
683 }