/*
* [UpdatePrices.java]
*
* Summary: Add prices to existing Amazon Electronic products from US, Canada and Britain, but not Europe.
*
* 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-17 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.ItemAttributes;
import com.mindprod.aws.jax.ItemLookup;
import com.mindprod.aws.jax.ItemLookupRequest;
import com.mindprod.aws.jax.Items;
import com.mindprod.aws.jax.OfferSummary;
import com.mindprod.aws.jax.OperationRequest;
import com.mindprod.aws.jax.Price;
import com.mindprod.common18.EIO;
import com.mindprod.common18.Misc;
import com.mindprod.common18.ST;
import com.mindprod.htmlmacros.macro.Global;
import com.mindprod.htmlmacros.support.ConfigurationForMindprod;
import com.mindprod.hunkio.HunkIO;
import com.mindprod.stores.BStore;
import com.mindprod.stores.ElectronicMacroFileFilter;
import com.mindprod.stores.OnlineStore;
import com.mindprod.stores.TidyElectronicMacros;
import javax.xml.namespace.QName;
import javax.xml.ws.Holder;
import javax.xml.ws.WebServiceException;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.List;
import java.util.regex.Pattern;
import static java.lang.System.*;
/**
* Add prices to existing Amazon Electronic products from US, Canada and Britain, but not Europe.
*
* Needs unlimited key JCE extension.
* Updates title and price is both ElectronicHead and Electronic macro
*
* @author Roedy Green, Canadian Mind Products
* @version 1.0 2012-03-17 initial version
* // OBSOLETE???
* @since 2012-03-17
*/
public class UpdatePrices extends com.mindprod.stores.TidyElectronicMacros
{
// declarations
/**
* true if debugging, and want extra output
*/
private static final boolean DEBUGGING = false;
/**
* how many milliseconds to stall between probes
*/
private static final long STALL = 2000;
/**
* factor to convert CAD to US$ 2016-05-31
*/
private static final double EXCHANGE_CAD = 0.76d;
/**
* factor to convert GBP to US$ 2016-05-31
*/
private static final double EXCHANGE_GBP = 1.45d;
/**
* 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" );
/**
* Namespace 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";
/**
* 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";
/**
* Template for two decimal places.
*/
private static final DecimalFormat DF2 = new DecimalFormat( "###,##0.00" );
/**
* split asin at comma, gets rid of spaces around comma
*/
private static final Pattern SPLIT_ON_COMMA = Pattern.compile( "\\s*,\\s*" );
// /declarations
// methods
/**
* 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( OnlineStore store ) 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 store.getPort( service );
}// /method
/**
* query AWS database
*
* @param asin Amazon part number
* @param responseGroup which fields we want
* @param store which store's database to query *
*
* @return data items returned
* @throws MalformedURLException if bad URL
*/
private static Holder> queryAWS( final String asin, final String responseGroup,
final OnlineStore store ) throws MalformedURLException
{
// for amazon.com in USA
// define magic configuring strings
final AWSECommerceServicePortType port = getPort( store );
// 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 responseGroupList = itemLookupRequest.getResponseGroup();
// responseGroup.add( "ItemAttributes" );
responseGroupList.add( responseGroup );
// 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 associateTagUS
// note order validate, xmlEscaping, different from itemSearch
port.itemLookup( marketplaceDomain, AWS_ACCESS_KEY_ID, store.getAccount(), validate, xmlEscaping,
itemLookupRequest, itemLookupRequests, operationRequest, items );
return items;
}// /method
/**
* Get price for list of asins. picks first price, not best price.
*
* @return price in form $12.45
* @see FetchElectronicFacts
*/
static String queryAdjustedPrice( final String asins ) throws MalformedURLException
{
final String price = queryFirstPrice( asins ).replace( ",", "" );
if ( price.startsWith( "CDN$ " ) )
{
out.println( price );
return '$' + DF2.format( Double.parseDouble( price.substring( 5 ) ) * EXCHANGE_CAD );
}
if ( price.startsWith( "\u00a3" ) )
{
out.println( price );
return '$' + DF2.format( Double.parseDouble( price.substring( 1 ) ) * EXCHANGE_GBP );
}
else
{
return price;
}
}// /method
/**
* Get price for list of asins. picks first price, not best price.
*
* @return price in form CDN$ 137.00
* @see FetchElectronicFacts
*/
private static String queryFirstPrice( final String asins ) throws MalformedURLException
{
final String[] allAsins = SPLIT_ON_COMMA.split( asins );
String price;
for ( String asin : allAsins )
{
price = queryNewPrice( asin, BStore.AMAZONCOM );
if ( price != null )
{
return price;
}
}
for ( String asin : allAsins )
{
price = queryNewPrice( asin, BStore.AMAZONCA );
if ( price != null )
{
return price;
}
}
for ( String asin : allAsins )
{
price = queryNewPrice( asin, BStore.AMAZONUK );
if ( price != null )
{
return price;
}
}
for ( String asin : allAsins )
{
price = queryListPrice( asin, BStore.AMAZONCOM );
if ( price != null )
{
return price;
}
}
for ( String asin : allAsins )
{
price = queryListPrice( asin, BStore.AMAZONCA );
if ( price != null )
{
return price;
}
}
for ( String asin : allAsins )
{
price = queryListPrice( asin, BStore.AMAZONUK );
if ( price != null )
{
return price;
}
}
for ( String asin : allAsins )
{
price = queryUsedPrice( asin, BStore.AMAZONCOM );
if ( price != null )
{
return price;
}
}
for ( String asin : allAsins )
{
price = queryUsedPrice( asin, BStore.AMAZONCA );
if ( price != null )
{
return price;
}
}
for ( String asin : allAsins )
{
price = queryUsedPrice( asin, BStore.AMAZONUK );
if ( price != null )
{
return price;
}
}
return "$0.00";
}// /method
/**
* Probe the a store for list price
*
* @return price as formatted String
* @throws java.net.MalformedURLException if generated URL is malformed.
*/
private static String queryListPrice( final String asin, final OnlineStore store ) throws MalformedURLException
{
final Holder> items = queryAWS( asin, "ItemAttributes", store );
// analyse results
//
//-
//
//
//
// $99.99
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 itemAttributes = item.getItemAttributes();
if ( itemAttributes == null )
{
continue;
}
final Price listPrice = itemAttributes.getListPrice();
if ( listPrice == null )
{
continue;
}
String price = listPrice.getFormattedPrice();
if ( price == null )
{
continue;
}
if ( price.equals( "Too low to display" ) )
{
continue;
}
return price;
} //end loop to process new results
return null;
}// /method
/**
* Probe the amazon AWS for new price
*
* @param asin asin to look up
* @param store store database to look it up in
*
* @return price as formatted String
* @throws java.net.MalformedURLException if generated URL is malformed.
*/
private static String queryNewPrice( final String asin, final OnlineStore store ) throws MalformedURLException
{
final Holder
> items = queryAWS( asin, "OfferSummary", store );
// analyse results
//
//-
//
//
//
// $99.99
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 OfferSummary offerSummary = item.getOfferSummary();
if ( offerSummary == null )
{
continue;
}
final Price lowestNewPrice = offerSummary.getLowestNewPrice();
if ( lowestNewPrice == null )
{
continue;
}
String price = lowestNewPrice.getFormattedPrice();
if ( price == null )
{
continue;
}
if ( price.equals( "Too low to display" ) )
{
continue;
}
return price;
} //end loop to process new results
return null;
}// /method
/**
* Probe the amazon AWS for used price
*
* @return price as formatted String
* @throws java.net.MalformedURLException if generated URL is malformed.
*/
private static String queryUsedPrice( final String asin, final OnlineStore store ) throws MalformedURLException
{
final Holder
> items = queryAWS( asin, "OfferSummary", store );
final List result = items.value;
final int size = result.get( 0 ).getItem().size();
// look for used price if can't find new one
// analyse results
//
//-
//
//
//
// $99.99
//
for ( int i = 0; i < size; i++ )
{
final Item item = result.get( 0 ).getItem().get( i );
final OfferSummary offerSummary = item.getOfferSummary();
if ( offerSummary == null )
{
continue;
}
final Price lowestUsedPrice = offerSummary.getLowestUsedPrice();
if ( lowestUsedPrice == null )
{
continue;
}
String price = lowestUsedPrice.getFormattedPrice();
if ( price == null )
{
continue;
}
if ( price.equals( "Too low to display" ) )
{
continue;
}
return price;
} //end loop to process new results
return null;
}// /method
/**
* Updates all prices in Amazon electronic products
*
* @param args isbn13/EAN to start probing, optional
*
* @throws java.io.IOException if trouble reading/writing files
* @see FetchBookFacts
*/
public static void main( String[] args ) throws IOException
{
Global.installConfiguration( new ConfigurationForMindprod() );
final File webrootDir = new File( Global.configuration.getLocalWebrootWithSlashes() );
electronicDir = new File( webrootDir, "electronic" );
if ( DEBUGGING )
{
out.println( "AWS_ACCESS_KEY_ID = " + AWS_ACCESS_KEY_ID );
out.println( "AWS_SECRET_ACCESS_KEY = " + AWS_SECRET_ACCESS_KEY );
}
final String[] files = electronicDir.list( new ElectronicMacroFileFilter() );
for ( String filename : files )
{
try
{
Thread.sleep( STALL );
}
catch ( InterruptedException e )
{
}
final File file = new File( electronicDir, filename );
final String big = HunkIO.readEntireFile( file, HunkIO.UTF8 );
final String electronicMacro = findFirstMacroContents( big, "Electronic" );
if ( electronicMacro == null )
{
err.println( "missing