1 package jalview.ws.rest;
3 import jalview.bin.Cache;
4 import jalview.datamodel.AlignmentI;
5 import jalview.datamodel.ColumnSelection;
6 import jalview.gui.AlignFrame;
7 import jalview.gui.WebserviceInfo;
8 import jalview.io.packed.DataProvider;
9 import jalview.io.packed.JalviewDataset;
10 import jalview.io.packed.ParsePackedSet;
11 import jalview.io.packed.SimpleDataProvider;
12 import jalview.io.packed.DataProvider.JvDataType;
13 import jalview.ws.AWSThread;
14 import jalview.ws.AWsJob;
16 import java.awt.Desktop;
17 import java.awt.event.ActionEvent;
18 import java.awt.event.ActionListener;
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Map.Entry;
24 import org.apache.axis.transport.http.HTTPConstants;
25 import org.apache.http.Header;
26 import org.apache.http.HttpEntity;
27 import org.apache.http.HttpResponse;
28 import org.apache.http.client.ClientProtocolException;
29 import org.apache.http.client.methods.HttpGet;
30 import org.apache.http.client.methods.HttpPost;
31 import org.apache.http.client.methods.HttpRequestBase;
32 import org.apache.http.entity.mime.HttpMultipartMode;
33 import org.apache.http.entity.mime.MultipartEntity;
34 import org.apache.http.impl.client.DefaultHttpClient;
35 import org.apache.http.protocol.BasicHttpContext;
36 import org.apache.http.protocol.HttpContext;
37 import org.apache.http.util.EntityUtils;
39 public class RestJobThread extends AWSThread
46 protected RestClient restClient;;
48 public RestJobThread(RestClient restClient)
51 this.restClient = restClient; // may not be needed
53 // minimal job - submit given input and parse result onto alignment as
54 // annotation/whatever
56 // look for tree data, etc.
58 // for moment do following job type only
59 // input=visiblealignment,groupsindex
60 // ie one subjob using groups defined on alignment.
61 if (!restClient.service.isHseparable())
63 jobs = new RestJob[1];
64 jobs[0] = new RestJob(0, this,
65 restClient._input.getVisibleAlignment(restClient.service
67 restClient._input.getVisibleContigs());
68 // need a function to get a range on a view/alignment and return both
69 // annotation, groups and selection subsetted to just that region.
74 int[] viscontig = restClient._input.getVisibleContigs();
75 AlignmentI[] viscontigals = restClient._input
76 .getVisibleContigAlignments(restClient.service
78 if (viscontigals != null && viscontigals.length > 0)
80 jobs = new RestJob[viscontigals.length];
81 for (int j = 0; j < jobs.length; j++)
83 int[] visc = new int[]
84 { viscontig[j * 2], viscontig[j * 2 + 1] };
87 jobs[j] = new RestJob(j, this, viscontigals[j], visc);
91 jobs[j] = new RestJob(0, this, viscontigals[j], visc);
98 * subjob types row based: per sequence in alignment/selected region { input
99 * is one sequence or sequence ID } per alignment/selected region { input is
100 * set of sequences, alignment, one or more sets of sequence IDs,
103 if (!restClient.service.isHseparable())
106 // create a bunch of subjobs per visible contig to ensure result honours
108 // TODO: determine if a 'soft' hSeperable property is needed - e.g. if
109 // user does SS-pred on sequence with big hidden regions, its going to be
114 // create a single subjob for the visible/selected input
117 // TODO: decide if vSeperable exists: eg. column wide analysis where hidden
118 // rows do not affect output - generally no analysis that generates
119 // alignment annotation is vSeparable -
123 * create gui components for monitoring jobs
125 * @param webserviceInfo
127 public void setWebServiceInfo(WebserviceInfo webserviceInfo)
129 wsInfo = webserviceInfo;
130 for (int j = 0; j < jobs.length; j++)
133 // Copy over any per job params
136 wsInfo.setProgressName("region " + jobs[j].getJobnum(),
137 jobs[j].getJobnum());
141 wsInfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
146 private String getStage(Stage stg)
148 if (stg == Stage.SUBMIT)
149 return "submitting ";
150 if (stg == Stage.POLL)
151 return "checking status of ";
153 return (" being confused about ");
156 private void doPoll(RestJob rj) throws Exception
158 String postUrl = rj.getPollUrl();
159 doHttpReq(Stage.POLL, rj, postUrl);
163 * construct the post and handle the response.
167 public void doPost(RestJob rj) throws Exception
169 String postUrl = rj.getPostUrl();
170 doHttpReq(Stage.SUBMIT, rj, postUrl);
174 * do the HTTP request - and respond/set statuses appropriate to the current
179 * - provides any data needed for posting and used to record state
181 * - actual URL to post/get from
184 protected void doHttpReq(Stage stg, RestJob rj, String postUrl)
187 StringBuffer respText = new StringBuffer();
188 // con.setContentHandlerFactory(new
189 // jalview.ws.io.mime.HttpContentHandler());
190 HttpRequestBase request = null;
191 String messages = "";
192 if (stg == Stage.SUBMIT)
195 // http://evgenyg.wordpress.com/2010/05/01/uploading-files-multipart-post-apache/
197 HttpPost htpost = new HttpPost(postUrl);
198 MultipartEntity postentity = new MultipartEntity(
199 HttpMultipartMode.STRICT);
200 for (Entry<String, InputType> input : rj.getInputParams())
202 if (input.getValue().validFor(rj))
204 postentity.addPart(input.getKey(), input.getValue()
205 .formatForInput(rj));
209 messages += "Skipped an input (" + input.getKey()
210 + ") - Couldn't generate it from available data.";
213 htpost.setEntity(postentity);
218 request = new HttpGet(postUrl);
222 DefaultHttpClient httpclient = new DefaultHttpClient();
224 HttpContext localContext = new BasicHttpContext();
225 HttpResponse response = null;
228 response = httpclient.execute(request);
229 } catch (ClientProtocolException he)
231 rj.statMessage = "Web Protocol Exception when attempting to "
232 + getStage(stg) + "Job. See Console output for details.";
233 rj.setAllowedServerExceptions(0);// unrecoverable;
235 Cache.log.fatal("Unexpected REST Job " + getStage(stg)
236 + "exception for URL " + rj.rsd.postUrl);
238 } catch (IOException e)
240 rj.statMessage = "IO Exception when attempting to "
241 + getStage(stg) + "Job. See Console output for details.";
242 Cache.log.warn("IO Exception for REST Job " + getStage(stg)
243 + "exception for URL " + rj.rsd.postUrl);
247 switch (response.getStatusLine().getStatusCode())
251 Cache.log.debug("Processing result set.");
252 processResultSet(rj, response, request);
255 rj.statMessage = "<br>Job submitted successfully. Results available at this URL:\n"
265 if (!rj.isSubmitted()
267 .getHeaders(HTTPConstants.HEADER_LOCATION)) != null
273 .warn("Ignoring additional "
275 + " location(s) provided in response header ( next one is '"
276 + loc[1].getValue() + "' )");
278 rj.setJobId(loc[0].getValue());
279 rj.setSubmitted(true);
281 completeStatus(rj, response);
285 rj.setSubmitted(true);
286 rj.setAllowedServerExceptions(0);
287 rj.setSubjobComplete(true);
290 completeStatus(rj, response, "" + getStage(stg)
291 + "failed. Reason below:\n");
294 // Some other response. Probably need to pop up the content in a window.
295 // TODO: deal with all other HTTP response codes from server.
296 Cache.log.warn("Unhandled response status when " + getStage(stg)
297 + "for " + postUrl + ": " + response.getStatusLine());
300 response.getEntity().consumeContent();
301 } catch (IOException e)
303 Cache.log.debug("IOException when consuming unhandled response",
312 * job has completed. Something valid should be available from con
317 * is a stateless request - expected to return the same data
318 * regardless of how many times its called.
320 private void processResultSet(RestJob rj, HttpResponse con,
323 if (rj.statMessage == null)
327 rj.statMessage += "Job Complete.\n";
330 rj.resSet = new HttpResultSet(rj, con, req);
332 } catch (IOException e)
334 rj.statMessage += "Couldn't parse results. Failed.";
336 rj.gotresult = false;
340 private void completeStatus(RestJob rj, HttpResponse con)
343 completeStatus(rj, con, null);
347 private void completeStatus(RestJob rj, HttpResponse con, String prefix)
350 StringBuffer sb = new StringBuffer();
356 if (rj.statMessage != null && rj.statMessage.length() > 0)
358 sb.append(rj.statMessage);
360 HttpEntity en = con.getEntity();
362 * Just append the content as a string.
365 StringBuffer content = new StringBuffer(f = EntityUtils.toString(en));
367 int body = f.indexOf("<body");
370 content.delete(0, f.indexOf(">", body));
372 if (body > -1 && sb.length() > 0)
375 content.insert(0, sb);
379 rj.statMessage = content.toString();
383 public void pollJob(AWsJob job) throws Exception
385 assert (job instanceof RestJob);
386 System.err.println("Debug RestJob: Polling Job");
387 doPoll((RestJob) job);
391 public void StartJob(AWsJob job)
393 assert (job instanceof RestJob);
396 System.err.println("Debug RestJob: Posting Job");
397 doPost((RestJob) job);
398 } catch (Exception ex)
400 job.setSubjobComplete(true);
401 job.setAllowedServerExceptions(-1);
402 Cache.log.error("Exception when trying to start Rest Job.", ex);
407 public void parseResult()
409 // crazy users will see this message
410 // TODO: finish this! and remove the message below!
411 Cache.log.warn("Rest job result parser is currently INCOMPLETE!");
413 for (RestJob rj : (RestJob[]) jobs)
415 if (rj.hasResponse() && rj.resSet != null && rj.resSet.isValid())
420 Cache.log.debug("Parsing data for job " + rj.getJobId());
426 Cache.log.debug("Finished parsing data for job " + rj.getJobId());
430 Cache.log.warn("Failed to finish parsing data for job "
432 ex.printStackTrace();
433 } catch (Exception ex)
435 Cache.log.warn("Failed to finish parsing data for job "
437 ex.printStackTrace();
443 // add listeners and activate result display gui elements
445 * decisions based on job result content + state of alignFrame that
446 * originated the job:
449 * 1. Can/Should this job automatically open a new window for results
453 wsInfo.setViewResultsImmediatly(false);
457 // realiseResults(true, true);
459 // otherwise, should automatically view results
461 // TODO: check if at least one or more contexts are valid - if so, enable
463 wsInfo.showResultsNewFrame.addActionListener(new ActionListener()
467 public void actionPerformed(ActionEvent e)
469 realiseResults(false);
473 wsInfo.mergeResults.addActionListener(new ActionListener()
477 public void actionPerformed(ActionEvent e)
479 realiseResults(true);
484 wsInfo.setResultsReady();
488 // tell the user nothing was returned.
492 public void realiseResults(boolean merge)
495 * 2. Should the job modify the parent alignment frame/view(s) (if they
496 * still exist and the alignment hasn't been edited) in order to display new
497 * annotation/features.
500 * alignments. New alignments are added to dataset, and subsequently
501 * annotated/visualised accordingly. 1. New alignment frame created for new
502 * alignment. Decide if any vis settings should be inherited from old
503 * alignment frame (e.g. sequence ordering ?). 2. Subsequent data added to
504 * alignment as below:
507 * annotation update to original/newly created context alignment: 1.
508 * identify alignment where annotation is to be loaded onto. 2. Add
509 * annotation, excluding any duplicates. 3. Ensure annotation is visible on
510 * alignment - honouring ordering given by file.
513 * features updated to original or newly created context alignment: 1.
514 * Features are(or were already) added to dataset. 2. Feature settings
515 * modified to ensure all features are displayed - honouring any ordering
516 * given by result file. Consider merging action with the code used by the
517 * DAS fetcher to update alignment views with new info.
520 * Seq associated data files (PDB files). 1. locate seq association in
521 * current dataset/alignment context and add file as normal - keep handle of
522 * any created ref objects. 2. decide if new data should be displayed : PDB
523 * display: if alignment has PDB display already, should new pdb files be
527 jalview.gui.AlignmentPanel destPanel = null;
530 if (!restClient.isAlignmentModified())
532 destPanel = restClient.recoverAlignPanelForView();
533 if (restClient.isShowResultsInNewView())
535 destPanel = destPanel.alignFrame.newView(false);
539 if (destPanel == null)
541 Object[] idat = input.getAlignmentAndColumnSelection(restClient.av.getGapCharacter());
542 AlignFrame af = new AlignFrame((AlignmentI) idat[0],
543 (ColumnSelection) idat[1], AlignFrame.DEFAULT_WIDTH,
544 AlignFrame.DEFAULT_HEIGHT);
545 jalview.gui.Desktop.addInternalFrame(af,
546 "Results for " + restClient.service.details.Name + " "
547 + restClient.service.details.Action + " on "
548 + restClient.af.getTitle(), AlignFrame.DEFAULT_WIDTH,
549 AlignFrame.DEFAULT_HEIGHT);
550 destPanel = af.alignPanel;
551 // create totally new alignment from stashed data/results
555 for (int j = 0; j < jobs.length; j++)
557 RestJob rj = (RestJob) jobs[j];
558 if (rj.jvresultobj!=null && rj.jvresultobj.length>0) {
559 // transfer results onto panel
564 destPanel.adjustAnnotationHeight();
571 * @return true if the run method is safe to call
573 public boolean isValid()
577 for (RestJob rj : (RestJob[]) jobs)
579 if (!rj.hasValidInput())
581 // invalid input for this job
582 System.err.println("Job " + rj.getJobnum()
583 + " has invalid input.");
589 // TODO Auto-generated method stub