At the end of the second part of this series on creating the repository, we stopped at providing a means to upload software to the repository using the HTTP Post method and SOAP. In this, the third of a three part series on building a software repository, we will show how to extend the software repository to broadcast its presence using WSDL, and then provide an example program that brings all of the pieces together.
Before you go on with this article, you should know a few things about the protocols and the latest changes to the software. First, you should be familiar with the workings of the Web Services Description Language (WSDL) (see Resources for articles that explain how WSDL works).
The examples in this column require Python 1.5.2 or later (though Python 2.1 is recommended), 4Suite version 0.11.1 or later, 4Suite Server version 0.11.1 or later, and WSDL lib. The Resources section of this column has all the necessary information about downloading and installing all of the prerequisites.
A new feature in 4Suite Server 0.11.1 is a set of extensions to Python's
distutils
package that let you bootstrap a 4Suite Server installation directly from
a product's setup.py file. The setup.py file that is
downloadable from this article contains examples of how to do this. The end
result is that to install and bootstrap our example application, all you
need to do is run setup.py.
WSDL is an XML format used to describe the technical characteristics of a Web service. It is used in conjunction with UDDI (Universal Description, Discovery, and Integration) to let Web services announce their presence and capabilities. Using these technologies, it is possible for an application to query a UDDI repository, find information about a desired Web service, and make a request to that Web service.
Since UDDI by itself would take an entire series of articles (keep an eye on this column for just that, though), we will focus only on the WSDL aspect. To add WSDL to our software repository we will create a simple document definition that will be used make simple statements about the presence of WSDL documents. Listing 1 shows the WSDL document definition.
<DocDef xmlns='http://namespaces.4suite.org/4ss' name='WSDL'>
<RdfMapping>
<Subject>$uri</Subject>
<Predicate>'http://schema.4suite.org#wsdl'</Predicate>
<Object>/wsdl:definitions/@name</Object>
</RdfMapping>
<NsMapping>
<Prefix>wsdl</Prefix>
<Uri>http://schemas.xmlsoap.org/wsdl/</Uri>
</NsMapping>
</DocDef>
|
This document definition will add a statement to the Resource Description Framework (RDF) model for every WSDL document added to the repository. The statement will be used by our WSDL POST handler to keep track of all of the WSDL documents in the system.
We spent a fair amount of time in the previous installment of this series (see Resources) explaining how to use the 4Suite Server POST handler to add new content to the system. We will build on this knowledge to write a set of WSDL registration pages that will let you add, edit, and delete WSDL descriptions.
With the code installed, bring up the 4Suite Server HTTP server so we can walk through the example application together. Listing 2 shows the RDF descriptions you will need to make sure are in your 4Suite Server configuration file.
<rdf:Description ID='SoftRepoSoapHandler'> <rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/> <Priority>30</Priority> <Module>WebServices4.SoftRepoSoapHandler</Module> </rdf:Description> <rdf:Description ID='SoftRepoWsdlHandler'> <rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/> <Module>WebServices4.WsdlHandler</Module> </rdf:Description> <rdf:Description ID='SoftRepoGetHandler'> <rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/> <Module>FtServer.Protocols.Http.GetHandler</Module> <DocumentRoot>/WebServices-4</DocumentRoot> </rdf:Description> <rdf:Description ID='SoftRepoPostHandler'> <rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/> <Module>FtServer.Protocols.Http.PostHandler</Module> <DocumentRoot>/WebServices-4</DocumentRoot> </rdf:Description> <rdf:Description ID='SoftRepoDeleteHandler'> <rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/> <Module>FtServer.Protocols.Http.DeleteHandler</Module> <DocumentRoot>/WebServices-4</DocumentRoot> </rdf:Description> <rdf:Description ID='PythonSoftRepoServer'> <rdf:type resource='http://xmlns.4Suite.org/4ss/properties#PythonServer'/> <StartMode>MANUAL</StartMode> <Port>8080</Port> <PidFile>/tmp/PyHttp.pid</PidFile> <ErrorLog>/tmp/PyHttp.all</ErrorLog> <TransferLog>/tmp/PyHttp.all</TransferLog> <Handler resource='#SoftRepoGetHandler'/> <Handler resource='#SoftRepoWsdlHandler'/> <Handler resource='#SoftRepoPostHandler'/> <Handler resource='#SoftRepoDeleteHandler'/> <Handler resource='#SoftRepoSoapHandler'/> </rdf:Description> |
Then, bring up the server with the following command line:
[molson@penny molson]$ 4ss_manager start PythonSoftRepoServer PythonSoftRepoServer started (pid 6045) |
With 4Suite Server configured and running, you should be able to point your browser to http://localhost:8080/index.html to see the index page of our WSDL manager. From the index page, shown in Figure 1, we can either view existing WSDL descriptions or add a new WSDL description to the system.
Figure 1: WSDL Manager index page

