formatting
[jalview.git] / src / jalview / ws / rest / RestJob.java
1 /*******************************************************************************
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, 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.io.IOException;
21 import java.sql.ResultSet;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Hashtable;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Set;
29 import java.util.Vector;
30
31 import jalview.datamodel.AlignmentAnnotation;
32 import jalview.datamodel.AlignmentI;
33 import jalview.datamodel.AlignmentOrder;
34 import jalview.datamodel.AlignmentView;
35 import jalview.datamodel.SequenceGroup;
36 import jalview.datamodel.SequenceI;
37 import jalview.io.packed.DataProvider;
38 import jalview.io.packed.JalviewDataset;
39 import jalview.io.packed.ParsePackedSet;
40 import jalview.io.packed.SimpleDataProvider;
41 import jalview.io.packed.DataProvider.JvDataType;
42 import jalview.ws.AWsJob;
43 import jalview.ws.rest.params.Alignment;
44 import jalview.ws.rest.params.SeqGroupIndexVector;
45
46 public class RestJob extends AWsJob
47 {
48
49   // TODO: input alignmentview and other data for this job
50   RestServiceDescription rsd;
51
52   // boolean submitted;
53   boolean gotresponse;
54
55   boolean error;
56
57   boolean waiting;
58
59   boolean gotresult;
60
61   Hashtable squniq;
62
63   /**
64    * dataset associated with this input data.
65    */
66   AlignmentI dsForIO;
67
68   AlignmentOrder inputOrder;
69
70   /**
71    * context of input data with respect to an AlignmentView's visible contigs.
72    */
73   int[] origviscontig;
74
75   private AlignmentI contextAl = null;
76
77   /**
78    * create a rest job using data bounded by the given start/end column.
79    * 
80    * @param addJobPane
81    * @param restJobThread
82    * @param _input
83    * @param viscontigs
84    *          visible contigs of an alignment view from which _input was derived
85    */
86   public RestJob(int jobNum, RestJobThread restJobThread,
87           AlignmentI _input, int[] viscontigs)
88   {
89     rsd = restJobThread.restClient.service;
90     jobnum = jobNum;
91     if (viscontigs != null)
92     {
93       origviscontig = new int[viscontigs.length];
94       System.arraycopy(viscontigs, 0, origviscontig, 0, viscontigs.length);
95     }
96     // get sequences for the alignmentI
97     // get groups trimmed to alignment columns
98     // get any annotation trimmed to start/end columns, too.
99
100     // prepare input
101     // form alignment+groups+annotation,preprocess and then record references
102     // for formatters
103     ArrayList<InputType> alinp = new ArrayList<InputType>();
104     int paramsWithData = 0;
105     // we cheat for moment - since we know a-priori what data is available and
106     // what inputs we have implemented so far
107     for (Map.Entry<String, InputType> prm : rsd.inputParams.entrySet())
108     {
109       if (!prm.getValue().isConstant())
110       {
111         if (prm.getValue() instanceof Alignment)
112         {
113           alinp.add(prm.getValue());
114         }
115         else
116         {
117           // TODO: move validation of input data to SeqGroupIndexVector
118           if ((prm.getValue() instanceof SeqGroupIndexVector)
119                   && (_input.getGroups() != null && _input.getGroups()
120                           .size() >= prm.getValue().min))
121           {
122             alinp.add(prm.getValue());
123           }
124           else
125           {
126             statMessage = ("Not enough groups defined on the alignment - need at least " + prm
127                     .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 }