Skip to main content

skip to main content

developerWorks  >  SOA and Web services | Open source  >

Build a simple C++ service component, Part 2: Using Python, Ruby, and Web services with the service component architecture

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Intermediate

Andrew Borley (borley@uk.ibm.com), Software Engineer, IBM UK

03 May 2007

You can use your existing code to create service components. Learn how to expose your scripts as SCA components and Web services using the Python, Ruby, and Web services support in Apache Tuscany SCA for C++. Create reusable, composable SCA components that are linked together within composites and exposed and invoked using whichever technologies are most suitable to the system being built.

Learning about Tuscany

Apache Tuscany is a project undergoing incubation at the Apache Software Foundation. One of the goals of the project is to produce a C++ runtime that implements the following Service Component Architecture (SCA) specifications (see Resources for more information):

  • SCA Assembly Model
  • SCA C++ Client and Implementation

This article describes the development and deployment of Python and Ruby service components, and it explains how to expose service components as Web services using the Apache Tuscany C++ runtime, based on the Milestone 2 release.



Back to top


Introduction

The Tuscany C++ Service Component Architecture (SCA) runtime enables you to build SCA components using standard C++, Python, or Ruby code. You can then deploy them to a location where the SCA runtime can locate and load them. These components can be wired together, reused, replaced with other components written in different languages, or exposed as Apache Axis Web services

You create a simple SCA component in Python. Then, you create a second component in Ruby and wire the two together. You then expose the components as a Web service, and you create a client (again using SCA components) to call the service.

Begin by downloading the Tuscany C++ Milestone 2 release from the Tuscany Web site (see Resources) and getting the PythonCalculator and the RubyCalculator samples working by following the documentation. The working samples ensure that sure your SCA, Python, Ruby, and Axis2/C installations are valid, that you have the TUSCANY_SCACPP, TUSCANY_SDOCPP and AXIS2C_HOME environment variables set correctly, and that you have enabled the Tuscany Python and Ruby extensions.



Back to top


Starting with a Python component

Start with a simple Python script that you enable, such as an SCA component. The module below provides some useful methods for discovering the number of e-mails available on a POP3 e-mail server and for retrieving the contents of those e-mails one by one.


Listing 1. pop3access.py Python script
                
import poplib

# Returns the number of messages on the POP3 server
def getNumOfMessages():
    mail = connect()
    numOfMessages = mail.stat()[0]
    mail.quit()

    return numOfMessages

# Returns a single message from the POP3 server
def getMessageData(msgNum):

    mail = connect()

    data = "Message number " + str(msgNum) + "\n"

    for line in mail.retr(msgNum)[1]:
        data += str(line)
        data += "\n"

    mail.quit()
    return data

# Used by the 2 methods above to connect to the e-mail server
def connect():

    # Use poplib.POP3_SSL for POP3 servers that require security
    mail = poplib.POP3("pop.mycompany.com")
    mail.user("myusername")
    mail.pass_("mypassword")

    return mail
      

So, you have a Python module that provides some useful function, but as it is, there is not much that you can do with it, aside from writing a Python script that uses the methods. As an SCA component within an SCA composite, it could do much more. Here is how to turn it into a component.

  1. Create a directory called emailsample, with a subdirectory called emailcomposite.
  2. Copy the pop3access.py script into the emailcomposite directory.
  3. Create an XML file called email.composite in the emailcomposite directory containing the following XML:

    Listing 2. email.composite SCA composite file
                            
    <?xml version="1.0" encoding="UTF-8"?>
    <composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="email">
        <component name="email.pop3access.component">
            <implementation.python module="pop3access"/>
        </component>
    </composite>
          

  4. Create an XML file called pop3access.componentType in the emailcomposite directory containing the following XML:

    Listing 3. pop3access.componentType SCA ComponentType file
                            
    <?xml version="1.0" encoding="UTF-8"?>
    <componentType xmlns="http://www.osoa.org/xmlns/sca/1.0">
        <service name="POP3AccessService">
            <interface.python/>
        </service>
    </componentType>
          

