| /* |
| * 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; |
| } |
| |
| } |