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