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