blob: 158feab16efaa53685a03f6fcfc71dfa20bd5d74 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.taglibs.rdc.sampleapps.musicstore.ws;
import java.io.IOException;
import java.text.ParseException;
import java.util.Date;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Class representing an Amazon Web Services music store.
* Contains necessary AWS queries needed by the music store application.
*
* @author Jaroslav Gergic
* @author Rahul Akolkar
* @author Thomas Ling
*/
public class MusicStore {
protected static final String MERCHANT_ID = "Featured";
protected static final String XMLNS_NS = "http://www.w3.org/2000/xmlns/";
protected static final String RESPONSE_GRP = "ItemAttributes,Images,SalesRank,OfferSummary";
protected String subscriptionId;
private static Log log = LogFactory.getLog(MusicStore.class);
private static final boolean info = false;
/**
* @param subscriptionId Amazon ECS subscription ID
*/
public MusicStore(String subscriptionId) {
if (subscriptionId == null || subscriptionId.trim().length() == 0) {
String errDescription = "RDC MusicStore instantiated without an " +
"Amazon web services subscription ID. Read application " +
"README file.";
log.error(errDescription);
throw new IllegalArgumentException(errDescription);
}
this.subscriptionId = subscriptionId;
}
/**
* Retrurns an array of music genres (styles) listed on Amazon
*
* @param parent parent music style
* @return int[]
* @throws IOException
* @throws java.io.IOException
*/
public int[] getGenres(int parent) throws IOException {
int results[] = null;
StringBuffer sb = new StringBuffer();
sb.append("http://webservices.amazon.com/onca/xml");
sb.append("?Service=AWSECommerceService");
sb.append("&SubscriptionId=").append(subscriptionId);
sb.append("&Operation=BrowseNodeLookup");
sb.append("&BrowseNodeId=").append(parent);
String qs = sb.toString();
if (info) { log.info(qs); }
InputSource is = new InputSource(qs);
DocumentBuilder parser = getSimpleDOMBuilder();
if (parser != null) {
try {
Document doc = parser.parse(is);
Element root = (Element) doc.getDocumentElement();
//Amazon NS changes quite frequently, let's get the latest one
String ecs_ns = root.getAttribute("xmlns");
log.debug("xmlns = " + ecs_ns);
//Let's define a prefix required by XPath
root.setAttributeNS(XMLNS_NS, "xmlns:ecs", ecs_ns);
String xpath = "/ecs:BrowseNodeLookupResponse/ecs:BrowseNodes/ecs:BrowseNode/ecs:Children/ecs:BrowseNode";
NodeList lst = XPathAPI.selectNodeList(root, xpath, root);
int sz = lst.getLength();
results = new int[sz];
for (int i = 0; i < sz; i++) {
Element bnode = (Element) (lst.item(i));
NodeList nms = bnode.getElementsByTagNameNS(ecs_ns, "Name");
NodeList ids = bnode.getElementsByTagNameNS(ecs_ns,
"BrowseNodeId");
String name = xGetVal(nms.item(0));
String ndid = xGetVal(ids.item(0));
results[i] = Integer.parseInt(ndid);
}
} catch (Exception e) {
log.error("error parsing result set", e);
throw new IOException("error parsing result set");
}
}
return results;
}
/**
* @param genre
* @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
*/
public MusicAlbum[] getTopSellers(int genre) throws IOException {
StringBuffer sb = new StringBuffer();
sb.append("http://webservices.amazon.com/onca/xml");
sb.append("?Service=AWSECommerceService");
sb.append("&SubscriptionId=").append(subscriptionId);
sb.append("&MerchantId=").append(MERCHANT_ID);
sb.append("&Operation=").append("ItemSearch");
sb.append("&BrowseNode=").append(genre);
sb.append("&SearchIndex=").append("Music");
sb.append("&Condition=").append("New");
sb.append("&ResponseGroup=").append(RESPONSE_GRP);
sb.append("&Sort=").append("salesrank"); //Top Sellers
String qs = sb.toString();
if (info) { log.info(qs); }
return doAlbumQuery(qs, genre);
}
/**
* @param genre
* @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
*/
public MusicAlbum[] getNewReleases(int genre) throws IOException {
StringBuffer sb = new StringBuffer();
sb.append("http://webservices.amazon.com/onca/xml");
sb.append("?Service=AWSECommerceService");
sb.append("&SubscriptionId=").append(subscriptionId);
sb.append("&MerchantId=").append(MERCHANT_ID);
sb.append("&Operation=").append("ItemSearch");
sb.append("&BrowseNode=").append(genre);
sb.append("&SearchIndex=").append("Music");
sb.append("&Condition=").append("New");
sb.append("&ResponseGroup=").append(RESPONSE_GRP);
sb.append("&Sort=").append("-orig-rel-date"); //Reveresed Release Date
String qs = sb.toString();
if (info) { log.info(qs); }
return doAlbumQuery(qs, genre);
}
/**
* @param genre
* @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
*/
public MusicAlbum[] getFeaturedItems(int genre) throws IOException {
StringBuffer sb = new StringBuffer();
sb.append("http://webservices.amazon.com/onca/xml");
sb.append("?Service=AWSECommerceService");
sb.append("&SubscriptionId=").append(subscriptionId);
sb.append("&MerchantId=").append(MERCHANT_ID);
sb.append("&Operation=").append("ItemSearch");
sb.append("&BrowseNode=").append(genre);
sb.append("&SearchIndex=").append("Music");
sb.append("&Condition=").append("New");
sb.append("&ResponseGroup=").append(RESPONSE_GRP);
sb.append("&Sort=").append("psrank"); //Featured Items
String qs = sb.toString();
if (info) { log.info(qs); }
return doAlbumQuery(qs, genre);
}
/**
* @param album
* @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
*/
public MusicAlbum[] getSimilarItems(MusicAlbum album) throws IOException {
StringBuffer sb = new StringBuffer();
sb.append("http://webservices.amazon.com/onca/xml");
sb.append("?Service=AWSECommerceService");
sb.append("&SubscriptionId=").append(subscriptionId);
sb.append("&MerchantId=").append(MERCHANT_ID);
sb.append("&Operation=").append("SimilarityLookup");
sb.append("&ItemId=").append(album.getASIN());
sb.append("&SimilarityType=").append("Intersection");
sb.append("&ResponseGroup=").append(RESPONSE_GRP);
String qs = sb.toString();
if (info) { log.info(qs); }
MusicAlbum results[] = null;
InputSource is = new InputSource(qs);
DocumentBuilder parser = getSimpleDOMBuilder();
if (parser != null) {
try {
Document doc = parser.parse(is);
Element root = (Element) doc.getDocumentElement();
//Amazon NS changes quite frequently, let's get the latest one
String ecs_ns = root.getAttribute("xmlns");
log.debug("xmlns = " + ecs_ns);
//Let's define a prefix required by XPath
root.setAttributeNS(XMLNS_NS, "xmlns:ecs", ecs_ns);
//all items
String xpath = "/ecs:SimilarityLookupResponse/ecs:Items/ecs:Item";
NodeList items = XPathAPI.selectNodeList(root, xpath, root);
int sz = items.getLength();
results = new MusicAlbum[sz];
for (int i = 0; i < sz; i++) {
results[i] = parseAlbum(root, items.item(i), album
.getGenres()[0]);
}
} catch (Exception e) {
log.error("error parsing result set", e);
throw new IOException("error parsing result set");
}
}
return results;
}
/**
* @param album
* @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
*/
public Cart createCart(MusicAlbum album, int quantity) throws IOException {
StringBuffer sb = new StringBuffer();
sb.append("http://webservices.amazon.com/onca/xml");
sb.append("?Service=AWSECommerceService");
sb.append("&SubscriptionId=").append(subscriptionId);
sb.append("&Operation=").append("CartCreate");
sb.append("&Item.1.ASIN=").append(album.getASIN());
sb.append("&Item.1.Quantity=").append(quantity);
String qs = sb.toString();
if (info) { log.info(qs); }
Cart cart = doCartOperation(qs, "CartCreate");
return cart;
}
/**
* @param album
* @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
*/
public Cart addToCart(Cart cart, MusicAlbum album, int quantity)
throws IOException {
StringBuffer sb = new StringBuffer();
sb.append("http://webservices.amazon.com/onca/xml");
sb.append("?Service=AWSECommerceService");
sb.append("&SubscriptionId=").append(subscriptionId);
sb.append("&Operation=").append("CartAdd");
sb.append("&CartId=").append(cart.getCartId());
sb.append("&HMAC=").append(cart.getHMAC());
sb.append("&Item.1.ASIN=").append(album.getASIN());
sb.append("&Item.1.Quantity=").append(quantity);
String qs = sb.toString();
if (info) { log.info(qs); }
Cart newCart = doCartOperation(qs, "CartAdd");
return newCart;
}
/**
* a helper method - parser factory
*
* @return DocumentBuilder
*/
protected static DocumentBuilder getSimpleDOMBuilder() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setValidating(false);
return dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
log.fatal("can not instanitate XML parser", e);
}
return null;
}
/**
* retrieve a DOM node value as a string depending on Node type.
*
* @param target a node to be retreived
* @return node value as a string
*/
protected static String xGetVal(Node target) {
String result = "";
if (target == null)
return result;
switch (target.getNodeType()) {
case Node.ATTRIBUTE_NODE:
result = target.getNodeValue();
break;
case Node.ELEMENT_NODE:
if (target.hasChildNodes()) {
Node chld = target.getFirstChild();
StringBuffer buf = new StringBuffer();
while (chld != null) {
if (chld.getNodeType() == Node.TEXT_NODE)
buf.append(((CharacterData) chld).getData());
chld = chld.getNextSibling();
}
result = buf.toString();
}
break;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
result = ((CharacterData) target).getData();
break;
default: {
String err = "Trying to get value of a strange Node type: ";
err += target.getNodeType();
throw new IllegalArgumentException(err);
}
}
return result.trim();
}
protected static MusicAlbum[] doAlbumQuery(String request, int genre)
throws IOException {
MusicAlbum results[] = null;
InputSource is = new InputSource(request);
DocumentBuilder parser = getSimpleDOMBuilder();
if (parser != null) {
try {
Document doc = parser.parse(is);
Element root = (Element) doc.getDocumentElement();
//Amazon NS changes quite frequently, let's get the latest one
String ecs_ns = root.getAttribute("xmlns");
log.debug("xmlns = " + ecs_ns);
//Let's define a prefix required by XPath
root.setAttributeNS(XMLNS_NS, "xmlns:ecs", ecs_ns);
//all items
String xpath = "/ecs:ItemSearchResponse/ecs:Items/ecs:Item";
NodeList items = XPathAPI.selectNodeList(root, xpath, root);
int sz = items.getLength();
results = new MusicAlbum[sz];
for (int i = 0; i < sz; i++) {
results[i] = parseAlbum(root, items.item(i), genre);
}
} catch (Exception e) {
log.error("error parsing result set", e);
throw new IOException("error parsing result set");
}
}
return results;
}
protected static Cart doCartOperation(String request, String cartOp)
throws IOException {
Cart cart = null;
InputSource is = new InputSource(request);
DocumentBuilder parser = getSimpleDOMBuilder();
if (parser != null) {
try {
Document doc = parser.parse(is);
Element root = (Element) doc.getDocumentElement();
//Amazon NS changes quite frequently, let's get the latest one
String ecs_ns = root.getAttribute("xmlns");
log.debug("xmlns = " + ecs_ns);
//Let's define a prefix required by XPath
root.setAttributeNS(XMLNS_NS, "xmlns:ecs", ecs_ns);
String cartPath = "/ecs:" + cartOp + "Response/ecs:Cart";
String xpath = cartPath + "/ecs:CartId";
String cartId = xmlGet(root, xpath, root);
xpath = cartPath + "/ecs:HMAC";
String hMAC = xmlGet(root, xpath, root);
xpath = cartPath + "/ecs:PurchaseURL";
String purchaseURL = xmlGet(root, xpath, root);
cart = new Cart(cartId, hMAC, purchaseURL);
xpath = cartPath + "/ecs:CartItems/ecs:CartItem";
NodeList items = XPathAPI.selectNodeList(root, xpath, root);
int sz = items.getLength();
CartItem cartItems[] = new CartItem[sz];
for (int i = 0; i < sz; i++) {
cartItems[i] = parseCartItem(root, items.item(i));
}
cart.setCartItems(cartItems);
if (info) { log.info(cart); }
} catch (Exception e) {
log.error("error parsing result set", e);
throw new IOException("error parsing result set");
}
}
return cart;
}
protected static MusicAlbum parseAlbum(Element root, Node album, int genre) {
String asin = xmlGet(album, "ecs:ASIN", root);
String group =
xmlGet(album, "ecs:ItemAttributes/ecs:ProductGroup", root);
String title = xmlGet(album, "ecs:ItemAttributes/ecs:Title", root);
String artist = xmlGet(album, "ecs:ItemAttributes/ecs:Artist", root);
int listPrice = 0;
String sPrice =
xmlGet(album,"ecs:ItemAttributes/ecs:ListPrice/ecs:Amount", root);
try {
listPrice = Integer.parseInt(sPrice);
} catch (NumberFormatException numEx) {
log.warn("error parsing list price " + sPrice);
}
int rank = 0;
String sRank = xmlGet(album, "ecs:SalesRank", root);
try {
Integer.parseInt(sRank);
} catch (NumberFormatException numEx) {
log.warn("error parsing rank " + sRank);
}
Date relDate = null;
String sDate = "";
try {
sDate = xmlGet(album, "ecs:ItemAttributes/ecs:ReleaseDate", root);
relDate = MusicAlbum.DATE_FORMAT.parse(sDate);
} catch (ParseException e) {
log.warn("error parsing date: " + sDate);
}
String label = xmlGet(album, "ecs:ItemAttributes/ecs:Label", root);
int genres[] = new int[] { genre };
Image smallImage = null;
try {
Node imageNode =
XPathAPI.selectSingleNode(album, "ecs:SmallImage", root);
smallImage = parseImage(root, imageNode);
} catch (TransformerException e) {
log.warn("error evaluating XPath: ecs:SmallImage" + e);
}
OfferSummary offerSummary = null;
try {
Node offerSummaryNode =
XPathAPI.selectSingleNode(album, "ecs:OfferSummary", root);
offerSummary = parseOfferSummary(root, offerSummaryNode);
} catch (TransformerException e) {
log.warn("error evaluating XPath: ecs:OfferSummary" + e);
}
MusicAlbum ma =
new MusicAlbum(asin, group, title, listPrice, artist,
relDate, label, rank);
ma.setGenres(genres);
ma.setSmallImage(smallImage);
ma.setOfferSummary(offerSummary);
return ma;
}
protected static Image parseImage(Element root, Node imageNode) {
Image image = null;
try {
String imageURL = xmlGet(imageNode, "ecs:URL", root);
int height =
Integer.parseInt(xmlGet(imageNode, "ecs:Height", root));
int width =
Integer.parseInt(xmlGet(imageNode, "ecs:Width", root));
image = new Image(imageURL, height, width);
} catch (Exception e) {
log.warn("error parsing item's image");
}
return image;
}
protected static OfferSummary parseOfferSummary(Element root, Node offerSummary) {
int totalNew = 0;
String sTotalNew = xmlGet(offerSummary, "ecs:TotalNew", root);
try {
totalNew = Integer.parseInt(sTotalNew);
} catch (NumberFormatException numEx) {
log.warn("error parsing total new offer " + sTotalNew);
}
int totalUsed = 0;
String sTotalUsed = xmlGet(offerSummary, "ecs:TotalUsed", root);
try {
totalUsed = Integer.parseInt(sTotalUsed);
} catch (NumberFormatException numEx) {
log.warn("error parsing total used offer " + sTotalUsed);
}
int totalCollect = 0;
String sTotalCollect = xmlGet(offerSummary, "ecs:TotalCollectible", root);
try {
totalCollect = Integer.parseInt(sTotalCollect);
} catch (NumberFormatException numEx) {
log.warn("error parsing total collectible offer " + sTotalCollect);
}
int newPrice = 0;
if (totalNew > 0) {
String sNewPrice =
xmlGet(offerSummary, "ecs:LowestNewPrice/ecs:Amount", root);
try {
newPrice = Integer.parseInt(sNewPrice);
} catch (NumberFormatException numEx) {
log.warn("error parsing lowest new price " + sNewPrice);
}
}
int usedPrice = 0;
if (totalUsed > 0) {
String sUsedPrice =
xmlGet(offerSummary, "ecs:LowestUsedPrice/ecs:Amount", root);
try {
usedPrice = Integer.parseInt(sUsedPrice);
} catch (NumberFormatException numEx) {
log.warn("error parsing lowest ued price " + sUsedPrice);
}
}
int collectPrice = 0;
if (totalCollect > 0) {
String sCollectPrice =
xmlGet(offerSummary, "ecs:LowestCollectiblePrice/ecs:Amount", root);
try {
collectPrice = Integer.parseInt(sCollectPrice);
} catch (NumberFormatException numEx) {
log.warn("error parsing lowest collectible price " + sCollectPrice);
}
}
return new OfferSummary(newPrice, usedPrice, collectPrice,
totalNew, totalUsed, totalCollect);
}
protected static CartItem parseCartItem(Element root, Node cartItem) {
String cartItemId = xmlGet(cartItem, "ecs:CartItemId", root);
String asin = xmlGet(cartItem, "ecs:ASIN", root);
String merchantId = xmlGet(cartItem, "ecs:MerchantId", root);
int quantity = Integer.parseInt(xmlGet(cartItem, "ecs:Quantity", root));
String title = xmlGet(cartItem, "ecs:Title", root);
String group = xmlGet(cartItem, "ecs:ProductGroup", root);
int listPrice = Integer.parseInt(xmlGet(cartItem,
"ecs:Price/ecs:Amount", root));
CartItem item = new CartItem(cartItemId, asin, merchantId, quantity,
title, group, listPrice);
return item;
}
protected static final String xmlGet(Node node, String xpath, Node xmlnsNode) {
String rslt = "";
try {
rslt = xGetVal(XPathAPI.selectSingleNode(node, xpath, xmlnsNode));
} catch (TransformerException e) {
log.warn("error evaluating XPath: " + xpath, e);
}
return rslt;
}
}