/* * [FetchBookFacts.java] * * Summary: Get basic facts about a new book from Amazon AWS API including alternate bindings. Used to add new books. * * 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-04 initial version * 1.1 2012-03-07 get alternate bindings probe working. */ 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.ItemAttributes; import com.mindprod.aws.jax.ItemLookup; import com.mindprod.aws.jax.ItemLookupRequest; import com.mindprod.aws.jax.Items; import com.mindprod.aws.jax.OperationRequest; import com.mindprod.common18.EIO; import com.mindprod.common18.Misc; import com.mindprod.fastcat.FastCat; import com.mindprod.filetransfer.FileTransfer; import com.mindprod.htmlmacros.macro.Global; import com.mindprod.htmlmacros.support.ConfigurationForMindprod; import com.mindprod.http.Get; import com.mindprod.hunkio.HunkIO; import com.mindprod.isbn.ISBNValidate; import com.mindprod.stores.BStore; import javax.xml.namespace.QName; import javax.xml.ws.Holder; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.lang.System.*; /** * Get basic facts about a new book from Amazon AWS API including alternate bindings. Used to add new books. *

* It does not fetch info about whether the books are in stock. * * @author Roedy Green, Canadian Mind Products * @version 1.1 2012-03-07 get alternate bindings probe working. * @see AddBindings * @see Bindings for binding categories * @since 2012-03-04 */ public class FetchBookFacts { // declarations /** * true if debugging, and want extra output */ private static final boolean DEBUGGING = false; /** * how long to give B&N to respond in millis. */ private static final int CONNECT_TIMEOUT = ( int ) TimeUnit.SECONDS.toMillis( 70 ); /** * 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" ); /** * Nampspace for amazon.com in the USA. Would have to be modified for other countries. */ 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"; private static final String USAGE = "\nUsage: FetchBookFacts.jar isbn"; /** * 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 */ private static final String WSDL_LOCATION = "file:///E:/com/mindprod/aws/jax/AWSECommerceService.wsdl"; /** * pattern marking an nook ean */ private static final Pattern EBOOK_PATTERN = Pattern.compile( "\"NOOKEAN\"\\s+:\\s+\"(97[0-9]{11})\"," ); /** * fetch public amazon.com-assigned associateTag from environment set awsassociatetag=xxxxx */ private static String associateTag; /** * audio EAN */ private static String audio; /** * author's name */ private static String author; /** * isbn of ebook from nook */ private static String eBook; /** * hardcover EAN */ private static String hardcover; /** * primary EAN */ private static String isbn13; /** * primaryAsin of kindle */ private static String kindle; /** * EAN of paperback */ private static String paperback; /** * primary primaryAsin */ private static String primaryAsin; /** * yyyy-mm-dd published */ private static String published; /** * publisher */ private static String publisher; /** * title of book */ private static String title; /** * Which store do we want to probe for new products. */ private static BStore primaryBStore; /** * where Book cover images files are kept */ private static File bookCoverDir; /** * where Book files are kept */ private static File bookDir; // /declarations // methods /** * Convert primaryAsin back to EAN isbn-13 * * @param asin amazon primaryAsin number of book. If EAN already returns EAN. * * @return isbn13 * @throws java.net.MalformedURLException if generated URL is malformed. */ private static String asinToEAN( final String asin ) throws MalformedURLException { if ( asin == null || asin.length() == 13 ) { return asin; // actually EAN already } // define magic configuring strings final AWSECommerceServicePortType port = getPort(); // 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.setIdType( "ASIN" ); itemLookupRequest.getItemId().add( asin ); // there is no setItemID method // 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 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 ItemAttributes attributes = item.getItemAttributes(); if ( attributes != null ) { return attributes.getEAN(); } } err.println( "cannot find EAN corresponding to " + asin ); return null; }// /method /** * get isbn13 of Nook ebook equivalent * * @param isbn13 * * @return ebook-isbn13 or null if none */ private static final String fetchNookEAN( String isbn13 ) { final Get get = new Get(); get.setInstanceFollowRedirects( true ); // chase moved urls. get.setConnectTimeout( CONNECT_TIMEOUT ); // B&N will give us a 301 moved permanently with an url of the form: // http://search.barnesandnoble.com/title/author/e/9780321480910 try { final String response = get.send( new URL( "http://search.barnesandnoble" + ".com/booksearch/isbninquiry" + ".asp?isbn=" + isbn13 ), Get.UTF8 ); if ( response == null || response.length() == 0 ) { err.println( get.getResponseMessage() + " for " + isbn13 ); err.println( get.getInterruptResponseMessage() ); return null; } final Matcher m = EBOOK_PATTERN.matcher( response ); if ( m.find() ) { return m.group( 1 ); } else { return null; } } catch ( MalformedURLException e ) { err.println( "could not build URL" ); return null; } }// /method /** * generate the xxxx.html file containing the Book macro with info we gleaned from Amazon. * * @param target file where write generated Book macro * * @throws IOException if trouble writing file */ private static void generate( final File target ) throws IOException { // does not handle birth/death final FastCat sb = new FastCat( 36 ); sb.append( "\n" ); sb.append( "\n" ); sb.append( "\n" ); final String generated = sb.toString(); HunkIO.writeEntireFile( target, generated, HunkIO.UTF8 ); out.println(); out.println( EIO.getCanOrAbsPath( target ) + " written." ); }// /method /** * 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 AWSECommerceServicePortType getPort() 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 ) ); // Set the service port: return primaryBStore.getPort( service ); }// /method /** * Probe the amazon AWS api for isbn13, results in class vars * * @throws java.net.MalformedURLException if generated URL is malformed. */ private static void queryISBN() throws MalformedURLException { // for amazon.com in USA // define magic configuring strings final AWSECommerceServicePortType port = getPort(); // 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 // specify info to include in response final List responseGroup = itemLookupRequest.getResponseGroup(); responseGroup.add( "Images" ); 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 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 ); primaryAsin = item.getASIN(); final ItemAttributes attributes = item.getItemAttributes(); title = attributes.getTitle(); List authors = attributes.getAuthor(); final FastCat sb = new FastCat( authors.size() * 2 ); for ( String anAuthor : authors ) { sb.append( anAuthor ); sb.append( ", " ); } sb.drop(); // last , if any author = sb.toString(); publisher = attributes.getPublisher(); if ( publisher.equals( "O'Reilly Media" ) ) { publisher = "O’Reilly"; } published = attributes.getReleaseDate(); published = attributes.getPublicationDate(); final String binding = attributes.getBinding(); if ( binding != null ) { if ( binding.equals( "Paperback" ) ) { paperback = isbn13; } else if ( binding.equals( "Hardcover" ) ) { hardcover = isbn13; } } out.println( "isbn:" + isbn13 + " primaryAsin: " + primaryAsin + " title:" + title + " author:" + author + " publisher:" + publisher + " published:" + published + " binding: " + binding ); if ( item.getLargeImage() == null ) { err.println( "no image for " + isbn13 ); } else { final URL u = new URL( item.getLargeImage().getURL() ); File target = new File( bookCoverDir, isbn13 + ".jpg" ); new FileTransfer().download( u, target, false ); out.println(); out.println( EIO.getCanOrAbsPath( target ) + " downloaded" ); break; } } //end loop to process results }// /method /** * probe the amazon AWS api to find alternate bindings. * * @throws java.net.MalformedURLException if generated URL is malformed */ private static void queryOtherBindings() throws MalformedURLException { final AWSECommerceServicePortType port = getPort(); // 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( primaryAsin ); // there is no setItemID method // specify info to include in response final List responseGroup = itemLookupRequest.getResponseGroup(); responseGroup.add( "AlternateVersions" ); // 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 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 ); Item.AlternateVersions alternateVersions = item.getAlternateVersions(); if ( alternateVersions != null ) { for ( Item.AlternateVersions.AlternateVersion alternateVersion : alternateVersions .getAlternateVersion() ) { final String asin = alternateVersion.getASIN(); final String bindingName = alternateVersion.getBinding(); final Binding b = Binding.exactMatch( bindingName ); recordBindingASIN( b, asin ); } // end loop to process alternateVersions pass 1 for ( Item.AlternateVersions.AlternateVersion alternateVersion : alternateVersions .getAlternateVersion() ) { final String asin = alternateVersion.getASIN(); final String bindingName = alternateVersion.getBinding(); final Binding b = Binding.approximateMatch( bindingName ); if ( b == null ) { err.println( "Unrecognised binding " + bindingName + " on asin:" + asin ); } else { recordBindingASIN( b, asin ); } } // end loop to process alternateVersions pass 2 } // end alternateVersions not null } //end loop to process results paperback = asinToEAN( paperback ); hardcover = asinToEAN( hardcover ); audio = asinToEAN( audio ); out.println( "Alternate bindings" + " primaryAsin:" + primaryAsin + " paperback:" + paperback + " hardcover:" + hardcover + " audio:" + audio + " kindle:" + kindle + " eBook:" + eBook ); }// /method /** * record the asin in the corresponding binding variable, if we don't have a value already. */ private static void recordBindingASIN( final Binding b, final String asin ) { if ( b == null ) { return; } switch ( b ) { case PAPERBACK: if ( paperback == null ) { paperback = asin; } break; case HARDCOVER: if ( hardcover == null ) { hardcover = asin; } break; case AUDIO: if ( audio == null ) { audio = asin; } break; case KINDLE: if ( kindle == null ) { kindle = asin; } break; case EBOOK: case OTHER: default: // ignore } }// /method /** * takes an isbn13 on the command line. Creates a Book macro for it and downloads book cover. * * @param args isbn13/EAN to probe. * * @throws java.io.IOException if trouble reading/writing files */ public static void main( String[] args ) throws IOException { // BStore enum inits needs access to the global configuration Global.installConfiguration( new ConfigurationForMindprod() ); final File webrootDir = new File( Global.configuration.getLocalWebrootWithSlashes() ); bookDir = new File( webrootDir, "book" ); bookCoverDir = new File( webrootDir, "image/bookcover" ); primaryBStore = BStore.AMAZONCOM; associateTag = primaryBStore.getAccount(); if ( DEBUGGING ) { out.println( "primaryBStore = " + primaryBStore.name() ); out.println( "associateTag = " + associateTag ); out.println( "AWS_ACCESS_KEY_ID = " + AWS_ACCESS_KEY_ID ); out.println( "AWS_SECRET_ACCESS_KEY = " + AWS_SECRET_ACCESS_KEY ); } if ( args.length != 1 ) { throw new IllegalArgumentException( USAGE ); } isbn13 = ISBNValidate.tidyISBN10or13RemovingDashes( args[ 0 ] ); final File target = new File( bookDir, isbn13 + ".html" ); if ( target.exists() ) { err.println( "book already exists at " + EIO.getCanOrAbsPath( target ) ); } else { queryISBN(); queryOtherBindings(); eBook = fetchNookEAN( isbn13 ); generate( target ); } Misc.trackLastThread(); System.exit( 0 ); }// /method // /methods }