Pages

Wednesday, 16 April 2014

Using an external Web service (Amazon Product Advertising API) to display new book releases in ADF Mobile

Usecase:
In this blogpost, I will show you how you can consume the Amazon Product Advertising API in ADF Mobile and display the list of new book releases.

Pre-requisites:
JDeveloper 11.1.2.4.0 with Mobile extension. Some basic knowledge on the Amazon product advertising API can be obtained here.

Background:
I was recently trying to use the Amazon product advertising API to fetch the list of new book releases and display them in ADF Mobile. Amazon requires that anyone using their product advertising API must be registered as an Amazon Associate. Once you register as an Amazon associate, you get a unique associate tag. You would also require an AWS Access Key ID and secret to make calls to the Amazon web service. The details to get these are here. Once you have the keys, calls to the Product Advertising API to fetch new book releases can be made using the HTTP request like:
http://webservices.amazon.com/onca/xml?AWSAccessKeyId=######&AssociateTag=###&BrowseNodeId=283155&Operation=BrowseNodeLookup&ResponseGroup=NewReleases&Service=AWSECommerceService&Timestamp=2014-03-11T10%3A30%3A18.000Z&Version=2011-08-01&Signature=SpteidNFq2wuq15YxU2cRUEB%2BQl7dUx6ARAVhQQP%2FcM%3D

where the Timestamp and Signature are generated by Amazon for each request.
The request of this form will return ASIN and Titles of the most recently released books.
Amazon provides java code that takes care of generating the timestamp and signature for each request. However, the signature code utilizes HMAC-SHA 256 for authentication. ADF Mobile uses JDK 1.4 and unfortunately, JDK 1.4 has no support for HMAC-SHA 256. After some looking around and trying out different things, I came across http://apisigning.com/. Amazon API signing is a provision through which requests can be sent to apisigning and they internally take care of generating the signature and timestamp, and redirect you to the final URL like the one above. In this case, you can make calls to the Amazon API using the URL like:

http://free.apisigning.com/onca/xml?AWSAccessKeyId=###&Service=AWSECommerceService&Operation=BrowseNodeLookup&BrowseNodeId=283155&ResponseGroup=NewReleases&Version=2011-08-01&AssociateTag=###

Here, notice that we are making a call to free.apisigning.com. Internally, it will contact the Amazon server, sign & provide the timestamp to the request, and redirect to the webservice.amazon.com URL.

Approach:
Since we need to get the list of newly released books before the app loads, we will call the API through a java class. In the java class, we will first call api.signing.com, get the final URL with signature and timestamp, parse the response and store the response in an array. We then expose the java class as a bean DC. Once we have the data control, its the normal drag and drop approach to design the AMX page.

Steps:
1. Create an ADF Mobile application. Since we are interested in the ASIN and Title fields of the response, we create a class called NewReleases.java with these two fields and their corresponding getters and setters. This is done in the Model project.
package newReleasePack;

public class NewReleases {
    private String ASIN;
    private String Title;
    public NewReleases() {
        super();
    }
    
    public void setASIN(String ASIN) {
        this.ASIN = ASIN;
    }

    public String getASIN() {
        return ASIN;
    }

    public void setTitle(String Title) {
        this.Title = Title;
    }

    public String getTitle() {
        return Title;
    }
    
}
2. The class which will be converted to DC is named NewReleaseDCClass.java.
package newReleasePack;

import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import oracle.adfmf.dc.ws.rest.RestServiceAdapter;
import oracle.adfmf.framework.api.Model;
import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParser;
import sun.misc.Service;
public class NewReleaseDCClass {

    private static List newReleasesList = new ArrayList();
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    public NewReleaseDCClass() {
        super();
    }
    public void setNewReleasesList(List newReleasesList) {
        List oldNewReleasesList = NewReleaseDCClass.newReleasesList;
        NewReleaseDCClass.newReleasesList = newReleasesList;
        propertyChangeSupport.firePropertyChange("newReleasesList", oldNewReleasesList, newReleasesList);
    }

