Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneDocumentMaker.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneDocumentMaker.java (revision 1876609) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneDocumentMaker.java (working copy) @@ -20,6 +20,8 @@ package org.apache.jackrabbit.oak.plugins.index.lucene; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.jackrabbit.oak.api.Blob; @@ -40,7 +42,6 @@ import org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetField; import org.apache.lucene.index.IndexableField; import org.apache.lucene.util.BytesRef; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,6 +55,10 @@ public class LuceneDocumentMaker extends FulltextDocumentMaker { private static final Logger log = LoggerFactory.getLogger(LuceneDocumentMaker.class); + private static final String DYNAMIC_BOOST_TAG_NAME = "name"; + private static final String DYNAMIC_BOOST_TAG_CONFIDENCE = "confidence"; + private static final String DYNAMIC_BOOST_SPLIT_REGEX = "[:/]"; + private final FacetsConfigProvider facetsConfigProvider; private final IndexAugmentorFactory augmentorFactory; @@ -331,4 +336,69 @@ } } } + + @Override + protected boolean indexDynamicBoost(Document doc, PropertyDefinition pd, NodeState nodeState, String propertyName) { + NodeState propertNode = nodeState; + String parentName = PathUtils.getParentPath(propertyName); + for (String c : PathUtils.elements(parentName)) { + propertNode = propertNode.getChildNode(c); + } + boolean added = false; + for (String nodeName : propertNode.getChildNodeNames()) { + NodeState dynaTag = propertNode.getChildNode(nodeName); + String dynaTagName = dynaTag.getProperty(DYNAMIC_BOOST_TAG_NAME).getValue(Type.STRING); + Double dynaTagConfidence = dynaTag.getProperty(DYNAMIC_BOOST_TAG_CONFIDENCE).getValue(Type.DOUBLE); + + List tokens = new ArrayList<>(splitForIndexing(dynaTagName)); + if (tokens.size() > 1) { + // Actual name not in tokens + tokens.add(dynaTagName); + } + boolean addedForThisChild = false; + for (String token : tokens) { + if (token.length() > 0) { + AugmentedField f = new AugmentedField(parentName + "/" + token.toLowerCase(), dynaTagConfidence); + if (doc.getField(f.name()) == null) { + addedForThisChild = true; + added = true; + doc.add(f); + } + } + } + if (addedForThisChild) { + log.trace( + "Added augmented fields: {}[{}], {}", + parentName + "/", String.join(", ", tokens), dynaTagConfidence + ); + } + } + return added; + } + + private static class AugmentedField extends Field { + private static final FieldType ft = new FieldType(); + static { + ft.setIndexed(true); + ft.setStored(false); + ft.setTokenized(false); + ft.setOmitNorms(false); + ft.setIndexOptions(org.apache.lucene.index.FieldInfo.IndexOptions.DOCS_ONLY); + ft.freeze(); + } + + AugmentedField(String name, double weight) { + super(name, "1", ft); + setBoost((float) weight); + } + } + + private static List splitForIndexing(String tagName) { + return Arrays.asList(removeBackSlashes(tagName).split(DYNAMIC_BOOST_SPLIT_REGEX)); + } + + private static String removeBackSlashes(String text) { + return text.replaceAll("\\\\", ""); + } + } \ No newline at end of file Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/DynamicBoostTest.java =================================================================== --- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/DynamicBoostTest.java (nonexistent) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/DynamicBoostTest.java (working copy) @@ -0,0 +1,236 @@ +/* + * 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.dynamicBoost; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.Oak; +import org.apache.jackrabbit.oak.api.ContentRepository; +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.index.lucene.IndexAugmentorFactory; +import org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneDocumentMaker; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil; +import org.apache.jackrabbit.oak.plugins.index.lucene.dynamicBoost.IndexFieldProviderImpl; +import org.apache.jackrabbit.oak.plugins.index.lucene.spi.IndexFieldProvider; +import org.apache.jackrabbit.oak.plugins.index.search.ExtractedTextCache; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants; +import org.apache.jackrabbit.oak.plugins.index.search.IndexFormatVersion; +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.mount.Mounts; +import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; +import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.read.ListAppender; +import ch.qos.logback.core.spi.FilterReply; + +/** + * Tests the index augmentation feature. + */ +public class DynamicBoostTest extends AbstractQueryTest { + + public static final String ASSET_NODE_TYPE = + "[dam:Asset]\n" + + " - * (UNDEFINED) multiple\n" + + " - * (UNDEFINED)\n" + + " + * (nt:base) = oak:TestNode VERSION"; + + private static final String UNSTRUCTURED = "nt:unstructured"; + + private final SimpleIndexAugmentorFactory factory = new SimpleIndexAugmentorFactory(); + + @Override + protected void createTestIndexNode() throws Exception { + setTraversalEnabled(false); + } + + @Override + protected ContentRepository createRepository() { + IndexTracker tracker = new IndexTracker(); + LuceneIndexEditorProvider editorProvider = new LuceneIndexEditorProvider(null, + new ExtractedTextCache(0, 0), + factory, Mounts.defaultMountInfoProvider()); + LuceneIndexProvider provider = new LuceneIndexProvider(tracker, + factory); + return new Oak() + .with(new OpenSecurityProvider()) + .with((QueryIndexProvider) provider) + .with((Observer) provider) + .with(editorProvider) + .createContentRepository(); + } + + @Test public void withFieldProvider() throws Exception { + NodeTypeRegistry.register(root, toInputStream(ASSET_NODE_TYPE), "test nodeType"); + createIndex("dam:Asset"); + root.commit(); + + factory.indexFieldProvider = new IndexFieldProviderImpl(); + + Tree t = createTestNodes(root); + + AsyncLogFilter filter = new AsyncLogFilter(IndexFieldProviderImpl.class); + ListAppender appender = filter.subscribe(); + root.commit(); + // this is not detected + updateTestNode(t, 20); + root.commit(); + // now we change an indexed property: this is not detected with the IndexFieldProvider + t.getParent().setProperty("updateCount", 2); + updateTestNode(t, 30); + root.commit(); + filter.unsubscribe(appender); + assertEquals("[[TRACE] Added augmented fields: jcr:content/metadata/predictedTags/[my, a, my:a], 10.0]", appender.list.toString()); + } + + @Test public void withDynamicBoost() throws Exception { + NodeTypeRegistry.register(root, toInputStream(ASSET_NODE_TYPE), "test nodeType"); + Tree props = createIndex("dam:Asset"); + Tree pt = createNodeWithType(props, "predictedTags", UNSTRUCTURED); + pt.setProperty("name", "jcr:content/metadata/predictedTags/.*"); + pt.setProperty("isRegexp", true); + pt.setProperty("dynamicBoost", true); + pt.setProperty("propertyIndex", true); + root.commit(); + + Tree t = createTestNodes(root); + + AsyncLogFilter filter = new AsyncLogFilter(LuceneDocumentMaker.class); + ListAppender appender = filter.subscribe(); + root.commit(); + // this is not detected + updateTestNode(t, 20); + root.commit(); + // now we change an indexed property: this is detected + t.getParent().setProperty("updateCount", 2); + updateTestNode(t, 30); + root.commit(); + filter.unsubscribe(appender); + assertEquals( + "[" + + "[TRACE] Added augmented fields: jcr:content/metadata/predictedTags/[my, a, my:a], 10.0, " + + "[TRACE] Added augmented fields: jcr:content/metadata/predictedTags/[my, a, my:a], 30.0" + + "]", appender.list.toString()); + } + + private static Tree createTestNodes(Root root) { + Tree test = createNodeWithType(root.getTree("/"), "test", UNSTRUCTURED); + Tree node = createNodeWithType(test, "item", "dam:Asset"); + Tree predicted = + createNodeWithType( + createNodeWithType( + createNodeWithType(node, JcrConstants.JCR_CONTENT, UNSTRUCTURED), + "metadata", UNSTRUCTURED), + "predictedTags", UNSTRUCTURED); + Tree a = createNodeWithType(predicted, "a", UNSTRUCTURED); + a.setProperty("name", "my:a"); + a.setProperty("confidence", 10.0); + return a; + } + + private static void updateTestNode(Tree a, double value) { + a.setProperty("confidence", value); + } + + 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 static Tree createIndex(Tree root, String nodeType) throws Exception { + Tree index = createTestIndexNode(root, LuceneIndexConstants.TYPE_LUCENE); + index.setProperty(FulltextIndexConstants.COMPAT_MODE, IndexFormatVersion.V2.getVersion()); + return TestUtil.newRulePropTree(index, nodeType); + } + + private static class SimpleIndexAugmentorFactory extends IndexAugmentorFactory { + IndexFieldProvider indexFieldProvider = IndexFieldProvider.DEFAULT; + + @Override + public IndexFieldProvider getIndexFieldProvider(String nodeType) { + return indexFieldProvider; + } + + } + + private static InputStream toInputStream(String x) { + return new ByteArrayInputStream(x.getBytes()); + } + + private static class AsyncLogFilter extends Filter { + + private final Class loggerClass; + + AsyncLogFilter(Class loggerClass) { + this.loggerClass = loggerClass; + } + + private ListAppender subscribe() { + start(); + ListAppender appender = new ListAppender(); + appender.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); + appender.setName("asynclogcollector"); + appender.addFilter(this); + appender.start(); + ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger( + ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME).addAppender(appender); + ((LoggerContext) LoggerFactory.getILoggerFactory()). + getLogger(loggerClass).setLevel(Level.TRACE); + return appender; + } + + private void unsubscribe( ListAppender appender) { + ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger( + ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME).detachAppender(appender); + } + + @Override + public FilterReply decide(ILoggingEvent event) { + if (!event.getLoggerName().startsWith(loggerClass.getName())) { + return FilterReply.DENY; + } + return FilterReply.ACCEPT; + } + } + +} Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/FulltextQueryTermsProviderImpl.java =================================================================== --- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/FulltextQueryTermsProviderImpl.java (nonexistent) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/FulltextQueryTermsProviderImpl.java (working copy) @@ -0,0 +1,150 @@ +/* + * 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.dynamicBoost; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.jackrabbit.oak.plugins.index.lucene.spi.FulltextQueryTermsProvider; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.TermQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An example fulltext query terms provider. + */ +public class FulltextQueryTermsProviderImpl implements FulltextQueryTermsProvider { + + private static final Logger LOG = LoggerFactory.getLogger(FulltextQueryTermsProviderImpl.class); + private static final String SEARCH_SPLIT_REGEX = "[ ]"; + private static final String NT_DAM_ASSET = "dam:Asset"; + private static final String PREDICTED_TAGS_REL_PATH = ""; + private static final int MAX_FRAGMENT_SIZE = 2; + private static final int MAX_QUERY_SIZE = 10; + + @Override + public Set getSupportedTypes() { + Set supportedTypes = new HashSet(); + supportedTypes.add(NT_DAM_ASSET); + return supportedTypes; + } + + @Override + public org.apache.lucene.search.Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) { + if (analyzer == null || text == null) { + return null; + } + + LOG.debug("getQueryTerm Text: {}", text); + BooleanQuery query = this.createQuery(); + + Set charTerms = new HashSet(splitForSearch(text)); + LOG.debug("getQueryTerm charTerms: {}", charTerms); + if(charTerms.size() > MAX_QUERY_SIZE) { + LOG.debug("Not adding query terms for smart tags as number of terms in the query {} exceeds " + + "maximum permissible value of {}", charTerms.size(), MAX_QUERY_SIZE); + return null; + } + List fragments = prepareFragments(charTerms); + + for(String fragment : fragments) { + Term term = new Term(PREDICTED_TAGS_REL_PATH + fragment.toLowerCase(), "1"); + query.add(new TermQuery(term), BooleanClause.Occur.SHOULD); + LOG.debug("Added query term: {}", fragment.toLowerCase()); + } + + + Term term = new Term(PREDICTED_TAGS_REL_PATH + text.toLowerCase(), "1"); + query.add(new TermQuery(term), BooleanClause.Occur.SHOULD); + LOG.debug("Added query term: {}", text.toLowerCase()); + + //De-boosting smart tags based query. CQ-4194419 + query.setBoost(0.0001f); + return query; + + } + + private List prepareFragments(Set charTerms) { + + List fragments = new ArrayList(); + Set> powerSet = powerSet(charTerms); + + for(Set set : powerSet) { + StringBuilder sb = null; + for(String s : set) { + if(sb == null) { + sb = new StringBuilder(); + } + sb.append(s); + if(sb.length() > 0) { + sb.append(' '); + } + } + if(sb != null) { + fragments.add(sb.toString().trim()); + } + } + + return fragments; + } + + private Set> powerSet(Set originalSet) { + Set> powerSet = new HashSet>(); + if (originalSet.isEmpty()) { + powerSet.add(new HashSet()); + return powerSet; + } + List list = new ArrayList(originalSet); + T head = list.get(0); + Set rest = new HashSet(list.subList(1, list.size())); + for (Set subsetExcludingHead : powerSet(rest)) { + Set subsetIncludingHead = new HashSet(); + subsetIncludingHead.add(head); + subsetIncludingHead.addAll(subsetExcludingHead); + if(subsetIncludingHead.size() <= MAX_FRAGMENT_SIZE) { + powerSet.add(subsetIncludingHead); + } + if(subsetExcludingHead.size() <= MAX_FRAGMENT_SIZE) { + powerSet.add(subsetExcludingHead); + } + } + return powerSet; + } + + private List splitForSearch(String tagName) { + return Arrays.asList(removeBackSlashes(tagName).split(SEARCH_SPLIT_REGEX)); + } + + private String removeBackSlashes(String text) { + return text.replaceAll("\\\\", ""); + } + + protected BooleanQuery createQuery() { + return new BooleanQuery(); + } + +} Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/IndexFieldProviderImpl.java =================================================================== --- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/IndexFieldProviderImpl.java (nonexistent) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/dynamicBoost/IndexFieldProviderImpl.java (working copy) @@ -0,0 +1,112 @@ +/* + * 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.dynamicBoost; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.index.lucene.spi.IndexFieldProvider; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FieldType; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import static com.google.common.collect.Sets.newHashSet; + +/** + * An example index field provider. + */ +public class IndexFieldProviderImpl implements IndexFieldProvider { + + private static final Logger LOG = LoggerFactory.getLogger(IndexFieldProviderImpl.class); + + private static final String METADATA_FOLDER = "metadata"; + private static final String PREDICTED_TAGS = "predictedTags"; + private static final String PREDICTED_TAG_NAME = "name"; + private static final String PREDICTED_TAG_CONFIDENCE = "confidence"; + private static final String PREDICTED_TAGS_REL_PATH = JcrConstants.JCR_CONTENT + "/" + METADATA_FOLDER + "/" + + PREDICTED_TAGS + "/"; + private static final String INDEXING_SPLIT_REGEX = "[:/]"; + private static final String NT_DAM_ASSET = "dam:Asset"; + + @Override + public Set getSupportedTypes() { + Set supportedTypes = new HashSet(); + supportedTypes.add(NT_DAM_ASSET); + return supportedTypes; + } + + @Override + public @NotNull Iterable getAugmentedFields(String path, NodeState nodeState, NodeState indexDefinition) { + Set fields = newHashSet(); + NodeState dynaTags = nodeState.getChildNode(JcrConstants.JCR_CONTENT).getChildNode(METADATA_FOLDER).getChildNode(PREDICTED_TAGS); + for (String nodeName : dynaTags.getChildNodeNames()) { + NodeState dynaTag = dynaTags.getChildNode(nodeName); + String dynaTagName = dynaTag.getProperty(PREDICTED_TAG_NAME).getValue(Type.STRING); + Double dynaTagConfidence = dynaTag.getProperty(PREDICTED_TAG_CONFIDENCE).getValue(Type.DOUBLE); + + List tokens = new ArrayList<>(splitForIndexing(dynaTagName)); + if (tokens.size() > 1) { // Actual name not in tokens + tokens.add(dynaTagName); + } + for (String token : tokens) { + if (token.length() > 0) { + fields.add(new AugmentedField(PREDICTED_TAGS_REL_PATH + token.toLowerCase(), dynaTagConfidence)); + } + } + LOG.trace( + "Added augmented fields: {}[{}], {}", + PREDICTED_TAGS_REL_PATH, String.join(", ", tokens), dynaTagConfidence + ); + } + return fields; + } + + private static class AugmentedField extends Field { + private static final FieldType ft = new FieldType(); + static { + ft.setIndexed(true); + ft.setStored(false); + ft.setTokenized(false); + ft.setOmitNorms(false); + ft.setIndexOptions(org.apache.lucene.index.FieldInfo.IndexOptions.DOCS_ONLY); + ft.freeze(); + } + + AugmentedField(String name, double weight) { + super(name, "1", ft); + setBoost((float) weight); + } + } + + private static List splitForIndexing(String tagName) { + return Arrays.asList(removeBackSlashes(tagName).split(INDEXING_SPLIT_REGEX)); + } + + private static String removeBackSlashes(String text) { + return text.replaceAll("\\\\", ""); + } + +} \ No newline at end of file Index: oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java =================================================================== --- oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java (revision 1876609) +++ oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java (working copy) @@ -138,6 +138,8 @@ String PROP_WEIGHT = "weight"; + String PROP_DYNAMIC_BOOST = "dynamicBoost"; + /** * Boolean property in property definition to mark sync properties */ Index: oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyDefinition.java =================================================================== --- oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyDefinition.java (revision 1876609) +++ oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyDefinition.java (working copy) @@ -102,6 +102,8 @@ public final int weight; + public final boolean dynamicBoost; + /** * Property name excluding the relativePath. For regular expression based definition * its set to null @@ -136,6 +138,7 @@ this.relative = isRelativeProperty(name); this.boost = getOptionalValue(defn, FIELD_BOOST, DEFAULT_BOOST); this.weight = getOptionalValue(defn, PROP_WEIGHT, DEFAULT_PROPERTY_WEIGHT); + this.dynamicBoost = getOptionalValue(defn, FulltextIndexConstants.PROP_DYNAMIC_BOOST, false); //By default if a property is defined it is indexed this.index = getOptionalValue(defn, FulltextIndexConstants.PROP_INDEX, true); Index: oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextDocumentMaker.java =================================================================== --- oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextDocumentMaker.java (revision 1876609) +++ oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextDocumentMaker.java (working copy) @@ -101,6 +101,8 @@ protected abstract void indexTypedProperty(D doc, PropertyState property, String pname, PropertyDefinition pd, int index); + protected abstract boolean indexDynamicBoost(D doc, PropertyDefinition pd, NodeState nodeState, String propertyName); + protected abstract void indexAncestors(D doc, String path); protected abstract void indexNotNullProperty(D doc, PropertyDefinition pd); @@ -244,6 +246,9 @@ if (pd.propertyIndex && pd.includePropertyType(property.getType().tag())) { dirty |= addTypedFields(doc, property, pname, pd); } + if (pd.dynamicBoost) { + dirty |= indexDynamicBoost(doc, pd, state, pname); + } if (pd.fulltextEnabled() && includeTypeForFullText) { for (String value : property.getValue(Type.STRINGS)) {