Since there are no WSDL descriptions in the system initially, we will
add one. Follow the add new WSDL description link. A new page will ask that a new WSDL description be entered. Listing
3 (also in the example file data/softrepo.wsdl) shows a WSDL description example for our software repository.
<?xml version="1.0"?>
<definitions name="Software Repository"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://4Suite.org/webservices-4.wsdl"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<message name="AddSoftwareInput">
<part name="title"/>
<part name="creator"/>
<part name="home"/>
<part name="version"/>
<part name="description"/>
</message>
<portType name="AddSoftwarePortType">
<operation name="AddSoftware">
<input message="tns:AddSoftwareInput"/>
</operation>
</portType>
<binding name="SoftwareRepoBinding"
type="tns:AddSoftwarePortType">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="Add">
<soap:operation soapAction="http://localhost/Add"/>
<input>
<soap:body use="literal"
namespace="http://spam.com/softrepo"/>
</input>
</operation>
</binding>
<service name="SoftwareRepoService">
<documentation>Example Software Repository Service</documentation>
<port name="SoftwareRepoPort" binding="tns:SoftwareRepoBinding">
<soap:address location="http://localhost/Add"/>
</port>
</service>
</definitions>
|
<xsl:stylesheet  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  version="1.0" >  <xsl:output method='text'/>  <xsl:param name='wsdl'/>  <xsl:template match='/'>    <xsl:value-of select='$wsdl'/>  </xsl:template> </xsl:stylesheet> |
To add the new WSDL to the system, copy the sample description into
the text entry box. We post the message to the file add.doc and
add.xslt
for the generation of the WSDL in the system. Because we want to add the
WSDL exactly as it is entered, our XSLT simply copies the value of the
XSLT variable wsdl to the output. Notice that we had to set the
output method to text so that it is properly formatted (and the character
entities are not escaped). Listing 4 shows the
contents of add.xslt.
After adding the WSDL, we are presented with the view screen as shown in Figure 2. This is the same screen we would have gone to had we chosen "View WSDL Definitions" from the index page. From here we have the ability to edit and delete WSDL definitions from our repository. (The edit and delete functions work in a relatively straightforward way and should not need additional explanation.)
Figure 2: WSDL Manager view page

We now have a neat little software repository, we know how to connect to it using SOAP, and we can even set and get a description of it using WSDL. Now we will write a small program that uses all of this to add software to the system. The program will be an extension of the application written in the last column, but here it will query the repository for available software repository services. From the results of the query, it will prompt the user for the needed input and add a new software entry to the system.
In the world of Web services, a UDDI registry is where the WSDL of a Web service is stored. Unfortunately, it would take a year's worth of columns to write and describe a proper UDDI example, so we will use our own method of discovery for Web services in our repository.
The proper way to do this is to extend our SOAP handler to accept a new message that lets us query for available Web services. However, we've already shown you how to write a SOAP handler in 4Suite Server, so for this example we will extend the allowed methods in a HTTP 1.1 request to include a request type of "WSDL." This request will require a header field of name which will be the name of the Web service we are inquiring about. Listing 5 shows the code for a custom 4Suite Server WSDL handler.
There are three things to notice about the handler. First, it inherits
from CommonHandler. This is a base class that makes writing handler
classes easier. It adds many common functions like _safeHandleRequest
and _createErrorResponse, both used in our handler. _safeHandleRequest
calls the function that is the second argument inside a try and
except
block. It catches many standard exceptions, such as "URI unknown" and "access
denied," and turns them into the proper HTTP responses (in this case, 404
and 403, respectively.
from FtServer.Protocols.Http import CommonHandler
from FtServer import Core
class WsdlHandler(CommonHandler.CommonHandler):
   def handle(self, request):
       return self._safeHandleRequest(self._handle, request)
   def _handle(self,request):
       #See if the required name header is present
       if not request.headers.has_key('name'): return None
       name = request.headers['name']
       repo = Core.GetRepository()
       try:
           model = repo.getModel()
           #Get all of the statements from the model that define WSDL
           #documents with the requested name
           stmts = model.complete(None,"http://schema.4suite.org#wsdl",name)
           if not stmts:
               return self._createErrorResponse(request, 404, uri=name)
           #Fetch the document
           uri = stmts[0].subject
           body = repo.fetchDocument(uri).getContent()
       finally:
           repo.txRollback()
       headers = {'Content-type' : 'text/xml; charset=iso-8859-1',
                  'Content-length' : len(body),
                  }
       return CommonHandler.Response(200, headers, body)
       Â
def Register(properties):
   #Register are new handler to accept "WSDL" requests
   handler = WsdlHandler()
   return [(handler.handle, 'WSDL')]
