Merge branch 'improvement/JAL-3848_slivka_0.8' into alpha/JAL-3066_Jalview_212_slivka...
[jalview.git] / src / jalview / ws / jws2 / JPredThread.java
1 package jalview.ws.jws2;
2
3 import static java.lang.String.format;
4
5 import java.util.Hashtable;
6 import java.util.List;
7
8 import jalview.analysis.SeqsetUtils;
9 import jalview.bin.Cache;
10 import jalview.commands.RemoveGapsCommand;
11 import jalview.datamodel.Alignment;
12 import jalview.datamodel.AlignmentAnnotation;
13 import jalview.datamodel.AlignmentI;
14 import jalview.datamodel.AlignmentView;
15 import jalview.datamodel.HiddenColumns;
16 import jalview.datamodel.SequenceI;
17 import jalview.gui.AlignFrame;
18 import jalview.gui.Desktop;
19 import jalview.gui.WebserviceInfo;
20 import jalview.io.JnetAnnotationMaker;
21 import jalview.util.MessageManager;
22 import jalview.ws.AWSThread;
23 import jalview.ws.AWsJob;
24 import jalview.ws.JobStateSummary;
25 import jalview.ws.WSClientI;
26 import jalview.ws.api.CancellableI;
27 import jalview.ws.api.JPredServiceI;
28 import jalview.ws.gui.WsJob;
29 import jalview.ws.gui.WsJob.JobState;
30
31
32 public class JPredThread extends AWSThread implements WSClientI
33 {
34
35   private static class JPredJob extends WsJob
36   {
37     private final Hashtable<?, ?> sequenceInfo;
38     private final List<SequenceI> msf;
39     private final int[] delMap;
40     private AlignmentI alignment = null;
41     private HiddenColumns hiddenCols = null;
42
43     private JPredJob(Hashtable<?, ?> sequenceInfo, SequenceI[] msf, int[] delMap)
44     {
45       this.sequenceInfo = sequenceInfo;
46       this.msf = List.of(msf);
47       this.delMap = delMap;
48     }
49
50     @Override
51     public boolean hasValidInput()
52     {
53       return true;
54     }
55
56     @Override
57     public boolean hasResults()
58     {
59       return (isSubjobComplete() && alignment != null);
60     }
61
62     public boolean isMSA()
63     {
64       return msf.size() > 1;
65     }
66   }
67
68
69   private JPredServiceI server;
70   private String title;
71   private Hashtable<?, ?> sequenceInfo;
72   private SequenceI[] msf;
73   private int[] delMap;
74
75   public JPredThread(WebserviceInfo wsInfo, String title,
76       JPredServiceI server, Hashtable<?, ?> sequenceInfo,
77       SequenceI[] msf, int[] delMap, AlignmentView view, AlignFrame frame,
78       String wsURL)
79   {
80     super(frame, wsInfo, view, wsURL);
81     this.server = server;
82     this.title = title;
83     this.sequenceInfo = sequenceInfo;
84     this.msf = msf;
85     this.delMap = delMap;
86     JPredJob job = new JPredJob(sequenceInfo, msf, delMap);
87     this.jobs = new JPredJob[] { job };
88   }
89
90   @Override
91   public boolean isCancellable()
92   {
93     return server instanceof CancellableI;
94   }
95
96   @Override
97   public boolean canMergeResults()
98   {
99     return false;
100   }
101
102   @Override
103   public void cancelJob()
104   {
105     // TODO Auto-generated method stub
106
107   }
108
109   @Override
110   public void pollJob(AWsJob job_) throws Exception
111   {
112     var job = (JPredJob) job_;
113     server.updateStatus(job);
114     server.updateJobProgress(job);
115   }
116
117   @Override
118   public void StartJob(AWsJob job_)
119   {
120     if (!(job_ instanceof JPredJob))
121       throw new RuntimeException("Invalid job type");
122     var job = (JPredJob) job_;
123     if (job.isSubmitted())
124     {
125       return;
126     }
127     try {
128       try
129       {
130         var jobHandle = server.predict(job.msf, job.isMSA());
131         if (jobHandle != null)
132           job.setJobHandle(jobHandle);
133       }
134       catch (Throwable th) {
135         if (!server.handleSubmitError(th, job, wsInfo)) {
136           throw th;
137         }
138       }
139       if (job.getJobId() != null) {
140         job.setSubmitted(true);
141         job.setSubjobComplete(false);
142         return;
143       }
144       else {
145         throw new Exception(MessageManager.formatMessage(
146                 "exception.web_service_returned_null_try_later",
147                 new String[]
148                 { WsUrl }));
149       }
150     }
151     catch (Throwable th)
152     {
153       // For unexpected errors
154       System.err.println(WebServiceName
155               + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
156               + "When contacting Server:" + WsUrl + "\n");
157       th.printStackTrace(System.err);
158       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
159       wsInfo.setStatus(job.getJobnum(),
160               WebserviceInfo.STATE_STOPPED_SERVERERROR);
161
162     }
163     finally
164     {
165       if (!job.isSubmitted())
166       {
167         job.setAllowedServerExceptions(0);
168         wsInfo.appendProgressText(job.getJobnum(), MessageManager.getString(
169                 "info.failed_to_submit_sequences_for_alignment"));
170       }
171     }
172   }
173
174   @Override
175   public void parseResult()
176   {
177     long progbar = (long) (Math.random() * ~(1L << 63));
178     wsInfo.setProgressBar(
179         MessageManager.getString("status.collecting_job_results"), progbar);
180     int results = 0;
181     var finalState = new JobStateSummary();
182     try
183     {
184       for (int i = 0; i < jobs.length; i++) {
185         final var job = (JPredJob) jobs[i];
186         finalState.updateJobPanelState(wsInfo, OutputHeader, job);
187         if (job.isFinished()) {
188           try {
189             server.updateJobProgress(job);
190           }
191           catch (Exception e) {
192             Cache.log.warn(format(
193                 "Exception when retrieving remaining Job progress data " +
194                 "for job %s on server %s", job.getJobId(), WsUrl));
195             e.printStackTrace();
196           }
197           // removed the waiting loop
198           Cache.log.debug(format("Job Execution file for job: %s " +
199               "on server %s%n%s", job.getJobId(), WsUrl, job.getStatus()));
200           try {
201             prepareJobResult(job);
202           }
203           catch (Exception e) {
204             if (!server.handleCollectionException(e, job, wsInfo)) {
205               Cache.log.error("Could not get alignment for job.", e);
206               job.setState(JobState.SERVERERROR);
207             }
208           }
209         }
210         finalState.updateJobPanelState(wsInfo, OutputHeader, job);
211         if (job.isSubmitted() && job.isSubjobComplete() && job.hasResults()) {
212           results++;
213         }
214       }
215     }
216     catch (Exception e) {
217       Cache.log.error(
218           "Unexpected exception when processing results for " + title, e);
219       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
220     }
221     if (results > 0) {
222       wsInfo.showResultsNewFrame.addActionListener(
223           (evt) -> displayResults(true));
224       wsInfo.mergeResults.addActionListener(
225           (evt) -> displayResults(false));
226       wsInfo.setResultsReady();
227     }
228     else {
229       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
230       wsInfo.appendInfoText("No jobs ran.");
231       wsInfo.setFinishedNoResults();
232     }
233     updateGlobalStatus(finalState);
234     wsInfo.removeProgressBar(progbar);
235   }
236
237   static final int msaIndex = 0;
238
239   private void prepareJobResult(JPredJob job) throws Exception
240   {
241     HiddenColumns hiddenCols = null;
242     int firstSeq = -1;
243     AlignmentI alignment;
244     var prediction = server.getPrediction(job.getJobHandle());
245     var preds = prediction.getSeqsAsArray();
246
247     if (job.msf.size() > 1)
248     {
249       if (job.delMap != null)
250       {
251         Object[] alandcolsel = input
252                 .getAlignmentAndHiddenColumns(getGapChar());
253         alignment = new Alignment((SequenceI[]) alandcolsel[0]);
254         hiddenCols = (HiddenColumns) alandcolsel[1];
255       }
256       else
257       {
258         alignment = server.getAlignment(job.getJobHandle());
259         var seqs = new SequenceI[alignment.getHeight()];
260         for (int i = 0; i < alignment.getHeight(); i++)
261         {
262           seqs[i] = alignment.getSequenceAt(i);
263         }
264         if (!SeqsetUtils.deuniquify(sequenceInfo, seqs))
265         {
266           throw (new Exception(MessageManager.getString(
267                   "exception.couldnt_recover_sequence_properties_for_alignment")));
268         }
269       }
270       firstSeq = 0;
271       if (currentView.getDataset() != null)
272       {
273         alignment.setDataset(currentView.getDataset());
274       }
275       else
276       {
277         alignment.setDataset(null);
278       }
279       JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, false,
280               job.delMap);
281     }
282     else
283     {
284       alignment = new Alignment(preds);
285       firstSeq = prediction.getQuerySeqPosition();
286       if (job.delMap != null)
287       {
288         Object[] alanndcolsel = input.getAlignmentAndHiddenColumns(getGapChar());
289         SequenceI[] seqs = (SequenceI[]) alanndcolsel[0];
290         new RemoveGapsCommand(MessageManager.getString("label.remove_gaps"),
291                 new SequenceI[] {seqs[msaIndex]}, currentView);
292         SequenceI profileSeq = alignment.getSequenceAt(firstSeq);
293         profileSeq.setSequence(seqs[msaIndex].getSequenceAsString());
294       }
295       if (!SeqsetUtils.SeqCharacterUnhash(
296               alignment.getSequenceAt(firstSeq), sequenceInfo))
297       {
298         throw new Exception(MessageManager.getString(
299                 "exception.couldnt_recover_sequence_props_for_jnet_query"));
300       }
301       alignment.setDataset(currentView.getDataset());
302       JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, true,
303               job.delMap);
304       SequenceI profileSeq = alignment.getSequenceAt(0);
305       alignToProfileSeq(alignment, profileSeq);
306       if (job.delMap != null)
307       {
308         hiddenCols = alignment.propagateInsertions(profileSeq, input);
309       }
310     }
311
312     for (var annot : alignment.getAlignmentAnnotation())
313     {
314       if (annot.sequenceRef != null)
315       {
316         replaceAnnotationOnAlignmentWith(annot, annot.label,
317                 "jalview.ws.JPred", annot.sequenceRef);
318       }
319     }
320     job.alignment = alignment;
321     job.hiddenCols = hiddenCols;
322   }
323
324   private static void replaceAnnotationOnAlignmentWith(
325           AlignmentAnnotation newAnnot, String typeName, String calcId,
326           SequenceI aSeq)
327   {
328     SequenceI dsseq = aSeq.getDatasetSequence();
329     while (dsseq.getDatasetSequence() != null)
330     {
331       dsseq = dsseq.getDatasetSequence();
332     }
333     // look for same annotation on dataset and lift this one over
334     List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
335             typeName);
336     if (dsan != null && dsan.size() > 0)
337     {
338       for (AlignmentAnnotation dssan : dsan)
339       {
340         dsseq.removeAlignmentAnnotation(dssan);
341       }
342     }
343     AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
344     dsseq.addAlignmentAnnotation(dssan);
345     dssan.adjustForAlignment();
346   }
347
348   private static void alignToProfileSeq(AlignmentI al, SequenceI profileseq)
349   {
350     char gc = al.getGapCharacter();
351     int[] gapMap = profileseq.gapMap();
352     // insert gaps into profile
353     for (int lp = 0, r = 0; r < gapMap.length; r++)
354     {
355       if (gapMap[r] - lp > 1)
356       {
357         StringBuffer sb = new StringBuffer();
358         for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
359         {
360           sb.append(gc);
361         }
362         for (int s = 1, ns = al.getHeight(); s < ns; s++)
363         {
364           String sq = al.getSequenceAt(s).getSequenceAsString();
365           int diff = gapMap[r] - sq.length();
366           if (diff > 0)
367           {
368             // pad gaps
369             sq = sq + sb;
370             while ((diff = gapMap[r] - sq.length()) > 0)
371             {
372               sq = sq + ((diff >= sb.length()) ? sb.toString()
373                       : sb.substring(0, diff));
374             }
375             al.getSequenceAt(s).setSequence(sq);
376           }
377           else
378           {
379             al.getSequenceAt(s).setSequence(sq.substring(0, gapMap[r])
380                     + sb.toString() + sq.substring(gapMap[r]));
381           }
382         }
383       }
384       lp = gapMap[r];
385     }
386   }
387
388   private void displayResults(boolean newWindow)
389   {
390     if (jobs == null || jobs.length == 0)
391     {
392       return;
393     }
394     var job = (JPredJob) jobs[0];
395     if (job.hasResults() && newWindow)
396     {
397       job.alignment.setSeqrep(job.alignment.getSequenceAt(0));
398       AlignFrame frame = new AlignFrame(job.alignment, job.hiddenCols,
399               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
400       Desktop.addInternalFrame(frame, title, AlignFrame.DEFAULT_WIDTH,
401               AlignFrame.DEFAULT_HEIGHT);
402     }
403   }
404
405 }