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