Sunday, November 7, 2010

Invoking RESTful WebServices - ODI11g

--

Web services can be accessed via HTTP and so they can be invoked\consumed using HTTP GET/POST methods.

With ODI there are different options like creating a customized Open tool or Knowledge Module to implement this. In this post, will be demonstrating how to invoke a RESTful webservice and write the response XML to a file using an ODI Procedure.

Will be using Yahoo's Place finder geocoding webservice as an example to pass street address to get additional information like latitude,longitude,county,etc.



Using the Yahoo's geocoding webservice -

To use geocoding webservice you need an Applicaion_Id (appid) key from yahoo, you can generate it from here(There is no charge to it).

The format to invoke the webserivce in this case is http://where.yahooapis.com/geocode?q=STATE,CITY,STATE&appid=[yourappidhere]

If you hit the URL below on any browser you should see the XML (that's one of the beauty of REST )
http://where.yahooapis.com/geocode?q=1600+Pennsylvania+Avenue,+Washington,+DC&appid=[yourappidhere]




ODI procedure to Invoke a single RESTful web service:

A simple ODI procedure which uses Jython to invoke and consume web service. This procedure has many input parameter.These parameters can be set either using Variables or OPTIONS.

Required Parameter -
1) URLFormat - URL of the webservice to be invoked.

In this case the value is

URLFormat="http://where.yahooapis.com/geocode?q='#STREET','#CITY','#STATE'&appid=myappid"

Note: STREET,CITY,STATE are the ODI variables.
         myappid is the application key provided by yahoo

2) OutputXMLFile="C:\\temp\\yahooREST.xml"

Optional Parameters -
There are other optional parameters which you can set if needed

PROXY_HOST - proxy host name
PROXY_PORT - proxy port number
and others

ODI Procedure Code -

import java.io.BufferedReader as BR
import java.io.InputStreamReader as ISR
import java.net.HttpURLConnection as uc
import java.net.URL as URL
import java.net.Proxy as Proxy
import java.net.InetSocketAddress as ISA
import base64
import string

#### Set the parameters below - Use Variables or Options

########### Required Parameters #######################

URLFormat="http://where.yahooapis.com/geocode?q='#STREET','#CITY','#STATE'&appid=myappid"

OutputXMLFile="C:\\temp\\yahooREST.xml"

######### Optional Parameters - set if required ##############

# Encrypt the procedure, if needed 

# if you are setting the Proxy_Host , set the Proxy_Port too
Proxy_Host=""
# Proxy Port  - If not using Proxy, default value should be 0
Proxy_Port=0

## Use Either Proxy_Username and Proxy_Password OR Proxy_UserPass_Encoded
Proxy_Username = ""
Proxy_Password = ""
Proxy_UserPass_Encoded=""

## Use Either Webservice_Username and Webservice_Password OR Webservice_UserPass_Encoded
Webservice_Username = ""
Webservice_Password = ""
Webservice_UserPass_Encoded=""


#####################################
####### Code starts here   ##########
#####################################

## Encode the username password for basic authentication

if Webservice_UserPass_Encoded == "":
   Webservice_Authorization = 'Basic ' + string.strip(base64.encodestring(Webservice_Username + ':'  +  Webservice_Password))
else:   
   Webservice_Authorization = 'Basic ' + Webservice_UserPass_Encoded
   
if Proxy_UserPass_Encoded == "":
   Proxy_Authorization = 'Basic ' + string.strip(base64.encodestring(Proxy_Username + ':' + Proxy_Password))
else:
   Proxy_Authorization = 'Basic ' + Proxy_UserPass_Encoded

inURL=URLFormat.replace(' ','%20')

if Proxy_Host == "": 
   url = URL(inURL)
   uc =  url.openConnection()
else:
   isa = ISA(Proxy_Host,Proxy_Port)
   proxy = Proxy(Proxy.Type.HTTP, isa)
   url = URL(inURL)
   uc =  url.openConnection(proxy)   
uc.setRequestMethod("GET")
uc.setRequestProperty("Authorization",Webservice_Authorization) 
uc.setRequestProperty("Proxy-Authorization",Proxy_Authorization)
uc.connect()
responseCode=uc.getResponseCode()
responseMessage=uc.getResponseMessage()

