Index: /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/DictionaryWhitelist.java
===================================================================
--- /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/DictionaryWhitelist.java	(revision 31175)
+++ /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/DictionaryWhitelist.java	(revision 31175)
@@ -0,0 +1,39 @@
+package org.hathitrust.extractedfeatures;
+
+
+import java.util.Scanner;
+
+public class DictionaryWhitelist {
+	
+	public static void main(String[] args) 
+	{
+		Scanner scan = new Scanner(System.in);
+		System.out.println("Press enter to start:");
+		String wait = scan.nextLine();
+		
+		String dictionary_filename = "whitelist-placeholder.txt";
+		
+			
+		WhitelistBloomFilter whitelist = new WhitelistBloomFilter(dictionary_filename);
+		//WhitelistHashmap whitelist = new WhitelistHashmap(dictionary_filename);
+		
+		whitelist.storeEntries();
+				
+		System.out.println("Enter words to test (enter '.quit' to quit program");
+		
+		boolean quit = false;
+		while (!quit) {
+			String test_word = scan.next();
+			boolean might_contain_test = whitelist.contains(test_word);
+			if (test_word.equals(".quit")) {
+				quit = true;
+			}
+			else {
+				System.out.println("Whitelist ["+test_word+"] = " + might_contain_test);
+			}
+		}
+		scan.close();
+		System.out.println("Program ended.");
+	}
+
+}
Index: /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/PerVolumeWordStreamFlatmap.java
===================================================================
--- /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/PerVolumeWordStreamFlatmap.java	(revision 31175)
+++ /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/PerVolumeWordStreamFlatmap.java	(revision 31175)
@@ -0,0 +1,116 @@
+package org.hathitrust.extractedfeatures;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.spark.api.java.function.FlatMapFunction;
+import org.apache.spark.util.DoubleAccumulator;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+class PerVolumeWordStreamFlatmap implements FlatMapFunction<String, String>
+{
+	private static final long serialVersionUID = 1L;
+	
+	protected String _input_dir;
+	protected int    _verbosity;
+	
+	protected DoubleAccumulator _progress_accum;
+	protected double            _progress_step;
+	
+	boolean _strict_file_io;
+	
+	public PerVolumeWordStreamFlatmap(String input_dir, int verbosity, 
+					 		  DoubleAccumulator progress_accum, double progress_step,
+					 		  boolean strict_file_io)
+	{
+		_input_dir  = input_dir;
+		_verbosity  = verbosity;
+		
+		_progress_accum = progress_accum;
+		_progress_step  = progress_step;
+		
+		_strict_file_io = strict_file_io;
+	}
+	
+	public Iterator<String> call(String json_file_in) throws IOException
+	{ 
+	     
+		String full_json_file_in = _input_dir + "/" + json_file_in;
+		JSONObject extracted_feature_record = JSONClusterFileIO.readJSONFile(full_json_file_in);
+		
+		ArrayList<String> all_word_list = new ArrayList<String>();
+		
+		if (extracted_feature_record != null) {
+			String volume_id = extracted_feature_record.getString("id");
+
+			JSONObject ef_features = extracted_feature_record.getJSONObject("features");
+
+			int ef_page_count = ef_features.getInt("pageCount");
+
+			if (_verbosity >= 1) {
+				System.out.println("Processing: " + json_file_in);
+				System.out.println("  pageCount = " + ef_page_count);
+			}
+
+			JSONArray ef_pages = ef_features.getJSONArray("pages");
+			int ef_num_pages = ef_pages.length();
+			if (ef_num_pages != ef_page_count) {
+				System.err.println("Warning: number of page elements in JSON (" + ef_num_pages + ")"
+						+" does not match 'pageCount' metadata (" + ef_page_count + ")"); 
+			}
+	
+			if (_verbosity >= 2) {
+				System.out.print("  Pages: ");
+			}
+
+			for (int i = 0; i < ef_page_count; i++) {
+				String formatted_i = String.format("page-%06d", i);
+				String page_id = volume_id + "." + formatted_i;
+
+				if (_verbosity >= 2) {
+					if (i>0) {
+						System.out.print(", ");
+					}
+					System.out.print(page_id);
+				}
+
+				if (i==(ef_page_count-1)) {
+					if (_verbosity >= 2) {
+						System.out.println();
+					}
+				}
+
+				JSONObject ef_page = ef_pages.getJSONObject(i);
+
+				if (ef_page != null) {
+					
+					ArrayList<String> page_word_list = SolrDocJSON.generateTokenPostCountText(volume_id, page_id, ef_page);					
+					all_word_list.addAll(page_word_list);
+				}
+				else {
+					System.err.println("Skipping: " + page_id);
+				}
+			}
+		}
+		else {
+			// File did not exist, or could not be parsed
+			String mess = "Failed to read in bzipped JSON file '" + full_json_file_in + "'";
+			if (_strict_file_io) {
+				throw new IOException(mess);
+			}
+			else {
+				System.err.println("Warning: " + mess);
+				System.out.println("Warning: " + mess);
+			}
+		}
+
+		_progress_accum.add(_progress_step);
+		
+		return all_word_list.iterator();
+	}
+	
+	
+}
+
Index: /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/ProcessForWhitelist.java
===================================================================
--- /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/ProcessForWhitelist.java	(revision 31175)
+++ /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/ProcessForWhitelist.java	(revision 31175)
@@ -0,0 +1,186 @@
+package org.hathitrust.extractedfeatures;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Serializable;
+import org.apache.commons.cli.*;
+
+import org.apache.spark.api.java.*;
+import org.apache.spark.api.java.function.Function2;
+import org.apache.spark.api.java.function.PairFunction;
+import org.apache.spark.util.DoubleAccumulator;
+import scala.Tuple2;
+
+import org.apache.spark.SparkConf;
+
+public class ProcessForWhitelist implements Serializable
+{
+	private static final long serialVersionUID = 1L;
+
+	// Following details on number of partitions to use given in 
+	//  "Parallelized collections" section of:
+	//   https://spark.apache.org/docs/2.0.1/programming-guide.html
+	//
+	// For a more detailed discussion see:
+	//   http://blog.cloudera.com/blog/2015/03/how-to-tune-your-apache-spark-jobs-part-2/
+	
+	protected static final int DEFAULT_NUM_CORES = 6;
+	protected static final int DEFAULT_NUM_PARTITIONS = 3*DEFAULT_NUM_CORES; 
+	
+	protected String _input_dir;
+	protected String _json_list_filename;
+	
+	protected int    _verbosity;
+
+	public ProcessForWhitelist(String input_dir, String json_list_filename, int verbosity)
+	{
+		_input_dir = input_dir;
+		_json_list_filename = (json_list_filename != null) ? json_list_filename : input_dir;
+
+		_verbosity  = verbosity;
+	}
+
+	protected String generateSparkAppName(String exec_mode)
+	{
+		String spark_app_name = "[" + exec_mode + "] Extracted Features: Process for Whitelist";
+		spark_app_name += " [" + _json_list_filename + "]";
+	
+		return spark_app_name;
+	}
+		
+		
+	
+	public void execWordCount()
+	{	
+		String spark_app_name = generateSparkAppName("Per Page");		
+		
+		SparkConf conf = new SparkConf().setAppName(spark_app_name);
+		JavaSparkContext jsc = new JavaSparkContext(conf);
+
+		/*
+		if (_verbosity >= 2) {
+			System.out.println("Default Minimum Partions: " + jsc.defaultMinPartitions());
+			System.out.println("Default Parallelism: " + jsc.defaultParallelism());
+		}
+			*/
+		
+		int num_partitions = Integer.getInteger("wcsa-ef-ingest.num-partitions", DEFAULT_NUM_PARTITIONS);
+		JavaRDD<String> json_list_data = jsc.textFile(_json_list_filename,num_partitions).cache();
+
+		long num_volumes = json_list_data.count();
+		double per_vol = 100.0/(double)num_volumes;
+		
+		//JavaRDD<String> json_list_data_rp = json_list_data.repartition((int)(num_volumes/100));
+
+		DoubleAccumulator per_vol_progress_accum = jsc.sc().doubleAccumulator("Per Volume Progress Percent");
+		
+		//String strict_file_io_str = System.getProperty("wcsa-ef-ingest.strict-file-io","true");
+		boolean strict_file_io = Boolean.getBoolean("wcsa-ef-ingest.strict-file-io");
+				
+		PerVolumeWordStreamFlatmap paged_solr_wordfreq_flatmap 
+			= new PerVolumeWordStreamFlatmap(_input_dir,_verbosity, 
+								     per_vol_progress_accum,per_vol,
+								     strict_file_io);
+		JavaRDD<String> words = json_list_data.flatMap(paged_solr_wordfreq_flatmap); // .cache() *****
+		
+		
+		JavaPairRDD<String, Integer> pairs = words.mapToPair(new PairFunction<String, String, Integer>() {
+			public Tuple2<String, Integer> call(String s) { return new Tuple2<String, Integer>(s, 1); }
+		});
+
+		JavaPairRDD<String, Integer> counts = pairs.reduceByKey(new Function2<Integer, Integer, Integer>() {
+			public Integer call(Integer a, Integer b) { return a + b; }
+		});
+
+		
+		//JavaRDD<String> per_page_ids = per_page_jsonobjects.map(paged_json_id_map);
+		
+		//long num_page_ids = per_page_ids.count(); // trigger lazy eval of: flatmap:per-vol -> map:per-page
+
+		counts.saveAsTextFile(_json_list_filename + ".out");
+
+
+		//System.out.println("");
+		//System.out.println("############");
+		//System.out.println("# Number of page ids: " + num_page_ids);
+		//System.out.println("############");
+		
+		jsc.close();
+	}
+
+	
+	public static void print_usage(HelpFormatter formatter, Options options)
+	{
+		formatter.printHelp("RUN.bash [options] input-dir json-filelist.txt", options);
+	}
+	
+	public static void main(String[] args) {
+		Options options = new Options();
+
+		Option verbosity_opt = new Option("v", "verbosity", true, 
+				"Set to control the level of debugging output [0=none, 1=some, 2=lots]");
+		verbosity_opt.setRequired(false);
+		options.addOption(verbosity_opt);
+		
+		Option properties_opt = new Option("p", "properties", true, 
+				"Read in the specified Java properties file");
+		properties_opt.setRequired(false);
+		options.addOption(properties_opt);
+		
+		// Need to work with CLI v1.2 as this is the JAR that is bundled with Hadoop/Spark 
+		CommandLineParser parser = new GnuParser(); 
+		//CommandLineParser parser = new DefaultParser(); // if working with CLI v1.3 and above
+		
+		HelpFormatter formatter = new HelpFormatter();
+		CommandLine cmd = null;
+
+		try {
+			cmd = parser.parse(options, args);
+		}
+		catch (ParseException e) {
+			System.err.println(e.getMessage());
+			print_usage(formatter,options);
+			System.exit(1);
+		}
+
+		String verbosity_str = cmd.getOptionValue("verbosity","1");
+		int verbosity = Integer.parseInt(verbosity_str);
+
+		String property_filename = cmd.getOptionValue("properties",null);
+		
+		String[] filtered_args = cmd.getArgs();
+
+		if (filtered_args.length != 2) {
+			print_usage(formatter,options);
+			System.exit(1);
+		}
+	
+		if (property_filename != null) {
+			try {
+				FileInputStream fis = new FileInputStream(property_filename);
+				BufferedInputStream bis = new BufferedInputStream(fis);
+				
+				System.getProperties().load(bis);
+			} 
+			catch (FileNotFoundException e) {
+				e.printStackTrace();
+				System.err.println("File not found: '" + property_filename + "'. Skipping property file read");
+			}
+			catch (IOException e) {
+				System.err.println("IO Exception for: '" + property_filename + "'. Malformed syntax? Skipping property file read");
+			}
+		}
+				
+		String input_dir  = filtered_args[0];
+		String json_list_filename = filtered_args[1];
+		
+		ProcessForWhitelist prep_for_whitelist 
+			= new ProcessForWhitelist(input_dir,json_list_filename,verbosity);
+		
+		//String process_ef_json_mode = System.getProperty("wcsa-ef-ingest.process-ef-json-mode","per-page");
+		prep_for_whitelist.execWordCount();
+	
+	}
+}
Index: /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/WhitelistBloomFilter.java
===================================================================
--- /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/WhitelistBloomFilter.java	(revision 31175)
+++ /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/WhitelistBloomFilter.java	(revision 31175)
@@ -0,0 +1,92 @@
+package org.hathitrust.extractedfeatures;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+
+import com.google.common.hash.BloomFilter;
+import com.google.common.hash.Funnel;
+import com.google.common.hash.Funnels;
+
+public class WhitelistBloomFilter {
+	
+	protected String _dictionary_filename;
+	protected BloomFilter<CharSequence> _bloomFilter;
+	
+	protected int FILL_FACTOR = 26;
+	
+	// http://stackoverflow.com/questions/453018/number-of-lines-in-a-file-in-java
+		public static int countLines(String filename) throws IOException 
+		{
+		    InputStream is = new BufferedInputStream(new FileInputStream(filename));
+		
+		    try {
+		        byte[] c = new byte[1024];
+		        int count = 0;
+		        int readChars = 0;
+		        boolean empty = true;
+		        while ((readChars = is.read(c)) != -1) {
+		            empty = false;
+		            for (int i = 0; i < readChars; ++i) {
+		                if (c[i] == '\n') {
+		                    ++count;
+		                }
+		            }
+		        }
+		        return (count == 0 && !empty) ? 1 : count;
+		    } finally {
+		        is.close();
+		    }
+		}
+		
+		
+	public WhitelistBloomFilter(String dictionary_filename) {
+		System.out.println("Constructing: WhitelistBloomFilter");
+	
+		_dictionary_filename = dictionary_filename;
+		
+		System.out.println("Counting lines in: " + dictionary_filename);
+		int num_lines;
+		try {
+			num_lines = countLines(dictionary_filename);
+			
+			Funnel<CharSequence> string_funnel = Funnels.stringFunnel(StandardCharsets.UTF_8);
+			_bloomFilter = BloomFilter.create(string_funnel, FILL_FACTOR * num_lines,0.01);
+			
+		} catch (IOException e) {
+			
+			e.printStackTrace();
+		}
+	
+	}
+	
+	public void storeEntries() 
+	{
+		System.out.println("Build Bloom filter ...");
+		
+		for (int i=0; i<FILL_FACTOR; i++) {
+			
+			char prefix = (char) ('a' + i);
+			
+			//read file into stream, try-with-resources
+			try (Stream<String> stream = Files.lines(Paths.get(_dictionary_filename))) {
+
+				stream.forEach(word -> {_bloomFilter.put(prefix+word);});
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+		System.out.println("... done");
+
+	}
+	
+	public boolean contains(String key)
+	{
+		return _bloomFilter.mightContain(key);
+	}
+}
Index: /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/WhitelistHashmap.java
===================================================================
--- /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/WhitelistHashmap.java	(revision 31175)
+++ /other-projects/hathitrust/wcsa/extracted-features-solr/trunk/solr-ingest/src/main/java/org/hathitrust/extractedfeatures/WhitelistHashmap.java	(revision 31175)
@@ -0,0 +1,53 @@
+package org.hathitrust.extractedfeatures;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.stream.Stream;
+
+
+public class WhitelistHashmap {
+
+	protected String _dictionary_filename;
+	protected HashMap<String,Boolean> _hashmap;
+	
+	protected int FILL_FACTOR = 26;
+	
+	public WhitelistHashmap(String dictionary_filename) {
+		System.out.println("Constructing: WhitelistHashmap");
+	
+		_dictionary_filename = dictionary_filename;
+		
+		_hashmap = new HashMap<String,Boolean>();
+	}
+	
+	public void storeEntries() 
+	{
+		System.out.println("Build hashmap ...");
+	
+		for (int i=0; i<FILL_FACTOR; i++) {
+			char prefix = (char) ('a' + i);
+			
+			//read file into stream, try-with-resources
+			try (Stream<String> stream = Files.lines(Paths.get(_dictionary_filename))) {
+
+				stream.forEach(word -> {_hashmap.put(prefix+word,true);});
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+		System.out.println("... done");
+
+	}
+	
+	public boolean contains(String key)
+	{
+		return _hashmap.containsKey(key);
+	}
+
+}
