Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.ws.rest

File RestServiceDescription.java

 

Coverage histogram

../../../img/srcFileCovDistChart7.png
28% of files have more coverage

Code metrics

102
258
52
2
910
614
119
0.46
4.96
26
2.29

Classes

Class Line # Actions
RestServiceDescription 41 252 113 136
0.6666%
RestServiceDescription.UIinfo 111 6 6 12
0.00%
 

Contributing tests

This file is covered by 102 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10    * of the License, or (at your option) any later version.
11    *
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.
16    *
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.
20    */
21    package jalview.ws.rest;
22   
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;
29   
30    import java.net.URL;
31    import java.util.ArrayList;
32    import java.util.HashMap;
33    import java.util.Hashtable;
34    import java.util.List;
35    import java.util.Map;
36    import java.util.NoSuchElementException;
37    import java.util.StringTokenizer;
38    import java.util.regex.Matcher;
39    import java.util.regex.Pattern;
40   
 
41    public class RestServiceDescription
42    {
43    private static final Pattern PARAM_ENCODED_URL_PATTERN = Pattern
44    .compile("([?&])([A-Za-z0-9_]+)=\\$([^$]+)\\$");
45   
46    /**
47    * create a new rest service description ready to be configured
48    */
 
49  1 toggle public RestServiceDescription()
50    {
51   
52    }
53   
54    /**
55    * @param details
56    * @param postUrl
57    * @param urlSuffix
58    * @param inputParams
59    * @param hseparable
60    * @param vseparable
61    * @param gapCharacter
62    */
 
63  3 toggle public RestServiceDescription(String action, String description,
64    String name, String postUrl, String urlSuffix,
65    Map<String, InputType> inputParams, boolean hseparable,
66    boolean vseparable, char gapCharacter)
67    {
68  3 super();
69  3 this.details = new UIinfo();
70  3 details.Action = action == null ? "" : action;
71  3 details.description = description == null ? "" : description;
72  3 details.Name = name == null ? "" : name;
73  3 this.postUrl = postUrl == null ? "" : postUrl;
74  3 this.urlSuffix = urlSuffix == null ? "" : urlSuffix;
75  3 if (inputParams != null)
76    {
77  3 this.inputParams = inputParams;
78    }
79  3 this.hseparable = hseparable;
80  3 this.vseparable = vseparable;
81  3 this.gapCharacter = gapCharacter;
82    }
83   
 
84  2 toggle @Override
85    public boolean equals(Object o)
86    {
87  2 if (o == null || !(o instanceof RestServiceDescription))
88    {
89  0 return false;
90    }
91  2 RestServiceDescription other = (RestServiceDescription) o;
92  2 boolean diff = (gapCharacter != other.gapCharacter);
93  2 diff |= vseparable != other.vseparable;
94  2 diff |= hseparable != other.hseparable;
95  2 diff |= !(urlSuffix == null && other.urlSuffix == null
96    || (urlSuffix != null && other.urlSuffix != null
97    && urlSuffix.equals(other.urlSuffix)));
98    // TODO - robust diff that includes constants and reordering of URL
99    // diff |= !(postUrl.equals(other.postUrl));
100    // diff |= !inputParams.equals(other.inputParams);
101  2 diff |= !details.Name.equals(other.details.Name);
102  2 diff |= !details.Action.equals(other.details.Action);
103  2 diff |= !details.description.equals(other.details.description);
104  2 return !diff;
105    }
106   
107    /**
108    * Service UI Info { Action, Specific Name of Service, Brief Description }
109    */
110   
 
111    public class UIinfo
112    {
 
113  0 toggle public String getAction()
114    {
115  0 return Action;
116    }
117   
 
118  0 toggle public void setAction(String action)
119    {
120  0 Action = action;
121    }
122   
 
123  0 toggle public String getName()
124    {
125  0 return Name;
126    }
127   
 
128  0 toggle public void setName(String name)
129    {
130  0 Name = name;
131    }
132   
 
133  0 toggle public String getDescription()
134    {
135  0 return description;
136    }
137   
 
138  0 toggle public void setDescription(String description)
139    {
140  0 this.description = description;
141    }
142   
143    String Action;
144   
145    String Name;
146   
147    String description;
148    }
149   
150    public UIinfo details = new UIinfo();
151   
 
152  0 toggle public String getAction()
153    {
154  0 return details.getAction();
155    }
156   
 
157  0 toggle public void setAction(String action)
158    {
159  0 details.setAction(action);
160    }
161   
 
162  0 toggle public String getName()
163    {
164  0 return details.getName();
165    }
166   
 
167  0 toggle public void setName(String name)
168    {
169  0 details.setName(name);
170    }
171   
 
172  0 toggle public String getDescription()
173    {
174  0 return details.getDescription();
175    }
176   
 
177  0 toggle public void setDescription(String description)
178    {
179  0 details.setDescription(description);
180    }
181   
182    /**
183    * Service base URL
184    */
185    String postUrl;
186   
 
187  0 toggle public String getPostUrl()
188    {
189  0 return postUrl;
190    }
191   
 
192  0 toggle public void setPostUrl(String postUrl)
193    {
194  0 this.postUrl = postUrl;
195    }
196   
 
197  0 toggle public String getUrlSuffix()
198    {
199  0 return urlSuffix;
200    }
201   
 
202  0 toggle public void setUrlSuffix(String urlSuffix)
203    {
204  0 this.urlSuffix = urlSuffix;
205    }
206   
 
207  2 toggle public Map<String, InputType> getInputParams()
208    {
209  2 return inputParams;
210    }
211   
 
212  0 toggle public void setInputParams(Map<String, InputType> inputParams)
213    {
214  0 this.inputParams = inputParams;
215    }
216   
 
217  0 toggle public void setHseparable(boolean hseparable)
218    {
219  0 this.hseparable = hseparable;
220    }
221   
 
222  0 toggle public void setVseparable(boolean vseparable)
223    {
224  0 this.vseparable = vseparable;
225    }
226   
 
227  0 toggle public void setGapCharacter(char gapCharacter)
228    {
229  0 this.gapCharacter = gapCharacter;
230    }
231   
232    /**
233    * suffix that should be added to any url used if it does not already end in
234    * the suffix.
235    */
236    String urlSuffix;
237   
238    /**
239    * input info given as key/value pairs - mapped to post arguments
240    */
241    Map<String, InputType> inputParams = new HashMap<String, InputType>();
242   
243    /**
244    * assigns the given inputType it to its corresponding input parameter token
245    * it.token
246    *
247    * @param it
248    */
 
249  0 toggle public void setInputParam(InputType it)
250    {
251  0 inputParams.put(it.token, it);
252    }
253   
254    /**
255    * remove the given input type it from the set of service input parameters.
256    *
257    * @param it
258    */
 
259  0 toggle public void removeInputParam(InputType it)
260    {
261  0 inputParams.remove(it.token);
262    }
263   
264    /**
265    * service requests alignment data
266    */
267    boolean aligndata;
268   
269    /**
270    * service requests alignment and/or seuqence annotationo data
271    */
272    boolean annotdata;
273   
274    /**
275    * service requests partitions defined over input (alignment) data
276    */
277    boolean partitiondata;
278   
279    /**
280    * process ths input data and set the appropriate shorthand flags describing
281    * the input the service wants
282    */
 
283  1 toggle public void setInvolvesFlags()
284    {
285  1 aligndata = inputInvolves(Alignment.class);
286  1 annotdata = inputInvolves(AnnotationFile.class);
287  1 partitiondata = inputInvolves(SeqGroupIndexVector.class);
288    }
289   
290    /**
291    * Service return info { alignment, annotation file (loaded back on to
292    * alignment), tree (loaded back on to alignment), sequence annotation -
293    * loaded back on to alignment), text report, pdb structures with sequence
294    * mapping )
295    *
296    */
297   
298    /**
299    * Start with bare minimum: input is alignment + groups on alignment
300    *
301    * @author JimP
302    *
303    */
304   
305    private String invalidMessage = null;
306   
307    /**
308    * parse the given linkString of the form '<label>|<url>|separator
309    * char[|optional sequence separator char]' into parts. url may contain a
310    * string $SEQUENCEIDS<=optional regex=>$ where <=optional regex=> must be of
311    * the form =/<perl style regex>/=$ or $SEQUENCES<=optional regex=>$ or
312    * $SEQUENCES<=optional regex=>$.
313    *
314    * @param link
315    */
 
316  1105 toggle public RestServiceDescription(String link)
317    {
318  1105 StringBuffer warnings = new StringBuffer();
319  1105 if (!configureFromEncodedString(link, warnings))
320    {
321  0 if (warnings.length() > 0)
322    {
323  0 invalidMessage = warnings.toString();
324    }
325    }
326    }
327   
 
328  0 toggle public RestServiceDescription(RestServiceDescription toedit)
329    {
330    // Rather then do the above, we cheat and use our human readable
331    // serialization code to clone everything
332  0 this(toedit.toString());
333    /**
334    * if (toedit == null) { return; } /** urlSuffix = toedit.urlSuffix; postUrl
335    * = toedit.postUrl; hseparable = toedit.hseparable; vseparable =
336    * toedit.vseparable; gapCharacter = toedit.gapCharacter; details = new
337    * RestServiceDescription.UIinfo(); details.Action = toedit.details.Action;
338    * details.description = toedit.details.description; details.Name =
339    * toedit.details.Name; for (InputType itype: toedit.inputParams.values()) {
340    * inputParams.put(itype.token, itype.clone());
341    *
342    * }
343    */
344    // TODO Implement copy constructor NOW*/
345    }
346   
347    /**
348    * @return the invalidMessage
349    */
 
350  0 toggle public String getInvalidMessage()
351    {
352  0 return invalidMessage;
353    }
354   
355    /**
356    * Check if URL string was parsed properly.
357    *
358    * @return boolean - if false then <code>getInvalidMessage</code> returns an
359    * error message
360    */
 
361  2 toggle public boolean isValid()
362    {
363  2 return invalidMessage == null;
364    }
365   
366    /**
367    * parse a string containing a list of service properties and configure the
368    * service description
369    *
370    * @param propList
371    * param warnings a StringBuffer that any warnings about invalid
372    * content will be appended to.
373    */
 
374  1106 toggle private boolean configureFromServiceInputProperties(String propList,
375    StringBuffer warnings)
376    {
377  1106 String[] props = StringUtils.separatorListToArray(propList, ",");
378  1106 if (props == null)
379    {
380  0 return true;
381    }
382  1106 ;
383  1106 boolean valid = true;
384  1106 String val = null;
385  1106 int l = warnings.length();
386  1106 int i;
387  1106 for (String prop : props)
388    {
389  ? if ((i = prop.indexOf("=")) > -1)
390    {
391  2212 val = prop.substring(i + 1);
392  2212 if (val.startsWith("\'") && val.endsWith("\'"))
393    {
394  2212 val = val.substring(1, val.length() - 1);
395    }
396  2212 prop = prop.substring(0, i);
397    }
398   
399  3318 if (prop.equals("hseparable"))
400    {
401  1106 hseparable = true;
402    }
403  3318 if (prop.equals("vseparable"))
404    {
405  0 vseparable = true;
406    }
407  3318 if (prop.equals("gapCharacter"))
408    {
409  1106 if (val == null || val.length() == 0 || val.length() > 1)
410    {
411  0 valid = false;
412  0 warnings.append((warnings.length() > 0 ? "\n" : "")
413    + ("Invalid service property: gapCharacter=' ' (single character) - was given '"
414    + val + "'"));
415    }
416    else
417    {
418  1106 gapCharacter = val.charAt(0);
419    }
420    }
421  3318 if (prop.equals("returns"))
422    {
423  1106 _configureOutputFormatFrom(val, warnings);
424    }
425    }
426    // return true if valid is true and warning buffer was not appended to.
427  1106 return valid && (l == warnings.length());
428    }
429   
 
430  5 toggle private String _genOutputFormatString()
431    {
432  5 String buff = "";
433  5 if (resultData == null)
434    {
435  0 return "";
436    }
437  5 for (JvDataType type : resultData)
438    {
439  5 if (buff.length() > 0)
440    {
441  0 buff += ";";
442    }
443  5 buff += type.toString();
444    }
445  5 return buff;
446    }
447   
 
448  1106 toggle private void _configureOutputFormatFrom(String outstring,
449    StringBuffer warnings)
450    {
451  1106 if (outstring.indexOf(";") == -1)
452    {
453    // we add a token, for simplicity
454  1106 outstring = outstring + ";";
455    }
456  1106 StringTokenizer st = new StringTokenizer(outstring, ";");
457  1106 String tok = "";
458  1106 resultData = new ArrayList<JvDataType>();
459  2212 while (st.hasMoreTokens())
460    {
461  1106 try
462    {
463  1106 resultData.add(JvDataType.valueOf(tok = st.nextToken()));
464    } catch (NoSuchElementException x)
465    {
466  0 warnings.append(
467    "Invalid result type: '" + tok + "' (must be one of: ");
468  0 String sep = "";
469  0 for (JvDataType vl : JvDataType.values())
470    {
471  0 warnings.append(sep);
472  0 warnings.append(vl.toString());
473  0 sep = " ,";
474    }
475  0 warnings.append(" separated by semi-colons)\n");
476    }
477    }
478    }
479   
 
480  5 toggle private String getServiceIOProperties()
481    {
482  5 ArrayList<String> vls = new ArrayList<String>();
483  5 if (isHseparable())
484    {
485  5 vls.add("hseparable");
486    }
487  5 ;
488  5 if (isVseparable())
489    {
490  0 vls.add("vseparable");
491    }
492  5 ;
493  5 vls.add(new String("gapCharacter='" + gapCharacter + "'"));
494  5 vls.add(new String("returns='" + _genOutputFormatString() + "'"));
495  5 return StringUtils.arrayToSeparatorList(vls.toArray(new String[0]),
496    ",");
497    }
498   
 
499  5 toggle public String toString()
500    {
501  5 StringBuffer result = new StringBuffer();
502  5 result.append("|");
503  5 result.append(details.Name);
504  5 result.append('|');
505  5 result.append(details.Action);
506  5 result.append('|');
507  5 if (details.description != null)
508    {
509  5 result.append(details.description);
510    }
511  5 ;
512    // list job input flags
513  5 result.append('|');
514  5 result.append(getServiceIOProperties());
515    // list any additional cgi parameters needed for result retrieval
516  5 if (urlSuffix != null && urlSuffix.length() > 0)
517    {
518  5 result.append('|');
519  5 result.append(urlSuffix);
520    }
521  5 result.append('|');
522  5 result.append(getInputParamEncodedUrl());
523  5 return result.toString();
524    }
525   
526    /**
527    * processes a service encoded as a string (as generated by
528    * RestServiceDescription.toString()) Note - this will only use the first
529    * service definition encountered in the string to configure the service.
530    *
531    * @param encoding
532    * @param warnings
533    * - where warning messages are reported.
534    * @return true if configuration was parsed successfully.
535    */
 
536  1105 toggle public boolean configureFromEncodedString(String encoding,
537    StringBuffer warnings)
538    {
539  1105 String[] list = StringUtils.separatorListToArray(encoding, "|");
540   
541  1105 int nextpos = parseServiceList(list, warnings, 0);
542  1105 if (nextpos > 0)
543    {
544  1105 return true;
545    }
546  0 return false;
547    }
548   
549    /**
550    * processes the given list from position p, attempting to configure the
551    * service from it. Service lists are formed by concatenating individual
552    * stringified services. The first character of a stringified service is '|',
553    * enabling this, and the parser will ignore empty fields in a '|' separated
554    * list when they fall outside a service definition.
555    *
556    * @param list
557    * @param warnings
558    * @param p
559    * @return
560    */
 
561  1106 toggle protected int parseServiceList(String[] list, StringBuffer warnings,
562    int p)
563    {
564  1106 boolean invalid = false;
565    // look for the first non-empty position - expect it to be service name
566  2212 while (list[p] != null && list[p].trim().length() == 0)
567    {
568  1106 p++;
569    }
570  1106 details.Name = list[p];
571  1106 details.Action = list[p + 1];
572  1106 details.description = list[p + 2];
573  1106 invalid |= !configureFromServiceInputProperties(list[p + 3], warnings);
574  1106 if (list.length - p > 5 && list[p + 5] != null
575    && list[p + 5].trim().length() > 5)
576    {
577  1106 urlSuffix = list[p + 4];
578  1106 invalid |= !configureFromInputParamEncodedUrl(list[p + 5], warnings);
579  1106 p += 6;
580    }
581    else
582    {
583  0 if (list.length - p > 4 && list[p + 4] != null
584    && list[p + 4].trim().length() > 5)
585    {
586  0 urlSuffix = null;
587  0 invalid |= !configureFromInputParamEncodedUrl(list[p + 4],
588    warnings);
589  0 p += 5;
590    }
591    }
592  1106 return invalid ? -1 : p;
593    }
594   
595    /**
596    * @return string representation of the input parameters, their type and
597    * constraints, appended to the service's base submission URL
598    */
 
599  5 toggle private String getInputParamEncodedUrl()
600    {
601  5 StringBuffer url = new StringBuffer();
602  5 if (postUrl == null || postUrl.length() < 5)
603    {
604  0 return "";
605    }
606   
607  5 url.append(postUrl);
608  5 char appendChar = (postUrl.indexOf("?") > -1) ? '&' : '?';
609  5 boolean consts = true;
610  5 do
611    {
612  10 for (Map.Entry<String, InputType> param : inputParams.entrySet())
613    {
614  20 List<String> vals = param.getValue().getURLEncodedParameter();
615  20 if (param.getValue().isConstant())
616    {
617  0 if (consts)
618    {
619  0 url.append(appendChar);
620  0 appendChar = '&';
621  0 url.append(param.getValue().token);
622  0 if (vals.size() == 1)
623    {
624  0 url.append("=");
625  0 url.append(vals.get(0));
626    }
627    }
628    }
629    else
630    {
631  20 if (!consts)
632    {
633  10 url.append(appendChar);
634  10 appendChar = '&';
635  10 url.append(param.getValue().token);
636  10 url.append("=");
637    // write parameter set as $TOKENPREFIX:csv list of params$ for this
638    // input param
639  10 url.append("$");
640  10 url.append(param.getValue().getURLtokenPrefix());
641  10 url.append(":");
642  10 url.append(StringUtils.arrayToSeparatorList(
643    vals.toArray(new String[0]), ","));
644  10 url.append("$");
645    }
646    }
647   
648    }
649    // toggle consts and repeat until !consts is false:
650  ? } while (!(consts = !consts));
651  5 return url.toString();
652    }
653   
654    /**
655    * parse the service URL and input parameters from the given encoded URL
656    * string and configure the RestServiceDescription from it.
657    *
658    * @param ipurl
659    * @param warnings
660    * where any warnings
661    * @return true if URL parsed correctly. false means the configuration failed.
662    */
 
663  1106 toggle private boolean configureFromInputParamEncodedUrl(String ipurl,
664    StringBuffer warnings)
665    {
666  1106 boolean valid = true;
667  1106 int lastp = 0;
668  1106 String url = new String();
669  1106 Matcher prms = PARAM_ENCODED_URL_PATTERN.matcher(ipurl);
670  1106 Map<String, InputType> iparams = new Hashtable<String, InputType>();
671  1106 InputType jinput;
672  3318 while (prms.find())
673    {
674  2212 if (lastp < prms.start(0))
675    {
676  1106 url += ipurl.substring(lastp, prms.start(0));
677  1106 lastp = prms.end(0) + 1;
678    }
679  2212 String sep = prms.group(1);
680  2212 String tok = prms.group(2);
681  2212 String iprm = prms.group(3);
682  2212 int colon = iprm.indexOf(":");
683  2212 String iprmparams = "";
684  2212 if (colon > -1)
685    {
686  2212 iprmparams = iprm.substring(colon + 1);
687  2212 iprm = iprm.substring(0, colon);
688    }
689  2212 valid = parseTypeString(prms.group(0), tok, iprm, iprmparams, iparams,
690    warnings);
691    }
692  1106 if (valid)
693    {
694  1106 try
695    {
696  1106 URL u = new URL(url);
697  1106 postUrl = url;
698  1106 inputParams = iparams;
699    } catch (Exception e)
700    {
701  0 warnings.append("Failed to parse '" + url + "' as a URL.\n");
702  0 valid = false;
703    }
704    }
705  1106 return valid;
706    }
707   
 
708  2212 toggle public static Class[] getInputTypes()
709    {
710    // TODO - find a better way of maintaining this classlist
711  2212 return new Class[] { jalview.ws.rest.params.Alignment.class,
712    jalview.ws.rest.params.AnnotationFile.class,
713    SeqGroupIndexVector.class, jalview.ws.rest.params.SeqIdVector.class,
714    jalview.ws.rest.params.SeqVector.class,
715    jalview.ws.rest.params.Tree.class };
716    }
717   
 
718  2212 toggle public static boolean parseTypeString(String fullstring, String tok,
719    String iprm, String iprmparams, Map<String, InputType> iparams,
720    StringBuffer warnings)
721    {
722  2212 boolean valid = true;
723  2212 InputType jinput;
724  2212 for (Class type : getInputTypes())
725    {
726  4424 try
727    {
728  4424 jinput = (InputType) (type.getConstructor().newInstance());
729  4424 if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix()))
730    {
731  2212 ArrayList<String> al = new ArrayList<String>();
732  2212 for (String prprm : StringUtils.separatorListToArray(iprmparams,
733    ","))
734    {
735    // hack to ensure that strings like "sep=','" containing unescaped
736    // commas as values are concatenated
737  5530 al.add(prprm.trim());
738    }
739  2212 if (!jinput.configureFromURLtokenString(al, warnings))
740    {
741  0 valid = false;
742  0 warnings.append("Failed to parse '" + fullstring + "' as a "
743    + jinput.getURLtokenPrefix() + " input tag.\n");
744    }
745    else
746    {
747  2212 jinput.token = tok;
748  2212 iparams.put(tok, jinput);
749  2212 valid = true;
750    }
751  2212 break;
752    }
753   
754    } catch (Throwable thr)
755    {
756    }
757  2212 ;
758    }
759  2212 return valid;
760    }
761   
762    /**
763    * covenience method to generate the id and sequence string vector from a set
764    * of seuqences using each sequence's getName() and getSequenceAsString()
765    * method
766    *
767    * @param seqs
768    * @return String[][] {{sequence ids},{sequence strings}}
769    */
 
