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