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