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