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