Building a Synchronous SADI Service (Advanced)

# Building a Synchronous SADI Service With Perl (Advanced)

This document summarises steps needed in order to develop (and to implement) a synchronous SADI web service using SADISeS (SADI Service Supoort). It should be noted that the service described in this document is more complicated than your average service.

The main thing to understand is that SADISeS does not give you a full implementation of your service. You still need to program the business logic (e.g. to extract data from your database) - but you do not need to worry about the SADI or HTTP protocol details.

At the end of this tutorial, you should have 3 things: a SADI service definition file, a PERL module containing your business logic, and a PERL CGI entry script to your service.

The service definition file is a properties based file that contains the information needed to describe what a SADI service will do. In most cases, this file will be located in the ~/Perl-SADI/definitions directory* and can be manually edited to reflect what your SADI service does.

The SADISeS generated PERL module contains a place for you to insert the business logic of your service. In most cases, this file will be located in the ~/Perl-SADI/services/Service/ directory*.

Finally, the PERL CGI entry script provides the interface with which a user interacts with your SADI service. This script is also generated by SADISeS and is located in the ~/Perl-SADI/cgi directory*.

*For those of you developing your SADI services using MS Windows, the ~ refers to your home directory. On Windows, this is usually C:\Users\Your_Name.

These 3 things are all you need to create a single SADI web service and SADISeS helps you do just that without worrying too much about the SADI protocol.

Let's now move on towards building our SADI service. In step 1 below, we will make sure that you have all of the dependencies in order.

## Table of Contents

Step 1: What is needed
Step 2: Service definition generation
Step 3: Service generation
Step 4: Service implementation
Step 5: Service testing
Step 6: Service deployment
Step 7: Service testing using HTTP
Step 8: Service registration

### Step 1: What is needed

To implement SADI services using SADISeS, you need to have the following installed on your machine:

1. Perl - perl has to be installed on your machine
2. A web server - this document assumes that you are using Apache2
3. Perl SADI - available on cpan

Once you have installed Perl SADI and all of its dependencies on your machine, you will have to run through the SADISeS set up. This is only done once per user of SADISeS (unless you are upgrading Perl-SADISeS). For more information, please view the SADI::SADI documentation.

### Step 2: Service definition generation

Before we can generate any code, we need to tell SADISeS a little bit about our service. This is done via a definitions file.

The service that we are going to implement in this document is one that given 2 or more (x,y) pairs, calculates the slope and intercept of the best fitting line through those pairs.

To generate a definition file for your service, issue the following command at the command prompt:

    $ sadi-generate-services.pl -D CalculateLinearRegression

Basically, this tells SADISeS that we would like to generate a definition file for the service 'CalculateLinearRegression'. The generated file can be found in ~/Perl-SADI/definitions/CalculateLinearRegression.

If you open the generated definitions file, you will see something like the following:

    # leave the following line as is!
    ServiceName = CalculateLinearRegression

    # modify the values below as you see fit.
    ServiceType = http://someontology.org/services/sometype
    InputClass = http://someontology.org/datatypes\#Input1
    OutputClass = http://someontology.org/datatypes\#Output1
    Description = A implementation of the 'CalculateLinearRegression' service
    UniqueIdentifier = urn:lsid:myservices:CalculateLinearRegression
    Authority = authority.for.CalculateLinearRegression
    Authoritative = 1
    Provider = myaddress@organization.org
    ServiceURI = http://localhost/cgi-bin/CalculateLinearRegression
    URL = http://localhost/cgi-bin/CalculateLinearRegression
    SignatureURL = http://localhost/cgi-bin/CalculateLinearRegression

SADISeS has done a nice job in preparing this file for us!

Beware, if you use the characters # or = you will need to escape them with a \.

We will have to edit it slightly, to ensure that we specify our actual inputs/outputs. For now, we will leave the URL as is.

