package com.manning.hsia.dvdstore.action.filter;

import java.io.IOException;
import java.util.BitSet;
import java.util.Map;

import org.apache.commons.collections.map.ReferenceMap;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.Filter;
import org.apache.lucene.util.DocIdBitSet;
import org.hibernate.search.engine.DocumentBuilder;

import com.manning.hsia.dvdstore.action.StockAction;
import com.manning.hsia.dvdstore.action.StockActionImpl;
import com.manning.hsia.dvdstore.model.Item;

/**
 * Example 8.12
 */
public class StockFilter extends Filter {
	private volatile long lastUpdateTime;  //update timestamp

	@SuppressWarnings("unchecked") 
	private final Map<IndexReader, DocIdSet> cache = 
		new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.HARD);  //keep cache in a SoftHashMap

	@Override
	public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
		StockAction action = getStockAction();    //retrieve the service
		long lastUpdateTime = action.geLastUpdateTime();
		if ( lastUpdateTime != this.lastUpdateTime ) {
			synchronized (cache) {  //clear outdated cache  
				cache.clear();
			}
		}
		synchronized (cache) {
			DocIdSet cached = cache.get(reader);  //check if in cache already
			if (cached != null) return cached;
		}
		//not in cache, build info
		final BitSet bitSet = getAllPositiveBitSet( reader.maxDoc() ); //by default, all documents pass
		
		Term clazzTerm = new Term(DocumentBuilder.CLASS_FIELDNAME, Item.class.getName() );
		if ( reader.docFreq( clazzTerm ) == 0) {  //no need to filter
			//index does not contain Item objects
			//no-op
		}
		else {
			//for each item out of stock, find the corresponding document id by item id
			//and switch off the corresponding bit
			for ( String ean : action.getEanOfItemsOutOfStock() ) {  //invoke external service
				Term term = new Term( "ean", ean );
				TermDocs termDocs = reader.termDocs(term);  //find document by ean
				while ( termDocs.next() ) {
					bitSet.clear(termDocs.doc());
				}
			}
		}
		DocIdSet docIdSet = new DocIdBitSet(bitSet); //build DocIdSet from BitSet
		
		synchronized (cache) {
			cache.put(reader, docIdSet);  //put results in the cache
		}
		this.lastUpdateTime = lastUpdateTime;  //update timestamp
		return docIdSet;
	}

	private BitSet getAllPositiveBitSet(int maxDoc) {
		final BitSet bitSet = new BitSet(maxDoc);
		bitSet.set(0, maxDoc-1);        //new BitSet with all bits on
		return bitSet;
	}

	private StockAction getStockAction() {
		return new StockActionImpl();
	}

}

package com.manning.hsia.dvdstore.action.filter;

import java.io.IOException;
import java.util.BitSet;
import java.util.Collections;
import java.util.Map;

import org.apache.commons.collections.map.ReferenceMap;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.Filter;
import org.apache.lucene.util.DocIdBitSet;
import org.hibernate.search.engine.DocumentBuilder;

import com.manning.hsia.dvdstore.action.StockAction;
import com.manning.hsia.dvdstore.action.StockActionImpl;
import com.manning.hsia.dvdstore.model.Item;

/**
 * Example 8.12
 */
public class StockFilter extends Filter {
	private volatile long lastUpdateTime;  //update timestamp

	@SuppressWarnings("unchecked") 
	private final Map<IndexReader, DocIdSet>
	    cache = Collections.synchronizedMap(
		   new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.HARD) );  //keep cache in a SoftHashMap

	@Override
	public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
		StockAction action = getStockAction();    //retrieve the service
		long lastUpdateTime = action.geLastUpdateTime();
		if ( lastUpdateTime != this.lastUpdateTime ) {
			cache.clear(); //clear outdated cache  
		}
		DocIdSet cached = cache.get( reader );  //check if in cache already
		if ( cached != null ) return cached;
		//not in cache, build info
		final BitSet bitSet = getAllPositiveBitSet( reader.maxDoc() ); //by default, all documents pass
		
		Term clazzTerm = new Term( DocumentBuilder.CLASS_FIELDNAME, Item.class.getName() );
		if ( reader.docFreq( clazzTerm ) == 0) {  //no need to filter
			//index does not contain Item objects
			//no-op
		}
		else {
			//for each item out of stock, find the corresponding document id by item id
			//and switch off the corresponding bit
			for ( String ean : action.getEanOfItemsOutOfStock() ) {  //invoke external service
				Term term = new Term( "ean", ean );
				TermDocs termDocs = reader.termDocs( term );  //find document by ean
				while ( termDocs.next() ) {
					bitSet.clear( termDocs.doc() );
				}
			}
		}
		DocIdSet docIdSet = new DocIdBitSet( bitSet ); //build DocIdSet from BitSet
		cache.put( reader, docIdSet );  //put results in the cache
		this.lastUpdateTime = lastUpdateTime;  //update timestamp
		return docIdSet;
	}

	private BitSet getAllPositiveBitSet(int maxDoc) {
		final BitSet bitSet = new BitSet( maxDoc );
		bitSet.set( 0, maxDoc-1 );        //new BitSet with all bits on
		return bitSet;
	}

	private StockAction getStockAction() {
		return new StockActionImpl();
	}

}

use Collections.synchronizedMap instead of synchronization