X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fws%2Frest%2FRestServiceDescription.java;h=7ae922c18c4d43914ae81352419c663a893a0260;hb=797df64fa2a0a30773d0f48f5494d4155e5a8be3;hp=dea7fc9347c2d83f8d56f28da0486d26950d3e94;hpb=9c80addd4e2e5e137e171744be2be1add9f9b483;p=jalview.git diff --git a/src/jalview/ws/rest/RestServiceDescription.java b/src/jalview/ws/rest/RestServiceDescription.java index dea7fc9..7ae922c 100644 --- a/src/jalview/ws/rest/RestServiceDescription.java +++ b/src/jalview/ws/rest/RestServiceDescription.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6) - * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle + * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7) + * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle * * This file is part of Jalview. * @@ -49,6 +49,13 @@ import com.sun.tools.doclets.internal.toolkit.util.DocFinder.Output; public class RestServiceDescription { /** + * create a new rest service description ready to be configured + */ + public RestServiceDescription() + { + + } + /** * @param details * @param postUrl * @param urlSuffix @@ -64,12 +71,15 @@ public class RestServiceDescription { super(); this.details = new UIinfo(); - details.Action = action; - details.description = description; - details.Name = name; - this.postUrl = postUrl; - this.urlSuffix = urlSuffix; - this.inputParams = inputParams; + details.Action = action == null ? "" : action; + details.description = description == null ? "" : description; + details.Name = name == null ? "" : name; + this.postUrl = postUrl == null ? "" : postUrl; + this.urlSuffix = urlSuffix == null ? "" : urlSuffix; + if (inputParams != null) + { + this.inputParams = inputParams; + } this.hseparable = hseparable; this.vseparable = vseparable; this.gapCharacter = gapCharacter; @@ -101,6 +111,36 @@ public class RestServiceDescription public class UIinfo { + public String getAction() + { + return Action; + } + + public void setAction(String action) + { + Action = action; + } + + public String getName() + { + return Name; + } + + public void setName(String name) + { + Name = name; + } + + public String getDescription() + { + return description; + } + + public void setDescription(String description) + { + this.description = description; + } + String Action; String Name; @@ -110,11 +150,86 @@ public class RestServiceDescription public UIinfo details = new UIinfo(); + public String getAction() + { + return details.getAction(); + } + + public void setAction(String action) + { + details.setAction(action); + } + + public String getName() + { + return details.getName(); + } + + public void setName(String name) + { + details.setName(name); + } + + public String getDescription() + { + return details.getDescription(); + } + + public void setDescription(String description) + { + details.setDescription(description); + } + /** * Service base URL */ String postUrl; + public String getPostUrl() + { + return postUrl; + } + + public void setPostUrl(String postUrl) + { + this.postUrl = postUrl; + } + + public String getUrlSuffix() + { + return urlSuffix; + } + + public void setUrlSuffix(String urlSuffix) + { + this.urlSuffix = urlSuffix; + } + + public Map getInputParams() + { + return inputParams; + } + + public void setInputParams(Map inputParams) + { + this.inputParams = inputParams; + } + + public void setHseparable(boolean hseparable) + { + this.hseparable = hseparable; + } + + public void setVseparable(boolean vseparable) + { + this.vseparable = vseparable; + } + + public void setGapCharacter(char gapCharacter) + { + this.gapCharacter = gapCharacter; + } + /** * suffix that should be added to any url used if it does not already end in * the suffix. @@ -124,7 +239,7 @@ public class RestServiceDescription /** * input info given as key/value pairs - mapped to post arguments */ - Map inputParams = new HashMap(); + Map inputParams = new HashMap(); /** * assigns the given inputType it to its corresponding input parameter token @@ -213,11 +328,21 @@ public class RestServiceDescription public RestServiceDescription(RestServiceDescription toedit) { - if (toedit == null) - { - return; - } - // TODO Implement copy constructor NOW + // Rather then do the above, we cheat and use our human readable + // serialization code to clone everything + this(toedit.toString()); + /** + * if (toedit == null) { return; } /** urlSuffix = toedit.urlSuffix; postUrl + * = toedit.postUrl; hseparable = toedit.hseparable; vseparable = + * toedit.vseparable; gapCharacter = toedit.gapCharacter; details = new + * RestServiceDescription.UIinfo(); details.Action = toedit.details.Action; + * details.description = toedit.details.description; details.Name = + * toedit.details.Name; for (InputType itype: toedit.inputParams.values()) { + * inputParams.put(itype.token, itype.clone()); + * + * } + */ + // TODO Implement copy constructor NOW*/ } /** @@ -255,12 +380,13 @@ public class RestServiceDescription return null; java.util.ArrayList jv = new ArrayList(); int cp = 0, pos, escape; - boolean wasescaped = false; + boolean wasescaped = false,wasquoted=false; String lstitem = null; - while ((pos = list.indexOf(separator, cp)) > cp) + while ((pos = list.indexOf(separator, cp)) >= cp) { - escape = (list.charAt(pos - 1) == '\\') ? -1 : 0; - if (wasescaped) + + escape = (pos > 0 && list.charAt(pos - 1) == '\\') ? -1 : 0; + if (wasescaped || wasquoted) { // append to previous pos jv.set(jv.size() - 1, @@ -274,11 +400,20 @@ public class RestServiceDescription } cp = pos + seplen; wasescaped = escape == -1; + if (!wasescaped) + { + // last separator may be in an unmatched quote + if (java.util.regex.Pattern.matches("('[^']*')*[^']*'",lstitem)) + { + wasquoted=true; + } + } + } if (cp < list.length()) { String c = list.substring(cp); - if (wasescaped) + if (wasescaped || wasquoted) { // append final separator jv.set(jv.size() - 1, lstitem + separator + c); @@ -330,7 +465,7 @@ public class RestServiceDescription { if (list[i] != null) { - if (i > 0) + if (v.length() > 0) { v.append(separator); } @@ -362,16 +497,18 @@ public class RestServiceDescription * param warnings a StringBuffer that any warnings about invalid * content will be appended to. */ - private void configureFromServiceInputProperties(String propList, + private boolean configureFromServiceInputProperties(String propList, StringBuffer warnings) { String[] props = separatorListToArray(propList, ","); if (props == null) { - return; + return true; } ; + boolean valid = true; String val = null; + int l = warnings.length(); int i; for (String prop : props) { @@ -395,8 +532,9 @@ public class RestServiceDescription } if (prop.equals("gapCharacter")) { - if (val == null || val.length() > 1) + if (val == null || val.length() == 0 || val.length() > 1) { + valid = false; warnings.append((warnings.length() > 0 ? "\n" : "") + ("Invalid service property: gapCharacter=' ' (single character) - was given '" + val + "'")); @@ -408,15 +546,17 @@ public class RestServiceDescription } if (prop.equals("returns")) { - _configureOurputFormatFrom(val, warnings); + _configureOutputFormatFrom(val, warnings); } } + // return true if valid is true and warning buffer was not appended to. + return valid && (l == warnings.length()); } private String _genOutputFormatString() { String buff = ""; - if (resultData==null) + if (resultData == null) { return ""; } @@ -431,9 +571,14 @@ public class RestServiceDescription return buff; } - private void _configureOurputFormatFrom(String outstring, + private void _configureOutputFormatFrom(String outstring, StringBuffer warnings) { + if (outstring.indexOf(";") == -1) + { + // we add a token, for simplicity + outstring = outstring + ";"; + } StringTokenizer st = new StringTokenizer(outstring, ";"); String tok = ""; resultData = new ArrayList(); @@ -460,18 +605,18 @@ public class RestServiceDescription private String getServiceIOProperties() { - String[] vls = new String[] - { isHseparable() ? "hseparable" : "", - isVseparable() ? "vseparable" : "", - (new String("gapCharacter='" + gapCharacter + "'")), - (new String("returns='" + _genOutputFormatString() + "'")) }; - - return arrayToSeparatorList(vls, ","); + ArrayList vls = new ArrayList(); + if (isHseparable()) { vls.add("hseparable");}; + if (isVseparable()) { vls.add("vseparable");}; + vls.add(new String("gapCharacter='" + gapCharacter + "'")); + vls.add(new String("returns='" + _genOutputFormatString() + "'")); + return arrayToSeparatorList(vls.toArray(new String[0]), ","); } public String toString() { StringBuffer result = new StringBuffer(); + result.append("|"); result.append(details.Name); result.append('|'); result.append(details.Action); @@ -495,28 +640,63 @@ public class RestServiceDescription return result.toString(); } + /** + * processes a service encoded as a string (as generated by RestServiceDescription.toString()) + * Note - this will only use the first service definition encountered in the string to configure the service. + * @param encoding + * @param warnings - where warning messages are reported. + * @return true if configuration was parsed successfully. + */ public boolean configureFromEncodedString(String encoding, StringBuffer warnings) { - boolean valid = false; String[] list = separatorListToArray(encoding, "|"); - details.Name = list[0]; - details.Action = list[1]; - details.description = list[2]; - configureFromServiceInputProperties(list[3], warnings); - if (list.length > 5) + + int nextpos=parseServiceList(list,warnings, 0); + if (nextpos>0) { - urlSuffix = list[4]; - valid |= configureFromInputParamEncodedUrl(list[5], warnings); + return true; + } + return false; + } + /** + * processes the given list from position p, attempting to configure the service from it. + * Service lists are formed by concatenating individual stringified services. The first character of a stringified service is '|', enabling this, and the parser will ignore empty fields in a '|' separated list when they fall outside a service definition. + * @param list + * @param warnings + * @param p + * @return + */ + protected int parseServiceList(String[] list, StringBuffer warnings, int p) + { + boolean invalid = false; + // look for the first non-empty position - expect it to be service name + while (list[p]!=null && list[p].trim().length()==0) + { + p++; + } + details.Name = list[p]; + details.Action = list[p+1]; + details.description = list[p+2]; + invalid |= !configureFromServiceInputProperties(list[p+3], warnings); + if (list.length-p > 5 && list[p+5]!=null && list[p+5].trim().length()>5) + { + urlSuffix = list[p+4]; + invalid |= !configureFromInputParamEncodedUrl(list[p+5], warnings); + p+=6; } else { - urlSuffix = null; - valid |= configureFromInputParamEncodedUrl(list[4], warnings); + if (list.length-p > 4 && list[p+4]!=null && list[p+4].trim().length()>5) + { + urlSuffix = null; + invalid |= !configureFromInputParamEncodedUrl(list[p+4], warnings); + p+=5; + } } - return valid; + return invalid ? -1 : p; } - + /** * @return string representation of the input parameters, their type and * constraints, appended to the service's base submission URL @@ -612,45 +792,8 @@ public class RestServiceDescription iprmparams = iprm.substring(colon + 1); iprm = iprm.substring(0, colon); } - // TODO - find a better way of maintaining this classlist - for (Class type : new Class[] - { jalview.ws.rest.params.Alignment.class, - jalview.ws.rest.params.AnnotationFile.class, - SeqGroupIndexVector.class, - jalview.ws.rest.params.SeqIdVector.class, - jalview.ws.rest.params.SeqVector.class, - jalview.ws.rest.params.Tree.class }) - { - try - { - jinput = (InputType) (type.getConstructor().newInstance(null)); - if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix())) - { - ArrayList al = new ArrayList(); - for (String prprm : separatorListToArray(iprmparams, ",")) - { - al.add(prprm); - } - if (!jinput.configureFromURLtokenString(al, warnings)) - { - valid = false; - warnings.append("Failed to parse '" + prms.group(0) - + "' as a " + jinput.getURLtokenPrefix() - + " input tag.\n"); - } - else - { - jinput.token = tok; - iparams.put(tok, jinput); - } - break; - } - - } catch (Throwable thr) - { - } - ; - } + valid = parseTypeString(prms.group(0), tok, iprm, iprmparams, + iparams, warnings); } if (valid) { @@ -668,8 +811,74 @@ public class RestServiceDescription return valid; } + public static Class[] getInputTypes() + { + // TODO - find a better way of maintaining this classlist + return new Class[] + { jalview.ws.rest.params.Alignment.class, + jalview.ws.rest.params.AnnotationFile.class, + SeqGroupIndexVector.class, + jalview.ws.rest.params.SeqIdVector.class, + jalview.ws.rest.params.SeqVector.class, + jalview.ws.rest.params.Tree.class }; + } + + public static boolean parseTypeString(String fullstring, String tok, + String iprm, String iprmparams, Map iparams, + StringBuffer warnings) + { + boolean valid = true; + InputType jinput; + for (Class type : getInputTypes()) + { + try + { + jinput = (InputType) (type.getConstructor().newInstance(null)); + if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix())) + { + ArrayList al = new ArrayList(); + for (String prprm : separatorListToArray(iprmparams, ",")) + { + // hack to ensure that strings like "sep=','" containing unescaped commas as values are concatenated + al.add(prprm.trim()); + } + if (!jinput.configureFromURLtokenString(al, warnings)) + { + valid = false; + warnings.append("Failed to parse '" + fullstring + "' as a " + + jinput.getURLtokenPrefix() + " input tag.\n"); + } + else + { + jinput.token = tok; + iparams.put(tok, jinput); + valid = true; + } + break; + } + + } catch (Throwable thr) + { + } + ; + } + return valid; + } + public static void main(String argv[]) { + // test separator list + try { + assert(separatorListToArray("foo=',',min='foo',max='1,2,3',fa=','", ",").length==4); + if (separatorListToArray("minsize='2', sep=','", ",").length==2) + { + assert(false); + } + + } catch (AssertionError x) + { + System.err.println("separatorListToArray is faulty."); + } if (argv.length == 0) { if (!testRsdExchange("Test using default Shmmr service", @@ -826,7 +1035,7 @@ public class RestServiceDescription return jobId + urlSuffix; } - private List resultData; + private List resultData = new ArrayList(); /** * @@ -858,4 +1067,30 @@ public class RestServiceDescription { return resultData; } + + /** + * parse a concatenated list of rest service descriptions into an array + * @param services + * @return zero or more services. + * @throws exceptions if the services are improperly encoded. + */ + public static List parseDescriptions(String services) throws Exception + { + String[] list = separatorListToArray(services, "|"); + List svcparsed = new ArrayList(); + int p=0,lastp=0; + StringBuffer warnings=new StringBuffer(); + do { + RestServiceDescription rsd = new RestServiceDescription(); + p=rsd.parseServiceList(list, warnings, lastp=p); + if (p>lastp && rsd.isValid()) + { + svcparsed.add(rsd); + } else { + throw new Exception("Failed to parse user defined RSBS services from :"+services+"\nFirst error was encountered at token "+lastp+" starting "+list[lastp]+":\n"+rsd.getInvalidMessage()); + } + } while (p