if responseCode ==  200 and responseMessage == "OK":
   br=BR(ISR(uc.getInputStream()))
   outRESTXML = ""                           
   outline = br.readLine()
   while (outline != None):  
          outRESTXML += outline + "\n" 
          outline = br.readLine() 
   XMLfilehandle= open(OutputXMLFile,'w')
   XMLfilehandle.write(outRESTXML)
   XMLfilehandle.close()
else:
   uc.disconnect()
   raise  'HTTP Error - Response Code:' + str(responseCode) + ' and Response Message: ' + responseMessage + '\n'

uc.disconnect()

To run it for every row , design a package to loop through all rows , populate variables and run the procedure and parse the XML using XML adapter.

Note: This code can also be used as a workaround for Jython URLLIB2 issue.


ODI procedure to Invoke a RESTful web service for all the rows in a query:

In some cases , the XML returned also contains information about the parameters that were used to invoke the webserivce. In such a case you can customize the code to invoke webservice and generate XML file for every row. This help save lot of work and time.

Note: This scenario is not suitable in the above case as the returned XML does not contain the street,city,state. Code can be changed to insert the XML into a table also.


Command on Source:




Command on Target:






The entire code:

import java.io.BufferedReader as BR
import java.io.InputStreamReader as ISR
import java.net.HttpURLConnection as uc
import java.net.URL as URL
import java.net.Proxy as Proxy
import java.net.InetSocketAddress as ISA
import java.text.SimpleDateFormat as SDF
import java.util.Date as date
import base64,string


## Set the parameters below - Use Variables or Options

######### Required Parameters ###############


# input URL format - replace the input parameters with # variables
URLFormat="""http://where.yahooapis.com/geocode?q='#STREET','#CITY','#STATE'&appid=myappid"""

# rownum as part of filename to generate distinct filenames
rownbr = '#ROWNBR'


# Output XML file Format - don't add the ".xml" extension
OutputXMLFileFormat="C:\\temp\\yahooOutput\\YahooREST"


######### Optional Parameters - set if required ##############

# Encrypt the procedure, if needed 

# if you are setting the Proxy_Host , set the Proxy_Port too
Proxy_Host=""
# Proxy Port  - If not using Proxy, default value should be 0
Proxy_Port=0

## Use Either Proxy_Username and Proxy_Password OR Proxy_UserPass_Encoded
Proxy_Username = ""
Proxy_Password = ""
Proxy_UserPass_Encoded=""

## Use Either Webservice_Username and Webservice_Password OR Webservice_UserPass_Encoded
Webservice_Username = ""
Webservice_Password = ""
Webservice_UserPass_Encoded=""

# the date/datetime format will be appended to the xml file (needs to be Java Compatible)
xmlFileNameDateAppend = "yyyy-MM-dd"


############################################
###  Main Starts here                  #####
############################################
         
def getRESTXML(inURLData):
# setup and call the webservice
    responseCode=0
    responseMessage=""
    errorMessage=''
    outRESTXML = ""                           
    if Proxy_Host == "":
       url = URL(inURLData)
       uc =  url.openConnection()
    else:
       isa = ISA(Proxy_Host,Proxy_Port)
       proxy = Proxy(Proxy.Type.HTTP, isa)
       url = URL(inURLData)
       uc =  url.openConnection(proxy)   
    uc.setRequestMethod("GET")
    uc.setRequestProperty("Authorization",Webservice_Authorization)
    uc.setRequestProperty("Proxy-Authorization",Proxy_Authorization)
    uc.connect()
    responseCode=uc.getResponseCode()
    responseMessage=uc.getResponseMessage()      
    if responseCode ==  200 and responseMessage == "OK":
       br=BR(ISR(uc.getInputStream()))
       outline = br.readLine() 
       while (outline != None):  
            outRESTXML += outline + "\n" 
            outline = br.readLine() 
       uc.disconnect()
    else:
       errorMessage = 'Error - Response Code:' + str(responseCode) + ' and Response Message: ' + responseMessage + '\n'
    return outRESTXML,errorMessage

### Start of Main

## Encode the username password for basic authentication
if Webservice_UserPass_Encoded == "":
   Webservice_Authorization = 'Basic ' + string.strip(base64.encodestring(Webservice_Username + ':'  +  Webservice_Password))
