OME::Remote::Prototypes - specifies which API methods are visible via the Remote Framework
package OME::MyPackage;
use OME::Remote::Prototypes;
addPrototype("OME::MyPackage","foo",[],['$']);
sub foo {
my ($self) = @_;
return 5;
}
Contains methods for describing and checking the prototypes of the method calls visible via the Remote Framework. When writing a new package, object methods can be published by calling the addPrototype procedure. The module writer must specify the prototype of the function, since most RPC protocols to do not support the same flexibility in subroutine calls that Perl has.
Each published method must have a prototype declared for both its input parameters and its results. Because information about the type of values cannot easily be passed across the RPC channel, all of the typing information must be fully specified by the prototypes. Specifically, the Remote Framework must know which parameters on the input channel are object references, so that the Dispatcher can demarshall them into the Perl objects they represent.
A single prototype (for either the input or output) is fairly straightforward: it is an anonymous array of strings. Each string in the array specifies the type of one parameter. The possible values for each each string are as follows:
Any scalar value
Any binary scalar value
An array (treated in Perl as an array reference). Can be abritrarily complex, but should not contain any object references.
A hash, struct, or the equivalent (treated in Perl as a hash reference). Can be arbitrarily complex, but should not contain any object references.
In the RPC channel, this value must be an object reference, which can only be obtained from the output of a previous method. The Dispatcher will ensure that the object is an instance of a subclass of the specified class, and throw an error otherwise. (If you want to accept any object at all, you can always use the "UNIVERSAL" class, which all Perl classes are descendants of.)
Signifies a list; all remaining parameters are checked against the previous entry in the prototype. This entry, if it exists, should be last in the prototype, since anything after it will be ignored. Further, it cannot be the first element of a prototype. If it is, the Remote Framework will throw an error when the prototype is registered.
For input prototypes, it is important to know that the published method will be called in Perl using object-oriented syntax. As such, there will always be an implied first parameter (usually called $self), which should not be specified in the prototype.
Note that these prototypes are not nearly as complicated as what a Perl method can actually accept as input or generate as output. There are some methods which cannot be published without providing a wrapper method with a simpler interface. This wrapper method can then be published. There are no cases of complex methods like this in the OME API.
Below are some hypothetical subroutines and their corresponding input and output prototypes.
sub add {
# Takes two numbers and adds them together.
my ($self,$addend1,$addend2) = @_;
return $addend1+$addend2;
}
Input: ['$','$']
Output: ['$']
sub addList {
my $self = shift;
# Takes in a list of numbers and adds them together.
my $sum = 0;
$sum += $_ foreach @_;
return $sum;
}
Input: ['$','*']
Output: ['$']
sub OME::Project::addDatasetToProject {
# This method doesn't really exist; don't try to call it.
my ($self,$dataset) = @_;
$self->Session()->Factory()->
maybeNewObject("OME::Project::DatasetMap",
{
project_id => $self->id(),
dataset_id => $dataset->id()
});
return;
}
Input: ['OME::Dataset']
Output: []
sub OME::Factory::findObjects {
my ($self,$className,@criteria) = @_;
# You can find the code for this in OME::Factory.
return @listOfObjects;
}
Input: ['$','$','*'] (technically, ['$','*'] would also work)
Output: ['OME::DBObject','*']
Perl method calls also support the notion of context, which allows a single subroutine to generate different results depending on how its called. (See Context in the perldata manpage.) No currently RPC implementations support the notion of context, so the prototype of a method must specify which context to use explicitly, or the Dispatcher must guess. It does this based on the length of the output prototype; if the prototype is empty ([]), the method is called in void context. If it has one element, it is called in scalar context. Otherwise, it is called in list context.
Note that almost all RPC protocols require a method to return exactly one return value. To support Perl methods that return lists, the Dispatcher will automatically wrap the results of a list-context function into an array, and return that array as its single return value.
Many Perl methods are overloaded, either by parameter list or by context. Currently, the Remote Framework will only support one set of prototypes (including a single context specification, if any) for any published method. To support overloading, the Remote Framework allows methods to be published under names which are different than the Perl subroutine implementing them.
The following methods are available for handling prototypes:
addPrototype($className,$methodName,
$inputPrototype,$outputPrototype,
[ context => $context, ]
[ publishedName => $publishedName ],
[ force => 1 ]);
Publishes the $className::$methodName method via the Remote Framework. The prototypes of the input parameters and results are specified by the $inputPrototype and $outputPrototype parameters, and must be of the format described above. The context, publishedName, and force parameters are optional. The context option forces the Dispatcher to call the method in a particular context ('void', 'scalar', or 'list'). The publishedName option makes the Dispatcher publish the method under a different name. If the force option is set to 1, addPrototype will silently replace any existing prototype.
my $prototype = findPrototype($className,$methodName);
Returns a prototype corresponding to the given $className and $methodName. Note that $methodName in this case refers to the published method name if it differs from the Perl method name. The prototype returned is a hash with the following format:
{
input => the input prototype,
output => the output prototype,
method => the Perl method name,
context => the context
}
The values should not be modified. The hash can be passed into other routines in this package which require a prototype.
This method will follow the inheritance tree to find a prototype for the method specified. If the specified class does not publish the given method, each of its ancestors will be checked in order. If after checking the entire ancestry tree no prototype is found, findPrototype will return undef.
verifyInputPrototype($prototype,$params,$subroutine,[@subParams]);
Verifies that a list of input parameters matches $prototype, which should be a value returned from the findPrototype function. Uses $subroutine to translate object references into Perl objects. If a parameter is supposed to be an object, $subroutine will be called as follows:
my ($object, $replacement) = $subroutine->($parameter,@subParams);
This routine should return the object that $parameter represents, and the value that should be placed in the parameter list in place of the original parameter. (The second result can be undefined, signifying that the parameter list should not be modified.) The $object result must be a descendant of the class specified in the input prototype for the parameter list to match.
verifyOutputPrototype($prototype,$params,$subroutine,[@subParams]);
Similar to verifyInputPrototype, but checks a result list against the output prototype. Uses $subroutine to translate Perl objects into object references. If a parameter is supposed to be an object, $subroutine will be called as follows:
my ($object, $replacement) = $subroutine->($result,@subParams);
In this case, the routine should return $result as the $object output, since it's already a Perl object. The $replacement output should be the object reference to place in the result list. The $object output will be checked to make sure it is a descendant of the class specified in the output prototype.
Douglas Creager (dcreager@alum.mit.edu)