1 package org.apache.lucene.facet.search.params;
3 import java.io.IOException;
4 import java.util.Arrays;
7 import org.apache.lucene.analysis.MockAnalyzer;
8 import org.apache.lucene.analysis.MockTokenizer;
9 import org.apache.lucene.document.Document;
10 import org.apache.lucene.index.CorruptIndexException;
11 import org.apache.lucene.index.IndexReader;
12 import org.apache.lucene.index.RandomIndexWriter;
13 import org.apache.lucene.store.Directory;
14 import org.junit.Test;
16 import org.apache.lucene.util.LuceneTestCase;
17 import org.apache.lucene.facet.index.CategoryDocumentBuilder;
18 import org.apache.lucene.facet.index.params.CategoryListParams;
19 import org.apache.lucene.facet.index.params.DefaultFacetIndexingParams;
20 import org.apache.lucene.facet.index.params.FacetIndexingParams;
21 import org.apache.lucene.facet.search.CategoryListIterator;
22 import org.apache.lucene.facet.search.FacetArrays;
23 import org.apache.lucene.facet.search.FacetResultsHandler;
24 import org.apache.lucene.facet.search.FacetsAccumulator;
25 import org.apache.lucene.facet.search.ScoredDocIDs;
26 import org.apache.lucene.facet.search.StandardFacetsAccumulator;
27 import org.apache.lucene.facet.search.TopKFacetResultsHandler;
28 import org.apache.lucene.facet.search.cache.CategoryListCache;
29 import org.apache.lucene.facet.search.results.FacetResult;
30 import org.apache.lucene.facet.search.results.FacetResultNode;
31 import org.apache.lucene.facet.search.results.IntermediateFacetResult;
32 import org.apache.lucene.facet.taxonomy.CategoryPath;
33 import org.apache.lucene.facet.taxonomy.TaxonomyReader;
34 import org.apache.lucene.facet.taxonomy.TaxonomyWriter;
35 import org.apache.lucene.facet.taxonomy.lucene.LuceneTaxonomyReader;
36 import org.apache.lucene.facet.taxonomy.lucene.LuceneTaxonomyWriter;
37 import org.apache.lucene.facet.util.ScoredDocIdsUtils;
40 * Licensed to the Apache Software Foundation (ASF) under one or more
41 * contributor license agreements. See the NOTICE file distributed with
42 * this work for additional information regarding copyright ownership.
43 * The ASF licenses this file to You under the Apache License, Version 2.0
44 * (the "License"); you may not use this file except in compliance with
45 * the License. You may obtain a copy of the License at
47 * http://www.apache.org/licenses/LICENSE-2.0
49 * Unless required by applicable law or agreed to in writing, software
50 * distributed under the License is distributed on an "AS IS" BASIS,
51 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
52 * See the License for the specific language governing permissions and
53 * limitations under the License.
57 * Test faceted search with creation of multiple category list iterators by the
58 * same CLP, depending on the provided facet request
60 public class MultiIteratorsPerCLParamsTest extends LuceneTestCase {
62 CategoryPath[][] perDocCategories = new CategoryPath[][] {
63 { new CategoryPath("author", "Mark Twain"),
64 new CategoryPath("date", "2010") },
65 { new CategoryPath("author", "Robert Frost"),
66 new CategoryPath("date", "2009") },
67 { new CategoryPath("author", "Artur Miller"),
68 new CategoryPath("date", "2010") },
69 { new CategoryPath("author", "Edgar Allan Poe"),
70 new CategoryPath("date", "2009") },
71 { new CategoryPath("author", "Henry James"),
72 new CategoryPath("date", "2010") } };
74 String countForbiddenDimension;
77 public void testCLParamMultiIteratorsByRequest() throws Exception {
78 doTestCLParamMultiIteratorsByRequest(false);
82 public void testCLParamMultiIteratorsByRequestCacheCLI() throws Exception {
83 doTestCLParamMultiIteratorsByRequest(true);
86 private void doTestCLParamMultiIteratorsByRequest(boolean cacheCLI) throws Exception,
87 CorruptIndexException, IOException {
88 // Create a CLP which generates different CLIs according to the
89 // FacetRequest's dimension
90 CategoryListParams clp = new CategoryListParams();
91 FacetIndexingParams iParams = new DefaultFacetIndexingParams(clp);
92 Directory indexDir = newDirectory();
93 Directory taxoDir = newDirectory();
94 populateIndex(iParams, indexDir, taxoDir);
96 TaxonomyReader taxo = new LuceneTaxonomyReader(taxoDir);
97 IndexReader reader = IndexReader.open(indexDir);
99 CategoryListCache clCache = null;
101 // caching the iteratorr, so:
102 // 1: create the cached iterator, using original params
103 clCache = new CategoryListCache();
104 clCache.loadAndRegister(clp, reader, taxo, iParams);
107 ScoredDocIDs allDocs = ScoredDocIdsUtils
108 .createAllDocsScoredDocIDs(reader);
110 // Search index with 'author' should filter ONLY ordinals whose parent
112 countForbiddenDimension = "date";
113 validateFacetedSearch(iParams, taxo, reader, clCache, allDocs, "author", 5, 5);
115 // Search index with 'date' should filter ONLY ordinals whose parent is
117 countForbiddenDimension = "author";
118 validateFacetedSearch(iParams, taxo, reader, clCache, allDocs, "date", 5, 2);
120 // Search index with both 'date' and 'author'
121 countForbiddenDimension = null;
122 validateFacetedSearch(iParams, taxo, reader, clCache, allDocs, new String[] {
123 "author", "date" }, new int[] { 5, 5 }, new int[] { 5, 2 });
130 private void validateFacetedSearch(FacetIndexingParams iParams,
131 TaxonomyReader taxo, IndexReader reader, CategoryListCache clCache,
132 ScoredDocIDs allDocs, String dimension, int expectedValue, int expectedNumDescendants) throws IOException {
133 validateFacetedSearch(iParams, taxo, reader, clCache, allDocs,
134 new String[] { dimension }, new int[] { expectedValue },
135 new int[] { expectedNumDescendants });
138 private void validateFacetedSearch(FacetIndexingParams iParams,
139 TaxonomyReader taxo, IndexReader reader, CategoryListCache clCache, ScoredDocIDs allDocs,
140 String[] dimension, int[] expectedValue,
141 int[] expectedNumDescendants)
143 FacetSearchParams sParams = new FacetSearchParams(iParams);
144 sParams.setClCache(clCache);
145 for (String dim : dimension) {
146 sParams.addFacetRequest(new PerDimCountFacetRequest(
147 new CategoryPath(dim), 10));
149 FacetsAccumulator acc = new StandardFacetsAccumulator(sParams, reader, taxo);
151 // no use to test this with complement since at that mode all facets are taken
152 acc.setComplementThreshold(FacetsAccumulator.DISABLE_COMPLEMENT);
154 List<FacetResult> results = acc.accumulate(allDocs);
155 assertEquals("Wrong #results", dimension.length, results.size());
157 for (int i = 0; i < results.size(); i++) {
158 FacetResult res = results.get(i);
159 assertEquals("wrong num-descendants for dimension " + dimension[i],
160 expectedNumDescendants[i], res.getNumValidDescendants());
161 FacetResultNode resNode = res.getFacetResultNode();
162 assertEquals("wrong value for dimension " + dimension[i],
163 expectedValue[i], (int) resNode.getValue());
167 private void populateIndex(FacetIndexingParams iParams, Directory indexDir,
168 Directory taxoDir) throws Exception {
169 RandomIndexWriter writer = new RandomIndexWriter(random, indexDir,
170 newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.KEYWORD, false)));
171 TaxonomyWriter taxoWriter = new LuceneTaxonomyWriter(taxoDir);
173 for (CategoryPath[] categories : perDocCategories) {
174 writer.addDocument(new CategoryDocumentBuilder(taxoWriter, iParams)
175 .setCategoryPaths(Arrays.asList(categories)).build(
185 private class PerDimCountFacetRequest extends CountFacetRequest {
187 public PerDimCountFacetRequest(CategoryPath path, int num) {
192 public CategoryListIterator createCategoryListIterator(IndexReader reader,
193 TaxonomyReader taxo, FacetSearchParams sParams, int partition) throws IOException {
194 // categories of certain dimension only
195 return new PerDimensionCLI(taxo, super.createCategoryListIterator(
196 reader, taxo, sParams, partition), getCategoryPath());
200 /** Override this method just for verifying that only specified facets are iterated.. */
201 public FacetResultsHandler createFacetResultsHandler(
202 TaxonomyReader taxonomyReader) {
203 return new TopKFacetResultsHandler(taxonomyReader, this) {
205 public IntermediateFacetResult fetchPartitionResult(
206 FacetArrays facetArrays, int offset) throws IOException {
207 final IntermediateFacetResult res = super.fetchPartitionResult(facetArrays, offset);
208 if (countForbiddenDimension!=null) {
209 int ord = taxonomyReader.getOrdinal(new CategoryPath(countForbiddenDimension));
210 assertEquals("Should not have accumulated for dimension '"+countForbiddenDimension+"'!",0,facetArrays.getIntArray()[ord]);
219 * a CLI which filters another CLI for the dimension of the provided
222 private static class PerDimensionCLI implements CategoryListIterator {
223 private final CategoryListIterator superCLI;
224 private final int[] parentArray;
225 private final int parentOrdinal;
227 PerDimensionCLI(TaxonomyReader taxo, CategoryListIterator superCLI,
228 CategoryPath requestedPath) throws IOException {
229 this.superCLI = superCLI;
230 if (requestedPath == null) {
233 CategoryPath cp = new CategoryPath(requestedPath.getComponent(0));
234 parentOrdinal = taxo.getOrdinal(cp);
236 parentArray = taxo.getParentArray();
239 public boolean init() throws IOException {
240 return superCLI.init();
243 public long nextCategory() throws IOException {
245 while ((next = superCLI.nextCategory()) <= Integer.MAX_VALUE
246 && !isInDimension((int) next)) {
252 /** look for original parent ordinal, meaning same dimension */
253 private boolean isInDimension(int ordinal) {
254 while (ordinal > 0) {
255 if (ordinal == parentOrdinal) {
258 ordinal = parentArray[ordinal];
263 public boolean skipTo(int docId) throws IOException {
264 return superCLI.skipTo(docId);