The Python script is now enabled as an SCA component. The email.composite file defines the components (and, as shown later, references and services) and their implementations. In this example, the component has a Python implementation that the SCA runtime finds using the module attribute. The pop3access.componentType file tells the SCA runtime which services (and, later, references and properties) the implementation script provides. In this example, the implementation provides a service named POP3AccessService.



Back to top


Testing the new component

So now test the component with a quick SCA client, written in Python, again.

First, enable your composite in the SCA runtime, using another composite file! Define a root composite file that specifies which components are used in the entire SCA system and that generally contains components implemented as composites themselves. This enables the real function to be separated into discrete parts that can then be assembled up into the entire system. In this example, create an email.solution.composite file in the emailsample directory containing the following XML:


Listing 4. email.solution.composite SCA composite file
                
<?xml version="1.0" encoding="UTF-8"?>
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="email.solution">
    <component name="EmailComponent">
        <implementation.composite name="email"/>
    </component>
</composite>
      

This file makes the newly created composite (containing the Python component) into a component that a client can reach. Now you can write a client that can find and invoke the functions in the Python component. Create a new subdirectory in the email directory called client, and create a Python script there called client.py. You can use this code:


Listing 5. client.py Python script
                
import sca

# Locate the email service
emailService = sca.locateservice("email.pop3access.component")

print "You have", emailService.getNumOfMessages(), "messages available"

print "First message:"
print emailService.getMessageData(1)
      

Aside from that locateservice call, it looks like you are just using that pop3access module directly. Now, to run this, set up a few Tuscany environment variables, and then run the script. On Windows®, open a command prompt, and use the following commands:


Listing 6. Running the client on Windows
                
set TUSCANY_SCACPP_SYSTEM_ROOT=C:\path\to\emailsample\
set TUSCANY_SCACPP_DEFAULT_COMPONENT=EmailComponent
set PATH=%TUSCANY_SCACPP%\bin;%PATH%
set PATH=%TUSCANY_SDOCPP%\bin;%PATH%
set PATH=%TUSCANY_SCACPP%\extensions\python\bin;%PATH%
set PYTHONPATH=%TUSCANY_SCACPP%\extensions\python\bin
cd C:\path\to\emailsample\client
python client.py
      

On Linux®, use the following commands:


Listing 7. Running the client on Linux
                
export TUSCANY_SCACPP_SYSTEM_ROOT=/path/to/emailsample
export TUSCANY_SCACPP_DEFAULT_COMPONENT=EmailComponent
export LD_LIBRARY_PATH=$TUSCANY_SCACPP/bin:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$TUSCANY_SCACPP/extensions/python/bin:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$TUSCANY_SDOCPP/bin:$LD_LIBRARY_PATH
export PYTHONPATH=$TUSCANY_SCACPP/extensions/python/bin
cd /path/to/emailsample/client
python client.py
      

These environment variables set the root path that Tuscany loads any composite files from, sets the default component (which is the component you created in the email.solution.composite file that links to your Python composite), and makes sure the path is correct. Your results should be like the following:


Listing 8. Client results
                
You have 8 messages available
First message:
Message number 1
X-Gmail-Received: 12345678905be7fdffc1ba5c3cea07
Delivered-To: username@mycompany.com
Received: by 169.78.50.5 with SMTP id x5cs2436hux;
        Fri, 5 May 2006 12:38:06 -0700 (PDT)
Received: by 169.35.66.12 with SMTP id 1234asd;
        Fri, 05 May 2006 12:38:05 -0700 (PDT)
Return-Path: <myproject-dev-return-2512-username=mycompany.com@ws.mycompany.com>
Received: from mail.mycompany.com (mail.mycompany.com [192.237.227.199])
        by mx.mycompany.com with SMTP id f19si2206285pyf.2006.05.05.12.38.05;
        Fri, 05 May 2006 12:38:05 -0700 (PDT)
