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