/* * [ProbeViaAWS.java] * * Summary: Find Amazon part number corresponding to an ISBN. Used in production htmlmacros. Probe all Amazon stores. * * Copyright: (c) 2012-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2012-03-26 initial version */ package com.mindprod.aws; import com.mindprod.aws.jax.AWSECommerceService; import com.mindprod.aws.jax.AWSECommerceServicePortType; import com.mindprod.aws.jax.AwsHandlerResolver; import com.mindprod.aws.jax.Item; import com.mindprod.aws.jax.ItemLookup; import com.mindprod.aws.jax.ItemLookupRequest; import com.mindprod.aws.jax.Items; import com.mindprod.aws.jax.Offer; import com.mindprod.aws.jax.OfferListing; import com.mindprod.aws.jax.Offers; import com.mindprod.aws.jax.OperationRequest; import com.mindprod.stores.BStore; import com.mindprod.stores.OnlineStore; import com.mindprod.stores.StockStatus; import javax.xml.namespace.QName; import javax.xml.ws.Holder; import javax.xml.ws.WebServiceException; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import static java.lang.System.*; /** * Find Amazon part number corresponding to an ISBN. Used in production htmlmacros. Probe all Amazon stores. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2012-03-26 initial version * @see FetchBookFacts * @since 2012-03-26 */ public class ProbeViaAWS { /** * true if want extra output and stop on first error. */ private static final boolean DEBUGGING = true; /** * fetch confidential amazon.com-assigned awsAccessKeyId from environment set awsaccesskeyid=xxxxx */ private static final String AWS_ACCESS_KEY_ID = System.getenv( "awsaccesskeyid" ); /** * fetch confidential amazon.com-assigned awsSecretAccessKey assigned by Amazon set awssecretaccesskey=xxxxx */ private static final String AWS_SECRET_ACCESS_KEY = System.getenv( "awssecretaccesskey" ); /** * identifies the context in which names should be interpreted in the schema. * Why do we need to specify this? It appears FOUR times in the wsdl file. Perhaps as a check the code * matches the generated Java. URLs for other stores are embedded in the WSDL file. */ private static final String NAME_SPACE_URI = "http://webservices.amazon.com/AWSECommerceService/2013-08-01"; /** * name of the SOAP service */ private static final String QNAME = "AWSECommerceService"; /** * location of JAX schmo, locally cached. for amazon.com in the USA. Would have to be modified for other countries. * Original came from http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsd * Both Amazon.ca and Amazon.com use the same WSDL. */ private static final String WSDL_LOCATION = "file:///E:/com/mindprod/aws/jax/AWSECommerceService.wsdl"; /** * standard boilerplate to connect to amazon.com AWS server with SOAP * * @return port to use to send requests. * @throws java.net.MalformedURLException if some URLs were mis-specified in the following code. */ private static AWSECommerceService getService() throws MalformedURLException { // Set the service: AWSECommerceService service = new AWSECommerceService( new URL( WSDL_LOCATION ), new QName( NAME_SPACE_URI, QNAME ) ); // AwsHandlerResolver does the timestamp, signing and Base64 encoding service.setHandlerResolver( new AwsHandlerResolver( AWS_SECRET_ACCESS_KEY ) ); // get the corresponding service port. This looks up the endpoint server. return service; } /** * convert isbn13 to asin, probe just one country. * * @param isbn13 isbn13 * * @return corresponding asin, or null if cannot find it. */ private static String isbnToAsinViaAWSProbingOneStore( final String isbn13, final BStore bookstore ) { try { final String associateTag = bookstore.getAccount(); // don't use environment as we did in experiments final AWSECommerceServicePortType port = bookstore.getPort( getService() ); // new ItemLookup final ItemLookup itemLookup = new ItemLookup(); // new ItemLookupRequest which is part of the ItemLookup final ItemLookupRequest itemLookupRequest = new ItemLookupRequest(); // odd our lookup request to the the Lookup. final List itemLookupRequests = itemLookup.getRequest(); itemLookupRequests.add( itemLookupRequest ); // Set up the values of the ItemLookupRequest itemLookupRequest.setSearchIndex( "Books" ); itemLookupRequest.setIdType( "EAN" ); itemLookupRequest.getItemId().add( isbn13 ); // there is no setItemID method itemLookupRequest.setMerchantId( "All" ); itemLookupRequest.setCondition( "All" ); // specify info to include in response final List responseGroup = itemLookupRequest.getResponseGroup(); responseGroup.add( "ItemAttributes" ); // set up Holder for the response tree final Holder operationrequest = new Holder<>(); final Holder> items = new Holder<>(); final String marketplaceDomain = ""; // not yet supported final String xmlEscaping = "Single"; final String validate = ""; // Probe Amazon Server, WARNING! Amazon changes the parms to this method frequently. // Inserts awsAccessKeyID and associateTag // note order validate, xmlEscaping, different from itemSearch if ( false ) { out.println( "isbn13 = " + isbn13 ); out.println( "bookstore = " + bookstore ); out.println( "AWS_ACCESS_KEY_ID = " + AWS_ACCESS_KEY_ID ); out.println( "AWS_SECRET_ACCESS_KEY = " + AWS_SECRET_ACCESS_KEY ); out.println( "associateTag = " + associateTag ); out.println( "port = " + port ); } if ( false ) { out.println( "Probing AWS for " + bookstore.getEnumName() + " to lookup ISBN " + isbn13 + " to find the ASIN" ); } port.itemLookup( marketplaceDomain, AWS_ACCESS_KEY_ID, associateTag, validate, xmlEscaping, itemLookupRequest, itemLookupRequests, operationrequest, items ); // analyse results final List result = items.value; final int size = result.get( 0 ).getItem().size(); for ( int i = 0; i < size; i++ ) { final Item item = result.get( 0 ).getItem().get( i ); final String asin = item.getASIN(); if ( asin != null ) { out.println( "ISBN " + isbn13 + " -> " + asin + " lookup successful for store " + bookstore.getEnumName() ); return asin; } } //end loop to process results // if it simply does not have in in the database out.println( "ISBN " + isbn13 + " has no corresponding ASIN for store " + bookstore.getEnumName() ); return null; } catch ( MalformedURLException e ) { throw new IllegalArgumentException( "Amazon JAX bug. Generating malformed URLS" ); } catch ( WebServiceException e ) { err.println( "Error: Unable to find asin for " + isbn13 + " at AWS server for store " + bookstore.getEnumName() + " " + e .getMessage() ); return null; } } /** * probe the amazon AWS api to find if a given asin is in stock. * * @param asin Amazon part number * * @return StockStatus enum */ public static StockStatus isASINInStockViaAWS( final String asin, final OnlineStore store ) { try { store.setProbeCompletionStage( 5 ); final String associateTag = store.getAccount(); // this is how we select which country Amazon store to probe. // The following line takes quite a while final AWSECommerceServicePortType port = store.getPort( getService() ); if ( port == null ) { err.println( "Unable to make AWS connection to " + store.getEnumName() ); return StockStatus.REFUSINGPROBES; } // new ItemLookup final ItemLookup itemLookup = new ItemLookup(); // new ItemLookupRequest which is part of the ItemLookup final ItemLookupRequest itemLookupRequest = new ItemLookupRequest(); // odd our lookup request to the the Lookup. final List itemLookupRequests = itemLookup.getRequest(); itemLookupRequests.add( itemLookupRequest ); // Set up the values of the ItemLookupRequest // lookup of variants only works with ASIN itemLookupRequest.setIdType( "ASIN" ); itemLookupRequest.getItemId().add( asin ); // there is no setItemID method itemLookupRequest.setMerchantId( "All" ); itemLookupRequest.setCondition( "All" ); // specify info to include in response final List responseGroup = itemLookupRequest.getResponseGroup(); responseGroup.add( "Offers" ); // set up Holder for the response tree final Holder operationRequest = new Holder<>(); final Holder> items = new Holder<>(); final String marketplaceDomain = ""; // not yet supported final String xmlEscaping = "Single"; final String validate = ""; store.setProbeCompletionStage( 6 ); // Probe Amazon Server, WARNING! Amazon changes the parms to this method frequently. // Inserts awsAccessKeyID and associateTag // note order validate, xmlEscaping, different from itemSearch port.itemLookup( marketplaceDomain, AWS_ACCESS_KEY_ID, associateTag, validate, xmlEscaping, itemLookupRequest, itemLookupRequests, operationRequest, items ); store.setProbeCompletionStage( 7 ); // analyse results final List result = items.value; final int size = result.get( 0 ).getItem().size(); for ( int i = 0; i < size; i++ ) { final Item item = result.get( 0 ).getItem().get( i ); final Offers offers = item.getOffers(); if ( offers == null ) { continue; } final List offerList = offers.getOffer(); for ( Offer offer : offerList ) { // getOfferListing will never be null for ( OfferListing offerListing : offer.getOfferListing() ) { final String availabilityType = offerListing.getAvailabilityAttributes().getAvailabilityType(); if ( false ) { out.println( "asin = " + asin + " availabilityType = " + availabilityType ); } // might be "now" or "unknown" (temp out of stock) or "futureDate" if ( availabilityType.equals( "now" ) ) { return StockStatus.INSTOCK; } // other possibilities? ??? } } } //end loop to process results // must be carried in some sense or we would not have an ASIN to lookup. return StockStatus.OUTOFSTOCK; } catch ( MalformedURLException e ) { throw new IllegalArgumentException( "Amazon JAX bug. Generating malformed URLS" ); } } /** * convert isbn13 to asin, probe just as many countries as necessary. * * @param isbn13 isbn13 * * @return corresponding asin, or null if cannot find it. */ public static String isbnToASINviaAWS( final String isbn13 ) { // this is quite slow if you don't get a hit on Amazon.com // We only do it once, and record the failure as a "none" in the cache. String asin = isbnToAsinViaAWSProbingOneStore( isbn13, BStore.AMAZONCOM ); if ( asin != null ) { return asin; } asin = isbnToAsinViaAWSProbingOneStore( isbn13, BStore.AMAZONCA ); if ( asin != null ) { return asin; } asin = isbnToAsinViaAWSProbingOneStore( isbn13, BStore.AMAZONUK ); if ( asin != null ) { return asin; } asin = isbnToAsinViaAWSProbingOneStore( isbn13, BStore.AMAZONDE ); if ( asin != null ) { return asin; } asin = isbnToAsinViaAWSProbingOneStore( isbn13, BStore.AMAZONFR ); if ( asin != null ) { return asin; } return isbnToAsinViaAWSProbingOneStore( isbn13, BStore.AMAZONIT ); // don't bother with AMAZONES or AMAZONIN } }