JAL-845 code/refactoring/tests related to linking DNA and protein
[jalview.git] / src / jalview / ws / AWSThread.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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;
22
23 import jalview.bin.Cache;
24 import jalview.datamodel.AlignedCodonFrame;
25 import jalview.datamodel.Alignment;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.AlignmentView;
28 import jalview.datamodel.SequenceI;
29 import jalview.gui.AlignFrame;
30 import jalview.gui.FeatureRenderer.FeatureRendererSettings;
31 import jalview.gui.WebserviceInfo;
32 import jalview.util.MessageManager;
33
34 import java.util.LinkedHashSet;
35 import java.util.Set;
36
37 public abstract class AWSThread extends Thread
38 {
39
40   /**
41    * view that this job was associated with
42    */
43   protected AlignmentI currentView = null;
44
45   /**
46    * feature settings from view that job was associated with
47    */
48   protected FeatureRendererSettings featureSettings = null;
49
50   /**
51    * metadata about this web service
52    */
53   protected WebserviceInfo wsInfo = null;
54
55   /**
56    * original input data for this job
57    */
58   protected AlignmentView input = null;
59
60   /**
61    * dataset sequence relationships to be propagated onto new results
62    */
63   protected Set<AlignedCodonFrame> codonframe = null;
64
65   /**
66    * are there jobs still running in this thread.
67    */
68   protected boolean jobComplete = false;
69
70   /**
71    * one or more jobs being managed by this thread.
72    */
73   protected AWsJob jobs[] = null;
74
75   /**
76    * full name of service
77    */
78   protected String WebServiceName = null;
79
80   protected char defGapChar = '-';
81
82   /**
83    * header prepended to all output from job
84    */
85   protected String OutputHeader;
86
87   /**
88    * only used when reporting a web service out of memory error - the job ID
89    * will be concatenated to the URL
90    */
91   protected String WsUrl = null;
92
93   /**
94    * generic web service job/subjob poll loop
95    */
96   public void run()
97   {
98     JobStateSummary jstate = null;
99     if (jobs == null)
100     {
101       jobComplete = true;
102     }
103     while (!jobComplete)
104     {
105       jstate = new JobStateSummary();
106       for (int j = 0; j < jobs.length; j++)
107       {
108
109         if (!jobs[j].submitted && jobs[j].hasValidInput())
110         {
111           StartJob(jobs[j]);
112         }
113
114         if (jobs[j].submitted && !jobs[j].subjobComplete)
115         {
116           try
117           {
118             pollJob(jobs[j]);
119             if (!jobs[j].hasResponse())
120             {
121               throw (new Exception(
122                       "Timed out when communicating with server\nTry again later.\n"));
123             }
124             jalview.bin.Cache.log.debug("Job " + j + " Result state "
125                     + jobs[j].getState() + "(ServerError="
126                     + jobs[j].isServerError() + ")");
127           } catch (Exception ex)
128           {
129             // Deal with Transaction exceptions
130             wsInfo.appendProgressText(jobs[j].jobnum, 
131                         MessageManager.formatMessage("info.server_exception", new String[]{WebServiceName,ex.getMessage()}));
132             // always output the exception's stack trace to the log
133             Cache.log.warn(WebServiceName + " job(" + jobs[j].jobnum
134                     + ") Server exception.");
135             // todo: could limit trace to cause if this is a SOAPFaultException.
136             ex.printStackTrace();
137
138             if (jobs[j].allowedServerExceptions > 0)
139             {
140               jobs[j].allowedServerExceptions--;
141               Cache.log.debug("Sleeping after a server exception.");
142               try
143               {
144                 Thread.sleep(5000);
145               } catch (InterruptedException ex1)
146               {
147               }
148             }
149             else
150             {
151               Cache.log.warn("Dropping job " + j + " " + jobs[j].jobId);
152               jobs[j].subjobComplete = true;
153               wsInfo.setStatus(jobs[j].jobnum,
154                       WebserviceInfo.STATE_STOPPED_SERVERERROR);
155             }
156           } catch (OutOfMemoryError er)
157           {
158             jobComplete = true;
159             jobs[j].subjobComplete = true;
160             jobs[j].clearResponse(); // may contain out of date result data
161             wsInfo.setStatus(jobs[j].jobnum,
162                     WebserviceInfo.STATE_STOPPED_ERROR);
163             Cache.log.error("Out of memory when retrieving Job " + j
164                     + " id:" + WsUrl + "/" + jobs[j].jobId, er);
165             new jalview.gui.OOMWarning("retrieving result for "
166                     + WebServiceName, er);
167             System.gc();
168           }
169         }
170         jstate.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
171       }
172       // Decide on overall state based on collected jobs[] states
173       updateGlobalStatus(jstate);
174       if (!jobComplete)
175       {
176         try
177         {
178           Thread.sleep(5000);
179         } catch (InterruptedException e)
180         {
181           Cache.log
182                   .debug("Interrupted sleep waiting for next job poll.", e);
183         }
184         // System.out.println("I'm alive "+alTitle);
185       }
186     }
187     if (jobComplete && jobs != null)
188     {
189       parseResult(); // tidy up and make results available to user
190     }
191     else
192     {
193       Cache.log
194               .debug("WebServiceJob poll loop finished with no jobs created.");
195       wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
196       wsInfo.appendProgressText(MessageManager.getString("info.no_jobs_ran"));
197       wsInfo.setFinishedNoResults();
198     }
199   }
200
201   protected void updateGlobalStatus(JobStateSummary jstate)
202   {
203     if (jstate.running > 0)
204     {
205       wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
206     }
207     else if (jstate.queuing > 0)
208     {
209       wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
210     }
211     else
212     {
213       jobComplete = true;
214       if (jstate.finished > 0)
215       {
216         wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
217       }
218       else if (jstate.error > 0)
219       {
220         wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
221       }
222       else if (jstate.serror > 0)
223       {
224         wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
225       }
226     }
227   }
228
229   public AWSThread()
230   {
231     super();
232   }
233
234   public AWSThread(Runnable target)
235   {
236     super(target);
237   }
238
239   public AWSThread(String name)
240   {
241     super(name);
242   }
243
244   public AWSThread(ThreadGroup group, Runnable target)
245   {
246     super(group, target);
247   }
248
249   public AWSThread(ThreadGroup group, String name)
250   {
251     super(group, name);
252   }
253
254   public AWSThread(Runnable target, String name)
255   {
256     super(target, name);
257   }
258
259   public AWSThread(ThreadGroup group, Runnable target, String name)
260   {
261     super(group, target, name);
262   }
263
264   /**
265    * query web service for status of job. on return, job.result must not be null
266    * - if it is then it will be assumed that the job status query timed out and
267    * a server exception will be logged.
268    * 
269    * @param job
270    * @throws Exception
271    *           will be logged as a server exception for this job
272    */
273   public abstract void pollJob(AWsJob job) throws Exception;
274
275   /**
276    * submit job to web service
277    * 
278    * @param job
279    */
280   public abstract void StartJob(AWsJob job);
281
282   /**
283    * process the set of AWsJob objects into a set of results, and tidy up.
284    */
285   public abstract void parseResult();
286
287   /**
288    * helper function to conserve dataset references to sequence objects returned
289    * from web services 1. Propagates AlCodonFrame data from
290    * <code>codonframe</code> to <code>al</code> TODO: refactor to datamodel
291    * 
292    * @param al
293    */
294   public void propagateDatasetMappings(Alignment al)
295   {
296     if (codonframe != null)
297     {
298       SequenceI[] alignment = al.getSequencesArray();
299       for (int sq = 0; sq < alignment.length; sq++)
300       {
301         for (AlignedCodonFrame acf : codonframe)
302         {
303           final SequenceI seq = alignment[sq];
304           if (acf != null && acf.involvesSequence(seq))
305           {
306             al.addCodonFrame(acf);
307             break;
308           }
309         }
310       }
311     }
312   }
313
314   public AWSThread(ThreadGroup group, Runnable target, String name,
315           long stackSize)
316   {
317     super(group, target, name, stackSize);
318   }
319
320   /**
321    * 
322    * @return gap character to use for any alignment generation
323    */
324   public char getGapChar()
325   {
326     return defGapChar;
327   }
328
329   /**
330    * 
331    * @param alignFrame
332    *          reference for copying mappings across
333    * @param wsInfo
334    *          gui attachment point
335    * @param input
336    *          input data for the calculation
337    * @param webServiceName
338    *          name of service
339    * @param wsUrl
340    *          url of the service being invoked
341    */
342   public AWSThread(AlignFrame alignFrame, WebserviceInfo wsinfo,
343           AlignmentView input, String webServiceName, String wsUrl)
344   {
345     this(alignFrame, wsinfo, input, wsUrl);
346     WebServiceName = webServiceName;
347   }
348
349   /**
350    * Extracts additional info from alignment view's context.
351    * 
352    * @param alframe
353    *          - reference for copying mappings and display styles across
354    * @param wsinfo2
355    *          - gui attachment point - may be null
356    * @param alview
357    *          - input data for the calculation
358    * @param wsurl2
359    *          - url of the service being invoked
360    */
361   public AWSThread(AlignFrame alframe, WebserviceInfo wsinfo2,
362           AlignmentView alview, String wsurl2)
363   {
364     super();
365     // this.alignFrame = alframe;
366     currentView = alframe.getCurrentView().getAlignment();
367     featureSettings = alframe.getFeatureRenderer().getSettings();
368     defGapChar = alframe.getViewport().getGapCharacter();
369     this.wsInfo = wsinfo2;
370     this.input = alview;
371     WsUrl = wsurl2;
372     if (alframe != null)
373     {
374       Set<AlignedCodonFrame> cf = alframe.getViewport().getAlignment()
375               .getCodonFrames();
376       if (cf != null)
377       {
378         codonframe = new LinkedHashSet<AlignedCodonFrame>();
379         codonframe.addAll(cf);
380       }
381     }
382   }
383 }