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