Clover icon

Coverage Report

  1. Project Clover database Fri Dec 6 2024 13:47:14 GMT
  2. Package jalview.ws.rest

File RestServiceDescription.java

 

Coverage histogram

../../../img/srcFileCovDistChart7.png
29% 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
0.6666%
RestServiceDescription.UIinfo 111 6 6
0.00%
 

Contributing tests

This file is covered by 211 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  6 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  8 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  8 super();
69  8 this.details = new UIinfo();
70  8 details.Action = action == null ? "" : action;
71  8 details.description = description == null ? "" : description;
72  8 details.Name = name == null ? "" : name;
73  8 this.postUrl = postUrl == null ? "" : postUrl;
74  8 this.urlSuffix = urlSuffix == null ? "" : urlSuffix;
75  8 if (inputParams != null)
76    {
77  8 this.inputParams = inputParams;
78    }
79  8 this.hseparable = hseparable;
80  8 this.vseparable = vseparable;
81  8 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  2697 toggle public RestServiceDescription(String link)
317    {
318  2697 StringBuffer warnings = new StringBuffer();
319  2697 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  7 toggle public boolean isValid()
362    {
363  7 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  2703 toggle private boolean configureFromServiceInputProperties(String propList,
375    StringBuffer warnings)
376    {
377  2703 String[] props = StringUtils.separatorListToArray(propList, ",");
378  2703 if (props == null)
379    {
380  0 return true;
381    }
382  2703 ;
383  2703 boolean valid = true;
384  2703 String val = null;
385  2703 int l = warnings.length();
386  2703 int i;
387  2703 for (String prop : props)
388    {
389  ? if ((i = prop.indexOf("=")) > -1)
390    {
391  5406 val = prop.substring(i + 1);
392  5406 if (val.startsWith("\'") && val.endsWith("\'"))
393    {
394  5406 val = val.substring(1, val.length() - 1);
395    }
396  5406 prop = prop.substring(0, i);
397    }
398   
399  8109 if (prop.equals("hseparable"))
400    {
401  2703 hseparable = true;
402    }
403  8109 if (prop.equals("vseparable"))
404    {
405  0 vseparable = true;
406    }
407  8109 if (prop.equals("gapCharacter"))
408    {
409  2703 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  2703 gapCharacter = val.charAt(0);
419    }
420    }
421  8109 if (prop.equals("returns"))
422    {
423  2703 _configureOutputFormatFrom(val, warnings);
424    }
425    }
426    // return true if valid is true and warning buffer was not appended to.
427  2703 return valid && (l == warnings.length());
428    }
429   
 
430  15 toggle private String _genOutputFormatString()
431    {
432  15 String buff = "";
433  15 if (resultData == null)
434    {
435  0 return "";
436    }
437  15 for (JvDataType type : resultData)
438    {
439  15 if (buff.length() > 0)
440    {
441  0 buff += ";";
442    }
443  15 buff += type.toString();
444    }
445  15 return buff;
446    }
447   
 
448  2703 toggle private void _configureOutputFormatFrom(String outstring,
449    StringBuffer warnings)
450    {
451  2703 if (outstring.indexOf(";") == -1)
452    {
453    // we add a token, for simplicity
454  2703 outstring = outstring + ";";
455    }
456  2703 StringTokenizer st = new StringTokenizer(outstring, ";");
457  2703 String tok = "";
458  2703 resultData = new ArrayList<JvDataType>();
459  5406 while (st.hasMoreTokens())
460    {
461  2703 try
462    {
463  2703 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  15 toggle private String getServiceIOProperties()
481    {
482  15 ArrayList<String> vls = new ArrayList<String>();
483  15 if (isHseparable())
484    {
485  15 vls.add("hseparable");
486    }
487  15 ;
488  15 if (isVseparable())
489    {
490  0 vls.add("vseparable");
491    }
492  15 ;
493  15 vls.add(new String("gapCharacter='" + gapCharacter + "'"));
494  15 vls.add(new String("returns='" + _genOutputFormatString() + "'"));
495  15 return StringUtils.arrayToSeparatorList(vls.toArray(new String[0]),
496    ",");
497    }
498   
 
499  15 toggle public String toString()
500    {
501  15 StringBuffer result = new StringBuffer();
502  15 result.append("|");
503  15 result.append(details.Name);
504  15 result.append('|');
505  15 result.append(details.Action);
506  15 result.append('|');
507  15 if (details.description != null)
508    {
509  15 result.append(details.description);
510    }
511  15 ;
512    // list job input flags
513  15 result.append('|');
514  15 result.append(getServiceIOProperties());
515    // list any additional cgi parameters needed for result retrieval
516  15 if (urlSuffix != null && urlSuffix.length() > 0)
517    {
518  15 result.append('|');
519  15 result.append(urlSuffix);
520    }
521  15 result.append('|');
522  15 result.append(getInputParamEncodedUrl());
523  15 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  2697 toggle public boolean configureFromEncodedString(String encoding,
537    StringBuffer warnings)
538    {
539  2697 String[] list = StringUtils.separatorListToArray(encoding, "|");
540   
541  2697 int nextpos = parseServiceList(list, warnings, 0);
542  2697 if (nextpos > 0)
543    {
544  2697 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  2703 toggle protected int parseServiceList(String[] list, StringBuffer warnings,
562    int p)
563    {
564  2703 boolean invalid = false;
565    // look for the first non-empty position - expect it to be service name
566  5406 while (list[p] != null && list[p].trim().length() == 0)
567    {
568  2703 p++;
569    }
570  2703 details.Name = list[p];
571  2703 details.Action = list[p + 1];
572  2703 details.description = list[p + 2];
573  2703 invalid |= !configureFromServiceInputProperties(list[p + 3], warnings);
574  2703 if (list.length - p > 5 && list[p + 5] != null
575    && list[p + 5].trim().length() > 5)
576    {
577  2703 urlSuffix = list[p + 4];
578  2703 invalid |= !configureFromInputParamEncodedUrl(list[p + 5], warnings);
579  2703 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  2703 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  15 toggle private String getInputParamEncodedUrl()
600    {
601  15 StringBuffer url = new StringBuffer();
602  15 if (postUrl == null || postUrl.length() < 5)
603    {
604  0 return "";
605    }
606   
607  15 url.append(postUrl);
608  15 char appendChar = (postUrl.indexOf("?") > -1) ? '&' : '?';
609  15 boolean consts = true;
610  15 do
611    {
612  30 for (Map.Entry<String, InputType> param : inputParams.entrySet())
613    {
614  60 List<String> vals = param.getValue().getURLEncodedParameter();
615  60 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  60 if (!consts)
632    {
633  30 url.append(appendChar);
634  30 appendChar = '&';
635  30 url.append(param.getValue().token);
636  30 url.append("=");
637    // write parameter set as $TOKENPREFIX:csv list of params$ for this
638    // input param
639  30 url.append("$");
640  30 url.append(param.getValue().getURLtokenPrefix());
641  30 url.append(":");
642  30 url.append(StringUtils.arrayToSeparatorList(
643    vals.toArray(new String[0]), ","));
644  30 url.append("$");
645    }
646    }
647   
648    }
649    // toggle consts and repeat until !consts is false:
650  ? } while (!(consts = !consts));
651  15 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  2703 toggle private boolean configureFromInputParamEncodedUrl(String ipurl,
664    StringBuffer warnings)
665    {
666  2703 boolean valid = true;
667  2703 int lastp = 0;
668  2703 String url = new String();
669  2703 Matcher prms = PARAM_ENCODED_URL_PATTERN.matcher(ipurl);
670  2703 Map<String, InputType> iparams = new Hashtable<String, InputType>();
671  2703 InputType jinput;
672  8109 while (prms.find())
673    {
674  5406 if (lastp < prms.start(0))
675    {
676  2703 url += ipurl.substring(lastp, prms.start(0));
677  2703 lastp = prms.end(0) + 1;
678    }
679  5406 String sep = prms.group(1);
680  5406 String tok = prms.group(2);
681  5406 String iprm = prms.group(3);
682  5406 int colon = iprm.indexOf(":");
683  5406 String iprmparams = "";
684  5406 if (colon > -1)
685    {
686  5406 iprmparams = iprm.substring(colon + 1);
687  5406 iprm = iprm.substring(0, colon);
688    }
689  5406 valid = parseTypeString(prms.group(0), tok, iprm, iprmparams, iparams,
690    warnings);
691    }
692  2703 if (valid)
693    {
694  2703 try
695    {
696  2703 URL u = new URL(url);
697  2703 postUrl = url;
698  2703 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  2703 return valid;
706    }
707   
 
708  5406 toggle public static Class[] getInputTypes()
709    {
710    // TODO - find a better way of maintaining this classlist
711  5406 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  5406 toggle public static boolean parseTypeString(String fullstring, String tok,
719    String iprm, String iprmparams, Map<String, InputType> iparams,
720    StringBuffer warnings)
721    {
722  5406 boolean valid = true;
723  5406 InputType jinput;
724  5406 for (Class type : getInputTypes())
725    {
726  10812 try
727    {
728  10812 jinput = (InputType) (type.getConstructor().newInstance());
729  10812 if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix()))
730    {
731  5406 ArrayList<String> al = new ArrayList<String>();
732  5406 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  13515 al.add(prprm.trim());
738    }
739  5406 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  5406 jinput.token = tok;
748  5406 iparams.put(tok, jinput);
749  5406 valid = true;
750    }
751  5406 break;
752    }
753   
754    } catch (Throwable thr)
755    {
756    }
757  5406 ;
758    }
759  5406 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  19 toggle public boolean isHseparable()
790    {
791  19 return hseparable;
792    }
793   
794    /**
795    *
796    * @return
797    */
 
798  15 toggle public boolean isVseparable()
799    {
800  15 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  8 toggle public void addResultDatatype(JvDataType dt)
852    {
853  8 if (resultData == null)
854    {
855  0 resultData = new ArrayList<JvDataType>();
856    }
857  8 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  6 toggle public static List<RestServiceDescription> parseDescriptions(
883    String services) throws Exception
884    {
885  6 String[] list = StringUtils.separatorListToArray(services, "|");
886  6 List<RestServiceDescription> svcparsed = new ArrayList<RestServiceDescription>();
887  6 int p = 0, lastp = 0;
888  6 StringBuffer warnings = new StringBuffer();
889  6 do
890    {
891  6 RestServiceDescription rsd = new RestServiceDescription();
892  6 p = rsd.parseServiceList(list, warnings, lastp = p);
893  6 if (p > lastp && rsd.isValid())
894    {
895  6 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  6 } while (p < lastp && p < list.length - 1);
907  6 return svcparsed;
908    }
909   
910    }