org.modeshape.jcr.RepositoryIndexManager Maven / Gradle / Ivy
/*
* 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;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.jcr.RepositoryException;
import org.infinispan.commons.util.ReflectionUtil;
import org.modeshape.common.SystemFailureException;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.collection.ArrayListMultimap;
import org.modeshape.common.collection.Collections;
import org.modeshape.common.collection.Multimap;
import org.modeshape.common.collection.Problems;
import org.modeshape.common.collection.SimpleProblems;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.RepositoryConfiguration.Component;
import org.modeshape.jcr.api.index.IndexColumnDefinition;
import org.modeshape.jcr.api.index.IndexColumnDefinitionTemplate;
import org.modeshape.jcr.api.index.IndexDefinition;
import org.modeshape.jcr.api.index.IndexDefinition.IndexKind;
import org.modeshape.jcr.api.index.IndexDefinitionTemplate;
import org.modeshape.jcr.api.index.IndexExistsException;
import org.modeshape.jcr.api.index.InvalidIndexDefinitionException;
import org.modeshape.jcr.api.index.NoSuchIndexException;
import org.modeshape.jcr.cache.SessionCache;
import org.modeshape.jcr.cache.WorkspaceNotFoundException;
import org.modeshape.jcr.cache.change.Change;
import org.modeshape.jcr.cache.change.ChangeSet;
import org.modeshape.jcr.cache.change.NodeAdded;
import org.modeshape.jcr.cache.change.NodeRemoved;
import org.modeshape.jcr.cache.change.PropertyChanged;
import org.modeshape.jcr.cache.change.WorkspaceAdded;
import org.modeshape.jcr.cache.change.WorkspaceRemoved;
import org.modeshape.jcr.query.CompositeIndexWriter;
import org.modeshape.jcr.query.engine.ScanningQueryEngine;
import org.modeshape.jcr.spi.index.IndexDefinitionChanges;
import org.modeshape.jcr.spi.index.IndexFeedback;
import org.modeshape.jcr.spi.index.IndexFeedback.IndexingCallback;
import org.modeshape.jcr.spi.index.IndexManager;
import org.modeshape.jcr.spi.index.IndexWriter;
import org.modeshape.jcr.spi.index.WorkspaceChanges;
import org.modeshape.jcr.spi.index.provider.IndexProvider;
import org.modeshape.jcr.spi.index.provider.IndexProviderExistsException;
import org.modeshape.jcr.spi.index.provider.NoSuchProviderException;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NameFactory;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.PathFactory;
import org.modeshape.jcr.value.StringFactory;
import org.modeshape.jcr.value.ValueFactory;
/**
* The {@link RepositoryIndexManager} is the maintainer of index definitions for the entire repository at run-time. The repository
* index manager maintains an immutable view of all index definitions.
*/
@ThreadSafe
class RepositoryIndexManager implements IndexManager, NodeTypes.Listener {
/**
* Names of properties that are known to have non-unique values when used in a single-valued index.
*/
private static final Set NON_UNIQUE_PROPERTY_NAMES = Collections.unmodifiableSet(JcrLexicon.PRIMARY_TYPE,
JcrLexicon.MIXIN_TYPES,
JcrLexicon.PATH,
ModeShapeLexicon.DEPTH,
ModeShapeLexicon.LOCALNAME);
/**
* Names of properties that are known to have non-enumerated values when used in a single-valued index.
*/
private static final Set NON_ENUMERATED_PROPERTY_NAMES = Collections.unmodifiableSet(JcrLexicon.PRIMARY_TYPE,
JcrLexicon.MIXIN_TYPES,
JcrLexicon.PATH,
ModeShapeLexicon.DEPTH,
ModeShapeLexicon.LOCALNAME);
private final JcrRepository.RunningState repository;
private final RepositoryConfiguration config;
private final ExecutionContext context;
private final String systemWorkspaceName;
private final Path indexesPath;
private final Collection components;
private final ConcurrentMap providers = new ConcurrentHashMap<>();
private final AtomicBoolean initialized = new AtomicBoolean(false);
private volatile IndexWriter indexWriter;
private final Logger logger = Logger.getLogger(getClass());
private volatile RepositoryIndexes indexes = RepositoryIndexes.NO_INDEXES;
RepositoryIndexManager( JcrRepository.RunningState repository,
RepositoryConfiguration config ) {
this.repository = repository;
this.config = config;
this.context = repository.context();
this.systemWorkspaceName = this.repository.repositoryCache().getSystemWorkspaceName();
PathFactory pathFactory = this.context.getValueFactories().getPathFactory();
this.indexesPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, ModeShapeLexicon.INDEXES);
// Set up the index providers ...
this.components = config.getIndexProviders();
for (Component component : components) {
try {
IndexProvider provider = component.createInstance(ScanningQueryEngine.class.getClassLoader());
register(provider);
} catch (Throwable t) {
if (t.getCause() != null) {
t = t.getCause();
}
this.repository.error(t, JcrI18n.unableToInitializeIndexProvider, component, repository.name(), t.getMessage());
}
}
}
/**
* Initialize this manager by calling {@link IndexProvider#initialize()} on each of the currently-registered providers.
*
* @return the information about the portions of the repository that need to be scanned to (re)build indexes; null if no
* scanning is required
*/
protected synchronized ScanningTasks initialize() {
if (initialized.get()) {
// nothing to do ...
return null;
}
// Initialize each of the providers, removing any that are not properly initialized ...
for (Iterator> providerIter = providers.entrySet().iterator(); providerIter.hasNext();) {
IndexProvider provider = providerIter.next().getValue();
try {
doInitialize(provider);
} catch (Throwable t) {
if (t.getCause() != null) {
t = t.getCause();
}
repository.error(t, JcrI18n.unableToInitializeIndexProvider, provider.getName(), repository.name(),
t.getMessage());
providerIter.remove();
}
}
// Re-read the index definitions in case there were disabled index definitions that used the now-available provider ...
RepositoryIndexes indexes = readIndexDefinitions();
// Notify the providers of all the index definitions (which we'll treat as "new" since we're just starting up) ...
ScanningTasks feedback = new ScanningTasks();
for (Iterator> providerIter = providers.entrySet().iterator(); providerIter.hasNext();) {
IndexProvider provider = providerIter.next().getValue();
if (provider == null) continue;
final String providerName = provider.getName();
IndexChanges changes = new IndexChanges();
for (IndexDefinition indexDefn : indexes.getIndexDefinitions().values()) {
if (!providerName.equals(indexDefn.getProviderName())) continue;
changes.change(indexDefn);
}
// Even if there are no definitions, we still want to notify each of the providers ...
try {
provider.notify(changes, repository.changeBus(), repository.nodeTypeManager(), repository.repositoryCache()
.getWorkspaceNames(),
feedback.forProvider(providerName));
} catch (RuntimeException e) {
logger.error(e, JcrI18n.errorNotifyingProviderOfIndexChanges, providerName, repository.name(), e.getMessage());
}
}
// Refresh the index writer ...
refreshIndexWriter();
initialized.set(true);
return feedback;
}
@Override
public void notify( NodeTypes updatedNodeTypes ) {
// Notify all of the providers about the change in node types ...
for (IndexProvider provider : providers.values()) {
provider.notify(updatedNodeTypes);
}
}
synchronized void importIndexDefinitions() throws RepositoryException {
RepositoryConfiguration.Indexes indexes = config.getIndexes();
if (indexes.isEmpty()) return;
List defns = new ArrayList<>();
for (String indexName : indexes.getIndexNames()) {
IndexDefinition defn = indexes.getIndex(indexName);
if (defn != null) defns.add(defn);
}
if (!defns.isEmpty()) {
IndexDefinition[] array = defns.toArray(new IndexDefinition[defns.size()]);
registerIndexes(array, true);
// Wait while the indexes get created ...
try {
Thread.sleep(500L + array.length * 50L);
} catch (Exception e) {
throw new SystemFailureException(e);
}
// We have to index the '/jcr:system' content, since it was created before these indexes were registered ...
repository.queryManager().reindexSystemContent();
}
}
protected void refreshIndexWriter() {
indexWriter = CompositeIndexWriter.create(providers.values());
}
/**
* Initialize the supplied provider.
*
* @param provider the provider; may not be null
* @throws RepositoryException if there is a problem initializing the provider
*/
protected void doInitialize( IndexProvider provider ) throws RepositoryException {
// Set the execution context instance ...
ReflectionUtil.setValue(provider, "context", repository.context());
provider.initialize();
// If successful, call the 'postInitialize' method reflectively (due to inability to call directly) ...
Method postInitialize = ReflectionUtil.findMethod(IndexProvider.class, "postInitialize");
ReflectionUtil.invokeAccessibly(provider, postInitialize, new Object[] {});
if (logger.isDebugEnabled()) {
logger.debug("Successfully initialized index provider '{0}' in repository '{1}'", provider.getName(),
repository.name());
}
}
void shutdown() {
for (IndexProvider provider : providers.values()) {
try {
provider.shutdown();
} catch (RepositoryException e) {
logger.error(e, JcrI18n.errorShuttingDownIndexProvider, repository.name(), provider.getName(), e.getMessage());
}
}
}
/**
* Get the query index writer that will delegate to all registered providers.
*
* @return the query index writer instance; never null
*/
IndexWriter getIndexWriter() {
return indexWriter;
}
/**
* Get the query index writer that will delegate to only those registered providers with the given names.
*
* @param providerNames the names of the providers that require indexing
* @return a query index writer instance; never null
*/
IndexWriter getIndexWriterForProviders( Set providerNames ) {
List reindexProviders = new LinkedList<>();
for (IndexProvider provider : providers.values()) {
if (providerNames.contains(provider.getName())) {
reindexProviders.add(provider);
}
}
return CompositeIndexWriter.create(reindexProviders);
}
@Override
public synchronized void register( IndexProvider provider ) throws RepositoryException {
if (providers.containsKey(provider.getName())) {
throw new IndexProviderExistsException(JcrI18n.indexProviderAlreadyExists.text(provider.getName(), repository.name()));
}
// Set the repository name field ...
ReflectionUtil.setValue(provider, "repositoryName", repository.name());
// Set the logger instance
ReflectionUtil.setValue(provider, "logger", ExtensionLogger.getLogger(provider.getClass()));
if (initialized.get()) {
// This manager is already initialized, so we have to initialize the new provider ...
doInitialize(provider);
}
// Do this last so that it doesn't show up in the list of providers before it's properly initialized ...
IndexProvider existing = providers.putIfAbsent(provider.getName(), provider);
if (existing != null) {
throw new IndexProviderExistsException(JcrI18n.indexProviderAlreadyExists.text(provider.getName(), repository.name()));
}
// Re-read the index definitions in case there were disabled index definitions that used the now-available provider ...
readIndexDefinitions();
// Refresh the index writer ...
refreshIndexWriter();
}
@Override
public void unregister( String providerName ) throws RepositoryException {
IndexProvider provider = providers.remove(providerName);
if (provider == null) {
throw new NoSuchProviderException(JcrI18n.indexProviderDoesNotExist.text(providerName, repository.name()));
}
if (initialized.get()) {
provider.shutdown();
}
// Re-read the index definitions in case there were disabled index definitions that used the now-available provider ...
readIndexDefinitions();
// Refresh the index writer ...
refreshIndexWriter();
}
@Override
public Set getProviderNames() {
return Collections.unmodifiableSet(new HashSet<>(providers.keySet()));
}
protected Iterable getProviders() {
return new ArrayList<>(providers.values());
}
@Override
public IndexProvider getProvider( String name ) {
return providers.get(name);
}
@Override
public Map getIndexDefinitions() {
return indexes.getIndexDefinitions();
}
@Override
public IndexColumnDefinitionTemplate createIndexColumnDefinitionTemplate() {
return new RepositoryIndexColumnDefinitionTemplate();
}
@Override
public IndexDefinitionTemplate createIndexDefinitionTemplate() {
return new RepositoryIndexDefinitionTemplate();
}
@Override
public void registerIndex( IndexDefinition indexDefinition,
boolean allowUpdate )
throws InvalidIndexDefinitionException, IndexExistsException, RepositoryException {
registerIndexes(new IndexDefinition[] {indexDefinition}, allowUpdate);
}
@Override
public void registerIndexes( IndexDefinition[] indexDefinitions,
boolean allowUpdate ) throws InvalidIndexDefinitionException, IndexExistsException {
CheckArg.isNotNull(indexDefinitions, "indexDefinitions");
// Before we do anything, validate each of the index definitions and throw an exception ...
RepositoryNodeTypeManager nodeTypeManager = repository.nodeTypeManager();
List validated = new ArrayList<>(indexDefinitions.length);
Problems problems = new SimpleProblems();
for (IndexDefinition defn : indexDefinitions) {
String name = defn.getName();
String providerName = defn.getProviderName();
if (name == null) {
problems.addError(JcrI18n.indexMustHaveName, defn, repository.name());
continue;
}
if (indexes.getIndexDefinitions().containsKey(name) && !allowUpdate) {
// Throw this one immediately ...
String msg = JcrI18n.indexAlreadyExists.text(defn.getName(), repository.name());
throw new IndexExistsException(msg);
}
if (providerName == null) {
problems.addError(JcrI18n.indexMustHaveProviderName, defn.getName(), repository.name());
continue;
}
if (defn.hasSingleColumn()) {
IndexColumnDefinition columnDefn = defn.getColumnDefinition(0);
Name propName = context.getValueFactories().getNameFactory().create(columnDefn.getPropertyName());
switch (defn.getKind()) {
case UNIQUE_VALUE:
if (NON_UNIQUE_PROPERTY_NAMES.contains(propName)) {
problems.addError(JcrI18n.unableToCreateUniqueIndexForColumn, defn.getName(),
columnDefn.getPropertyName());
}
break;
case ENUMERATED_VALUE:
if (NON_ENUMERATED_PROPERTY_NAMES.contains(propName)) {
problems.addError(JcrI18n.unableToCreateEnumeratedIndexForColumn, defn.getName(),
columnDefn.getPropertyName());
}
break;
case VALUE:
case NODE_TYPE:
case TEXT:
break;
}
} else {
// Mulitple columns ...
if (defn.getKind() == IndexKind.NODE_TYPE) {
// must be single-column indexes
problems.addError(JcrI18n.nodeTypeIndexMustHaveOneColumn, defn.getName());
}
}
IndexProvider provider = providers.get(providerName);
if (provider == null) {
problems.addError(JcrI18n.indexProviderDoesNotExist, defn, repository.name());
} else {
// Have the provider validate the index
provider.validateProposedIndex(context, defn, nodeTypeManager, problems);
// Create an instance of our own definition implementation ...
defn = RepositoryIndexDefinition.createFrom(defn, true);
validated.add(defn);
}
}
if (problems.hasErrors()) {
String msg = JcrI18n.invalidIndexDefinitions.text(repository.name(), problems);
throw new InvalidIndexDefinitionException(new JcrProblems(problems), msg);
}
SessionCache systemCache = repository.createSystemSession(context, false);
SystemContent system = new SystemContent(systemCache);
for (IndexDefinition defn : validated) {
String providerName = defn.getProviderName();
// Determine if the index should be enabled ...
defn = RepositoryIndexDefinition.createFrom(defn, providers.containsKey(providerName));
// Write the definition to the system area ...
system.store(defn, allowUpdate);
}
// Save the changes ...
systemCache.save();
// Refresh the immutable snapshot ...
this.indexes = readIndexDefinitions();
}
@Override
public void unregisterIndexes( String... indexNames ) throws NoSuchIndexException, RepositoryException {
if (indexNames == null || indexNames.length == 0) return;
// Remove the definition from the system area ...
SessionCache systemCache = repository.createSystemSession(context, false);
SystemContent system = new SystemContent(systemCache);
for (String indexName : indexNames) {
IndexDefinition defn = indexes.getIndexDefinitions().get(indexName);
if (defn == null) {
throw new NoSuchIndexException(JcrI18n.indexDoesNotExist.text(indexName, repository.name()));
}
system.remove(defn);
}
system.save();
// Refresh the immutable snapshot ...
this.indexes = readIndexDefinitions();
}
RepositoryIndexManager with( JcrRepository.RunningState repository ) {
return new RepositoryIndexManager(repository, config);
}
protected final ValueFactory strings() {
return this.context.getValueFactories().getStringFactory();
}
/**
* Get an immutable snapshot of the index definitions. This can be used by the query engine to determine which indexes might
* be usable when querying a specific selector (node type).
*
* @return a snapshot of the index definitions at this moment; never null
*/
public RepositoryIndexes getIndexes() {
return indexes;
}
protected ScanningTasks notify( ChangeSet changeSet ) {
if (changeSet.getWorkspaceName() == null) {
// This is a change to the workspaces or repository metadata ...
// Refresh the index definitions ...
RepositoryIndexes indexes = readIndexDefinitions();
ScanningTasks feedback = new ScanningTasks();
if (!indexes.getIndexDefinitions().isEmpty()) {
// Build up the names of the added and removed workspace names ...
Set addedWorkspaces = new HashSet<>();
Set removedWorkspaces = new HashSet<>();
for (Change change : changeSet) {
if (change instanceof WorkspaceAdded) {
WorkspaceAdded added = (WorkspaceAdded)change;
addedWorkspaces.add(added.getWorkspaceName());
} else if (change instanceof WorkspaceRemoved) {
WorkspaceRemoved removed = (WorkspaceRemoved)change;
removedWorkspaces.add(removed.getWorkspaceName());
}
}
if (!addedWorkspaces.isEmpty() || !removedWorkspaces.isEmpty()) {
// Figure out which providers need to be called, and which definitions go with those providers ...
Map> defnsByProvider = new HashMap<>();
for (IndexDefinition defn : indexes.getIndexDefinitions().values()) {
String providerName = defn.getProviderName();
List defns = defnsByProvider.get(providerName);
if (defns == null) {
defns = new ArrayList<>();
defnsByProvider.put(providerName, defns);
}
defns.add(defn);
}
// Then for each provider ...
for (Map.Entry> entry : defnsByProvider.entrySet()) {
String providerName = entry.getKey();
WorkspaceIndexChanges changes = new WorkspaceIndexChanges(entry.getValue(), addedWorkspaces,
removedWorkspaces);
IndexProvider provider = providers.get(providerName);
if (provider == null) continue;
provider.notify(changes, repository.changeBus(), repository.nodeTypeManager(),
repository.repositoryCache().getWorkspaceNames(), feedback.forProvider(providerName));
}
}
}
return feedback;
}
if (!systemWorkspaceName.equals(changeSet.getWorkspaceName())) {
// The change does not affect the 'system' workspace, so skip it ...
return null;
}
// It is simple to listen to all local and remote changes. Therefore, any changes made locally to the index definitions
// will be propagated through the cached representation via this listener.
AtomicReference
© 2015 - 2024 Weber Informatics LLC | Privacy Policy