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