Received-SPF: pass (mycompany.com: domain of 
        myproject-dev-return-2512-username=mycompany.com@ws.mycompany.com designates 
        192.237.227.199 as permitted sender)
Received: (qmail 51644 invoked by uid 500); 5 May 2006 19:38:04 -0000
Mailing-List: contact myproject-dev-help@ws.mycompany.com; run by ezmlm
Precedence: bulk
List-Help: <mailto:myproject-dev-help@ws.mycompany.com>
List-Unsubscribe: <mailto:myproject-dev-unsubscribe@ws.mycompany.com>
List-Post: <mailto:myproject-dev@ws.mycompany.com>
List-Id: <myproject-dev.ws.mycompany.com>
Reply-To: myproject-dev@ws.mycompany.com
Delivered-To: mailing list myproject-dev@ws.mycompany.com
Received: (qmail 51634 invoked by uid 99); 5 May 2006 19:38:04 -0000
Received: from dev.mycompany.com (HELO dev.mycompany.com) (1192.211.166.49)
    by mycompany.com (qpsmtpd/0.29) with ESMTP; Fri, 05 May 2006 12:38:04 -0700
X-ASF-Spam-Status: No, hits=0.0 required=10.0
        tests=
X-Spam-Check-By: mycompany.com
Received: from [192.237.227.198] (HELO mailserver.mycompany.com) 
    by mycompany.com (qpsmtpd/0.29) with ESMTP; Fri, 05 May 2006 12:38:03 -0700
Received: from mailserver (localhost [127.0.0.1])
        by mailserver.mycompany.com (Postfix) with ESMTP id C70F87142BB
        for <myproject-dev@ws.mycompany.com>; Fri,  5 May 2006 19:37:27 +0000 (GMT)
Message-ID: <123456789@mailserver>
Date: Fri, 5 May 2006 19:37:27 +0000 (GMT+00:00)
From: "Andy" <andy@ws.mycompany.com>
To: myproject-dev@ws.mycompany.com
Subject: Apache Tuscany
In-Reply-To: <1234.56789@mailserver>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
X-Virus-Checked: Checked by ClamAV on mycompany.com

The Apache Tuscany project could help in myproject - check it out
at http://incubator.apache.org/tuscany

Cheers!
Andy
      

Wow, that is a lot of data for one simple e-mail! Chances are that you are not interested in most of that stuff. You just want to see the Date, Subject, From, and To fields, and the message itself. Read on.



Back to top


Connecting up a Ruby component

There is a chunk of Ruby code that can run a quick regular expression over some data and return the results. You could recode this in Python and add it into your script, but using Tuscany means we do not have to!

Put this Ruby script, named emailregex.rb, into the emailcomposite directory.


Listing 9. emailregex.rb Ruby script
                
# Gets the value of a named field from an e-mail
def getEmailField(fieldName, data)

    # Construct a regular expression to find the field
    pattern = '^'+fieldName+': (.*?)$'

    re = Regexp.new(pattern)
    re =~ data

    # If no data, return an empty string
    if $1 == nil
        return ''
    end

    return $1
end

# Gets the message data from an e-mail
def getEmailMessage(data)

    # Get the message via a regular expression
    re = Regexp.new('$^$^(.*)', Regexp::MULTILINE)
    re =~ data

    # If no data, return an empty string
    if $1 == nil
        return ''
    end
    return $1
end
      

Turning a Ruby script into an SCA component is even easier than it is for Python code. You only need to define the component element in your composite file. Ruby does not require the componentType file. The services, references, and properties that a component provides are automatically detected based on the Ruby script and the composite file. (In the next Tuscany release, Python will not require the componentType file, either).

Edit the email.composite file to include the new Ruby component. Also, add a reference element to the Python component definition to wire it to the new component. And, replace those hard-coded POP3 server username and password variables in the Python script with SCA properties defined here. Here is the updated XML:


Listing 10. Updated email.composite SCA composite file
                
