2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3 * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
5 * This file is part of Jalview.
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.
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.
16 * You should have received a copy of the GNU General Public License along with Jalview. If not, see <http://www.gnu.org/licenses/>.
18 package jalview.ws.rest;
20 import jalview.datamodel.SequenceI;
21 import jalview.io.packed.DataProvider;
22 import jalview.io.packed.SimpleDataProvider;
23 import jalview.io.packed.DataProvider.JvDataType;
24 import jalview.util.GroupUrlLink.UrlStringTooLongException;
25 import jalview.util.Platform;
26 import jalview.ws.rest.params.Alignment;
27 import jalview.ws.rest.params.AnnotationFile;
28 import jalview.ws.rest.params.JobConstant;
29 import jalview.ws.rest.params.SeqGroupIndexVector;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.Hashtable;
35 import java.util.List;
37 import java.util.NoSuchElementException;
38 import java.util.StringTokenizer;
39 import java.util.Vector;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
43 import javax.swing.JViewport;
45 import com.stevesoft.pat.Regex;
46 import com.sun.org.apache.xml.internal.serialize.OutputFormat.DTD;
47 import com.sun.tools.doclets.internal.toolkit.util.DocFinder.Output;
49 public class RestServiceDescription
60 public RestServiceDescription(String action, String description,
61 String name, String postUrl, String urlSuffix,
62 Map<String, InputType> inputParams, boolean hseparable,
63 boolean vseparable, char gapCharacter)
66 this.details = new UIinfo();
67 details.Action = action == null ? "" : action;
68 details.description = description == null ? "" : description;
69 details.Name = name == null ? "" : name;
70 this.postUrl = postUrl == null ? "" : postUrl;
71 this.urlSuffix = urlSuffix == null ? "" : urlSuffix;
72 if (inputParams != null)
74 this.inputParams = inputParams;
76 this.hseparable = hseparable;
77 this.vseparable = vseparable;
78 this.gapCharacter = gapCharacter;
81 public boolean equals(Object o)
83 if (o == null || !(o instanceof RestServiceDescription))
87 RestServiceDescription other = (RestServiceDescription) o;
88 boolean diff = (gapCharacter != other.gapCharacter);
89 diff |= vseparable != other.vseparable;
90 diff |= hseparable != other.hseparable;
91 diff |= !(urlSuffix.equals(other.urlSuffix));
92 // TODO - robust diff that includes constants and reordering of URL
93 // diff |= !(postUrl.equals(other.postUrl));
94 // diff |= !inputParams.equals(other.inputParams);
95 diff |= !details.Name.equals(other.details.Name);
96 diff |= !details.Action.equals(other.details.Action);
97 diff |= !details.description.equals(other.details.description);
102 * Service UI Info { Action, Specific Name of Service, Brief Description }
107 public String getAction()
112 public void setAction(String action)
117 public String getName()
122 public void setName(String name)
127 public String getDescription()
132 public void setDescription(String description)
134 this.description = description;
144 public UIinfo details = new UIinfo();
146 public String getAction()
148 return details.getAction();
151 public void setAction(String action)
153 details.setAction(action);
156 public String getName()
158 return details.getName();
161 public void setName(String name)
163 details.setName(name);
166 public String getDescription()
168 return details.getDescription();
171 public void setDescription(String description)
173 details.setDescription(description);
181 public String getPostUrl()
186 public void setPostUrl(String postUrl)
188 this.postUrl = postUrl;
191 public String getUrlSuffix()
196 public void setUrlSuffix(String urlSuffix)
198 this.urlSuffix = urlSuffix;
201 public Map<String, InputType> getInputParams()
206 public void setInputParams(Map<String, InputType> inputParams)
208 this.inputParams = inputParams;
211 public void setHseparable(boolean hseparable)
213 this.hseparable = hseparable;
216 public void setVseparable(boolean vseparable)
218 this.vseparable = vseparable;
221 public void setGapCharacter(char gapCharacter)
223 this.gapCharacter = gapCharacter;
227 * suffix that should be added to any url used if it does not already end in
233 * input info given as key/value pairs - mapped to post arguments
235 Map<String, InputType> inputParams = new HashMap<String, InputType>();
238 * assigns the given inputType it to its corresponding input parameter token
243 public void setInputParam(InputType it)
245 inputParams.put(it.token, it);
249 * remove the given input type it from the set of service input parameters.
253 public void removeInputParam(InputType it)
255 inputParams.remove(it.token);
259 * service requests alignment data
264 * service requests alignment and/or seuqence annotationo data
269 * service requests partitions defined over input (alignment) data
271 boolean partitiondata;
274 * process ths input data and set the appropriate shorthand flags describing
275 * the input the service wants
277 public void setInvolvesFlags()
279 aligndata = inputInvolves(Alignment.class);
280 annotdata = inputInvolves(AnnotationFile.class);
281 partitiondata = inputInvolves(SeqGroupIndexVector.class);
285 * Service return info { alignment, annotation file (loaded back on to
286 * alignment), tree (loaded back on to alignment), sequence annotation -
287 * loaded back on to alignment), text report, pdb structures with sequence
293 * Start with bare minimum: input is alignment + groups on alignment
299 private String invalidMessage = null;
302 * parse the given linkString of the form '<label>|<url>|separator
303 * char[|optional sequence separator char]' into parts. url may contain a
304 * string $SEQUENCEIDS<=optional regex=>$ where <=optional regex=> must be of
305 * the form =/<perl style regex>/=$ or $SEQUENCES<=optional regex=>$ or
306 * $SEQUENCES<=optional regex=>$.
310 public RestServiceDescription(String link)
312 StringBuffer warnings = new StringBuffer();
313 if (!configureFromEncodedString(link, warnings))
315 if (warnings.length() > 0)
317 invalidMessage = warnings.toString();
322 public RestServiceDescription(RestServiceDescription toedit)
324 // Rather then do the above, we cheat and use our human readable
325 // serialization code to clone everything
326 this(toedit.toString());
328 * if (toedit == null) { return; } /** urlSuffix = toedit.urlSuffix; postUrl
329 * = toedit.postUrl; hseparable = toedit.hseparable; vseparable =
330 * toedit.vseparable; gapCharacter = toedit.gapCharacter; details = new
331 * RestServiceDescription.UIinfo(); details.Action = toedit.details.Action;
332 * details.description = toedit.details.description; details.Name =
333 * toedit.details.Name; for (InputType itype: toedit.inputParams.values()) {
334 * inputParams.put(itype.token, itype.clone());
338 // TODO Implement copy constructor NOW*/
342 * @return the invalidMessage
344 public String getInvalidMessage()
346 return invalidMessage;
350 * Check if URL string was parsed properly.
352 * @return boolean - if false then <code>getInvalidMessage</code> returns an
355 public boolean isValid()
357 return invalidMessage == null;
360 private static boolean debug = false;
363 * parse the string into a list
367 * @return elements separated by separator
369 public static String[] separatorListToArray(String list, String separator)
371 int seplen = separator.length();
372 if (list == null || list.equals("") || list.equals(separator))
374 java.util.ArrayList<String> jv = new ArrayList<String>();
375 int cp = 0, pos, escape;
376 boolean wasescaped = false,wasquoted=false;
377 String lstitem = null;
378 while ((pos = list.indexOf(separator, cp)) >= cp)
381 escape = (pos > 0 && list.charAt(pos - 1) == '\\') ? -1 : 0;
382 if (wasescaped || wasquoted)
384 // append to previous pos
385 jv.set(jv.size() - 1,
386 lstitem = lstitem + separator
387 + list.substring(cp, pos + escape));
392 jv.add(lstitem = list.substring(cp, pos + escape));
395 wasescaped = escape == -1;
398 // last separator may be in an unmatched quote
399 if (java.util.regex.Pattern.matches("('[^']*')*[^']*'",lstitem))
406 if (cp < list.length())
408 String c = list.substring(cp);
409 if (wasescaped || wasquoted)
411 // append final separator
412 jv.set(jv.size() - 1, lstitem + separator + c);
416 if (!c.equals(separator))
424 String[] v = jv.toArray(new String[jv.size()]);
428 System.err.println("Array from '" + separator
429 + "' separated List:\n" + v.length);
430 for (int i = 0; i < v.length; i++)
432 System.err.println("item " + i + " '" + v[i] + "'");
439 System.err.println("Empty Array from '" + separator
440 + "' separated List");
446 * concatenate the list with separator
450 * @return concatenated string
452 public static String arrayToSeparatorList(String[] list, String separator)
454 StringBuffer v = new StringBuffer();
455 if (list != null && list.length > 0)
457 for (int i = 0, iSize = list.length; i < iSize; i++)
465 // TODO - escape any separator values in list[i]
471 System.err.println("Returning '" + separator
472 + "' separated List:\n");
473 System.err.println(v);
479 System.err.println("Returning empty '" + separator
480 + "' separated List\n");
482 return "" + separator;
486 * parse a string containing a list of service properties and configure the
487 * service description
490 * param warnings a StringBuffer that any warnings about invalid
491 * content will be appended to.
493 private boolean configureFromServiceInputProperties(String propList,
494 StringBuffer warnings)
496 String[] props = separatorListToArray(propList, ",");
502 boolean valid = true;
505 for (String prop : props)
507 if ((i = prop.indexOf("=")) > -1)
509 val = prop.substring(i + 1);
510 if (val.startsWith("\'") && val.endsWith("\'"))
512 val = val.substring(1, val.length() - 1);
514 prop = prop.substring(0, i);
517 if (prop.equals("hseparable"))
521 if (prop.equals("vseparable"))
525 if (prop.equals("gapCharacter"))
527 if (val == null || val.length() == 0 || val.length() > 1)
530 warnings.append((warnings.length() > 0 ? "\n" : "")
531 + ("Invalid service property: gapCharacter=' ' (single character) - was given '"
536 gapCharacter = val.charAt(0);
539 if (prop.equals("returns"))
541 int l = warnings.length();
542 _configureOutputFormatFrom(val, warnings);
543 valid = (l != warnings.length());
549 private String _genOutputFormatString()
552 if (resultData == null)
556 for (JvDataType type : resultData)
558 if (buff.length() > 0)
562 buff += type.toString();
567 private void _configureOutputFormatFrom(String outstring,
568 StringBuffer warnings)
570 if (outstring.indexOf(";") == -1)
572 // we add a token, for simplicity
573 outstring = outstring + ";";
575 StringTokenizer st = new StringTokenizer(outstring, ";");
577 resultData = new ArrayList<JvDataType>();
578 while (st.hasMoreTokens())
582 resultData.add(JvDataType.valueOf(tok = st.nextToken()));
583 } catch (NoSuchElementException x)
585 warnings.append("Invalid result type: '" + tok
586 + "' (must be one of: ");
588 for (JvDataType vl : JvDataType.values())
590 warnings.append(sep);
591 warnings.append(vl.toString());
594 warnings.append(" separated by semi-colons)\n");
599 private String getServiceIOProperties()
601 String[] vls = new String[]
602 { isHseparable() ? "hseparable" : "",
603 isVseparable() ? "vseparable" : "",
604 (new String("gapCharacter='" + gapCharacter + "'")),
605 (new String("returns='" + _genOutputFormatString() + "'")) };
607 return arrayToSeparatorList(vls, ",");
610 public String toString()
612 StringBuffer result = new StringBuffer();
613 result.append(details.Name);
615 result.append(details.Action);
617 if (details.description != null)
619 result.append(details.description);
622 // list job input flags
624 result.append(getServiceIOProperties());
625 // list any additional cgi parameters needed for result retrieval
626 if (urlSuffix != null && urlSuffix.length() > 0)
629 result.append(urlSuffix);
632 result.append(getInputParamEncodedUrl());
633 return result.toString();
636 public boolean configureFromEncodedString(String encoding,
637 StringBuffer warnings)
639 boolean invalid = false;
640 String[] list = separatorListToArray(encoding, "|");
641 details.Name = list[0];
642 details.Action = list[1];
643 details.description = list[2];
644 invalid |= !configureFromServiceInputProperties(list[3], warnings);
648 invalid |= !configureFromInputParamEncodedUrl(list[5], warnings);
655 invalid |= !configureFromInputParamEncodedUrl(list[4], warnings);
662 * @return string representation of the input parameters, their type and
663 * constraints, appended to the service's base submission URL
665 private String getInputParamEncodedUrl()
667 StringBuffer url = new StringBuffer();
668 if (postUrl == null || postUrl.length() < 5)
674 char appendChar = (postUrl.indexOf("?") > -1) ? '&' : '?';
675 boolean consts = true;
678 for (Map.Entry<String, InputType> param : inputParams.entrySet())
680 List<String> vals = param.getValue().getURLEncodedParameter();
681 if (param.getValue().isConstant())
685 url.append(appendChar);
687 url.append(param.getValue().token);
688 if (vals.size() == 1)
691 url.append(vals.get(0));
699 url.append(appendChar);
701 url.append(param.getValue().token);
703 // write parameter set as $TOKENPREFIX:csv list of params$ for this
706 url.append(param.getValue().getURLtokenPrefix());
708 url.append(arrayToSeparatorList(vals.toArray(new String[0]),
715 // toggle consts and repeat until !consts is false:
716 } while (!(consts = !consts));
717 return url.toString();
721 * parse the service URL and input parameters from the given encoded URL
722 * string and configure the RestServiceDescription from it.
727 * @return true if URL parsed correctly. false means the configuration failed.
729 private boolean configureFromInputParamEncodedUrl(String ipurl,
730 StringBuffer warnings)
732 boolean valid = true;
734 String url = new String();
735 Matcher prms = Pattern.compile("([?&])([A-Za-z0-9_]+)=\\$([^$]+)\\$")
737 Map<String, InputType> iparams = new Hashtable<String, InputType>();
741 if (lastp < prms.start(0))
743 url += ipurl.substring(lastp, prms.start(0));
744 lastp = prms.end(0) + 1;
746 String sep = prms.group(1);
747 String tok = prms.group(2);
748 String iprm = prms.group(3);
749 int colon = iprm.indexOf(":");
750 String iprmparams = "";
753 iprmparams = iprm.substring(colon + 1);
754 iprm = iprm.substring(0, colon);
756 valid = parseTypeString(prms.group(0), tok, iprm, iprmparams,
763 URL u = new URL(url);
765 inputParams = iparams;
766 } catch (Exception e)
768 warnings.append("Failed to parse '" + url + "' as a URL.\n");
775 public static Class[] getInputTypes()
777 // TODO - find a better way of maintaining this classlist
779 { jalview.ws.rest.params.Alignment.class,
780 jalview.ws.rest.params.AnnotationFile.class,
781 SeqGroupIndexVector.class,
782 jalview.ws.rest.params.SeqIdVector.class,
783 jalview.ws.rest.params.SeqVector.class,
784 jalview.ws.rest.params.Tree.class };
787 public static boolean parseTypeString(String fullstring, String tok,
788 String iprm, String iprmparams, Map<String, InputType> iparams,
789 StringBuffer warnings)
791 boolean valid = true;
793 for (Class type : getInputTypes())
797 jinput = (InputType) (type.getConstructor().newInstance(null));
798 if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix()))
800 ArrayList<String> al = new ArrayList<String>();
801 for (String prprm : separatorListToArray(iprmparams, ","))
803 // hack to ensure that strings like "sep=','" containing unescaped commas as values are concatenated
804 al.add(prprm.trim());
806 if (!jinput.configureFromURLtokenString(al, warnings))
809 warnings.append("Failed to parse '" + fullstring + "' as a "
810 + jinput.getURLtokenPrefix() + " input tag.\n");
815 iparams.put(tok, jinput);
821 } catch (Throwable thr)
829 public static void main(String argv[])
831 // test separator list
833 assert(separatorListToArray("foo=',',min='foo',max='1,2,3',fa=','", ",").length==4);
834 if (separatorListToArray("minsize='2', sep=','", ",").length==2)
839 } catch (AssertionError x)
841 System.err.println("separatorListToArray is faulty.");
843 if (argv.length == 0)
845 if (!testRsdExchange("Test using default Shmmr service",
846 RestClient.makeShmmrRestClient().service))
848 System.err.println("default test failed.");
852 System.err.println("default test passed.");
858 for (String svc : argv)
860 p += testRsdExchange("Test " + (++i), svc) ? 1 : 0;
862 System.err.println("" + p + " out of " + i + " tests passed.");
867 private static boolean testRsdExchange(String desc, String servicestring)
871 RestServiceDescription newService = new RestServiceDescription(
873 if (!newService.isValid())
875 throw new Error("Failed to create service from '" + servicestring
876 + "'.\n" + newService.getInvalidMessage());
878 return testRsdExchange(desc, newService);
879 } catch (Throwable x)
881 System.err.println("Failed for service (" + desc + "): "
888 private static boolean testRsdExchange(String desc,
889 RestServiceDescription service)
893 String fromservicetostring = service.toString();
894 RestServiceDescription newService = new RestServiceDescription(
895 fromservicetostring);
896 if (!newService.isValid())
898 throw new Error("Failed to create service from '"
899 + fromservicetostring + "'.\n"
900 + newService.getInvalidMessage());
903 if (!service.equals(newService))
905 System.err.println("Failed for service (" + desc + ").");
906 System.err.println("Original service and parsed service differ.");
907 System.err.println("Original: " + fromservicetostring);
908 System.err.println("Parsed : " + newService.toString());
911 } catch (Throwable x)
913 System.err.println("Failed for service (" + desc + "): "
914 + service.toString());
922 * covenience method to generate the id and sequence string vector from a set
923 * of seuqences using each sequence's getName() and getSequenceAsString()
927 * @return String[][] {{sequence ids},{sequence strings}}
929 public static String[][] formStrings(SequenceI[] seqs)
931 String[][] idset = new String[2][seqs.length];
932 for (int i = 0; i < seqs.length; i++)
934 idset[0][i] = seqs[i].getName();
935 idset[1][i] = seqs[i].getSequenceAsString();
941 * can this service be run on the visible portion of an alignment regardless
942 * of hidden boundaries ?
944 boolean hseparable = false;
946 boolean vseparable = false;
948 public boolean isHseparable()
957 public boolean isVseparable()
963 * search the input types for an instance of the given class
965 * @param <validInput.inputType> class1
968 public boolean inputInvolves(Class<?> class1)
970 assert (InputType.class.isAssignableFrom(class1));
971 for (InputType val : inputParams.values())
973 if (class1.isAssignableFrom(val.getClass()))
981 char gapCharacter = '-';
985 * @return the preferred gap character for alignments input/output by this
988 public char getGapCharacter()
993 public String getDecoratedResultUrl(String jobId)
995 // TODO: correctly write ?/& appropriate to result URL format.
996 return jobId + urlSuffix;
999 private List<JvDataType> resultData = new ArrayList<JvDataType>();
1004 * TODO: Extend to optionally specify relative/absolute url where data of this
1005 * type can be retrieved from
1009 public void addResultDatatype(JvDataType dt)
1011 if (resultData == null)
1013 resultData = new ArrayList<JvDataType>();
1018 public boolean removeRsultDatatype(JvDataType dt)
1020 if (resultData != null)
1022 return resultData.remove(dt);
1027 public List<JvDataType> getResultDataTypes()