This is the fifth tutorial of Nano tutorial series, in this tutorial, I will show you how to integrate Nano with Amazon Product Advertising API, if you are not familiar with this API, just have a quick review on its official site, basically, the Product Advertising API provides programmatic access to Amazon’s product selection and discovery functionality so that developers like you can advertise Amazon products to monetize your website. In this tutorial, I will show you how to add custom SOAP header which is required by the authentication of Amazon Product Advertising API.
NOTE
Per Amazon:
You will not, without our express prior written approval, use any Product Advertising Content on or in connection with any site or application designed or intended for use with a mobile phone or other handheld device.
So please consult Amazon for permission before you can use its Product Advertising Content on any Android devices.
Depends on the network speed, you may need to wait a few moments to let the code generator download the wsdl and generate code, you may also download the wsdl and run the code generator with a local wsdl.
Step 2 - Create New Android Project, Add Nano Library and Generated Proxy into Your Project
Create a simple Android project called HelloAmazonProductAdvertising in eclipse(or other IDE you prefer), then:
Now build the project to ensure it can build successfully.
The code generation will generate both SOAP and XML based interfaces from Amazon Product Advertising wsdl for us,
since we will use SOAP based interface in this tutorial, you may now review the generated Amazon Product Advertising SOAP interface(in the generated client sub-folder) to learn what kind of functions are provided by Amazon Product Advertising service, and what kind of parameters are needed to call the service, the interface is posted below:
// Generated by wsdl compiler for android/java// DO NOT CHANGE!packagecom.amazon.webservices.awsecommerceservice._2011_08_01.client;importcom.leansoft.nano.ws.SOAPServiceCallback;importcom.leansoft.nano.ws.NanoSOAPClient;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartClear;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartModify;importcom.amazon.webservices.awsecommerceservice._2011_08_01.SimilarityLookup;importcom.amazon.webservices.awsecommerceservice._2011_08_01.SimilarityLookupResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.ItemSearch;importcom.amazon.webservices.awsecommerceservice._2011_08_01.ItemSearchResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartClearResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartGet;importcom.amazon.webservices.awsecommerceservice._2011_08_01.ItemLookup;importcom.amazon.webservices.awsecommerceservice._2011_08_01.BrowseNodeLookup;importcom.amazon.webservices.awsecommerceservice._2011_08_01.ItemLookupResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartAdd;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartAddResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartGetResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartCreate;importcom.amazon.webservices.awsecommerceservice._2011_08_01.BrowseNodeLookupResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartCreateResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.CartModifyResponse;/** This class is the SOAP client to the AWSECommerceServicePortType Web Service.*/publicclassAWSECommerceServicePortType_SOAPClientextendsNanoSOAPClient{/** public method */publicvoiditemSearch(ItemSearchrequestObject,SOAPServiceCallback<ItemSearchResponse>serviceCallback){super.invoke(requestObject,serviceCallback,ItemSearchResponse.class);}/** public method */publicvoiditemLookup(ItemLookuprequestObject,SOAPServiceCallback<ItemLookupResponse>serviceCallback){super.invoke(requestObject,serviceCallback,ItemLookupResponse.class);}/** public method */publicvoidbrowseNodeLookup(BrowseNodeLookuprequestObject,SOAPServiceCallback<BrowseNodeLookupResponse>serviceCallback){super.invoke(requestObject,serviceCallback,BrowseNodeLookupResponse.class);}/** public method */publicvoidsimilarityLookup(SimilarityLookuprequestObject,SOAPServiceCallback<SimilarityLookupResponse>serviceCallback){super.invoke(requestObject,serviceCallback,SimilarityLookupResponse.class);}/** public method */publicvoidcartGet(CartGetrequestObject,SOAPServiceCallback<CartGetResponse>serviceCallback){super.invoke(requestObject,serviceCallback,CartGetResponse.class);}/** public method */publicvoidcartCreate(CartCreaterequestObject,SOAPServiceCallback<CartCreateResponse>serviceCallback){super.invoke(requestObject,serviceCallback,CartCreateResponse.class);}/** public method */publicvoidcartAdd(CartAddrequestObject,SOAPServiceCallback<CartAddResponse>serviceCallback){super.invoke(requestObject,serviceCallback,CartAddResponse.class);}/** public method */publicvoidcartModify(CartModifyrequestObject,SOAPServiceCallback<CartModifyResponse>serviceCallback){super.invoke(requestObject,serviceCallback,CartModifyResponse.class);}/** public method */publicvoidcartClear(CartClearrequestObject,SOAPServiceCallback<CartClearResponse>serviceCallback){super.invoke(requestObject,serviceCallback,CartClearResponse.class);}}
All the methods in the interface follow same calling paradigm - you call the service with required request object and a callback object implementing interface SOAPServiceCallback.
Step 3 - Implement Appliction Logic and UI, Call Proxy to Invoke Web Service as Needed.
packagecom.amazon.service.ecommerce;importjava.io.UnsupportedEncodingException;importjava.security.InvalidKeyException;importjava.security.NoSuchAlgorithmException;importjava.text.SimpleDateFormat;importjava.util.ArrayList;importjava.util.Calendar;importjava.util.List;importjava.util.TimeZone;importjavax.crypto.Mac;importjavax.crypto.spec.SecretKeySpec;importjavax.xml.parsers.DocumentBuilder;importjavax.xml.parsers.DocumentBuilderFactory;importjavax.xml.parsers.ParserConfigurationException;importorg.w3c.dom.Document;importorg.w3c.dom.Element;importcom.amazon.webservices.awsecommerceservice._2011_08_01.client.AWSECommerceServicePortType_SOAPClient;importcom.leansoft.nano.util.Base64;publicclassAWSECommerceClient{/** Update url according to your local location, see a list of supported location at the end of the wsdl: http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl */publicstaticStringAWSCEServiceURLString="https://webservices.amazon.com/onca/soap?Service=AWSECommerceService";/** Use this to specify the AWS Access Key ID */publicstaticStringAWSAccessKeyId="YOUR ACCESS KEY HERE";/** Use this to specify the AWS Secret Key */publicstaticStringAWSSecureKeyId="YOUR SECURE KEY HERE";/** Namespace for all AWS Security elements */publicstaticfinalStringAuthHeaderNS="http://security.amazonaws.com/doc/2007-01-01/";privatestaticAWSECommerceServicePortType_SOAPClientclient=null;/** Algorithm used to calculate string hashes */privatestaticfinalStringSIGNATURE_ALGORITHM="HmacSHA256";privatestaticMacmac=null;publicstaticAWSECommerceServicePortType_SOAPClientgetSharedClient(){if(client==null){synchronized(AWSECommerceClient.class){if(client==null){client=newAWSECommerceServicePortType_SOAPClient();client.setEndpointUrl(AWSCEServiceURLString);// init securitytry{byte[]bytes=AWSSecureKeyId.getBytes("UTF-8");SecretKeySpeckeySpec=newSecretKeySpec(bytes,SIGNATURE_ALGORITHM);mac=Mac.getInstance(SIGNATURE_ALGORITHM);mac.init(keySpec);}catch(UnsupportedEncodingExceptione){thrownewRuntimeException(e);}catch(NoSuchAlgorithmExceptione){thrownewRuntimeException(e);}catch(InvalidKeyExceptione){thrownewRuntimeException(e);}}}}returnclient;}/** Authentication of SOAP request see details here: http://docs.aws.amazon.com/AWSECommerceService/latest/DG/NotUsingWSSecurity.html */publicstaticvoidauthenticateRequest(Stringaction){Stringtimestamp=getTimestamp();Stringsignature=calculateSignature(action,timestamp);List<Object>securityHeaders=newArrayList<Object>();Documentdocument=getDocument();ElementaccessKeyElement=document.createElementNS(AuthHeaderNS,"AWSAccessKeyId");accessKeyElement.appendChild(document.createTextNode(AWSAccessKeyId));securityHeaders.add(accessKeyElement);ElementtimestampElement=document.createElementNS(AuthHeaderNS,"Timestamp");timestampElement.appendChild(document.createTextNode(timestamp));securityHeaders.add(timestampElement);ElementsignatureElement=document.createElementNS(AuthHeaderNS,"Signature");signatureElement.appendChild(document.createTextNode(signature));securityHeaders.add(signatureElement);client.setCustomSOAPHeaders(securityHeaders);}/** * Calculates a time stamp from "now" in UTC and returns it in ISO8601 string * format. The soap message expires 15 minutes after this time stamp. * AWS only supports UTC and it's canonical representation as 'Z' in an * ISO8601 time string. E.g. 2008-02-10T23:59:59Z * * See http://www.w3.org/TR/xmlschema-2/#dateTime for more details. * * @return ISO8601 time stamp string for "now" in UTC. */privatestaticStringgetTimestamp(){Calendarcalendar=Calendar.getInstance();SimpleDateFormatis08601=newSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");is08601.setTimeZone(TimeZone.getTimeZone("UTC"));returnis08601.format(calendar.getTime());}/** * Method borrowed from AWS example code at http://developer.amazonwebservices.com/ * @param action The single SOAP body element that is the action the * request is taking. * @param timestamp The time stamp string as provided in the <aws:Timestamp> * header element. * @return A hash calculated according to AWS security rules to be provided in the * <aws:signature> header element. * @throws Exception If there were errors or missing, required classes when * trying to calculate the hash. */privatestaticStringcalculateSignature(Stringaction,Stringtimestamp){StringtoSign=(action+timestamp);byte[]sigBytes=mac.doFinal(toSign.getBytes());returnnewString(Base64.encode(sigBytes));}/** * Get a W3C XML Document * * @return a document */privatestaticDocumentgetDocument(){try{DocumentBuilderFactorydbf=DocumentBuilderFactory.newInstance();dbf.setNamespaceAware(true);DocumentBuilderdb=dbf.newDocumentBuilder();returndb.newDocument();}catch(ParserConfigurationExceptione){thrownewRuntimeException("Failed to create DocumentBuilder!",e);}}}
The logic to build shared client is a little complex, let me give more comments:
Amazon Product Advertising API requires per-call request authentication, see details here, the authentication logic is implemented in the public static void authenticateRequest(String action) method.
To make the authentication work, you need to fill in your AWSAccessKeyId and AWSSecureKeyId in the shared client, if you are a registered Amazon Product Advertising API developer, you can get these keys from your account on Amazon developer site.
The authentication info is added in the SOAP request as SOAP header, to add SOAP header, we use Document(W3C DOM built in Android) to build header elements, populate the elements with authentication information, then add the header element list on the client, at runtime, Nano will add these elements to the SOAP request message header.
The authentication needs to be done on per-call basis, means everytime you call an Amazon Product Advertising service, you need to authenticate the request before the service call, see sample later.
Now the UI part and application logic, for this hello world like sample, we just need an EditText for book keyword input and a Button to trigger Amazon book search by invoking listening method onClick which will indirectly call Amazon Product Advertising itemSearch API through the proxy, fairly simple, see the full application logic in MainActivity class below:
packagecom.leansoft.nano.sample;importjava.util.ArrayList;importcom.amazon.service.ecommerce.AWSECommerceClient;importcom.amazon.webservices.awsecommerceservice._2011_08_01.Errors;importcom.amazon.webservices.awsecommerceservice._2011_08_01.Item;importcom.amazon.webservices.awsecommerceservice._2011_08_01.ItemSearch;importcom.amazon.webservices.awsecommerceservice._2011_08_01.ItemSearchRequest;importcom.amazon.webservices.awsecommerceservice._2011_08_01.ItemSearchResponse;importcom.amazon.webservices.awsecommerceservice._2011_08_01.Items;importcom.amazon.webservices.awsecommerceservice._2011_08_01.client.AWSECommerceServicePortType_SOAPClient;importcom.leansoft.nano.ws.SOAPServiceCallback;importandroid.os.Bundle;importandroid.app.Activity;importandroid.view.View;importandroid.view.View.OnClickListener;importandroid.widget.Button;importandroid.widget.EditText;importandroid.widget.Toast;publicclassMainActivityextendsActivity{@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButtonsearchButton=(Button)this.findViewById(R.id.search_button);searchButton.setOnClickListener(newOnClickListener(){@OverridepublicvoidonClick(Viewarg0){// Get shared clientAWSECommerceServicePortType_SOAPClientclient=AWSECommerceClient.getSharedClient();client.setDebug(true);// Build requestItemSearchrequest=newItemSearch();request.associateTag="teg";// seems any tag is okrequest.shared=newItemSearchRequest();request.shared.searchIndex="Books";request.shared.responseGroup=newArrayList<String>();request.shared.responseGroup.add("Images");request.shared.responseGroup.add("Small");ItemSearchRequestitemSearchRequest=newItemSearchRequest();itemSearchRequest.title=((EditText)findViewById(R.id.keyword_input)).getText().toString();request.request=newArrayList<ItemSearchRequest>();request.request.add(itemSearchRequest);// authenticate the request// http://docs.aws.amazon.com/AWSECommerceService/latest/DG/NotUsingWSSecurity.htmlAWSECommerceClient.authenticateRequest("ItemSearch");// Make API call and register callbacksclient.itemSearch(request,newSOAPServiceCallback<ItemSearchResponse>(){@OverridepublicvoidonSuccess(ItemSearchResponseresponseObject){// success handling logicif(responseObject.items!=null&&responseObject.items.size()>0){Itemsitems=responseObject.items.get(0);if(items.item!=null&&items.item.size()>0){Itemitem=items.item.get(0);Toast.makeText(MainActivity.this,item.itemAttributes.title,Toast.LENGTH_LONG).show();}else{Toast.makeText(MainActivity.this,"No result",Toast.LENGTH_LONG).show();}}else{if(responseObject.operationRequest!=null&&responseObject.operationRequest.errors!=null){Errorserrors=responseObject.operationRequest.errors;if(errors.error!=null&&errors.error.size()>0){com.amazon.webservices.awsecommerceservice._2011_08_01.errors.Errorerror=errors.error.get(0);Toast.makeText(MainActivity.this,error.message,Toast.LENGTH_LONG).show();}else{Toast.makeText(MainActivity.this,"No result",Toast.LENGTH_LONG).show();}}else{Toast.makeText(MainActivity.this,"No result",Toast.LENGTH_LONG).show();}}}@OverridepublicvoidonFailure(Throwableerror,StringerrorMessage){// http or parsing errorToast.makeText(MainActivity.this,errorMessage,Toast.LENGTH_LONG).show();}@OverridepublicvoidonSOAPFault(ObjectsoapFault){// soap faultcom.leansoft.nano.soap11.Faultfault=(com.leansoft.nano.soap11.Fault)soapFault;Toast.makeText(MainActivity.this,fault.faultstring,Toast.LENGTH_LONG).show();}});}});}}
More comments to the serivce call code:
I’ve added comments in the code so the whole service call flow should be easy to understand.
Before service call, the request must be authenticated by calling the authenticateRequest method, such as AWSECommerceClient.authenticateRequest("ItemSearch"), ItemSearch is the target action or API name.
In the success handling logic, we just show the title of the first result item.
In onSOAPFault failure handling logic, since we are using SOAP 1.1, we need to cast the soapFault object to type of com.leansoft.nano.soap11.Fault and handle it accordingly.
Amazon Product Advertising service supports response resident error(RRE), so even we get success response, we still need to check response for resident error and handle it accordingly.
Final Step - Run the Demo
Let’s run the demo in Android simulator, see a screen shot below:
If you forget to fill in your AWS Access Key and Secure Key in the shared client, then you will get error like below:
This is just a bare minimum Amazon Product Advertising service based application, for a demo with more functions, please see the AmazonBookFinder sample in the sample\webservice folder of Nano source, AmazonBookFinder calls following APIs in one app:
itemSearch for book search
cartCreate to add chosen book into shopping cart
Below is a few screen shots:
Search:
Details:
Add To Cart:
Now it’s your turn to create Android applications based on Amazon Product Advertising web service, see your next great service based app.