<?xml version="1.0" encoding="UTF-8"?>
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="email">
    <component name="email.pop3access.component">
        <implementation.python module="pop3access"/>
        <reference name="regexService">email.regex.component</reference>
        <property name="servername">pop.mycompany.com</property>
        <property name="username">myusername</property>
        <property name="password">mypassword</property>
    </component>
    <component name="email.regex.component">
        <implementation.ruby script="emailregex.rb"/>
    </component>
</composite>
      

To get the Python script to call the Ruby component and use those properties, add a couple of bits to the pop3access.py script and the pop3access.componentType file. Here is the updated script:


Listing 11. Updated pop3access.py Python script
                
import poplib

# Returns the number of messages on the POP3 server
def getNumOfMessages():
    mail = connect()
    numOfMessages = mail.stat()[0]
    mail.quit()

    return numOfMessages

# Returns a single message from the POP3 server
def getMessageData(msgNum):

    mail = connect()

    data = "Message number " + str(msgNum) + "\n"

    for line in mail.retr(msgNum)[1]:
        data += str(line)
        data += "\n"

    mail.quit()

    # Use the regexService reference
    toResult = regexService.getEmailField("To", data)
    fromResult = regexService.getEmailField("From", data)
    dateResult = regexService.getEmailField("Date", data)
    subjectResult = regexService.getEmailField("Subject", data)
    messageData = regexService.getEmailMessage(data)

    # Construct the result
    result =  "Date:    " + str(dateResult) +"\n"
    result += "To:      " + str(toResult) +"\n"
    result += "From:    " + str(fromResult) +"\n"
    result += "Subject: " + str(subjectResult) +"\n"
    result += str(messageData)

    return result

# Used by the 2 methods above to connect to the e-mail server
def connect():

    # Use the servername, username and password SCA properties
    mail = poplib.POP3(servername)
    mail.user(username)
    mail.pass_(password)

    return mail
      

And here is the updated componentType file:


Listing 12. Updated pop3access.componentType SCA ComponentType file
                
<?xml version="1.0" encoding="UTF-8"?>
<componentType xmlns="http://www.osoa.org/xmlns/sca/1.0"
              xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <service name="POP3AccessService">
        <interface.python/>
    </service>

    <reference name="regexService">
        <interface.python/>
    </reference>

    <property name="servername" type="xsd:string"/>
    <property name="username" type="xsd:string"/>
    <property name="password" type="xsd:string"/>
</componentType>
      

With some fairly simple changes, you made a Python script call a Ruby script, and it just looks like a normal call to another Python module. The regexService reference and the servername, username, and password properties look like they are uninitialized variables that will cause the script to fail, but do not worry: they will not fail. They get initialized and assigned the value given in the composite by the Tuscany runtime.

Run that client again (without any changes) with the same commands as above. The only change you need to make is to add the Tuscany Ruby extension onto your path:


Listing 13. Running the client on Windows
                
...
set PATH=%TUSCANY_SCACPP%\extensions\ruby\bin;%PATH%
...
python client.py
      

You should now see something like the following output:


Listing 14. Client results
                
You have 8 messages available
First message:
Date:    Fri, 5 May 2006 19:37:27 +0000 (GMT+00:00)
To:      myproject-dev@ws.mycompany.com
From:    "Andy" <andy@ws.mycompany.com>
Subject: Apache Tuscany

The Apache Tuscany project could help in myproject - check it out
at http://incubator.apache.org/tuscany

Cheers!
Andy
      

This is much better!



Back to top


Exposing the composite as a Web service

So now you have a couple of SCA components providing a useful service. Now, make that service available beyond your local machine by exposing it as a Web service. Tuscany uses Apache Axis2/C (see Resources) to provide its Web service function. In this example, use the Axis simple HTTP server to host your service.

If you ran the PythonCalculator and RubyCalculator samples (as we suggested above), you should have seen them exposed and invoked as Web services under Axis2C. This means you have set up your AXIS2C_HOME environment variable, and you enabled Axis2C to run Tuscany SCA services by deploying the Tuscany Web service extension in to Axis.

