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