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
5 * This file is part of Jalview.
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.
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.
16 * You should have received a copy of the GNU General Public License along with Jalview. If not, see <http://www.gnu.org/licenses/>.
18 package jalview.ws.rest;
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;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.Hashtable;
35 import java.util.List;
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;
43 import javax.swing.JViewport;
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;
49 public class RestServiceDescription
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)
66 this.details = new UIinfo();
67 details.Action = action;
68 details.description = description;
70 this.postUrl = postUrl;
71 this.urlSuffix = urlSuffix;
72 this.inputParams = inputParams;
73 this.hseparable = hseparable;
74 this.vseparable = vseparable;
75 this.gapCharacter = gapCharacter;
78 public boolean equals(Object o)
80 if (o == null || !(o instanceof RestServiceDescription))
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);
99 * Service UI Info { Action, Specific Name of Service, Brief Description }
111 public UIinfo details = new UIinfo();
119 * suffix that should be added to any url used if it does not already end in
125 * input info given as key/value pairs - mapped to post arguments
127 Map<String, InputType> inputParams = new HashMap();
130 * assigns the given inputType it to its corresponding input parameter token
135 public void setInputParam(InputType it)
137 inputParams.put(it.token, it);
141 * remove the given input type it from the set of service input parameters.
145 public void removeInputParam(InputType it)
147 inputParams.remove(it.token);
151 * service requests alignment data
156 * service requests alignment and/or seuqence annotationo data
161 * service requests partitions defined over input (alignment) data
163 boolean partitiondata;
166 * process ths input data and set the appropriate shorthand flags describing
167 * the input the service wants
169 public void setInvolvesFlags()
171 aligndata = inputInvolves(Alignment.class);
172 annotdata = inputInvolves(AnnotationFile.class);
173 partitiondata = inputInvolves(SeqGroupIndexVector.class);
177 * Service return info { alignment, annotation file (loaded back on to
178 * alignment), tree (loaded back on to alignment), sequence annotation -
179 * loaded back on to alignment), text report, pdb structures with sequence
185 * Start with bare minimum: input is alignment + groups on alignment
191 private String invalidMessage = null;
194 * parse the given linkString of the form '<label>|<url>|separator
195 * char[|optional sequence separator char]' into parts. url may contain a
196 * string $SEQUENCEIDS<=optional regex=>$ where <=optional regex=> must be of
197 * the form =/<perl style regex>/=$ or $SEQUENCES<=optional regex=>$ or
198 * $SEQUENCES<=optional regex=>$.
202 public RestServiceDescription(String link)
204 StringBuffer warnings = new StringBuffer();
205 if (!configureFromEncodedString(link, warnings))
207 if (warnings.length() > 0)
209 invalidMessage = warnings.toString();
214 public RestServiceDescription(RestServiceDescription toedit)
220 // TODO Implement copy constructor NOW
224 * @return the invalidMessage
226 public String getInvalidMessage()
228 return invalidMessage;
232 * Check if URL string was parsed properly.
234 * @return boolean - if false then <code>getInvalidMessage</code> returns an
237 public boolean isValid()
239 return invalidMessage == null;
242 private static boolean debug = false;
245 * parse the string into a list
249 * @return elements separated by separator
251 public static String[] separatorListToArray(String list, String separator)
253 int seplen = separator.length();
254 if (list == null || list.equals("") || list.equals(separator))
256 java.util.ArrayList<String> jv = new ArrayList<String>();
257 int cp = 0, pos, escape;
258 boolean wasescaped = false;
259 String lstitem = null;
260 while ((pos = list.indexOf(separator, cp)) > cp)
262 escape = (list.charAt(pos - 1) == '\\') ? -1 : 0;
265 // append to previous pos
266 jv.set(jv.size() - 1,
267 lstitem = lstitem + separator
268 + list.substring(cp, pos + escape));
273 jv.add(lstitem = list.substring(cp, pos + escape));
276 wasescaped = escape == -1;
278 if (cp < list.length())
280 String c = list.substring(cp);
283 // append final separator
284 jv.set(jv.size() - 1, lstitem + separator + c);
288 if (!c.equals(separator))
296 String[] v = jv.toArray(new String[jv.size()]);
300 System.err.println("Array from '" + separator
301 + "' separated List:\n" + v.length);
302 for (int i = 0; i < v.length; i++)
304 System.err.println("item " + i + " '" + v[i] + "'");
311 System.err.println("Empty Array from '" + separator
312 + "' separated List");
318 * concatenate the list with separator
322 * @return concatenated string
324 public static String arrayToSeparatorList(String[] list, String separator)
326 StringBuffer v = new StringBuffer();
327 if (list != null && list.length > 0)
329 for (int i = 0, iSize = list.length; i < iSize; i++)
337 // TODO - escape any separator values in list[i]
343 System.err.println("Returning '" + separator
344 + "' separated List:\n");
345 System.err.println(v);
351 System.err.println("Returning empty '" + separator
352 + "' separated List\n");
354 return "" + separator;
358 * parse a string containing a list of service properties and configure the
359 * service description
362 * param warnings a StringBuffer that any warnings about invalid
363 * content will be appended to.
365 private void configureFromServiceInputProperties(String propList,
366 StringBuffer warnings)
368 String[] props = separatorListToArray(propList, ",");
376 for (String prop : props)
378 if ((i = prop.indexOf("=")) > -1)
380 val = prop.substring(i + 1);
381 if (val.startsWith("\'") && val.endsWith("\'"))
383 val = val.substring(1, val.length() - 1);
385 prop = prop.substring(0, i);
388 if (prop.equals("hseparable"))
392 if (prop.equals("vseparable"))
396 if (prop.equals("gapCharacter"))
398 if (val == null || val.length() > 1)
400 warnings.append((warnings.length() > 0 ? "\n" : "")
401 + ("Invalid service property: gapCharacter=' ' (single character) - was given '"
406 gapCharacter = val.charAt(0);
409 if (prop.equals("returns"))
411 _configureOurputFormatFrom(val, warnings);
416 private String _genOutputFormatString()
419 if (resultData==null)
423 for (JvDataType type : resultData)
425 if (buff.length() > 0)
429 buff += type.toString();
434 private void _configureOurputFormatFrom(String outstring,
435 StringBuffer warnings)
437 StringTokenizer st = new StringTokenizer(outstring, ";");
439 resultData = new ArrayList<JvDataType>();
440 while (st.hasMoreTokens())
444 resultData.add(JvDataType.valueOf(tok = st.nextToken()));
445 } catch (NoSuchElementException x)
447 warnings.append("Invalid result type: '" + tok
448 + "' (must be one of: ");
450 for (JvDataType vl : JvDataType.values())
452 warnings.append(sep);
453 warnings.append(vl.toString());
456 warnings.append(" separated by semi-colons)\n");
461 private String getServiceIOProperties()
463 String[] vls = new String[]
464 { isHseparable() ? "hseparable" : "",
465 isVseparable() ? "vseparable" : "",
466 (new String("gapCharacter='" + gapCharacter + "'")),
467 (new String("returns='" + _genOutputFormatString() + "'")) };
469 return arrayToSeparatorList(vls, ",");
472 public String toString()
474 StringBuffer result = new StringBuffer();
475 result.append(details.Name);
477 result.append(details.Action);
479 if (details.description != null)
481 result.append(details.description);
484 // list job input flags
486 result.append(getServiceIOProperties());
487 // list any additional cgi parameters needed for result retrieval
488 if (urlSuffix != null && urlSuffix.length() > 0)
491 result.append(urlSuffix);
494 result.append(getInputParamEncodedUrl());
495 return result.toString();
498 public boolean configureFromEncodedString(String encoding,
499 StringBuffer warnings)
501 boolean valid = false;
502 String[] list = separatorListToArray(encoding, "|");
503 details.Name = list[0];
504 details.Action = list[1];
505 details.description = list[2];
506 configureFromServiceInputProperties(list[3], warnings);
510 valid |= configureFromInputParamEncodedUrl(list[5], warnings);
515 valid |= configureFromInputParamEncodedUrl(list[4], warnings);
521 * @return string representation of the input parameters, their type and
522 * constraints, appended to the service's base submission URL
524 private String getInputParamEncodedUrl()
526 StringBuffer url = new StringBuffer();
527 if (postUrl == null || postUrl.length() < 5)
533 char appendChar = (postUrl.indexOf("?") > -1) ? '&' : '?';
534 boolean consts = true;
537 for (Map.Entry<String, InputType> param : inputParams.entrySet())
539 List<String> vals = param.getValue().getURLEncodedParameter();
540 if (param.getValue().isConstant())
544 url.append(appendChar);
546 url.append(param.getValue().token);
547 if (vals.size() == 1)
550 url.append(vals.get(0));
558 url.append(appendChar);
560 url.append(param.getValue().token);
562 // write parameter set as $TOKENPREFIX:csv list of params$ for this
565 url.append(param.getValue().getURLtokenPrefix());
567 url.append(arrayToSeparatorList(vals.toArray(new String[0]),
574 // toggle consts and repeat until !consts is false:
575 } while (!(consts = !consts));
576 return url.toString();
580 * parse the service URL and input parameters from the given encoded URL
581 * string and configure the RestServiceDescription from it.
586 * @return true if URL parsed correctly. false means the configuration failed.
588 private boolean configureFromInputParamEncodedUrl(String ipurl,
589 StringBuffer warnings)
591 boolean valid = true;
593 String url = new String();
594 Matcher prms = Pattern.compile("([?&])([A-Za-z0-9_]+)=\\$([^$]+)\\$")
596 Map<String, InputType> iparams = new Hashtable<String, InputType>();
600 if (lastp < prms.start(0))
602 url += ipurl.substring(lastp, prms.start(0));
603 lastp = prms.end(0) + 1;
605 String sep = prms.group(1);
606 String tok = prms.group(2);
607 String iprm = prms.group(3);
608 int colon = iprm.indexOf(":");
609 String iprmparams = "";
612 iprmparams = iprm.substring(colon + 1);
613 iprm = iprm.substring(0, colon);
615 // TODO - find a better way of maintaining this classlist
616 for (Class type : new Class[]
617 { jalview.ws.rest.params.Alignment.class,
618 jalview.ws.rest.params.AnnotationFile.class,
619 SeqGroupIndexVector.class,
620 jalview.ws.rest.params.SeqIdVector.class,
621 jalview.ws.rest.params.SeqVector.class,
622 jalview.ws.rest.params.Tree.class })
626 jinput = (InputType) (type.getConstructor().newInstance(null));
627 if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix()))
629 ArrayList<String> al = new ArrayList<String>();
630 for (String prprm : separatorListToArray(iprmparams, ","))
634 if (!jinput.configureFromURLtokenString(al, warnings))
637 warnings.append("Failed to parse '" + prms.group(0)
638 + "' as a " + jinput.getURLtokenPrefix()
644 iparams.put(tok, jinput);
649 } catch (Throwable thr)
659 URL u = new URL(url);
661 inputParams = iparams;
662 } catch (Exception e)
664 warnings.append("Failed to parse '" + url + "' as a URL.\n");
671 public static void main(String argv[])
673 if (argv.length == 0)
675 if (!testRsdExchange("Test using default Shmmr service",
676 RestClient.makeShmmrRestClient().service))
678 System.err.println("default test failed.");
682 System.err.println("default test passed.");
688 for (String svc : argv)
690 p += testRsdExchange("Test " + (++i), svc) ? 1 : 0;
692 System.err.println("" + p + " out of " + i + " tests passed.");
697 private static boolean testRsdExchange(String desc, String servicestring)
701 RestServiceDescription newService = new RestServiceDescription(
703 if (!newService.isValid())
705 throw new Error("Failed to create service from '" + servicestring
706 + "'.\n" + newService.getInvalidMessage());
708 return testRsdExchange(desc, newService);
709 } catch (Throwable x)
711 System.err.println("Failed for service (" + desc + "): "
718 private static boolean testRsdExchange(String desc,
719 RestServiceDescription service)
723 String fromservicetostring = service.toString();
724 RestServiceDescription newService = new RestServiceDescription(
725 fromservicetostring);
726 if (!newService.isValid())
728 throw new Error("Failed to create service from '"
729 + fromservicetostring + "'.\n"
730 + newService.getInvalidMessage());
733 if (!service.equals(newService))
735 System.err.println("Failed for service (" + desc + ").");
736 System.err.println("Original service and parsed service differ.");
737 System.err.println("Original: " + fromservicetostring);
738 System.err.println("Parsed : " + newService.toString());
741 } catch (Throwable x)
743 System.err.println("Failed for service (" + desc + "): "
744 + service.toString());
752 * covenience method to generate the id and sequence string vector from a set
753 * of seuqences using each sequence's getName() and getSequenceAsString()
757 * @return String[][] {{sequence ids},{sequence strings}}
759 public static String[][] formStrings(SequenceI[] seqs)
761 String[][] idset = new String[2][seqs.length];
762 for (int i = 0; i < seqs.length; i++)
764 idset[0][i] = seqs[i].getName();
765 idset[1][i] = seqs[i].getSequenceAsString();
771 * can this service be run on the visible portion of an alignment regardless
772 * of hidden boundaries ?
774 boolean hseparable = false;
776 boolean vseparable = false;
778 public boolean isHseparable()
787 public boolean isVseparable()
793 * search the input types for an instance of the given class
795 * @param <validInput.inputType> class1
798 public boolean inputInvolves(Class<?> class1)
800 assert (InputType.class.isAssignableFrom(class1));
801 for (InputType val : inputParams.values())
803 if (class1.isAssignableFrom(val.getClass()))
811 char gapCharacter = '-';
815 * @return the preferred gap character for alignments input/output by this
818 public char getGapCharacter()
823 public String getDecoratedResultUrl(String jobId)
825 // TODO: correctly write ?/& appropriate to result URL format.
826 return jobId + urlSuffix;
829 private List<JvDataType> resultData;
834 * TODO: Extend to optionally specify relative/absolute url where data of this
835 * type can be retrieved from
839 public void addResultDatatype(JvDataType dt)
841 if (resultData == null)
843 resultData = new ArrayList<JvDataType>();
848 public boolean removeRsultDatatype(JvDataType dt)
850 if (resultData != null)
852 return resultData.remove(dt);
857 public List<JvDataType> getResultDataTypes()