Overview

The SAP PI Integration Builder Directory exposes a SOAP API to programmatically access, edit, and activate configuration objects. You may want to use it to perform bulk changes, to update routing rules such as a receiver determination at runtime or possibly to automatically generate some documentation. The code below  is an example of how to invoke an API service from Python.

h2. Getting the WSDLs

 

The service WSDLs are available in the Enterprise Service Repository (ESR) as external defintions under the software component version SAP BASIS 7.10 and the namespace http://sap.com/xi/XI/System. However, as is the case for ESR objects in general, the WSDLs are landscape agnostic, in other words details such as bindings are missing. You can either add these manually or, as I did below, dynamically load the WSDLs from the Webservice Navigator.

h2. Prerequisites

 

This demo uses the the Python SOAP client Suds available from https://fedorahosted.org/suds/ or the Python Cheese Shop. In addition your PI user requires the following roles:

    • SAP_XI_API_DISPLAY_J2EE

 

Demo module

def demo():<br />    """ <br />    Invoke the 'query' operation of the BusinessComponentService and print the<br />    response.<br />    """<br />    <br />    # Enable logging to print SOAP request and response to console<br />    logging.basicConfig(level=logging.INFO)<br />    logging.getLogger('suds.client').setLevel(logging.DEBUG)<br />    # Create client instance for BusinessComponentService<br />    client = getservice_client('BusinessComponentService')
    # Create empty request object - see print(client) for service & type info
    request = client.factory.create('ns2:BusinessComponentQueryIn')
    # invoke the BusinessComponentService's "query" operation
    response = client.service.query(request)
    print(response)
   
if __name__ == '__main__':
    _demo()h2. Resources
SAP Help - Integration Directory API

 

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.