Please specify that the InputClass is http://sadiframework.org/examples/regression.owl#PairedValueCollection and the OutputClass is http://sadiframework.org/examples/regression.owl#OutputClass. Save and close the file!

The edit file is shown below for clarity:

    # leave the following line as is!
    ServiceName = CalculateLinearRegression

    # modify the values below as you see fit.
    ServiceType = http://someontology.org/services/sometype
    InputClass = http://sadiframework.org/examples/regression.owl\#PairedValueCollection
    OutputClass = http://sadiframework.org/examples/regression.owl\#OutputClass
    Description = A implementation of the 'CalculateLinearRegression' service
    UniqueIdentifier = urn:lsid:myservices:CalculateLinearRegression
    Authority = authority.for.CalculateLinearRegression
    Authoritative = 1
    Provider = myaddress@organization.org
    ServiceURI = http://localhost/cgi-bin/CalculateLinearRegression
    URL = http://localhost/cgi-bin/CalculateLinearRegression
    SignatureURL = http://localhost/cgi-bin/CalculateLinearRegression

### Step 3: Service generation

Now that we have a definitions file, the next step in building a SADI service is to generate the actual service code!

To generate our service skeleton, issue the following command at the command prompt:

    $ sadi-generate-services.pl CalculateLinearRegression

SADISeS will then go ahead and generate 2 things for you! An entry script for your service (located in the ~/Perl-SADI/cgi/ directory) and a service implementation file (located in the ~/Perl-SADI/services/Service/ directory).

Your entry script will be called CalculateLinearRegression. The implementation file is called CalculateLinearRegression.pm. Go ahead and look at both files. In the next section, we will be editing CalculateLinearRegression.pm.

### Step 4: Service implementation

Now that we are ready to implement the business logic, we will have to find, open and edit the module CalculateLinearRegression.pm (look in the folder ~/Perl-SADI/services/Service/).

SADISeS automatically created this file for you and left just the subroutine process_it for you to code your implementation. Fortunately, SADISeS provides some sample code for you to see how some operations are done!

