Author: tommaso
Date: Tue Dec 15 10:01:39 2015
New Revision: 1720107

URL: http://svn.apache.org/viewvc?rev=1720107&view=rev
Log:
OAK-2511 - support for facets in lucene property index

Added:
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
   (with props)
Modified:
    jackrabbit/oak/trunk/oak-lucene/pom.xml
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/package-info.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/LuceneOakRepositoryStub.java

Modified: jackrabbit/oak/trunk/oak-lucene/pom.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/pom.xml?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-lucene/pom.xml Tue Dec 15 10:01:39 2015
@@ -238,6 +238,11 @@
       <artifactId>lucene-misc</artifactId>
       <version>${lucene.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.lucene</groupId>
+      <artifactId>lucene-facet</artifactId>
+      <version>${lucene.version}</version>
+    </dependency>
 
     <!-- Logging -->
     <dependency>

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
 Tue Dec 15 10:01:39 2015
@@ -220,6 +220,8 @@ class IndexDefinition implements Aggrega
 
     private final boolean suggestAnalyzed;
 
+    private final boolean secureFacets;
+
     public IndexDefinition(NodeState root, NodeState defn) {
         this(root, defn, null);
     }
@@ -286,6 +288,7 @@ class IndexDefinition implements Aggrega
         this.queryPaths = getQueryPaths(defn);
         this.saveDirListing = getOptionalValue(defn, 
LuceneIndexConstants.SAVE_DIR_LISTING, true);
         this.suggestAnalyzed = getOptionalValue(defn, 
LuceneIndexConstants.SUGGEST_ANALYZED, false);
+        this.secureFacets = getOptionalValue(defn, 
LuceneIndexConstants.PROP_SECURE_FACETS, true);
     }
 
     public boolean isFullTextEnabled() {
@@ -631,6 +634,10 @@ class IndexDefinition implements Aggrega
         return suggestAnalyzed;
     }
 
+    public boolean isSecureFacets() {
+        return secureFacets;
+    }
+
     public class IndexingRule {
         private final String baseNodeType;
         private final String nodeTypeName;

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
 Tue Dec 15 10:01:39 2015
@@ -699,7 +699,7 @@ public class LuceneIndex implements Adva
             }
 
             String name = pr.propertyName;
