com.amazonaws.a2s.AmazonA2SClient.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.a2s.AmazonA2SClient.java

Source

/******************************************************************************* 
 *  Copyright 2007 Amazon Technologies, Inc.  
 *  Licensed 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://aws.amazon.com/apache2.0
 *  This file 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.
 * ***************************************************************************** 
 *    __  _    _  ___ 
 *   (  )( \/\/ )/ __)
 *   /__\ \    / \__ \
 *  (_)(_) \/\/  (___/
 * 
 *  Amazon A2S Java Library
 *  API Version: 2007-10-29
 *  Generated: Thu Jan 10 05:27:32 PST 2008 
 * 
 */

package com.amazonaws.a2s;

import com.amazonaws.a2s.*;
import com.amazonaws.a2s.model.*;

import java.io.BufferedReader;
import java.util.Map;
import java.util.Iterator;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.JAXBException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.HttpMethodRetryHandler;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NoHttpResponseException;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.InputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.xml.transform.stream.StreamSource;

/**
 * 
 * Amazon Associates Web Service (A2S) is the best way to make money on the Internet.
 * Amazon has spent ten years and hundreds of millions of dollars developing a
 * world-class web service that millions of customers use every day. As a developer,
 * you can build A2S applications that leverage this robust, scalable, and reliable
 * technology. You get access to much of the data that is used by Amazon,
 * including the items for sale, customer reviews, seller reviews, as well as most
 * of the functionality that you see on www.amazon.com, such as finding items,
 * finding similar items, displaying customer reviews, and product promotions.
 * In short, A2S operations open the doors to Amazon's databases so that you can
 * take advantage of Amazon's sophisticated E-commerce data and functionality.
 * Build your own web store to sell Amazon items or your own items.
 * <br><br>
 * Best of all, A2S is free. By signing up to become a A2S developer, you join the
 * tens of thousands of developers who are already realizing financial gains by
 * creating A2S-driven applications and web stores. In 2006, A2S developers sold well
 * over $600 million worth of items. Would you like a percentage of that revenue?
 * <br><br>
 * E-commerce is the practice of conducting business over the Internet. This guide
 * explains in detail how you can use A2S operations to create storefronts in which
 * you enable Internet customers to search for your items, see pictures of them, find
 * related items, get customer reviews, and purchase items.
 * <br><br>
 * With e-commerce, the barrier of distance between the shopper and the store goes away:
 * the local video store must compete with stores across the country. E-commerce levels
 * the playing field: the web site of an individual seller can appear as sophisticated
 * and intoxicating as that of a major retailer. A2S is your opportunity to enter the
 * world market where patronage is not limited by the size of your storefront, foot traffic
 * or locality. Welcome to the world of A2S E-commerce.
 * 
 * 
 *
 * AmazonA2SClient is the implementation of AmazonA2S based on the
 * Apache <a href="http://jakarta.apache.org/commons/httpclient/">HttpClient</a>.
 *
 */
public class AmazonA2SClient implements AmazonA2S {

    private final Log log = LogFactory.getLog(AmazonA2SClient.class);

    private String awsAccessKeyId = null;
    private String associateTag = null;
    private AmazonA2SConfig config = null;
    private HttpClient httpClient = null;
    private static JAXBContext jaxbContext;
    private static ThreadLocal<Unmarshaller> unmarshaller;

    /** Initialize JAXBContext and  Unmarshaller **/
    static {
        try {
            jaxbContext = JAXBContext.newInstance("com.amazonaws.a2s.model", AmazonA2S.class.getClassLoader());
        } catch (JAXBException ex) {
            throw new ExceptionInInitializerError(ex);
        }
        unmarshaller = new ThreadLocal<Unmarshaller>() {
            @Override
            protected synchronized Unmarshaller initialValue() {
                try {
                    return jaxbContext.createUnmarshaller();
                } catch (JAXBException e) {
                    throw new ExceptionInInitializerError(e);
                }
            }
        };
    }

    /**
     * Constructs AmazonA2SClient with AWS Access Key ID and Accociate Tag
     *
     * @param awsAccessKeyId
     *          AWS Access Key ID
     * @param associateTag
     *          Associate Tag
     */
    public AmazonA2SClient(String awsAccessKeyId, String associateTag) {
        this(awsAccessKeyId, associateTag, AmazonA2SLocale.US);
    }

    /**
     * Constructs AmazonA2SClient with AWS Access Key ID,  Associate Tag
     * and specific service locale
     *
     * @param awsAccessKeyId
     *          AWS Access Key ID
     * @param associateTag
     *          Associate Tag
     * @param locale
     *          Locale you wish to invoke the service call at. 
     * 
     *  Supported Locales are: US, UK, DE, FR, CA, JP. Note, not all operations
     *  supported by all locales. Please refer to Amazon A2S documentation for
     *  more information
     *                                                          
     */
    public AmazonA2SClient(String awsAccessKeyId, String associateTag, AmazonA2SLocale locale) {
        this.awsAccessKeyId = awsAccessKeyId;
        this.associateTag = associateTag;
        this.config = locale.getConfig();
        this.httpClient = configureHttpClient();
    }

