Author: catholicon
Date: Tue Dec 22 16:27:58 2015
New Revision: 1721424

URL: http://svn.apache.org/viewvc?rev=1721424&view=rev
Log:
OAK-3576: Allow custom extension to augment indexed lucene documents

Added:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactory.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/FulltextQueryTermsProvider.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/IndexFieldProvider.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/package-info.java
      - copied, changed from r1721341, 
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/plugins/index/lucene/IndexAugmentorFactoryTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAugmentTest.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/LuceneIndexEditor.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.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/package-info.java

Modified: jackrabbit/oak/trunk/oak-lucene/pom.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/pom.xml?rev=1721424&r1=1721423&r2=1721424&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-lucene/pom.xml Tue Dec 22 16:27:58 2015
@@ -112,6 +112,7 @@
                 org.apache.jackrabbit.oak.plugins.index.lucene,
                 org.apache.jackrabbit.oak.plugins.index.lucene.util,
                 org.apache.jackrabbit.oak.plugins.index.lucene.score,
+                org.apache.jackrabbit.oak.plugins.index.lucene.spi,
             </Export-Package>
             <_exportcontents>
                 org.apache.lucene.*;version=${lucene.version}

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactory.java?rev=1721424&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactory.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactory.java
 Tue Dec 22 16:27:58 2015
