Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.ws.rest

File RestServiceDescription.java

 

Coverage histogram

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

Code metrics

96
246
46
1
864
580
110
0.45
5.35
46
2.39

Classes

Class Line # Actions
RestServiceDescription 42 246 110
0.657216565.7%
 

Contributing tests

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