The changes you need to make to the example SCA composite are tiny: just one update to the email.composite SCA composite file, as shown in Listing 15.


Listing 15. Updated email.composite SCA composite file
                
<?xml version="1.0" encoding="UTF-8"?>
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="email">     
    <service name="EmailWebService">
        <binding.ws/>
        <reference>email.pop3access.component</reference>
    </service>
    <component name="email.pop3access.component">
        <implementation.python module="pop3access"/>
        <reference name="regexService">email.regex.component</reference>
        <property name="servername">pop.mycompany.com</property>
        <property name="username">myusername</property>
        <property name="password">mypassword</property>
    </component>
    <component name="email.regex.component">
        <implementation.ruby script="emailregex.rb"/>
    </component>
</composite>
      

Adding that service element to your composite and specifying that it uses the Web service binding (binding.ws) makes the composite expose a service with a Web services interface, similar to how adding a service element in the componentType file exposes the component to other components.

Now you can start the Axis2C simple HTTP server with the following commands on Windows:


Listing 16. Starting the Axis2C Simple HTTP server on Windows
                
set TUSCANY_SCACPP_SYSTEM_ROOT=C:\path\to\emailsample\
set TUSCANY_SCACPP_DEFAULT_COMPONENT=EmailComponent
set PATH=%TUSCANY_SCACPP%\bin;%PATH%
set PATH=%TUSCANY_SDOCPP%\bin;%PATH%
set PATH=%TUSCANY_SCACPP%\extensions\python\bin;%PATH%
set PATH=%TUSCANY_SCACPP%\extensions\ruby\bin;%PATH%
set PATH=%AXIS2C_HOME%\lib;%PATH%
set PYTHONPATH=%TUSCANY_SCACPP%\extensions\python\bin
cd %AXIS2C_HOME%\bin
.\axis2_http_server.exe
      

On Linux, use the following commands:


Listing 17. Starting the Axis2C Simple HTTP server on Linux
                
export TUSCANY_SCACPP_SYSTEM_ROOT=/path/to/emailsample
export TUSCANY_SCACPP_DEFAULT_COMPONENT=EmailComponent
export LD_LIBRARY_PATH=$TUSCANY_SCACPP/bin:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$TUSCANY_SDOCPP/bin:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$TUSCANY_SCACPP/extensions/python/bin:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$TUSCANY_SCACPP/extensions/python/bin:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$AXIS2C_HOME/lib:$LD_LIBRARY_PATH
export PYTHONPATH=$TUSCANY_SCACPP/extensions/python/bin
cd $AXIS2C_HOME/bin
./axis2_http_server
      

The environment variables you set up here are almost the same as those required for the local client you ran earlier. Just add the Axis2C libraries onto the path.

So you now have Axis providing your SCA composite as a Web service. You need a Web service client to invoke it. You could write a client using the Axis APIs (in Java™ or C), or you could use some other Web service library, such as php_soap for PHP or SOAPpy for Python. Instead, you can simply use SCA to make Web service calls.

First, create another SCA composite that can run on the client computer. Do this by creating a new subdirectory in the emailsample directory named wsclient and creating a wsclient.composite file within it containing the following XML:


Listing 18. wsclient.composite SCA composite file
                
<?xml version="1.0" encoding="UTF-8"?>
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="wsclient"> 
    <reference name="EmailService">
        <binding.ws uri="http://localhost:9090/axis2/services/EmailWebService"/>
    </reference>
</composite>
      

This time, you just have a reference element that is wired to the service you have exposed as an Axis2C Web service using the specified address. Make sure you change this address if you are running the server on a different computer or port.

Now, write another root composite file to allow a local client to access the reference, and write the client code itself. This time, write and run a Ruby client. Put the following wsclient.solution.composite XML into the wsclient directory:


Listing 19. wsclient.solution.composite SCA composite file
                