|
The second thing to notice is the Register function at the
bottom of the file. The server calls this Register function when it starts. Register
takes a dictionary of properties from the configuration file and returns
a list of tuples. The first item in each tuple is a callable object (in
this case a method pointer) and the second is a HTTP request type that
the method should be called for. In this case we want to be called for
all WSDL request types.
Finally, in the beginning of the _handle function, we check
to see if the headers have the name key. If they do not, then
we return None. By returning None, we tell the server
that we did not try to handle the request. The server will then look to
see if there is any other registered handler listening for the WSDL request
method. If there is, it will be called. If no handlers are found to handle
the request, the server will return a 501 error.
Notice that there are three handlers
defined for a POST request: the SoapHandler, the PostHandler,
and the DeleteHandler. This mechanism is how all three of these
can coexist. The SoapHandler first looks at the message. If there
is not a SOAPAction header, it returns None. Then the
PostHandler
looks at the request. If there is not a template-xslt query argument,
it returns None. Finally, the DeleteHandler looks at
the request. If there is not a delete query argument, then it will return
None.
The idea is that eventually one of these handlers in the chain will handle
the request, and no error is raised.
To test our sample handler, we will start building our sample application. In our application, we will connect to the repository, get the WSDL description about the Software Repository, query the user for a set of parameters to satisfy the interface defined in the WSDL, and, finally, make a request to add new software.
The first step is covered in Listing 6. This
uses Python's standard httplib to connect to the server, make
an HTTP request of type WSDL, and get the response.
Now that we have the WSDL of the software repository, we need to parse
it and see what we can do. To do this we will use the Python wsdllib.py
module. Instructions for downloading and installation are available in
the resources section. Listing 7 shows the portion
of our example script that parses the WSDL from the server.
   #now that we have gotten the WSDL, parse it in    wsdl = wsdllib.ReadFromString(reply_body) |
With the WSDL parsed, we will call the function CreateSOAPMessage.
This function will use the WSDL to ask the user for the required inputs
to the SOAP message, then build the body of the response. With this brief
description and the comments in the code, you should be able to follow
what is happening in the function.
Similar to the previous column, we send the SOAP request to the server and parse and display the response. Listing 8 shows the entire example program and Listing 9 shows an execution example of the completed program.
[molson@penny code]$ python src/example1.py Please enter a selection for 'Service'  0. SoftwareRepoService Selection: 0 Please enter a selection for 'Service Port'  0. SoftwareRepoPort Selection: 0 Please enter a selection for 'Operation'  0. AddSoftware Selection: 0 Please enter value for 'title': New Software Please enter value for 'description': This is a new software Please enter value for 'version': 0.001 Please enter value for 'home': http://new-software.com Please enter value for 'creator': Mike.Olson@fourthought.com New Document (uri = /softrepo/incoming/New Software-0.001.xml) <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"   xmlns:dc="http://purl.org/dc/elements/1.1"   xmlns="http://namespaces.4suite.org/www/software-map"  >   <Software rdf:ID="New Software">     <dc:Title>New Software</dc:Title>     <dc:Creator>Mike.Olson@fourthought.com</dc:Creator>     <Home rdf:resource="http://new-software.com"/>     <CurrentVersion>0.001</CurrentVersion>     <dc:Description>This is a new software</dc:Description>   </Software>  </rdf:RDF> [molson@penny code]$ |
A Conclusion to our software repository
We've now seen how to use 4Suite Server to implement a Web service. In the first column, we built a software repository that displayed and retrieved software packages using HTTP from a repository. In the second column, we extended the example to allow HTTP POSTs to create new entries in the software repository. We also showed how to add packages to the repository using SOAP. Lastly, in this column, we created a WSDL document that defined our software repository service, then connected to the repository and used the WSDL to create a SOAP message to add a new package to the repository.
In the next installment of the column, we will look at some other options for processing SOAP message in Python.
- Participate in the discussion forum.
- Get an overview of the purpose of this series by reading the first
installment of our column.
- Previous installments of this series introduce the software repository
and have links to other relevant resources, including the software we use.
Please take a look at it: Web
services software repository, Part 1 and Web
services software repository, Part 2.
- Example program for this article.
- Download the WSDL4Py open-source project.
- You can find the latest information about the UDDI
system from UDDI.org site.
- You can download 4Suite and 4Suite Server
from our open-source site.
- More dW Web services resources.
Mike Olson (molson@fourthought.com) and Uche Ogbuji (uche@fourthought.com) are co-founders of and principal consultants at Fourthought Inc., where they develop the open-source tools 4Suite and 4Suite Server , and provide commercial consulting, training, and 4Suite customization for clients working with XML and Web services. They reside in Boulder, Colorado.

Mike Olson (molson@fourthought.com) and Uche Ogbuji (uche@ogbuji.net) are co-founders of and principal consultants at Fourthought Inc., where they develop the open-source tools 4Suite and 4Suite Server , and provide commercial consulting, training, and 4Suite customization for clients working with XML and Web services. They reside in Boulder, Colorado.




