Source code: org/apache/taglibs/rdc/sampleapps/musicstore/ws/MusicStore.java
1 /*
2 *
3 * Copyright 2004 The Apache Software Foundation.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18 package org.apache.taglibs.rdc.sampleapps.musicstore.ws;
19
20 import java.io.IOException;
21 import java.text.ParseException;
22 import java.util.Date;
23 import javax.xml.parsers.DocumentBuilder;
24 import javax.xml.parsers.DocumentBuilderFactory;
25 import javax.xml.parsers.ParserConfigurationException;
26 import javax.xml.transform.TransformerException;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.xpath.XPathAPI;
31 import org.w3c.dom.CharacterData;
32 import org.w3c.dom.Document;
33 import org.w3c.dom.Element;
34 import org.w3c.dom.Node;
35 import org.w3c.dom.NodeList;
36 import org.xml.sax.InputSource;
37
38 /**
39 * Class representing an Amazon Web Services music store.
40 * Contains necessary AWS queries needed by the music store application.
41 *
42 * @author Jaroslav Gergic
43 * @author Rahul Akolkar
44 * @author Thomas Ling
45 */
46 public class MusicStore {
47
48 protected static final String MERCHANT_ID = "Featured";
49 protected static final String XMLNS_NS = "http://www.w3.org/2000/xmlns/";
50 protected static final String RESPONSE_GRP = "ItemAttributes,Images,SalesRank,OfferSummary";
51
52 protected String subscriptionId;
53
54 private static Log log = LogFactory.getLog(MusicStore.class);
55 private static final boolean info = false;
56
57 /**
58 * @param subscriptionId Amazon ECS subscription ID
59 */
60 public MusicStore(String subscriptionId) {
61 if (subscriptionId == null || subscriptionId.trim().length() == 0) {
62 String errDescription = "RDC MusicStore instantiated without an " +
63 "Amazon web services subscription ID. Read application " +
64 "README file.";
65 log.error(errDescription);
66 throw new IllegalArgumentException(errDescription);
67 }
68 this.subscriptionId = subscriptionId;
69 }
70
71 /**
72 * Retrurns an array of music genres (styles) listed on Amazon
73 *
74 * @param parent parent music style
75 * @return int[]
76 * @throws IOException
77 * @throws java.io.IOException
78 */
79 public int[] getGenres(int parent) throws IOException {
80 int results[] = null;
81 StringBuffer sb = new StringBuffer();
82 sb.append("http://webservices.amazon.com/onca/xml");
83 sb.append("?Service=AWSECommerceService");
84 sb.append("&SubscriptionId=").append(subscriptionId);
85 sb.append("&Operation=BrowseNodeLookup");
86 sb.append("&BrowseNodeId=").append(parent);
87 String qs = sb.toString();
88 if (info) { log.info(qs); }
89 InputSource is = new InputSource(qs);
90 DocumentBuilder parser = getSimpleDOMBuilder();
91 if (parser != null) {
92 try {
93 Document doc = parser.parse(is);
94 Element root = (Element) doc.getDocumentElement();
95 //Amazon NS changes quite frequently, let's get the latest one
96 String ecs_ns = root.getAttribute("xmlns");
97 log.debug("xmlns = " + ecs_ns);
98 //Let's define a prefix required by XPath
99 root.setAttributeNS(XMLNS_NS, "xmlns:ecs", ecs_ns);
100 String xpath = "/ecs:BrowseNodeLookupResponse/ecs:BrowseNodes/ecs:BrowseNode/ecs:Children/ecs:BrowseNode";
101 NodeList lst = XPathAPI.selectNodeList(root, xpath, root);
102 int sz = lst.getLength();
103 results = new int[sz];
104 for (int i = 0; i < sz; i++) {
105 Element bnode = (Element) (lst.item(i));
106 NodeList nms = bnode.getElementsByTagNameNS(ecs_ns, "Name");
107 NodeList ids = bnode.getElementsByTagNameNS(ecs_ns,
108 "BrowseNodeId");
109 String name = xGetVal(nms.item(0));
110 String ndid = xGetVal(ids.item(0));
111 results[i] = Integer.parseInt(ndid);
112 }
113 } catch (Exception e) {
114 log.error("error parsing result set", e);
115 throw new IOException("error parsing result set");
116 }
117 }
118 return results;
119 }
120
121 /**
122 * @param genre
123 * @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
124 */
125 public MusicAlbum[] getTopSellers(int genre) throws IOException {
126 StringBuffer sb = new StringBuffer();
127 sb.append("http://webservices.amazon.com/onca/xml");
128 sb.append("?Service=AWSECommerceService");
129 sb.append("&SubscriptionId=").append(subscriptionId);
130 sb.append("&MerchantId=").append(MERCHANT_ID);
131 sb.append("&Operation=").append("ItemSearch");
132 sb.append("&BrowseNode=").append(genre);
133 sb.append("&SearchIndex=").append("Music");
134 sb.append("&Condition=").append("New");
135 sb.append("&ResponseGroup=").append(RESPONSE_GRP);
136 sb.append("&Sort=").append("salesrank"); //Top Sellers
137 String qs = sb.toString();
138 if (info) { log.info(qs); }
139 return doAlbumQuery(qs, genre);
140 }
141
142 /**
143 * @param genre
144 * @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
145 */
146 public MusicAlbum[] getNewReleases(int genre) throws IOException {
147 StringBuffer sb = new StringBuffer();
148 sb.append("http://webservices.amazon.com/onca/xml");
149 sb.append("?Service=AWSECommerceService");
150 sb.append("&SubscriptionId=").append(subscriptionId);
151 sb.append("&MerchantId=").append(MERCHANT_ID);
152 sb.append("&Operation=").append("ItemSearch");
153 sb.append("&BrowseNode=").append(genre);
154 sb.append("&SearchIndex=").append("Music");
155 sb.append("&Condition=").append("New");
156 sb.append("&ResponseGroup=").append(RESPONSE_GRP);
157 sb.append("&Sort=").append("-orig-rel-date"); //Reveresed Release Date
158 String qs = sb.toString();
159 if (info) { log.info(qs); }
160 return doAlbumQuery(qs, genre);
161 }
162
163 /**
164 * @param genre
165 * @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
166 */
167 public MusicAlbum[] getFeaturedItems(int genre) throws IOException {
168 StringBuffer sb = new StringBuffer();
169 sb.append("http://webservices.amazon.com/onca/xml");
170 sb.append("?Service=AWSECommerceService");
171 sb.append("&SubscriptionId=").append(subscriptionId);
172 sb.append("&MerchantId=").append(MERCHANT_ID);
173 sb.append("&Operation=").append("ItemSearch");
174 sb.append("&BrowseNode=").append(genre);
175 sb.append("&SearchIndex=").append("Music");
176 sb.append("&Condition=").append("New");
177 sb.append("&ResponseGroup=").append(RESPONSE_GRP);
178 sb.append("&Sort=").append("psrank"); //Featured Items
179 String qs = sb.toString();
180 if (info) { log.info(qs); }
181 return doAlbumQuery(qs, genre);
182 }
183
184 /**
185 * @param album
186 * @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
187 */
188 public MusicAlbum[] getSimilarItems(MusicAlbum album) throws IOException {
189 StringBuffer sb = new StringBuffer();
190 sb.append("http://webservices.amazon.com/onca/xml");
191 sb.append("?Service=AWSECommerceService");
192 sb.append("&SubscriptionId=").append(subscriptionId);
193 sb.append("&MerchantId=").append(MERCHANT_ID);
194 sb.append("&Operation=").append("SimilarityLookup");
195 sb.append("&ItemId=").append(album.getASIN());
196 sb.append("&SimilarityType=").append("Intersection");
197 sb.append("&ResponseGroup=").append(RESPONSE_GRP);
198 String qs = sb.toString();
199 if (info) { log.info(qs); }
200 MusicAlbum results[] = null;
201 InputSource is = new InputSource(qs);
202 DocumentBuilder parser = getSimpleDOMBuilder();
203 if (parser != null) {
204 try {
205 Document doc = parser.parse(is);
206 Element root = (Element) doc.getDocumentElement();
207 //Amazon NS changes quite frequently, let's get the latest one
208 String ecs_ns = root.getAttribute("xmlns");
209 log.debug("xmlns = " + ecs_ns);
210 //Let's define a prefix required by XPath
211 root.setAttributeNS(XMLNS_NS, "xmlns:ecs", ecs_ns);
212 //all items
213 String xpath = "/ecs:SimilarityLookupResponse/ecs:Items/ecs:Item";
214 NodeList items = XPathAPI.selectNodeList(root, xpath, root);
215 int sz = items.getLength();
216 results = new MusicAlbum[sz];
217 for (int i = 0; i < sz; i++) {
218 results[i] = parseAlbum(root, items.item(i), album
219 .getGenres()[0]);
220 }
221 } catch (Exception e) {
222 log.error("error parsing result set", e);
223 throw new IOException("error parsing result set");
224 }
225 }
226 return results;
227 }
228
229 /**
230 * @param album
231 * @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
232 */
233 public Cart createCart(MusicAlbum album, int quantity) throws IOException {
234 StringBuffer sb = new StringBuffer();
235 sb.append("http://webservices.amazon.com/onca/xml");
236 sb.append("?Service=AWSECommerceService");
237 sb.append("&SubscriptionId=").append(subscriptionId);
238 sb.append("&Operation=").append("CartCreate");
239 sb.append("&Item.1.ASIN=").append(album.getASIN());
240 sb.append("&Item.1.Quantity=").append(quantity);
241 String qs = sb.toString();
242 if (info) { log.info(qs); }
243 Cart cart = doCartOperation(qs, "CartCreate");
244 return cart;
245 }
246
247 /**
248 * @param album
249 * @return org.apache.taglibs.rdc.sampleapps.musicstore.ws.MusicAlbum[]
250 */
251 public Cart addToCart(Cart cart, MusicAlbum album, int quantity)
252 throws IOException {
253 StringBuffer sb = new StringBuffer();
254 sb.append("http://webservices.amazon.com/onca/xml");
255 sb.append("?Service=AWSECommerceService");
256 sb.append("&SubscriptionId=").append(subscriptionId);
257 sb.append("&Operation=").append("CartAdd");
258 sb.append("&CartId=").append(cart.getCartId());
259 sb.append("&HMAC=").append(cart.getHMAC());
260 sb.append("&Item.1.ASIN=").append(album.getASIN());
261 sb.append("&Item.1.Quantity=").append(quantity);
262 String qs = sb.toString();
263 if (info) { log.info(qs); }
264 Cart newCart = doCartOperation(qs, "CartAdd");
265 return newCart;
266 }
267
268 /**
269 * a helper method - parser factory
270 *
271 * @return DocumentBuilder
272 */
273 protected static DocumentBuilder getSimpleDOMBuilder() {
274 try {
275 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
276 dbf.setNamespaceAware(true);
277 dbf.setValidating(false);
278 return dbf.newDocumentBuilder();
279 } catch (ParserConfigurationException e) {
280 log.fatal("can not instanitate XML parser", e);
281 }
282 return null;
283 }
284
285 /**
286 * retrieve a DOM node value as a string depending on Node type.
287 *
288 * @param target a node to be retreived
289 * @return node value as a string
290 */
291 protected static String xGetVal(Node target) {
292 String result = "";
293 if (target == null)
294 return result;
295 switch (target.getNodeType()) {
296 case Node.ATTRIBUTE_NODE:
297 result = target.getNodeValue();
298 break;
299 case Node.ELEMENT_NODE:
300 if (target.hasChildNodes()) {
301 Node chld = target.getFirstChild();
302 StringBuffer buf = new StringBuffer();
303 while (chld != null) {
304 if (chld.getNodeType() == Node.TEXT_NODE)
305 buf.append(((CharacterData) chld).getData());
306 chld = chld.getNextSibling();
307 }
308 result = buf.toString();
309 }
310 break;
311 case Node.TEXT_NODE:
312 case Node.CDATA_SECTION_NODE:
313 result = ((CharacterData) target).getData();
314 break;
315 default: {
316 String err = "Trying to get value of a strange Node type: ";
317 err += target.getNodeType();
318 throw new IllegalArgumentException(err);
319 }
320 }
321 return result.trim();
322 }
323
324 protected static MusicAlbum[] doAlbumQuery(String request, int genre)
325 throws IOException {
326 MusicAlbum results[] = null;
327 InputSource is = new InputSource(request);
328 DocumentBuilder parser = getSimpleDOMBuilder();
329 if (parser != null) {
330 try {
331 Document doc = parser.parse(is);
332 Element root = (Element) doc.getDocumentElement();
333 //Amazon NS changes quite frequently, let's get the latest one
334 String ecs_ns = root.getAttribute("xmlns");
335 log.debug("xmlns = " + ecs_ns);
336 //Let's define a prefix required by XPath
337 root.setAttributeNS(XMLNS_NS, "xmlns:ecs", ecs_ns);
338 //all items
339 String xpath = "/ecs:ItemSearchResponse/ecs:Items/ecs:Item";
340 NodeList items = XPathAPI.selectNodeList(root, xpath, root);
341 int sz = items.getLength();
342 results = new MusicAlbum[sz];
343 for (int i = 0; i < sz; i++) {
344 results[i] = parseAlbum(root, items.item(i), genre);
345 }
346 } catch (Exception e) {
347 log.error("error parsing result set", e);
348 throw new IOException("error parsing result set");
349 }
350 }
351 return results;
352 }
353
354 protected static Cart doCartOperation(String request, String cartOp)
355 throws IOException {
356 Cart cart = null;
357 InputSource is = new InputSource(request);
358 DocumentBuilder parser = getSimpleDOMBuilder();
359 if (parser != null) {
360 try {
361 Document doc = parser.parse(is);
362 Element root = (Element) doc.getDocumentElement();
363 //Amazon NS changes quite frequently, let's get the latest one
364 String ecs_ns = root.getAttribute("xmlns");
365 log.debug("xmlns = " + ecs_ns);
366 //Let's define a prefix required by XPath
367 root.setAttributeNS(XMLNS_NS, "xmlns:ecs", ecs_ns);
368 String cartPath = "/ecs:" + cartOp + "Response/ecs:Cart";
369 String xpath = cartPath + "/ecs:CartId";
370 String cartId = xmlGet(root, xpath, root);
371 xpath = cartPath + "/ecs:HMAC";
372 String hMAC = xmlGet(root, xpath, root);
373 xpath = cartPath + "/ecs:PurchaseURL";
374 String purchaseURL = xmlGet(root, xpath, root);
375
376 cart = new Cart(cartId, hMAC, purchaseURL);
377 xpath = cartPath + "/ecs:CartItems/ecs:CartItem";
378 NodeList items = XPathAPI.selectNodeList(root, xpath, root);
379 int sz = items.getLength();
380 CartItem cartItems[] = new CartItem[sz];
381 for (int i = 0; i < sz; i++) {
382 cartItems[i] = parseCartItem(root, items.item(i));
383 }
384 cart.setCartItems(cartItems);
385 if (info) { log.info(cart); }
386 } catch (Exception e) {
387 log.error("error parsing result set", e);
388 throw new IOException("error parsing result set");
389 }
390 }
391
392 return cart;
393 }
394
395 protected static MusicAlbum parseAlbum(Element root, Node album, int genre) {
396 String asin = xmlGet(album, "ecs:ASIN", root);
397 String group =
398 xmlGet(album, "ecs:ItemAttributes/ecs:ProductGroup", root);
399 String title = xmlGet(album, "ecs:ItemAttributes/ecs:Title", root);
400 String artist = xmlGet(album, "ecs:ItemAttributes/ecs:Artist", root);
401
402 int listPrice = 0;
403 String sPrice =
404 xmlGet(album,"ecs:ItemAttributes/ecs:ListPrice/ecs:Amount", root);
405 try {
406 listPrice = Integer.parseInt(sPrice);
407 } catch (NumberFormatException numEx) {
408 log.warn("error parsing list price " + sPrice);
409 }
410
411 int rank = 0;
412 String sRank = xmlGet(album, "ecs:SalesRank", root);
413 try {
414 Integer.parseInt(sRank);
415 } catch (NumberFormatException numEx) {
416 log.warn("error parsing rank " + sRank);
417 }
418
419 Date relDate = null;
420 String sDate = "";
421 try {
422 sDate = xmlGet(album, "ecs:ItemAttributes/ecs:ReleaseDate", root);
423 relDate = MusicAlbum.DATE_FORMAT.parse(sDate);
424 } catch (ParseException e) {
425 log.warn("error parsing date: " + sDate);
426 }
427
428 String label = xmlGet(album, "ecs:ItemAttributes/ecs:Label", root);
429 int genres[] = new int[] { genre };
430
431 Image smallImage = null;
432 try {
433 Node imageNode =
434 XPathAPI.selectSingleNode(album, "ecs:SmallImage", root);
435 smallImage = parseImage(root, imageNode);
436 } catch (TransformerException e) {
437 log.warn("error evaluating XPath: ecs:SmallImage" + e);
438 }
439
440 OfferSummary offerSummary = null;
441 try {
442 Node offerSummaryNode =
443 XPathAPI.selectSingleNode(album, "ecs:OfferSummary", root);
444 offerSummary = parseOfferSummary(root, offerSummaryNode);
445 } catch (TransformerException e) {
446 log.warn("error evaluating XPath: ecs:OfferSummary" + e);
447 }
448
449 MusicAlbum ma =
450 new MusicAlbum(asin, group, title, listPrice, artist,
451 relDate, label, rank);
452 ma.setGenres(genres);
453 ma.setSmallImage(smallImage);
454 ma.setOfferSummary(offerSummary);
455
456 return ma;
457 }
458
459 protected static Image parseImage(Element root, Node imageNode) {
460 Image image = null;
461 try {
462 String imageURL = xmlGet(imageNode, "ecs:URL", root);
463 int height =
464 Integer.parseInt(xmlGet(imageNode, "ecs:Height", root));
465 int width =
466 Integer.parseInt(xmlGet(imageNode, "ecs:Width", root));
467 image = new Image(imageURL, height, width);
468 } catch (Exception e) {
469 log.warn("error parsing item's image");
470 }
471
472 return image;
473 }
474
475 protected static OfferSummary parseOfferSummary(Element root, Node offerSummary) {
476 int totalNew = 0;
477 String sTotalNew = xmlGet(offerSummary, "ecs:TotalNew", root);
478 try {
479 totalNew = Integer.parseInt(sTotalNew);
480 } catch (NumberFormatException numEx) {
481 log.warn("error parsing total new offer " + sTotalNew);
482 }
483
484 int totalUsed = 0;
485 String sTotalUsed = xmlGet(offerSummary, "ecs:TotalUsed", root);
486 try {
487 totalUsed = Integer.parseInt(sTotalUsed);
488 } catch (NumberFormatException numEx) {
489 log.warn("error parsing total used offer " + sTotalUsed);
490 }
491
492 int totalCollect = 0;
493 String sTotalCollect = xmlGet(offerSummary, "ecs:TotalCollectible", root);
494 try {
495 totalCollect = Integer.parseInt(sTotalCollect);
496 } catch (NumberFormatException numEx) {
497 log.warn("error parsing total collectible offer " + sTotalCollect);
498 }
499
500 int newPrice = 0;
501 if (totalNew > 0) {
502 String sNewPrice =
503 xmlGet(offerSummary, "ecs:LowestNewPrice/ecs:Amount", root);
504 try {
505 newPrice = Integer.parseInt(sNewPrice);
506 } catch (NumberFormatException numEx) {
507 log.warn("error parsing lowest new price " + sNewPrice);
508 }
509 }
510
511 int usedPrice = 0;
512 if (totalUsed > 0) {
513 String sUsedPrice =
514 xmlGet(offerSummary, "ecs:LowestUsedPrice/ecs:Amount", root);
515 try {
516 usedPrice = Integer.parseInt(sUsedPrice);
517 } catch (NumberFormatException numEx) {
518 log.warn("error parsing lowest ued price " + sUsedPrice);
519 }
520 }
521
522 int collectPrice = 0;
523 if (totalCollect > 0) {
524 String sCollectPrice =
525 xmlGet(offerSummary, "ecs:LowestCollectiblePrice/ecs:Amount", root);
526 try {
527 collectPrice = Integer.parseInt(sCollectPrice);
528 } catch (NumberFormatException numEx) {
529 log.warn("error parsing lowest collectible price " + sCollectPrice);
530 }
531 }
532
533 return new OfferSummary(newPrice, usedPrice, collectPrice,
534 totalNew, totalUsed, totalCollect);
535 }
536
537 protected static CartItem parseCartItem(Element root, Node cartItem) {
538 String cartItemId = xmlGet(cartItem, "ecs:CartItemId", root);
539 String asin = xmlGet(cartItem, "ecs:ASIN", root);
540 String merchantId = xmlGet(cartItem, "ecs:MerchantId", root);
541 int quantity = Integer.parseInt(xmlGet(cartItem, "ecs:Quantity", root));
542 String title = xmlGet(cartItem, "ecs:Title", root);
543 String group = xmlGet(cartItem, "ecs:ProductGroup", root);
544 int listPrice = Integer.parseInt(xmlGet(cartItem,
545 "ecs:Price/ecs:Amount", root));
546
547 CartItem item = new CartItem(cartItemId, asin, merchantId, quantity,
548 title, group, listPrice);
549 return item;
550 }
551
552 protected static final String xmlGet(Node node, String xpath, Node xmlnsNode) {
553 String rslt = "";
554 try {
555 rslt = xGetVal(XPathAPI.selectSingleNode(node, xpath, xmlnsNode));
556 } catch (TransformerException e) {
557 log.warn("error evaluating XPath: " + xpath, e);
558 }
559 return rslt;
560 }
561
562 }