Skip to Content

Integrating SAP and Google Wave using SAP Process Integration (PI) should be easy and, as it turns out, it is.

In this post I would like to show you how to build a simple Wave robot that communicates with a SAP backend system via PI to process a very simple ‘Purchase Order’. A wave robot is an automated wave participant, i.e. a program that can interact with a wave. I assume that you know PI and that you have had a look at the Wave tutorial for Python. One of the current Wave limitations is that all robots have to be deployed to Google’s Appengine (GAE), so you will also have to take a peek at GAE , Google’s cloud-computing service.

 

Below you can see the result of what we are trying to achieve . As you can see the wave has two participants – myself and the robot. At the top there is a dummy product description for surfboards, followed by a simple order form. In this example, I ordered three surfboards and the SAP backend system replied with a purchase order confirmation.

 

!http://media.use.com/images/s_4/6de920329508a71049e8.jpg|height=239|alt=Screenshot of SAP PI Bot on Google Wave|width=350|src=http://media.use.com/images/s_4/6de920329508a71049e8.jpg!

 

Here’s what we will have to build for this scenario.

 

*SAP PI </p><ul><li>create an outbound synchronous service interface that the robot will call from Appengine via SOAP</li><li>create a corresponding inbound service interface to our SAP backend system</li><li>configure the runtime in the PI Diectory</li></ul><br />SAP backend<br /><ul><li>a provider proxy for the inbound service interface to expose the business logic</li></ul><p> </p><p>Robot on Appegine </p><ul><li>The actual robot python code</li><li>app.yaml – this file serves as a deployment descriptor for Appengine</li><li>some static files such as the robot’s avatar and the bot’s HTML profile page </li></ul><p> </p><p>The diagram below depicts the various components of this scenario and the protocols used to communicate between them.</p><p> </p><p>!http://media.use.com/images/s_4/3c53d2fb21a3c8fab27d.jpg|height=68|alt=Wave to SAP communication|width=592|src=http://media.use.com/images/s_4/3c53d2fb21a3c8fab27d.jpg! </p><p> </p><p>Before presenting the actual code, let’s look at what messages will be exchanged and when.</p><p> </p><ol><li>When you add the bot (sappibot@appspot.com) as participant to the wave, Wave calls the robot at http://sappibot.appspot.com/_wave/jsonrpc to inform it that it was added (WAVELET_SELF_ADDED event). The HTTP body is a JSON (JavaScript Object Notation) string that contains data such as the id, the title and the participants of the wave.<br /><br /></li><li>The robot responds synchronously with a JSON message that instructs Wave to embed a simple order form into the wave.<br /><br /></li><li>When, at some later stage, a participant submits an order, Wave calls the same jsonrpc endpoint to notify the robot of the FORM_BUTTON_CLICKED event. The robot reads the form data and synchronously calls PI via SOAP.<br /><br /></li><li>PI synchronously calls the SAP backend system and returns the response to the robot.<br /><br /></li><li>The robot reads the response and returns it to Wave in a JSON message, that instructs Wave to append the message in a new blip (as a reminder of what a blip is, please see the diagram below). </li></ol><br /><p>!http://code.google.com/apis/wave/images/waveEntities.png|height=222|alt=|width=350|src=http://code.google.com/apis/wave/images/waveEntities.png!</p><p>Diagram source: Google Wave API Overview – Creative Commons license.</p><p> </p><p><u>SAP PI</u></p><p>Below is the WSDL for the service, as generated in the PI Directory. As you can see the interface is very simple. The request contains the wave id and order quantity and the response consists of the single field ‘message’.</p><p> </p><p>WSDL</p><p> </p><p><wsdl:definitions xmlns:wsdl=”http://schemas.xmlsoap.org/wsdl/” xmlns:p1=”urn:hcvst:test” name=”SI_OrderDemo_Sync_Out” targetNamespace=”urn:hcvst:test”><br />   <wsdl:documentation /><br />   <wsdl:types><br />      <xsd:schema xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns=”urn:hcvst:test” targetNamespace=”urn:hcvst:test”><br />         <xsd:element name=”MT_Request” type=”DT_Request” /><br />         <xsd:element name=”MT_Response” type=”DT_Response” /><br />         <xsd:complexType name=”DT_Response”><br />            <xsd:sequence><br />               <xsd:element name=”message” type=”xsd:string” /><br />            </xsd:sequence><br />         </xsd:complexType><br />         <xsd:complexType name=”DT_Request”><br />            <xsd:sequence><br />               <xsd:element name=”wid” type=”xsd:string” /><br />               <xsd:element name=”quantity” type=”xsd:integer” /><br />            </xsd:sequence><br />         </xsd:complexType><br />      </xsd:schema><br />   </wsdl:types><br />   <wsdl:message name=”MT_Request”><br />      <wsdl:documentation /><br />      <wsdl:part name=”MT_Request” element=”p1:MT_Request” /><br />   </wsdl:message><br />   <wsdl:message name=”MT_Response”><br />      <wsdl:documentation /><br />      <wsdl:part name=”MT_Response” element=”p1:MT_Response” /><br />   </wsdl:message><br />   <wsdl:portType name=”SI_OrderDemo_Sync_Out”><br />      <wsdl:documentation /><br />      <wsdl:operation name=”SI_OrderDemo_Sync_Out”><br />         <wsdl:input message=”p1:MT_Request” /><br />         <wsdl:output message=”p1:MT_Response” /><br />      </wsdl:operation><br />   </wsdl:portType><br />   <wsdl:binding name=”SI_OrderDemo_Sync_OutBinding” type=”p1:SI_OrderDemo_Sync_Out”><br />      <soap:binding xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/” style=”document” transport=”http://schemas.xmlsoap.org/soap/http” /><br />      <wsdl:operation name=”SI_OrderDemo_Sync_Out”><br />         <soap:operation xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/” soapAction=”http://sap.com/xi/WebService/soap1.1″ /><br />         <wsdl:input><br />            <soap:body xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/” use=”literal” /><br />         </wsdl:input><br />         <wsdl:output><br />            <soap:body xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/” use=”literal” /><br />         </wsdl:output><br />      </wsdl:operation><br />   </wsdl:binding><br />   <wsdl:service name=”SI_OrderDemo_Sync_OutService”><br />      <wsdl:port name=”SI_OrderDemo_Sync_OutPort” binding=”p1:SI_OrderDemo_Sync_OutBinding”><br />         <soap:address xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/” location=”http://YOUR_ENDPOINT:80?version=3.0&amp;Sender.Service=sappibot&amp;Interface=urn%3Ahcvst%3Atest%5ESI_OrderDemo_Sync_Out” /><br />      </wsdl:port><br />   </wsdl:service><br /></wsdl:definitions> </p><p><u><br />SAP Backend</u></p><p>The provider proxy implementation is kept to a minimum too. It only returns the message ‘Thanks for your order’. In a real scenario this is where you would process the order, of course.</p><p> </p><p>method ZHC_II_SI_ORDER_DEMO_SYNC_IN~SI_ORDER_DEMO_SYNC_IN.<br />OUTPUT-MT_RESPONSE-MESSAGE = ‘Thanks for your order’.<br />endmethod. </p><p> </p><p><u>Robot</u></p><p>The directory layout for the robot application is as follows:</p><p> </p><p>sappibot<br /> |- sappibot.py         # the Python robot code<br /> |- app.yaml            # deployment descriptor<br /> |- interface.wsdl      # the WSDL posted above<br /> |- static<br /> |   |- avatar.png      # the bot’s avatar<br /> |   |- profile.html     # the bot’s profile page<br /> |- suds                   # great SOAP library<br />     |- …</p><p> </p><p>sappibot.py*

 

