Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.ws.rest

File RestJobThread.java

 

Coverage histogram

../../../img/srcFileCovDistChart2.png
54% of files have more coverage

Code metrics

188
402
23
3
1,333
914
147
0.37
17.48
7.67
6.39

Classes

Class Line # Actions
RestJobThread 65 402 147
0.1549755315.5%
RestJobThread.Stage 67 0 0
-1.0 -
RestJobThread.AddDataTo 578 0 0
-1.0 -
 

Contributing tests

This file is covered by 1 test. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ 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.rest;
22   
23    import java.awt.event.ActionEvent;
24    import java.awt.event.ActionListener;
25    import java.io.IOException;
26    import java.util.ArrayList;
27    import java.util.Hashtable;
28    import java.util.List;
29    import java.util.Locale;
30    import java.util.Map.Entry;
31   
32    import org.apache.axis.transport.http.HTTPConstants;
33    import org.apache.http.Header;
34    import org.apache.http.HttpEntity;
35    import org.apache.http.HttpResponse;
36    import org.apache.http.client.ClientProtocolException;
37    import org.apache.http.client.methods.HttpGet;
38    import org.apache.http.client.methods.HttpPost;
39    import org.apache.http.client.methods.HttpRequestBase;
40    import org.apache.http.entity.mime.HttpMultipartMode;
41    import org.apache.http.entity.mime.MultipartEntity;
42    import org.apache.http.impl.client.DefaultHttpClient;
43    import org.apache.http.util.EntityUtils;
44   
45    import jalview.bin.Console;
46    import jalview.datamodel.Alignment;
47    import jalview.datamodel.AlignmentAnnotation;
48    import jalview.datamodel.AlignmentI;
49    import jalview.datamodel.AlignmentOrder;
50    import jalview.datamodel.Annotation;
51    import jalview.datamodel.HiddenColumns;
52    import jalview.datamodel.SequenceGroup;
53    import jalview.datamodel.SequenceI;
54    import jalview.gui.AlignFrame;
55    import jalview.gui.Desktop;
56    import jalview.gui.PaintRefresher;
57    import jalview.gui.WebserviceInfo;
58    import jalview.io.NewickFile;
59    import jalview.io.packed.JalviewDataset;
60    import jalview.io.packed.JalviewDataset.AlignmentSet;
61    import jalview.util.MessageManager;
62    import jalview.ws.AWSThread;
63    import jalview.ws.AWsJob;
64   
 
