added SeqSearch service interface. Documented and refactored web service client ...
[jalview.git] / src / jalview / ws / WSThread.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.ws;
20
21 import javax.swing.*;
22
23 import jalview.bin.*;
24 import jalview.datamodel.*;
25 import jalview.gui.*;
26 import jalview.gui.FeatureRenderer.FeatureRendererSettings;
27
28 public abstract class WSThread
29     extends Thread
30 {
31   /**
32    * Generic properties for Web Service Client threads.
33    */
34   /**
35    * view that this job was associated with
36    */
37   AlignmentI currentView = null;
38   /**
39    * feature settings from view that job was associated with
40    */
41   FeatureRendererSettings featureSettings = null;
42   /**
43    * metadata about this web service
44    */
45   WebserviceInfo wsInfo = null;
46   /**
47    * original input data for this job
48    */
49   AlignmentView input = null;
50   /**
51    * dataset sequence relationships to be propagated onto new results
52    */
53   AlignedCodonFrame[] codonframe = null;
54   /**
55    * are there jobs still running in this thread.
56    */
57   boolean jobComplete = false;
58   
59   abstract class WSJob
60   {
61     /**
62      * Generic properties for an individual job within a Web Service Client thread
63      */
64     int jobnum = 0; // WebServiceInfo pane for this job
65     String jobId; // ws job ticket
66     /**
67      * has job been cancelled
68      */
69     boolean cancelled = false;
70     /**
71      * number of exceptions left before job dies
72      */
73     int allowedServerExceptions = 3;
74     /**
75      * has job been submitted
76      */
77     boolean submitted = false;
78     /**
79      * are all sub-jobs complete
80      */
81     boolean subjobComplete = false;
82     /**
83      *
84      * @return true if job has completed and valid results are available
85      */
86     abstract boolean hasResults();
87
88     /**
89      *
90      * @return boolean true if job can be submitted.
91      */
92     abstract boolean hasValidInput();
93
94     /**
95      * The last result object returned by the service.
96      */
97     vamsas.objects.simple.Result result;
98   }
99
100   class JobStateSummary
101   {
102     /**
103      * number of jobs running
104      */
105     int running = 0;
106     /**
107      * number of jobs queued
108      */
109     int queuing = 0;
110     /**
111      * number of jobs finished
112      */
113     int finished = 0;
114     /**
115      * number of jobs failed
116      */
117     int error = 0;
118     /**
119      * number of jobs stopped due to server error
120      */
121     int serror = 0;
122     /**
123      * number of jobs cancelled
124      */
125     int cancelled = 0;
126     /**
127      * number of jobs finished with results
128      */
129     int results = 0;
130     /**
131      * processes WSJob and updates job status counters and WebService status displays
132      * @param wsInfo
133      * @param OutputHeader
134      * @param j
135      */
136     void updateJobPanelState(WebserviceInfo wsInfo, String OutputHeader,
137                              WSJob j)
138     {
139       if (j.result != null)
140       {
141         String progheader = "";
142         // Parse state of job[j]
143         if (j.result.isRunning())
144         {
145           running++;
146           wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_RUNNING);
147         }
148         else if (j.result.isQueued())
149         {
150           queuing++;
151           wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_QUEUING);
152         }
153         else if (j.result.isFinished())
154         {
155           finished++;
156           j.subjobComplete = true;
157           if (j.hasResults())
158           {
159             results++;
160           }
161           wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_OK);
162         }
163         else if (j.result.isFailed())
164         {
165           progheader += "Job failed.\n";
166           j.subjobComplete = true;
167           wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);
168           error++;
169         }
170         else if (j.result.isServerError())
171         {
172           serror++;
173           j.subjobComplete = true;
174           wsInfo.setStatus(j.jobnum,
175                            WebserviceInfo.STATE_STOPPED_SERVERERROR);
176         }
177         else if (j.result.isBroken() || j.result.isFailed())
178         {
179           error++;
180           j.subjobComplete = true;
181           wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);
182         }
183         // and pass on any sub-job messages to the user
184         wsInfo.setProgressText(j.jobnum, OutputHeader);
185         wsInfo.appendProgressText(j.jobnum, progheader);
186         if (j.result.getStatus() != null)
187         {
188           wsInfo.appendProgressText(j.jobnum, j.result.getStatus());
189         }
190       }
191       else
192       {
193         if (j.submitted && j.subjobComplete)
194         {
195           if (j.allowedServerExceptions == 0)
196           {
197             serror++;
198           }
199           else if (j.result == null)
200           {
201             error++;
202           }
203         }
204       }
205     }
206   }
207   /**
208    * one or more jobs being managed by this thread.
209    */
210   WSJob jobs[] = null;
211   /**
212    * full name of service
213    */
214   String WebServiceName = null;
215   String OutputHeader;
216   String WsUrl = null;
217   /**
218    * query web service for status of job.
219    * on return, job.result must not be null - if it is then it will be
220    * assumed that the job status query timed out and a server exception
221    * will be logged.
222    * @param job
223    * @throws Exception will be logged as a server exception for this job
224    */
225   abstract void pollJob(WSJob job)
226       throws Exception;
227
228   public void run()
229   {
230     JobStateSummary jstate = null;
231     if (jobs == null)
232     {
233       jobComplete = true;
234     }
235     while (!jobComplete)
236     {
237       jstate = new JobStateSummary();
238       for (int j = 0; j < jobs.length; j++)
239       {
240
241         if (!jobs[j].submitted && jobs[j].hasValidInput())
242         {
243           StartJob(jobs[j]);
244         }
245
246         if (jobs[j].submitted && !jobs[j].subjobComplete)
247         {
248           try
249           {
250             pollJob(jobs[j]);
251             if (jobs[j].result == null)
252             {
253               throw (new Exception(
254                   "Timed out when communicating with server\nTry again later.\n"));
255             }
256             jalview.bin.Cache.log.debug("Job " + j + " Result state " +
257                                         jobs[j].result.getState()
258                                         + "(ServerError=" +
259                                         jobs[j].result.isServerError() + ")");
260           }
261           catch (Exception ex)
262           {
263             // Deal with Transaction exceptions
264             wsInfo.appendProgressText(jobs[j].jobnum, "\n" + WebServiceName
265                                       + " Server exception!\n" + ex.getMessage());
266             Cache.log.warn(WebServiceName + " job(" + jobs[j].jobnum
267                            + ") Server exception: " + ex.getMessage());
268
269             if (jobs[j].allowedServerExceptions > 0)
270             {
271               jobs[j].allowedServerExceptions--;
272               Cache.log.debug("Sleeping after a server exception.");
273               try
274               {
275                 Thread.sleep(5000);
276               }
277               catch (InterruptedException ex1)
278               {
279               }
280             }
281             else
282             {
283               Cache.log.warn("Dropping job " + j + " " + jobs[j].jobId);
284               jobs[j].subjobComplete = true;
285               wsInfo.setStatus(jobs[j].jobnum,
286                                WebserviceInfo.STATE_STOPPED_SERVERERROR);
287             }
288           }
289           catch (OutOfMemoryError er)
290           {
291             jobComplete = true;
292             jobs[j].subjobComplete = true;
293             jobs[j].result = null; // may contain out of date result object
294             wsInfo.setStatus(jobs[j].jobnum,
295                              WebserviceInfo.STATE_STOPPED_ERROR);
296             JOptionPane
297                 .showInternalMessageDialog(
298                     Desktop.desktop,
299                     "Out of memory handling result for job !!"
300                     +
301                     "\nSee help files for increasing Java Virtual Machine memory.",
302                     "Out of memory", JOptionPane.WARNING_MESSAGE);
303             Cache.log.error("Out of memory when retrieving Job " + j + " id:" +
304                             WsUrl + "/" + jobs[j].jobId, er);
305             System.gc();
306           }
307         }
308         jstate.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
309       }
310       // Decide on overall state based on collected jobs[] states
311       if (jstate.running > 0)
312       {
313         wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
314       }
315       else if (jstate.queuing > 0)
316       {
317         wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
318       }
319       else
320       {
321         jobComplete = true;
322         if (jstate.finished > 0)
323         {
324           wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
325         }
326         else if (jstate.error > 0)
327         {
328           wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
329         }
330         else if (jstate.serror > 0)
331         {
332           wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
333         }
334       }
335       if (!jobComplete)
336       {
337         try
338         {
339           Thread.sleep(5000);
340         }
341         catch (InterruptedException e)
342         {
343           Cache.log.debug("Interrupted sleep waiting for next job poll.", e);
344         }
345         // System.out.println("I'm alive "+alTitle);
346       }
347     }
348     if (jobComplete && jobs != null)
349     {
350       parseResult(); // tidy up and make results available to user
351     }
352     else
353     {
354       Cache.log.debug("WebServiceJob poll loop finished with no jobs created.");
355       wsInfo.setFinishedNoResults();
356     }
357   }
358
359   /**
360    * submit job to web service
361    * @param job
362    */
363   abstract void StartJob(WSJob job);
364
365   /**
366    * process the set of WSJob objects into a set of results, and tidy up.
367    */
368   abstract void parseResult();
369
370   /**
371    * helper function to conserve dataset references to sequence objects returned from web services
372    * 1. Propagates AlCodonFrame data from <code>codonframe</code> to <code>al</code>
373    * @param al
374    */
375   protected void propagateDatasetMappings(Alignment al)
376   {
377     if (codonframe!=null)
378     {
379       SequenceI[] alignment = al.getSequencesArray();
380       for (int sq = 0; sq<alignment.length; sq++)
381       {
382         for (int i=0; i<codonframe.length; i++)
383         {
384           if (codonframe[i]!=null &&
385                   codonframe[i].involvesSequence(alignment[sq]))
386           {
387             al.addCodonFrame(codonframe[i]);
388             codonframe[i] = null;
389             break;
390           }
391         }
392       }
393     }
394   }
395
396   /**
397    * 
398    * @param alignFrame reference for copying mappings across
399    * @param wsInfo gui attachment point
400    * @param input input data for the calculation
401    * @param webServiceName name of service
402    * @param wsUrl  url of the service being invoked
403    */
404   public WSThread(AlignFrame alignFrame, WebserviceInfo wsinfo,
405           AlignmentView input, String webServiceName,
406           String wsUrl)
407   {
408     this(alignFrame, wsinfo, input, wsUrl);
409     WebServiceName = webServiceName;
410   }
411   char defGapChar = '-';
412   /**
413    * 
414    * @return gap character to use for any alignment generation
415    */
416   public char getGapChar()
417   {
418     return defGapChar;
419   }
420
421   /**
422    * 
423    * @param alframe - reference for copying mappings and display styles across
424    * @param wsinfo2 - gui attachment point
425    * @param alview - input data for the calculation
426    * @param wsurl2 - url of the service being invoked
427    */
428   public WSThread(AlignFrame alframe, WebserviceInfo wsinfo2,
429           AlignmentView alview, String wsurl2)
430   {
431     super();
432     // this.alignFrame = alframe;
433     currentView = alframe.getCurrentView().getAlignment();
434     featureSettings = alframe.getFeatureRenderer().getSettings();
435     defGapChar = alframe.getViewport().getGapCharacter();
436     this.wsInfo = wsinfo2;
437     this.input = alview;
438     WsUrl = wsurl2;
439     if (alframe!=null)
440     {
441       AlignedCodonFrame[] cf = alframe.getViewport().getAlignment().getCodonFrames();
442       if (cf!=null)
443       {
444         codonframe = new AlignedCodonFrame[cf.length];
445         System.arraycopy(cf, 0, codonframe, 0, cf.length);
446       }
447     }
448   }
449 }