<?xml version="1.0" encoding="UTF-8"?>
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="wsclient.solution">
    <component name="WSClientComponent">
        <implementation.composite name="wsclient"/>
    </component>
</composite>
      

And Listing 20 shows the Ruby client code. Again, put this file into your wsclient directory.


Listing 20. WSClient.rb Ruby script
                
require("tuscany_sca_ruby")

emailService = SCA::locateService("EmailService")

puts "You have " << (emailService.getNumOfMessages()).to_s << " messages available"

puts "First message:"
puts emailService.getMessageData(1)
      

You might notice that this code looks almost identical to the Python client you wrote earlier. You are correct: you could, in fact, use that client (with the tweak of changing the name in the sca.locateservice method from email.pop3access.component to EmailService). Instead, you can invoke the Ruby client. You need to set up some environment variables again before running the script. Here are the commands on Windows:


Listing 21. Running the Web service client on Windows
                
set TUSCANY_SCACPP_SYSTEM_ROOT=C:\path\to\emailsample\wsclient
set TUSCANY_SCACPP_DEFAULT_COMPONENT=WSClientComponent
set PATH=%TUSCANY_SCACPP%\bin;%PATH%
set PATH=%TUSCANY_SDOCPP%\bin;%PATH%
set PATH=%AXIS2C_HOME%\lib;%PATH%
cd C:\path\to\emailsample\wsclient
ruby -I%TUSCANY_SCACPP%\extensions\ruby\bin WSClient.rb
      

Here are the commands for Linux:


Listing 22. Running the Web service client on Linux
                
export TUSCANY_SCACPP_SYSTEM_ROOT=/path/to/emailsample/wsclient
export TUSCANY_SCACPP_DEFAULT_COMPONENT=WSClientComponent
export LD_LIBRARY_PATH=$TUSCANY_SCACPP/bin:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$TUSCANY_SDOCPP/bin:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$AXIS2C_HOME/lib:$LD_LIBRARY_PATH
cd /path/to/emailsample/wsclient
ruby -I$TUSCANY_SCACPP/extensions/ruby/bin WSClient.rb
      

The environment variables you set up here direct the Tuscany runtime to use the wsclient.solution.composite and the linked wsclient.composite. Running this client invokes the Web service running on the Axis2C server. You see something similar to the following results returned again:


Listing 23. Web service client results
                
You have 8 messages available
First message:
Date:    Fri, 5 May 2006 19:37:27 +0000 (GMT+00:00)
To:      myproject-dev@ws.mycompany.com
From:    "Andy" <andy@ws.mycompany.com>
Subject: Apache Tuscany

The Apache Tuscany project could help in myproject - check it out
at http://incubator.apache.org/tuscany

Cheers!
Andy
      



Back to top


Conclusion

With the help of this article, you took some existing Python code, turned it into an SCA component, and then wired it to a second component that provides some useful function implemented in Ruby. You then exposed your system as a Web service and used a second SCA composite to invoke that service.

This article offered you a taste of the power of Service Component Architecture and the Apache Tuscany implementation, which enables you to use your skills and existing code to create reusable, composable components that are linked together within composites and exposed and invoked using whichever technologies are most suitable to the system being built.



Resources

Learn

Get products and technologies

Discuss


About the author

Andrew Borley photo

Andrew Borley is an IT Specialist at IBM Hursley, UK. He has held various roles including developer, team leader, and project manager and has worked with various technologies in his 7 years at IBM. Since 2001 he has been working with customers on Service-Oriented Architecture projects, and his current role is developing the SCA for C++ implementation within the Apache Tuscany open source SOA project.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top


IBM is a trademark of IBM Corporation in the United States, other countries, or both. Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. Microsoft, Windows, Windows NT, and the Windows logo are trademarks of Microsoft Corporation in the United States, other countries, or both. Linux is a registered trademark of Linus Torvalds in the United States, other countries, or both. Other company, product, or service names may be trademarks or service marks of others. Other company, product, or service names may be trademarks or service marks of others.