65    public class RestJobThread extends AWSThread
66    {
 
67    enum Stage
68    {
69    SUBMIT, POLL
70    }
71   
72    protected RestClient restClient;
73   
 
74  2 toggle public RestJobThread(RestClient restClient)
75    {
76  2 super(restClient.af, null, restClient._input,
77    restClient.service.postUrl);
78  2 this.restClient = restClient; // may not be needed
79    // Test Code
80    // minimal job - submit given input and parse result onto alignment as
81    // annotation/whatever
82   
83    // look for tree data, etc.
84   
85    // for moment do following job type only
86    // input=visiblealignment,groupsindex
87    // ie one subjob using groups defined on alignment.
88  2 if (!restClient.service.isHseparable())
89    {
90  0 jobs = new RestJob[1];
91  0 jobs[0] = new RestJob(0, this,
92    restClient._input.getVisibleAlignment(
93    restClient.service.getGapCharacter()),
94    restClient._input.getVisibleContigs());
95    // need a function to get a range on a view/alignment and return both
96    // annotation, groups and selection subsetted to just that region.
97   
98    }
99    else
100    {
101  2 int[] viscontig = restClient._input.getVisibleContigs();
102  2 AlignmentI[] viscontigals = restClient._input
103    .getVisibleContigAlignments(
104    restClient.service.getGapCharacter());
105  2 if (viscontigals != null && viscontigals.length > 0)
106    {
107  2 jobs = new RestJob[viscontigals.length];
108  4 for (int j = 0; j < jobs.length; j++)
109    {
110  2 int[] visc = new int[] { viscontig[j * 2], viscontig[j * 2 + 1] };
111  2 if (j != 0)
112    {
113  0 jobs[j] = new RestJob(j, this, viscontigals[j], visc);
114    }
115    else
116    {
117  2 jobs[j] = new RestJob(0, this, viscontigals[j], visc);
118    }
119    }
120    }
121    }
122    // end Test Code
123    /**
124    * subjob types row based: per sequence in alignment/selected region { input
125    * is one sequence or sequence ID } per alignment/selected region { input is
126    * set of sequences, alignment, one or more sets of sequence IDs,
127    */
128   
129  2 if (!restClient.service.isHseparable())
130    {
131   
132    // create a bunch of subjobs per visible contig to ensure result honours
133    // hidden boundaries
134    // TODO: determine if a 'soft' hSeperable property is needed - e.g. if
135    // user does SS-pred on sequence with big hidden regions, its going to be
136    // less reliable.
137    }
138    else
139    {
140    // create a single subjob for the visible/selected input
141   
142    }
143    // TODO: decide if vSeperable exists: eg. column wide analysis where hidden
144    // rows do not affect output - generally no analysis that generates
145    // alignment annotation is vSeparable -
146    }
147   
148    /**
149    * create gui components for monitoring jobs
150    *
151    * @param webserviceInfo
152    */
 
153  0 toggle public void setWebServiceInfo(WebserviceInfo webserviceInfo)
154    {
155  0 wsInfo = webserviceInfo;
156  0 for (int j = 0; j < jobs.length; j++)
157    {
158  0 wsInfo.addJobPane();
159    // Copy over any per job params
160  0 if (jobs.length > 1)
161    {
162  0 wsInfo.setProgressName("region " + jobs[j].getJobnum(),
163    jobs[j].getJobnum());
164    }
165    else
166    {
167  0 wsInfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
168    }
169    }
170    }
171   
 
172  0 toggle private String getStage(Stage stg)
173    {
174  0 if (stg == Stage.SUBMIT)
175    {
176  0 return "submitting ";
177    }
178  0 if (stg == Stage.POLL)
179    {
180  0 return "checking status of ";
181    }
182   
183  0 return (" being confused about ");
184    }
185   
 
186  0 toggle private void doPoll(RestJob rj) throws Exception
187    {
188  0 String postUrl = rj.getPollUrl();
189  0 doHttpReq(Stage.POLL, rj, postUrl);
190    }
191   
192    /**
193    * construct the post and handle the response.
194    *
195    * @throws Exception
196    */
 
197  1 toggle public void doPost(RestJob rj) throws Exception
198    {
199  1 String postUrl = rj.getPostUrl();
200  1 doHttpReq(Stage.SUBMIT, rj, postUrl);
201  1 wsInfo.invalidate();
202    }
203   
204    /**
205    * do the HTTP request - and respond/set statuses appropriate to the current
206    * stage.
207    *
208    * @param stg
209    * @param rj
210    * - provides any data needed for posting and used to record state
211    * @param postUrl
212    * - actual URL to post/get from
213    * @throws Exception
214    */
 
215  1 toggle protected void doHttpReq(Stage stg, RestJob rj, String postUrl)
216    throws Exception
217    {
218  1 HttpRequestBase request = null;
219  1 String messages = "";
220  1 if (stg == Stage.SUBMIT)
221    {
222    // Got this from
223    // http://evgenyg.wordpress.com/2010/05/01/uploading-files-multipart-post-apache/
224   
225  1 HttpPost htpost = new HttpPost(postUrl);
226  1 MultipartEntity postentity = new MultipartEntity(
227    HttpMultipartMode.STRICT);
228  1 for (Entry<String, InputType> input : rj.getInputParams())
229    {
230  2 if (input.getValue().validFor(rj))
231    {
232  2 postentity.addPart(input.getKey(),
233    input.getValue().formatForInput(rj));
234    }
235    else
236    {
237  0 messages += "Skipped an input (" + input.getKey()
238    + ") - Couldn't generate it from available data.";
239    }
240    }
241  1 htpost.setEntity(postentity);
242  1 request = htpost;
243    }
244    else
245    {
246  0 request = new HttpGet(postUrl);
247    }
248  1 if (request != null)
249    {
250  1 DefaultHttpClient httpclient = new DefaultHttpClient();
251   
252  1 HttpResponse response = null;
253  1 try
254    {
255  1 response = httpclient.execute(request);
256    } catch (ClientProtocolException he)
257    {
258  0 rj.statMessage = "Web Protocol Exception when " + getStage(stg)
259    + "Job. <br>Problematic url was <a href=\""
260    + request.getURI() + "\">" + request.getURI()
261    + "</a><br>See Console output for details.";
262  0 rj.setAllowedServerExceptions(0);// unrecoverable;
263  0 rj.error = true;
264  0 Console.fatal("Unexpected REST Job " + getStage(stg)
265    + "exception for URL " + rj.rsd.postUrl);
266  0 throw (he);
267    } catch (IOException e)
268    {
269  0 rj.statMessage = "IO Exception when " + getStage(stg)
270    + "Job. <br>Problematic url was <a href=\""
271    + request.getURI() + "\">" + request.getURI()
272    + "</a><br>See Console output for details.";
273  0 Console.warn("IO Exception for REST Job " + getStage(stg)
274    + "exception for URL " + rj.rsd.postUrl);
275   
276  0 throw (e);
277    }
278  1 switch (response.getStatusLine().getStatusCode())
279    {
280  0 case 200:
281  0 rj.running = false;
282  0 Console.debug("Processing result set.");
283  0 processResultSet(rj, response, request);
284  0 break;
285  0 case 202:
286  0 markJobAsRunning(rj);
287  0 break;
288  0 case 201:
289    // Created - redirect may be present. Fallthrough to 302
290  1 case 302:
291  1 extractJobId(rj, response);
292  1 completeStatus(rj, response);
293  1 break;
294  0 case 500:
295  0 markAsFailed(rj, response);
296  0 completeStatus(rj, response,
297    "" + getStage(stg) + "failed. Reason below:\n");
298  0 break;
299  0 default:
300    // Some other response. Probably need to pop up the content in a window.
301    // TODO: deal with all other HTTP response codes from server.
302  0 Console.warn("Unhandled response status when " + getStage(stg)
303    + "for " + postUrl + ": " + response.getStatusLine());
304  0 rj.error = true;
305  0 rj.setAllowedServerExceptions(0);
306  0 rj.setSubjobComplete(true);
307  0 rj.setSubmitted(true);
308  0 try
309    {
310  0 completeStatus(rj, response, "" + getStage(stg)
311    + " resulted in an unexpected server response.<br/>Url concerned was <a href=\""
312    + request.getURI() + "\">" + request.getURI()
313    + "</a><br/>Filtered response content below:<br/>");
314    } catch (IOException e)
315    {
316  0 Console.debug("IOException when consuming unhandled response", e);
317    }
318  0 ;
319    }
320    }
321    }
322   
 
323  0 toggle private void markAsFailed(RestJob rj, HttpResponse response)
324    {
325    // Failed.
326  0 rj.setSubmitted(true);
327  0 rj.setAllowedServerExceptions(0);
328  0 rj.setSubjobComplete(true);
329  0 rj.error = true;
330  0 rj.running = false;
331    }
332   
333    /**
334    * set the jobRunning flag and post a link to the physical result page encoded
335    * in rj.getJobId()
336    *
337    * @param rj
338    */
 
339  0 toggle private void markJobAsRunning(RestJob rj)
340    {
341  0 rj.statMessage = "<br>Job submitted successfully. Results available at this URL:\n"
342    + "<a href=" + rj.getJobId() + "\">" + rj.getJobId()
343    + "</a><br>";
344  0 rj.running = true;
345    }
346   
347    /**
348    * extract the job ID URL from the redirect page. Does nothing if job is
349    * already running.
350    *
351    * @param rj
352    * @param response
353    */
 
354  1 toggle private void extractJobId(RestJob rj, HttpResponse response)
355    {
356  1 Header[] loc;
357  1 if (!rj.isSubmitted())
358    {
359   
360    // redirect URL - typical for IBIVU type jobs.
361  ? if ((loc = response.getHeaders(HTTPConstants.HEADER_LOCATION)) != null
362    && loc.length > 0)
363    {
364  1 if (loc.length > 1)
365    {
366  0 Console.warn("Ignoring additional " + (loc.length - 1)
367    + " location(s) provided in response header ( next one is '"
368    + loc[1].getValue() + "' )");
369    }
370  1 rj.setJobId(loc[0].getValue());
371  1 rj.setSubmitted(true);
372    }
373    }
374    }
375   
376    /**
377    * job has completed. Something valid should be available from con
378    *
379    * @param rj
380    * @param con
381    * @param req
382    * is a stateless request - expected to return the same data
383    * regardless of how many times its called.
384    */
 
385  0 toggle private void processResultSet(RestJob rj, HttpResponse con,
386    HttpRequestBase req)
387    {
388  0 if (rj.statMessage == null)
389    {
390  0 rj.statMessage = "";
391    }
392  0 rj.statMessage += "Job Complete.\n";
393  0 try
394    {
395  0 rj.resSet = new HttpResultSet(rj, con, req);
396  0 rj.gotresult = true;
397    } catch (IOException e)
398    {
399  0 rj.statMessage += "Couldn't parse results. Failed.";
400  0 rj.error = true;
401  0 rj.gotresult = false;
402    }
403    }
404   
 
405  1 toggle private void completeStatus(RestJob rj, HttpResponse con)
406    throws IOException
407    {
408  1 completeStatus(rj, con, null);
409   
410    }
411   
 
412  1 toggle private void completeStatus(RestJob rj, HttpResponse con, String prefix)
413    throws IOException
414    {
415  1 StringBuffer sb = new StringBuffer();
416  1 if (prefix != null)
417    {
418  0 sb.append(prefix);
419    }
420  1 ;
421  1 if (rj.statMessage != null && rj.statMessage.length() > 0)
422    {
423  0 sb.append(rj.statMessage);
424    }
425  1 HttpEntity en = con.getEntity();
426    /*
427    * Just append the content as a string.
428    */
429  1 String f;
430  1 StringBuffer content = new StringBuffer(f = EntityUtils.toString(en));
431  1 f = f.toLowerCase(Locale.ROOT);
432  1 int body = f.indexOf("<body");
433  1 if (body > -1)
434    {
435  0 content.delete(0, f.indexOf(">", body) + 1);
436    }
437  1 if (body > -1 && sb.length() > 0)
438    {
439  0 sb.append("\n");
440  0 content.insert(0, sb);
441  0 sb = null;
442    }
443  1 f = null;
444  1 rj.statMessage = content.toString();
445    }
446   
 
447  0 toggle @Override
448    public void pollJob(AWsJob job) throws Exception
449    {
450  0 assert (job instanceof RestJob);
451  0 jalview.bin.Console.errPrintln("Debug RestJob: Polling Job");
452  0 doPoll((RestJob) job);
453    }
454   
 
455  1 toggle @Override
456    public void StartJob(AWsJob job)
457    {
458  1 assert (job instanceof RestJob);
459  1 try
460    {
461  1 jalview.bin.Console.errPrintln("Debug RestJob: Posting Job");
462  1 doPost((RestJob) job);
463    } catch (NoValidInputDataException erex)
464    {
465  0 job.setSubjobComplete(true);
466  0 job.setSubmitted(true);
467  0 ((RestJob) job).statMessage = "<br>It looks like there was a problem with the data sent to the service :<br>"
468    + erex.getMessage() + "\n";
469  0 ((RestJob) job).error = true;
470   
471    } catch (Exception ex)
472    {
473  1 job.setSubjobComplete(true);
474  1 job.setAllowedServerExceptions(-1);
475  1 Console.error("Exception when trying to start Rest Job.", ex);
476    }
477    }
478   
 
479  0 toggle @Override
480    public void parseResult()
481    {
482    // crazy users will see this message
483    // TODO: finish this! and remove the message below!
484  0 Console.warn("Rest job result parser is currently INCOMPLETE!");
485  0 int validres = 0;
486  0 for (RestJob rj : (RestJob[]) jobs)
487    {
488  0 if (rj.hasResponse() && rj.resSet != null && rj.resSet.isValid())
489    {
490  0 String ln = null;
491  0 try
492    {
493  0 Console.debug("Parsing data for job " + rj.getJobId());
494  0 rj.parseResultSet();
495  0 if (rj.hasResults())
496    {
497  0 validres++;
498    }
499  0 Console.debug("Finished parsing data for job " + rj.getJobId());
500   
501    } catch (Error ex)
502    {
503  0 Console.warn(
504    "Failed to finish parsing data for job " + rj.getJobId());
505  0 ex.printStackTrace();
506    } catch (Exception ex)
507    {
508  0 Console.warn(
509    "Failed to finish parsing data for job " + rj.getJobId());
510  0 ex.printStackTrace();
511    } finally
512    {
513  0 rj.error = true;
514  0 rj.statMessage = "Error whilst parsing data for this job.<br>URL for job response is :<a href=\""
515    + rj.resSet.getUrl() + "\">" + rj.resSet.getUrl()
516    + "</a><br>";
517    }
518    }
519    }
520  0 if (validres > 0)
521    {
522    // add listeners and activate result display gui elements
523    /**
524    * decisions based on job result content + state of alignFrame that
525    * originated the job:
526    */
527    /*
528    * 1. Can/Should this job automatically open a new window for results
529    */
530  0 if (true)
531    {
532    // preserver current jalview behaviour
533  0 wsInfo.setViewResultsImmediatly(true);
534    }
535    else
536    {
537    // realiseResults(true, true);
538    }
539    // otherwise, should automatically view results
540   
541    // TODO: check if at least one or more contexts are valid - if so, enable
542    // gui
543  0 wsInfo.showResultsNewFrame.addActionListener(new ActionListener()
544    {
545   
 
546  0 toggle @Override
547    public void actionPerformed(ActionEvent e)
548    {
549  0 realiseResults(false);
550    }
551   
552    });
553  0 wsInfo.mergeResults.addActionListener(new ActionListener()
554    {
555   
 
556  0 toggle @Override
557    public void actionPerformed(ActionEvent e)
558    {
559  0 realiseResults(true);
560    }
561   
562    });
563   
564  0 wsInfo.setResultsReady();
565    }
566    else
567    {
568    // tell the user nothing was returned.
569  0 wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
570  0 wsInfo.setFinishedNoResults();
571    }
572    }
573   
574    /**
575    * instructions for whether to create new alignment view on current alignment
576    * set, add to current set, or create new alignFrame
577    */
 
578    private enum AddDataTo
579    {
580    /**
581    * add annotation, trees etc to current view
582    */
583    currentView,
584    /**
585    * create a new view derived from current view and add data to that
586    */
587    newView,
588    /**
589    * create a new alignment frame for the result set and add annotation to
590    * that.
591    */
592    newAlignment
593    };
594   
 
595  0 toggle public void realiseResults(boolean merge)
596    {
597    /*
598    * 2. Should the job modify the parent alignment frame/view(s) (if they
599    * still exist and the alignment hasn't been edited) in order to display new
600    * annotation/features.
601    */
602    /**
603    * alignment panels derived from each alignment set returned by service.
604    */
605  0 ArrayList<jalview.api.AlignmentViewPanel> destPanels = new ArrayList<jalview.api.AlignmentViewPanel>();
606    /**
607    * list of instructions for how to process each distinct alignment set
608    * returned by the job set
609    */
610  0 ArrayList<AddDataTo> resultDest = new ArrayList<AddDataTo>();
611    /**
612    * when false, zeroth pane is panel derived from input deta.
613    */
614  0 boolean newAlignment = false;
615    /**
616    * gap character to be used for alignment reconstruction
617    */
618  0 char gapCharacter = restClient.av.getGapCharacter();
619    // Now, iterate over all alignment sets returned from all jobs:
620    // first inspect jobs and collate results data in order to count alignments
621    // and other results
622    // then assemble results from decomposed (v followed by h-separated) jobs
623    // finally, new views and alignments will be created and displayed as
624    // necessary.
625  0 boolean hsepjobs = restClient.service.isHseparable();
626  0 boolean vsepjobs = restClient.service.isVseparable();
627    // total number of distinct alignment sets generated by job set.
628  0 int numAlSets = 0, als = 0;
629  0 List<AlignmentI> destAls = new ArrayList<AlignmentI>();
630  0 List<jalview.datamodel.HiddenColumns> destColsel = new ArrayList<jalview.datamodel.HiddenColumns>();
631  0 List<List<NewickFile>> trees = new ArrayList<List<NewickFile>>();
632   
633  0 do
634    {
635    // Step 1.
636    // iterate over each alignment set returned from each subjob. Treating
637    // each one returned in parallel with others.
638    // Result collation arrays
639   
640    /**
641    * mapping between index of sequence in alignment that was submitted to
642    * server and index of sequence in the input alignment
643    */
644  0 int[][] ordermap = new int[jobs.length][];
645  0 SequenceI[][] rseqs = new SequenceI[jobs.length][];
646  0 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
647  0 AlignmentAnnotation[][] alan = new AlignmentAnnotation[jobs.length][];
648  0 SequenceGroup[][] sgrp = new SequenceGroup[jobs.length][];
649    // Now collect all data for alignment Set als from job array
650  0 for (int j = 0; j < jobs.length; j++)
651    {
652  0 RestJob rj = (RestJob) jobs[j];
653  0 if (rj.hasResults())
654    {
655  0 JalviewDataset rset = rj.context;
656  0 if (rset.hasAlignments())
657    {
658  0 if (numAlSets < rset.getAl().size())
659    {
660  0 numAlSets = rset.getAl().size();
661    }
662  0 if (als < rset.getAl().size()
663    && rset.getAl().get(als).isModified())
664    {
665    // Collate result data
666    // TODO: decide if all alignmentI should be collected rather than
667    // specific alignment data containers
668    // for moment, we just extract content, but this means any
669    // alignment properties may be lost.
670  0 AlignmentSet alset = rset.getAl().get(als);
671  0 alan[j] = alset.al.getAlignmentAnnotation();
672  0 if (alset.al.getGroups() != null)
673    {
674  0 sgrp[j] = new SequenceGroup[alset.al.getGroups().size()];
675  0 alset.al.getGroups().toArray(sgrp[j]);
676    }
677    else
678    {
679  0 sgrp[j] = null;
680    }
681  0 orders[j] = new AlignmentOrder(alset.al);
682  0 rseqs[j] = alset.al.getSequencesArray();
683  0 ordermap[j] = rj.getOrderMap();
684    // if (rj.isInputUniquified()) {
685    // jalview.analysis.AlignmentSorter.recoverOrder(rseqs[als]);
686    // }
687   
688  0 if (alset.trees != null)
689    {
690  0 trees.add(new ArrayList<NewickFile>(alset.trees));
691    }
692    else
693    {
694  0 trees.add(new ArrayList<NewickFile>());
695    }
696    }
697    }
698    }
699    }
700    // Now aggregate and present results from this frame of alignment data.
701  0 int nvertsep = 0, nvertseps = 1;
702  0 if (vsepjobs)
703    {
704    // Jobs relate to different rows of input alignment.
705    // Jobs are subdivided by rows before columns,
706    // so there will now be a number of subjobs according tohsep for each
707    // vertsep
708    // TODO: get vertical separation intervals for each job and set
709    // nvertseps
710    // TODO: merge data from each group/sequence onto whole
711    // alignment
712    }
713    /**
714    * index into rest jobs subdivided vertically
715    */
716  0 int vrestjob = 0;
717    // Destination alignments for all result data.
718  0 ArrayList<SequenceGroup> visgrps = new ArrayList<SequenceGroup>();
719  0 Hashtable<String, SequenceGroup> groupNames = new Hashtable<String, SequenceGroup>();
720  0 ArrayList<AlignmentAnnotation> visAlAn = null;
721  0 for (nvertsep = 0; nvertsep < nvertseps; nvertsep++)
722    {
723    // TODO: set scope w.r.t. original alignment view for vertical
724    // separation.
725    {
726    // results for a job exclude hidden columns of input data, so map
727    // back on to all visible regions
728    /**
729    * rest job result we are working with
730    */
731  0 int nrj = vrestjob;
732   
733  0 RestJob rj = (RestJob) jobs[nrj];
734  0 int contigs[] = input.getVisibleContigs();
735  0 AlignmentI destAl = null;
736  0 jalview.datamodel.HiddenColumns destHCs = null;
737    // Resolve destAl for this data.
738  0 if (als == 0 && rj.isInputContextModified())
739    {
740    // special case: transfer features, annotation, groups, etc,
741    // from input
742    // context to align panel derived from input data
743  0 if (destAls.size() > als)
744    {
745  0 destAl = destAls.get(als);
746    }
747    else
748    {
749  0 if (!restClient.isAlignmentModified() && merge)
750    {
751  0 destAl = restClient.av.getAlignment();
752  0 destHCs = restClient.av.getAlignment().getHiddenColumns();
753  0 resultDest.add(restClient.isShowResultsInNewView()
754    ? AddDataTo.newView
755    : AddDataTo.currentView);
756  0 destPanels.add(restClient.recoverAlignPanelForView());
757    }
758    else
759    {
760  0 newAlignment = true;
761    // recreate the input alignment data
762  0 Object[] idat = input
763    .getAlignmentAndHiddenColumns(gapCharacter);
764  0 destAl = new Alignment((SequenceI[]) idat[0]);
765  0 destHCs = (HiddenColumns) idat[1];
766  0 resultDest.add(AddDataTo.newAlignment);
767    // but do not add to the alignment panel list - since we need to
768    // create a whole new alignFrame set.
769    }
770  0 destAls.add(destAl);
771  0 destColsel.add(destHCs);
772    }
773    }
774    else
775    {
776    // alignment(s) returned by service is to be re-integrated and
777    // displayed
778  0 if (destAls.size() > als)
779    {
780  0 if (!vsepjobs)
781    {
782    // TODO: decide if multiple multiple alignments returned by
783    // non-vseparable services are allowed.
784  0 Console.warn(
785    "dealing with multiple alignment products returned by non-vertically separable service.");
786    }
787    // recover reference to last alignment created for this rest frame
788    // ready for extension
789  0 destAl = destAls.get(als);
790  0 destHCs = destColsel.get(als);
791    }
792    else
793    {
794  0 Object[] newview;
795   
796  0 if (!hsepjobs)
797    {
798    // single alignment for any job that gets mapped back on to
799    // input data. Reconstruct by interleaving parts of returned
800    // alignment with hidden parts of input data.
801  0 SequenceI[][] nsq = splitSeqsOnVisibleContigs(rseqs[nrj],
802    contigs, gapCharacter);
803  0 AlignmentOrder alo[] = new AlignmentOrder[nsq.length];
804  0 for (int no = 0; no < alo.length; no++)
805    {
806  0 alo[no] = new AlignmentOrder(orders[nrj].getOrder());
807    }
808  0 newview = input.getUpdatedView(nsq, orders, gapCharacter);
809    }
810    else
811    {
812    // each job maps to a single visible contig, and all need to be
813    // stitched back together.
814    // reconstruct using sub-region based MSA alignment construction
815    // mechanism
816  0 newview = input.getUpdatedView(rseqs, orders, gapCharacter);
817    }
818  0 destAl = new Alignment((SequenceI[]) newview[0]);
819  0 destHCs = (HiddenColumns) newview[1];
820  0 newAlignment = true;
821    // TODO create alignment from result data with propagated
822    // references.
823  0 destAls.add(destAl);
824  0 destColsel.add(destHCs);
825  0 resultDest.add(AddDataTo.newAlignment);
826  0 throw new Error(
827    MessageManager.getString("error.implementation_error")
828    + "TODO: ");
829    }
830    }
831    /**
832    * save initial job in this set in case alignment is h-separable
833    */
834  0 int initnrj = nrj;
835    // Now add in groups
836  0 for (int ncnt = 0; ncnt < contigs.length; ncnt += 2)
837    {
838  0 if (!hsepjobs)
839    {
840    // single alignment for any job that gets mapped back on to input
841    // data.
842    }
843    else
844    {
845    // each job maps to a single visible contig, and all need to be
846    // stitched back together.
847  0 if (ncnt > 0)
848    {
849  0 nrj++;
850    }
851    // TODO: apply options for group merging and annotation merging.
852    // If merging not supported, then either clear hashtables now or
853    // use them to rename the new annotation/groups for each contig if
854    // a conflict occurs.
855    }
856  0 if (sgrp[nrj] != null)
857    {
858  0 for (SequenceGroup sg : sgrp[nrj])
859    {
860  0 boolean recovered = false;
861  0 SequenceGroup exsg = null;
862  0 if (sg.getName() != null)
863    {
864  0 exsg = groupNames.get(sg.getName());
865    }
866  0 if (exsg == null)
867    {
868  0 exsg = new SequenceGroup(sg);
869  0 groupNames.put(exsg.getName(), exsg);
870  0 visgrps.add(exsg);
871  0 exsg.setStartRes(sg.getStartRes() + contigs[ncnt]);
872  0 exsg.setEndRes(sg.getEndRes() + contigs[ncnt]);
873    }
874    else
875    {
876  0 recovered = true;
877    }
878    // now replace any references from the result set with
879    // corresponding refs from alignment input set.
880   
881    // TODO: cope with recovering hidden sequences from
882    // resultContext
883    {
884  0 for (SequenceI oseq : sg.getSequences(null))
885    {
886  0 SequenceI nseq = getNewSeq(oseq, rseqs[nrj],
887    ordermap[nrj], destAl);
888  0 if (nseq != null)
889    {
890  0 if (!recovered)
891    {
892  0 exsg.deleteSequence(oseq, false);
893    }
894  0 exsg.addSequence(nseq, false);
895    }
896    else
897    {
898  0 Console.warn(
899    "Couldn't resolve original sequence for new sequence.");
900    }
901    }
902  0 if (sg.hasSeqrep())
903    {
904  0 if (exsg.getSeqrep() == sg.getSeqrep())
905    {
906    // lift over sequence rep reference too
907  0 SequenceI oseq = sg.getSeqrep();
908  0 SequenceI nseq = getNewSeq(oseq, rseqs[nrj],
909    ordermap[nrj], destAl);
910  0 if (nseq != null)
911    {
912  0 exsg.setSeqrep(nseq);
913    }
914    }
915    }
916    }
917  0 if (recovered)
918    {
919    // adjust boundaries of recovered group w.r.t. new group being
920    // merged on to original alignment.
921  0 int start = sg.getStartRes() + contigs[ncnt],
922    end = sg.getEndRes() + contigs[ncnt];
923  0 if (start < exsg.getStartRes())
924    {
925  0 exsg.setStartRes(start);
926    }
927  0 if (end > exsg.getEndRes())
928    {
929  0 exsg.setEndRes(end);
930    }
931    }
932    }
933    }
934    }
935    // reset job counter
936  0 nrj = initnrj;
937    // and finally add in annotation and any trees for each job
938  0 for (int ncnt = 0; ncnt < contigs.length; ncnt += 2)
939    {
940  0 if (!hsepjobs)
941    {
942    // single alignment for any job that gets mapped back on to input
943    // data.
944    }
945    else
946    {
947    // each job maps to a single visible contig, and all need to be
948    // stitched back together.
949  0 if (ncnt > 0)
950    {
951  0 nrj++;
952    }
953    }
954   
955    // merge alignmentAnnotation into one row
956  0 if (alan[nrj] != null)
957    {
958  0 for (int an = 0; an < alan[nrj].length; an++)
959    {
960  0 SequenceI sqass = null;
961  0 SequenceGroup grass = null;
962  0 if (alan[nrj][an].sequenceRef != null)
963    {
964    // TODO: ensure this relocates sequence reference to local
965    // context.
966  0 sqass = getNewSeq(alan[nrj][an].sequenceRef, rseqs[nrj],
967    ordermap[nrj], destAl);
968    }
969  0 if (alan[nrj][an].groupRef != null)
970    {
971    // TODO: verify relocate group reference to local context
972    // works correctly
973  0 grass = groupNames.get(alan[nrj][an].groupRef.getName());
974  0 if (grass == null)
975    {
976  0 Console.error(
977    "Couldn't relocate group referemce for group "
978    + alan[nrj][an].groupRef.getName());
979    }
980    }
981  0 if (visAlAn == null)
982    {
983  0 visAlAn = new ArrayList<AlignmentAnnotation>();
984    }
985  0 AlignmentAnnotation visan = null;
986  0 for (AlignmentAnnotation v : visAlAn)
987    {
988  0 if (v.label != null
989    && v.label.equals(alan[nrj][an].label))
990    {
991  0 visan = v;
992    }
993    }
994  0 if (visan == null)
995    {
996  0 visan = new AlignmentAnnotation(alan[nrj][an]);
997    // copy annotations, and wipe out/update refs.
998  0 visan.annotations = new Annotation[input.getWidth()];
999  0 visan.groupRef = grass;
1000  0 visan.sequenceRef = sqass;
1001  0 visAlAn.add(visan);
1002    }
1003  0 if (contigs[ncnt]
1004    + alan[nrj][an].annotations.length > visan.annotations.length)
1005    {
1006    // increase width of annotation row
1007  0 Annotation[] newannv = new Annotation[contigs[ncnt]
1008    + alan[nrj][an].annotations.length];
1009  0 System.arraycopy(visan.annotations, 0, newannv, 0,
1010    visan.annotations.length);
1011  0 visan.annotations = newannv;
1012    }
1013    // now copy local annotation data into correct position
1014  0 System.arraycopy(alan[nrj][an].annotations, 0,
1015    visan.annotations, contigs[ncnt],
1016    alan[nrj][an].annotations.length);
1017   
1018    }
1019    }
1020    // Trees
1021  0 if (trees.get(nrj).size() > 0)
1022    {
1023  0 for (NewickFile nf : trees.get(nrj))
1024    {
1025    // TODO: process each newick file, lifting over sequence refs to
1026    // current alignment, if necessary.
1027  0 Console.error(
1028    "Tree recovery from restjob not yet implemented.");
1029    }
1030    }
1031    }
1032    }
1033    } // end of vseps loops.
1034  0 if (visAlAn != null)
1035    {
1036  0 for (AlignmentAnnotation v : visAlAn)
1037    {
1038  0 destAls.get(als).addAnnotation(v);
1039    }
1040    }
1041  0 if (visgrps != null)
1042    {
1043  0 for (SequenceGroup sg : visgrps)
1044    {
1045  0 destAls.get(als).addGroup(sg);
1046    }
1047    }
1048  0 } while (++als < numAlSets);
1049    // Finally, assemble each new alignment, and create new gui components to
1050    // present it.
1051    /**
1052    * current AlignFrame where results will go.
1053    */
1054  0 AlignFrame destaf = restClient.recoverAlignFrameForView();
1055    /**
1056    * current pane being worked with
1057    */
1058  0 jalview.api.AlignmentViewPanel destPanel = restClient
1059    .recoverAlignPanelForView();
1060  0 als = 0;
1061  0 for (AddDataTo action : resultDest)
1062    {
1063  0 AlignmentI destal;
1064  0 HiddenColumns destcs;
1065  0 String alTitle = MessageManager
1066    .formatMessage("label.webservice_job_title_on", new String[]
1067    { restClient.service.details.getAction(),
1068    restClient.service.details.getName(),
1069    restClient.viewTitle });
1070  0 switch (action)
1071    {
1072  0 case newAlignment:
1073  0 destal = destAls.get(als);
1074  0 destcs = destColsel.get(als);
1075  0 destaf = new AlignFrame(destal, destcs, AlignFrame.DEFAULT_WIDTH,
1076    AlignFrame.DEFAULT_HEIGHT);
1077  0 PaintRefresher.Refresh(destaf,
1078    destaf.getViewport().getSequenceSetId());
1079    // todo transfer any feature settings and colouring
1080    /*
1081    * destaf.getFeatureRenderer().transferSettings(this.featureSettings);
1082    * // update orders if (alorders.size() > 0) { if (alorders.size() == 1)
1083    * { af.addSortByOrderMenuItem(WebServiceName + " Ordering",
1084    * (AlignmentOrder) alorders.get(0)); } else { // construct a
1085    * non-redundant ordering set Vector names = new Vector(); for (int i =
1086    * 0, l = alorders.size(); i < l; i++) { String orderName = new
1087    * String(" Region " + i); int j = i + 1;
1088    *
1089    * while (j < l) { if (((AlignmentOrder) alorders.get(i))
1090    * .equals(((AlignmentOrder) alorders.get(j)))) { alorders.remove(j);
1091    * l--; orderName += "," + j; } else { j++; } }
1092    *
1093    * if (i == 0 && j == 1) { names.add(new String("")); } else {
1094    * names.add(orderName); } } for (int i = 0, l = alorders.size(); i < l;
1095    * i++) { af.addSortByOrderMenuItem( WebServiceName + ((String)
1096    * names.get(i)) + " Ordering", (AlignmentOrder) alorders.get(i)); } } }
1097    */
1098    // TODO: modify this and previous alignment's title if many alignments
1099    // have been returned.
1100  0 Desktop.addInternalFrame(destaf, alTitle, AlignFrame.DEFAULT_WIDTH,
1101    AlignFrame.DEFAULT_HEIGHT);
1102   
1103  0 break;
1104  0 case newView:
1105    // TODO: determine title for view
1106  0 break;
1107  0 case currentView:
1108  0 break;
1109    }
1110    }
1111  0 if (!newAlignment)
1112    {
1113  0 if (restClient.isShowResultsInNewView())
1114    {
1115    // destPanel = destPanel.alignFrame.newView(false);
1116    }
1117    }
1118    else
1119    {
1120   
1121    }
1122    /*
1123    * if (als) // add the destination panel to frame zero of result panel set }
1124    * } if (destPanels.size()==0) { AlignFrame af = new AlignFrame((AlignmentI)
1125    * idat[0], (ColumnSelection) idat[1], AlignFrame.DEFAULT_WIDTH,
1126    * AlignFrame.DEFAULT_HEIGHT);
1127    *
1128    * jalview.gui.Desktop.addInternalFrame(af, "Results for " +
1129    * restClient.service.details.Name + " " + restClient.service.details.Action
1130    * + " on " + restClient.af.getTitle(), AlignFrame.DEFAULT_WIDTH,
1131    * AlignFrame.DEFAULT_HEIGHT); destPanel = af.alignPanel; // create totally
1132    * new alignment from stashed data/results
1133    */
1134   
1135    /*
1136    */
1137   
1138    /**
1139    * alignments. New alignments are added to dataset, and subsequently
1140    * annotated/visualised accordingly. 1. New alignment frame created for new
1141    * alignment. Decide if any vis settings should be inherited from old
1142    * alignment frame (e.g. sequence ordering ?). 2. Subsequent data added to
1143    * alignment as below:
1144    */
1145    /**
1146    * annotation update to original/newly created context alignment: 1.
1147    * identify alignment where annotation is to be loaded onto. 2. Add
1148    * annotation, excluding any duplicates. 3. Ensure annotation is visible on
1149    * alignment - honouring ordering given by file.
1150    */
1151    /**
1152    * features updated to original or newly created context alignment: 1.
1153    * Features are(or were already) added to dataset. 2. Feature settings
1154    * modified to ensure all features are displayed - honouring any ordering
1155    * given by result file. Consider merging action with the code used by the
1156    * DAS fetcher to update alignment views with new info.
1157    */
1158    /**
1159    * Seq associated data files (PDB files). 1. locate seq association in
1160    * current dataset/alignment context and add file as normal - keep handle of
1161    * any created ref objects. 2. decide if new data should be displayed : PDB
1162    * display: if alignment has PDB display already, should new pdb files be
1163    * aligned to it ?
1164    *
1165    */
1166    // destPanel.adjustAnnotationHeight();
1167   
1168    }
1169   
1170    /**
1171    * split the given array of sequences into blocks of subsequences
1172    * corresponding to each visible contig
1173    *
1174    * @param sequenceIs
1175    * @param contigs
1176    * @param gapChar
1177    * padding character for ragged ends of visible contig region.
1178    * @return
1179    */
 
1180  0 toggle private SequenceI[][] splitSeqsOnVisibleContigs(SequenceI[] sequenceIs,
1181    int[] contigs, char gapChar)
1182    {
1183  0 int nvc = contigs == null ? 1 : contigs.length / 2;
1184  0 SequenceI[][] blocks = new SequenceI[nvc][];
1185  0 if (contigs == null)
1186    {
1187  0 blocks[0] = new SequenceI[sequenceIs.length];
1188  0 System.arraycopy(sequenceIs, 0, blocks[0], 0, sequenceIs.length);
1189    }
1190    else
1191    {
1192    // deja vu - have I written this before ?? propagateGaps does this in a
1193    // way
1194  0 char[] gapset = null;
1195  0 int start = 0, width = 0;
1196  0 for (int c = 0; c < nvc; c++)
1197    {
1198  0 width = contigs[c * 2 + 1] - contigs[c * 2] + 1;
1199  0 for (int s = 0; s < sequenceIs.length; s++)
1200    {
1201  0 int end = sequenceIs[s].getLength();
1202  0 if (start < end)
1203    {
1204  0 if (start + width < end)
1205    {
1206  0 blocks[c][s] = sequenceIs[s].getSubSequence(start,
1207    start + width);
1208    }
1209    else
1210    {
1211  0 blocks[c][s] = sequenceIs[s].getSubSequence(start, end);
1212  0 String sq = blocks[c][s].getSequenceAsString();
1213  0 for (int n = start + width; n > end; n--)
1214    {
1215  0 sq += gapChar;
1216    }
1217    }
1218    }
1219    else
1220    {
1221  0 if (gapset == null || gapset.length < width)
1222    {
1223  0 char ng[] = new char[width];
1224  0 int gs = 0;
1225  0 if (gapset != null)
1226    {
1227  0 System.arraycopy(gapset, 0, ng, 0, gs = gapset.length);
1228    }
1229  0 while (gs < ng.length)
1230    {
1231  0 ng[gs++] = gapChar;
1232    }
1233    }
1234  0 blocks[c][s] = sequenceIs[s].getSubSequence(end, end);
1235  0 blocks[c][s].setSequence(gapset.toString().substring(0, width));
1236    }
1237    }
1238  0 if (c > 0)
1239    {
1240    // adjust window for next visible segnment
1241  0 start += contigs[c * 2 + 1] - contigs[c * 2];
1242    }
1243    }
1244    }
1245  0 return blocks;
1246    }
1247   
1248    /**
1249    * recover corresponding sequence from original input data corresponding to
1250    * sequence in a specific job's input data.
1251    *
1252    * @param oseq
1253    * @param sequenceIs
1254    * @param is
1255    * @param destAl
1256    * @return
1257    */
 
1258  0 toggle private SequenceI getNewSeq(SequenceI oseq, SequenceI[] sequenceIs,
1259    int[] is, AlignmentI destAl)
1260    {
1261  0 int p = 0;
1262  0 while (p < sequenceIs.length && sequenceIs[p] != oseq)
1263    {
1264  0 p++;
1265    }
1266  0 if (p < sequenceIs.length && p < destAl.getHeight())
1267    {
1268  0 return destAl.getSequenceAt(is[p]);
1269    }
1270  0 return null;
1271    }
1272   
1273    /**
1274    *
1275    * @return true if the run method is safe to call
1276    */
 
1277  1 toggle public boolean isValid()
1278    {
1279  1 ArrayList<String> _warnings = new ArrayList<String>();
1280  1 boolean validt = true;
1281  1 if (jobs != null)
1282    {
1283  1 for (RestJob rj : (RestJob[]) jobs)
1284    {
1285  1 if (!rj.hasValidInput())
1286    {
1287    // invalid input for this job
1288  0 jalview.bin.Console.errPrintln("Job " + rj.getJobnum()
1289    + " has invalid input. ( " + rj.getStatus() + ")");
1290  0 if (rj.hasStatus() && !_warnings.contains(rj.getStatus()))
1291    {
1292  0 _warnings.add(rj.getStatus());
1293    }
1294  0 validt = false;
1295    }
1296    }
1297    }
1298  1 if (!validt)
1299    {
1300  0 warnings = "";
1301  0 for (String st : _warnings)
1302    {
1303  0 if (warnings.length() > 0)
1304    {
1305  0 warnings += "\n";
1306    }
1307  0 warnings += st;
1308   
1309    }
1310    }
1311  1 return validt;
1312    }
1313   
1314    protected String warnings;
1315   
 
1316  0 toggle public boolean hasWarnings()
1317    {
1318    // TODO Auto-generated method stub
1319  0 return warnings != null && warnings.length() > 0;
1320    }
1321   
1322    /**
1323    * get any informative messages about why the job thread couldn't start.
1324    *
1325    * @return
1326    */
 
1327  0 toggle public String getWarnings()
1328    {
1329  0 return isValid() ? "Job can be started. No warnings."
1330  0 : hasWarnings() ? warnings : "";
1331    }
1332   
1333    }