JAL-3899 Update usages of uniquify and deuniquify.
[jalview.git] / src / jalview / ws / rest / RestJob.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 jalview.analysis.SeqsetUtils.SequenceInfo;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.AlignmentOrder;
27 import jalview.datamodel.SequenceGroup;
28 import jalview.datamodel.SequenceI;
29 import jalview.io.packed.JalviewDataset;
30 import jalview.ws.AWsJob;
31 import jalview.ws.rest.params.Alignment;
32 import jalview.ws.rest.params.SeqGroupIndexVector;
33
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Hashtable;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.Vector;
40
41 public class RestJob extends AWsJob
42 {
43
44   // TODO: input alignmentview and other data for this job
45   RestServiceDescription rsd;
46
47   // boolean submitted;
48   boolean gotresponse;
49
50   boolean error;
51
52   boolean waiting;
53
54   boolean gotresult;
55
56   Map<String, SequenceInfo> squniq;
57
58   /**
59    * dataset associated with this input data.
60    */
61   AlignmentI dsForIO;
62
63   AlignmentOrder inputOrder;
64
65   /**
66    * context of input data with respect to an AlignmentView's visible contigs.
67    */
68   int[] origviscontig;
69
70   private AlignmentI contextAl = null;
71
72   /**
73    * create a rest job using data bounded by the given start/end column.
74    * 
75    * @param addJobPane
76    * @param restJobThread
77    * @param _input
78    * @param viscontigs
79    *          visible contigs of an alignment view from which _input was derived
80    */
81   public RestJob(int jobNum, RestJobThread restJobThread, AlignmentI _input,
82           int[] viscontigs)
83   {
84     rsd = restJobThread.restClient.service;
85     jobnum = jobNum;
86     if (viscontigs != null)
87     {
88       origviscontig = new int[viscontigs.length];
89       System.arraycopy(viscontigs, 0, origviscontig, 0, viscontigs.length);
90     }
91     // get sequences for the alignmentI
92     // get groups trimmed to alignment columns
93     // get any annotation trimmed to start/end columns, too.
94     squniq = jalview.analysis.SeqsetUtils
95             .uniquify(_input.getSequencesArray(), true);
96     // prepare input
97     // form alignment+groups+annotation,preprocess and then record references
98     // for formatters
99     ArrayList<InputType> alinp = new ArrayList<InputType>();
100     int paramsWithData = 0;
101     // TODO: JAL-715 - generalise the following validation logic for all
102     // parameter types
103     // we cheat for moment - since we know a-priori what data is available and
104     // what inputs we have implemented so far
105     for (Map.Entry<String, InputType> prm : rsd.inputParams.entrySet())
106     {
107       if (!prm.getValue().isConstant())
108       {
109         if (prm.getValue() instanceof Alignment)
110         {
111           alinp.add(prm.getValue());
112         }
113         else
114         {
115           if (prm.getValue() instanceof SeqGroupIndexVector
116                   && _input.getGroups() != null
117                   && _input.getGroups().size() >= -1 + prm.getValue().min)
118           {
119             // the test above is not rigorous but fixes JAL-1298, since
120             // submission will fail if the partition set doesn't contain at
121             // least one partition
122             alinp.add(prm.getValue());
123           }
124           else
125           {
126             statMessage = ("Not enough groups defined on the alignment - need at least "
127                     + prm.getValue().min);
128           }
129         }
130       }
131       else
132       {
133         paramsWithData++;
134       }
135     }
136     if ((paramsWithData + alinp.size()) == rsd.inputParams.size())
137     {
138       inputOrder = new AlignmentOrder(_input);
139       if ((dsForIO = _input.getDataset()) == null)
140       {
141         _input.setDataset(null);
142       }
143       dsForIO = _input.getDataset();
144       if (contextAl == null)
145       {
146         contextAl = _input;
147       }
148       setAlignmentForInputs(alinp, _input);
149       validInput = true;
150     }
151     else
152     {
153       // not enough data, so we bail.
154       validInput = false;
155     }
156   }
157
158   boolean validInput = false;
159
160   @Override
161   public boolean hasResults()
162   {
163     return gotresult && (parsedResults ? validJvresults : true);
164   }
165
166   @Override
167   public boolean hasValidInput()
168   {
169     return validInput;
170   }
171
172   @Override
173   public boolean isRunning()
174   {
175     return running; // TODO: can we check the response body for status messages
176                     // ?
177   }
178
179   @Override
180   public boolean isQueued()
181   {
182     return waiting;
183   }
184
185   @Override
186   public boolean isFinished()
187   {
188     return resSet != null;
189   }
190
191   @Override
192   public boolean isFailed()
193   {
194     // TODO logic for error
195     return error;
196   }
197
198   @Override
199   public boolean isBroken()
200   {
201     // TODO logic for error
202     return error;
203   }
204
205   @Override
206   public boolean isServerError()
207   {
208     // TODO logic for error
209     return error;
210   }
211
212   @Override
213   public boolean hasStatus()
214   {
215     return statMessage != null;
216   }
217
218   protected String statMessage = null;
219
220   public HttpResultSet resSet;
221
222   @Override
223   public String getStatus()
224   {
225     return statMessage;
226   }
227
228   @Override
229   public boolean hasResponse()
230   {
231     return statMessage != null || resSet != null;
232   }
233
234   @Override
235   public void clearResponse()
236   {
237     // only clear the transient server response
238     // statMessage=null;
239   }
240
241   /*
242    * (non-Javadoc)
243    * 
244    * @see jalview.ws.AWsJob#getState()
245    */
246   @Override
247   public String getState()
248   {
249     // TODO generate state string - prolly should have a default abstract method
250     // for this
251     return "Job is clueless";
252   }
253
254   public String getPostUrl()
255   {
256
257     // TODO Auto-generated method stub
258     return rsd.postUrl;
259   }
260
261   public Set<Map.Entry<String, InputType>> getInputParams()
262   {
263     return rsd.inputParams.entrySet();
264   }
265
266   // return the URL that should be polled for this job
267   public String getPollUrl()
268   {
269     return rsd.getDecoratedResultUrl(jobId);
270   }
271
272   /**
273    * 
274    * @return the context for parsing results from service
275    */
276   public JalviewDataset newJalviewDataset()
277   {
278     if (context == null)
279     {
280       context = new JalviewDataset(dsForIO, null, squniq, null);
281       if (contextAl != null)
282       {
283         // TODO devise way of merging new annotation onto (identical) existing
284         // annotation that was used as input
285         // delete all input annotation
286         if (contextAl.getAlignmentAnnotation() != null)
287         {
288           for (AlignmentAnnotation alan : contextAl
289                   .getAlignmentAnnotation())
290           {
291             contextAl.deleteAnnotation(alan);
292           }
293         }
294         // TODO devise way of merging new groups onto (identical) existing
295         // groups when they were used as input to service
296         // delete all existing groups
297         if (contextAl.getGroups() != null)
298         {
299           contextAl.deleteAllGroups();
300         }
301         context.addAlignment(contextAl);
302       }
303
304     }
305     return context;
306   }
307
308   /**
309    * Extract list of sequence IDs for input parameter 'token' with given
310    * molecule type
311    * 
312    * @param token
313    * @param type
314    * @return
315    */
316   public SequenceI[] getSequencesForInput(String token,
317           InputType.molType type) throws NoValidInputDataException
318   {
319     Object sgdat = inputData.get(token);
320     // can we form an alignment from this data ?
321     if (sgdat == null)
322     {
323       throw new NoValidInputDataException(
324               "No Sequence vector data bound to input '" + token
325                       + "' for service at " + rsd.postUrl);
326     }
327     if (sgdat instanceof AlignmentI)
328     {
329       return ((AlignmentI) sgdat).getSequencesArray();
330     }
331     if (sgdat instanceof SequenceGroup)
332     {
333       return ((SequenceGroup) sgdat).getSequencesAsArray(null);
334     }
335     if (sgdat instanceof Vector)
336     {
337       if (((Vector) sgdat).size() > 0
338               && ((Vector) sgdat).get(0) instanceof SequenceI)
339       {
340         SequenceI[] sq = new SequenceI[((Vector) sgdat).size()];
341         ((Vector) sgdat).copyInto(sq);
342         return sq;
343       }
344     }
345     throw new NoValidInputDataException(
346             "No Sequence vector data bound to input '" + token
347                     + "' for service at " + rsd.postUrl);
348   }
349
350   /**
351    * binding between input data (AlignmentI, SequenceGroup, NJTree) and input
352    * param names.
353    */
354   private Hashtable<String, Object> inputData = new Hashtable<String, Object>();
355
356   /**
357    * is the job fully submitted to server and apparently in progress ?
358    */
359   public boolean running = false;
360
361   /**
362    * 
363    * @param itypes
364    * @param al
365    *          - reference to object to be stored as input. Note - input data may
366    *          be modifed by formatter
367    */
368   public void setAlignmentForInputs(Collection<InputType> itypes,
369           AlignmentI al)
370   {
371     for (InputType itype : itypes)
372     {
373       if (!rsd.inputParams.values().contains(itype))
374       {
375         throw new IllegalArgumentException("InputType " + itype.getClass()
376                 + " is not valid for service at " + rsd.postUrl);
377       }
378       if (itype instanceof AlignmentProcessor)
379       {
380         ((AlignmentProcessor) itype).prepareAlignment(al);
381       }
382       // stash a reference for recall when the alignment data is formatted
383       inputData.put(itype.token, al);
384     }
385
386   }
387
388   /**
389    * 
390    * @param token
391    * @param type
392    * @return alignment object bound to the given token
393    * @throws NoValidInputDataException
394    */
395   public AlignmentI getAlignmentForInput(String token,
396           InputType.molType type) throws NoValidInputDataException
397   {
398     Object al = inputData.get(token);
399     // can we form an alignment from this data ?
400     if (al == null || !(al instanceof AlignmentI))
401     {
402       throw new NoValidInputDataException(
403               "No alignment data bound to input '" + token
404                       + "' for service at " + rsd.postUrl);
405     }
406     return (AlignmentI) al;
407   }
408
409   /**
410    * test to see if the job has data of type cl that's needed for the job to run
411    * 
412    * @param cl
413    * @return true or false
414    */
415   public boolean hasDataOfType(Class cl)
416   {
417     if (AlignmentI.class.isAssignableFrom(cl))
418     {
419       return true;
420     }
421     // TODO: add more source data types
422
423     return false;
424   }
425
426   /**
427    * context used to parse results from service
428    */
429   JalviewDataset context = null;
430
431   protected boolean parsedResults = false;
432
433   protected boolean validJvresults = false;
434
435   Object[] jvresultobj = null;
436
437   /**
438    * process the results obtained from the server into jalview datamodel objects
439    * ready to be merged/added to the users' view. Use hasResults to test if
440    * results were added to context.
441    */
442   public void parseResultSet() throws Exception, Error
443   {
444     if (!parsedResults)
445     {
446       parsedResults = true;
447       jvresultobj = resSet.parseResultSet();
448       validJvresults = true;
449     }
450   }
451
452   /**
453    * 
454    * @return true if job has an input alignment and it was annotated when
455    *         results were parsed
456    */
457   public boolean isInputContextModified()
458   {
459     return contextAl != null && validJvresults
460             && context.getAl().get(0).isModified();
461   }
462
463   /**
464    * 
465    * @return true if the ID/metadata for the input sequences were saved and
466    *         sequence IDs renamed.
467    */
468   public boolean isInputUniquified()
469   {
470     // TODO Auto-generated method stub
471     return false;
472   }
473
474   /**
475    * Return map between ordering of alignment submitted as input, and ordering
476    * of alignment as provided by user
477    * 
478    * @return int[sequence index in submitted data]==sequence index in input.
479    */
480   public int[] getOrderMap()
481   {
482     SequenceI[] contseq = contextAl.getSequencesArray();
483     int map[] = new int[contseq.length];
484     for (int i = 0; i < contseq.length; i++)
485     {
486       // TODO: optimise for large N - build a lookup hash for IDs returning
487       // order, and then lookup each sequ's original order
488       map[i] = inputOrder.getOrder().indexOf(contseq[i]);
489     }
490     return map;
491   }
492
493 }