    // Public API ------------------------------------------------------------//

    /**
     * Help  Request
     * 
     * <br><br>
     * The Help operation provides information about A2S operations and
     * response groups. For operations, Help lists required and optional
     * request parameters, as well as default and optional response groups the
     * operation can use. For response groups, Help lists the operations that can
     * use the response group as well as the response tags returned by the
     * response group in the XML response.
     * <br><br>
     * The Help operation is not often used in customer applications. It can, however, be
     * used to help the developer in the following ways:
     * <br><br>
     * <ul>
     * <li>Provide contextual help in an interactive development environment (IDE) for developerst</li>
     * <li>Automate documentation creation as part of a developer's toolkit. </li>
     * </ul>
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Help</li>
     * </ul>
     *   
     * @param request
     *          Help Request
     * @return
     *          Help Response from the service
     *
     * @throws AmazonA2SException
     */
    public HelpResponse help(HelpRequest... request) throws AmazonA2SException {
        Help action = new Help();
        return invoke(HelpResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Item Search  Request
     * 
     * <br><br>
     * The ItemSearch operation returns items that satisfy the search
     * criteria, including one or more search indices
     * <br><br>
     * ItemSearch returns up to ten search results at a time. When condition
     * equals "All," ItemSearch returns up to three offers per condition (if they exist),
     * for example, three new, three used, three refurbished, and three collectible items.
     * Or, for example, if there are no collectible or refurbished offers, ItemSearch
     * returns three new and three used offers.
     * <br><br>
     * Because there are thousands of items in each search index, ItemSearch requires
     * that you specify the value for at least one parameter in addition to a search index.
     * The additional parameter value must reference items within the specified search index.
     * For example, you might specify a browse node (BrowseNode is an ItemSearch parameter),
     * Harry Potter Books, within the Books product category. You would not get results,
     * for example, if you specified the search index to be Automotive and the browse node
     * to be Harry Potter Books. In this case, the parameter value is not associated with
     * the search index value.
     * <br><br>
     * The ItemPage parameter enables you to return a specified page of results. The maximum
     * ItemPage number that can be returned is 400. An error is returned if you try to access
     * higher numbered pages. If you do not include ItemPage in your request, the first page
     * will be returned by default. There can be up to ten items per page.
     * <br><br>
     * ItemSearch is the operation that is used most often in requests. In general,
     * when trying to find an item for sale, you use this operation.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Small</li>
     * <li>Accessories</li>
     * <li>BrowseNodes</li>
     * <li>EditorialReview</li>
     * <li>Images</li>
     * <li>ItemAttributes</li>
     * <li>ItemIds</li>
     * <li>Large</li>
     * <li>ListmaniaLists</li>
     * <li>Medium</li>
     * <li>MerchantItemAttributes</li>
     * <li>OfferFull</li>
     * <li>Offers</li>
     * <li>OfferSummary</li>
     * <li>Reviews</li>
     * <li>SalesRank</li>
     * <li>SearchBins</li>
     * <li>Similarities</li>
     * <li>Subjects</li>
     * <li>Tracks</li>
     * <li>TagsSummary</li>
     * <li>Tags</li>
     * <li>VariationImages</li>
     * <li>VariationMinimum</li>
     * <li>Variations</li>
     * <li>VariationSummary</li>
     * </ul>
     *   
     * @param request
     *          ItemSearch Request
     * @return
     *          ItemSearch Response from the service
     *
     * @throws AmazonA2SException
     */
    public ItemSearchResponse itemSearch(ItemSearchRequest... request) throws AmazonA2SException {
        ItemSearch action = new ItemSearch();
        return invoke(ItemSearchResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Item Lookup  Request
     * 
     * <br><br>
     * Given an Item identifier, the ItemLookup operation returns some or all
     * of the item attributes, depending on the response group specified in the request.
     * By default, ItemLookup returns an item's ASIN, DetailPageURL, Manufacturer,
     * ProductGroup, and Title of the item.
     * <br><br>
     * ItemLookup supports many response groups, so you can retrieve many different
     * kinds of product information, called item attributes, including product reviews,
     * variations, similar products, pricing, availability, images of products, accessories,
     * and other information.
     * <br><br>
     * To look up more than one item at a time, separate the item identifiers by commas.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Small</li>
     * <li>Accessories</li>
     * <li>BrowseNodes</li>
     * <li>EditorialReview</li>
     * <li>Images</li>
     * <li>ItemAttributes</li>
     * <li>ItemIds</li>
     * <li>Large</li>
     * <li>ListmaniaLists</li>
     * <li>Medium</li>
     * <li>MerchantItemAttributes</li>
     * <li>OfferFull</li>
     * <li>Offers</li>
     * <li>OfferSummary</li>
     * <li>Reviews</li>
     * <li>SalesRank</li>
     * <li>Similarities</li>
     * <li>Subjects</li>
     * <li>Tracks</li>
     * <li>TagsSummary</li>
     * <li>Tags</li>
     * <li>VariationImages</li>
     * <li>VariationMinimum</li>
     * <li>Variations</li>
     * <li>VariationSummary</li>
     * </ul>
     *   
     * @param request
     *          ItemLookup Request
     * @return
     *          ItemLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public ItemLookupResponse itemLookup(ItemLookupRequest... request) throws AmazonA2SException {
        ItemLookup action = new ItemLookup();
        return invoke(ItemLookupResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Browse Node Lookup  Request
     * 
     * <br><br>
     * Given a browse node ID, BrowseNodeLookup returns the specified browse node's name, children, and ancestors.
     * The names and browse node IDs of the children and ancestor browse nodes are also returned.
     * BrowseNodeLookup enables you to traverse the browse node hierarchy to find a browse node.
     * As you traverse down the hierarchy, you refine your search and limit the number of items returned.
     * For example, you might traverse the following hierarchy: DVD Used DVDs Kids and Family,
     * to select out of all the DVDs offered by Amazon only those that are appropriate for family viewing.
     * Returning the items associated with Kids and Family produces a much more targeted result than a search
     * based at the level of Used DVDs.
     * <br><br>
     * Alternatively, by traversing up the browse node tree, you can determine the root category of an item.
     * You might do that, for example, to return the top seller of the root product category using the
     * TopSeller response group in an ItemSearch request.
     * <br><br>
     * You can use BrowseNodeLookup iteratively to navigate through the browse node hierarchy to
     * reach the node that most appropriately suits your search. Then you can use the browse node ID in
     * an ItemSearch request. This response would be far more targeted than, for example,
     * searching through all of the browse nodes in a search index.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>BrowseNodeInfo</li>
     * </ul>
     *   
     * @param request
     *          BrowseNodeLookup Request
     * @return
     *          BrowseNodeLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public BrowseNodeLookupResponse browseNodeLookup(BrowseNodeLookupRequest... request) throws AmazonA2SException {
        BrowseNodeLookup action = new BrowseNodeLookup();
        return invoke(BrowseNodeLookupResponse.class, action.withRequest(request).toMap());
    }

    /**
     * List Search  Request
     * 
     * <br><br>
     * Given a customer name or Email address, the ListSearch operation
     * returns the associated list ID(s) but not the list items. To find those,
     * use the list ID returned by ListSearch with  ListLookup  .
     * <br><br>
     * Specifying a full name or just a first or last name in the request typically
     * returns multiple lists belonging to different people. Using Email as the
     * identifier produces more filtered results.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>ListInfo</li>
     * <li>ListMinimum</li>
     * </ul>
     *   
     * @param request
     *          ListSearch Request
     * @return
     *          ListSearch Response from the service
     *
     * @throws AmazonA2SException
     */
    public ListSearchResponse listSearch(ListSearchRequest... request) throws AmazonA2SException {
        ListSearch action = new ListSearch();
        return invoke(ListSearchResponse.class, action.withRequest(request).toMap());
    }

    /**
     * List Lookup  Request
     * 
     * <br><br>
     * The ListLookup operation returns, by default, summary information about a
     * list that you specify in the request. The summary information includes the:
     * <br><br>
     * <ul>
     * <li>Creation date of the list</li>
     * <li>Name of the list's creator</li>
     * </ul>
     * <br><br>
     * The operation returns up to ten sets of summary information per page.
     * <br><br>
     * Lists are specified by list type and list ID, which can be found using ListSearch.
     * <br><br>
     * You cannot lookup more than one list at a time in a single request.
     * You can, however, make a batch request to look for more than one list simultaneously.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>ListInfo</li>
     * <li>Accessories</li>
     * <li>BrowseNodes</li>
     * <li>EditorialReview</li>
     * <li>Images</li>
     * <li>ItemAttributes</li>
     * <li>ItemIds</li>
     * <li>Large</li>
     * <li>ListFull</li>
     * <li>ListItems</li>
     * <li>ListmaniaLists</li>
     * <li>Medium</li>
     * <li>Offers</li>
     * <li>OfferSummary</li>
     * <li>Reviews</li>
     * <li>SalesRank</li>
     * <li>Similarities</li>
     * <li>Small</li>
     * <li>Subjects</li>
     * <li>Tracks</li>
     * <li>VariationMinimum</li>
     * <li>Variations</li>
     * <li>VariationSummary</li>
     * </ul>
     *   
     * @param request
     *          ListLookup Request
     * @return
     *          ListLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public ListLookupResponse listLookup(ListLookupRequest... request) throws AmazonA2SException {
        ListLookup action = new ListLookup();
        return invoke(ListLookupResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Customer Content Search  Request
     * 
     * <br><br>
     * For a given customer Email address or name, the CustomerContentSearch
     * operation returns matching customer IDs, names, nicknames, and residence
     * information (city, state, and country). In general, supplying an Email
     * address returns unique results whereas supplying a name more often
     * returns multiple results.
     * <br><br>
     * Often you use CustomerContentSearch to find a customer ID that you can
     * use in the CustomerContentLookup operation, which returns more
     * extensive customer information.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>CustomerInfo</li>
     * </ul>
     *   
     * @param request
     *          CustomerContentSearch Request
     * @return
     *          CustomerContentSearch Response from the service
     *
     * @throws AmazonA2SException
     */
    public CustomerContentSearchResponse customerContentSearch(CustomerContentSearchRequest... request)
            throws AmazonA2SException {
        CustomerContentSearch action = new CustomerContentSearch();
        return invoke(CustomerContentSearchResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Customer Content Lookup  Request
     * 
     * <br><br>
     * For a given customer ID, the CustomerContentLookup operation
     * retrieves all of the information a customer has made public about
     * themselves on Amazon. Such information includes some or all of the following:
     * <br><br>
     * <ul>
     * <li>AboutMe</li>
     * <li>Birthday</li>
     * <li>City, State and Country</li>
     * <li>Customer Reviews</li>
     * <li>Customer ID</li>
     * <li>Name</li>
     * <li>Nickname</li>
     * <li>Wedding Registry</li>
     * <li>WishList</li>
     * </ul>
     * <br><br>
     * To find a customer ID, use the CustomerContentSearch operation.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>CustomerInfo</li>
     * <li>CustomerReviews</li>
     * <li>CustomerLists</li>
     * <li>CustomerFull</li>
     * <li>TaggedGuides</li>
     * <li>TaggedItems</li>
     * <li>TaggedListmaniaLists</li>
     * <li>TagsSummary</li>
     * <li>Tags</li>
     * </ul>
     *   
     * @param request
     *          CustomerContentLookup Request
     * @return
     *          CustomerContentLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public CustomerContentLookupResponse customerContentLookup(CustomerContentLookupRequest... request)
            throws AmazonA2SException {
        CustomerContentLookup action = new CustomerContentLookup();
        return invoke(CustomerContentLookupResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Similarity Lookup  Request
     * 
     * <br><br>
     * The SimilarityLookup operation returns up to ten products per page that are
     * similar to one or more items specified in the request. This operation is
     * typically used to pique a customer's interest in buying something similar to what they've already ordered.
     * <br><br>
     * If you specify more than one item, SimilarityLookup returns the intersection of similar
     * items each item would return separately. Alternatively, you can use the SimilarityType
     * parameter to return the union of items that are similar to any of the specified items.
     * A maximum of ten similar items are returned; the operation does not return additional
     * pages of similar items. if there are more than ten similar items, running the same
     * request can result in different answers because the ten that are included in the
     * response are picked randomly.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Small</li>
     * <li>Accessories</li>
     * <li>BrowseNodes</li>
     * <li>EditorialReview</li>
     * <li>Images</li>
     * <li>Large</li>
     * <li>ItemAttributes</li>
     * <li>ItemIds</li>
     * <li>ListmaniaLists</li>
     * <li>Medium</li>
     * <li>Offers</li>
     * <li>OfferSummary</li>
     * <li>PromotionDetails</li>
     * <li>PromotionSummary</li>
     * <li>Reviews</li>
     * <li>SalesRank</li>
     * <li>Similarities</li>
     * <li>Tracks</li>
     * <li>VariationMinimum</li>
     * <li>Variations</li>
     * <li>VariationSummary</li>
     * </ul>
     *   
     * @param request
     *          SimilarityLookup Request
     * @return
     *          SimilarityLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public SimilarityLookupResponse similarityLookup(SimilarityLookupRequest... request) throws AmazonA2SException {
        SimilarityLookup action = new SimilarityLookup();
        return invoke(SimilarityLookupResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Seller Lookup  Request
     * 
     * <br><br>
     * The SellerLookup operation returns detailed information about sellers and,
     * in the US locale, merchants. To lookup a seller, you must use their seller ID.
     * The information returned includes the seller's name, location, average rating by
     * customers, and the first five customer feedback entries. SellerLookup will not,
     * however, return the seller's Email or business addresses.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Seller</li>
     * </ul>
     *   
     * @param request
     *          SellerLookup Request
     * @return
     *          SellerLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public SellerLookupResponse sellerLookup(SellerLookupRequest... request) throws AmazonA2SException {
        SellerLookup action = new SellerLookup();
        return invoke(SellerLookupResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Cart Get  Request
     * 
     * <br><br>
     * The CartGet operation enables you to retrieve the IDs, quantities,
     * and prices of all of the items, including SavedForLater items in a
     * remote shopping cart.
     * <br><br>
     * Because the contents of a cart can change for different reasons,
     * such as availability, you should not keep a copy of a cart locally.
     * Instead, use CartGet to retrieve the items in a remote shopping cart.
     * <br><br>
     * To retrieve the items in a cart, you must specify the cart using the
     * CartId and HMAC values, which are returned in the CartCreate operation.
     * A value similar to HMAC, URLEncodedHMAC, is also returned. This value is the
     * URL encoded version of the HMAC. This encoding is necessary because some
     * characters, such as + and /, cannot be included in a URL. Rather than
     * encoding the HMAC yourself, use the URLEncodedHMAC value for the HMAC parameter.
     * <br><br>
     * CartGet does not work after the customer has used the PurchaseURL to either
     * purchase the items or merge them with the items in their Amazon cart.
     * <br><br>
     * If the associated CartCreate request specified an AssociateTag, all CartGet
     * requests must also include a value for AssociateTag otherwise the request will fail.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Cart</li>
     * <li>CartSimilarities</li>
     * <li>CartTopSellers</li>
     * <li>CartNewReleases</li>
     * </ul>
     *   
     * @param request
     *          CartGet Request
     * @return
     *          CartGet Response from the service
     *
     * @throws AmazonA2SException
     */
    public CartGetResponse cartGet(CartGetRequest... request) throws AmazonA2SException {
        CartGet action = new CartGet();
        return invoke(CartGetResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Cart Add  Request
     * 
     * <br><br>
     * The CartAdd operation enables you to add items to an existing remote shopping cart.
     * CartAdd can only be used to place a new item in a shopping cart. It cannot be used to increase
     * the quantity of an item already in the cart. If you would like to increase the quantity of
     * an item that is already in the cart, you must use the CartModify operation.
     * <br><br>
     * You add an item to a cart by specifying the item's OfferListingId, or ASIN and ListItemId.
     * Once in a cart, an item can only be identified by its CartItemId. That is, an item in a cart
     * cannot be accessed by its ASIN or OfferListingId. CartItemId is returned by CartCreate, CartGet, and CartAdd.
     * <br><br>
     * To add items to a cart, you must specify the cart using the CartId and HMAC values, which are
     * returned by the CartCreate operation.
     * <br><br>
     * If the associated CartCreate request specified an AssociateTag, all CartAdd requests must
     * also include a value for Associate Tag otherwise the request will fail.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Cart</li>
     * <li>CartSimilarities</li>
     * <li>CartTopSellers</li>
     * <li>CartNewReleases</li>
     * </ul>
     *   
     * @param request
     *          CartAdd Request
     * @return
     *          CartAdd Response from the service
     *
     * @throws AmazonA2SException
     */
    public CartAddResponse cartAdd(CartAddRequest... request) throws AmazonA2SException {
        CartAdd action = new CartAdd();
        return invoke(CartAddResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Cart Create  Request
     * 
     * <br><br>
     * The CartCreate operation enables you to create a remote shopping cart.
     * A shopping cart is the metaphor used by most e-commerce solutions. It is a
     * temporary data storage structure that resides on Amazon servers. The
     * structure contains the items a customer wants to buy. In A2S, the shopping
     * cart is considered remote because it is hosted by Amazon servers.
     * In this way, the cart is remote to the vendor's web site where the customer
     * views and selects the items they want to purchase.
     * <br><br>
     * Once you add an item to a cart by specifying the item's ListItemId and ASIN,
     * or OfferListing ID, the item is assigned a CartItemId and accessible only
     * by that value. That is, in subsequent requests, an item in a cart cannot
     * be accessed by its ListItemId and ASIN, or OfferListingId. CartItemId is
     * returned by CartCreate , CartGet , and CartAdd .
     * <br><br>
     * Because the contents of a cart can change for different reasons, such as
     * item availability, you should not keep a copy of a cart locally. Instead,
     * use the other cart operations to modify the cart contents. For example,
     * to retrieve contents of the cart, which are represented by CartItemIds,
     * use CartGet .
     * <br><br>
     * Available products are added as cart items. Unavailable items, for example,
     * items out of stock, discontinued, or future releases, are added as SaveForLaterItems.
     * No error is generated. The Amazon database changes regularly. You may find a product
     * with an offer listing ID but by the time the item is added to the cart the product
     * is no longer available. The checkout page in the Order Pipeline clearly lists
     * items that are available and those that are SaveForLaterItems.
     * <br><br>
     * It is impossible to create an empty shopping cart. You have to add at least one
     * item to a shopping cart using a single CartCreate request. You can add specific
     * quantities (up to 999) of each item.
     * <br><br>
     * CartCreate can be used only once in the life cycle of a cart. To modify the
     * contents of the cart, use one of the other cart operations.
     * <br><br>
     * Carts cannot be deleted. They expire automatically after being unused for 7 days.
     * The lifespan of a cart restarts, however, every time a cart is modified.
     * In this way, a cart can last for more than 7 days. If, for example, on day 6,
     * the customer modifies a cart, the 7 day countdown starts over.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Cart</li>
     * <li>CartSimilarities</li>
     * <li>CartTopSellers</li>
     * <li>CartNewReleases</li>
     * </ul>
     *   
     * @param request
     *          CartCreate Request
     * @return
     *          CartCreate Response from the service
     *
     * @throws AmazonA2SException
     */
    public CartCreateResponse cartCreate(CartCreateRequest... request) throws AmazonA2SException {
        CartCreate action = new CartCreate();
        return invoke(CartCreateResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Cart Modify  Request
     * 
     * <br><br>
     * The CartModify operation enables you to:
     * <br><br>
     * <ul>
     * <li>Change the quantity of items that are already in a remote shopping cart.</li>
     * <li>Move items from the active area of a cart to the SaveForLater area or the reverse</li>
     * <li>Change the MergeCart setting. </li>
     * </ul>
     * <br><br>
     * To modify the number of items in a cart, you must specify the
     * cart using the CartId and HMAC values that are returned in the
     * CartCreate operation. A value similar to HMAC, URLEncodedHMAC,
     * is also returned. This value is the URL encoded version of the HMAC.
     * This encoding is necessary because some characters, such as + and /,
     * cannot be included in a URL. Rather than encoding the HMAC yourself,
     * use the URLEncodedHMAC value for the HMAC parameter.
     * <br><br>
     * You can use CartModify to modify the number of items in a remote shopping
     * cart by setting the value of the Quantity parameter appropriately. You can
     * eliminate an item from a cart by setting the value of the Quantity parameter
     * to zero. Or, you can double the number of a particular item in the cart by
     * doubling its Quantity . You cannot, however, use CartModify to add new items to a cart.
     * <br><br>
     * If the associated CartCreate request specified an AssociateTag, all
     * CartModify requests must also include a value for Associate Tag
     * otherwise the request will fail.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Cart</li>
     * <li>CartSimilarities</li>
     * <li>CartTopSellers</li>
     * <li>CartNewReleases</li>
     * </ul>
     *   
     * @param request
     *          CartModify Request
     * @return
     *          CartModify Response from the service
     *
     * @throws AmazonA2SException
     */
    public CartModifyResponse cartModify(CartModifyRequest... request) throws AmazonA2SException {
        CartModify action = new CartModify();
        return invoke(CartModifyResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Cart Clear  Request
     * 
     * <br><br>
     * The CartClear operation enables you to remove all of the items in a remote shopping cart, including
     * SavedForLater items. To remove only some of the items in a cart or to reduce the quantity
     * of one or more items, use  CartModify  .
     * <br><br>
     * To delete all of the items from a remote shopping cart, you must specify the cart using the
     * CartId and HMAC values, which are returned by the CartCreate operation. A value similar
     * to the HMAC, URLEncodedHMAC, is also returned. This value is the URL encoded version
     * of the HMAC. This encoding is necessary because some characters, such as + and /,
     * cannot be included in a URL. Rather than encoding the HMAC yourself, use the
     * URLEncodedHMAC value for the HMAC parameter.
     * <br><br>
     * CartClear does not work after the customer has used the PurchaseURL to either purchase the
     * items or merge them with the items in their Amazon cart.
     * <br><br>
     * Carts exist even though they have been emptied. The lifespan of a cart is 7 days since the
     * last time it was acted upon. For example, if a cart created 6 days ago is modified,
     * the cart lifespan is reset to 7 days.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Cart</li>
     * </ul>
     *   
     * @param request
     *          CartClear Request
     * @return
     *          CartClear Response from the service
     *
     * @throws AmazonA2SException
     */
    public CartClearResponse cartClear(CartClearRequest... request) throws AmazonA2SException {
        CartClear action = new CartClear();
        return invoke(CartClearResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Transaction Lookup  Request
     * 
     * <br><br>
     * The TransactionLookup operation returns information about up to ten purchases
     * that have already taken place. Transaction IDs are created whenever a purchase
     * request is made by a customer.
     * <br><br>
     * For a specified transaction ID, TransactionLookup returns:
     * <ul>
     * <li>Price details</li>
     * <li>Sale date</li>
     * <li>Shipping details</li>
     * <li>Seller details</li>
     * <li>Item's condition </li>
     * </ul>
     * <br><br>
     * For privacy reasons, this operation does not return information about the customer
     * who purchased the items.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>TransactionDetails</li>
     * </ul>
     *   
     * @param request
     *          TransactionLookup Request
     * @return
     *          TransactionLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public TransactionLookupResponse transactionLookup(TransactionLookupRequest... request)
            throws AmazonA2SException {
        TransactionLookup action = new TransactionLookup();
        return invoke(TransactionLookupResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Seller Listing Search  Request
     * 
     * <br><br>
     * The SellerListingSearch operation enables you to search for items offered
     * by specific sellers. You cannot use SellerListingSearch to look up items sold by merchants.
     * To look up an item sold by a merchant, use  ItemLookup   or  ItemSearch
     * along with the MerchantId parameter.
     * <br><br>
     * SellerListingSearch returns the listing ID or exchange ID of an item.
     * Typically, you use those values with SellerListingLookup to find out more about those items.
     * <br><br>
     * Each SellerListingSearch request returns up to ten items. By default, the first ten
     * items are returned. You can use the ListingPage parameter to retrieve additional pages
     * of (up to) ten listings.
     * <br><br>
     * To use A2S, sellers must have less than 100,000 items for sale. Sellers that have more
     * items for sale should use, instead of A2S, other seller APIs, including the Amazon
     * Inventory Management System, and the Merchant@ API.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>SellerListing</li>
     * </ul>
     *   
     * @param request
     *          SellerListingSearch Request
     * @return
     *          SellerListingSearch Response from the service
     *
     * @throws AmazonA2SException
     */
    public SellerListingSearchResponse sellerListingSearch(SellerListingSearchRequest... request)
            throws AmazonA2SException {
        SellerListingSearch action = new SellerListingSearch();
        return invoke(SellerListingSearchResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Seller Listing Lookup  Request
     * 
     * <br><br>
     * The SellerListingLookup operation enables you to return information
     * about a seller's listings, including product descriptions, availability,
     * condition, and quantity available. The response also includes the seller's nickname.
     * Each request requires a seller ID.
     * <br><br>
     * You can also find a seller's items using ItemLookup. There are, however,
     * some reasons why it is better to use SellerListingLookup:
     * <br><br>
     * <ul>
     * <li>SellerListingLookup enables you to search by seller ID.</li>
     * <li>SellerListingLookup returns much more information than ItemLookup.</li>
     * </ul>
     * <br><br>
     * This operation only works with sellers who have less than 100,000 items for sale.
     * Sellers that have more items for sale should use, instead of A2S, other APIs,
     * including the Amazon Inventory Management System, and the Merchant@ API.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>SellerListing</li>
     * </ul>
     *   
     * @param request
     *          SellerListingLookup Request
     * @return
     *          SellerListingLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public SellerListingLookupResponse sellerListingLookup(SellerListingLookupRequest... request)
            throws AmazonA2SException {
        SellerListingLookup action = new SellerListingLookup();
        return invoke(SellerListingLookupResponse.class, action.withRequest(request).toMap());
    }

    /**
     * Tag Lookup  Request
     * 
     * <br><br>
     * The TagLookup operation returns entities based on specifying one to five tags.
     * A tag is a descriptive word that a customer uses to label entities on Amazon's retail website.
     * Entities can be items for sale, Listmania lists, guides, and so forth. For example, a customer might tag a
     * given entity with the phrase, "BestCookbook." For more information, see Tags.
     * <br><br>
     * In the tag-related response groups, Tags and TagSummary specify the amount of informtion returned. The
     * other tag-related response groups, TaggedGuides, TaggedItems, and Tagged listmaniaLists,
     * specify the kind of entity tagged.
     * <br><br>
     * <b>Available Response Groups</b>:
     * <ul>
     * <li>Request</li>
     * <li>Small</li>
     * <li>Accessories</li>
     * <li>AlternateVersions</li>
     * <li>BrowseNodes</li>
     * <li>Collections</li>
     * <li>EditorialReview</li>
     * <li>Images</li>
     * <li>ItemAttributes</li>
     * <li>ItemIds</li>
     * <li>Large</li>
     * <li>ListmaniaLists</li>
     * <li>Medium</li>
     * <li>MerchantItemAttributes</li>
     * <li>OfferFull</li>
     * <li>OfferListings</li>
     * <li>Offers</li>
     * <li>OfferSummary</li>
     * <li>PromotionDetails</li>
     * <li>PromotionSummary</li>
     * <li>Reviews</li>
     * <li>SalesRank</li>
     * <li>ShippingCharges</li>
     * <li>Similarities</li>
     * <li>Subjects</li>
     * <li>TaggedGuides</li>
     * <li>TaggedItems</li>
     * <li>TaggedListmaniaLists</li>
     * <li>TagsSummary</li>
     * <li>Tags</li>
     * <li>Tracks</li>
     * </ul>
     *   
     * @param request
     *          TagLookup Request
     * @return
     *          TagLookup Response from the service
     *
     * @throws AmazonA2SException
     */
    public TagLookupResponse tagLookup(TagLookupRequest... request) throws AmazonA2SException {
        TagLookup action = new TagLookup();
        return invoke(TagLookupResponse.class, action.withRequest(request).toMap());
    }

    // Private API ------------------------------------------------------------//

    /**
     * Configure HttpClient with set of defaults as well as configuration
     * from AmazonA2SConfig instance
     *
     */
    private HttpClient configureHttpClient() {

        /* Set http client parameters */
        HttpClientParams httpClientParams = new HttpClientParams();
        httpClientParams.setParameter(HttpMethodParams.USER_AGENT, config.getUserAgent());
        httpClientParams.setParameter(HttpClientParams.RETRY_HANDLER, new HttpMethodRetryHandler() {
            public boolean retryMethod(HttpMethod method, IOException exception, int executionCount) {
                if (executionCount > 3) {
                    log.debug("Maximum Number of Retry attempts reached, will not retry");
                    return false;
                }
                log.debug("Retrying request. Attempt " + executionCount);
                if (exception instanceof NoHttpResponseException) {
                    log.debug("Retrying on NoHttpResponseException");
                    return true;
                }
                if (!method.isRequestSent()) {
                    log.debug("Retrying on failed sent request");
                    return true;
                }
                return false;
            }
        });

        /* Set host configuration */
        HostConfiguration hostConfiguration = new HostConfiguration();

        /* Set connection manager parameters */
        HttpConnectionManagerParams connectionManagerParams = new HttpConnectionManagerParams();
        connectionManagerParams.setConnectionTimeout(50000);
        connectionManagerParams.setSoTimeout(50000);
        connectionManagerParams.setStaleCheckingEnabled(true);
        connectionManagerParams.setTcpNoDelay(true);
        connectionManagerParams.setMaxTotalConnections(3);
        connectionManagerParams.setMaxConnectionsPerHost(hostConfiguration, 3);

        /* Set connection manager */
        MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
        connectionManager.setParams(connectionManagerParams);

        /* Set http client */
        httpClient = new HttpClient(httpClientParams, connectionManager);

        /* Set proxy if configured */
        if (config.isSetProxyHost() && config.isSetProxyPort()) {
            log.info("Configuring Proxy. Proxy Host: " + config.getProxyHost() + "Proxy Port: "
                    + config.getProxyPort());
            hostConfiguration.setProxy(config.getProxyHost(), config.getProxyPort());

        }

        httpClient.setHostConfiguration(hostConfiguration);
        return httpClient;
    }

    /**
     * Invokes request using parameters from parameters map.
     * Returns response of the T type passed to this method
     */
    @SuppressWarnings("unchecked")
    private <T> T invoke(Class<T> responseClass, Map<String, String> parameters) throws AmazonA2SException {

        String actionName = parameters.get("Operation");
        T response = null;
        String responseBody = null;
        PostMethod method = new PostMethod(config.getServiceURL());
        int status = -1;

        log.debug("Invoking " + actionName + " request. Current parameters: " + parameters);

        try {

            /* Add required request parameters and set request body */
            log.debug("Adding required parameters...");
            addRequiredParametersToRequest(method, parameters);
            log.debug("Done adding additional required parameteres. Parameters now: " + parameters);

            log.debug("Sending Request to host:  " + config.getServiceURL());

            /* Submit request */
            status = httpClient.executeMethod(method);

            /* Consume response stream */
            responseBody = readStream(method.getResponseBodyAsStream());

            log.debug("Received Response. Status: " + status + ". " + "Response Body: " + responseBody);

            log.debug("Checking for errors in the response...");
            throwIfErrors(responseBody, status);

            log.debug("Attempting to unmarshal into the " + actionName + "Response type...");
            response = (T) getUnmarshaller().unmarshal(new StreamSource(new StringReader(responseBody)));

            log.debug("Unmarshalled response into " + actionName + "Response type.");

        } catch (JAXBException je) { /* Response cannot be unmarshalled  as <Action>Response */
            log.error("Caught JAXBException exception", je);
        } catch (IOException ioe) {
            log.error("Caught IOException exception", ioe);
            throw new AmazonA2SException("Internal Error", ioe);
        } catch (Exception e) {
            log.error("Caught Exception", e);
            throw new AmazonA2SException(e);
        } finally {
            method.releaseConnection();
        }
        return response;
    }

    /**
     * Add  parameters and set request body
     * with all of the parameters
     */
    private void addRequiredParametersToRequest(PostMethod method, Map<String, String> parameters) {
        parameters.put("Version", config.getServiceVersion());
        parameters.put("AWSAccessKeyId", this.awsAccessKeyId);
        parameters.put("AssociateTag", this.associateTag);
        Iterator it = parameters.keySet().iterator();
        while (it.hasNext()) {
            String parameterName = (String) it.next();
            method.addParameter(parameterName, parameters.get(parameterName));
        }
    }

    /**
     * Reads stream into String
     */
    private String readStream(InputStream input) throws IOException {

        StringBuffer stringBuffer = new StringBuffer();
        Reader reader = new BufferedReader(new InputStreamReader(input, "UTF8"));
        int ch;
        while ((ch = reader.read()) > -1) {
            stringBuffer.append((char) ch);
        }
        reader.close();
        input.close();
        return stringBuffer.toString();
    }

    /**
     * Checks for presense of the Errors in the response
     * If errors found, constructs and throws AmazonA2SException
     * with information from the Errors
     *
     */
    private void throwIfErrors(String responseString, int status) throws AmazonA2SException {
        Pattern errorPattern = Pattern.compile(
                ".*\\<RequestId>(.*)\\</RequestId>.*"
                        + "(\\<Error>\\<Code>(.*)\\</Code>\\<Message>(.*)\\</Message>\\</Error>).*(\\<Error>)?.*",
                Pattern.MULTILINE | Pattern.DOTALL);
        Matcher errorMatcher = errorPattern.matcher(responseString);
        if (errorMatcher.matches()) {
            String requestId = errorMatcher.group(1);
            String xml = errorMatcher.group(2);
            String code = errorMatcher.group(3);
            String message = errorMatcher.group(4);

            AmazonA2SException exception = new AmazonA2SException(message, status, code, requestId, xml);
            log.debug("Error found in the response: " + "Error code: " + code + "; " + "Error message: " + message
                    + "; " + "Response XML: " + xml + "; " + "Request ID : " + requestId + "; ");
            throw exception;
        }
    }

    /**
     * Get unmarshaller for current thread
     */
    private Unmarshaller getUnmarshaller() {
        return unmarshaller.get();
    }

}