NAME
MOBY::CommonSubs.pm - a set of exportable subroutines that are useful in clients and services to deal with the input/output from MOBY Services
DESCRIPTION
CommonSubs are used to do various manipulations of MOBY service-invocation and response messages. It is useful both Client and Service side and will ensure that the message structure is valid as per the latest version of the MOBY API.
It DOES NOT connect to MOBY Central for any of its functions, though it does contact the ontology server for some of its functions, so it may require a network connection.
NOTE THAT MOST LEGACY SUBROUTINES IN THIS MODULE HAVE BEEN DEPRECATED The code is still here for legacy reasons only, but the documentation has been removed permanently, so if there is a routine that you use that is no longer documented, consider yourself on dangerous ground. THIS CODE WILL NOT FUNCTION WITH SERVICES THAT ARE NOT COMPLIANT WITH THE NEW API - FOR EXAMPLE SERVICES THAT ARE NOT USING NAMED INPUTS AND OUTPUTS!
COMMON USAGE EXAMPLES
Client Side Paradigm
The following is a generalized architecture for all BioMOBY services showing how to parse response messages using the subroutines provided in CommonSubs
Services Returning Simples
my $resp = $SI->execute(XMLInputList => \@input_data);
my $responses = serviceResponseParser($resp); # returns MOBY objects
foreach my $queryID(keys %$responses){
$this_invocation = $responses->{$queryID}; # this is the <mobyData> block with this queryID
my $this_output = "";
if (my $data = $this_invocation->{'responseArticleName'}){ # whatever your articleName is...
# $data is a MOBY::Client::Simple|Collection|ParameterArticle
my ($namespace) = @{$data->namespaces};
my $id = $data->id;
my $XML_LibXML = $data->XML_DOM; # get access to the DOM
# assuming that you have an element of type "String"
# with articleName "Description"
my $desc = getNodeContentWithArticle($XML_LibXML, "String", "Description");
###################
# DO SOMETHING TO RESPOSE DATA HERE
###################
}
}
Services Returning Collections
my $resp = $SI->execute(XMLInputList => \@input_data);
my $responses = serviceResponseParser($resp); # returns MOBY objects
foreach my $queryID(keys %$responses){ # $inputs is a hashref of $input{queryid}->{articlename} = input object
my $this_invocation = $responses->{$queryID};
if (my $data = $this_invocation->{'responseArticleName'}){ # $input is a MOBY::Client::Simple|Collection|Parameter object
my $simples = $data->Simples;
foreach my $simple(@$simples){
my ($ns) = @{$simple->namespaces};
my $id = $simple->id;
my $XML_LibXML = $input->XML_DOM; # get access to the DOM
}
}
}
Service-Side Paradigm
The following is a generalized architecture for *all* BioMOBY services showing how to parse incoming messages using the subroutines provided in CommonSubs
Services Generating simple outputs
sub _generic_service_name { my ($caller, $data) = @_;
my $MOBY_RESPONSE;
my $inputs = serviceInputParser($data); # returns MOBY objects
return SOAP::Data->type('base64' => responseHeader("illuminae.com") . responseFooter()) unless (keys %$inputs);
foreach my $queryID(keys %$inputs){
$this_invocation = $inputs->{$queryID}; # this is the <mobyData> block with this queryID
my $this_output = "";
if (my $input = $this_invocation->{incomingRequest}){
my ($namespace) = @{$input->namespaces};
my $id = $input->id;
my $XML_LibXML = $input->XML_DOM; # get access to the DOM
###################
# DO YOUR BUSINESS LOGIC HERE
###################
$this_output = "<moby:Object... rest of the output XML .../>";
}
$MOBY_RESPONSE .= simpleResponse(
$this_output
, "myArticleName" # the article name of that output object
, $queryID); # the queryID of the input that we are responding to
}
return SOAP::Data->type('base64' => (responseHeader("illuminae.com") . $MOBY_RESPONSE . responseFooter));
}
Services generating collection outputs
sub _generic_service_returning_collections { my($caller, $message) = @_; my $inputs = serviceInputParser($message); my $MOBY_RESPONSE = ""; # set empty response
# return empty SOAP envelope if ther is no moby input
return SOAP::Data->type('base64' => responseHeader().responseFooter()) unless (keys %$inputs);
foreach my $queryID(keys %$inputs){ # $inputs is a hashref of $input{queryid}->{articlename} = input object
my $this_invocation = $inputs->{$queryID};
my @outputs;
if (my $input = $this_invocation->{incomingArticleName}){ # $input is a MOBY::Client::Simple|Collection|Parameter object
my $id = $input->id;
my @agis = &_getMyOutputList($id); # this subroutine contains your business logic and returns a list of ids
foreach (@agis){
push @outputs, "<Object namespace='MyNamespace' id='$_'/>";
}
}
$MOBY_RESPONSE .= collectionResponse (\@outputs, "myOutputArticleName", $queryID);
}
return SOAP::Data->type('base64' => (responseHeader("my.authority.org") . $MOBY_RESPONSE . responseFooter));
}
EXAMPLE SERVICE CODE
A COMPLETE EXAMPLE OF AN EASY MOBY SERVICE
This is a service that:
CONSUMES: base Object in the GO namespace
EXECUTES: Retrieval
PRODUCES: GO_Term (in the GO namespace)
# this subroutine is called from your dispatch_with line
# in your SOAP daemon
sub getGoTerm {
use MOBY::CommonSubs qw{:all};
my ($caller, $incoming_message) = @_;
my $MOBY_RESPONSE; # holds the response raw XML
my @validNS = validateNamespaces("GO"); # do this if you intend to be namespace aware!
my $dbh = _connectToGoDatabase(); # connect to some database
return SOAP::Data->type('base64' => responseHeader('my.authURI.com') . responseFooter()) unless $dbh;
my $sth = $dbh->prepare(q{ # prepare your query
select name, term_definition
from term, term_definition
where term.id = term_definition.term_id
and acc=?});
my $inputs= serviceInputParser($incoming_message); # get incoming invocations
# or fail properly with an empty response if there is no input
return SOAP::Data->type('base64' => responseHeader("my.authURI.com") . responseFooter()) unless (keys %$inputs);
foreach my $queryID(keys %$inputs){
my $this_invocation = $inputs->{$queryID}; # this is the <mobyData> block with this queryID
my $invocation_output=""; # prepare a variable to hold the output XML from this invocation
if (my $input = $this_invocation->{GO_id}){ # the articleName of your services input
my ($namespace) = @{$input->namespaces}; # this is returned as a list!
my $id = $input->id;
# optional - if we want to ENSURE that the incoming ID is in the GO namespace
# we can validate it using the validateThisNamespace routine of CommonSubs
# @validNS comes from validateNamespaces routine of CommonSubs (called above)
next unless validateThisNamespace($namespace, @validNS);
# here's our business logic...
$sth->execute($id);
my ($term, $def) = $sth->fetchrow_array;
if ($term){
$invocation_output =
"<moby:GO_Term namespace='GO' id='$id'>
<moby:String namespace='' id='' articleName='Term'>$term</moby:String>
<moby:String namespace='' id='' articleName='Definition'>$def</moby:String>
</moby:GO_Term>";
}
}
# was our service execution successful?
# if so, then build an output message
# if not, build an empty output message
$MOBY_RESPONSE .= simpleResponse( # simpleResponse is exported from CommonSubs
$invocation_output # response for this query
, "A_GoTerm" # the article name of that output object
, $queryID); # the queryID of the input that we are responding to
}
# now return the result
return SOAP::Data->type('base64' => (responseHeader("illuminae.com") . $MOBY_RESPONSE . responseFooter));
}
PARSING MOBY INPUT AND OUTPUT
serviceInputParser and serviceResponseParser
function: These routines will take a Moby invocation (server side usage) or response (client-side usage) and extract the Simple/Collection/Parameter objects out of it as MOBY::Client::SimpleArticle, MOBY::Client::CollectionArticle, and/or MOBY::Client::SecondaryArticle objects. The inputs are broken up into individual queryID's. Each queryID is associated with one or more individual articles, and each article is available by its articleName.
usage: my $inputs = serviceInputParser($MOBY_invocation_mssage));
usage: my $outputs = serviceResponseParser($MOBY_response_message));
args: $message
- this is the SOAP payload; i.e. the XML document containing the MOBY message
returns: $inputs
or $outputs
are a hashref with the following structure:
$Xputs->{$queryID}->{articleName} =
MOBY::Client::SimpleArticle |
MOBY::Client::CollectionArticle |
MOBY::Client::SecondaryArticle
the SimpleArticle and CollectionArticle have methods to provide you with their objectType, their namespace and their ID. If you want to get more out of them, you should retrieve the XML::LibXML DOM using the ->XML_DOM method call in either of those objects. This can be passed into other CommonSubs routines such as getNodeContentWithArticle in order to retrieve sub-components of the Moby object you have in-hand.
See also:
Simples
For example, the input message:
<mobyData queryID = 'a1a'>
<Simple articleName='name1'>
<Object namespace=blah id=blah/>
</Simple>
<Parameter articleName='cutoff'>
<Value>10</Value>
</Parameter>
</mobyData>
will become:
$inputs->{a1a}->{name1} = $MOBY::Client::Simple, # the <Simple> block
$inputs->{a1a}->{cutoff} = $MOBY::Client::Secondary # <Parameter> block
Collections
With inputs that have collections these are presented as a listref of Simple article DOM's. So for the following message:
<mobyData queryID = '2b2'>
<Collection articleName='name1'>
<Simple>
<Object namespace=blah id=blah/>
</Simple>
<Simple>
<Object namespace=blah id=blah/>
</Simple>
</Collection>
<Parameter articleName='cutoff'>
<Value>10</Value>
</Parameter>
</mobyData>
will become
$inputs->{2b2}->{name1} = $MOBY::Client::Collection, # the <Collection> Block
$inputs->{2b2}->{cutoff} = $MOBY::Client::Secondary, # the <Parameter> Block
MESSAGE COMPONENT IDENTITY AND VALIDATION
This section describes functionality associated with identifying parts of a message, and checking that it is valid.
isSimpleArticle, isCollectionArticle, isSecondaryArticle
function: tests XML (text) or an XML DOM node to see if it represents a Simple, Collection, or Secondary article
These routines are unlikely to be useful in a MOBY Object oriented service but they have been retained for legacy reasons.
usage:
if (isSimpleArticle($node)){do something to it}
or
if (isCollectionArticle($node)){do something to it}
or
if (isSecondaryArticle($node)){do something to it}
input : an XML::LibXML node, an XML::LibXML::Document or straight XML
returns: boolean
validateNamespaces
function: checks the namespace ontology for the namespace lsid
usage: @LSIDs = validateNamespaces(@namespaces)
args: ordered list of either human-readable or lsid presumptive namespaces
returns: ordered list of the LSID's corresponding to those presumptive namespaces; undef for each namespace that was invalid
validateThisNamespace
function: checks a given namespace against a list of valid namespaces
usage: $valid = validateThisNamespace($ns, @validNS);
args: ordered list of the namespace of interest and the list of valid NS's
returns: boolean
CONSTRUCTING OUTPUT
This section describes how to construct output, in response to an incoming message. Responses come in three varieties:
Simple - Only simple article(s)
Collection - Only collection(s) of simples
Complex - Any combination of simple and/or collection and/or secondary articles.
simpleResponse
function: wraps a simple article in the appropriate (mobyData) structure. Works only for simple articles. If you need to mix simples with collections and/or secondaries use complexReponse instead.
usage: $responseBody = simpleResponse($object, $ArticleName, $queryID);
args: (in order) $object
- (optional) a MOBY Object as raw XML. $articleName
- (optional) an article name for this article. $queryID
- (optional, but strongly recommended) the query ID value for the mobyData block to which you are responding.
notes: As required by the API you must return a response for every input. If one of the inputs was invalid, you return a valid (empty) MOBY response by calling simpleResponse(undef, undef, $queryID) with no arguments.
collectionResponse
function: wraps a set of articles in the appropriate mobyData structure. Works only for collection articles. If you need to mix collections with simples and/or secondaries use complexReponse instead.
usage: $responseBody = collectionResponse(\@objects, $articleName, $queryID);
args: (in order) \@objects
- (optional) a listref of MOBY Objects as raw XML. $articleName
- (optional) an artice name for this article. $queryID
- (optional, but strongly recommended) the ID of the query to which you are responding.
notes: as required by the API you must return a response for every input. If one of the inputs was invalid, you return a valid (empty) MOBY response by calling collectionResponse(undef, undef, $queryID).
complexResponse
function: wraps articles in the appropriate (mobyData) structure. Can be used to send any combination of the three BioMOBY article types - simple, collection and secondary - back to a client.
usage: $responseBody = complexResponse(\@articles, $queryID);
args: (in order)
\@articles
- (optional) a listref of arrays. Each element of @articles is itself a listref of [$articleName, $article], where $article is either the article's raw XML for simples and secondaries or a reference to an array containing [$articleName, $simpleXML] elements for a collection of simples.
$queryID
- the queryID value for the mobyData block to which you are responding
notes: as required by the API you must return a response for every input. If one of the inputs was invalid, you return a valid (empty) MOBY response by calling complexResponse(undef, $queryID) with no arguments.
responseHeader
function: print the XML string of a MOBY response header +/- serviceNotes +/- Exceptions
usage:
responseHeader('illuminae.com')
responseHeader(
-authority => 'illuminae.com',
-note => 'here is some data from the service provider'
-exception=>'an xml encoded exception string')
args: a string representing the service providers authority URI, OR a set of named arguments with the authority and the service provision notes which can include already xml encoded exceptions
caveat :
notes: returns everything required up to the response articles themselves. i.e. something like:
<?xml version='1.0' encoding='UTF-8'?>
<moby:MOBY xmlns:moby='http://www.biomoby.org/moby'>
<moby:Response moby:authority='http://www.illuminae.com'>
encodeException
function: wraps a Biomoby Exception with all its parameters into the appropiate MobyData structure
usage:
encodeException(
-refElement => 'refers to the queryID of the offending input mobyData',
-refQueryID => 'refers to the articleName of the offending input Simple or Collection'
-severity=>'error'
-exceptionCode=>'An error code '
-exceptionMessage=>'a human readable description for the error code')
args:the different arguments required by the mobyException API severity can be either error, warning or information valid error codes are decribed on the biomoby website
notes: returns everything required to use for the responseHeader:
<moby:mobyException moby:refElement='input1' moby:refQueryID='1' moby:severity =''>
<moby:exceptionCode>600</moby:exceptionCode>
<moby:exceptionMessage>Unable to execute the service</moby:exceptionMessage>
</moby:mobyException>
responseFooter
function: print the XML string of a MOBY response footer
usage:
return responseHeader('illuminae.com') . $DATA . responseFooter;
notes: returns everything required after the response articles themselves i.e. something like:
</moby:Response>
</moby:MOBY>
ANCILIARY ELEMENTS
This section contains subroutines that handle processing of optional message elements containing meta-data. Examples are the ServiceNotes, and CrossReference blocks.
getServiceNotes
function: to get the content of the Service Notes block of the MOBY message
usage: getServiceNotes($message)
args: $message
is either the XML::LibXML of the MOBY message, or plain XML
returns: String content of the ServiceNotes block of the MOBY Message
getExceptions
function: to get the content of the Exception part of the Service Notes block of the MOBY message
usage: getExceptions($message)
args: $message
is either the XML::LibXML of the MOBY message, or plain XML
returns: an Array of Hashes containing the exception Elemtents and attributes
example: my @ex=getExceptions($XML); foreach my $exception (@ex) { print "Reference:",$exception->{refElement},"\n"; print "Query ID:",$exception->{refQueryID},"\n"; print "Severity of message:",$exception->{severity},"\n"; print "Readable message:",$exception->{exceptionMessage},"\n"; print "Exception Code:",$exception->{exceptionCode},"\n"; }
getCrossReferences
function: to get the cross-references for a Simple article. This is primarily a Client-side function, since service providers should not, usually, be interpreting Cross-references.
usage: @xrefs = getCrossReferences($XML)
args: $XML
is either a SIMPLE article (<Simple>...</Simple>) or an object (the payload of a Simple article), and may be either raw XML or an XML::LibXML node.
returns: an array of MOBY::CrossReference objects
example:
my (($colls, $simps) = getResponseArticles($query); # returns DOM nodes
foreach (@{$simps}){
my @xrefs = getCrossReferences($_);
foreach my $xref(@xrefs){
print "Cross-ref type: ",$xref->type,"\n";
print "namespace: ",$xref->namespace,"\n";
print "id: ",$xref->id,"\n";
if ($xref->type eq "Xref"){
print "Cross-ref relationship: ", $xref->xref_type,"\n";
}
}
}
MISCELLANEOUS FUNCTIONS
This section contains routines that didn't quite seem to fit anywhere else.
getNodeContentWithArticle
function: give me a DOM, a TagName, an articleName and I will return you the content of that node **as a string** (beware if there are additional XML tags in there!) this is meant for MOBYesque PRIMITIVES - things like: <String articleName="SequenceString">TAGCTGATCGAGCTGATGCTGA </String> call _getNodeContentsWithAttribute($DOM_NODE, "String", "SequenceString") and I will return "TACGATGCTAGCTAGCGATCGG" Caveat Emptor - I will NOT chop off leading and trailing whitespace or carriage returns, as these might be meaningful!
usage: @content = getNodeContentWithArticle($XML_DOM, $elementname, $articleName)
args: $XML_DOM is the DOM of a MOBY Object $elementName is the Tag of an XML element $articleName is the articleName attribute of the desired XML element
returns: an array of strings representing every line of every match (you probably want to concatenate these with a "join"... probably...
example: given <SomeObject namespace='' id=''> <String namespace='' id='' articleName="SequenceString">TAGCTGATCGAGCTGATGCTGA </String> </SomeObject>
my $seq = getNodeContentWithArticle($DOM, "String", "SequenceString");
print "yeah!" if $seq eq "TAGCTGATCGAGCTGATGCTGA";
whichDeepestParentObject
function: select the parent node from nodeList that is closest to the querynode
usage:
($term, $lsid) = whichDeepestParentObject($CENTRAL, $queryTerm, \@termList)
args:
$CENTRAL
- your MOBY::Client::Central object
$queryTerm
- the object type I am interested in
\@termlist
- the list of object types that I know about
returns: an ontology term and LSID as a scalar, or undef if there is no parent of this node in the nodelist. note that it will only return the term if you give it term names in the @termList. If you give it LSID's in the termList, then both the parameters returned will be LSID's - it doesn't back-translate...)
_rearrange (stolen from BioPerl ;-) )
usage: $object->_rearrange( array_ref, list_of_arguments)
Purpose : Rearranges named parameters to requested order.
Example: $self->_rearrange([qw(SEQUENCE ID DESC)],@param); Where @param = (-sequence =
$s, -desc => $d, -id => $i);>
returns: @params
- an array of parameters in the requested order.
The above example would return ($s, $i, $d). Unspecified parameters will return undef. For example, if @param = (-sequence =
$s);> the above _rearrange call would return ($s, undef, undef)
Argument: $order
: a reference to an array which describes the desired order of the named parameters.
@param :
an array of parameters, either as a list (in which case the function simply returns the list), or as an associative array with hyphenated tags (in which case the function sorts the values according to @{$order} and returns that new array.) The tags can be upper, lower, or mixed case but they must start with a hyphen (at least the first one should be hyphenated.)
Source: This function was taken from CGI.pm, written by Dr. Lincoln Stein, and adapted for use in Bio::Seq by Richard Resnick and then adapted for use in Bio::Root::Object.pm by Steve Chervitz, then migrated into Bio::Root::RootI.pm by Ewan Birney.
Comments: Uppercase tags are the norm, (SAC) This method may not be appropriate for method calls that are within in an inner loop if efficiency is a concern.
Parameters can be specified using any of these formats: @param = (-name=>'me', -color=>'blue'); @param = (-NAME=>'me', -COLOR=>'blue'); @param = (-Name=>'me', -Color=>'blue'); @param = ('me', 'blue');
A leading hyphenated argument is used by this function to indicate that named parameters are being used. Therefore, the ('me', 'blue') list will be returned as-is.
Note that Perl will confuse unquoted, hyphenated tags as function calls if there is a function of the same name in the current namespace: -name =
'foo'> is interpreted as -&name =
'foo'>
For ultimate safety, put single quotes around the tag: ('-name'=
'me', '-color' =>'blue');>
This can be a bit cumbersome and I find not as readable as using all uppercase, which is also fairly safe:(-NAME=
'me', -COLOR =>'blue');>
Personal note (SAC): I have found all uppercase tags to be more managable: it involves less single-quoting, the key names stand out better, and there are no method naming conflicts. The drawbacks are that it's not as easy to type as lowercase, and lots of uppercase can be hard to read. Regardless of the style, it greatly helps to line the parameters up vertically for long/complex lists.
DEPRECATED FUNCTIONS
processResponse
DEPRECATED
genericServiceInputParser
DEPRECATED
complexServiceInputParser
DEPRECATED
getArticles
DEPRECATED
getSimpleArticleIDs
DEPRECATED
getSimpleArticleNamespaceURI
DEPRECATED
getInputs
DEPRECATED
getInputID
DEPRECATED
getArticlesAsObjects
DEPRECATED
getCollectedSimples
DEPRECATED
getInputArticles
DEPRECATED
extractRawContent
DEPRECATED
getResponseArticles (a.k.a. extractResponseArticles)
DEPRECATED
AUTHORS
Mark Wilkinson (markw at illuminae dot com), Pieter Neerincx, Frank Gibbons
BioMOBY Project: http://www.biomoby.org