/* * ModeShape (http://www.modeshape.org) * * Licensed 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.modeshape.jcr.index.lucene; import java.io.File; import java.nio.file.Paths; import java.util.Collection; import javax.jcr.RepositoryException; import javax.jcr.query.qom.ChildNodeJoinCondition; import javax.jcr.query.qom.DescendantNodeJoinCondition; import javax.jcr.query.qom.DynamicOperand; import javax.jcr.query.qom.JoinCondition; import org.modeshape.common.collection.Problems; import org.modeshape.jcr.ExecutionContext; import org.modeshape.jcr.NodeTypes; import org.modeshape.jcr.api.index.IndexDefinition; import org.modeshape.jcr.api.query.qom.ChildCount; import org.modeshape.jcr.cache.change.ChangeSetAdapter; import org.modeshape.jcr.query.QueryContext; import org.modeshape.jcr.query.model.FullTextSearch; import org.modeshape.jcr.query.model.Or; import org.modeshape.jcr.spi.index.IndexCostCalculator; import org.modeshape.jcr.spi.index.provider.IndexProvider; import org.modeshape.jcr.spi.index.provider.IndexUsage; import org.modeshape.jcr.spi.index.provider.ManagedIndexBuilder; /** * {@link org.modeshape.jcr.spi.index.provider.IndexProvider} implementation which uses the Apache Lucene library. * * @author Horia Chiorean (hchiorea@redhat.com) * @since 4.5 */ public class LuceneIndexProvider extends IndexProvider { /** * The cost estimate for this index; this is the same as the local index atm */ private static final int COST_ESTIMATE = IndexCostCalculator.Costs.LOCAL; /** * The directory in which the indexes are to be stored. This can to be set, or the {@link #path} and {@link #relativeTo} * need to be set. If neither of those properties are present, the provider will store the indexes in RAM. */ private String directory; /** * The path in which the indexes are to be stored, relative to {@link #relativeTo}. These can be set, or the * {@link #directory} can to be set. */ private String path; /** * The directory relative to which the {@link #path} specifies where the indexes are to be stored. These can be * set, or {@link #directory} can be set. */ private String relativeTo; /** * A number of properties which allow advanced lucene configuration. Each of them is optional and will default to Lucene * defaults */ private String lockFactoryClass; private String directoryClass; private String analyzerClass; private String codec; private LuceneConfig luceneConfig; @Override protected void doInitialize() throws RepositoryException { String baseDir = baseDir(); this.luceneConfig = new LuceneConfig(baseDir, lockFactoryClass, directoryClass, analyzerClass, codec, environment()); } private String baseDir() throws RepositoryException { if (directory == null && relativeTo != null && path != null) { // Try to set the directory using relativeTo and path ... try { File rel = new File(relativeTo); File dir = Paths.get(rel.toURI()).resolve(path).toFile(); directory = dir.getAbsolutePath(); } catch (RuntimeException e) { throw new RepositoryException(e); } } if (directory == null) { logger().debug("The lucene index provider '{0}' for repository '{1}' will be held in memory", getName(), getRepositoryName()); return null; } else { logger().debug("Initializing the lucene index provider '{0}' in repository '{1}' at: {2}", getName(), getRepositoryName(), directory); return directory; } } @Override public void validateProposedIndex( ExecutionContext context, IndexDefinition defn, NodeTypes.Supplier nodeTypesSupplier, Problems problems ) { LuceneManagedIndexBuilder.validate(defn, problems); } @Override protected int getCostEstimate() { return COST_ESTIMATE; } @Override public Long getLatestIndexUpdateTime() { return luceneConfig.lastSuccessfulCommitTime(); } @Override protected ManagedIndexBuilder getIndexBuilder( IndexDefinition defn, String workspaceName, NodeTypes.Supplier nodeTypesSupplier, ChangeSetAdapter.NodeTypePredicate matcher ) { return new LuceneManagedIndexBuilder(context(), defn, workspaceName, nodeTypesSupplier, matcher, luceneConfig); } @Override protected IndexUsage evaluateUsage( QueryContext context, final IndexCostCalculator calculator, final IndexDefinition defn ) { return new IndexUsage(context, calculator, defn) { @Override protected boolean applies( ChildCount operand ) { // nothing to do about this... return false; } @Override protected boolean applies( DynamicOperand operand ) { if (IndexDefinition.IndexKind.TEXT == defn.getKind() && !(operand instanceof FullTextSearch)) { // text indexes only support FTS operands... return false; } return super.applies(operand); } @Override protected boolean indexAppliesTo( Or or ) { boolean appliesToConstraints = super.indexAppliesTo(or); if (!appliesToConstraints) { return false; } Collection<JoinCondition> joinConditions = calculator.joinConditions(); if (joinConditions.isEmpty()) { return true; } for (JoinCondition joinCondition : joinConditions) { if (joinCondition instanceof ChildNodeJoinCondition || joinCondition instanceof DescendantNodeJoinCondition) { // the index can't handle OUTER JOINS with OR criteria (see https://issues.jboss.org/browse/MODE-2054) // so reject it, making the query engine fallback to the default behavior which works return false; } } return true; } }; } }