770  0 toggle public static String[][] formStrings(SequenceI[] seqs)
771    {
772  0 String[][] idset = new String[2][seqs.length];
773  0 for (int i = 0; i < seqs.length; i++)
774    {
775  0 idset[0][i] = seqs[i].getName();
776  0 idset[1][i] = seqs[i].getSequenceAsString();
777    }
778  0 return idset;
779    }
780   
781    /**
782    * can this service be run on the visible portion of an alignment regardless
783    * of hidden boundaries ?
784    */
785    boolean hseparable = false;
786   
787    boolean vseparable = false;
788   
 
789  9 toggle public boolean isHseparable()
790    {
791  9 return hseparable;
792    }
793   
794    /**
795    *
796    * @return
797    */
 
798  5 toggle public boolean isVseparable()
799    {
800  5 return vseparable;
801    }
802   
803    /**
804    * search the input types for an instance of the given class
805    *
806    * @param <validInput.inputType>
807    * class1
808    * @return
809    */
 
810  3 toggle public boolean inputInvolves(Class<?> class1)
811    {
812  3 assert (InputType.class.isAssignableFrom(class1));
813  3 for (InputType val : inputParams.values())
814    {
815  5 if (class1.isAssignableFrom(val.getClass()))
816    {
817  2 return true;
818    }
819    }
820  1 return false;
821    }
822   
823    char gapCharacter = '-';
824   
825    /**
826    *
827    * @return the preferred gap character for alignments input/output by this
828    * service
829    */
 
