All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.index.local.LocalIndexProvider Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * 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.local;

import java.io.File;
import java.nio.file.Paths;
import java.util.Collections;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.query.qom.Constraint;
import javax.jcr.query.qom.JoinCondition;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.modeshape.common.collection.Problems;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.JcrI18n;
import org.modeshape.jcr.NodeTypes;
import org.modeshape.jcr.NodeTypes.Supplier;
import org.modeshape.jcr.api.index.IndexColumnDefinition;
import org.modeshape.jcr.api.index.IndexDefinition;
import org.modeshape.jcr.api.query.qom.QueryObjectModelConstants;
import org.modeshape.jcr.api.query.qom.Relike;
import org.modeshape.jcr.cache.change.ChangeSetAdapter.NodeTypePredicate;
import org.modeshape.jcr.query.QueryContext;
import org.modeshape.jcr.query.model.Comparison;
import org.modeshape.jcr.query.model.FullTextSearch;
import org.modeshape.jcr.spi.index.IndexCostCalculator;
import org.modeshape.jcr.spi.index.IndexCostCalculator.Costs;
import org.modeshape.jcr.spi.index.IndexFeedback;
import org.modeshape.jcr.spi.index.provider.IndexProvider;
import org.modeshape.jcr.spi.index.provider.IndexUsage;
import org.modeshape.jcr.spi.index.provider.ManagedIndex;

