1 package jalview.ws.rest;
3 import jalview.bin.Cache;
4 import jalview.datamodel.AlignmentI;
5 import jalview.gui.WebserviceInfo;
6 import jalview.io.packed.DataProvider;
7 import jalview.io.packed.JalviewDataset;
8 import jalview.io.packed.ParsePackedSet;
9 import jalview.io.packed.SimpleDataProvider;
10 import jalview.io.packed.DataProvider.JvDataType;
11 import jalview.ws.AWSThread;
12 import jalview.ws.AWsJob;
14 import java.awt.event.ActionEvent;
15 import java.awt.event.ActionListener;
16 import java.io.IOException;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.Map.Entry;
21 import org.apache.axis.transport.http.HTTPConstants;
22 import org.apache.http.Header;
23 import org.apache.http.HttpEntity;
24 import org.apache.http.HttpResponse;
25 import org.apache.http.client.ClientProtocolException;
26 import org.apache.http.client.methods.HttpGet;
27 import org.apache.http.client.methods.HttpPost;
28 import org.apache.http.client.methods.HttpRequestBase;
29 import org.apache.http.entity.mime.HttpMultipartMode;
30 import org.apache.http.entity.mime.MultipartEntity;
31 import org.apache.http.impl.client.DefaultHttpClient;
32 import org.apache.http.protocol.BasicHttpContext;
33 import org.apache.http.protocol.HttpContext;
34 import org.apache.http.util.EntityUtils;
36 public class RestJobThread extends AWSThread
43 protected RestClient restClient;;
45 public RestJobThread(RestClient restClient)
48 this.restClient = restClient; // may not be needed
50 // minimal job - submit given input and parse result onto alignment as
51 // annotation/whatever
53 // look for tree data, etc.
55 // for moment do following job type only
56 // input=visiblealignment,groupsindex
57 // ie one subjob using groups defined on alignment.
58 if (!restClient.service.isHseparable())
60 jobs = new RestJob[1];
61 jobs[0] = new RestJob(0, this,
62 restClient._input.getVisibleAlignment(restClient.service
64 // need a function to get a range on a view/alignment and return both
65 // annotation, groups and selection subsetted to just that region.
70 AlignmentI[] viscontigals = restClient._input
71 .getVisibleContigAlignments(restClient.service
73 if (viscontigals != null && viscontigals.length > 0)
75 jobs = new RestJob[viscontigals.length];
76 for (int j = 0; j < jobs.length; j++)
80 jobs[j] = new RestJob(j, this, viscontigals[j]);
84 jobs[j] = new RestJob(0, this, viscontigals[j]);
91 * subjob types row based: per sequence in alignment/selected region { input
92 * is one sequence or sequence ID } per alignment/selected region { input is
93 * set of sequences, alignment, one or more sets of sequence IDs,
96 if (!restClient.service.isHseparable())
99 // create a bunch of subjobs per visible contig to ensure result honours
101 // TODO: determine if a 'soft' hSeperable property is needed - e.g. if
102 // user does SS-pred on sequence with big hidden regions, its going to be
107 // create a single subjob for the visible/selected input
110 // TODO: decide if vSeperable exists: eg. column wide analysis where hidden
111 // rows do not affect output - generally no analysis that generates
112 // alignment annotation is vSeparable -
116 * create gui components for monitoring jobs
118 * @param webserviceInfo
120 public void setWebServiceInfo(WebserviceInfo webserviceInfo)
122 wsInfo = webserviceInfo;
123 for (int j = 0; j < jobs.length; j++)
126 // Copy over any per job params
129 wsInfo.setProgressName("region " + jobs[j].getJobnum(),
130 jobs[j].getJobnum());
134 wsInfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
139 private String getStage(Stage stg)
141 if (stg == Stage.SUBMIT)
142 return "submitting ";
143 if (stg == Stage.POLL)
144 return "checking status of ";
146 return (" being confused about ");
149 private void doPoll(RestJob rj) throws Exception
151 String postUrl = rj.getPollUrl();
152 doHttpReq(Stage.POLL, rj, postUrl);
156 * construct the post and handle the response.
160 public void doPost(RestJob rj) throws Exception
162 String postUrl = rj.getPostUrl();
163 doHttpReq(Stage.SUBMIT, rj, postUrl);
167 * do the HTTP request - and respond/set statuses appropriate to the current
172 * - provides any data needed for posting and used to record state
174 * - actual URL to post/get from
177 protected void doHttpReq(Stage stg, RestJob rj, String postUrl)
180 StringBuffer respText = new StringBuffer();
181 // con.setContentHandlerFactory(new jalview.io.mime.HttpContentHandler());
182 HttpRequestBase request = null;
183 String messages = "";
184 if (stg == Stage.SUBMIT)
187 // http://evgenyg.wordpress.com/2010/05/01/uploading-files-multipart-post-apache/
189 HttpPost htpost = new HttpPost(postUrl);
190 MultipartEntity postentity = new MultipartEntity(
191 HttpMultipartMode.STRICT);
192 for (Entry<String, InputType> input : rj.getInputParams())
194 if (input.getValue().validFor(rj))
196 postentity.addPart(input.getKey(), input.getValue()
197 .formatForInput(rj));
201 messages += "Skipped an input (" + input.getKey()
202 + ") - Couldn't generate it from available data.";
205 htpost.setEntity(postentity);
210 request = new HttpGet(postUrl);
214 DefaultHttpClient httpclient = new DefaultHttpClient();
216 HttpContext localContext = new BasicHttpContext();
217 HttpResponse response = null;
220 response = httpclient.execute(request);
221 } catch (ClientProtocolException he)
223 rj.statMessage = "Web Protocol Exception when attempting to "
224 + getStage(stg) + "Job. See Console output for details.";
225 rj.setAllowedServerExceptions(0);// unrecoverable;
227 Cache.log.fatal("Unexpected REST Job " + getStage(stg)
228 + "exception for URL " + rj.rsd.postUrl);
230 } catch (IOException e)
232 rj.statMessage = "IO Exception when attempting to "
233 + getStage(stg) + "Job. See Console output for details.";
234 Cache.log.warn("IO Exception for REST Job " + getStage(stg)
235 + "exception for URL " + rj.rsd.postUrl);
239 switch (response.getStatusLine().getStatusCode())
243 Cache.log.debug("Processing result set.");
244 processResultSet(rj, response, request);
247 rj.statMessage = "Job submitted successfully. Results available at this URL:\n"
248 + rj.getJobId() + "\n";
253 if (!rj.isSubmitted()
255 .getHeaders(HTTPConstants.HEADER_LOCATION)) != null
261 .warn("Ignoring additional "
263 + " location(s) provided in response header ( next one is '"
264 + loc[1].getValue() + "' )");
266 rj.setJobId(loc[0].getValue());
267 rj.setSubmitted(true);
269 completeStatus(rj, response);
273 rj.setSubmitted(true);
274 rj.setAllowedServerExceptions(0);
275 rj.setSubjobComplete(true);
277 completeStatus(rj, response, "" + getStage(stg)
278 + "failed. Reason below:\n");
281 // Some other response. Probably need to pop up the content in a window.
282 // TODO: deal with all other HTTP response codes from server.
283 Cache.log.warn("Unhandled response status when " + getStage(stg)
284 + "for " + postUrl + ": " + response.getStatusLine());
287 response.getEntity().consumeContent();
288 } catch (IOException e)
290 Cache.log.debug("IOException when consuming unhandled response",
299 * job has completed. Something valid should be available from con
304 * is a stateless request - expected to return the same data
305 * regardless of how many times its called.
307 private void processResultSet(RestJob rj, HttpResponse con,
310 if (rj.statMessage == null)
314 rj.statMessage += "Job Complete.\n";
317 rj.resSet = new HttpResultSet(rj, con, req);
319 } catch (IOException e)
321 rj.statMessage += "Couldn't parse results. Failed.";
323 rj.gotresult = false;
327 private void completeStatus(RestJob rj, HttpResponse con)
330 completeStatus(rj, con, null);
334 private void completeStatus(RestJob rj, HttpResponse con, String prefix)
337 StringBuffer sb = new StringBuffer();
343 if (rj.statMessage != null && rj.statMessage.length() > 0)
345 sb.append(rj.statMessage);
347 HttpEntity en = con.getEntity();
349 * Just show the content as a string.
351 rj.statMessage = EntityUtils.toString(en);
355 public void pollJob(AWsJob job) throws Exception
357 assert (job instanceof RestJob);
358 System.err.println("Debug RestJob: Polling Job");
359 doPoll((RestJob) job);
363 public void StartJob(AWsJob job)
365 assert (job instanceof RestJob);
368 System.err.println("Debug RestJob: Posting Job");
369 doPost((RestJob) job);
370 } catch (Exception ex)
372 job.setSubjobComplete(true);
373 job.setAllowedServerExceptions(-1);
374 Cache.log.error("Exception when trying to start Rest Job.", ex);
379 public void parseResult()
381 // crazy users will see this message
382 System.err.println("WARNING: Rest job result parser is INCOMPLETE!");
383 for (RestJob rj : (RestJob[]) jobs)
385 // TODO: call each jobs processResults() method and collect valid
387 if (rj.hasResponse() && rj.resSet != null && rj.resSet.isValid())
390 System.out.println("Parsing data for job " + rj.getJobId());
391 if (!restClient.isAlignmentModified())
396 * while ((ln=rj.resSet.nextLine())!=null) { System.out.println(ln);
399 List<DataProvider> dp = new ArrayList<DataProvider>();
400 restClient.af.newView_actionPerformed(null);
401 dp.add(new SimpleDataProvider(JvDataType.ANNOTATION, rj.resSet, null));
402 JalviewDataset context = new JalviewDataset(restClient.av.getAlignment().getDataset(), null, null,restClient.av.getAlignment());
403 ParsePackedSet pps = new ParsePackedSet();
404 pps.getAlignment(context, dp);
406 // do an ap.refresh restClient.av.alignmentChanged(Desktop.getAlignmentPanels(restClient.av.getViewId())[0]);
407 System.out.println("Finished parsing data for job "
410 } catch (Exception ex)
412 System.out.println("Failed to finish parsing data for job "
414 ex.printStackTrace();
420 * decisions based on job result content + state of alignFrame that
421 * originated the job:
424 * 1. Can/Should this job automatically open a new window for results
426 wsInfo.setViewResultsImmediatly(false);
428 * 2. Should the job modify the parent alignment frame/view(s) (if they
429 * still exist and the alignment hasn't been edited) in order to display new
430 * annotation/features.
433 * alignments. New alignments are added to dataset, and subsequently
434 * annotated/visualised accordingly. 1. New alignment frame created for new
435 * alignment. Decide if any vis settings should be inherited from old
436 * alignment frame (e.g. sequence ordering ?). 2. Subsequent data added to
437 * alignment as below:
440 * annotation update to original/newly created context alignment: 1.
441 * identify alignment where annotation is to be loaded onto. 2. Add
442 * annotation, excluding any duplicates. 3. Ensure annotation is visible on
443 * alignment - honouring ordering given by file.
446 * features updated to original or newly created context alignment: 1.
447 * Features are(or were already) added to dataset. 2. Feature settings
448 * modified to ensure all features are displayed - honouring any ordering
449 * given by result file. Consider merging action with the code used by the
450 * DAS fetcher to update alignment views with new info.
453 * Seq associated data files (PDB files). 1. locate seq association in
454 * current dataset/alignment context and add file as normal - keep handle of
455 * any created ref objects. 2. decide if new data should be displayed : PDB
456 * display: if alignment has PDB display already, should new pdb files be
460 // TODO: check if at least one or more contexts are valid - if so, enable
462 wsInfo.showResultsNewFrame.addActionListener(new ActionListener()
466 public void actionPerformed(ActionEvent e)
468 // TODO: call method to show results in new window
472 wsInfo.mergeResults.addActionListener(new ActionListener()
476 public void actionPerformed(ActionEvent e)
478 // TODO: call method to merge results into existing window
483 wsInfo.setResultsReady();
489 * @return true if the run method is safe to call
491 public boolean isValid()
495 for (RestJob rj : (RestJob[]) jobs)
497 if (!rj.hasValidInput())
499 // invalid input for this job
500 System.err.println("Job " + rj.getJobnum()
501 + " has invalid input.");
507 // TODO Auto-generated method stub