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.JalviewDataset.AlignmentSet;
11 import jalview.io.packed.ParsePackedSet;
12 import jalview.io.packed.SimpleDataProvider;
13 import jalview.io.packed.DataProvider.JvDataType;
14 import jalview.ws.AWSThread;
15 import jalview.ws.AWsJob;
17 import java.awt.Desktop;
18 import java.awt.event.ActionEvent;
19 import java.awt.event.ActionListener;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Map.Entry;
25 import org.apache.axis.transport.http.HTTPConstants;
26 import org.apache.http.Header;
27 import org.apache.http.HttpEntity;
28 import org.apache.http.HttpResponse;
29 import org.apache.http.client.ClientProtocolException;
30 import org.apache.http.client.methods.HttpGet;
31 import org.apache.http.client.methods.HttpPost;
32 import org.apache.http.client.methods.HttpRequestBase;
33 import org.apache.http.entity.mime.HttpMultipartMode;
34 import org.apache.http.entity.mime.MultipartEntity;
35 import org.apache.http.impl.client.DefaultHttpClient;
36 import org.apache.http.protocol.BasicHttpContext;
37 import org.apache.http.protocol.HttpContext;
38 import org.apache.http.util.EntityUtils;
40 public class RestJobThread extends AWSThread
47 protected RestClient restClient;;
49 public RestJobThread(RestClient restClient)
51 super(restClient.af, null, restClient._input,
52 restClient.service.postUrl);
53 this.restClient = restClient; // may not be needed
55 // minimal job - submit given input and parse result onto alignment as
56 // annotation/whatever
58 // look for tree data, etc.
60 // for moment do following job type only
61 // input=visiblealignment,groupsindex
62 // ie one subjob using groups defined on alignment.
63 if (!restClient.service.isHseparable())
65 jobs = new RestJob[1];
66 jobs[0] = new RestJob(0, this,
67 restClient._input.getVisibleAlignment(restClient.service
69 restClient._input.getVisibleContigs());
70 // need a function to get a range on a view/alignment and return both
71 // annotation, groups and selection subsetted to just that region.
76 int[] viscontig = restClient._input.getVisibleContigs();
77 AlignmentI[] viscontigals = restClient._input
78 .getVisibleContigAlignments(restClient.service
80 if (viscontigals != null && viscontigals.length > 0)
82 jobs = new RestJob[viscontigals.length];
83 for (int j = 0; j < jobs.length; j++)
85 int[] visc = new int[]
86 { viscontig[j * 2], viscontig[j * 2 + 1] };
89 jobs[j] = new RestJob(j, this, viscontigals[j], visc);
93 jobs[j] = new RestJob(0, this, viscontigals[j], visc);
100 * subjob types row based: per sequence in alignment/selected region { input
101 * is one sequence or sequence ID } per alignment/selected region { input is
102 * set of sequences, alignment, one or more sets of sequence IDs,
105 if (!restClient.service.isHseparable())
108 // create a bunch of subjobs per visible contig to ensure result honours
110 // TODO: determine if a 'soft' hSeperable property is needed - e.g. if
111 // user does SS-pred on sequence with big hidden regions, its going to be
116 // create a single subjob for the visible/selected input
119 // TODO: decide if vSeperable exists: eg. column wide analysis where hidden
120 // rows do not affect output - generally no analysis that generates
121 // alignment annotation is vSeparable -
125 * create gui components for monitoring jobs
127 * @param webserviceInfo
129 public void setWebServiceInfo(WebserviceInfo webserviceInfo)
131 wsInfo = webserviceInfo;
132 for (int j = 0; j < jobs.length; j++)
135 // Copy over any per job params
138 wsInfo.setProgressName("region " + jobs[j].getJobnum(),
139 jobs[j].getJobnum());
143 wsInfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
148 private String getStage(Stage stg)
150 if (stg == Stage.SUBMIT)
151 return "submitting ";
152 if (stg == Stage.POLL)
153 return "checking status of ";
155 return (" being confused about ");
158 private void doPoll(RestJob rj) throws Exception
160 String postUrl = rj.getPollUrl();
161 doHttpReq(Stage.POLL, rj, postUrl);
165 * construct the post and handle the response.
169 public void doPost(RestJob rj) throws Exception
171 String postUrl = rj.getPostUrl();
172 doHttpReq(Stage.SUBMIT, rj, postUrl);
176 * do the HTTP request - and respond/set statuses appropriate to the current
181 * - provides any data needed for posting and used to record state
183 * - actual URL to post/get from
186 protected void doHttpReq(Stage stg, RestJob rj, String postUrl)
189 StringBuffer respText = new StringBuffer();
190 // con.setContentHandlerFactory(new
191 // jalview.ws.io.mime.HttpContentHandler());
192 HttpRequestBase request = null;
193 String messages = "";
194 if (stg == Stage.SUBMIT)
197 // http://evgenyg.wordpress.com/2010/05/01/uploading-files-multipart-post-apache/
199 HttpPost htpost = new HttpPost(postUrl);
200 MultipartEntity postentity = new MultipartEntity(
201 HttpMultipartMode.STRICT);
202 for (Entry<String, InputType> input : rj.getInputParams())
204 if (input.getValue().validFor(rj))
206 postentity.addPart(input.getKey(), input.getValue()
207 .formatForInput(rj));
211 messages += "Skipped an input (" + input.getKey()
212 + ") - Couldn't generate it from available data.";
215 htpost.setEntity(postentity);
220 request = new HttpGet(postUrl);
224 DefaultHttpClient httpclient = new DefaultHttpClient();
226 HttpContext localContext = new BasicHttpContext();
227 HttpResponse response = null;
230 response = httpclient.execute(request);
231 } catch (ClientProtocolException he)
233 rj.statMessage = "Web Protocol Exception when attempting to "
234 + getStage(stg) + "Job. See Console output for details.";
235 rj.setAllowedServerExceptions(0);// unrecoverable;
237 Cache.log.fatal("Unexpected REST Job " + getStage(stg)
238 + "exception for URL " + rj.rsd.postUrl);
240 } catch (IOException e)
242 rj.statMessage = "IO Exception when attempting to "
243 + getStage(stg) + "Job. See Console output for details.";
244 Cache.log.warn("IO Exception for REST Job " + getStage(stg)
245 + "exception for URL " + rj.rsd.postUrl);
249 switch (response.getStatusLine().getStatusCode())
253 Cache.log.debug("Processing result set.");
254 processResultSet(rj, response, request);
257 rj.statMessage = "<br>Job submitted successfully. Results available at this URL:\n"
267 if (!rj.isSubmitted()
269 .getHeaders(HTTPConstants.HEADER_LOCATION)) != null
275 .warn("Ignoring additional "
277 + " location(s) provided in response header ( next one is '"
278 + loc[1].getValue() + "' )");
280 rj.setJobId(loc[0].getValue());
281 rj.setSubmitted(true);
283 completeStatus(rj, response);
287 rj.setSubmitted(true);
288 rj.setAllowedServerExceptions(0);
289 rj.setSubjobComplete(true);
292 completeStatus(rj, response, "" + getStage(stg)
293 + "failed. Reason below:\n");
296 // Some other response. Probably need to pop up the content in a window.
297 // TODO: deal with all other HTTP response codes from server.
298 Cache.log.warn("Unhandled response status when " + getStage(stg)
299 + "for " + postUrl + ": " + response.getStatusLine());
302 response.getEntity().consumeContent();
303 } catch (IOException e)
305 Cache.log.debug("IOException when consuming unhandled response",
314 * job has completed. Something valid should be available from con
319 * is a stateless request - expected to return the same data
320 * regardless of how many times its called.
322 private void processResultSet(RestJob rj, HttpResponse con,
325 if (rj.statMessage == null)
329 rj.statMessage += "Job Complete.\n";
332 rj.resSet = new HttpResultSet(rj, con, req);
334 } catch (IOException e)
336 rj.statMessage += "Couldn't parse results. Failed.";
338 rj.gotresult = false;
342 private void completeStatus(RestJob rj, HttpResponse con)
345 completeStatus(rj, con, null);
349 private void completeStatus(RestJob rj, HttpResponse con, String prefix)
352 StringBuffer sb = new StringBuffer();
358 if (rj.statMessage != null && rj.statMessage.length() > 0)
360 sb.append(rj.statMessage);
362 HttpEntity en = con.getEntity();
364 * Just append the content as a string.
367 StringBuffer content = new StringBuffer(f = EntityUtils.toString(en));
369 int body = f.indexOf("<body");
372 content.delete(0, f.indexOf(">", body));
374 if (body > -1 && sb.length() > 0)
377 content.insert(0, sb);
381 rj.statMessage = content.toString();
385 public void pollJob(AWsJob job) throws Exception
387 assert (job instanceof RestJob);
388 System.err.println("Debug RestJob: Polling Job");
389 doPoll((RestJob) job);
393 public void StartJob(AWsJob job)
395 assert (job instanceof RestJob);
398 System.err.println("Debug RestJob: Posting Job");
399 doPost((RestJob) job);
400 } catch (Exception ex)
402 job.setSubjobComplete(true);
403 job.setAllowedServerExceptions(-1);
404 Cache.log.error("Exception when trying to start Rest Job.", ex);
409 public void parseResult()
411 // crazy users will see this message
412 // TODO: finish this! and remove the message below!
413 Cache.log.warn("Rest job result parser is currently INCOMPLETE!");
415 for (RestJob rj : (RestJob[]) jobs)
417 if (rj.hasResponse() && rj.resSet != null && rj.resSet.isValid())
422 Cache.log.debug("Parsing data for job " + rj.getJobId());
428 Cache.log.debug("Finished parsing data for job " + rj.getJobId());
432 Cache.log.warn("Failed to finish parsing data for job "
434 ex.printStackTrace();
435 } catch (Exception ex)
437 Cache.log.warn("Failed to finish parsing data for job "
439 ex.printStackTrace();
445 // add listeners and activate result display gui elements
447 * decisions based on job result content + state of alignFrame that
448 * originated the job:
451 * 1. Can/Should this job automatically open a new window for results
455 wsInfo.setViewResultsImmediatly(false);
459 // realiseResults(true, true);
461 // otherwise, should automatically view results
463 // TODO: check if at least one or more contexts are valid - if so, enable
465 wsInfo.showResultsNewFrame.addActionListener(new ActionListener()
469 public void actionPerformed(ActionEvent e)
471 realiseResults(false);
475 wsInfo.mergeResults.addActionListener(new ActionListener()
479 public void actionPerformed(ActionEvent e)
481 realiseResults(true);
486 wsInfo.setResultsReady();
490 // tell the user nothing was returned.
494 public void realiseResults(boolean merge)
497 * 2. Should the job modify the parent alignment frame/view(s) (if they
498 * still exist and the alignment hasn't been edited) in order to display new
499 * annotation/features.
502 * alignment panels derived from each alignment set returned by service.
504 ArrayList<jalview.gui.AlignmentPanel> destPanels = new ArrayList<jalview.gui.AlignmentPanel>();
506 * current pane being worked with
508 jalview.gui.AlignmentPanel destPanel;
510 * when false, zeroth pane is panel derived from input deta.
512 boolean newAlignment = false;
515 if (!restClient.isAlignmentModified())
517 destPanel = restClient.recoverAlignPanelForView();
518 if (restClient.isShowResultsInNewView())
520 destPanel = destPanel.alignFrame.newView(false);
522 // add the destination panel to frame zero of result panel set
523 destPanels.add(destPanel);
526 if (destPanels.size()==0)
528 Object[] idat = input.getAlignmentAndColumnSelection(restClient.av
530 AlignFrame af = new AlignFrame((AlignmentI) idat[0],
531 (ColumnSelection) idat[1], AlignFrame.DEFAULT_WIDTH,
532 AlignFrame.DEFAULT_HEIGHT);
533 jalview.gui.Desktop.addInternalFrame(af,
534 "Results for " + restClient.service.details.Name + " "
535 + restClient.service.details.Action + " on "
536 + restClient.af.getTitle(), AlignFrame.DEFAULT_WIDTH,
537 AlignFrame.DEFAULT_HEIGHT);
538 destPanel = af.alignPanel;
539 // create totally new alignment from stashed data/results
542 // Now process results, adding/creating new views as necessary.
544 boolean hsepjobs = restClient.service.isHseparable();
545 boolean vsepjobs = restClient.service.isVseparable();
546 // total number of distinct alignment sets generated by job set.
547 int totalSets = 0, numAlSets = 0;
548 for (int j = 0; j < jobs.length; j++)
550 RestJob rj = (RestJob) jobs[j];
553 JalviewDataset rset = rj.context;
554 numAlSets = rset.hasAlignments() ? 0 : rset.getAl().size();
557 for (int als = 0; als < numAlSets; als++)
559 // gather data from context
562 // todo: merge data from each group/sequence onto whole
569 // map single result back on to all visible region of original alignment
570 if (als==0 && rj.isInputContextModified())
572 // transfer features, annotation, groups, etc, from input context to align panel derived from input data
573 new jalview.datamodel.Alignment(new jalview.datamodel.SequenceI[] {null}).getAlignmentAnnotation();
579 // map result onto visible contigs.
580 AlignmentSet alset = rset.getAl().get(als);
585 // alignment is added straight to
592 // transfer results onto panel
596 * alignments. New alignments are added to dataset, and subsequently
597 * annotated/visualised accordingly. 1. New alignment frame created for
598 * new alignment. Decide if any vis settings should be inherited from old
599 * alignment frame (e.g. sequence ordering ?). 2. Subsequent data added to
600 * alignment as below:
603 * annotation update to original/newly created context alignment: 1.
604 * identify alignment where annotation is to be loaded onto. 2. Add
605 * annotation, excluding any duplicates. 3. Ensure annotation is visible
606 * on alignment - honouring ordering given by file.
609 * features updated to original or newly created context alignment: 1.
610 * Features are(or were already) added to dataset. 2. Feature settings
611 * modified to ensure all features are displayed - honouring any ordering
612 * given by result file. Consider merging action with the code used by the
613 * DAS fetcher to update alignment views with new info.
616 * Seq associated data files (PDB files). 1. locate seq association in
617 * current dataset/alignment context and add file as normal - keep handle
618 * of any created ref objects. 2. decide if new data should be displayed :
619 * PDB display: if alignment has PDB display already, should new pdb files
625 // destPanel.adjustAnnotationHeight();
631 * @return true if the run method is safe to call
633 public boolean isValid()
637 for (RestJob rj : (RestJob[]) jobs)
639 if (!rj.hasValidInput())
641 // invalid input for this job
642 System.err.println("Job " + rj.getJobnum()
643 + " has invalid input.");
649 // TODO Auto-generated method stub