One of the very first things that you will see in process_it is the line:

     foreach my $input (@inputs) { ...

Basically, our service is iterating over any and all inputs recieved that are of class InputClass. It is then up to us to use that data in our business logic and output the result as class OutputClass.

The input, $input, is of type RDF::Core::Resource.

Our business logic will be placed after the line:

    # do something with $input ... (sorry, can't help with that)

#### Our business logic:

Reading the inputs:

    # keep the regression URI
    my $rURI = 'http://sadiframework.org/examples/regression.owl#';

    # the x,y pairs (2d array)
    my @xy;

    # extract all x,y pairs from the input data

    # get the elements

    my $statements = $core->getStatements(subject => $input, predicate => $rURI . "element");

    # iterate over the elements
    foreach my $e (@$statements) {

         # get the x value
         my $x = $core->getObjects(subject => $e->getObject, predicate => $rURI . "x");
         $x = "" unless $$x[0];
         $x=  $$x[0]->getValue if ref ($x) eq 'ARRAY' and $$x[0];

         # get the y value
         my $y = $core->getObjects(subject => $e->getObject, predicate => $rURI . "y");
         $y = "" unless $$y[0];
         $y =  $$y[0]->getValue if ref ($y) eq 'ARRAY' and $$y[0];

         # push x,y onto  @xy if we have a value for both x,y
         push @xy, [$x,$y] if $x ne "" and $y ne "";
    }

Performing our calculation on the data:

    # now we calculate the regression!

    # values we will use in the calculation
    my ($sumX, $sumY, $sumXX, $sumXY, $n);

    # calculate sumations
    for my $pair (@xy) {
        $n++;
        $sumX+= $pair->[0];
        $sumY+= $pair->[1];
        $sumXX+= ($pair->[0] * $pair->[0]);
        $sumXY+= ($pair->[0] * $pair->[1]);
    }

    # calculate intercept
    my $intercept = ($sumY*$sumXX) - ($sumX * $sumXY);
    $intercept = $intercept / ( ($n*$sumXX) - ($sumX * $sumX)  );

    # calculate slope
    my $slope = ($n * $sumXY) - ($sumX * $sumY);
    $slope = $slope / ( ($n * $sumXX) - ($sumX * $sumX)  );

Populating our output:

    # create the output resource
    my $regressionModel = new RDF::Core::Resource('regressionModelOutput');

    $core->addOutputData(
            node => $regressionModel,
            value => RDF::Core::Resource->new($rURI . "LinearRegressionModel"),
            predicate =>$regressionModel->new( SADI::RDF::Predicates::RDF->type )
    );

    # add slope to output model
    $core->addOutputData(
            node => $regressionModel,
            value => $slope,
            predicate => $rURI . "slope",
            force_literal => 1
    );

    # add the intercept to output model
    $core->addOutputData(
            node => $regressionModel,
            value => $intercept,
            predicate => $rURI . "intercept",
            force_literal => 1
    );

    # add our regressionModel to output model and type it
    $core->addOutputData(
            node => $input->getURI,
            value => $regressionModel,
            predicate => $rURI . "hasRegressionModel",
            typed_as_output => 1
    );

Notice how we call addOutputData() each time we wish to add something to our output document. Save and close the file. We will test our service in the next section.

### Step 5: Service testing

Now that we have implemented our service, we will test it to make sure that it works. The input that we will be using is shown below:

    <rdf:RDF
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:regress="http://sadiframework.org/examples/regression.owl#">
       <regress:PairedValueCollection
            rdf:about="http://sadiframework.org/examples/input/regression1">
         <regress:element>
           <regress:PairedValue>
             <regress:x rdf:datatype="http://www.w3.org/2001/XMLSchema#int">1</regress:x>
             <regress:y rdf:datatype="http://www.w3.org/2001/XMLSchema#int">2</regress:y>
           </regress:PairedValue>
         </regress:element>
         <regress:element>
           <regress:PairedValue>
              <regress:x rdf:datatype="http://www.w3.org/2001/XMLSchema#int">3</regress:x>
              <regress:y rdf:datatype="http://www.w3.org/2001/XMLSchema#int">5</regress:y>
           </regress:PairedValue>
         </regress:element>
      </regress:PairedValueCollection>
    </rdf:RDF>

Copy and save the input to a file (I will assume that the file is saved as regression-input.xml).

Assuming that you saved the file under the name go-input.xml, our SADI service can be tested with the following command:

    $ sadi-testing-service.pl Service::CalculateLinearRegression regression-input.xml

The expected output of our service:

    <rdf:RDF
    xmlns:a="http://sadiframework.org/examples/regression.owl#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    >
    <rdf:Description rdf:about="http://sadiframework.org/examples/input/regression1">
    <a:hasRegressionModel>
    <rdf:Description rdf:about="regressionModelOutput">
    <rdf:type rdf:resource="http://sadiframework.org/examples/regression.owl#LinearRegressionModel"/>
    <a:slope>1.5</a:slope>
    <a:intercept>0.5</a:intercept>
    </rdf:Description>
    </a:hasRegressionModel>
    <rdf:type rdf:resource="http://sadiframework.org/examples/regression.owl#OutputClass"/>
    </rdf:Description>
    </rdf:RDF>

If you have no errors, then proceed to step 6! If you have some errors, hopefully SADISeS stack trace will help you pinpoint the problem!

### Step 6: Service deployment

Deploying our SADI service is extremely straight forward!

The only thing you need to do is to tell your Web Server where the cgi script that we generated is located.

If you recall, our services' cgi script was called CalculateLinearRegression (one of the files generated using sadi-generate-services.pl)

Make a symbolic link from the cgi-bin directory of your Web Server (e.g on some Linux distributions, using Apache Web server, the cgi-bin directory is /usr/lib/cgi-bin) to the cgi-bin script.

For example:

    cd /usr/lib/cgi-bin
    sudo ln -s /home/ekawas/Perl-SADI/cgi/CalculateLinearRegression .

Every time that you generate a cgi service using Perl SADI, you will have to perform an operation similar to this one for the service that you created in order to deploy it.

### Step 7: Service testing using HTTP

Now that the service has been deployed, you can test it using HTTP. We will be using the same input file that we used to locally test our service.

    <rdf:RDF
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:regress="http://sadiframework.org/examples/regression.owl#">
       <regress:PairedValueCollection
            rdf:about="http://sadiframework.org/examples/input/regression1">
         <regress:element>
           <regress:PairedValue>
             <regress:x rdf:datatype="http://www.w3.org/2001/XMLSchema#int">1</regress:x>
             <regress:y rdf:datatype="http://www.w3.org/2001/XMLSchema#int">2</regress:y>
           </regress:PairedValue>
         </regress:element>
         <regress:element>
           <regress:PairedValue>
              <regress:x rdf:datatype="http://www.w3.org/2001/XMLSchema#int">3</regress:x>
              <regress:y rdf:datatype="http://www.w3.org/2001/XMLSchema#int">5</regress:y>
           </regress:PairedValue>
         </regress:element>
      </regress:PairedValueCollection>
    </rdf:RDF>

Assuming that you saved the file under the name regression-input.xml, our SADI service can be tested with the following command:

    $ sadi-testing-service.pl -e http://localhost/cgi-bin/CalculateLinearRegression regression-input.xml

Of course, you may need to modify the URL http://localhost/cgi-bin/CalculateLinearRegression (to the actual address that you deployed the service to!).

When we call the script with the -e option, we tell the sadi-testing-service.pl script that we would like to call our service using HTTP Post. We then must provide the script with 1 (or an optional second) parameter:

1. the url to the service
2. an optional file containing the input to our service

The expected output should be very similar to the output we saw above when we tested our service locally:

    HTTP/1.1 200 OK
    Connection: close
    Date: Thu, 17 Sep 2009 14:05:04 GMT
    Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.3 with Suhosin-Patch
    Vary: Accept-Encoding
    Content-Type: text/xml; charset=ISO-8859-1
    Client-Date: Thu, 17 Sep 2009 14:05:07 GMT
    Client-Peer: 127.0.0.1:80
    Client-Response-Num: 1
    Client-Transfer-Encoding: chunked

    <rdf:RDF
    xmlns:a="http://sadiframework.org/examples/regression.owl#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    >
    <rdf:Description rdf:about="http://sadiframework.org/examples/input/regression1">
    <a:hasRegressionModel>
    <rdf:Description rdf:about="regressionModelOutput">
    <rdf:type rdf:resource="http://sadiframework.org/examples/regression.owl#LinearRegressionModel"/>
    <a:slope>1.5</a:slope>
    <a:intercept>0.5</a:intercept>
    </rdf:Description>
    </a:hasRegressionModel>
    <rdf:type rdf:resource="http://sadiframework.org/examples/regression.owl#OutputClass"/>
    </rdf:Description>
    </rdf:RDF>

To see what else the service testing script can do, run it without parameters or with the -h parameter.

### Step 8: Service registration

Before we can register our service, we will need to open up the service definition file. Once this file is open, we need to verify a few things first!

First of all, we need to ensure that our URL/SignatureURL both point to the remote HTTP address of our entry script CalculateLinearRegression (during testing, this was http://localhost/cgi-bin/CalculateLinearRegression).

Second of all, we ... actually, there is no second of all! We just need to make sure that if we enter the remote HTTP address of our entry script CalculateLinearRegression in our web browser, we will see some XML (the SADI service signature) outputted.

To actually register our service, we need to open our browser to http://sadiframework.org/registry/ and enter our service URL into the textbox. Once we have done that, sadiframework.org will add our service to the list of services that it knows about!

That's all there is to constructing synchronous Perl SADI services!