/**
 * An {@link IndexProvider} implementation that maintains indexes on the local file system using MapDB.
 * 

* This provider is instantiated with: *

    *
  • an {@code directory} attribute, or
  • *
  • an {@code path} attribute and an {@code relativeTo} attribute
  • *
* * @author Randall Hauch ([email protected]) */ public class LocalIndexProvider extends IndexProvider { private static final Float MAX_SELECTIVITY = new Float(1.0f); private static final String DB_FILENAME = "local-indexes.db"; /** * The directory in which the indexes are to be stored. This needs to be set, or the {@link #path} and {@link #relativeTo} * need to be set. */ private String directory; /** * The path in which the indexes are to be stored, relative to {@link #relativeTo}. Both of these need to be set, or the * {@link #directory} needs to be set. */ private String path; /** * The directory relative to which the {@link #path} specifies where the indexes are to be stored. Both of these need to be * set, or the {@link #directory} needs to be set. */ private String relativeTo; private DB db; public LocalIndexProvider() { } /** * Get the absolute or relative path to the directory where this provider should store the indexes. * * @return the path to the directory */ public String getDirectory() { return directory; } @Override protected void doInitialize() 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) { throw new RepositoryException(JcrI18n.localIndexProviderMustHaveDirectory.text(getRepositoryName())); } logger().debug("Initializing the local index provider '{0}' in repository '{1}' at: {2}", getName(), getRepositoryName(), directory); // Find the directory and make sure it exists and we have read and write permission ... File dir = new File(directory); if (!dir.exists()) { // Try to make it ... logger().debug("Attempting to create directory for local indexes in repository '{1}' at: {0}", dir.getAbsolutePath(), getRepositoryName()); if (dir.mkdirs()) { logger().debug("Created directory for local indexes in repository '{1}' at: {0}", dir.getAbsolutePath(), getRepositoryName()); } else { logger().debug("Unable to create directory for local indexes in repository '{1}' at: {0}", dir.getAbsolutePath(), getRepositoryName()); } } if (!dir.canRead()) { throw new RepositoryException(JcrI18n.localIndexProviderDirectoryMustBeReadable.text(dir, getRepositoryName())); } if (!dir.canWrite()) { throw new RepositoryException(JcrI18n.localIndexProviderDirectoryMustBeWritable.text(dir, getRepositoryName())); } // Find the file for the indexes ... File file = new File(dir, DB_FILENAME); if (logger().isDebugEnabled()) { String action = file.exists() ? "Opening" : "Creating"; logger().debug("{0} the local index provider database for repository '{1}' at: {2}", action, getRepositoryName(), file.getAbsolutePath()); } // Get the database ... this.db = DBMaker.newFileDB(file).make(); logger().trace("Found the index files {0} in index database for repository '{1}' at: {2}", db.getCatalog(), getRepositoryName(), file.getAbsolutePath()); } @Override protected void postShutdown() { logger().debug("Shutting down the local index provider '{0}' in repository '{1}'", getName(), getRepositoryName()); if (db != null) { try { db.commit(); db.close(); } finally { db = null; } } } @Override public void validateProposedIndex( ExecutionContext context, IndexDefinition defn, NodeTypes.Supplier nodeTypeSupplier, Problems problems ) { ManagedLocalIndexBuilder.create(context, defn, nodeTypeSupplier, null).validate(problems); } @Override protected ManagedIndex createIndex( final IndexDefinition defn, final String workspaceName, Supplier nodeTypesSupplier, NodeTypePredicate matcher, IndexFeedback feedback ) { ManagedLocalIndexBuilder builder = ManagedLocalIndexBuilder.create(context(), defn, nodeTypesSupplier, matcher); logger().debug("Index provider '{0}' is creating index in workspace '{1}': {2}", getName(), workspaceName, defn); final ManagedLocalIndex index = builder.build(workspaceName, db); // this is a new [index definition, workspace] pair so we should always scan feedback.scan(workspaceName, new IndexFeedback.IndexingCallback() { @SuppressWarnings( "synthetic-access" ) @Override public void beforeIndexing() { logger().debug( "Disabling index '{0}' from provider '{1}' in workspace '{2}' while it is reindexed. It will not be used in queries until reindexing has completed", defn.getName(), defn.getProviderName(), workspaceName); index.enable(false); } @SuppressWarnings( "synthetic-access" ) @Override public void afterIndexing() { index.enable(true); logger().debug("Enabled index '{0}' from provider '{1}' in workspace '{2}' after reindexing has completed", defn.getName(), defn.getProviderName(), workspaceName); } }); return index; } @Override protected ManagedIndex updateIndex( IndexDefinition oldDefn, final IndexDefinition updatedDefn, ManagedIndex existingIndex, final String workspaceName, Supplier nodeTypesSupplier, NodeTypePredicate matcher, IndexFeedback feedback ) { if (!isChanged(oldDefn, updatedDefn)) { // Nothing about the index definition that we care about really changed, so don't do anything ... logger().debug("Index provider '{0}' is not updating index in workspace '{1}' because there were no changes: {2}", getName(), workspaceName, updatedDefn); return existingIndex; } // This is very crude, but we'll just destroy the old index and rebuild the new one ... existingIndex.shutdown(true); ManagedLocalIndexBuilder builder = ManagedLocalIndexBuilder.create(context(), updatedDefn, nodeTypesSupplier, matcher); logger().debug("Index provider '{0}' is updating index in workspace '{1}': {2}", getName(), workspaceName, updatedDefn); final ManagedLocalIndex index = builder.build(workspaceName, db); if (index.isNew()) { feedback.scan(workspaceName, new IndexFeedback.IndexingCallback() { @SuppressWarnings( "synthetic-access" ) @Override public void beforeIndexing() { logger().debug("Disabling index '{0}' from provider '{1}' in workspace '{2}' while it is reindexed. It will not be used in queries until reindexing has completed", updatedDefn.getName(), updatedDefn.getProviderName(), workspaceName); index.enable(false); } @SuppressWarnings( "synthetic-access" ) @Override public void afterIndexing() { index.enable(true); logger().debug("Enabled index '{0}' from provider '{1}' in workspace '{2}' after reindexing has completed", updatedDefn.getName(), updatedDefn.getProviderName(), workspaceName); } }); } return index; } @Override protected void removeIndex( IndexDefinition oldDefn, ManagedIndex existingIndex, String workspaceName ) { logger().debug("Index provider '{0}' is removing index in workspace '{1}': {2}", getName(), workspaceName, oldDefn); existingIndex.shutdown(true); } private boolean isChanged( IndexDefinition defn1, IndexDefinition defn2 ) { if (defn1.getKind() != defn2.getKind()) return true; if (defn1.size() != defn2.size()) return true; for (int i = 0; i != defn1.size(); ++i) { IndexColumnDefinition col1 = defn1.getColumnDefinition(i); IndexColumnDefinition col2 = defn2.getColumnDefinition(i); if (isChanged(col1, col2)) return true; } // We don't care about any properties ... return false; } private boolean isChanged( IndexColumnDefinition defn1, IndexColumnDefinition defn2 ) { if (defn1.getColumnType() != defn2.getColumnType()) return true; if (!defn1.getPropertyName().equals(defn2.getPropertyName())) return true; return false; } @Override protected void planUseOfIndex( QueryContext context, IndexCostCalculator calculator, String workspaceName, ManagedIndex index, final IndexDefinition defn ) { ManagedLocalIndex localIndex = (ManagedLocalIndex)index; IndexUsage planner = new IndexUsage(context, calculator, defn) { @Override protected boolean applies( FullTextSearch search ) { // We don't support full text search criteria ... return false; } @Override protected boolean indexAppliesTo( Relike constraint ) { // Relike can only work if the column types are all strings ... for (IndexColumnDefinition columnDefn : defn) { if (columnDefn.getColumnType() != PropertyType.STRING) return false; } return super.indexAppliesTo(constraint); } @Override protected boolean indexAppliesTo( Comparison constraint ) { if (QueryObjectModelConstants.JCR_OPERATOR_LIKE.equals(constraint.getOperator())) { // Our indexes don't handle LIKE operations ... return false; } return super.indexAppliesTo(constraint); } }; // Does this index apply to any of the ANDed constraints? for (Constraint constraint : calculator.andedConstraints()) { if (planner.indexAppliesTo(constraint)) { logger().trace("Index '{0}' in '{1}' provider applies to query in workspace '{2}' with constraint: {3}", defn.getName(), getName(), workspaceName, constraint); // The index does apply to this constraint ... long cardinality = localIndex.estimateCardinality(constraint, context.getVariables()); long total = localIndex.estimateTotalCount(); Float selectivity = null; if (total >= 0L) { double ratio = (double)cardinality / (double)total; selectivity = cardinality <= total ? new Float(ratio) : MAX_SELECTIVITY; } calculator.addIndex(defn.getName(), workspaceName, getName(), Collections.singleton(constraint), Costs.LOCAL, cardinality, selectivity); } } // Does this index apply to any of the join conditions ... for (JoinCondition joinCondition : calculator.joinConditions()) { if (planner.indexAppliesTo(joinCondition)) { logger().trace("Index '{0}' in '{1}' provider applies to query in workspace '{2}' with constraint: {3}", defn.getName(), getName(), workspaceName, joinCondition); // The index does apply to this constraint, but the number of values corresponds to the total number of values // in the index (this is a JOIN CONDITON for which there is no literal values) ... long total = localIndex.estimateTotalCount(); calculator.addIndex(defn.getName(), workspaceName, getName(), Collections.singleton(joinCondition), Costs.LOCAL, total); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy