2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.ws.rest;
23 import jalview.datamodel.SequenceI;
24 import jalview.io.packed.DataProvider.JvDataType;
25 import jalview.util.StringUtils;
26 import jalview.ws.rest.params.Alignment;
27 import jalview.ws.rest.params.AnnotationFile;
28 import jalview.ws.rest.params.SeqGroupIndexVector;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.Hashtable;
34 import java.util.List;
36 import java.util.NoSuchElementException;
37 import java.util.StringTokenizer;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
41 public class RestServiceDescription
43 private static final Pattern PARAM_ENCODED_URL_PATTERN = Pattern.compile("([?&])([A-Za-z0-9_]+)=\\$([^$]+)\\$");
46 * create a new rest service description ready to be configured
48 public RestServiceDescription()
57 * null or prefix for forming result URL from job
64 public RestServiceDescription(String action, String description,
65 String name, String postUrl, String urlSuffix,
66 List<String> urlPrefix,
67 Map<String, InputType> inputParams,
68 boolean hseparable, boolean vseparable, char gapCharacter)
71 this.details = new UIinfo();
72 details.Action = action == null ? "" : action;
73 details.description = description == null ? "" : description;
74 details.Name = name == null ? "" : name;
75 this.postUrl = postUrl == null ? "" : postUrl;
76 // TODO decide if nullable
77 this.rurlPref = urlPrefix;
78 this.urlSuffix = urlSuffix == null ? "" : urlSuffix;
79 if (inputParams != null)
81 this.inputParams = inputParams;
83 this.hseparable = hseparable;
84 this.vseparable = vseparable;
85 this.gapCharacter = gapCharacter;
89 public boolean equals(Object o)
91 if (o == null || !(o instanceof RestServiceDescription))
95 RestServiceDescription other = (RestServiceDescription) o;
96 boolean diff = (gapCharacter != other.gapCharacter);
97 diff |= vseparable != other.vseparable;
98 diff |= hseparable != other.hseparable;
99 diff |= !(urlSuffix == null && other.urlSuffix == null || (urlSuffix != null
100 && other.urlSuffix != null && urlSuffix.equals(other.urlSuffix)));
101 diff |= !(rurlPref == null && other.rurlPref == null || (rurlPref != null
102 && other.rurlPref != null && rurlPref.equals(other.rurlPref)));
103 // TODO - robust diff that includes constants and reordering of URL
104 // diff |= !(postUrl.equals(other.postUrl));
105 // diff |= !inputParams.equals(other.inputParams);
106 diff |= !details.Name.equals(other.details.Name);
107 diff |= !details.Action.equals(other.details.Action);
108 diff |= !details.description.equals(other.details.description);
113 * Service UI Info { Action, Specific Name of Service, Brief Description }
118 public String getAction()
123 public void setAction(String action)
128 public String getName()
133 public void setName(String name)
138 public String getDescription()
143 public void setDescription(String description)
145 this.description = description;
155 public UIinfo details = new UIinfo();
157 public String getAction()
159 return details.getAction();
162 public void setAction(String action)
164 details.setAction(action);
167 public String getName()
169 return details.getName();
172 public void setName(String name)
174 details.setName(name);
177 public String getDescription()
179 return details.getDescription();
182 public void setDescription(String description)
184 details.setDescription(description);
192 public String getPostUrl()
197 public void setPostUrl(String postUrl)
199 this.postUrl = postUrl;
202 public String getUrlSuffix()
207 public void setUrlSuffix(String urlSuffix)
209 this.urlSuffix = urlSuffix;
212 public Map<String, InputType> getInputParams()
217 public void setInputParams(Map<String, InputType> inputParams)
219 this.inputParams = inputParams;
222 public void setHseparable(boolean hseparable)
224 this.hseparable = hseparable;
227 public void setVseparable(boolean vseparable)
229 this.vseparable = vseparable;
232 public void setGapCharacter(char gapCharacter)
234 this.gapCharacter = gapCharacter;
238 * suffix that should be added to any url used if it does not already end in
244 * prefix for poll and result URLs when null, urlSuffix is used
246 List<String> rurlPref = null;
249 * input info given as key/value pairs - mapped to post arguments
251 Map<String, InputType> inputParams = new HashMap<String, InputType>();
254 * assigns the given inputType it to its corresponding input parameter token
259 public void setInputParam(InputType it)
261 inputParams.put(it.token, it);
265 * remove the given input type it from the set of service input parameters.
269 public void removeInputParam(InputType it)
271 inputParams.remove(it.token);
275 * service requests alignment data
280 * service requests alignment and/or sequence annotation data
285 * service requests partitions defined over input (alignment) data
287 boolean partitiondata;
290 * process the input data and set the appropriate shorthand flags describing
291 * the input the service wants
293 public void setInvolvesFlags()
295 aligndata = inputInvolves(Alignment.class);
296 annotdata = inputInvolves(AnnotationFile.class);
297 partitiondata = inputInvolves(SeqGroupIndexVector.class);
301 * Service return info { alignment, annotation file (loaded back on to
302 * alignment), tree (loaded back on to alignment), sequence annotation -
303 * loaded back on to alignment), text report, pdb structures with sequence
309 * Start with bare minimum: input is alignment + groups on alignment
315 private String invalidMessage = null;
318 * parse the given linkString of the form '<label>|<url>|separator
319 * char[|optional sequence separator char]' into parts. url may contain a
320 * string $SEQUENCEIDS<=optional regex=>$ where <=optional regex=> must be of
321 * the form =/<perl style regex>/=$ or $SEQUENCES<=optional regex=>$ or
322 * $SEQUENCES<=optional regex=>$.
326 public RestServiceDescription(String link)
328 StringBuffer warnings = new StringBuffer();
329 if (!configureFromEncodedString(link, warnings))
331 if (warnings.length() > 0)
333 invalidMessage = warnings.toString();
338 public RestServiceDescription(RestServiceDescription toedit)
340 // Rather then do the above, we cheat and use our human readable
341 // serialization code to clone everything
342 this(toedit.toString());
344 * if (toedit == null) { return; } /** urlSuffix = toedit.urlSuffix; postUrl
345 * = toedit.postUrl; hseparable = toedit.hseparable; vseparable =
346 * toedit.vseparable; gapCharacter = toedit.gapCharacter; details = new
347 * RestServiceDescription.UIinfo(); details.Action = toedit.details.Action;
348 * details.description = toedit.details.description; details.Name =
349 * toedit.details.Name; for (InputType itype: toedit.inputParams.values()) {
350 * inputParams.put(itype.token, itype.clone());
354 // TODO Implement copy constructor NOW*/
358 * @return the invalidMessage
360 public String getInvalidMessage()
362 return invalidMessage;
366 * Check if URL string was parsed properly.
368 * @return boolean - if false then <code>getInvalidMessage</code> returns an
371 public boolean isValid()
373 return invalidMessage == null;
377 * parse a string containing a list of service properties and configure the
378 * service description
381 * param warnings a StringBuffer that any warnings about invalid
382 * content will be appended to.
384 private boolean configureFromServiceInputProperties(String propList,
385 StringBuffer warnings)
387 String[] props = StringUtils.separatorListToArray(propList, ",");
393 boolean valid = true;
395 int l = warnings.length();
397 for (String prop : props)
399 if ((i = prop.indexOf("=")) > -1)
401 val = prop.substring(i + 1);
402 if (val.startsWith("\'") && val.endsWith("\'"))
404 val = val.substring(1, val.length() - 1);
406 prop = prop.substring(0, i);
409 if (prop.equals("hseparable"))
413 if (prop.equals("vseparable"))
417 if (prop.equals("gapCharacter"))
419 if (val == null || val.length() == 0 || val.length() > 1)
422 warnings.append((warnings.length() > 0 ? "\n" : "")
423 + ("Invalid service property: gapCharacter=' ' (single character) - was given '"
428 gapCharacter = val.charAt(0);
431 if (prop.equals("returns"))
433 _configureOutputFormatFrom(val, warnings);
436 // return true if valid is true and warning buffer was not appended to.
437 return valid && (l == warnings.length());
440 private String _genOutputFormatString()
443 if (resultData == null)
447 for (JvDataType type : resultData)
449 if (buff.length() > 0)
453 buff += type.toString();
458 private void _configureOutputFormatFrom(String outstring,
459 StringBuffer warnings)
461 if (outstring.indexOf(";") == -1)
463 // we add a token, for simplicity
464 outstring = outstring + ";";
466 StringTokenizer st = new StringTokenizer(outstring, ";");
468 resultData = new ArrayList<JvDataType>();
469 while (st.hasMoreTokens())
473 resultData.add(JvDataType.valueOf(tok = st.nextToken()));
474 } catch (NoSuchElementException x)
476 warnings.append("Invalid result type: '" + tok
477 + "' (must be one of: ");
479 for (JvDataType vl : JvDataType.values())
481 warnings.append(sep);
482 warnings.append(vl.toString());
485 warnings.append(" separated by semi-colons)\n");
490 private String getServiceIOProperties()
492 ArrayList<String> vls = new ArrayList<String>();
495 vls.add("hseparable");
500 vls.add("vseparable");
503 vls.add(new String("gapCharacter='" + gapCharacter + "'"));
504 vls.add(new String("returns='" + _genOutputFormatString() + "'"));
505 return StringUtils.arrayToSeparatorList(vls.toArray(new String[0]), ",");
508 public String toString()
510 StringBuffer result = new StringBuffer();
512 result.append(details.Name);
514 result.append(details.Action);
516 if (details.description != null)
518 result.append(details.description);
521 // list job input flags
523 result.append(getServiceIOProperties());
524 // list any additional cgi parameters needed for result retrieval
525 if (urlSuffix != null && urlSuffix.length() > 0)
528 result.append(urlSuffix);
531 result.append(getInputParamEncodedUrl());
532 return result.toString();
536 * processes a service encoded as a string (as generated by
537 * RestServiceDescription.toString()) Note - this will only use the first
538 * service definition encountered in the string to configure the service.
542 * - where warning messages are reported.
543 * @return true if configuration was parsed successfully.
545 public boolean configureFromEncodedString(String encoding,
546 StringBuffer warnings)
548 String[] list = StringUtils.separatorListToArray(encoding, "|");
550 int nextpos = parseServiceList(list, warnings, 0);
559 * processes the given list from position p, attempting to configure the
560 * service from it. Service lists are formed by concatenating individual
561 * stringified services. The first character of a stringified service is '|',
562 * enabling this, and the parser will ignore empty fields in a '|' separated
563 * list when they fall outside a service definition.
570 protected int parseServiceList(String[] list, StringBuffer warnings, int p)
572 boolean invalid = false;
573 // look for the first non-empty position - expect it to be service name
574 while (list[p] != null && list[p].trim().length() == 0)
578 details.Name = list[p];
579 details.Action = list[p + 1];
580 details.description = list[p + 2];
581 invalid |= !configureFromServiceInputProperties(list[p + 3], warnings);
582 if (list.length - p > 5 && list[p + 5] != null
583 && list[p + 5].trim().length() > 5)
585 urlSuffix = list[p + 4];
586 invalid |= !configureFromInputParamEncodedUrl(list[p + 5], warnings);
591 if (list.length - p > 4 && list[p + 4] != null
592 && list[p + 4].trim().length() > 5)
595 invalid |= !configureFromInputParamEncodedUrl(list[p + 4], warnings);
599 return invalid ? -1 : p;
603 * @return string representation of the input parameters, their type and
604 * constraints, appended to the service's base submission URL
606 private String getInputParamEncodedUrl()
608 StringBuffer url = new StringBuffer();
609 if (postUrl == null || postUrl.length() < 5)
615 char appendChar = (postUrl.indexOf("?") > -1) ? '&' : '?';
616 boolean consts = true;
619 for (Map.Entry<String, InputType> param : inputParams.entrySet())
621 List<String> vals = param.getValue().getURLEncodedParameter();
622 if (param.getValue().isConstant())
626 url.append(appendChar);
628 url.append(param.getValue().token);
629 if (vals.size() == 1)
632 url.append(vals.get(0));
640 url.append(appendChar);
642 url.append(param.getValue().token);
644 // write parameter set as $TOKENPREFIX:csv list of params$ for this
647 url.append(param.getValue().getURLtokenPrefix());
649 url.append(StringUtils.arrayToSeparatorList(vals.toArray(new String[0]),
656 // toggle consts and repeat until !consts is false:
657 } while (!(consts = !consts));
658 return url.toString();
662 * parse the service URL and input parameters from the given encoded URL
663 * string and configure the RestServiceDescription from it.
668 * @return true if URL parsed correctly. false means the configuration failed.
670 private boolean configureFromInputParamEncodedUrl(String ipurl,
671 StringBuffer warnings)
673 boolean valid = true;
675 String url = new String();
676 Matcher prms = PARAM_ENCODED_URL_PATTERN
678 Map<String, InputType> iparams = new Hashtable<String, InputType>();
682 if (lastp < prms.start(0))
684 url += ipurl.substring(lastp, prms.start(0));
685 lastp = prms.end(0) + 1;
687 String sep = prms.group(1);
688 String tok = prms.group(2);
689 String iprm = prms.group(3);
690 int colon = iprm.indexOf(":");
691 String iprmparams = "";
694 iprmparams = iprm.substring(colon + 1);
695 iprm = iprm.substring(0, colon);
697 valid = parseTypeString(prms.group(0), tok, iprm, iprmparams,
704 URL u = new URL(url);
706 inputParams = iparams;
707 } catch (Exception e)
709 warnings.append("Failed to parse '" + url + "' as a URL.\n");
716 public static Class[] getInputTypes()
718 // TODO - find a better way of maintaining this classlist
720 { jalview.ws.rest.params.Alignment.class,
721 jalview.ws.rest.params.AnnotationFile.class,
722 SeqGroupIndexVector.class,
723 jalview.ws.rest.params.SeqIdVector.class,
724 jalview.ws.rest.params.SeqVector.class,
725 jalview.ws.rest.params.Tree.class };
728 public static boolean parseTypeString(String fullstring, String tok,
729 String iprm, String iprmparams, Map<String, InputType> iparams,
730 StringBuffer warnings)
732 boolean valid = true;
734 for (Class type : getInputTypes())
738 jinput = (InputType) (type.getConstructor().newInstance());
739 if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix()))
741 ArrayList<String> al = new ArrayList<String>();
742 for (String prprm : StringUtils.separatorListToArray(iprmparams, ","))
744 // hack to ensure that strings like "sep=','" containing unescaped
745 // commas as values are concatenated
746 al.add(prprm.trim());
748 if (!jinput.configureFromURLtokenString(al, warnings))
751 warnings.append("Failed to parse '" + fullstring + "' as a "
752 + jinput.getURLtokenPrefix() + " input tag.\n");
757 iparams.put(tok, jinput);
763 } catch (Throwable thr)
772 * convenience method to generate the id and sequence string vector from a set
773 * of sequences using each sequence's getName() and getSequenceAsString()
777 * @return String[][] {{sequence ids},{sequence strings}}
779 public static String[][] formStrings(SequenceI[] seqs)
781 String[][] idset = new String[2][seqs.length];
782 for (int i = 0; i < seqs.length; i++)
784 idset[0][i] = seqs[i].getName();
785 idset[1][i] = seqs[i].getSequenceAsString();
791 * can this service be run on the visible portion of an alignment regardless
792 * of hidden boundaries ?
794 boolean hseparable = false;
796 boolean vseparable = false;
798 public boolean isHseparable()
807 public boolean isVseparable()
813 * search the input types for an instance of the given class
815 * @param <validInput.inputType> class1
818 public boolean inputInvolves(Class<?> class1)
820 assert (InputType.class.isAssignableFrom(class1));
821 for (InputType val : inputParams.values())
823 if (class1.isAssignableFrom(val.getClass()))
831 char gapCharacter = '-';
835 * @return the preferred gap character for alignments input/output by this
838 public char getGapCharacter()
843 public String getDecoratedResultUrl(String jobId)
845 // TODO: JPred4 - jobs are checked at a different path to the jobId ?
846 // TODO: correctly write ?/& appropriate to result URL format.
847 return jobId + urlSuffix;
850 private List<JvDataType> resultData = new ArrayList<JvDataType>();
855 * TODO: Extend to optionally specify relative/absolute url where data of this
856 * type can be retrieved from
860 public void addResultDatatype(JvDataType dt)
862 if (resultData == null)
864 resultData = new ArrayList<JvDataType>();
869 public boolean removeRsultDatatype(JvDataType dt)
871 if (resultData != null)
873 return resultData.remove(dt);
878 public List<JvDataType> getResultDataTypes()
884 * parse a concatenated list of rest service descriptions into an array
887 * @return zero or more services.
889 * if the services are improperly encoded.
891 public static List<RestServiceDescription> parseDescriptions(
892 String services) throws Exception
894 String[] list = StringUtils.separatorListToArray(services, "|");
895 List<RestServiceDescription> svcparsed = new ArrayList<RestServiceDescription>();
896 int p = 0, lastp = 0;
897 StringBuffer warnings = new StringBuffer();
900 RestServiceDescription rsd = new RestServiceDescription();
901 p = rsd.parseServiceList(list, warnings, lastp = p);
902 if (p > lastp && rsd.isValid())
909 "Failed to parse user defined RSBS services from :"
911 + "\nFirst error was encountered at token " + lastp
912 + " starting " + list[lastp] + ":\n"
913 + rsd.getInvalidMessage());
915 } while (p < lastp && p < list.length - 1);