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