20895a21c7c650bc84aba4135135fb86a16af1c2
[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 = (pos>0 && 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 (v.length()>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 boolean configureFromServiceInputProperties(String propList,
491           StringBuffer warnings)
492   {
493     String[] props = separatorListToArray(propList, ",");
494     if (props == null)
495     {
496       return true;
497     }
498     ;
499     boolean valid=true;
500     String val = null;
501     int i;
502     for (String prop : props)
503     {
504       if ((i = prop.indexOf("=")) > -1)
505       {
506         val = prop.substring(i + 1);
507         if (val.startsWith("\'") && val.endsWith("\'"))
508         {
509           val = val.substring(1, val.length() - 1);
510         }
511         prop = prop.substring(0, i);
512       }
513
514       if (prop.equals("hseparable"))
515       {
516         hseparable = true;
517       }
518       if (prop.equals("vseparable"))
519       {
520         vseparable = true;
521       }
522       if (prop.equals("gapCharacter"))
523       {
524         if (val == null || val.length()==0 || val.length() > 1)
525         {
526           valid=false;
527           warnings.append((warnings.length() > 0 ? "\n" : "")
528                   + ("Invalid service property: gapCharacter=' ' (single character) - was given '"
529                           + val + "'"));
530         }
531         else
532         {
533           gapCharacter = val.charAt(0);
534         }
535       }
536       if (prop.equals("returns"))
537       {
538         int l=warnings.length();
539         _configureOutputFormatFrom(val, warnings);
540         valid =  (l!=warnings.length());
541       }
542     }
543     return valid;
544   }
545
546   private String _genOutputFormatString()
547   {
548     String buff = "";
549     if (resultData==null)
550     {
551       return "";
552     }
553     for (JvDataType type : resultData)
554     {
555       if (buff.length() > 0)
556       {
557         buff += ";";
558       }
559       buff += type.toString();
560     }
561     return buff;
562   }
563
564   private void _configureOutputFormatFrom(String outstring,
565           StringBuffer warnings)
566   {
567     if (outstring.indexOf(";")==-1) {
568       // we add a token, for simplicity
569       outstring = outstring+";";
570     }
571     StringTokenizer st = new StringTokenizer(outstring, ";");
572     String tok = "";
573     resultData = new ArrayList<JvDataType>();
574     while (st.hasMoreTokens())
575     {
576       try
577       {
578         resultData.add(JvDataType.valueOf(tok = st.nextToken()));
579       } catch (NoSuchElementException x)
580       {
581         warnings.append("Invalid result type: '" + tok
582                 + "' (must be one of: ");
583         String sep = "";
584         for (JvDataType vl : JvDataType.values())
585         {
586           warnings.append(sep);
587           warnings.append(vl.toString());
588           sep = " ,";
589         }
590         warnings.append(" separated by semi-colons)\n");
591       }
592     }
593   }
594
595   private String getServiceIOProperties()
596   {
597     String[] vls = new String[]
598     { isHseparable() ? "hseparable" : "",
599         isVseparable() ? "vseparable" : "",
600         (new String("gapCharacter='" + gapCharacter + "'")),
601         (new String("returns='" + _genOutputFormatString() + "'")) };
602
603     return arrayToSeparatorList(vls, ",");
604   }
605
606   public String toString()
607   {
608     StringBuffer result = new StringBuffer();
609     result.append(details.Name);
610     result.append('|');
611     result.append(details.Action);
612     result.append('|');
613     if (details.description != null)
614     {
615       result.append(details.description);
616     }
617     ;
618     // list job input flags
619     result.append('|');
620     result.append(getServiceIOProperties());
621     // list any additional cgi parameters needed for result retrieval
622     if (urlSuffix != null && urlSuffix.length() > 0)
623     {
624       result.append('|');
625       result.append(urlSuffix);
626     }
627     result.append('|');
628     result.append(getInputParamEncodedUrl());
629     return result.toString();
630   }
631
632   public boolean configureFromEncodedString(String encoding,
633           StringBuffer warnings)
634   {
635     boolean invalid = false;
636     String[] list = separatorListToArray(encoding, "|");
637     details.Name = list[0];
638     details.Action = list[1];
639     details.description = list[2];
640     invalid|=!configureFromServiceInputProperties(list[3], warnings);
641     if (list.length > 5)
642     {
643       urlSuffix = list[4];
644       invalid |= !configureFromInputParamEncodedUrl(list[5], warnings);
645     }
646     else
647     {
648       urlSuffix = null;
649       invalid |= !configureFromInputParamEncodedUrl(list[4], warnings);
650     }
651     return !invalid;
652   }
653
654   /**
655    * @return string representation of the input parameters, their type and
656    *         constraints, appended to the service's base submission URL
657    */
658   private String getInputParamEncodedUrl()
659   {
660     StringBuffer url = new StringBuffer();
661     if (postUrl == null || postUrl.length() < 5)
662     {
663       return "";
664     }
665
666     url.append(postUrl);
667     char appendChar = (postUrl.indexOf("?") > -1) ? '&' : '?';
668     boolean consts = true;
669     do
670     {
671       for (Map.Entry<String, InputType> param : inputParams.entrySet())
672       {
673         List<String> vals = param.getValue().getURLEncodedParameter();
674         if (param.getValue().isConstant())
675         {
676           if (consts)
677           {
678             url.append(appendChar);
679             appendChar = '&';
680             url.append(param.getValue().token);
681             if (vals.size() == 1)
682             {
683               url.append("=");
684               url.append(vals.get(0));
685             }
686           }
687         }
688         else
689         {
690           if (!consts)
691           {
692             url.append(appendChar);
693             appendChar = '&';
694             url.append(param.getValue().token);
695             url.append("=");
696             // write parameter set as $TOKENPREFIX:csv list of params$ for this
697             // input param
698             url.append("$");
699             url.append(param.getValue().getURLtokenPrefix());
700             url.append(":");
701             url.append(arrayToSeparatorList(vals.toArray(new String[0]),
702                     ","));
703             url.append("$");
704           }
705         }
706
707       }
708       // toggle consts and repeat until !consts is false:
709     } while (!(consts = !consts));
710     return url.toString();
711   }
712
713   /**
714    * parse the service URL and input parameters from the given encoded URL
715    * string and configure the RestServiceDescription from it.
716    * 
717    * @param ipurl
718    * @param warnings
719    *          where any warnings
720    * @return true if URL parsed correctly. false means the configuration failed.
721    */
722   private boolean configureFromInputParamEncodedUrl(String ipurl,
723           StringBuffer warnings)
724   {
725     boolean valid = true;
726     int lastp = 0;
727     String url = new String();
728     Matcher prms = Pattern.compile("([?&])([A-Za-z0-9_]+)=\\$([^$]+)\\$")
729             .matcher(ipurl);
730     Map<String, InputType> iparams = new Hashtable<String, InputType>();
731     InputType jinput;
732     while (prms.find())
733     {
734       if (lastp < prms.start(0))
735       {
736         url += ipurl.substring(lastp, prms.start(0));
737         lastp = prms.end(0) + 1;
738       }
739       String sep = prms.group(1);
740       String tok = prms.group(2);
741       String iprm = prms.group(3);
742       int colon = iprm.indexOf(":");
743       String iprmparams = "";
744       if (colon > -1)
745       {
746         iprmparams = iprm.substring(colon + 1);
747         iprm = iprm.substring(0, colon);
748       }
749       valid = parseTypeString(prms.group(0), tok,  iprm, iprmparams, iparams, warnings);
750     }
751     if (valid)
752     {
753       try
754       {
755         URL u = new URL(url);
756         postUrl = url;
757         inputParams = iparams;
758       } catch (Exception e)
759       {
760         warnings.append("Failed to parse '" + url + "' as a URL.\n");
761         valid = false;
762       }
763     }
764     return valid;
765   }
766
767   public static boolean parseTypeString(String fullstring, String tok, String iprm, String iprmparams,
768            Map<String, InputType> iparams, StringBuffer warnings)
769   {
770     boolean valid=true;
771     InputType jinput;
772     // TODO - find a better way of maintaining this classlist
773     for (Class type : new Class[]
774     { jalview.ws.rest.params.Alignment.class,
775         jalview.ws.rest.params.AnnotationFile.class,
776         SeqGroupIndexVector.class,
777         jalview.ws.rest.params.SeqIdVector.class,
778         jalview.ws.rest.params.SeqVector.class,
779         jalview.ws.rest.params.Tree.class })
780     {
781       try
782       {
783         jinput = (InputType) (type.getConstructor().newInstance(null));
784         if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix()))
785         {
786           ArrayList<String> al = new ArrayList<String>();
787           for (String prprm : separatorListToArray(iprmparams, ","))
788           {
789             al.add(prprm.trim());
790           }
791           if (!jinput.configureFromURLtokenString(al, warnings))
792           {
793             valid = false;
794             warnings.append("Failed to parse '" + fullstring
795                     + "' as a " + jinput.getURLtokenPrefix()
796                     + " input tag.\n");
797           }
798           else
799           {
800             jinput.token = tok;
801             iparams.put(tok, jinput);
802             valid=true;
803           }
804           break;
805         }
806
807       } catch (Throwable thr)
808       {
809       }
810       ;
811     }
812     return valid;
813   }
814
815
816   public static void main(String argv[])
817   {
818     if (argv.length == 0)
819     {
820       if (!testRsdExchange("Test using default Shmmr service",
821               RestClient.makeShmmrRestClient().service))
822       {
823         System.err.println("default test failed.");
824       }
825       else
826       {
827         System.err.println("default test passed.");
828       }
829     }
830     else
831     {
832       int i = 0, p = 0;
833       for (String svc : argv)
834       {
835         p += testRsdExchange("Test " + (++i), svc) ? 1 : 0;
836       }
837       System.err.println("" + p + " out of " + i + " tests passed.");
838
839     }
840   }
841
842   private static boolean testRsdExchange(String desc, String servicestring)
843   {
844     try
845     {
846       RestServiceDescription newService = new RestServiceDescription(
847               servicestring);
848       if (!newService.isValid())
849       {
850         throw new Error("Failed to create service from '" + servicestring
851                 + "'.\n" + newService.getInvalidMessage());
852       }
853       return testRsdExchange(desc, newService);
854     } catch (Throwable x)
855     {
856       System.err.println("Failed for service (" + desc + "): "
857               + servicestring);
858       x.printStackTrace();
859       return false;
860     }
861   }
862
863   private static boolean testRsdExchange(String desc,
864           RestServiceDescription service)
865   {
866     try
867     {
868       String fromservicetostring = service.toString();
869       RestServiceDescription newService = new RestServiceDescription(
870               fromservicetostring);
871       if (!newService.isValid())
872       {
873         throw new Error("Failed to create service from '"
874                 + fromservicetostring + "'.\n"
875                 + newService.getInvalidMessage());
876       }
877
878       if (!service.equals(newService))
879       {
880         System.err.println("Failed for service (" + desc + ").");
881         System.err.println("Original service and parsed service differ.");
882         System.err.println("Original: " + fromservicetostring);
883         System.err.println("Parsed  : " + newService.toString());
884         return false;
885       }
886     } catch (Throwable x)
887     {
888       System.err.println("Failed for service (" + desc + "): "
889               + service.toString());
890       x.printStackTrace();
891       return false;
892     }
893     return true;
894   }
895
896   /**
897    * covenience method to generate the id and sequence string vector from a set
898    * of seuqences using each sequence's getName() and getSequenceAsString()
899    * method
900    * 
901    * @param seqs
902    * @return String[][] {{sequence ids},{sequence strings}}
903    */
904   public static String[][] formStrings(SequenceI[] seqs)
905   {
906     String[][] idset = new String[2][seqs.length];
907     for (int i = 0; i < seqs.length; i++)
908     {
909       idset[0][i] = seqs[i].getName();
910       idset[1][i] = seqs[i].getSequenceAsString();
911     }
912     return idset;
913   }
914
915   /**
916    * can this service be run on the visible portion of an alignment regardless
917    * of hidden boundaries ?
918    */
919   boolean hseparable = false;
920
921   boolean vseparable = false;
922
923   public boolean isHseparable()
924   {
925     return hseparable;
926   }
927
928   /**
929    * 
930    * @return
931    */
932   public boolean isVseparable()
933   {
934     return vseparable;
935   }
936
937   /**
938    * search the input types for an instance of the given class
939    * 
940    * @param <validInput.inputType> class1
941    * @return
942    */
943   public boolean inputInvolves(Class<?> class1)
944   {
945     assert (InputType.class.isAssignableFrom(class1));
946     for (InputType val : inputParams.values())
947     {
948       if (class1.isAssignableFrom(val.getClass()))
949       {
950         return true;
951       }
952     }
953     return false;
954   }
955
956   char gapCharacter = '-';
957
958   /**
959    * 
960    * @return the preferred gap character for alignments input/output by this
961    *         service
962    */
963   public char getGapCharacter()
964   {
965     return gapCharacter;
966   }
967
968   public String getDecoratedResultUrl(String jobId)
969   {
970     // TODO: correctly write ?/& appropriate to result URL format.
971     return jobId + urlSuffix;
972   }
973
974   private List<JvDataType> resultData=new ArrayList<JvDataType>();
975
976   /**
977    * 
978    * 
979    * TODO: Extend to optionally specify relative/absolute url where data of this
980    * type can be retrieved from
981    * 
982    * @param dt
983    */
984   public void addResultDatatype(JvDataType dt)
985   {
986     if (resultData == null)
987     {
988       resultData = new ArrayList<JvDataType>();
989     }
990     resultData.add(dt);
991   }
992
993   public boolean removeRsultDatatype(JvDataType dt)
994   {
995     if (resultData != null)
996     {
997       return resultData.remove(dt);
998     }
999     return false;
1000   }
1001
1002   public List<JvDataType> getResultDataTypes()
1003   {
1004     return resultData;
1005   }
1006   
1007 }