@@ -0,0 +1,198 @@
+/*
+ * 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.plugins.index.lucene;
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.spi.FulltextQueryTermsProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.spi.IndexFieldProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.whiteboard.Tracker;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.util.PerfLogger;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class IndexAugmentorFactory {
+
+    private static final PerfLogger PERFLOG = new PerfLogger(
+            LoggerFactory.getLogger(IndexAugmentorFactory.class.getName() + 
".perf"));
+
+    private final Tracker<IndexFieldProvider> indexFieldProviderTracker;
+    private final Tracker<FulltextQueryTermsProvider> 
fulltextQueryTermsProviderTracker;
+
+    private volatile Map<String, CompositeIndexFieldProvider> 
indexFieldProviderMap;
+    private volatile Map<String, CompositeFulltextQueryTermsProvider> 
fulltextQueryTermsProviderMap;
+
+    public IndexAugmentorFactory(Whiteboard whiteboard) {
+        indexFieldProviderTracker = whiteboard.track(IndexFieldProvider.class);
+        fulltextQueryTermsProviderTracker = 
whiteboard.track(FulltextQueryTermsProvider.class);
+
+        indexFieldProviderMap = Maps.newHashMap();
+        fulltextQueryTermsProviderMap = Maps.newHashMap();
+    }
+
+    @Nonnull
+    public IndexFieldProvider getIndexFieldProvider(String nodeType) {
+        IndexFieldProvider provider = indexFieldProviderMap.get(nodeType);
+        return (provider != null) ? provider : IndexFieldProvider.DEFAULT;
+    }
+
+    @Nonnull
+    public FulltextQueryTermsProvider getFulltextQueryTermsProvider(String 
nodeType) {
+        FulltextQueryTermsProvider provider = 
fulltextQueryTermsProviderMap.get(nodeType);
+        return (provider != null) ? provider : 
FulltextQueryTermsProvider.DEFAULT;
+    }
+
+    public void refreshServices() {
+        refreshIndexFieldProviderServices();
+        refreshFulltextQueryTermsProviderServices();
+    }
+
+    private void refreshIndexFieldProviderServices() {
+        ListMultimap<String, IndexFieldProvider> 
indexFieldProviderListMultimap =
+                LinkedListMultimap.create();
+        for (IndexFieldProvider provider : 
indexFieldProviderTracker.getServices()) {
+            Set<String> supportedNodeTypes = provider.getSupportedTypes();
+            for (String nodeType : supportedNodeTypes) {
+                indexFieldProviderListMultimap.put(nodeType, provider);
+            }
+        }
+
+        Map<String, CompositeIndexFieldProvider> tempMap = Maps.newHashMap();
+        for (String nodeType : indexFieldProviderListMultimap.keySet()) {
+            List<IndexFieldProvider> providers = 
indexFieldProviderListMultimap.get(nodeType);
+            CompositeIndexFieldProvider compositeIndexFieldProvider =
+                    new CompositeIndexFieldProvider(nodeType, providers);
+            tempMap.put(nodeType, compositeIndexFieldProvider);
+        }
+
+        indexFieldProviderMap = tempMap;
+    }
+
+    private void refreshFulltextQueryTermsProviderServices() {
+        ListMultimap<String, FulltextQueryTermsProvider> 
fulltextQueryTermsProviderLinkedListMultimap =
+                LinkedListMultimap.create();
+        for (FulltextQueryTermsProvider provider : 
fulltextQueryTermsProviderTracker.getServices()) {
+            Set<String> supportedNodeTypes = provider.getSupportedTypes();
+            for (String nodeType : supportedNodeTypes) {
+                fulltextQueryTermsProviderLinkedListMultimap.put(nodeType, 
provider);
+            }
+        }
+
+        Map<String, CompositeFulltextQueryTermsProvider> tempMap = 
Maps.newHashMap();
+        for (String nodeType : 
fulltextQueryTermsProviderLinkedListMultimap.keySet()) {
+            List<FulltextQueryTermsProvider> providers = 
fulltextQueryTermsProviderLinkedListMultimap.get(nodeType);
+            CompositeFulltextQueryTermsProvider 
compositeFulltextQueryTermsProvider =
+                    new CompositeFulltextQueryTermsProvider(nodeType, 
providers);
+            tempMap.put(nodeType, compositeFulltextQueryTermsProvider);
+        }
+
+        fulltextQueryTermsProviderMap = tempMap;
+    }
+
+    class CompositeIndexFieldProvider implements IndexFieldProvider {
+
+        private final String nodeType;
+        private final List<IndexFieldProvider> providers;
+
+        CompositeIndexFieldProvider(String nodeType, List<IndexFieldProvider> 
providers) {
+            this.nodeType = nodeType;
+            this.providers = providers;
+        }
+
+        @Nonnull
+        @Override
+        public List<Field> getAugmentedFields(final String path,
+                                              final NodeState document, final 
NodeState indexDefinition) {
+            List<Field> fields = Lists.newArrayList();
+            for (IndexFieldProvider indexFieldProvider : providers) {
+                final long start = PERFLOG.start();
+                Iterable<Field> providedFields = 
indexFieldProvider.getAugmentedFields(path, document, indexDefinition);
+                PERFLOG.end(start, 1, "indexFieldProvider: {}, path: {}, doc: 
{}, indexDef: {}",
+                        indexFieldProvider, path, document, indexDefinition);
+                for (Field f : providedFields) {
+                    fields.add(f);
+                }
+            }
+            return fields;
+        }
+
+        @Nonnull
+        @Override
+        public Set<String> getSupportedTypes() {
+            return Collections.singleton(nodeType);
+        }
+    }
+
+    class CompositeFulltextQueryTermsProvider implements 
FulltextQueryTermsProvider {
+
+        private final String nodeType;
+        private final List<FulltextQueryTermsProvider> providers;
+
+        CompositeFulltextQueryTermsProvider(String nodeType, 
List<FulltextQueryTermsProvider> providers) {
+            this.nodeType = nodeType;
+            this.providers = providers;
+        }
+
+        @Override
+        public Query getQueryTerm(final String text, final Analyzer analyzer) {
+            List<Query> subQueries = Lists.newArrayList();
+            for (FulltextQueryTermsProvider fulltextQueryTermsProvider : 
providers) {
+                final long start = PERFLOG.start();
+                Query subQuery = fulltextQueryTermsProvider.getQueryTerm(text, 
analyzer);
+                PERFLOG.end(start, 1, "fulltextQueryTermsProvider: {}, text: 
{}", fulltextQueryTermsProvider, text);
+                if (subQuery != null) {
+                    subQueries.add(subQuery);
+                }
+            }
+
+            Query ret;
+            if (subQueries.size() == 0) {
+                ret = null;
+            } else if (subQueries.size() == 1) {
+                ret = subQueries.get(0);
+            } else {
+                BooleanQuery query = new BooleanQuery();
+                for ( Query subQuery : subQueries ) {
+                    query.add(subQuery, BooleanClause.Occur.SHOULD);
+                }
+                ret = query;
+            }
+
+            return ret;
+        }
+
+        @Nonnull
+        @Override
+        public Set<String> getSupportedTypes() {
+            return Collections.singleton(nodeType);
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

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=1721424&r1=1721423&r2=1721424&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 22 16:27:58 2015
@@ -42,6 +42,7 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.index.fulltext.ExtractedText;
 import 
org.apache.jackrabbit.oak.plugins.index.fulltext.ExtractedText.ExtractionResult;
 import org.apache.jackrabbit.oak.plugins.index.lucene.Aggregate.Matcher;
+import org.apache.jackrabbit.oak.plugins.index.lucene.spi.IndexFieldProvider;
 import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
 import org.apache.jackrabbit.oak.plugins.tree.TreeFactory;
 import org.apache.jackrabbit.oak.spi.commit.Editor;
@@ -122,12 +123,13 @@ public class LuceneIndexEditor implement
     LuceneIndexEditor(NodeState root, NodeBuilder definition,
                         IndexUpdateCallback updateCallback,
                         @Nullable IndexCopier indexCopier,
-                        ExtractedTextCache extractedTextCache) throws 
CommitFailedException {
+                        ExtractedTextCache extractedTextCache,
+                      IndexAugmentorFactory augmentorFactory) throws 
CommitFailedException {
         this.parent = null;
         this.name = null;
         this.path = "/";
         this.context = new LuceneIndexEditorContext(root, definition,
-                updateCallback, indexCopier, extractedTextCache);
+                updateCallback, indexCopier, extractedTextCache, 
augmentorFactory);
         this.root = root;
         this.isDeleted = false;
         this.matcherState = MatcherState.NONE;
@@ -341,6 +343,8 @@ public class LuceneIndexEditor implement
         dirty |= indexNullCheckEnabledProps(path, fields, state);
         dirty |= indexNotNullCheckEnabledProps(path, fields, state);
 
+        dirty |= augmentCustomFields(path, fields, state);
+
         // Check if a node having a single property was modified/deleted
         if (!dirty) {
             dirty = indexIfSinglePropertyRemoved();
@@ -452,14 +456,14 @@ public class LuceneIndexEditor implement
                                   String pname,
                                   PropertyDefinition pd) {
         boolean includeTypeForFullText = 
indexingRule.includePropertyType(property.getType().tag());
+
+        boolean dirty = false;
         if (Type.BINARY.tag() == property.getType().tag()
                 && includeTypeForFullText) {
             fields.addAll(newBinary(property, state, null, path + "@" + 
pname));
-            return true;
-        }  else {
-            boolean dirty = false;
-
-            if (pd.propertyIndex && 
pd.includePropertyType(property.getType().tag())){
+            dirty = true;
+        } else {
+            if (pd.propertyIndex && 
pd.includePropertyType(property.getType().tag())) {
                 dirty |= addTypedFields(fields, property, pname);
             }
 
@@ -489,8 +493,9 @@ public class LuceneIndexEditor implement
                 dirty |= addFacetFields(fields, property, pname, pd);
             }
 
-            return dirty;
         }
+
+        return dirty;
     }
 
     private String constructAnalyzedPropertyName(String pname) {
@@ -627,6 +632,26 @@ public class LuceneIndexEditor implement
         return fields;
     }
 
+    private boolean augmentCustomFields(final String path, final List<Field> 
fields,
+                                        final NodeState document) {
+        boolean dirty = false;
+
+        IndexAugmentorFactory augmentorFactory = context.getAugmentorFactory();
+        if (augmentorFactory != null) {
+            IndexDefinition defn = getDefinition();
+            Iterable<Field> augmentedFields = augmentorFactory
+                    .getIndexFieldProvider(indexingRule.getNodeTypeName())
+                    .getAugmentedFields(path, document, 
defn.getDefinitionNodeState());
+
+            for (Field field : augmentedFields) {
+                fields.add(field);
+                dirty = true;
+            }
+        }
+
+        return dirty;
+    }
+
     //~-------------------------------------------------------< NullCheck 
Support >
 
     private boolean indexNotNullCheckEnabledProps(String path, List<Field> 
fields, NodeState state) {
@@ -765,7 +790,6 @@ public class LuceneIndexEditor implement
         });
         return dirtyFlag.get();
     }
-
     /**
      * Create the fulltext field from the aggregated nodes. If result is for 
aggregate for a relative node
      * include then

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java?rev=1721424&r1=1721423&r2=1721424&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
 Tue Dec 22 16:27:58 2015
@@ -147,19 +147,23 @@ public class LuceneIndexEditorContext {
     private final TextExtractionStats textExtractionStats = new 
TextExtractionStats();
 
     private final ExtractedTextCache extractedTextCache;
+
+    private final IndexAugmentorFactory augmentorFactory;
     /**
      * The media types supported by the parser used.
      */
     private Set<MediaType> supportedMediaTypes;
 
     LuceneIndexEditorContext(NodeState root, NodeBuilder definition, 
IndexUpdateCallback updateCallback,
-                             @Nullable IndexCopier indexCopier, 
ExtractedTextCache extractedTextCache) {
+                             @Nullable IndexCopier indexCopier, 
ExtractedTextCache extractedTextCache,
+                             IndexAugmentorFactory augmentorFactory) {
         this.definitionBuilder = definition;
         this.indexCopier = indexCopier;
         this.definition = new IndexDefinition(root, definition);
         this.indexedNodes = 0;
         this.updateCallback = updateCallback;
         this.extractedTextCache = extractedTextCache;
+        this.augmentorFactory = augmentorFactory;
         if (this.definition.isOfOldFormat()){
             IndexDefinition.updateDefinition(definition);
         }
@@ -349,6 +353,10 @@ public class LuceneIndexEditorContext {
         return extractedTextCache;
     }
 
+    IndexAugmentorFactory getAugmentorFactory() {
+        return augmentorFactory;
+    }
+
     public boolean isReindex() {
         return reindex;
     }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java?rev=1721424&r1=1721423&r2=1721424&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
 Tue Dec 22 16:27:58 2015
@@ -39,6 +39,7 @@ import static org.apache.jackrabbit.oak.
 public class LuceneIndexEditorProvider implements IndexEditorProvider {
     private final IndexCopier indexCopier;
     private final ExtractedTextCache extractedTextCache;
+    private final IndexAugmentorFactory augmentorFactory;
 
     public LuceneIndexEditorProvider() {
         this(null);
@@ -51,8 +52,15 @@ public class LuceneIndexEditorProvider i
 
     public LuceneIndexEditorProvider(@Nullable IndexCopier indexCopier,
                                      ExtractedTextCache extractedTextCache) {
+        this(indexCopier, extractedTextCache, null);
+    }
+
+    public LuceneIndexEditorProvider(@Nullable IndexCopier indexCopier,
+                                     ExtractedTextCache extractedTextCache,
+                                     IndexAugmentorFactory augmentorFactory) {
         this.indexCopier = indexCopier;
         this.extractedTextCache = extractedTextCache;
+        this.augmentorFactory = augmentorFactory;
     }
 
     @Override
@@ -61,7 +69,7 @@ public class LuceneIndexEditorProvider i
             @Nonnull IndexUpdateCallback callback)
             throws CommitFailedException {
         if (TYPE_LUCENE.equals(type)) {
-            return new LuceneIndexEditor(root, definition, callback, 
indexCopier, extractedTextCache);
+            return new LuceneIndexEditor(root, definition, callback, 
indexCopier, extractedTextCache, augmentorFactory);
         }
         return null;
     }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java?rev=1721424&r1=1721423&r2=1721424&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
 Tue Dec 22 16:27:58 2015
@@ -45,21 +45,24 @@ public class LuceneIndexProvider impleme
 
     ScorerProviderFactory scorerFactory;
 
+    IndexAugmentorFactory augmentorFactory;
+
     public LuceneIndexProvider() {
         this(new IndexTracker());
     }
 
     public LuceneIndexProvider(IndexCopier indexCopier) {
-        this(new IndexTracker(indexCopier), ScorerProviderFactory.DEFAULT);
+        this(new IndexTracker(indexCopier));
     }
 
     public LuceneIndexProvider(IndexTracker tracker) {
-        this(tracker, ScorerProviderFactory.DEFAULT);
+        this(tracker, ScorerProviderFactory.DEFAULT, null);
     }
 
-    public LuceneIndexProvider(IndexTracker tracker, ScorerProviderFactory 
scorerFactory) {
+    public LuceneIndexProvider(IndexTracker tracker, ScorerProviderFactory 
scorerFactory, IndexAugmentorFactory augmentorFactory) {
         this.tracker = tracker;
         this.scorerFactory = scorerFactory;
+        this.augmentorFactory = augmentorFactory;
     }
 
     public void close() {
@@ -85,7 +88,7 @@ public class LuceneIndexProvider impleme
     }
 
     protected LucenePropertyIndex newLucenePropertyIndex() {
-        return new LucenePropertyIndex(tracker, scorerFactory);
+        return new LucenePropertyIndex(tracker, scorerFactory, 
augmentorFactory);
     }
 
     /**

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java?rev=1721424&r1=1721423&r2=1721424&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java
 Tue Dec 22 16:27:58 2015
@@ -45,6 +45,7 @@ import org.apache.felix.scr.annotations.
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.ReferencePolicy;
 import org.apache.felix.scr.annotations.ReferencePolicyOption;
+import org.apache.felix.scr.annotations.References;
 import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
 import org.apache.jackrabbit.oak.cache.CacheStats;
 import org.apache.jackrabbit.oak.commons.PropertiesUtil;
@@ -52,6 +53,8 @@ import org.apache.jackrabbit.oak.osgi.Os
 import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
 import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator;
 import 
org.apache.jackrabbit.oak.plugins.index.fulltext.PreExtractedTextProvider;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.spi.FulltextQueryTermsProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.spi.IndexFieldProvider;
 import org.apache.jackrabbit.oak.spi.commit.BackgroundObserver;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.score.ScorerProviderFactory;
 import org.apache.jackrabbit.oak.spi.commit.BackgroundObserverMBean;
@@ -74,6 +77,20 @@ import static org.apache.jackrabbit.oak.
 
 @SuppressWarnings("UnusedDeclaration")
 @Component(metatype = true, label = "Apache Jackrabbit Oak 
LuceneIndexProvider")
+@References({
+        @Reference(name = "IndexFieldProvider",
+                policy = ReferencePolicy.DYNAMIC,
+                cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+                referenceInterface = IndexFieldProvider.class,
+                bind = "indexFieldProviderServiceUpdated",
+                unbind = "indexFieldProviderServiceUpdated"),
+        @Reference(name = "FulltextQueryTermsProvider",
+                policy = ReferencePolicy.DYNAMIC,
+                cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+                referenceInterface = FulltextQueryTermsProvider.class,
+                bind = "indexFulltextQueryTermsProviderServiceUpdated",
+                unbind = "indexFulltextQueryTermsProviderServiceUpdated")
+})
 public class LuceneIndexProviderService {
     public static final String REPOSITORY_HOME = "repository.home";
 
@@ -177,6 +194,8 @@ public class LuceneIndexProviderService
     @Reference
     ScorerProviderFactory scorerFactory;
 
+    private IndexAugmentorFactory augmentorFactory;
+
     @Reference(policy = ReferencePolicy.DYNAMIC,
             cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
             policyOption = ReferencePolicyOption.GREEDY
@@ -207,7 +226,8 @@ public class LuceneIndexProviderService
         whiteboard = new OsgiWhiteboard(bundleContext);
         threadPoolSize = 
PropertiesUtil.toInteger(config.get(PROP_THREAD_POOL_SIZE), 
PROP_THREAD_POOL_SIZE_DEFAULT);
         initializeExtractedTextCache(bundleContext, config);
-        indexProvider = new LuceneIndexProvider(createTracker(bundleContext, 
config), scorerFactory);
+        augmentorFactory = new IndexAugmentorFactory(whiteboard);
+        indexProvider = new LuceneIndexProvider(createTracker(bundleContext, 
config), scorerFactory, augmentorFactory);
         initializeLogging(config);
         initialize();
 
@@ -288,10 +308,10 @@ public class LuceneIndexProviderService
         LuceneIndexEditorProvider editorProvider;
         if (enableCopyOnWrite){
             initializeIndexCopier(bundleContext, config);
-            editorProvider = new LuceneIndexEditorProvider(indexCopier, 
extractedTextCache);
+            editorProvider = new LuceneIndexEditorProvider(indexCopier, 
extractedTextCache, augmentorFactory);
             log.info("Enabling CopyOnWrite support. Index files would be 
copied under {}", indexDir.getAbsolutePath());
         } else {
-            editorProvider = new LuceneIndexEditorProvider(null, 
extractedTextCache);
+            editorProvider = new LuceneIndexEditorProvider(null, 
extractedTextCache, augmentorFactory);
         }
         
regs.add(bundleContext.registerService(IndexEditorProvider.class.getName(), 
editorProvider, null));
         oakRegs.add(registerMBean(whiteboard,
@@ -471,4 +491,11 @@ public class LuceneIndexProviderService
         registerExtractedTextProvider(null);
     }
 
+    private void indexFieldProviderServiceUpdated(IndexFieldProvider 
indexFieldProvider) {
+        augmentorFactory.refreshServices();
+    }
+
+    private void 
indexFulltextQueryTermsProviderServiceUpdated(FulltextQueryTermsProvider 
fulltextQueryTermsProvider) {
+        augmentorFactory.refreshServices();
+    }
 }

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=1721424&r1=1721423&r2=1721424&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 22 16:27:58 2015
@@ -47,6 +47,7 @@ import org.apache.jackrabbit.oak.plugins
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.IndexingRule;
 import org.apache.jackrabbit.oak.plugins.index.lucene.IndexPlanner.PlanResult;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.score.ScorerProviderFactory;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.spi.FulltextQueryTermsProvider;
 import org.apache.jackrabbit.oak.plugins.index.lucene.util.FacetHelper;
 import org.apache.jackrabbit.oak.plugins.index.lucene.util.MoreLikeThisHelper;
 import org.apache.jackrabbit.oak.plugins.index.lucene.util.SpellcheckHelper;
@@ -208,14 +209,20 @@ public class LucenePropertyIndex impleme
     private final Highlighter highlighter = new Highlighter(new 
SimpleHTMLFormatter("<strong>", "</strong>"),
             new SimpleHTMLEncoder(), null);
 
+    private final IndexAugmentorFactory augmentorFactory;
+
     public LucenePropertyIndex(IndexTracker tracker) {
-        this.tracker = tracker;
-        this.scorerProviderFactory = ScorerProviderFactory.DEFAULT;
+        this(tracker, ScorerProviderFactory.DEFAULT);
     }
 
     public LucenePropertyIndex(IndexTracker tracker, ScorerProviderFactory 
factory) {
+        this(tracker, factory, null);
+    }
+
+    public LucenePropertyIndex(IndexTracker tracker, ScorerProviderFactory 
factory, IndexAugmentorFactory augmentorFactory) {
         this.tracker = tracker;
         this.scorerProviderFactory = factory;
+        this.augmentorFactory = augmentorFactory;
     }
 
     @Override
@@ -275,7 +282,7 @@ public class LucenePropertyIndex impleme
                     .append("(")
                     .append(path)
                     .append(") ");
-            sb.append(getLuceneRequest(plan, null));
+            sb.append(getLuceneRequest(plan, augmentorFactory, null));
             if (plan.getSortOrder() != null && !plan.getSortOrder().isEmpty()) 
{
                 sb.append(" ordering:").append(plan.getSortOrder());
             }
@@ -366,7 +373,7 @@ public class LucenePropertyIndex impleme
                 checkState(indexNode != null);
                 try {
                     IndexSearcher searcher = indexNode.getSearcher();
-                    LuceneRequestFacade luceneRequestFacade = 
getLuceneRequest(plan, searcher.getIndexReader());
+                    LuceneRequestFacade luceneRequestFacade = 
getLuceneRequest(plan, augmentorFactory, searcher.getIndexReader());
                     if (luceneRequestFacade.getLuceneRequest() instanceof 
Query) {
                         Query query = (Query) 
luceneRequestFacade.getLuceneRequest();
 
@@ -514,7 +521,7 @@ public class LucenePropertyIndex impleme
                 checkState(indexNode != null);
                 try {
                     IndexSearcher searcher = indexNode.getSearcher();
-                    LuceneRequestFacade luceneRequestFacade = 
getLuceneRequest(plan, searcher.getIndexReader());
+                    LuceneRequestFacade luceneRequestFacade = 
getLuceneRequest(plan, augmentorFactory, searcher.getIndexReader());
                     if (luceneRequestFacade.getLuceneRequest() instanceof 
Query) {
                         Query query = (Query) 
luceneRequestFacade.getLuceneRequest();
                         TotalHitCountCollector collector = new 
TotalHitCountCollector();
@@ -649,7 +656,8 @@ public class LucenePropertyIndex impleme
      * @param reader the Lucene reader
      * @return the Lucene query
      */
-    private static LuceneRequestFacade getLuceneRequest(IndexPlan plan, 
IndexReader reader) {
+    private static LuceneRequestFacade getLuceneRequest(IndexPlan plan, 
IndexAugmentorFactory augmentorFactory, IndexReader reader) {
+        FulltextQueryTermsProvider augmentor = getIndexAgumentor(plan, 
augmentorFactory);
         List<Query> qs = new ArrayList<Query>();
         Filter filter = plan.getFilter();
         FullTextExpression ft = filter.getFullTextConstraint();
@@ -661,7 +669,7 @@ public class LucenePropertyIndex impleme
             // when using the LowCostLuceneIndexProvider
             // which is used for testing
         } else {
-            qs.add(getFullTextQuery(plan, ft, analyzer));
+            qs.add(getFullTextQuery(plan, ft, analyzer, augmentor));
         }
 
 
@@ -815,6 +823,16 @@ public class LucenePropertyIndex impleme
         }
         return null;
     }
+    private static FulltextQueryTermsProvider getIndexAgumentor(IndexPlan 
plan, IndexAugmentorFactory augmentorFactory) {
+        PlanResult planResult = getPlanResult(plan);
+        IndexDefinition defn = planResult.indexDefinition;
+
+        if (augmentorFactory != null && 
defn.getVersion().isAtLeast(IndexFormatVersion.V2)){
+            return 
augmentorFactory.getFulltextQueryTermsProvider(getPlanResult(plan).indexingRule.getNodeTypeName());
+        }
+
+        return null;
+    }
 
     private static void addNonFullTextConstraints(List<Query> qs,
                                                   IndexPlan plan, IndexReader 
reader) {
@@ -1161,7 +1179,7 @@ public class LucenePropertyIndex impleme
     }
 
     static Query getFullTextQuery(final IndexPlan plan, FullTextExpression ft,
-                                  final Analyzer analyzer) {
+                                  final Analyzer analyzer, final 
FulltextQueryTermsProvider augmentor) {
         final PlanResult pr = getPlanResult(plan);
         // a reference to the query, so it can be set in the visitor
         // (a "non-local return")
@@ -1178,7 +1196,7 @@ public class LucenePropertyIndex impleme
             public boolean visit(FullTextOr or) {
                 BooleanQuery q = new BooleanQuery();
                 for (FullTextExpression e : or.list) {
-                    Query x = getFullTextQuery(plan, e, analyzer);
+                    Query x = getFullTextQuery(plan, e, analyzer, augmentor);
                     q.add(x, SHOULD);
                 }
                 result.set(q);
@@ -1189,7 +1207,7 @@ public class LucenePropertyIndex impleme
             public boolean visit(FullTextAnd and) {
                 BooleanQuery q = new BooleanQuery();
                 for (FullTextExpression e : and.list) {
-                    Query x = getFullTextQuery(plan, e, analyzer);
+                    Query x = getFullTextQuery(plan, e, analyzer, augmentor);
                     /* Only unwrap the clause if MUST_NOT(x) */
                     boolean hasMustNot = false;
                     if (x instanceof BooleanQuery) {
@@ -1216,7 +1234,7 @@ public class LucenePropertyIndex impleme
 
             private boolean visitTerm(String propertyName, String text, String 
boost, boolean not) {
                 String p = getLuceneFieldName(propertyName, pr);
-                Query q = tokenToQuery(text, p, pr.indexingRule, analyzer);
+                Query q = tokenToQuery(text, p, pr.indexingRule, analyzer, 
augmentor);
                 if (q == null) {
                     return false;
                 }
@@ -1262,7 +1280,8 @@ public class LucenePropertyIndex impleme
         return p;
     }
 
-    private static Query tokenToQuery(String text, String fieldName, 
IndexingRule indexingRule, Analyzer analyzer) {
+    private static Query tokenToQuery(String text, String fieldName, 
IndexingRule indexingRule, Analyzer analyzer, FulltextQueryTermsProvider 
augmentor) {
+        Query ret;
         //Expand the query on fulltext field
         if (FieldNames.FULLTEXT.equals(fieldName) &&
                 !indexingRule.getNodeScopeAnalyzedProps().isEmpty()) {
@@ -1276,9 +1295,25 @@ public class LucenePropertyIndex impleme
             //Add the query for actual fulltext field also. That query would
             //not be boosted
             in.add(tokenToQuery(text, fieldName, analyzer), 
BooleanClause.Occur.SHOULD);
-            return in;
+            ret = in;
+        } else {
+            ret = tokenToQuery(text, fieldName, analyzer);
         }
-        return tokenToQuery(text, fieldName, analyzer);
+
+        //Augment query terms if available (as a 'SHOULD' clause)
+        if (augmentor != null && FieldNames.FULLTEXT.equals(fieldName)) {
+            Query subQuery = augmentor.getQueryTerm(text, analyzer);
+            if (subQuery != null) {
+                BooleanQuery query = new BooleanQuery();
+
+                query.add(ret, BooleanClause.Occur.SHOULD);
+                query.add(subQuery, BooleanClause.Occur.SHOULD);
+
+                ret = query;
+            }
+        }
+
+        return ret;
     }
 
     static Query tokenToQuery(String text, String fieldName, Analyzer 
analyzer) {

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=1721424&r1=1721423&r2=1721424&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 22 16:27:58 2015
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("2.9.0")
+@Version("3.0.0")
 @Export(optional = "provide:=true")
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
 import aQute.bnd.annotation.Version;
-import aQute.bnd.annotation.Export;
\ No newline at end of file
+import aQute.bnd.annotation.Export;

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/FulltextQueryTermsProvider.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/FulltextQueryTermsProvider.java?rev=1721424&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/FulltextQueryTermsProvider.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/FulltextQueryTermsProvider.java
 Tue Dec 22 16:27:58 2015
@@ -0,0 +1,62 @@
+/*
+ * 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.plugins.index.lucene.spi;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.search.Query;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Implementations of this interface would get callbacks while forming lucene 
full text queries.
+ */
+public interface FulltextQueryTermsProvider {
+    /**
+     * Implementation which doesn't do anything useful... yet, abides with the 
contract.
+     */
+    FulltextQueryTermsProvider DEFAULT = new FulltextQueryTermsProvider() {
+        @Override
+        public Query getQueryTerm(String text, Analyzer analyzer) {
+            return null;
+        }
+
+        @Override
+        public Set<String> getSupportedTypes() {
+            return Collections.EMPTY_SET;
+        }
+    };
+    /**
+     * This method would get called while forming full text clause for full 
text clause not constrained on a particular
+     * field.
+     * @param text full text term
+     * @param analyzer {@link Analyzer} being used while forming the query. 
Can be used to analyze text consistently.
+     * @return {@link Query} object to be OR'ed with query being prepared. 
{@code null}, if nothing is to be added.
+     */
+    Query getQueryTerm(String text, Analyzer analyzer);
+
+    /**
+     * This method is used to find which node types are supported by the 
implementation. Based, on the index
+     * definition being used to query the document, only those implementations 
would get callback to
+     * {@link FulltextQueryTermsProvider#getQueryTerm} which declare a 
matching node type. Note, node types are
+     * exact matches and do not support inheritance.
+     * @return {@link Set} of types supported by the implementation
+     */
+    @Nonnull
+    Set<String> getSupportedTypes();
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/FulltextQueryTermsProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/IndexFieldProvider.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/IndexFieldProvider.java?rev=1721424&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/IndexFieldProvider.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/IndexFieldProvider.java
 Tue Dec 22 16:27:58 2015
@@ -0,0 +1,66 @@
+/*
+ * 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.plugins.index.lucene.spi;
+
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.lucene.document.Field;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Implementations of this interface would get callbacks while indexing 
documents. It's the responsibility
+ * of the implementation to exit as early as possible if it doesn't care about 
the document being indexed.
+ */
+public interface IndexFieldProvider {
+    /**
+     * Implementation which doesn't do anything useful... yet, abides with the 
contract.
+     */
+    IndexFieldProvider DEFAULT = new IndexFieldProvider() {
+        @Override
+        public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+            return Collections.EMPTY_LIST;
+        }
+
+        @Override
+        public Set<String> getSupportedTypes() {
+            return Collections.EMPTY_SET;
+        }
+    };
+
+    /**
+     * This method would get called while indexing a document.
+     *
+     * @param path path of the document being indexed
+     * @param document {@link NodeState} of the document being indexed
+     * @param indexDefinition {@link NodeState} of index definition
+     * @return {@link Iterable} of fields that are to be added to {@link 
org.apache.lucene.document.Document} being prepared
+     */
+    @Nonnull
+    Iterable<Field> getAugmentedFields(String path, NodeState document, 
NodeState indexDefinition);
+
+    /**
+     * This method is used to find which node types are supported by the 
implementation. Based, on the index
+     * definition being used to index the document, only those implementations 
would get callback to
+     * {@link IndexFieldProvider#getAugmentedFields} which declare a matching 
node type. Note, node types are
+     * exact matches and do not support inheritance.
+     * @return {@link Set} of types supported by the implementation
+     */
+    @Nonnull
+    Set<String> getSupportedTypes();
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/IndexFieldProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Copied: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/package-info.java
 (from r1721341, 
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/spi/package-info.java?p2=jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/spi/package-info.java&p1=jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/package-info.java&r1=1721341&r2=1721424&rev=1721424&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/spi/package-info.java
 Tue Dec 22 16:27:58 2015
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("2.9.0")
+@Version("1.0.0")
 @Export(optional = "provide:=true")
-package org.apache.jackrabbit.oak.plugins.index.lucene;
+package org.apache.jackrabbit.oak.plugins.index.lucene.spi;
 
+import aQute.bnd.annotation.Export;
 import aQute.bnd.annotation.Version;
-import aQute.bnd.annotation.Export;
\ No newline at end of file

Added: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactoryTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactoryTest.java?rev=1721424&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactoryTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactoryTest.java
 Tue Dec 22 16:27:58 2015
@@ -0,0 +1,185 @@
+/*
+ * 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.plugins.index.lucene;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.spi.FulltextQueryTermsProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.spi.IndexFieldProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+import java.util.Set;
+
+import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class IndexAugmentorFactoryTest {
+    private IndexAugmentorFactory indexAugmentorFactory;
+    private Whiteboard whiteboard = new DefaultWhiteboard();
+
+    @Before
+    public void initializeFactory() {
+        indexAugmentorFactory = new IndexAugmentorFactory(whiteboard);
+    }
+
+    @Test
+    public void compositeIndexProvider()
+    {
+        final String typeA = "type:A";
+        final String typeB = "type:B";
+        final String typeC = "type:C";
+        final String typeD = "type:D";
+
+        new IdentifiableIndexFiledProvider("1", Sets.newHashSet(typeA, typeB));
+        new IdentifiableIndexFiledProvider("2", Sets.newHashSet(typeC));
+        new IdentifiableIndexFiledProvider("3", Sets.newHashSet(typeA, typeB));
+
+        indexAugmentorFactory.refreshServices();
+
+        validateComposedFields(typeA, "1", "3");
+        validateComposedFields(typeC, "2");
+        validateComposedFields(typeD);
+    }
+
+    @Test
+    public void compositeQueryTermsProvider()
+    {
+        final String typeA = "type:A";
+        final String typeB = "type:B";
+        final String typeC = "type:C";
+        final String typeD = "type:D";
+        final String typeE = "type:E";
+
+        new IdentifiableQueryTermsProvider("1", Sets.newHashSet(typeA, typeB));
+        new IdentifiableQueryTermsProvider("2", Sets.newHashSet(typeC));
+        new IdentifiableQueryTermsProvider("3", Sets.newHashSet(typeA, typeB));
+        new IdentifiableQueryTermsProvider(null, Sets.newHashSet(typeE));
+
+        indexAugmentorFactory.refreshServices();
+
+        validateComposedQueryTerms(typeA, "1", "3");
+        validateComposedQueryTerms(typeC, "2");
+        validateComposedQueryTerms(typeD);
+        validateComposedQueryTerms(typeE);
+    }
+
+    void validateComposedFields(String type, String ... expected) {
+        IndexFieldProvider compositeIndexProvider = 
indexAugmentorFactory.getIndexFieldProvider(type);
+        Iterable<Field> fields = 
compositeIndexProvider.getAugmentedFields(null, null, null);
+        Set<String> ids = Sets.newHashSet();
+        for (Field f : fields) {
+            ids.add(f.stringValue());
+        }
+
+        assertEquals(expected.length, Iterables.size(ids));
+        assertThat(ids, CoreMatchers.hasItems(expected));
+    }
+
+    void validateComposedQueryTerms(String type, String ... expected) {
+        FulltextQueryTermsProvider compositeQueryTermsProvider = 
indexAugmentorFactory.getFulltextQueryTermsProvider(type);
+        Query q = compositeQueryTermsProvider.getQueryTerm(null, null);
+        if (q == null) {
+            assertEquals("No query terms generated for " + type + ".", 0, 
expected.length);
+        } else {
+            Set<String> ids = Sets.newHashSet();
+            if (q instanceof BooleanQuery) {
+                BooleanQuery query = (BooleanQuery) q;
+                List<BooleanClause> clauses = query.clauses();
+                for (BooleanClause clause : clauses) {
+                    assertEquals(SHOULD, clause.getOccur());
+
+                    Query subQuery = clause.getQuery();
+                    String subQueryStr = subQuery.toString();
+                    ids.add(subQueryStr.substring(0, 
subQueryStr.indexOf(":1")));
+                }
+            } else {
+                String subQueryStr = q.toString();
+                ids.add(subQueryStr.substring(0, subQueryStr.indexOf(":1")));
+            }
+
+            assertEquals(expected.length, Iterables.size(ids));
+            assertThat(ids, CoreMatchers.hasItems(expected));
+        }
+    }
+
+    class IdentifiableIndexFiledProvider implements IndexFieldProvider {
+        private final Field id;
+        private final Set<String> nodeTypes;
+
+        IdentifiableIndexFiledProvider(String id, Set<String> nodeTypes) {
+            this.id = new StringField("id", id, Field.Store.NO);
+            this.nodeTypes = nodeTypes;
+
+            whiteboard.register(IndexFieldProvider.class, this, null);
+        }
+
+        @Nonnull
+        @Override
+        public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+            return Lists.newArrayList(id);
+        }
+
+        @Nonnull
+        @Override
+        public Set<String> getSupportedTypes() {
+            return nodeTypes;
+        }
+    }
+
+    class IdentifiableQueryTermsProvider implements FulltextQueryTermsProvider 
{
+        private final Query id;
+        private final Set<String> nodeTypes;
+
+        IdentifiableQueryTermsProvider(String id, Set<String> nodeTypes) {
+            this.id = (id == null)?null:new TermQuery(new Term(id, "1"));
+            this.nodeTypes = nodeTypes;
+
+            whiteboard.register(FulltextQueryTermsProvider.class, this, null);
+        }
+
+        @Override
+        public Query getQueryTerm(String text, Analyzer analyzer) {
+            return id;
+        }
+
+        @Nonnull
+        @Override
+        public Set<String> getSupportedTypes() {
+            return nodeTypes;
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexAugmentorFactoryTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAugmentTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAugmentTest.java?rev=1721424&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAugmentTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAugmentTest.java
 Tue Dec 22 16:27:58 2015
@@ -0,0 +1,703 @@
+/*
+ * 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.plugins.index.lucene;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.score.ScorerProviderFactory;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.spi.FulltextQueryTermsProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.spi.IndexFieldProvider;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.NodeTypeRegistry;
+import org.apache.jackrabbit.oak.query.AbstractQueryTest;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class LuceneIndexAugmentTest extends AbstractQueryTest {
+    private final SimpleIndexAugmentorFactory factory = new 
SimpleIndexAugmentorFactory();
+
+    private IndexTracker tracker = new IndexTracker();
+
+    private IndexNode indexNode;
+
+    @Override
+    protected void createTestIndexNode() throws Exception {
+        setTraversalEnabled(false);
+    }
+
+    @Override
+    protected ContentRepository createRepository() {
+        LuceneIndexEditorProvider editorProvider = new 
LuceneIndexEditorProvider(null,
+                new ExtractedTextCache(0, 0),
+                factory);
+        LuceneIndexProvider provider = new LuceneIndexProvider(tracker,
+                ScorerProviderFactory.DEFAULT,
+                factory);
+        return new Oak()
+                .with(new OpenSecurityProvider())
+                .with((QueryIndexProvider) provider)
+                .with((Observer) provider)
+                .with(editorProvider)
+                .createContentRepository();
+    }
+
+    //OAK-3576
+    @Test public void queryHook() throws Exception {
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        TestUtil.enableForFullText(props, "foo");
+        root.commit();
+
+        //setup query augmentor
+        final String testSearchText = "search this text";
+        final String realSearchText = "bar";
+        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                assertEquals("Full text term passed to provider isn't same as 
the one passed in query",
+                        testSearchText, text);
+                return new TermQuery(new Term(":fulltext", realSearchText));
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+
+        //add content
+        Tree test = root.getTree("/").addChild("test");
+        Tree node = createNodeWithType(test, "item", TestUtil.NT_TEST);
+        node.setProperty("foo", realSearchText);
+        root.commit();
+
+        //query (testSearchText doesn't have 'bar'... our augment would search 
for :fulltext:bar
+        String query = "select [jcr:path] from [oak:TestNode] where 
CONTAINS(*, '" + testSearchText + "')";
+        List<String> paths = executeQuery(query, SQL2);
+        assertEquals("Augmented query wasn't used to search", 1, paths.size());
+        assertEquals("/test/item", paths.get(0));
+    }
+
+    //OAK-3576
+    @Test public void indexHookCallbackFrequency() throws Exception {
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        TestUtil.enablePropertyIndex(props, "foo1", false);
+        TestUtil.enablePropertyIndex(props, "foo2", false);
+        TestUtil.enablePropertyIndex(props, "subChild/foo3", false);
+        root.commit();
+
+        //setup index augmentor
+        final AtomicInteger counter = new AtomicInteger(0);
+        factory.indexFieldProvider = new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                counter.incrementAndGet();
+                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, 
document, indexDefinition);
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+
+        //add content
+        counter.set(0);
+        Tree test = root.getTree("/").addChild("test");
+        Tree node = createNodeWithType(test, "item", TestUtil.NT_TEST);
+        node.setProperty("foo1", "bar1");
+        node.setProperty("foo2", "bar2");
+        Tree subChild = node.addChild("subChild");
+        subChild.setProperty("foo3", "bar3");
+        root.commit();
+        assertEquals("Number of callbacks should be same as number of changed 
properties", 1, counter.get());
+
+        //change sub-property
+        counter.set(0);
+        subChild = root.getTree("/test/item/subChild");
+        subChild.setProperty("foo3", "bar4");
+        root.commit();
+        assertEquals("Sub child property change should make call backs for all 
indexed properties", 1, counter.get());
+    }
+
+    //OAK-3576
+    @Test public void indexHookCallbackAndStorage() throws Exception {
+        final String propName = "subChild/foo";
+
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        TestUtil.enableForFullText(props, propName);
+        root.commit();
+
+        //setup index augmentor
+        factory.indexFieldProvider = new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                assertEquals("/test/item", path);
+                assertEquals(TestUtil.NT_TEST, 
document.getName(JcrConstants.JCR_PRIMARYTYPE));
+                assertEquals(IndexConstants.INDEX_DEFINITIONS_NODE_TYPE,
+                        indexDefinition.getName(JcrConstants.JCR_PRIMARYTYPE));
+                return Lists.<Field>newArrayList(new StringField("barbar", 
"1", Field.Store.NO));
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+
+        //add content
+        Tree test = root.getTree("/").addChild("test");
+        Tree node = createNodeWithType(test, "item", 
TestUtil.NT_TEST).addChild("subChild");
+        node.setProperty("foo", "bar");
+        root.commit();
+
+        //Check document that made to the index
+        IndexSearcher searcher = getSearcher();
+        TopDocs docs = searcher.search(new TermQuery(new Term("barbar", "1")), 
10);
+        ScoreDoc[] scoreDocs = docs.scoreDocs;
+        assertEquals("Number of results should be 1", 1, scoreDocs.length);
+        Document doc = searcher.doc(scoreDocs[0].doc);
+        String path = doc.get(":path");
+        assertEquals("/test/item", path);
+    }
+
+    //OAK-3576
+    @Test
+    public void nullBehavior() throws Exception {
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        TestUtil.enableForFullText(props, "foo");
+        root.commit();
+
+        Tree rootTree = root.getTree("/").addChild("test");
+
+        //Note: augmentor behavior is tested elsewhere... we are just checking 
if default works
+
+        int testIndex = 1;
+        //both query and index augmentors are null (no exception expected)
+        checkSimpleBehavior(rootTree, testIndex++);
+
+        //Set a very sad query augmentor
+        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                return null;
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return FulltextQueryTermsProvider.DEFAULT.getSupportedTypes();
+            }
+        };
+        checkSimpleBehavior(rootTree, testIndex++);
+
+        //Set query augmentor... with null query
+        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                return null;
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+        checkSimpleBehavior(rootTree, testIndex++);
+
+        //Set query augmentor... with some query
+        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                return new TermQuery(new Term("bar", "baz"));
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+        checkSimpleBehavior(rootTree, testIndex++);
+
+        factory.fulltextQueryTermsProvider = null;
+
+        //Set a very sad index augmentor
+        factory.indexFieldProvider = IndexFieldProvider.DEFAULT;
+        checkSimpleBehavior(rootTree, testIndex++);
+
+        //Set index augmentor... with null fields
+        factory.indexFieldProvider = new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, 
document, indexDefinition);
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+        checkSimpleBehavior(rootTree, testIndex++);
+
+        //Set index augmentor... with some fields
+        factory.fulltextQueryTermsProvider = null;
+        factory.indexFieldProvider = new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                List<Field> fields = Lists.newArrayList();
+                fields.add(new StringField("bar", "baz", Field.Store.NO));
+                return fields;
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+        checkSimpleBehavior(rootTree, testIndex++);
+    }
+
+    //OAK-3576
+    @Test
+    public void skipDefaultOnlyUsingAugmentors() throws Exception {
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        Tree prop = props.addChild("foo1");
+        prop.setProperty(LuceneIndexConstants.PROP_INDEX, true);
+        prop = props.addChild("foo2");
+        prop.setProperty(LuceneIndexConstants.PROP_NAME, "subChild/foo2");
+        prop.setProperty(LuceneIndexConstants.PROP_INDEX, true);
+        root.commit();
+
+        //setup augmentors
+        final AtomicInteger indexingCounter = new AtomicInteger(0);
+        factory.indexFieldProvider = new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                indexingCounter.incrementAndGet();
+                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, 
document, indexDefinition);
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+        final AtomicInteger queryingCounter = new AtomicInteger(0);
+        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                queryingCounter.set(1);
+                return null;
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+
+        //add content
+        Tree node1 = createNodeWithType(root.getTree("/"), "node1", 
TestUtil.NT_TEST);
+        node1.setProperty("foo1", "bar1");
+        node1.addChild("subChild").setProperty("foo2", "bar2");
+        root.commit();
+
+        //indexing assertions
+        assertEquals("Indexing augment should get called once", 1, 
indexingCounter.get());
+        assertEquals("No docs should get indexed (augmentor hasn't added any 
field)",
+                0, getSearcher().getIndexReader().numDocs());
+
+        String query = "EXPLAIN SELECT [jcr:path] from [" + TestUtil.NT_TEST + 
"] WHERE [foo1]='bar1'";
+        List<String> paths = executeQuery(query, SQL2);
+        assertTrue("indexed prop name shouldn't decide query plan (" + 
paths.get(0) + ")",
+                paths.get(0).contains("/* no-index "));
+
+        query = "EXPLAIN SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] 
WHERE [subChild/foo2]='bar2'";
+        paths = executeQuery(query, SQL2);
+        assertTrue("indexed prop name shouldn't decide query plan (" + 
paths.get(0) + ")",
+                paths.get(0).contains("/* no-index "));
+    }
+
+    //OAK-3576
+    @Test
+    public void propertyIndexUsingAugmentors() throws Exception {
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        TestUtil.enablePropertyIndex(props, "foo1", false);
+        TestUtil.enablePropertyIndex(props, "subChild/foo2", false);
+        root.commit();
+
+        //setup augmentors
+        final AtomicInteger indexingCounter = new AtomicInteger(0);
+        factory.indexFieldProvider = new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                indexingCounter.incrementAndGet();
+                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, 
document, indexDefinition);
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+        final AtomicInteger queryingCounter = new AtomicInteger(0);
+        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                queryingCounter.set(1);
+                return null;
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+
+        //add content
+        Tree node1 = createNodeWithType(root.getTree("/"), "node1", 
TestUtil.NT_TEST);
+        node1.setProperty("foo1", "bar1");
+        node1.addChild("subChild").setProperty("foo2", "bar2");
+        root.commit();
+
+        //indexing assertions
+        assertEquals("Indexing augment should get called once", 1, 
indexingCounter.get());
+
+        String query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] 
WHERE [foo1]='bar1'";
+        executeQuery(query, SQL2);
+        assertEquals("Query augmentor should not get called for property 
constraints", 0, queryingCounter.get());
+        query = "EXPLAIN " + query;
+        List<String> paths = executeQuery(query, SQL2, false);
+        assertTrue("property index should have made the index selected (" + 
paths.get(0) + ")",
+                paths.get(0).contains("/* lucene:test-index("));
+
+        query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE 
[subChild/foo2]='bar2'";
+        executeQuery(query, SQL2);
+        assertEquals("Query augmentor should not get called for property 
constraints", 0, queryingCounter.get());
+        query = "EXPLAIN " + query;
+        paths = executeQuery(query, SQL2);
+        assertTrue("property index should have made the index selected (" + 
paths.get(0) + ")",
+                paths.get(0).contains("/* lucene:test-index("));
+    }
+
+    //OAK-3576
+    @Test
+    public void fulltextIndexUsingAugmentors() throws Exception {
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        TestUtil.enableForFullText(props, "foo1");
+        TestUtil.enableForFullText(props, "subChild/foo2");
+        root.commit();
+
+        //setup augmentors
+        final AtomicInteger indexingCounter = new AtomicInteger(0);
+        factory.indexFieldProvider = new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                indexingCounter.incrementAndGet();
+                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, 
document, indexDefinition);
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+        final AtomicInteger queryingCounter = new AtomicInteger(0);
+        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                queryingCounter.set(1);
+                return null;
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        };
+
+        //add content
+        Tree node1 = createNodeWithType(root.getTree("/"), "node1", 
TestUtil.NT_TEST);
+        node1.setProperty("foo1", "bar1");
+        node1.addChild("subChild").setProperty("foo2", "bar2");
+        root.commit();
+
+        //indexing assertions
+        assertEquals("Indexing augment should get called once", 1, 
indexingCounter.get());
+
+        String query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] 
WHERE CONTAINS(*, 'bar1')";
+        executeQuery(query, SQL2);
+        assertEquals("Query augmentor should get called for full text 
constraints", 1, queryingCounter.get());
+        queryingCounter.set(0);
+        query = "EXPLAIN " + query;
+        List<String> paths = executeQuery(query, SQL2, false);
+        assertEquals("Query augmentor should get called for full text 
constraints", 1, queryingCounter.get());
+        assertTrue("property index should have made the index selected (" + 
paths.get(0) + ")",
+                paths.get(0).contains("/* lucene:test-index("));
+
+        queryingCounter.set(0);
+        query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE 
CONTAINS(*, 'bar2')";
+        executeQuery(query, SQL2);
+        assertEquals("Query augmentor should get called for full text 
constraints", 1, queryingCounter.get());
+        queryingCounter.set(0);
+        query = "EXPLAIN " + query;
+        paths = executeQuery(query, SQL2, false);
+        assertEquals("Query augmentor should get called for full text 
constraints", 1, queryingCounter.get());
+        assertTrue("property index should have made the index selected (" + 
paths.get(0) + ")",
+                paths.get(0).contains("/* lucene:test-index("));
+    }
+
+    @Test
+    public void indexAugmentorMismatchedNodeType() throws Exception {
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        TestUtil.enableForFullText(props, "foo1");
+        root.commit();
+
+        //setup augmentors
+        final AtomicInteger indexingCounter1 = new AtomicInteger(0);
+        final AtomicInteger indexingCounter2 = new AtomicInteger(0);
+        factory.registerIndexFieldProvider(new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                indexingCounter1.incrementAndGet();
+                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, 
document, indexDefinition);
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(JcrConstants.NT_BASE);
+            }
+        });
+        factory.registerIndexFieldProvider(new IndexFieldProvider() {
+            @Nonnull
+            @Override
+            public Iterable<Field> getAugmentedFields(String path, NodeState 
document, NodeState indexDefinition) {
+                indexingCounter2.incrementAndGet();
+                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, 
document, indexDefinition);
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        });
+        factory.useSuperBehavior = true;
+
+        //add content
+        createNodeWithType(root.getTree("/"), "node1", 
TestUtil.NT_TEST).setProperty("foo1", "bar1");
+        root.commit();
+
+        assertEquals("Mismatching node type should not let index augmentor 
called", 0, indexingCounter1.get());
+        assertEquals("Matching node type should get augmentor called", 1, 
indexingCounter2.get());
+    }
+
+    @Test
+    public void queryAugmentorMismatchedNodeType() throws Exception {
+        //setup repo and index
+        NodeTypeRegistry.register(root, 
IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
+        Tree props = createIndex(TestUtil.NT_TEST);
+        TestUtil.enableForFullText(props, "foo1", false);
+        root.commit();
+
+        //setup augmentors
+        final AtomicInteger indexingCounter1 = new AtomicInteger(0);
+        final AtomicInteger indexingCounter2 = new AtomicInteger(0);
+        factory.registerQueryTermsProvider(new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                indexingCounter1.set(1);
+                return null;
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(JcrConstants.NT_BASE);
+            }
+        });
+        factory.registerQueryTermsProvider(new FulltextQueryTermsProvider() {
+            @Override
+            public Query getQueryTerm(String text, Analyzer analyzer) {
+                indexingCounter2.set(1);
+                return null;
+            }
+
+            @Nonnull
+            @Override
+            public Set<String> getSupportedTypes() {
+                return Collections.singleton(TestUtil.NT_TEST);
+            }
+        });
+        factory.useSuperBehavior = true;
+
+
+        executeQuery("SELECT [jcr:path] FROM [" + TestUtil.NT_TEST + "] WHERE 
CONTAINS(*, 'test')", SQL2, false);
+
+        assertEquals("Mismatching node type should not let index augmentor 
called", 0, indexingCounter1.get());
+        assertEquals("Matching node type should get augmentor called", 1, 
indexingCounter2.get());
+    }
+
+    private static Tree createNodeWithType(Tree t, String nodeName, String 
typeName){
+        t = t.addChild(nodeName);
+        t.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME);
+        return t;
+    }
+
+    private Tree createIndex(String nodeType) throws Exception {
+        Tree rootTree = root.getTree("/");
+        return createIndex(rootTree, nodeType);
+    }
+
+    private Tree createIndex(Tree root, String nodeType) throws Exception {
+        Tree index = createTestIndexNode(root, 
LuceneIndexConstants.TYPE_LUCENE);
+        return TestUtil.newRulePropTree(index, nodeType);
+    }
+
+    private static class SimpleIndexAugmentorFactory extends 
IndexAugmentorFactory {
+        IndexFieldProvider indexFieldProvider = null;
+        FulltextQueryTermsProvider fulltextQueryTermsProvider = null;
+        private boolean useSuperBehavior = false;
+        private final Whiteboard whiteboard;
+
+        SimpleIndexAugmentorFactory() {
+            this(new DefaultWhiteboard());
+        }
+
+        SimpleIndexAugmentorFactory(Whiteboard whiteboard) {
+            super(whiteboard);
+            this.whiteboard = whiteboard;
+        }
+
+        void registerIndexFieldProvider(IndexFieldProvider provider) {
+            whiteboard.register(IndexFieldProvider.class, provider, null);
+            refreshServices();
+        }
+
+        void registerQueryTermsProvider(FulltextQueryTermsProvider provider) {
+            whiteboard.register(FulltextQueryTermsProvider.class, provider, 
null);
+            refreshServices();
+        }
+
+        @Nonnull
+        @Override
+        public IndexFieldProvider getIndexFieldProvider(String nodeType) {
+            return useSuperBehavior?
+                    super.getIndexFieldProvider(nodeType):
+                    (indexFieldProvider != null)?
+                            indexFieldProvider:
+                            IndexFieldProvider.DEFAULT;
+        }
+
+        @Nonnull
+        @Override
+        public FulltextQueryTermsProvider getFulltextQueryTermsProvider(String 
nodeType) {
+            return useSuperBehavior?
+                    super.getFulltextQueryTermsProvider(nodeType):
+                    (fulltextQueryTermsProvider != null)?
+                        fulltextQueryTermsProvider:
+                        FulltextQueryTermsProvider.DEFAULT;
+        }
+    }
+
+    private IndexSearcher getSearcher(){
+        if(indexNode == null){
+            indexNode = tracker.acquireIndexNode("/oak:index/" + 
TEST_INDEX_NAME);
+        }
+        return indexNode.getSearcher();
+    }
+
+    private void checkSimpleBehavior(Tree rootTree, int testIndex) throws 
Exception {
+        createNodeWithType(rootTree, "node" + testIndex, TestUtil.NT_TEST)
+                .setProperty("foo", "bar" + testIndex);
+        root.commit();
+
+        String query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] 
WHERE contains(*, 'bar" + testIndex + "')";
+        List<String> paths = executeQuery(query, SQL2);
+        assertEquals(1, paths.size());
+        assertEquals("/test/node" + testIndex, paths.get(0));
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAugmentTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to