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