@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 ... Reflection.setValue(provider, "repositoryName", repository.name()); // Set the logger instance Reflection.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 notify(NodeTypes updatedNodeTypes) { // Notify all of the providers about the change in node types ... for (IndexProvider provider : providers.values()) { provider.notify(updatedNodeTypes); } }
/** * 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<String> providerNames) { List<IndexProvider> reindexProviders = new LinkedList<>(); for (IndexProvider provider : providers.values()) { if (providerNames.contains(provider.getName())) { reindexProviders.add(provider); } } return CompositeIndexWriter.create(reindexProviders); }
@Override public IndexStatus getIndexStatus(String providerName, String indexName, String workspaceName) { CheckArg.isNotNull(providerName, "providerName"); CheckArg.isNotNull(indexName, "indexName"); CheckArg.isNotNull(workspaceName, "workspaceName"); IndexProvider provider = getProvider(providerName); if (provider == null) { return IndexStatus.NON_EXISTENT; } ManagedIndex managedIndex = provider.getManagedIndex(indexName, workspaceName); return managedIndex != null ? managedIndex.getStatus() : IndexStatus.NON_EXISTENT; }
void shutdown() { for (IndexProvider provider : providers.values()) { try { provider.shutdown(); } catch (RepositoryException e) { logger.error( e, JcrI18n.errorShuttingDownIndexProvider, repository.name(), provider.getName(), e.getMessage()); } } }
@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(); }
/** * 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 ... Reflection.setValue(provider, "context", repository.context()); // Set the environment Reflection.setValue(provider, "environment", repository.environment()); provider.initialize(); // If successful, call the 'postInitialize' method reflectively (due to inability to call // directly) ... Method postInitialize = Reflection.findMethod(IndexProvider.class, "postInitialize"); Reflection.invokeAccessibly(provider, postInitialize, new Object[] {}); if (logger.isDebugEnabled()) { logger.debug( "Successfully initialized index provider '{0}' in repository '{1}'", provider.getName(), repository.name()); } }
@Override public List<String> getIndexNames( String providerName, String workspaceName, final IndexStatus status) { CheckArg.isNotNull(providerName, "providerName"); CheckArg.isNotNull(workspaceName, "workspaceName"); final List<String> result = new ArrayList<>(); IndexProvider provider = getProvider(providerName); if (provider == null) { return result; } provider.onEachIndexInWorkspace( workspaceName, new IndexProvider.ManagedIndexOperation() { @Override public void apply(String workspaceName, ManagedIndex index, IndexDefinition defn) { if (index.getStatus().equals(status)) { result.add(defn.getName()); } } }); return result; }
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<String> addedWorkspaces = new HashSet<>(); Set<String> 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<String, List<IndexDefinition>> defnsByProvider = new HashMap<>(); for (IndexDefinition defn : indexes.getIndexDefinitions().values()) { String providerName = defn.getProviderName(); List<IndexDefinition> defns = defnsByProvider.get(providerName); if (defns == null) { defns = new ArrayList<>(); defnsByProvider.put(providerName, defns); } defns.add(defn); } // Then for each provider ... for (Map.Entry<String, List<IndexDefinition>> 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<Map<Name, IndexChangeInfo>> changesByProviderName = new AtomicReference<>(); for (Change change : changeSet) { if (change instanceof NodeAdded) { NodeAdded added = (NodeAdded) change; Path addedPath = added.getPath(); if (indexesPath.isAncestorOf(addedPath)) { // Get the name of the affected provider ... Name providerName = addedPath.getSegment(2).getName(); if (addedPath.size() > 3) { // Adding an index (or column definition), but all we care about is the name of the // index Name indexName = addedPath.getSegment(3).getName(); changeInfoForProvider(changesByProviderName, providerName).changed(indexName); } } } else if (change instanceof NodeRemoved) { NodeRemoved removed = (NodeRemoved) change; Path removedPath = removed.getPath(); if (indexesPath.isAncestorOf(removedPath)) { // Get the name of the affected provider ... Name providerName = removedPath.getSegment(2).getName(); if (removedPath.size() > 4) { // It's a column definition being removed, so the index is changed ... Name indexName = removedPath.getSegment(3).getName(); changeInfoForProvider(changesByProviderName, providerName).removed(indexName); } else if (removedPath.size() > 3) { // Removing an index (or column definition), but all we care about is the name of the // index Name indexName = removedPath.getSegment(3).getName(); changeInfoForProvider(changesByProviderName, providerName).removed(indexName); } else if (removedPath.size() == 3) { // The whole provider was removed ... changeInfoForProvider(changesByProviderName, providerName).removedAll(); } } } else if (change instanceof PropertyChanged) { PropertyChanged propChanged = (PropertyChanged) change; Path changedPath = propChanged.getPathToNode(); if (indexesPath.isAncestorOf(changedPath)) { if (changedPath.size() > 3) { // Adding an index (or column definition), but all we care about is the name of the // index Name providerName = changedPath.getSegment(2).getName(); Name indexName = changedPath.getSegment(3).getName(); changeInfoForProvider(changesByProviderName, providerName).changed(indexName); } } } // we don't care about node moves (don't happen) or property added/removed (handled by node // add/remove) } if (changesByProviderName.get() == null || changesByProviderName.get().isEmpty()) { // No changes to the indexes ... return null; } // Refresh the index definitions ... RepositoryIndexes indexes = readIndexDefinitions(); // And notify the affected providers ... StringFactory strings = context.getValueFactories().getStringFactory(); ScanningTasks feedback = new ScanningTasks(); for (Map.Entry<Name, IndexChangeInfo> entry : changesByProviderName.get().entrySet()) { String providerName = strings.create(entry.getKey()); IndexProvider provider = providers.get(providerName); if (provider == null) continue; IndexChanges changes = new IndexChanges(); IndexChangeInfo info = entry.getValue(); if (info.removedAll) { // Get all of the definitions for this provider ... for (IndexDefinition defn : indexes.getIndexDefinitions().values()) { if (defn.getProviderName().equals(providerName)) changes.remove(defn.getName()); } } // Others might have been added or changed after the existing ones were removed ... for (Name name : info.removedIndexes) { changes.remove(strings.create(name)); } for (Name name : info.changedIndexes) { IndexDefinition defn = indexes.getIndexDefinitions().get(strings.create(name)); if (defn != null) changes.change(defn); } // Notify the provider ... 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()); } } // Finally swap the snapshot of indexes ... this.indexes = indexes; return feedback; }
@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<IndexDefinition> 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.getName(), repository.name()); } else { // Perform some default validations that should be applied to all providers... provider.validateDefaultColumnTypes(context, defn, problems); // Then have the provider perform any custom validations 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(); }
/** * 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<Map.Entry<String, IndexProvider>> 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<Map.Entry<String, IndexProvider>> 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; }