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