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