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