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.
* <p/>
* 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
{
/**
* 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;
/**
* 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;
}
final AWSECommerceServicePortType port = getPort();
final ItemLookup itemLookup = new ItemLookup();
final ItemLookupRequest itemLookupRequest = new ItemLookupRequest();
final List<ItemLookupRequest> itemLookupRequests = itemLookup.getRequest();
itemLookupRequests.add( itemLookupRequest );
itemLookupRequest.setIdType( "ASIN" );
itemLookupRequest.getItemId().add( asin );
final List<String> responseGroup = itemLookupRequest.getResponseGroup();
responseGroup.add( "ItemAttributes" );
final Holder<OperationRequest> operationrequest = new Holder<>();
final Holder<List<Items>> items = new Holder<>();
final String marketplaceDomain = "";
final String xmlEscaping = "Single";
final String validate = "";
port.itemLookup( marketplaceDomain, AWS_ACCESS_KEY_ID, associateTag, validate, xmlEscaping,
itemLookupRequest, itemLookupRequests, operationrequest, items );
final List<Items> 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;
}
/**
* 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 );
get.setConnectTimeout( CONNECT_TIMEOUT );
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;
}
}
/**
* 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
{
final FastCat sb = new FastCat( 36 );
sb.append( "<!-- macro BookHead {" );
sb.append( title );
sb.append( "} published=", published, " -->\n" );
sb.append( "<!-- macro Book isbn=", isbn13, "\n" );
if ( paperback != null && paperback.length() != 0 )
{
sb.append( "paperback=", paperback, "\n" );
}
if ( hardcover != null && hardcover.length() != 0 )
{
sb.append( "hardcover=", hardcover, "\n" );
}
if ( kindle != null && kindle.length() != 0 )
{
sb.append( "kindle=", kindle, "\n" );
}
if ( eBook != null && eBook.length() != 0 )
{
sb.append( "ebook=", eBook, "\n" );
}
if ( audio != null && audio.length() != 0 )
{
sb.append( "audio=", audio, "\n" );
}
sb.append( "title={", title, "}\n" );
sb.append( "author={", author, "}\n" );
sb.append( "publisher={", publisher, "}", " published=", published, "\n" );
sb.append( "notes={} -->\n" );
sb.append( "<!-- macro Foot -->\n" );
final String generated = sb.toString();
HunkIO.writeEntireFile( target, generated, HunkIO.UTF8 );
out.println();
out.println( EIO.getCanOrAbsPath( target ) + " written." );
}
/**
* 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
{
AWSECommerceService service = new AWSECommerceService( new URL( WSDL_LOCATION ), new QName( NAME_SPACE_URI,
QNAME ) );
service.setHandlerResolver( new AwsHandlerResolver( AWS_SECRET_ACCESS_KEY ) );
return primaryBStore.getPort( service );
}
/**
* 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
{
final AWSECommerceServicePortType port = getPort();
final ItemLookup itemLookup = new ItemLookup();
final ItemLookupRequest itemLookupRequest = new ItemLookupRequest();
final List<ItemLookupRequest> itemLookupRequests = itemLookup.getRequest();
itemLookupRequests.add( itemLookupRequest );
itemLookupRequest.setSearchIndex( "Books" );
itemLookupRequest.setIdType( "EAN" );
itemLookupRequest.getItemId().add( isbn13 );
final List<String> responseGroup = itemLookupRequest.getResponseGroup();
responseGroup.add( "Images" );
responseGroup.add( "ItemAttributes" );
final Holder<OperationRequest> operationrequest = new Holder<>();
final Holder<List<Items>> items = new Holder<>();
final String marketplaceDomain = "";
final String xmlEscaping = "Single";
final String validate = "";
port.itemLookup( marketplaceDomain, AWS_ACCESS_KEY_ID, associateTag, validate, xmlEscaping,
itemLookupRequest, itemLookupRequests, operationrequest, items );
final List<Items> 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<String> authors = attributes.getAuthor();
final FastCat sb = new FastCat( authors.size() * 2 );
for ( String anAuthor : authors )
{
sb.append( anAuthor );
sb.append( ", " );
}
sb.drop();
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;
}
}
}
/**
* 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();
final ItemLookup itemLookup = new ItemLookup();
final ItemLookupRequest itemLookupRequest = new ItemLookupRequest();
final List<ItemLookupRequest> itemLookupRequests = itemLookup.getRequest();
itemLookupRequests.add( itemLookupRequest );
itemLookupRequest.setIdType( "ASIN" );
itemLookupRequest.getItemId().add( primaryAsin );
final List<String> responseGroup = itemLookupRequest.getResponseGroup();
responseGroup.add( "AlternateVersions" );
final Holder<OperationRequest> operationRequest = new Holder<>();
final Holder<List<Items>> items = new Holder<>();
final String marketplaceDomain = "";
final String xmlEscaping = "Single";
final String validate = "";
port.itemLookup( marketplaceDomain, AWS_ACCESS_KEY_ID, associateTag, validate, xmlEscaping,
itemLookupRequest, itemLookupRequests, operationRequest, items );
final List<Items> 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 );
}
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 );
}
}
}
}
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 );
}
/**
* 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:
}
}
/**
* 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
{
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 );
}
}