package com.browseengine.bobo.api; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.log4j.Logger; import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.MultiReader; import org.apache.lucene.search.Collector; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.SortField; import org.apache.lucene.search.Weight; import org.apache.lucene.search.similarities.Similarity; import com.browseengine.bobo.facets.FacetHandler; import com.browseengine.bobo.sort.SortCollector; /** * Provides implementation of Browser for multiple Browser instances */ public class MultiBoboBrowser extends MultiReader implements Browsable { private static Logger logger = Logger.getLogger(MultiBoboBrowser.class); private IndexSearcher _indexSearcher = null; protected Browsable[] _subBrowsers; public MultiBoboBrowser(BoboMultiReader reader) throws IOException { this(reader._subReaders); } public MultiBoboBrowser(List<BoboSegmentReader> segmentReaders) throws IOException { super(segmentReaders.toArray(new BoboSegmentReader[0]), false); _indexSearcher = new IndexSearcher(this); initSubBrowsers(); } /** * * @param browsers * Browsers to search on * @throws IOException */ public MultiBoboBrowser(Browsable[] browsers) throws IOException { super(getSubReaders(browsers), false); _indexSearcher = new IndexSearcher(this); initSubBrowsers(); } public void initSubBrowsers() { List<AtomicReaderContext> leaves = getContext().leaves(); _subBrowsers = new BoboSubBrowser[leaves.size()]; for (int i = 0; i < leaves.size(); ++i) { _subBrowsers[i] = new BoboSubBrowser(leaves.get(i)); } } private static IndexReader[] getSubReaders(Browsable[] browsers) { IndexReader[] readers = new IndexReader[browsers.length]; for (int i = 0; i < browsers.length; ++i) { readers[i] = browsers[i].getIndexReader(); } return readers; } public void browse(BrowseRequest req, final Collector hc, Map<String, FacetAccessible> facetMap) throws BrowseException { Weight w = null; try { Query q = req.getQuery(); MatchAllDocsQuery matchAllDocsQuery = new MatchAllDocsQuery(); if (q == null) { q = matchAllDocsQuery; } else if (!(q instanceof MatchAllDocsQuery)) { // MatchAllQuery is needed to filter out the deleted docids, that reside in // ZoieSegmentReader and are not visible on Bobo level matchAllDocsQuery.setBoost(0f); q = QueriesSupport.combineAnd(matchAllDocsQuery, q); } req.setQuery(q); w = _indexSearcher.createNormalizedWeight(q); } catch (Exception ioe) { throw new BrowseException(ioe.getMessage(), ioe); } browse(req, w, hc, facetMap, 0); } @Override public void browse(BrowseRequest req, Weight w, final Collector hc, Map<String, FacetAccessible> facetMap, int start) throws BrowseException { // index empty if (_subBrowsers == null || _subBrowsers.length == 0) { return; } Map<String, List<FacetAccessible>> mergedMap = new HashMap<String, List<FacetAccessible>>(); try { Map<String, FacetAccessible> facetColMap = new HashMap<String, FacetAccessible>(); for (int i = 0; i < _subBrowsers.length; i++) { try { _subBrowsers[i].browse(req, w, hc, facetColMap, (start + readerBase(i))); } finally { Set<Entry<String, FacetAccessible>> entries = facetColMap.entrySet(); for (Entry<String, FacetAccessible> entry : entries) { String name = entry.getKey(); FacetAccessible facetAccessor = entry.getValue(); List<FacetAccessible> list = mergedMap.get(name); if (list == null) { list = new ArrayList<FacetAccessible>(_subBrowsers.length); mergedMap.put(name, list); } list.add(facetAccessor); } facetColMap.clear(); } } } finally { if (req.getMapReduceWrapper() != null) { req.getMapReduceWrapper().finalizePartition(); } Set<Entry<String, List<FacetAccessible>>> entries = mergedMap.entrySet(); for (Entry<String, List<FacetAccessible>> entry : entries) { String name = entry.getKey(); FacetHandler<?> handler = getFacetHandler(name); try { List<FacetAccessible> subList = entry.getValue(); if (subList != null) { FacetAccessible merged = handler.merge(req.getFacetSpec(name), subList); facetMap.put(name, merged); } } catch (Exception e) { logger.error(e.getMessage(), e); } } } } /** * Generate a merged BrowseResult from the given BrowseRequest * @param req * BrowseRequest for generating the facets * @return BrowseResult of the results of the BrowseRequest */ @Override public BrowseResult browse(BrowseRequest req) throws BrowseException { final BrowseResult result = new BrowseResult(); // index empty if (_subBrowsers == null || _subBrowsers.length == 0) { return result; } long start = System.currentTimeMillis(); int offset = req.getOffset(); int count = req.getCount(); if (offset < 0 || count < 0) { throw new IllegalArgumentException("both offset and count must be > 0: " + offset + "/" + count); } SortCollector collector = getSortCollector(req.getSort(), req.getQuery(), offset, count, req.isFetchStoredFields(), req.getTermVectorsToFetch(), req.getGroupBy(), req.getMaxPerGroup(), req.getCollectDocIdCache()); Map<String, FacetAccessible> facetCollectors = new HashMap<String, FacetAccessible>(); browse(req, collector, facetCollectors); if (req.getMapReduceWrapper() != null) { result.setMapReduceResult(req.getMapReduceWrapper().getResult()); } BrowseHit[] hits = null; try { hits = collector.topDocs(); } catch (IOException e) { logger.error(e.getMessage(), e); result.addError(e.getMessage()); hits = new BrowseHit[0]; } Query q = req.getQuery(); if (req.isShowExplanation()) { for (BrowseHit hit : hits) { try { int doc = hit.getDocid(); Explanation expl = _indexSearcher.explain(q, doc); hit.setExplanation(expl); } catch (IOException e) { logger.error(e.getMessage(), e); result.addError(e.getMessage()); } } } result.setHits(hits); result.setNumHits(collector.getTotalHits()); result.setNumGroups(collector.getTotalGroups()); result.setGroupAccessibles(collector.getGroupAccessibles()); result.setSortCollector(collector); result.setTotalDocs(numDocs()); result.addAll(facetCollectors); long end = System.currentTimeMillis(); result.setTime(end - start); // set the transaction ID to trace transactions result.setTid(req.getTid()); return result; } /** * Return the values of a field for the given doc * */ @Override public String[] getFieldVal(int docid, final String fieldname) throws IOException { int i = readerIndex(docid); Browsable browser = _subBrowsers[i]; return browser.getFieldVal(docid - readerBase(i), fieldname); } @Override public Object[] getRawFieldVal(int docid, String fieldname) throws IOException { int i = readerIndex(docid); Browsable browser = _subBrowsers[i]; return browser.getRawFieldVal(docid - readerBase(i), fieldname); } /** * Compare BrowseFacets by their value */ public static class BrowseFacetValueComparator implements Comparator<BrowseFacet> { @Override public int compare(BrowseFacet o1, BrowseFacet o2) { return o1.getValue().compareTo(o2.getValue()); } } /** * Gets the sub-browser for a given docid * * @param docid * @return sub-browser instance */ public Browsable subBrowser(int docid) { int i = readerIndex(docid); return _subBrowsers[i]; } @Override public Set<String> getFacetNames() { Set<String> names = new HashSet<String>(); for (Browsable subBrowser : _subBrowsers) { names.addAll(subBrowser.getFacetNames()); } return names; } @Override public FacetHandler<?> getFacetHandler(String name) { for (Browsable subBrowser : _subBrowsers) { FacetHandler<?> subHandler = subBrowser.getFacetHandler(name); if (subHandler != null) { return subHandler; } } return null; } @Override public Map<String, FacetHandler<?>> getFacetHandlerMap() { HashMap<String, FacetHandler<?>> map = new HashMap<String, FacetHandler<?>>(); for (Browsable subBrowser : _subBrowsers) { map.putAll(subBrowser.getFacetHandlerMap()); } return map; } @Override public void setFacetHandler(FacetHandler<?> facetHandler) throws IOException { for (Browsable subBrowser : _subBrowsers) { subBrowser.setFacetHandler(facetHandler); } } @Override public SortCollector getSortCollector(SortField[] sort, Query q, int offset, int count, boolean fetchStoredFields, Set<String> termVectorsToFetch, String[] groupBy, int maxPerGroup, boolean collectDocIdCache) { if (_subBrowsers.length == 1) { return _subBrowsers[0].getSortCollector(sort, q, offset, count, fetchStoredFields, termVectorsToFetch, groupBy, maxPerGroup, collectDocIdCache); } return SortCollector.buildSortCollector(this, q, sort, offset, count, fetchStoredFields, termVectorsToFetch, groupBy, maxPerGroup, collectDocIdCache); } @Override public void doClose() throws IOException { super.doClose(); for (Browsable subBrowser : _subBrowsers) { subBrowser.doClose(); } } @Override public IndexReader getIndexReader() { return this; } public void setSimilarity(Similarity similarity) { if (similarity != null) _indexSearcher.setSimilarity(similarity); } }