else:   
   Webservice_Authorization = 'Basic ' + Webservice_UserPass_Encoded
   
if Proxy_UserPass_Encoded == "":
   Proxy_Authorization = 'Basic ' + string.strip(base64.encodestring(Proxy_Username + ':' + Proxy_Password))
else:
   Proxy_Authorization = 'Basic ' + Proxy_UserPass_Encoded

inDateFormat = SDF(xmlFileNameDateAppend)
inDate=inDateFormat.format(date())

if xmlFileNameDateAppend == "":
    outXMLFile = OutputXMLFileFormat + '_ '+ str(rownbr)  +  '.xml'
else:
    outXMLFile = OutputXMLFileFormat + '_ ' + str(rownbr)  + '_' +  inDate + '.xml'


inURLData = URLFormat.replace(' ','%20')
retRESTXML,errorMessage=getRESTXML(inURLData)
if errorMessage <> "":
    raise errorMessage
else:
    xmlFileHandle=open(outXMLFile,'w')
    xmlFileHandle.write(retRESTXML)
    xmlFileHandle.close()


Output -



RESTful webservices can also return data in different format like CSV,TEXT,JSON etc.
They can also manipulate data using HTTP POST\DELETE methods.
Code can be easily customized to handle any condition.

7 comments:

  1. Hi Suraj,
    What do I need to do to invoke WS defined with POST method???
    Thought I just only need to replace the method with this line: uc.setRequestMethod("POST")
    But the procedure never ends....

    Hope you can help me, thanks in advance!!!

    ReplyDelete
    Replies
    1. You need to test code in eclipse before you put it into ODI. There may be other POST properties that you need to set.

      Delete
  2. Hi Suraj,

    We are trying to invoke rest api from ODI and found your blog useful.

    But unfortunately we are getting an error and finding it really hard to resolve it.

    Below is the error:
    UnicodeEncodeError: 'ascii' codec can't encode character u'\xa9' in position 115: ordinal not in range(128)

    It will be great help if you could suggest what could be the reason for it, as our understanding of java is very limited.

    So, below is the code which we pasted in ODI procedure and set langauage as Jython.


    import java.net.URL as URL
    import java.io.BufferedReader as BR
    import java.io.InputStreamReader as SR
    import java.net.HttpURLConnection as con
    import base64
    import string
    import codecs
    import urllib
    import urllib2

    URLFormat="http://nominatim.openstreetmap.org/search?q=3314+Eastern+Ave,+21224,+Baltimore,+MD,+US&format=xml&polygon=0&addressdetails=1"

    XMLFile="/u01/Middleware/Oracle_Home_1213/odi/demo/file/OSMGeo_1.xml"

    OSMURL=URLFormat.replace(' ','%20')
    OSMURL=OSMURL.replace('&','%26')
    url = URL(OSMURL)

    con = url.openConnection()

    con.setRequestMethod("GET")

    con.connect()
    responseCode=con.getResponseCode()
    responseMessage=con.getResponseMessage()

    if responseCode == 200 and responseMessage == "OK":
    br=BR(SR(con.getInputStream()))
    outRESTXML = ""
    outline = br.readLine()
    while (outline != None):
    outRESTXML += outline + "n"
    outline = br.readLine()
    XMLfilehandle= open(XMLFile,'w')
    XMLfilehandle.write(outRESTXML)
    XMLfilehandle.close()
    else:
    con.disconnect()

    con.disconnect()


    Any help will be greatly appreciated.

    ReplyDelete
  3. Is this supported in ODI 11.1.1.6 version??

    ReplyDelete
  4. Hi,

    I have requirement that there is a source table(on sqlserver) which has 10 records and i have target SugarCRM table which is on Cloud and we dont have access to that table. So for inserting/updating data on to the target table we need to use REST webservice as we have REST endpoint URL.

    Now the issue is how can we achieve it.

    If i create a mapping for bringing data from source to staging, and inside the package if i point this mapping to a ODIRestfulWebService component and provide endpoint URL and select the operation inside the HTTP analyzer. Will the data is going to get inserted/updated?

    Appreciate quick help.

    Thanks,
    Venkatesh

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete