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