import os
import urlparse
import logging
import suds.client
from suds.transport.http import HttpAuthenticated
from waveapi import events
from waveapi import robot
from waveapi import document

PI_USER = ‘YOUR_USERNAME’

PI_PASS = ‘YOUR_PASSWORD’
SERVER = ‘http://sappibot.appspot.com
WSDL = urlparse.urlunsplit((‘file’, os.getcwd(), ‘interface.wsdl’,None, None))
INPUT_NAME = ‘order_input’

def OnRobotAdded(properties, context):
    “”” Called by Wave after Robot has been added as wave participant.
        In response we add the order form to the root blip.
    “””
    root_wavelet = context.GetRootWavelet()
    root_blip = context.GetBlipById(root_wavelet.GetRootBlipId())
    doc = root_blip.GetDocument()
    doc.AppendElement(document.FormElement(document.ELEMENT_TYPE.LABEL,
        ‘order_label’, ‘Order quantity’))
    doc.AppendElement(document.FormElement(document.ELEMENT_TYPE.INPUT,
        INPUT_NAME))
    doc.AppendElement(document.FormElement(document.ELEMENT_TYPE.BUTTON,
        ‘btn_order’, ‘Send Order’))

def OnFormButtonClicked(properties, context):
    “”” Called by Wave on form submission. We get the form data here and
        synchronously call PI.
    “””
    t = HttpAuthenticated(username=PI_USER, password=PI_PASS)
    client = suds.client.Client(WSDL, transport=t)
    root_wavelet = context.GetRootWavelet() 
    waveid = root_wavelet.GetWaveId()
    root_blip = context.GetBlipById(root_wavelet.GetRootBlipId())
    quantity = None
    for e in root_blip.GetElements().values():
        if e.type == document.ELEMENT_TYPE.INPUT:
            quantity = e.value
    logging.debug(‘Quantity: %s’ % quantity)
    if quantity:
        response = client.service.SI_OrderDemo_Sync_Out(waveid, quantity)        Reply(context, response.__str__())