    public static List getNewReleasesList() {
        return newReleasesList;
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public NewReleases[] allNewReleases() {
        String response = "";
        try {
            HttpURLConnection ucon = null;
            URL url =
                new URL("http://free.apisigning.com/onca/xml?AWSAccessKeyId=####&Service=AWSECommerceService&Operation=BrowseNodeLookup&BrowseNodeId=283155&ResponseGroup=NewReleases&Version=2011-08-01&AssociateTag=####");
            ucon = (HttpURLConnection)url.openConnection();
            ucon.setFollowRedirects(false); //to stop auto redirect
            ucon.setInstanceFollowRedirects(false); //to stop auto redirect
            ucon.connect();
            System.out.println("Connected to free signing");
            String newUrlLoc = ucon.getHeaderField("Location"); //extract the value of Location header which contains the new URL with timestamp & signature
            System.out.println(" After  getting response code");
            ucon.disconnect(); //disconnect from free.apisigning.com
            System.out.println("After  disconnect ");
            URL newUrl = new URL(newUrlLoc); //connect to new URL to get the query parameters
            String requestQuery = newUrl.getQuery(); // get the query parameters
            
            RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
            restServiceAdapter.clearRequestProperties();
            restServiceAdapter.setConnectionName("conn"); //URL connection to http://webservices.amazon.com/onca/xml
            restServiceAdapter.setRequestURI("?"+requestQuery); // appending the parameters with timestamp & signature
            restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
            restServiceAdapter.addRequestProperty("Content-Type", "text/xml;charset=UTF-8");
            restServiceAdapter.addRequestProperty("Accept", "application/xml");
            restServiceAdapter.setRetryLimit(0);
            response = restServiceAdapter.send("");
            System.out.println("Response received!" + response);
            
            //parsing the response. Refer http://stick2code.blogspot.com/2014/04/xml-parsing-in-adf-mobile.html for explanation
            KXmlParser parser = new KXmlParser(); //create a parser instance
            Reader stream = new StringReader(response);
            parser.setInput(stream);
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "BrowseNodeLookupResponse");
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "OperationRequest");
            parser.skipSubTree();
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "BrowseNodes");
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "Request");
            parser.skipSubTree();
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "BrowseNode");
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "BrowseNodeId");
            parser.nextText();
            parser.require(XmlPullParser.END_TAG, null, "BrowseNodeId");
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "Name");
            parser.nextText();
            parser.require(XmlPullParser.END_TAG, null, "Name");
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "NewReleases");
            while (parser.nextTag() != XmlPullParser.END_TAG) {
                parser.require(XmlPullParser.START_TAG, null, "NewRelease");
                NewReleases newRelaseObj = new NewReleases();
                while (parser.nextTag() != XmlPullParser.END_TAG) {
                    String tagName = parser.getName();
                    if (tagName.equals("ASIN")) {
                        String asin = parser.nextText();
                        newRelaseObj.setASIN(asin);
                    } else if (tagName.equals("Title")) {
                        String title = parser.nextText();
                        newRelaseObj.setTitle(title);
                    }
                    parser.require(XmlPullParser.END_TAG, null, tagName);
                }
                newReleasesList.add(newRelaseObj);
                parser.require(XmlPullParser.END_TAG, null, "NewRelease");
            }
            parser.require(XmlPullParser.END_TAG, null, "NewReleases");
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "TopItemSet");
            parser.skipSubTree();
            parser.nextTag();
            parser.require(XmlPullParser.END_TAG, null, "BrowseNode");
            parser.nextTag();
            parser.require(XmlPullParser.END_TAG, null, "BrowseNodes");
            parser.nextTag();
            parser.require(XmlPullParser.END_TAG, null, "BrowseNodeLookupResponse");
            NewReleases[] newRel = (NewReleases[])newReleasesList.toArray(new NewReleases[newReleasesList.size()]); //store the ASIN and titles in an array
            return newRel;
        } catch (InvalidKeyException e) {
            System.out.println("InvalidKeyException" + e);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("NoSuchAlgorithmException" + e);
        } catch (UnsupportedEncodingException e) {
            System.out.println("UnsupportedEncodingException" + e);
        } catch (Exception e) {
            System.out.println("Exception" + e);
            System.out.println(e.getStackTrace());
            e.printStackTrace();
        }
        return null;
    }
}

Here, the important method is allNewReleases(). In this, we are creating a HttpURLConnection to freesigning API. Since this URL redirects the request to webservices.amazon.com after appending the timestamp & signature, the HTTP response of this request is 302 and the Location header contains the new request URL. We are picking up this new URL using getHeaderField.
We are then getting the query parameters from this new URL and using restServiceAdapter.setRequestURI to set the parameters to the request. Note that the connection has been created to the URL http://webservices.amazon.com/onca/xml. This will enable us to use RestServiceAdapter for the main request. After setting other headers(done before the try block in my code), the request is sent. On receiving the response, the response is parsed and set into an array. For parsing the XML response, refer my previous blog.
3. Next, we create Bean DC out of this class, by right clicking on the class and selecting 'Create Data Control'.


4. Drop NewReleases under allNewReleases in DC palette as ADF Form. Also drop the navigation buttons and the app is ready.
Below is a snapshot. I have also used a listView to show the new releases.


Complete app can be found here. Don't forget to replace the AWSAccessKeyId and AssociateTag with the values generated for you in NewReleaseDCClass.java.

No comments:

Post a Comment