/*
* 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.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.jcr.RepositoryException;
import org.modeshape.common.annotation.GuardedBy;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.common.util.ImmediateFuture;
import org.modeshape.jcr.JcrRepository.RunningState;
import org.modeshape.jcr.RepositoryIndexManager.ScanOperation;
import org.modeshape.jcr.RepositoryIndexManager.ScanningRequest;
import org.modeshape.jcr.RepositoryIndexManager.ScanningTasks;
import org.modeshape.jcr.api.index.IndexDefinition;
import org.modeshape.jcr.api.index.IndexManager;
import org.modeshape.jcr.api.query.QueryCancelledException;
import org.modeshape.jcr.api.query.qom.QueryCommand;
import org.modeshape.jcr.cache.CachedNode;
import org.modeshape.jcr.cache.ChildReference;
import org.modeshape.jcr.cache.ChildReferences;
import org.modeshape.jcr.cache.NodeCache;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.PathCache;
import org.modeshape.jcr.cache.RepositoryCache;
import org.modeshape.jcr.cache.change.ChangeSet;
import org.modeshape.jcr.cache.change.ChangeSetListener;
import org.modeshape.jcr.cache.document.WorkspaceCache;
import org.modeshape.jcr.journal.ChangeJournal;
import org.modeshape.jcr.query.BufferManager;
import org.modeshape.jcr.query.CancellableQuery;
import org.modeshape.jcr.query.CompositeIndexWriter;
import org.modeshape.jcr.query.QueryContext;
import org.modeshape.jcr.query.QueryEngine;
import org.modeshape.jcr.query.QueryEngineBuilder;
import org.modeshape.jcr.query.QueryResults;
import org.modeshape.jcr.query.engine.IndexQueryEngine;
import org.modeshape.jcr.query.engine.ScanningQueryEngine;
import org.modeshape.jcr.query.plan.PlanHints;
import org.modeshape.jcr.query.validate.Schemata;
import org.modeshape.jcr.spi.index.IndexWriter;
import org.modeshape.jcr.spi.index.provider.IndexProvider;
import org.modeshape.jcr.spi.index.provider.ManagedIndex;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.Path.Segment;
/**
* The query manager a the repository. Each instance lazily starts up the {@link QueryEngine}, which can be expensive.
*/
class RepositoryQueryManager implements ChangeSetListener {
private final Logger logger = Logger.getLogger(getClass());
private final Logger indexLogger = Logger.getLogger(getClass().getPackage().getName() + ".index");
private final RunningState runningState;
private final ExecutorService indexingExecutorService;
private final RepositoryConfiguration repoConfig;
private final RepositoryConfiguration.Reindexing reindexingCfg;
private final RepositoryIndexManager indexManager;
private final Lock engineInitLock = new ReentrantLock();
@GuardedBy( "engineInitLock" )
private volatile QueryEngine queryEngine;
private volatile Future<Void> asyncReindexingResult;
private volatile ScanningTasks toBeScanned = new ScanningTasks();
private final AtomicBoolean started = new AtomicBoolean(false);
RepositoryQueryManager( RunningState runningState,
ExecutorService indexingExecutorService,
RepositoryConfiguration config,
RepositoryConfiguration.Reindexing reindexingCfg) {
this.runningState = runningState;
this.indexingExecutorService = indexingExecutorService;
this.repoConfig = config;
this.reindexingCfg = reindexingCfg;
this.indexManager = new RepositoryIndexManager(runningState, config);
}
synchronized void initialize() {
this.toBeScanned.add(indexManager.initialize());
started.set(true);
}
@Override
public synchronized void notify( ChangeSet changeSet ) {
if (started.get()) {
boolean scanRequired = this.toBeScanned.add(this.indexManager.notify(changeSet));
if (scanRequired) {
// refresh the index writer
this.indexManager.refreshIndexWriter();
// It's initialized, so we have to call it ...
reindexIfNeeded(reindexingCfg.isAsync(), false);
}
}
// If not yet initialized, the "reindexIfNeeded" method will be called by the JcrRepository.
}
ChangeSetListener getListener() {
return this;
}
void shutdown() {
started.compareAndSet(true, false);
indexingExecutorService.shutdown();
if (queryEngine != null) {
try {
engineInitLock.lock();
if (queryEngine != null) {
try {
queryEngine.shutdown();
} finally {
queryEngine = null;
}
}
} finally {
engineInitLock.unlock();
}
}
indexManager.shutdown();
}
void stopReindexing() {
try {
engineInitLock.lock();
if (asyncReindexingResult != null) {
try {
asyncReindexingResult.get(1, TimeUnit.MINUTES);
} catch (java.util.concurrent.TimeoutException e) {
logger.debug("Re-indexing has not finished in time, attempting to cancel operation");
asyncReindexingResult.cancel(true);
} catch (Exception e) {
logger.debug(e, "Unexpected exception while waiting for re-indexing to terminate");
}
}
} finally {
asyncReindexingResult = null;
engineInitLock.unlock();
}
}
public CancellableQuery query( ExecutionContext context,
RepositoryCache repositoryCache,
Set<String> workspaceNames,
Map<String, NodeCache> overriddenNodeCachesByWorkspaceName,
final QueryCommand query,
Schemata schemata,
RepositoryIndexes indexDefns,
NodeTypes nodeTypes,
PlanHints hints,
Map<String, Object> variables ) {
final QueryEngine queryEngine = queryEngine();
final QueryContext queryContext = queryEngine.createQueryContext(context, repositoryCache, workspaceNames,
overriddenNodeCachesByWorkspaceName, schemata,
indexDefns, nodeTypes, new BufferManager(context),
hints, variables);
final org.modeshape.jcr.query.model.QueryCommand command = (org.modeshape.jcr.query.model.QueryCommand)query;
return new CancellableQuery() {
private final Lock lock = new ReentrantLock();
private QueryResults results;
@Override
public QueryResults execute() throws QueryCancelledException, RepositoryException {
try {
lock.lock();
if (results == null) {
// this will block and will hold the lock until it is done ...
results = queryEngine.execute(queryContext, command);
}
return results;
} finally {
lock.unlock();
}
}
@Override
public boolean cancel() {
return queryContext.cancel();
}
};
}
/**
* Get the writer to the indexes. The resulting instance will only write to the index providers that were registered at the
* time this method is called. Therefore, the writer should be used and discarded relatively quickly, since query index
* providers may be {@link RepositoryIndexManager#register(org.modeshape.jcr.spi.index.provider.IndexProvider) added} or
* {@link RepositoryIndexManager#unregister(String) removed} at any time.
*
* @return the index writer; never null
*/
public IndexWriter getIndexWriter() {
return indexManager.getIndexWriter();
}
protected RepositoryIndexManager getIndexManager() {
return indexManager;
}
/**
* Get an immutable snapshot of the index definitions. This can be used by the query engine to determine which indexes might
* be usable when quering a specific selector (node type).
*
* @return a snapshot of the index definitions at this moment; never null
*/
RepositoryIndexes getIndexes() {
return indexManager.getIndexes();
}
/**
* Obtain the query engine, which is created lazily and in a thread-safe manner.
*
* @return the query engine; never null
*/
protected final QueryEngine queryEngine() {
if (queryEngine == null) {
try {
engineInitLock.lock();
if (queryEngine == null) {
QueryEngineBuilder builder = null;
if (!repoConfig.getIndexProviders().isEmpty()) {
// There is at least one index provider ...
builder = IndexQueryEngine.builder();
logger.debug("Queries with indexes are enabled for the '{0}' repository. Executing queries may require scanning the repository contents when the query cannot use the defined indexes.",
repoConfig.getName());
} else {
// There are no indexes ...
builder = ScanningQueryEngine.builder();
logger.debug("Queries with no indexes are enabled for the '{0}' repository. Executing queries will always scan the repository contents.",
repoConfig.getName());
}
queryEngine = builder.using(repoConfig, indexManager, runningState.context()).build();
}
} finally {
engineInitLock.unlock();
}
}
return queryEngine;
}
protected void reindex() {
if (!indexManager.hasProviders()) {
// there are no index providers, so nothing to reindex
return;
}
boolean async = reindexingCfg.isAsync();
RepositoryConfiguration.ReindexingMode mode = reindexingCfg.mode();
switch (mode) {
case INCREMENTAL: {
final ChangeJournal journal = runningState.journal();
if (journal == null) {
logger.warn(JcrI18n.warnIncrementalIndexingJournalNotEnabled, repoConfig.getName());
break;
} else if (!journal.started()) {
logger.warn(JcrI18n.warnIncrementalIndexingJournalNotStarted, repoConfig.getName());
break;
} else {
if (!async) {
reindexIncrementally(journal);
} else {
asyncReindexingResult = indexingExecutorService.submit(() -> {
reindexIncrementally(journal);
return null;
});
}
break;
}
}
case IF_MISSING: {
// when reindexing after startup, make sure the system area is included as well or indexes will lack this
// information
reindexIfNeeded(async, true);
break;
}
default: {
throw new IllegalArgumentException("Unknown indexing mode: " + mode.toString());
}
}
}
private void reindexIncrementally( ChangeJournal journal ) {
// each provider may have a different timestamp when it last updated its indexes successfully.
// so for simplicity we'll use the lowest (earliest) timestamp of them all when reindexing
long earliestTimestamp = Long.MAX_VALUE;
List<IndexProvider> incrementalIndexingProviders = new ArrayList<>();
for (IndexProvider provider : indexManager.getProviders()) {
Long latestIndexUpdateTime = provider.getLatestIndexUpdateTime();
if (latestIndexUpdateTime == null) {
logger.warn(JcrI18n.warnIncrementalIndexingNotSupported, provider.getName());
continue;
}
incrementalIndexingProviders.add(provider);
earliestTimestamp = Math.min(earliestTimestamp, latestIndexUpdateTime);
}
if (incrementalIndexingProviders.isEmpty()) {
// none of the providers support incremental reindexing so nothing to do...
return;
}
assert earliestTimestamp != Long.MAX_VALUE;
Iterator<NodeKey> changedNodes = journal.changedNodesSince(earliestTimestamp);
IndexWriter writer = CompositeIndexWriter.create(incrementalIndexingProviders);
RepositoryCache repositoryCache = runningState.repositoryCache();
for (String workspaceName : repositoryCache.getWorkspaceNames()) {
if (logger.isDebugEnabled()) {
logger.debug("Performing incremental reindexing since '{0}' for repository '{1}' on workspace '{2}'",
new Date(earliestTimestamp), repositoryCache.getName(), workspaceName);
}
WorkspaceCache workspaceCache = repositoryCache.getWorkspaceCache(workspaceName);
reindexSince(workspaceCache, writer, changedNodes);
}
}
/**
* Reindex the repository only if there is at least one provider that required scanning and reindexing.
*
* @param async whether the reindexing should be performed asynchronously or not
* @param includeSystemContent whether the /jcr:system area should be indexed or not
*/
protected void reindexIfNeeded( boolean async, final boolean includeSystemContent ) {
final ScanningRequest request = toBeScanned.drain();
if (!request.isEmpty()) {
final RepositoryCache repoCache = runningState.repositoryCache();
scan(async, () -> {
// Scan each of the workspace-path pairs ...
ScanOperation op = (workspaceName, path, writer) -> {
NodeCache workspaceCache = repoCache.getWorkspaceCache(workspaceName);
if (workspaceCache != null) {
// The workspace is still valid ...
CachedNode node = workspaceCache.getNode(workspaceCache.getRootKey());
if (!path.isRoot()) {
for (Segment segment : path) {
ChildReference child = node.getChildReferences(workspaceCache).getChild(segment);
if (child == null) {
// The child no longer exists, so ignore this pair ...
node = null;
break;
}
node = workspaceCache.getNode(child);
if (node == null) break;
}
}
if (node != null) {
if (logger.isDebugEnabled()) {
logger.debug("Performing full reindexing for repository '{0}' and workspace '{1}'",
repoCache.getName(),
workspaceName);
}
// If we find a node to start at, then scan the content ...
// in certain cases (e.g. at startup) we have to index the system content (if it applies to
// any of the indexes)
boolean scanSystemContent = includeSystemContent ||
repoCache.getSystemWorkspaceName().equals(workspaceName);
updateIndexesStatus(workspaceName, IndexManager.IndexStatus.ENABLED, IndexManager.IndexStatus.REINDEXING);
if (reindexContent(workspaceName, workspaceCache, node, Integer.MAX_VALUE, scanSystemContent,
writer)) {
commitChanges(workspaceName);
}
updateIndexesStatus(workspaceName, IndexManager.IndexStatus.REINDEXING, IndexManager.IndexStatus.ENABLED);
}
}
};
request.onEachPathInWorkspace(op);
return null;
});
}
}
/**
* Clean all indexes and reindex all content.
*
* @param async true if the reindexing should be done in the background, or false if it should be done using this thread
*/
protected void cleanAndReindex( boolean async ) {
final IndexWriter writer = getIndexWriter();
scan(async, getIndexWriter(), new Callable<Void>() {
@SuppressWarnings( "synthetic-access" )
@Override
public Void call() throws Exception {
writer.clearAllIndexes();
reindexContent(true, writer);
return null;
}
});
}
private void scan( boolean async,
final IndexWriter indexes,
Callable<Void> callable ) {
if (!indexes.canBeSkipped()) {
if (async) {
asyncReindexingResult = indexingExecutorService.submit(callable);
} else {
try {
callable.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException();
}
}
}
}
private void scan( boolean async,
Callable<Void> callable ) {
if (async) {
asyncReindexingResult = indexingExecutorService.submit(callable);
} else {
try {
callable.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException();
}
}
}
/**
* Crawl and index all of the repository content.
*
* @param includeSystemContent true if the system content should also be indexed
* @param indexes the index writer that should be use; may not be null
*/
private void reindexContent( boolean includeSystemContent,
IndexWriter indexes ) {
if (indexes.canBeSkipped()) return;
// The node type schemata changes every time a node type is (un)registered, so get the snapshot that we'll use throughout
RepositoryCache repoCache = runningState.repositoryCache();
logger.debug(JcrI18n.reindexAll.text(runningState.name()));
if (includeSystemContent) {
String systemWorkspaceName = repoCache.getSystemWorkspaceName();
updateIndexesStatus(systemWorkspaceName, IndexManager.IndexStatus.ENABLED, IndexManager.IndexStatus.REINDEXING);
NodeCache systemWorkspaceCache = repoCache.getWorkspaceCache(systemWorkspaceName);
CachedNode rootNode = systemWorkspaceCache.getNode(repoCache.getSystemKey());
// Index the system content ...
logger.debug("Starting reindex of system content in '{0}' repository.", runningState.name());
if (reindexSystemContent(rootNode, Integer.MAX_VALUE, indexes)) {
commitChanges(systemWorkspaceName);
}
logger.debug("Completed reindex of system content in '{0}' repository.", runningState.name());
updateIndexesStatus(systemWorkspaceName, IndexManager.IndexStatus.REINDEXING, IndexManager.IndexStatus.ENABLED);
}
// Index the non-system workspaces ...
for (String workspaceName : repoCache.getWorkspaceNames()) {
// change the status of the indexes to reindexing
updateIndexesStatus(workspaceName, IndexManager.IndexStatus.ENABLED, IndexManager.IndexStatus.REINDEXING);
NodeCache workspaceCache = repoCache.getWorkspaceCache(workspaceName);
CachedNode rootNode = workspaceCache.getNode(workspaceCache.getRootKey());
logger.debug("Starting reindex of workspace '{0}' content in '{1}' repository.", runningState.name(), workspaceName);
if (reindexContent(workspaceName, workspaceCache, rootNode, Integer.MAX_VALUE, false, indexes)) {
commitChanges(workspaceName);
}
logger.debug("Completed reindex of workspace '{0}' content in '{1}' repository.", runningState.name(), workspaceName);
updateIndexesStatus(workspaceName, IndexManager.IndexStatus.REINDEXING, IndexManager.IndexStatus.ENABLED);
}
}
/**
* Crawl and index the content in the named workspace.
*
* @param workspace the workspace
* @throws IllegalArgumentException if the workspace is null
*/
public void reindexContent( JcrWorkspace workspace ) {
reindexContent(workspace, Path.ROOT_PATH, Integer.MAX_VALUE);
}
/**
* Crawl and index the content starting at the supplied path in the named workspace, to the designated depth.
*
* @param workspace the workspace
* @param path the path of the content to be indexed
* @param depth the depth of the content to be indexed
* @throws IllegalArgumentException if the workspace or path are null, or if the depth is less than 1
*/
public void reindexContent( JcrWorkspace workspace,
Path path,
int depth ) {
if (getIndexWriter().canBeSkipped()) {
// There's no indexes that require updating ...
return;
}
CheckArg.isPositive(depth, "depth");
JcrSession session = workspace.getSession();
NodeCache cache = session.cache().getWorkspace();
String workspaceName = workspace.getName();
// Look for the node ...
CachedNode node = cache.getNode(cache.getRootKey());
for (Segment segment : path) {
// Look for the child by name ...
ChildReference ref = node.getChildReferences(cache).getChild(segment);
if (ref == null) return;
node = cache.getNode(ref);
}
updateIndexesStatus(workspaceName, IndexManager.IndexStatus.ENABLED, IndexManager.IndexStatus.REINDEXING);
// If the node is in the system workspace ...
RepositoryCache repoCache = runningState.repositoryCache();
String systemWorkspaceName = repoCache.getSystemWorkspaceName();
String systemWorkspaceKey = repoCache.getSystemWorkspaceKey();
if (node.getKey().getWorkspaceKey().equals(systemWorkspaceKey)) {
if (reindexSystemContent(node, depth, getIndexWriter())) {
commitChanges(systemWorkspaceName);
}
} else {
// It's just a regular node in the workspace ...
if (reindexContent(workspaceName, cache, node, depth, path.isRoot(), getIndexWriter())) {
commitChanges(workspaceName);
}
}
updateIndexesStatus(workspaceName, IndexManager.IndexStatus.REINDEXING, IndexManager.IndexStatus.ENABLED);
}
protected void reindexSince( JcrWorkspace workspace,
long timestamp ) {
ChangeJournal journal = runningState.journal();
assert journal != null;
Iterator<NodeKey> changedNodes = journal.changedNodesSince(timestamp);
if (!changedNodes.hasNext()) {
// there are no nodes which have been changed since the given timestamp
return;
}
reindexSince(workspace.getSession().cache().getWorkspace(), getIndexWriter(), changedNodes);
}
protected void reindexSince( final WorkspaceCache cache,
final IndexWriter writer,
final Iterator<NodeKey> changedNodes ) {
if (writer.canBeSkipped()) {
// There's no indexes that require updating ...
return;
}
String workspaceName = cache.getWorkspaceName();
String workspaceKey = NodeKey.keyForWorkspaceName(workspaceName);
boolean commitRequired = false;
updateIndexesStatus(workspaceName, IndexManager.IndexStatus.ENABLED, IndexManager.IndexStatus.REINDEXING);
// take each of node keys that have been changed since the given timestamp and reindex each one if they belong to this WS
while (changedNodes.hasNext()) {
NodeKey nodeKey = changedNodes.next();
if (!workspaceKey.equals(nodeKey.getWorkspaceKey())) {
// this node does not belong to this WS cache, so ignore it...
continue;
}
CachedNode node = cache.getNode(nodeKey);
if (node != null) {
// the node still exists in the repository so reindex based on the latest available data...
commitRequired |= reindexContent(workspaceName, cache, node, 1, true, writer);
} else {
// the node has been removed from the repository so clear the information from the indexes...
commitRequired |= writer.remove(workspaceName, nodeKey);
}
}
if (commitRequired) {
commitChanges(workspaceName);
}
updateIndexesStatus(workspaceName, IndexManager.IndexStatus.REINDEXING, IndexManager.IndexStatus.ENABLED);
}
protected Future<Boolean> reindexSinceAsync( final JcrWorkspace workspace,
final long timestamp ) {
ChangeJournal journal = runningState.journal();
assert journal != null;
Iterator<NodeKey> changedNodes = journal.changedNodesSince(timestamp);
if (!changedNodes.hasNext()) {
// there are no nodes which have been changed since the given timestamp
return new ImmediateFuture<>(Boolean.FALSE);
}
return reindexSinceAsync(workspace.getSession().cache().getWorkspace(), getIndexWriter(), changedNodes);
}
protected Future<Boolean> reindexSinceAsync( final WorkspaceCache cache,
final IndexWriter indexWriter,
final Iterator<NodeKey> changedNodes ) {
return indexingExecutorService.submit(() -> {
reindexSince(cache, indexWriter, changedNodes);
return Boolean.TRUE;
});
}
protected boolean reindexContent( final String workspaceName,
NodeCache cache,
CachedNode node,
int depth,
boolean reindexSystemContent,
final IndexWriter indexes ) {
assert indexes != null;
if (indexes.canBeSkipped()) {
return false;
}
if (node.isExcludedFromSearch(cache)) {
return false;
}
// track if at least one index was updated as a result of this reindexing....
boolean indexesUpdated = false;
// Get the path for the first node (we already have it, but we need to populate the cache) ...
final PathCache paths = new PathCache(cache);
Path nodePath = paths.getPath(node);
// Index the first node ...
if (indexLogger.isTraceEnabled()) {
String path = runningState.context().getValueFactories().getStringFactory().create(nodePath);
indexLogger.debug("Reindexing node '{0}' in workspace '{1}' of repository '{2}': {3}", path, workspaceName,
runningState.name(), node);
}
indexesUpdated |= indexes.add(workspaceName, node.getKey(), nodePath, node.getPrimaryType(cache),
node.getMixinTypes(cache),
node.getPropertiesByName(cache));
if (depth == 1) {
return indexesUpdated;
}
// Create a queue for processing the subgraph
final Queue<NodeKey> queue = new LinkedList<NodeKey>();
if (reindexSystemContent) {
// We need to look for the system node, and index it differently ...
ChildReferences childRefs = node.getChildReferences(cache);
ChildReference systemRef = childRefs.getChild(JcrLexicon.SYSTEM);
NodeKey systemKey = systemRef != null ? systemRef.getKey() : null;
for (ChildReference childRef : node.getChildReferences(cache)) {
NodeKey childKey = childRef.getKey();
if (childKey.equals(systemKey)) {
// This is the "/jcr:system" node ...
node = cache.getNode(childKey);
indexesUpdated |= reindexSystemContent(node, depth - 1, indexes);
} else {
queue.add(childKey);
}
}
} else {
// Add all children to the queue ...
for (ChildReference childRef : node.getChildReferences(cache)) {
NodeKey childKey = childRef.getKey();
// we should not reindex anything which is in the system area
if (!childKey.getWorkspaceKey().equals(runningState.systemWorkspaceKey())) {
queue.add(childKey);
}
}
}
// Now, process the queue until empty ...
while (true) {
NodeKey key = queue.poll();
if (key == null) {
break;
}
// Look up the node and find the path ...
node = cache.getNode(key);
if (node == null || node.isExcludedFromSearch(cache)) {
continue;
}
nodePath = paths.getPath(node);
// Index the node ...
if (indexLogger.isTraceEnabled()) {
String path = runningState.context().getValueFactories().getStringFactory().create(nodePath);
indexLogger.debug("Reindexing node '{0}' in workspace '{1}' of repository '{2}': {3}", path, workspaceName,
runningState.name(), node);
}
indexesUpdated |= indexes.add(workspaceName, node.getKey(), nodePath, node.getPrimaryType(cache),
node.getMixinTypes(cache),
node.getPropertiesByName(cache));
// Check the depth ...
if (nodePath.size() <= depth) {
// Add the children to the queue ...
for (ChildReference childRef : node.getChildReferences(cache)) {
queue.add(childRef.getKey());
}
}
}
return indexesUpdated;
}
protected void updateIndexesStatus( String workspaceName, final IndexManager.IndexStatus currentStatus, final IndexManager.IndexStatus newStatus ) {
for (IndexProvider indexProvider : indexManager.getProviders()) {
indexProvider.onEachIndexInWorkspace(workspaceName, new IndexProvider.ManagedIndexOperation() {
@Override
public void apply( String workspaceName, ManagedIndex index, IndexDefinition defn ) {
index.updateStatus(currentStatus, newStatus);
}
});
}
}
protected void commitChanges( String workspaceName ) {
for (IndexProvider indexProvider : indexManager.getProviders()) {
indexProvider.getIndexWriter().commit(workspaceName);
}
}
protected boolean reindexSystemContent( CachedNode nodeInSystemBranch,
int depth,
IndexWriter indexes ) {
RepositoryCache repoCache = runningState.repositoryCache();
String workspaceName = repoCache.getSystemWorkspaceName();
NodeCache systemWorkspaceCache = repoCache.getWorkspaceCache(workspaceName);
return reindexContent(workspaceName, systemWorkspaceCache, nodeInSystemBranch, depth, true, indexes);
}
protected void reindexSystemContent() {
RepositoryCache repoCache = runningState.repositoryCache();
String workspaceName = repoCache.getSystemWorkspaceName();
NodeCache systemWorkspaceCache = repoCache.getWorkspaceCache(workspaceName);
CachedNode systemNode = systemWorkspaceCache.getNode(repoCache.getSystemKey());
if (reindexContent(workspaceName, systemWorkspaceCache, systemNode, Integer.MAX_VALUE, true, getIndexWriter())) {
commitChanges(workspaceName);
}
}
/**
* Asynchronously crawl and index the content in the named workspace.
*
* @param workspace the workspace
* @return the future for the asynchronous operation; never null
* @throws IllegalArgumentException if the workspace is null
*/
public Future<Boolean> reindexContentAsync( final JcrWorkspace workspace ) {
return indexingExecutorService.submit(() -> {
reindexContent(workspace);
return Boolean.TRUE;
});
}
/**
* Asynchronously crawl and index the content starting at the supplied path in the named workspace, to the designated depth.
*
* @param workspace the workspace
* @param path the path of the content to be indexed
* @param depth the depth of the content to be indexed
* @return the future for the asynchronous operation; never null
* @throws IllegalArgumentException if the workspace or path are null, or if the depth is less than 1
*/
public Future<Boolean> reindexContentAsync( final JcrWorkspace workspace,
final Path path,
final int depth ) {
return indexingExecutorService.submit(() -> {
reindexContent(workspace, path, depth);
return Boolean.TRUE;
});
}
}