4c51d57efa150a49f830c4275584d9234919b026
[jalview.git] / src / jalview / ws / rest / InputType.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 jalview.ws.params.ArgumentI;
21 import jalview.ws.params.InvalidArgumentException;
22 import jalview.ws.params.OptionI;
23 import jalview.ws.params.ParameterI;
24 import jalview.ws.params.simple.IntegerParameter;
25 import jalview.ws.params.simple.Option;
26
27 import java.io.UnsupportedEncodingException;
28 import java.nio.charset.Charset;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.List;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34
35 import org.apache.http.entity.mime.content.ContentBody;
36 import org.apache.http.entity.mime.content.StringBody;
37
38 /***
39  * InputType is the abstract model of each input parameter that a rest service
40  * might take. It enables the engine to validate input by providing { formatter
41  * for type, parser for type }
42  * 
43  */
44 public abstract class InputType
45 {
46   /**
47    * not used yet
48    */
49   boolean replaceids;
50
51   public enum molType
52   {
53     NUC, PROT, MIX;
54
55     public static Collection<String> toStringValues()
56     {
57       Collection<String> c = new ArrayList<String>();
58       for (molType type : values())
59       {
60         c.add(type.toString());
61       }
62       return c;
63     }
64   }
65
66   public String token;
67
68   public int min = 1;
69
70   public int max = 0; // unbounded
71
72   protected ArrayList<Class> inputData = new ArrayList<Class>();
73
74   /**
75    * initialise the InputType with a list of jalview data classes that the
76    * RestJob needs to be able to provide to it.
77    * 
78    * @param types
79    */
80   protected InputType(Class[] types)
81   {
82     if (types != null)
83     {
84       for (Class t : types)
85       {
86         inputData.add(t);
87       }
88     }
89   }
90
91   /**
92    * do basic tests to ensure the job's service takes this parameter, and the
93    * job's input data can be used to generate the input data
94    * 
95    * @param restJob
96    * @return
97    */
98   public boolean validFor(RestJob restJob)
99   {
100     if (!validFor(restJob.rsd))
101       return false;
102     for (Class cl : inputData)
103     {
104       if (!restJob.hasDataOfType(cl))
105       {
106         return false;
107       }
108     }
109     return true;
110   }
111
112   public boolean validFor(RestServiceDescription restServiceDescription)
113   {
114     if (!restServiceDescription.inputParams.values().contains(this))
115       return false;
116
117     return true;
118   }
119
120   protected ContentBody utf8StringBody(String content, String type)
121   {
122     Charset utf8 = Charset.forName("UTF-8");
123     try
124     {
125       if (type == null)
126       {
127         return new StringBody(utf8.encode(content).asCharBuffer()
128                 .toString());
129       }
130       else
131       {
132         return new StringBody(utf8.encode(content).asCharBuffer()
133                 .toString(), type, utf8);
134       }
135     } catch (Exception ex)
136     {
137       System.err.println("Couldn't transform string\n" + content
138               + "\nException was :");
139       ex.printStackTrace(System.err);
140     }
141     return null;
142   }
143
144   /**
145    * 
146    * @param rj
147    *          data from which input is to be extracted and formatted
148    * @return StringBody or FileBody ready for posting
149    */
150   abstract public ContentBody formatForInput(RestJob rj)
151           throws UnsupportedEncodingException, NoValidInputDataException;
152
153   /**
154    * 
155    * @return true if no input data needs to be provided for this parameter
156    */
157   public boolean isConstant()
158   {
159     return (inputData == null || inputData.size() == 0);
160   }
161
162   /**
163    * return a url encoded version of this parameter's value, or an empty string
164    * if the parameter has no ='value' content.
165    * 
166    * @return
167    */
168   public abstract List<String> getURLEncodedParameter();
169
170   /**
171    * set the property known as tok, possibly by assigning it with a given val
172    * 
173    * @param tok
174    * @param val
175    *          (may be empty or null)
176    * @param warnings
177    *          place where parse warnings are reported
178    * @return true if property was set
179    */
180   public abstract boolean configureProperty(String tok, String val,
181           StringBuffer warnings);
182
183   /**
184    * Get unique key for this type of parameter in a URL encoding.
185    * 
186    * @return the string that prefixes an input parameter of InputType<T> type in
187    *         the string returned from getURLEncodedParameter
188    */
189   public abstract String getURLtokenPrefix();
190
191   /**
192    * parse the given token String and set InputParameter properties
193    * appropriately
194    * 
195    * @param tokenstring
196    *          - urlencoded parameter string as returned from
197    *          getURLEncodedParameter
198    * @param warnings
199    *          - place where any warning messages about bad property values are
200    *          written
201    * @return true if configuration succeeded, false otherwise.
202    */
203   public boolean configureFromURLtokenString(List<String> tokenstring,
204           StringBuffer warnings)
205   {
206     boolean valid = true;
207     for (String tok : tokenstring)
208     {
209       Matcher mtch = Pattern.compile("^([^=]+)=?'?([^']*)?'?").matcher(tok);
210       if (mtch.find())
211       {
212         try
213         {
214           if (mtch.group(1).equals("min"))
215           {
216             min = Integer.parseInt(mtch.group(2));
217             continue;
218
219           }
220           else if (mtch.group(1).equals("max"))
221           {
222             max = Integer.parseInt(mtch.group(2));
223             continue;
224           }
225         } catch (NumberFormatException x)
226         {
227           valid = false;
228           warnings.append("Invalid value for parameter "
229                   + mtch.group(1).toLowerCase() + " '" + mtch.group(2)
230                   + "' (expected an integer)\n");
231         }
232
233         if (!configureProperty(mtch.group(1), mtch.group(2), warnings))
234         {
235           if (warnings.length() == 0)
236           {
237             warnings.append("Failed to configure InputType :"
238                     + getURLtokenPrefix() + " with property string: '"
239                     + mtch.group(0) + "'\n (token is '" + mtch.group(1)
240                     + "' and value is '" + mtch.group(2) + "')\n");
241           }
242           valid = false;
243         }
244       }
245     }
246     return valid;
247   }
248
249   public void addBaseParams(ArrayList<String> prms)
250   {
251     // todo : check if replaceids should be a global for the service, rather
252     // than for a specific parameter.
253     if (min != 1)
254     {
255       prms.add("min='" + min + "'");
256     }
257     if (max != 0)
258     {
259       prms.add("max='" + max + "'");
260     }
261   }
262
263   public abstract List<OptionI> getOptions();
264
265   public List<OptionI> getBaseOptions()
266   {
267     ArrayList<OptionI> opts = new ArrayList<OptionI>();
268     opts.add(new IntegerParameter("min",
269             "Minimum number of data of this type", true, 1, min, 0, -1));
270     opts.add(new IntegerParameter("max",
271             "Maximum number of data of this type", false, 0, max, 0, -1));
272     return opts;
273   }
274
275   /**
276    * make a copy of this InputType
277    * 
278    * @return may not be needed public abstract InputType copy();
279    */
280
281   /**
282    * parse a set of configuration options
283    * 
284    * @param currentSettings
285    *          - modified settings originally from getOptions
286    * @throws InvalidArgumentException
287    *           thrown if currentSettings contains invalid options for this type.
288    */
289   public void configureFromArgumentI(List<ArgumentI> currentSettings)
290           throws InvalidArgumentException
291   {
292     ArrayList<String> urltoks = new ArrayList<String>();
293     String rg;
294     for (ArgumentI arg : currentSettings)
295     {
296       if (arg instanceof ParameterI)
297       {
298         rg = arg.getName() + "='" + arg.getValue() + "'";
299       }
300       else
301       {
302         // TODO: revise architecture - this is counter intuitive - options with
303         // different values to their names are actually parameters
304         rg = (arg.getValue().length() > 0) ? (arg.getValue().equals(
305                 arg.getName()) ? arg.getName() : arg.getName() + "='"
306                 + arg.getValue() + "'") : arg.getName();
307       }
308       if (rg.length() > 0)
309       {
310         urltoks.add(rg);
311       }
312     }
313     StringBuffer warnings;
314     if (!configureFromURLtokenString(urltoks, warnings = new StringBuffer()))
315     {
316       throw new InvalidArgumentException(warnings.toString());
317     }
318   }
319
320   protected OptionI createMolTypeOption(String name, String descr,
321           boolean req, molType curType, molType defType)
322   {
323     return new Option(name, descr, req, defType == null ? ""
324             : defType.toString(),
325             curType == null ? "" : curType.toString(),
326             molType.toStringValues(), null);
327   }
328 }