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