-            if (QueryImpl.REP_EXCERPT.equals(name)) {
+            if (QueryImpl.REP_EXCERPT.equals(name) || 
QueryImpl.OAK_SCORE_EXPLANATION.equals(name) || 
QueryImpl.REP_FACET.equals(name)) {
                 continue;
             }
             if (JCR_PRIMARYTYPE.equals(name)) {

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
 Tue Dec 15 10:01:39 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.analysis.util.AbstractAnalysisFactory;
@@ -286,8 +287,20 @@ public interface LuceneIndexConstants {
     String INDEX_PATH = "indexPath";
 
     /**
+     * Optional (property definition) property indicating facet can be 
retrieved together with plain queries.
+     * Default is false
+     */
+    String PROP_FACET = "facets";
+
+    /**
      * Optional property to set the suggest field to be analyzed and therefore 
allow more fine
      * grained and flexible suggestions.
      */
     String SUGGEST_ANALYZED = "suggestAnalyzed";
+
+    /**
+     * Optional (index definition) property indicating whether facets should 
be ACL checked.
+     * Default is true
+     */
+    String PROP_SECURE_FACETS = "secureFacets";
 }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
 Tue Dec 15 10:01:39 2015
@@ -16,7 +16,9 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
+import static org.apache.jackrabbit.JcrConstants.JCR_CREATED;
 import static org.apache.jackrabbit.JcrConstants.JCR_DATA;
+import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
 import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
 import static org.apache.jackrabbit.oak.commons.PathUtils.getName;
 import static 
org.apache.jackrabbit.oak.plugins.index.lucene.FieldFactory.newDepthField;
@@ -69,6 +71,8 @@ import org.apache.lucene.document.LongFi
 import org.apache.lucene.document.NumericDocValuesField;
 import org.apache.lucene.document.SortedDocValuesField;
 import org.apache.lucene.document.StringField;
+import org.apache.lucene.facet.FacetsConfig;
+import org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetField;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.search.PrefixQuery;
 import org.apache.lucene.util.BytesRef;
@@ -81,7 +85,7 @@ import org.slf4j.LoggerFactory;
 /**
  * {@link IndexEditor} implementation that is responsible for keeping the
  * {@link LuceneIndex} up to date
- * 
+ *
  * @see LuceneIndex
  */
 public class LuceneIndexEditor implements IndexEditor, Aggregate.AggregateRoot 
{
@@ -124,6 +128,8 @@ public class LuceneIndexEditor implement
 
     private final PathFilter.Result pathFilterResult;
 
+    private static final FacetsConfig facetsConfig = new FacetsConfig();
+
     LuceneIndexEditor(NodeState root, NodeBuilder definition,
                         IndexUpdateCallback updateCallback,
                         @Nullable IndexCopier indexCopier,
@@ -311,11 +317,13 @@ public class LuceneIndexEditor implement
         return false;
     }
 
-    private Document makeDocument(String path, NodeState state, boolean 
isUpdate) {
+    private Document makeDocument(String path, NodeState state, boolean 
isUpdate) throws IOException {
         if (!isIndexable()) {
             return null;
         }
 
+        boolean facet = false;
+
         List<Field> fields = new ArrayList<Field>();
         boolean dirty = false;
         for (PropertyState property : state.getProperties()) {
@@ -335,13 +343,18 @@ public class LuceneIndexEditor implement
                 dirty |= addTypedOrderedFields(fields, property, pname, pd);
             }
 
+            if (pd.facet) {
+                dirty |= addFacetFields(fields, property, pname, pd);
+                facet = true;
+            }
+
             dirty |= indexProperty(path, fields, state, property, pname, pd);
         }
 
         dirty |= indexAggregates(path, fields, state);
         dirty |= indexNullCheckEnabledProps(path, fields, state);
         dirty |= indexNotNullCheckEnabledProps(path, fields, state);
-        
+
         // Check if a node having a single property was modified/deleted
         if (!dirty) {
             dirty = indexIfSinglePropertyRemoved();
@@ -394,11 +407,76 @@ public class LuceneIndexEditor implement
             document.add(suggestField);
         }
 
+        if (facet) {
+            document = facetsConfig.build(document);
+        }
+
         //TODO Boost at document level
 
         return document;
     }
 
+    private boolean addFacetFields(List<Field> fields, PropertyState property, 
String pname, PropertyDefinition pd) {
+        // force skipping some JCR properties whose faceting wouldn't make 
sense (unique or possibly very large values)
+        if (!pname.equals(JCR_DATA) && !pname.equals(JCR_CREATED) && 
!pname.equals(JCR_UUID)) {
+            String facetFieldName = pname + "_facet";
+            facetsConfig.setIndexFieldName(pname, facetFieldName); // facets 
are indexed in *_facet fields
+            int tag = property.getType().tag();
+            int idxDefinedTag = pd.getType();
+            // Try converting type to the defined type in the index definition
+            if (tag != idxDefinedTag) {
+                log.debug("[{}] Facet property defined with type {} differs 
from property {} with type {} in "
+                                + "path {}",
+                        getIndexName(),
+                        Type.fromTag(idxDefinedTag, false), 
property.toString(),
+                        Type.fromTag(tag, false), getPath());
+                tag = idxDefinedTag;
+            }
+
+            boolean fieldAdded = false;
+            try {
+                if (tag == Type.LONG.tag()) {
+                    fields.add(new SortedSetDocValuesFacetField(pname, 
property.getValue(Type.LONG).toString()));
+                    fieldAdded = true;
+                } else if (tag == Type.DATE.tag()) {
+                    String date = property.getValue(Type.DATE);
+                    fields.add(new SortedSetDocValuesFacetField(pname, date));
+                    fieldAdded = true;
+                } else if (tag == Type.DOUBLE.tag()) {
+                    fields.add(new DoubleDocValuesField(pname, 
property.getValue(Type.DOUBLE)));
+                    fieldAdded = true;
+                } else if (tag == Type.BOOLEAN.tag()) {
+                    fields.add(new SortedSetDocValuesFacetField(pname, 
property.getValue(Type.BOOLEAN).toString()));
+                    fieldAdded = true;
+                } else if (tag == Type.STRINGS.tag() && property.isArray()) {
+                    facetsConfig.setMultiValued(pname, true);
+                    Iterable<String> values = property.getValue(Type.STRINGS);
+                    for (String value : values) {
+                        if (value != null && value.length() > 0) {
+                            fields.add(new SortedSetDocValuesFacetField(pname, 
value));
+                        }
+                    }
+                    fieldAdded = true;
+                } else if (tag == Type.STRING.tag()) {
+                    String value = property.getValue(Type.STRING);
+                    if (value.length() > 0) {
+                        fields.add(new SortedSetDocValuesFacetField(pname, 
value));
+                        fieldAdded = true;
+                    }
+                }
+
+            } catch (Throwable e) {
+                log.warn("[{}] Ignoring facet property. Could not convert 
property {} of type {} to type {} for path {}",
+                        getIndexName(), pname,
+                        Type.fromTag(property.getType().tag(), false),
+                        Type.fromTag(tag, false), getPath(), e);
+            }
+            return fieldAdded;
+        } else {
+            return false;
+        }
+    }
+
     private boolean indexProperty(String path,
                                   List<Field> fields,
                                   NodeState state,
@@ -600,14 +678,14 @@ public class LuceneIndexEditor implement
         }
         return fieldAdded;
     }
-    
+
     private boolean indexIfSinglePropertyRemoved() {
         boolean dirty = false;
         for (PropertyState ps : propertiesModified) {
             PropertyDefinition pd = indexingRule.getConfig(ps.getName());
-            if (pd != null 
-                    && pd.index 
-                    && (pd.includePropertyType(ps.getType().tag()) 
+            if (pd != null
+                    && pd.index
+                    && (pd.includePropertyType(ps.getType().tag())
                             || 
indexingRule.includePropertyType(ps.getType().tag()))) {
                 dirty = true;
                 break;
@@ -615,7 +693,7 @@ public class LuceneIndexEditor implement
         }
         return dirty;
     }
-    
+
     /**
      * Determine if the property as defined by PropertyDefinition exists or 
not.
      *

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
 Tue Dec 15 10:01:39 2015
@@ -24,10 +24,14 @@ import javax.annotation.Nullable;
 import javax.jcr.PropertyType;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Deque;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -69,11 +73,21 @@ import org.apache.lucene.analysis.Analyz
 import org.apache.lucene.analysis.CachingTokenFilter;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.document.Document;
+import org.apache.lucene.facet.FacetResult;
+import org.apache.lucene.facet.Facets;
+import org.apache.lucene.facet.FacetsCollector;
+import org.apache.lucene.facet.FacetsConfig;
+import org.apache.lucene.facet.LabelAndValue;
+import org.apache.lucene.facet.MultiFacets;
+import org.apache.lucene.facet.sortedset.DefaultSortedSetDocValuesReaderState;
+import org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetCounts;
+import org.apache.lucene.facet.sortedset.SortedSetDocValuesReaderState;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.FieldInfo;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexableField;
 import org.apache.lucene.index.MultiFields;
+import org.apache.lucene.index.SortedSetDocValues;
 import org.apache.lucene.index.StoredFieldVisitor;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.queries.CustomScoreQuery;
@@ -105,6 +119,7 @@ import org.apache.lucene.search.highligh
 import org.apache.lucene.search.highlight.TextFragment;
 import org.apache.lucene.search.spell.SuggestWord;
 import org.apache.lucene.search.suggest.Lookup;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.Version;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -299,7 +314,7 @@ public class LucenePropertyIndex impleme
                 return endOfData();
             }
 
-            private LuceneResultRow convertToRow(ScoreDoc doc, IndexSearcher 
searcher, String excerpt,
+            private LuceneResultRow convertToRow(ScoreDoc doc, IndexSearcher 
searcher, String excerpt,  Facets facets,
                                                  String explanation) throws 
IOException {
                 IndexReader reader = searcher.getIndexReader();
                 //TODO Look into usage of field cache for retrieving the path
@@ -329,7 +344,7 @@ public class LucenePropertyIndex impleme
                     }
 
                     LOG.trace("Matched path {}", path);
-                    return new LuceneResultRow(path, doc.score, excerpt, 
explanation);
+                    return new LuceneResultRow(path, doc.score, excerpt, 
facets, explanation);
                 }
                 return null;
             }
@@ -362,6 +377,15 @@ public class LucenePropertyIndex impleme
 
                         checkForIndexVersionChange(searcher);
 
+                        List<String> facetFields = new LinkedList<String>();
+                        List<PropertyRestriction> facetRestriction = 
filter.getPropertyRestrictions(QueryImpl.REP_FACET);
+                        if (facetRestriction != null && 
facetRestriction.size() > 0) {
+                            for (PropertyRestriction pr : facetRestriction) {
+                                String value = pr.first.getValue(Type.STRING);
+                                
facetFields.add(value.substring(QueryImpl.REP_FACET.length() + 1, 
value.length() - 1));
+                            }
+                        }
+
                         TopDocs docs;
                         long start = PERF_LOGGER.start();
                         while (true) {
@@ -383,6 +407,37 @@ public class LucenePropertyIndex impleme
                             PERF_LOGGER.end(start, -1, "{} ...", 
docs.scoreDocs.length);
                             nextBatchSize = (int) Math.min(nextBatchSize * 2L, 
100000);
 
+                            Facets facets = null;
+                            if (facetFields.size() > 0) {
+                                Map<String, Facets> facetsMap = new 
HashMap<String, Facets>();
+
+                                long f = System.currentTimeMillis();
+                                for (String facetField : facetFields) {
+                                    FacetsCollector facetsCollector = new 
FacetsCollector();
+                                    try {
+                                        DefaultSortedSetDocValuesReaderState 
state = new DefaultSortedSetDocValuesReaderState(
+                                                searcher.getIndexReader(), 
facetField + "_facet"); // facets are indexed in *_facet fields
+                                        if (lastDoc != null) {
+                                            
facetsCollector.searchAfter(searcher, lastDoc, query, 10, facetsCollector);
+                                        } else {
+                                            FacetsCollector.search(searcher, 
query, 10, facetsCollector);
+                                        }
+                                        facetsMap.put(facetField, 
indexNode.getDefinition().isSecureFacets() ?
+                                                new 
FilteredSortedSetDocValuesFacetCounts(state, facetsCollector, filter, docs) :
+                                                new 
SortedSetDocValuesFacetCounts(state, facetsCollector));
+
+                                    } catch (IllegalArgumentException iae) {
+                                        LOG.warn("facets for {} not yet 
indexed", facetField);
+                                    }
+                                }
+                                if (facetsMap.size() > 0) {
+                                    facets = new MultiFacets(facetsMap);
+                                    LOG.debug("facets retrieved in {}ms", 
(System.currentTimeMillis() - f));
+                                    LOG.info("facets in {}ms : {}", 
(System.currentTimeMillis() - f), facets.getTopChildren(10, 
facetFields.get(0)));
+                                }
+
+                            }
+
                             PropertyRestriction restriction = 
filter.getPropertyRestriction(QueryImpl.REP_EXCERPT);
                             boolean addExcerpt = restriction != null && 
restriction.isNotNullRestriction();
 
@@ -400,7 +455,7 @@ public class LucenePropertyIndex impleme
                                     explanation = searcher.explain(query, 
doc.doc).toString();
                                 }
 
-                                LuceneResultRow row = convertToRow(doc, 
searcher, excerpt, explanation);
+                                LuceneResultRow row = convertToRow(doc, 
searcher, excerpt, facets, explanation);
                                 if (row != null) {
                                     queue.add(row);
                                 }
@@ -842,7 +897,8 @@ public class LucenePropertyIndex impleme
         for (PropertyRestriction pr : filter.getPropertyRestrictions()) {
             String name = pr.propertyName;
 
-            if (QueryImpl.REP_EXCERPT.equals(name) || 
QueryImpl.OAK_SCORE_EXPLANATION.equals(name)) {
+            if (QueryImpl.REP_EXCERPT.equals(name) || 
QueryImpl.OAK_SCORE_EXPLANATION.equals(name)
+                    || QueryImpl.REP_FACET.equals(name)) {
                 continue;
             }
 
@@ -1336,10 +1392,12 @@ public class LucenePropertyIndex impleme
         final boolean isVirutal;
         final String excerpt;
         final String explanation;
+        final Facets facets;
 
-        LuceneResultRow(String path, double score, String excerpt, String 
explanation) {
+        LuceneResultRow(String path, double score, String excerpt, Facets 
facets, String explanation) {
             this.explanation = explanation;
             this.excerpt = excerpt;
+            this.facets = facets;
             this.isVirutal = false;
             this.path = path;
             this.score = score;
@@ -1352,6 +1410,7 @@ public class LucenePropertyIndex impleme
             this.score = weight;
             this.suggestion = suggestion;
             this.excerpt = null;
+            this.facets = null;
             this.explanation = null;
         }
 
@@ -1448,6 +1507,22 @@ public class LucenePropertyIndex impleme
                     if (QueryImpl.REP_EXCERPT.equals(columnName)) {
                         return PropertyValues.newString(currentRow.excerpt);
                     }
+                    if (columnName.startsWith(QueryImpl.REP_FACET)) {
+                        String facetFieldName = 
columnName.substring(QueryImpl.REP_FACET.length() + 1, columnName.length() - 1);
+                        Facets facets = currentRow.facets;
+                        try {
+                            if (facets != null) {
+                                FacetResult topChildren = 
facets.getTopChildren(10, facetFieldName);
+                                if (topChildren != null) {
+                                    return 
PropertyValues.newString(facetFieldName + ":" + 
Arrays.toString(topChildren.labelValues));
+                                } else {
+                                    return null;
+                                }
+                            }
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
                     return pathRow.getValue(columnName);
                 }
 
@@ -1491,4 +1566,79 @@ public class LucenePropertyIndex impleme
         }
     }
 
+    private class FilteredSortedSetDocValuesFacetCounts extends 
SortedSetDocValuesFacetCounts {
+        private final TopDocs docs;
+        private final Filter filter;
+        private final IndexReader reader;
+        private final SortedSetDocValuesReaderState state;
+
+        public 
FilteredSortedSetDocValuesFacetCounts(DefaultSortedSetDocValuesReaderState 
state, FacetsCollector facetsCollector, Filter filter, TopDocs docs) throws 
IOException {
+            super(state, facetsCollector);
+            this.reader = state.origReader;
+            this.filter = filter;
+            this.docs = docs;
+            this.state = state;
+        }
+
+        @Override
+        public FacetResult getTopChildren(int topN, String dim, String... 
path) throws IOException {
+            FacetResult topChildren = super.getTopChildren(topN, dim, path);
+
+            LabelAndValue[] labelAndValues = topChildren.labelValues;
+
+            for (ScoreDoc scoreDoc : docs.scoreDocs) {
+                labelAndValues = filterFacet(scoreDoc.doc, dim, 
labelAndValues);
+            }
+
+            int childCount = labelAndValues.length;
+            Number value = 0;
+            for (LabelAndValue lv : labelAndValues) {
+                value = value.longValue() + lv.value.longValue();
+            }
+
+            return new FacetResult(dim, path, value, labelAndValues, 
childCount);
+        }
+
+        private LabelAndValue[] filterFacet(int docId, String dimension, 
LabelAndValue[] labelAndValues) throws IOException {
+            boolean filterd = false;
+            Map<String, Long> newValues = new HashMap<String, Long>();
+
+            Document document = reader.document(docId);
+            SortedSetDocValues docValues = state.getDocValues();
+            docValues.setDocument(docId);
+
+            // filter using doc values (avoiding requiring stored values)
+            if (!filter.isAccessible(document.getField(PATH).stringValue() + 
"/" + dimension)) {
+                filterd = true;
+                for (LabelAndValue lv : labelAndValues) {
+                    long existingCount = lv.value.longValue();
+
+                    BytesRef key = new 
BytesRef(FacetsConfig.pathToString(dimension, new String[]{lv.label}));
+                    long l = docValues.lookupTerm(key);
+                    if (l >= 0) {
+                        if (existingCount > 0) {
+                            newValues.put(lv.label, existingCount - 1);
+                        } else {
+                            if (newValues.containsKey(lv.label)) {
+                                newValues.remove(lv.label);
+                            }
+                        }
+                    }
+                }
+            }
+            LabelAndValue[] filteredLVs;
+            if (filterd) {
+                filteredLVs = new LabelAndValue[newValues.size()];
+                int i = 0;
+                for (Map.Entry<String, Long> entry : newValues.entrySet()) {
+                    filteredLVs[i] = new LabelAndValue(entry.getKey(), 
entry.getValue());
+                    i++;
+                }
+            } else {
+                filteredLVs = labelAndValues;
+            }
+
+            return filteredLVs;
+        }
+    }
 }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
 Tue Dec 15 10:01:39 2015
@@ -86,6 +86,8 @@ class PropertyDefinition {
 
     final boolean useInSpellcheck;
 
+    final boolean facet;
+
     final String[] ancestors;
 
     /**
@@ -128,6 +130,7 @@ class PropertyDefinition {
         this.notNullCheckEnabled = getOptionalValueIfIndexed(defn, 
LuceneIndexConstants.PROP_NOT_NULL_CHECK_ENABLED, false);
         this.nonRelativeName = determineNonRelativeName();
         this.ancestors = computeAncestors(name);
+        this.facet = getOptionalValue(defn, LuceneIndexConstants.PROP_FACET, 
false);
         validate();
     }
 

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/package-info.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/package-info.java?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/package-info.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/package-info.java
 Tue Dec 15 10:01:39 2015
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("2.8.0")
+@Version("2.9.0")
 @Export(optional = "provide:=true")
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/LuceneOakRepositoryStub.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/LuceneOakRepositoryStub.java?rev=1720107&r1=1720106&r2=1720107&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/LuceneOakRepositoryStub.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/LuceneOakRepositoryStub.java
 Tue Dec 15 10:01:39 2015
@@ -88,19 +88,23 @@ public class LuceneOakRepositoryStub ext
                         
.setProperty(LuceneIndexConstants.EVALUATE_PATH_RESTRICTION, true)
                         
.setProperty(LuceneIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, 10);
 
-                NodeBuilder ntBase = 
index.child(LuceneIndexConstants.INDEX_RULES)
-                        .child("nt:base");
+                NodeBuilder rules = 
index.child(LuceneIndexConstants.INDEX_RULES);
+                rules.setProperty(JCR_PRIMARYTYPE, "nt:unstructured", NAME);
+                NodeBuilder ntBase = rules.child("nt:base");
+                ntBase.setProperty(JCR_PRIMARYTYPE, "nt:unstructured", NAME);
 
                 //Enable nodeName index support
                 ntBase.setProperty(LuceneIndexConstants.INDEX_NODE_NAME, true);
                 NodeBuilder props = 
ntBase.child(LuceneIndexConstants.PROP_NODE);
+                props.setProperty(JCR_PRIMARYTYPE, "nt:unstructured", NAME);
 
                 enableFulltextIndex(props.child("allProps"));
             }
         }
 
         private void enableFulltextIndex(NodeBuilder propNode){
-            propNode.setProperty(LuceneIndexConstants.PROP_ANALYZED, true)
+            propNode.setProperty(JCR_PRIMARYTYPE, "nt:unstructured", NAME)
+                    .setProperty(LuceneIndexConstants.PROP_ANALYZED, true)
                     .setProperty(LuceneIndexConstants.PROP_NODE_SCOPE_INDEX, 
true)
                     .setProperty(LuceneIndexConstants.PROP_USE_IN_EXCERPT, 
true)
                     .setProperty(LuceneIndexConstants.PROP_PROPERTY_INDEX, 
true)

Added: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java?rev=1720107&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
 Tue Dec 15 10:01:39 2015
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.query;
+
+import org.apache.jackrabbit.core.query.AbstractQueryTest;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants;
+import org.junit.After;
+import org.junit.Before;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+
+/**
+ * Test for faceting capabilities via JCR API
+ */
+public class FacetTest extends AbstractQueryTest {
+
+    @Before
+    protected void setUp() throws Exception {
+        super.setUp();
+        Node node = 
superuser.getNode("/oak:index/luceneGlobal/indexRules/nt:base/properties/allProps");
+        node.setProperty(LuceneIndexConstants.PROP_FACET, true);
+    }
+
+    @After
+    protected void tearDown() throws Exception {
+        
superuser.getNode("/oak:index/luceneGlobal/indexRules/nt:base/properties/allProps").getProperty(LuceneIndexConstants.PROP_FACET).remove();
+        super.tearDown();
+    }
+
+    public void testFacetsNA() throws Exception {
+        Node node = 
superuser.getNode("/oak:index/luceneGlobal/indexRules/nt:base/properties/allProps");
+        node.setProperty(LuceneIndexConstants.PROP_FACET, false);
+        Session session = superuser;
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        Node n1 = testRootNode.addNode("node1");
+        n1.setProperty("text", "foo");
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty("text", "bar");
+        session.save();
+
+        String sql2 = "select [jcr:path], [rep:facet(text)] from [nt:base] " +
+                "where contains([text], 'foo OR bar')";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        String facetResult = "";
+        assertEquals(facetResult, getResult(result, "rep:facet(text)"));
+    }
+
+    public void testFacetRetrieval() throws Exception {
+        Session session = superuser;
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        Node n1 = testRootNode.addNode("node1");
+        n1.setProperty("text", "hello");
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty("text", "hallo");
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty("text", "oh hallo");
+        session.save();
+
+        String sql2 = "select [jcr:path], [rep:facet(text)] from [nt:base] " +
+                "where contains([text], 'hello OR hallo') order by [jcr:path]";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        String facetResult = "text:[hallo (1), hello (1), oh hallo (1)]";
+        assertEquals(facetResult + ", " + facetResult + ", " + facetResult, 
getResult(result, "rep:facet(text)"));
+    }
+
+    public void testFacetRetrievalMV() throws Exception {
+        Session session = superuser;
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        Node n1 = testRootNode.addNode("node1");
+        n1.setProperty("jcr:title", "apache jackrabbit oak");
+        n1.setProperty("tags", new String[]{"software", "repository", 
"apache"});
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty("jcr:title", "oak furniture");
+        n2.setProperty("tags", "furniture");
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty("jcr:title", "oak cosmetics");
+        n3.setProperty("tags", "cosmetics");
+        Node n4 = testRootNode.addNode("node4");
+        n4.setProperty("jcr:title", "oak and aem");
+        n4.setProperty("tags", new String[]{"software", "repository", "aem"});
+        session.save();
+
+        String sql2 = "select [jcr:path], [rep:facet(tags)] from [nt:base] " +
+                "where contains([jcr:title], 'oak') order by [jcr:path]";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        String facetResult = "tags:[repository (2), software (2), aem (1), 
apache (1), cosmetics (1), furniture (1)], tags:[repository (2), software (2), 
aem (1), apache (1), cosmetics (1), furniture (1)], tags:[repository (2), 
software (2), aem (1), apache (1), cosmetics (1), furniture (1)], 
tags:[repository (2), software (2), aem (1), apache (1), cosmetics (1), 
furniture (1)]";
+        assertEquals(facetResult, getResult(result, "rep:facet(tags)"));
+    }
+
+    public void testFacetRetrievalWithAnonymousUser() throws Exception {
+        Session session = superuser;
+
+        Node n1 = testRootNode.addNode("node1");
+        n1.setProperty("text", "hello");
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty("text", "hallo");
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty("text", "oh hallo");
+        session.save();
+
+        session = getHelper().getReadOnlySession();
+        QueryManager qm = session.getWorkspace().getQueryManager();
+
+        String sql2 = "select [jcr:path], [rep:facet(text)] from [nt:base] " +
+                "where contains([text], 'hello OR hallo') order by [jcr:path]";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        String facetResult = "text:[hallo (1), hello (1), oh hallo (1)]";
+        assertEquals(facetResult + ", " + facetResult + ", " + facetResult, 
getResult(result, "rep:facet(text)"));
+    }
+
+    public void testFacetRetrieval2() throws Exception {
+        Session session = superuser;
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        Node n1 = testRootNode.addNode("node1");
+        String pn = "jcr:title";
+        n1.setProperty(pn, "hello");
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty(pn, "hallo");
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty(pn, "oh hallo");
+        session.save();
+
+        String sql2 = "select [jcr:path], [rep:facet(" + pn + ")] from 
[nt:base] " +
+                "where contains([" + pn + "], 'hallo') order by [jcr:path]";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        String facetResult = pn + ":[hallo (1), oh hallo (1)]";
+        assertEquals(facetResult + ", " + facetResult, getResult(result, 
"rep:facet(" + pn + ")"));
+    }
+
+    public void testMultipleFacetsRetrieval() throws Exception {
+        Session session = superuser;
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        Node n1 = testRootNode.addNode("node1");
+        String pn = "jcr:title";
+        String pn2 = "jcr:description";
+        n1.setProperty(pn, "hello");
+        n1.setProperty(pn2, "a");
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty(pn, "hallo");
+        n2.setProperty(pn2, "b");
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty(pn, "oh hallo");
+        n3.setProperty(pn2, "a");
+        session.save();
+
+        String sql2 = "select [jcr:path], [rep:facet(" + pn + ")], 
[rep:facet(" + pn2 + ")] from [nt:base] " +
+                "where contains([" + pn + "], 'hallo') order by [jcr:path]";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        String facetResult = pn + ":[hallo (1), oh hallo (1)], " + pn2 + ":[a 
(1), b (1)], " + pn + ":[hallo (1), oh hallo (1)], " + pn2 + ":[a (1), b (1)]";
+        assertEquals(facetResult, getResult(result, "rep:facet(" + pn + ")", 
"rep:facet(" + pn2 + ")"));
+    }
+
+    static String getResult(QueryResult result, String... propertyNames) 
throws RepositoryException {
+        StringBuilder buff = new StringBuilder();
+        RowIterator it = result.getRows();
+        while (it.hasNext()) {
+
+            Row row = it.nextRow();
+            for (String propertyName : propertyNames) {
+                Value value = row.getValue(propertyName);
+                if (value != null) {
+                    if (buff.length() > 0) {
+                        buff.append(", ");
+                    }
+                    buff.append(value.getString());
+                }
+            }
+        }
+        return buff.toString();
+    }
+
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
------------------------------------------------------------------------------
    svn:eol-style = native



Reply via email to