Amazon Products API è un servizio di integrazione messo a disposizione da Amazon per l'accesso alle funzionalità di ricerca e selezione dei prodotti presenti all'interno della base dati di Amazon, così da renderle facilmente utilizzabili all'interno di una nostra Web application. In questo articolo faremo due semplici esempi di utilizzo di questo servizio con Java.
L'articolo dedicato ai servizi RESTful in Java ci ha fatto vedere un paradigma di integrazione che negli ultimi anni è stato ampiamente adottato soprattutto per le applicazioni di tipo "social" in ambiente Web. L'uso di API di terze parti si è andato cementando nello sviluppo web oriented ed è diventato praticamente un must per la maggior parte di siti.
I vari servizi di integrazione di Facebook, Twitter, Google sono solo la punta dell'iceberg ed ogni nuova applicazione che si rispetti deve tenere in considerazione l'integrazione con i servizi utilizzati quotidianamente dalla maggior parte di utenti internet. Le prime API di integrazione però vengono da molto più lontano: prima ancora della moda "social networking" c'è stata una esigenza ben più radicata negli anni, l'e-commerce.
Ebay ed Amazon, infatti sono probabilmente le pioniere nell'offrire servizi di integrazione per la semplice ragione di avere un modello di business ben chiaro: vendere prodotti. In particolare in questo articolo esaminiamo l'API di Amazon ed il suo utilizzo da linguaggio Java. Vedremo come utilizzando opportunamente tale API potremo navigare il vastissimo catalogo del leader di e-commerce mondiale, recuperando descrizioni di prodotti, recensioni, immagini ed infine avere la possibilità di guadagnare sull'eventuale vendita generata dal nostro sito.
Amazon (come il resto di big internazionali del settore internet) dà l'opportunità di utilizzare un interfaccia REST o WSDL, ma, per noi tale differenza non esisterà affatto. Infatti ci preoccuperemo di utilizzare gli strumenti di Java 1.6 per generare tutta l'infrastruttura e utilizzare il servizio in stile RPC (Remote Procedure Call), senza preoccuparci minimamente di cosa avviene dietro le quinte. Il paradigma che andremo ad illustrare ben si presta ad integrazioni off-line ed asincrone, dove vedremo come recuperare le informazioni di un dato prodotto o ricercare nel database prodotti in base a dei determinati criteri di ricerca.
Amazon Products API (ex Amazon Web Service AWS)
Quando parliamo del servizio di integrazione di Amazon, dobbiamo fare un po' di attenzione. La Amazon Products API, infatti, prima era nota più genericamente come Amazon Web Services. Nel 2009, l'azienda ha deciso di cambiarne il nome per poter utilizzare il secondo per la sua suite di servizi di cloud computing (che non ci interessa allo scopo dell'articolo).
Per poter utilizzare il servizio dobbiamo quindi creare un account al seguente indirizzo:
Come possiamo vedere, la stessa Amazon pubblicizza il suo servizio elencando le ragioni per cui bisognerebbe usare la API:
- Accedere ai prodotti del catalogo
- Fare leva sulle funzioni di ricerca dei prodotti
- Guadagnare dalla vendita dei prodotti
Oggettivamente sono delle ottime ragioni, quindi procediamo con la creazione di un account. La creazione dell'account è necessaria per generare i codici di sicurezza che utilizzeremo successivamente nell'accedere al sistema Amazon. Si tratta di AccessKey e SecretKey. Inoltre, vedremo nell'esempio, sarà utile determinare il tag con cui sponsorizzerete i prodotti (per poter essere identificati come merchant e guadagnarci sulla vendita): in questo caso dovrete recuperare il tag della vostra affiliazione.
Adesso abbiamo tutti gli elementi per procedere allo sviluppo di un'applicazione.
Data Model
Come abbiamo detto, lo sviluppatore viene schermato dai meccanismi di comunicazione del web service e si concentra nello sviluppo della logica applicativa che, ricordiamo, è puramente JSE, quindi potrà essere utilizzata come standalone o da Application Server JEE. Unica limitazione è la necessità di usare una versione Java superiore alla 1.6 (inclusa).
Tale limitazione è dovuta al fatto che la costruzione del datamodel passa per un tool chiamato wsimport, che si basa su JAX-WS e consente un meccanismo di trasformazione WSDL-Java POJO potente e diretto astraendo tutta la complessità della comunicazione remota tra il client (la nostra applicazione) e il server (il sistema Amazon).
La sintassi del wsimport
è molto semplice:
wsimport -p package http://service.xyz/serv?wsdl
Con questo semplice comando chiediamo al tool di creare un package con le classi e la struttura recuperata dal WSDL in oggetto. Stessa cosa dovremo fare per creare il modello dati che poi useremo per interrogare il sistema Amazon. Quindi, per prima cosa recuperiamo il WSDL del servizio che si trova all'indirizzo http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsdl
Molto interessante da notare che il team di sviluppo Amazon lavora su diverse versioni dello stesso WSDL e si occupa di modificarne il targetNamespace in funzione della più recente data di rilascio:
...
<definitions targetNamespace="http://webservices.amazon.com/AWSECommerceService/2010-12-01">
<types>
<xs:schema targetNamespace="http://webservices.amazon.com/AWSECommerceService/2010-12-01" elementFormDefault="qualified">
<xs:element name="Bin">
...
In questo caso vediamo che stiamo lavorando con la versione rilasciata il 01 Dicembre 2010. Prima di procedere alla creazione delle classi dobbiamo aggiungere un commento relativo al cosiddetto "Wrapper Style", che, in base all'IDE utilizzato deve essere disattivato. In particolare se usate Eclipse dovrete creare un file xml con la seguente logica:
<jaxws:bindings wsdlLocation="http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsdl" xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
<jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
</jaxws:bindings>
Chiameremo tale file javaws-custom.xml. In caso usiate Netbeans o altri IDEs che supportano il wrapping, tale file risulterà superfluo.
Il passo seguente è quindi usare il wsimport
sul WSDL di Amazon e useremo come package com.ECS.client.jax
(che è il default proposto dalla stessa Amazon). Aggiungeremo inoltre l'uso del wrapper style creato in precedenza (omettere il comando nel caso usiate Netbeans come IDE).
wsimport -d ./build -s ./src -p com.ECS.client.jax http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsdl -b jaxws-custom.xml
Le directory di build o di source conterranno alla fine del processo le classi compilate e i sorgenti rispettivamente: aprendole potrete quindi vedere le decine di classi che automaticamente sono state create, e che rappresentano il layer di data model che useremo nel prossimo paragrafo per effettuare operazioni di logica applicativa. Prendete particolare confidenza con l'oggetto Item che sarà il punto chiave della nostra futura applicazione (di seguito un estratto delle proprietà dell'oggetto):
...
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"asin",
"parentASIN",
"errors",
"detailPageURL",
"itemLinks",
"salesRank",
"smallImage",
"mediumImage",
"largeImage",
"imageSets",
"itemAttributes",
"merchantItemAttributes",
"variationAttributes",
"relatedItems",
"collections",
"subjects",
"offerSummary",
"offers",
"variationSummary",
"variations",
"customerReviews",
"editorialReviews",
"similarProducts",
"accessories",
"tracks",
"browseNodes",
"tags",
"listmaniaLists",
"searchInside",
"alternateVersions"
})
@XmlRootElement(name = "Item")
public class Item {
...
Business Logic
Adesso vediamo come usare il layer visto in precedenza per poter integrare i servizi di Amazon con la nostra web application. Il concetto di integrazione è abbastanza generale e vale per qualsiasi tipo di contesto. Evidentemente se la vostra applicazione è orientata ad un business specifico, sarà opportuno modellare ulteriormente e adattare i contenuti provenienti da Amazon (che sono tantissimi) e quelli della vostra applicazione. Immaginate per esempio di gestire un sito web dedicato ai libri o uno dedicato a articoli sportivi: evidentemente le informazioni di dettaglio che cercherete potrebbero variare nell'uno o nell'altro caso.
Prima di passare ad un esempio concreto però, dobbiamo aggiungere una classe, necessaria ad effettuare l'autenticazione ai servizi di Amazon. Dall'Agosto 2009, infatti, l'accesso all'API è vincolato da un sistema di autenticazione cifrato. A tal proposito introduciamo una nuova classe nel package com.ECS.client.jax.ext, la classe AwsHandlerResolver che estenda da HandlerResolver:
package com.ECS.client.jax.ext;
import ...
public class AwsHandlerResolver implements HandlerResolver {
private String awsSecretKey;
public AwsHandlerResolver(String awsSecretKey) {...}
@SuppressWarnings("unchecked")
public List getHandlerChain(PortInfo portInfo) {...}
private static class AwsHandler implements SOAPHandler {
private byte[] secretBytes;
public AwsHandler(String awsSecretKey) {...}
public void close(MessageContext messagecontext) {...}
public Set getHeaders() {...}
public boolean handleFault(SOAPMessageContext messagecontext) {...}
public boolean handleMessage(SOAPMessageContext messagecontext) {...}
private String getSignature(String operation, String timeStamp, byte[] secretBytes) {...}
private String getTimestamp() {...}
private byte[] stringToUtf8(String source) {...}
private void appendTextElement(Node node, String elementName, String elementText) {...}
}
}
visualizza il sorgente della classe AwsHandlerResolver
Per poter far funzionare tale classe è necessario scaricare la libreria commons-codec-1.4.jar
Sfortunatamente il team di Amazon non dà nessun riferimento a tal proposito in quanto suppongo lasci allo sviluppatore l'onere di creare un proprio handler e relativa logica di cifratura. Adesso però davvero saremo in grado di fare la nostra richiesta!
Per fare ciò, creiamo una classe di nome AWSCaller, il cui scopo è quello di effettuare l'autenticazione al sistema e gestire tutta la logica applicativa di cui abbiamo bisogno, per semplicità due metodi di ricerca che stampano a video alcune informazioni dei prodotti (qui lascio a voi il compito di sviluppare logiche più aderenti alle vostre necessità).
package it.html.aws.test;
...
public class AWSCaller{
//Indice dove effettuare la ricerca
private static String INDEX = "Books";
//Chiavi di accesso
private String AWSAccessKey;
private String AWSAssociateTag;
/*
* Bridge ai servizi SOAP, attraverso tali classi avremo accesso a tutte
* le funzioni di Amazon Product API
*/
AWSECommerceService service;
AWSECommerceServicePortType port;
//Costruttore
public AWSCaller(String AWSAccessKey, String AWSSecretKey, String AWSAssociateTag){
this.AWSAccessKey = AWSAccessKey;
this.AWSAssociateTag = AWSAssociateTag;
/*
* Inizializziamo il sistema: per poter funzionare dobbiamo associare
* l'handler ad-hoc creato in precedenza
*/
service = new AWSECommerceService();
service.setHandlerResolver(new AwsHandlerResolver(AWSSecretKey));
port = service.getAWSECommerceServicePort();
}
...
I commenti nel codice sono abbastanza auto esplicativi. Attraverso il costruttore creiamo tutti gli oggetti di cui abbiamo bisogno per poter accedere al sistema Amazon, in particolare l'oggetto di tipo AWSECommerceServicePortType (port) che è il vero gateway verso il mondo Amazon.
Il main si comporrà quindi di una semplice chiamata verso due metodi, lookup e search:
...
public static void main (String args[]) throws Exception{
String AWSAccessKey = "YOUR ACCESSKEY";
String AWSSecretKey = "YOUR SECRETKEY" ;
String AWSAssociateTag = "youraffiliatetag-20";
AWSCaller aws = new AWSCaller(AWSAccessKey, AWSSecretKey, AWSAssociateTag);
//Cerca l'oggetto 1930235321 (un libro di Tim Powers)
System.out.println("################ ITEM LOOKUP ########");
aws.lookup("1930235321");
System.out.println("################ ITEM SEARCH ########");
//Cerca tutti i libri sui pirati
aws.search("Pirates");
}
...
Ovviamente, per poter funzionare dovrete cambiare le chiavi con le vostre credenziali (come illustrato nel secondo paragrafo dell'articolo).
Nella seconda parte dell'articolo implementeremo di due semplici casi d'uso: la richiesta di un prodotto noto e la richiesta di prodotti non noti a priori.
Scenario Lookup: richiesta di un prodotto noto
Entriamo nel dettaglio del primo metodo:
/*
* Metodo di ricerca. Attraverso l'ASIN (id univoco) recuperiamo
* delle informazioni su un prodotto.
*/
public void lookup(String asin) throws Exception{
//Crea l'oggetto per effettuare la richiesta
ItemLookupRequest ilr = new ItemLookupRequest();
//Definiamo la richiesta
List itemIds = new ArrayList();
itemIds.add(asin);
ilr.setItemId(itemIds);
/*
* Crea l'oggetto per effettuare la ricerca passandogli
* la chiave d'accesso ed il tag con cui verrà valorizzato il
* link per refereziare una vendita
*/
ItemLookup search = new ItemLookup();
search.setAWSAccessKeyId(AWSAccessKey);
search.setAssociateTag(AWSAssociateTag);
search.getRequest().add(ilr);
/*
* Effettua il servizio di 'lookup' e salva il contenuto
* nell'oggetto response
*/
ItemLookupResponse response = port.itemLookup(search);
//Ci potrebbero essere più risultati in base alla richiesta fatta
//Più ASIN, per esempio
java.util.List itemssList = response.getItems();
//Processa il contenuto della risposta
processItems(itemssList);
}
L'input è un codice di testo, noto nel mondo Amazon come ASIN. Dal codice si può dedurre la logica che è piuttosto lineare. Utilizzando le classi create durante la procedura di costruzione del modello e attraverso il nostro gateway (l'oggetto port), effettuiamo una richiesta di lookup.
L'operazione di processing l'abbiamo delegata ad un ulteriore metodo in quanto la riutilizzeremo in seguito in varie occasioni. Qui, in realtà dovrete preoccuparvi di modellare il sistema in base alle vostre necessità, nel caso dell'esempio ci limitiamo a stampare a video alcune informazioni del prodotto.
...
/*
* Il metodo itera la collezione di risultati ottenuti e recupera alcune
* informazioni. Per semplicità le stampiamo a video, ma idealmente verranno
* salvate in un oggetto POJO e gestite in funzione della logica desiderata
*/
private void processItems(List itemssList) throws Exception{
//Error checking
if (itemssList==null || itemssList.size()==0 ){
throw new Exception("Nessun risultato trovato!!");
}
for (Items items : itemssList) {
java.util.List itemsList = items.getItem();
for (Item item : itemsList) {
/*
* Stampiamo a video alcune informazioni relative all'oggetto
*/
System.out.print("[#"+item.getASIN()+"]");
/*
* Le informazioni sono strutturate in funzione della tipologia
*/
ItemAttributes itemAttributes = item.getItemAttributes();
if (itemAttributes!=null){
System.out.print(" "+itemAttributes.getTitle());
Price listPrice = itemAttributes.getListPrice();
if(listPrice!=null){
System.out.print(" - "+listPrice.getFormattedPrice());
}
}
System.out.print(" - "+item.getDetailPageURL());
//... altri dati di cui avete bisogno
System.out.println();
}
}
}
...
Scenario Ricerca: richiesta di prodotti non noti
Il secondo scenario è molto simile al primo, nel senso che anche qui bisognerà definire una richiesta in funzione di un filtro che vogliamo applicare. Il caso della ricerca ha però sicuramente uno spettro di applicazione più ampio, in quanto la maggior parte delle occasioni utilizzeremo il sistema di Amazon proprio per cercare contenuti che ancora non conosciamo:
...
/*
* Il metodo di ricerca effettua una query al sistema in base alla keyword
* inserita. La logica è identica a lookup, ma cambiano le classi (ogetti
* search anzichè lookup)
*/
public void search(String keyword) throws Exception {
ItemSearchRequest itemRequest = new ItemSearchRequest();
/*
* Definiamo la pagina su cui cercare: ogni ricerca ci restituisce
* 10 risultati, quindi bisognerà sviluppare una logica di iterazione
* per recuperare tutti i risultati di una ricerca
*/
itemRequest.setItemPage(new BigInteger("1"));
itemRequest.setSearchIndex(AWSCaller.INDEX);
itemRequest.setKeywords(keyword);
ItemSearch search= new ItemSearch();
search.setAWSAccessKeyId(AWSAccessKey);
search.setAssociateTag(AWSAssociateTag);
search.getRequest().add(itemRequest);
ItemSearchResponse response = port.itemSearch(search);
java.util.List itemssList = response.getItems();
//Processa il contenuto della risposta
processItems(itemssList);
}
...
Il metodo è pressoché identico al metodo di lookup, con la differenza che qui avremo a che fare con oggetti diversi e, in un'implementazione reale, dovremo anche fare i conti con la logica di paginazione (Amazon limita infatti a 10 i risultati restituiti in ogni pagina).
Il risultato finale è comunque garantito, e lo illustriamo di seguito. Importante è sottolineare che grazie al codice di affiliazione, ogni utente che accederà con il link appena creato vi garantirà una percentuale sull'eventuale acquisto del prodotto.