830  2 toggle public char getGapCharacter()
831    {
832  2 return gapCharacter;
833    }
834   
 
835  0 toggle public String getDecoratedResultUrl(String jobId)
836    {
837    // TODO: correctly write ?/& appropriate to result URL format.
838  0 return jobId + urlSuffix;
839    }
840   
841    private List<JvDataType> resultData = new ArrayList<JvDataType>();
842   
843    /**
844    *
845    *
846    * TODO: Extend to optionally specify relative/absolute url where data of this
847    * type can be retrieved from
848    *
849    * @param dt
850    */
 
851  3 toggle public void addResultDatatype(JvDataType dt)
852    {
853  3 if (resultData == null)
854    {
855  0 resultData = new ArrayList<JvDataType>();
856    }
857  3 resultData.add(dt);
858    }
859   
 
860  0 toggle public boolean removeRsultDatatype(JvDataType dt)
861    {
862  0 if (resultData != null)
863    {
864  0 return resultData.remove(dt);
865    }
866  0 return false;
867    }
868   
 
869  0 toggle public List<JvDataType> getResultDataTypes()
870    {
871  0 return resultData;
872    }
873   
874    /**
875    * parse a concatenated list of rest service descriptions into an array
876    *
877    * @param services
878    * @return zero or more services.
879    * @throws exceptions
880    * if the services are improperly encoded.
881    */
 
882  1 toggle public static List<RestServiceDescription> parseDescriptions(
883    String services) throws Exception
884    {
885  1 String[] list = StringUtils.separatorListToArray(services, "|");
886  1 List<RestServiceDescription> svcparsed = new ArrayList<RestServiceDescription>();
887  1 int p = 0, lastp = 0;
888  1 StringBuffer warnings = new StringBuffer();
889  1 do
890    {
891  1 RestServiceDescription rsd = new RestServiceDescription();
892  1 p = rsd.parseServiceList(list, warnings, lastp = p);
893  1 if (p > lastp && rsd.isValid())
894    {
895  1 svcparsed.add(rsd);
896    }
897    else
898    {
899  0 throw new Exception(
900    "Failed to parse user defined RSBS services from :"
901    + services
902    + "\nFirst error was encountered at token " + lastp
903    + " starting " + list[lastp] + ":\n"
904    + rsd.getInvalidMessage());
905    }
906  1 } while (p < lastp && p < list.length - 1);
907  1 return svcparsed;
908    }
909   
910    }