def Reply(context, message):
    “”” Helper function to write a response blip “””
    root_wavelet = context.GetRootWavelet()
    root_wavelet.CreateBlip().GetDocument().SetText(message)

if __name__ == ‘__main__’:
    piBot = robot.Robot(‘PI Bot Demo’,

        image_url=SERVER‘/static/avatar.png’,<br />        version=’1′,<br />        profile_url=SERVER‘/static/index.html’)
    piBot.RegisterHandler(events.FORM_BUTTON_CLICKED, OnFormButtonClicked)
    piBot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded)
    piBot.Run()

 

app.yaml

The app.yaml file tells Google Appengine about your application. In particular it informs GAE of how to map URLs to program and static files. As you can see all URLs starting with wave are to be serviced by sappibot.py. Wave expects robots to use this path. Static files, such as avatar.png, are served from the static directory.</p><p> </p><p>application: sappibot<br />version: 1<br />runtime: python<br />apiversion: 1

handlers:
– url: /_wave/.<br />  script: sappibot.py<br />- url: /static<br />  static_dir: static</p><p> </p><p>Final notes*

 

    To run this exmple, Appengine has to be able to call PI.
    Also, Appengine only allows HTTP endpoints on port 80 (or 443 for
    HTTPS). I used a PI VMWare image on my laptop and forwarded port 80 to
    the port of the Integration Server (If you use Linux, check out the
    command socat).

    I had to make one small change to the SOAP library suds.client in line 107 by replacing options.cache = FileCache(days=1) with options.cache = None, as GAE does not permit file access.

       

      This concludes this brief integration scenario. I hope you enjoyed it. I know I enjoyed building it. Thanks for reading.

       

      To report this post you need to login first.

      6 Comments

      You must be Logged on to comment or reply to a post.

      1. Pete White
        Great blog Hans…

        I’m trying to replicate this example and am having problems. How did you get the suds library working with GAE? I tried various methods of disabling caching and am now getting a socket timeout error… Any help would be much appreciated…

        (0) 
        1. Hans Christian Stockhausen Post author
          Hi Pete,

          Timeout error sounds like you might be trying to talk to a closed port. What endpoint are you calling? Are you sure there’s no firewall blocking the call? I would try to get the connectivity to work first. Run a simple server (netcat for example) on your box and try to connect to it from GAE using Python’s urllib or GAE’s fetchurl. Does that work? -HC

          (0) 
          1. Pete White
            Hi Hans, thanks for the quick response… My endpoint is a PI 7.1 server which I have a clear route to from the internet on port 80. I tested the connectivity using SOAPUI and it all works great. The problem seems to be with the SUDS library… Setting “options.cache = None” for example, still gave an error in the GAE logs. Setting cache=None in the Client constructor also failed. I got round this problem by changing the cache to type NoCache. Could I check which version of Suds you have working please. Thanks, Pete
            (0) 

      Leave a Reply