@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();
  }
  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());
      }
    }
  }
  /**
   * Check if the named node type is in use in any workspace in the repository
   *
   * @param nodeTypeName the name of the node type to check
   * @return true if at least one node is using that type; false otherwise
   * @throws InvalidQueryException if there is an error searching for uses of the named node type
   */
  boolean isNodeTypeInUse(Name nodeTypeName) throws InvalidQueryException {

    String nodeTypeString = nodeTypeName.getString(context.getNamespaceRegistry());
    String expression = "SELECT * from [" + nodeTypeString + "] LIMIT 1";
    TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
    // Parsing must be done now ...
    QueryCommand command = queryParser.parseQuery(expression, typeSystem);
    assert command != null : "Could not parse " + expression;

    Schemata schemata = getRepositorySchemata();

    // Now query the entire repository for any nodes that use this node type ...
    RepositoryCache repoCache = repository.repositoryCache();
    RepositoryQueryManager queryManager = repository.queryManager();
    Set<String> workspaceNames = repoCache.getWorkspaceNames();
    Map<String, NodeCache> overridden = null;
    NodeTypes nodeTypes = repository.nodeTypeManager().getNodeTypes();
    RepositoryIndexes indexDefns = repository.queryManager().getIndexes();
    CancellableQuery query =
        queryManager.query(
            context,
            repoCache,
            workspaceNames,
            overridden,
            command,
            schemata,
            indexDefns,
            nodeTypes,
            null,
            null);
    try {
      QueryResults result = query.execute();
      if (result.isEmpty()) return false;
      if (result.getRowCount() < 0) {
        // Try to get the first row ...
        NodeSequence seq = result.getRows();
        Batch batch = seq.nextBatch();
        while (batch != null) {
          if (batch.hasNext()) return true;
          // It's not common for the first batch may be empty, but it's possible. So try the next
          // batch ...
          batch = seq.nextBatch();
        }
        return false;
      }
      return result.getRowCount() > 0;
    } catch (RepositoryException e) {
      logger.error(e, JcrI18n.errorCheckingNodeTypeUsage, nodeTypeName, e.getLocalizedMessage());
      return true;
    }
  }
 RepositoryNodeTypeManager with(
     JcrRepository.RunningState repository,
     boolean includeColumnsForInheritedProperties,
     boolean includePseudoColumnsInSelectStar) {
   assert this.systemWorkspaceName.equals(repository.repositoryCache().getSystemWorkspaceName());
   PathFactory pathFactory = repository.context().getValueFactories().getPathFactory();
   Path nodeTypesPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, JcrLexicon.NODE_TYPES);
   assert this.nodeTypesPath.equals(nodeTypesPath);
   RepositoryNodeTypeManager result =
       new RepositoryNodeTypeManager(
           repository, includeColumnsForInheritedProperties, includePseudoColumnsInSelectStar);
   // Now copy the node types from this cache into the new manager's cache ...
   // (If we didn't do this, we'd have to refresh from the system storage)
   result.nodeTypesCache = result.nodeTypesCache.with(this.nodeTypesCache.getAllNodeTypes());
   return result;
 }
 protected RepositoryIndexes readIndexDefinitions() {
   // There were at least some changes ...
   NodeTypes nodeTypes = repository.nodeTypeManager().getNodeTypes();
   try {
     // Read the affected index definitions ...
     SessionCache systemCache = repository.createSystemSession(context, false);
     SystemContent system = new SystemContent(systemCache);
     Collection<IndexDefinition> indexDefns = system.readAllIndexDefinitions(providers.keySet());
     this.indexes = new Indexes(context, indexDefns, nodeTypes);
     return this.indexes;
   } catch (WorkspaceNotFoundException e) {
     // This happens occasionally when shutting down ...
   } catch (Throwable e) {
     logger.error(e, JcrI18n.errorRefreshingIndexDefinitions, repository.name());
   }
   return indexes;
 }
 /**
  * Refresh the node types from the stored representation.
  *
  * @return true if there was at least one node type found, or false if there were none
  */
 protected boolean refreshFromSystem() {
   this.nodeTypesLock.writeLock().lock();
   try {
     // Re-read and re-register all of the node types ...
     SessionCache systemCache = repository.createSystemSession(context, true);
     SystemContent system = new SystemContent(systemCache);
     Collection<NodeTypeDefinition> nodeTypes = system.readAllNodeTypes();
     if (nodeTypes.isEmpty()) return false;
     registerNodeTypes(nodeTypes, false, false, false);
     return true;
   } catch (Throwable e) {
     logger.error(e, JcrI18n.errorRefreshingNodeTypes, repository.name());
     return false;
   } finally {
     this.nodeTypesLock.writeLock().unlock();
   }
 }
 /**
  * Refresh the node types from the stored representation.
  *
  * @return true if there was at least one node type found, or false if there were none
  */
 protected boolean refreshFromSystem() {
   Lock lock = this.namespacesLock.writeLock();
   try {
     lock.lock();
     // Re-read and re-register all of the namespaces ...
     SessionCache systemCache = repository.createSystemSession(context, false);
     SystemContent system = new SystemContent(systemCache);
     Collection<Namespace> namespaces = system.readAllNamespaces();
     if (namespaces.isEmpty()) return false;
     this.cache.clear();
     this.cache.register(namespaces);
   } catch (Throwable e) {
     logger.error(e, JcrI18n.errorRefreshingNodeTypes, repository.name());
   } finally {
     lock.unlock();
   }
   return true;
 }
  @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();
  }
  /**
   * 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());
    }
  }
 void shutdown() {
   for (IndexProvider provider : providers.values()) {
     try {
       provider.shutdown();
     } catch (RepositoryException e) {
       logger.error(
           e,
           JcrI18n.errorShuttingDownIndexProvider,
           repository.name(),
           provider.getName(),
           e.getMessage());
     }
   }
 }
  RepositoryNodeTypeManager(
      JcrRepository.RunningState repository,
      boolean includeColumnsForInheritedProperties,
      boolean includePseudoColumnsInSelectStar) {
    this.repository = repository;
    this.context = repository.context();
    this.nameFactory = this.context.getValueFactories().getNameFactory();
    this.systemWorkspaceName = this.repository.repositoryCache().getSystemWorkspaceName();

    PathFactory pathFactory = this.context.getValueFactories().getPathFactory();
    this.nodeTypesPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, JcrLexicon.NODE_TYPES);
    this.nodeTypesCache = new NodeTypes(this.context);

    this.includeColumnsForInheritedProperties = includeColumnsForInheritedProperties;
    this.includePseudoColumnsInSelectStar = includePseudoColumnsInSelectStar;
    queryParser = new BasicSqlQueryParser();
  }
  @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();
  }
  private JcrPropertyDefinition propertyDefinitionFrom(PropertyDefinition propDefn)
      throws RepositoryException {
    Name propertyName = nameFactory.create(propDefn.getName());
    int onParentVersionBehavior = propDefn.getOnParentVersion();
    int requiredType = propDefn.getRequiredType();
    boolean mandatory = propDefn.isMandatory();
    boolean multiple = propDefn.isMultiple();
    boolean autoCreated = propDefn.isAutoCreated();
    boolean isProtected = propDefn.isProtected();
    boolean fullTextSearchable = propDefn.isFullTextSearchable();
    boolean queryOrderable = propDefn.isQueryOrderable();

    Value[] defaultValues = propDefn.getDefaultValues();
    JcrValue[] jcrDefaultValues = null;
    if (defaultValues != null) {
      jcrDefaultValues = new JcrValue[defaultValues.length];
      for (int i = 0; i != defaultValues.length; ++i) {
        Value value = defaultValues[i];
        jcrDefaultValues[i] = new JcrValue(this.context.getValueFactories(), value);
      }
    }

    String[] valueConstraints = propDefn.getValueConstraints();
    String[] queryOperators = propDefn.getAvailableQueryOperators();
    if (valueConstraints == null) valueConstraints = new String[0];
    NodeKey prototypeKey = repository.repositoryCache().getSystemKey();
    return new JcrPropertyDefinition(
        this.context,
        null,
        prototypeKey,
        propertyName,
        onParentVersionBehavior,
        autoCreated,
        mandatory,
        isProtected,
        jcrDefaultValues,
        requiredType,
        valueConstraints,
        multiple,
        fullTextSearchable,
        queryOrderable,
        queryOperators);
  }
  private JcrNodeType nodeTypeFrom(NodeTypeDefinition nodeType, List<JcrNodeType> supertypes)
      throws RepositoryException {
    PropertyDefinition[] propDefns = nodeType.getDeclaredPropertyDefinitions();
    NodeDefinition[] childDefns = nodeType.getDeclaredChildNodeDefinitions();
    List<JcrPropertyDefinition> properties = new ArrayList<JcrPropertyDefinition>();
    List<JcrNodeDefinition> childNodes = new ArrayList<JcrNodeDefinition>();

    if (propDefns != null) {
      for (PropertyDefinition propDefn : propDefns) {
        properties.add(propertyDefinitionFrom(propDefn));
      }
    }
    if (childDefns != null) {
      for (NodeDefinition childNodeDefn : childDefns) {
        childNodes.add(childNodeDefinitionFrom(childNodeDefn));
      }
    }

    Name name = nameFactory.create(nodeType.getName());
    Name primaryItemName = nameFactory.create(nodeType.getPrimaryItemName());
    boolean mixin = nodeType.isMixin();
    boolean isAbstract = nodeType.isAbstract();
    boolean queryable = nodeType.isQueryable();
    boolean orderableChildNodes = nodeType.hasOrderableChildNodes();

    NodeKey prototypeKey = repository.repositoryCache().getSystemKey();
    return new JcrNodeType(
        prototypeKey,
        this.context,
        null,
        this,
        name,
        supertypes,
        primaryItemName,
        childNodes,
        properties,
        mixin,
        isAbstract,
        queryable,
        orderableChildNodes);
  }
 synchronized void importIndexDefinitions() throws RepositoryException {
   RepositoryConfiguration.Indexes indexes = config.getIndexes();
   if (indexes.isEmpty()) return;
   List<IndexDefinition> 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();
   }
 }
  private JcrNodeDefinition childNodeDefinitionFrom(NodeDefinition childNodeDefn) {
    Name childNodeName = nameFactory.create(childNodeDefn.getName());
    Name defaultPrimaryTypeName = nameFactory.create(childNodeDefn.getDefaultPrimaryTypeName());
    int onParentVersion = childNodeDefn.getOnParentVersion();

    boolean mandatory = childNodeDefn.isMandatory();
    boolean allowsSns = childNodeDefn.allowsSameNameSiblings();
    boolean autoCreated = childNodeDefn.isAutoCreated();
    boolean isProtected = childNodeDefn.isProtected();

    Name[] requiredTypes;
    String[] requiredTypeNames = childNodeDefn.getRequiredPrimaryTypeNames();
    if (requiredTypeNames != null) {
      List<Name> names = new ArrayList<Name>(requiredTypeNames.length);
      for (String typeName : requiredTypeNames) {
        names.add(nameFactory.create(typeName));
      }
      requiredTypes = names.toArray(new Name[names.size()]);
    } else {
      requiredTypes = new Name[0];
    }

    NodeKey prototypeKey = repository.repositoryCache().getSystemKey();
    return new JcrNodeDefinition(
        this.context,
        null,
        prototypeKey,
        childNodeName,
        onParentVersion,
        autoCreated,
        mandatory,
        isProtected,
        allowsSns,
        defaultPrimaryTypeName,
        requiredTypes);
  }
  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();
  }
  /**
   * Allows the collection of node types to be unregistered if they are not referenced by other node
   * types as supertypes, default primary types of child nodes, or required primary types of child
   * nodes.
   *
   * @param nodeTypeNames the names of the node types to be unregistered
   * @param failIfNodeTypesAreUsed true if this method should fail to unregister the named node
   *     types if any of the node types are still in use by nodes, or false if this method should
   *     not perform such a check
   * @throws NoSuchNodeTypeException if any of the node type names do not correspond to a registered
   *     node type
   * @throws InvalidNodeTypeDefinitionException if any of the node types with the given names cannot
   *     be unregistered because they are the supertype, one of the required primary types, or a
   *     default primary type of a node type that is not being unregistered.
   * @throws RepositoryException if any other error occurs
   */
  void unregisterNodeType(Collection<Name> nodeTypeNames, boolean failIfNodeTypesAreUsed)
      throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException {
    CheckArg.isNotNull(nodeTypeNames, "nodeTypeNames");
    if (nodeTypeNames.isEmpty()) return;

    if (failIfNodeTypesAreUsed) {
      long start = System.nanoTime();
      // Search the content graph to make sure that this type isn't being used
      for (Name nodeTypeName : nodeTypeNames) {
        if (isNodeTypeInUse(nodeTypeName)) {
          String name = nodeTypeName.getString(context.getNamespaceRegistry());
          throw new InvalidNodeTypeDefinitionException(
              JcrI18n.cannotUnregisterInUseType.text(name));
        }
      }
      long time =
          TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start), TimeUnit.NANOSECONDS);
      logger.debug(
          "{0} milliseconds to check if any of these node types are unused before unregistering them: {1}",
          time, nodeTypeNames);
    }

    try {
      /*
       * Grab an exclusive lock on this data to keep other nodes from being added/saved while the unregistration checks are occurring
       */
      List<JcrNodeType> removedNodeTypes = new ArrayList<JcrNodeType>(nodeTypeNames.size());
      nodeTypesLock.writeLock().lock();
      final NodeTypes nodeTypes = this.nodeTypesCache;

      for (Name nodeTypeName : nodeTypeNames) {
        /*
         * Check that the type names are valid
         */
        if (nodeTypeName == null) {
          throw new NoSuchNodeTypeException(JcrI18n.invalidNodeTypeName.text());
        }
        String name = nodeTypeName.getString(context.getNamespaceRegistry());

        JcrNodeType foundNodeType = nodeTypes.getNodeType(nodeTypeName);
        if (foundNodeType == null) {
          throw new NoSuchNodeTypeException(JcrI18n.noSuchNodeType.text(name));
        }
        removedNodeTypes.add(foundNodeType);

        /*
         * Check that no other node definitions have dependencies on any of the named types
         */
        for (JcrNodeType nodeType : nodeTypes.getAllNodeTypes()) {
          // If this node is also being unregistered, don't run checks against it
          if (nodeTypeNames.contains(nodeType.getInternalName())) {
            continue;
          }

          for (JcrNodeType supertype : nodeType.supertypes()) {
            if (nodeTypeName.equals(supertype.getInternalName())) {
              throw new InvalidNodeTypeDefinitionException(
                  JcrI18n.cannotUnregisterSupertype.text(name, supertype.getName()));
            }
          }

          for (JcrNodeDefinition childNode : nodeType.childNodeDefinitions()) {
            NodeType defaultPrimaryType = childNode.getDefaultPrimaryType();
            if (defaultPrimaryType != null && name.equals(defaultPrimaryType.getName())) {
              throw new InvalidNodeTypeDefinitionException(
                  JcrI18n.cannotUnregisterDefaultPrimaryType.text(
                      name, nodeType.getName(), childNode.getName()));
            }
            if (childNode.requiredPrimaryTypeNameSet().contains(nodeTypeName)) {
              throw new InvalidNodeTypeDefinitionException(
                  JcrI18n.cannotUnregisterRequiredPrimaryType.text(
                      name, nodeType.getName(), childNode.getName()));
            }
          }
        }
      }

      // Create the new cache ...
      NodeTypes newNodeTypes = nodeTypes.without(removedNodeTypes);

      // Remove the node types from persistent storage ...
      SessionCache system = repository.createSystemSession(context, false);
      SystemContent systemContent = new SystemContent(system);
      systemContent.unregisterNodeTypes(
          removedNodeTypes.toArray(new JcrNodeType[removedNodeTypes.size()]));
      systemContent.save();

      // Now change the cache ...
      this.nodeTypesCache = newNodeTypes;
      this.schemata = null;
    } finally {
      nodeTypesLock.writeLock().unlock();
    }
  }
  List<JcrNodeType> registerNodeTypes(
      Iterable<NodeTypeDefinition> nodeTypeDefns,
      boolean failIfNodeTypeDefinitionsExist,
      boolean skipIfNodeTypeDefinitionExists,
      boolean persist)
      throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {

    if (nodeTypeDefns == null) {
      return Collections.emptyList();
    }

    List<JcrNodeType> typesPendingRegistration = new ArrayList<JcrNodeType>();

    try {
      nodeTypesLock.writeLock().lock();
      final NodeTypes nodeTypes = this.nodeTypesCache;

      for (NodeTypeDefinition nodeTypeDefn : nodeTypeDefns) {
        if (nodeTypeDefn instanceof JcrNodeTypeTemplate) {
          // Switch to use this context, so names are properly prefixed ...
          nodeTypeDefn = ((JcrNodeTypeTemplate) nodeTypeDefn).with(context);
        }
        Name internalName = nodeTypes.nameFactory().create(nodeTypeDefn.getName());
        if (internalName == null || internalName.getLocalName().length() == 0) {
          throw new InvalidNodeTypeDefinitionException(JcrI18n.invalidNodeTypeName.text());
        }

        boolean found = nodeTypes.hasNodeType(internalName);
        if (found && failIfNodeTypeDefinitionsExist) {
          String name = nodeTypeDefn.getName();
          throw new NodeTypeExistsException(internalName, JcrI18n.nodeTypeAlreadyExists.text(name));
        }
        if (found && skipIfNodeTypeDefinitionExists) continue;

        List<JcrNodeType> supertypes =
            nodeTypes.supertypesFor(nodeTypeDefn, typesPendingRegistration);
        JcrNodeType nodeType = nodeTypeFrom(nodeTypeDefn, supertypes);

        typesPendingRegistration.add(nodeType);
      }

      if (!typesPendingRegistration.isEmpty()) {
        // Make sure the nodes have primary types that are either already registered, or pending
        // registration ...
        validateTypes(typesPendingRegistration);

        // Validate each of types that should be registered
        for (JcrNodeType typePendingRegistration : typesPendingRegistration) {
          nodeTypes.validate(
              typePendingRegistration,
              Arrays.asList(typePendingRegistration.getDeclaredSupertypes()),
              typesPendingRegistration);
        }

        SystemContent system = null;
        if (persist) {
          SessionCache systemCache = repository.createSystemSession(context, false);
          system = new SystemContent(systemCache);
        }

        for (JcrNodeType nodeType : typesPendingRegistration) {
          if (system != null) system.store(nodeType, true);
        }

        // Create the new cache ...
        NodeTypes newNodeTypes = nodeTypes.with(typesPendingRegistration);

        // Save the changes ...
        if (system != null) system.save();

        // And finally update the capabilities cache ...
        this.nodeTypesCache = newNodeTypes;
        this.schemata = null;
      }
    } finally {
      nodeTypesLock.writeLock().unlock();
    }

    return typesPendingRegistration;
  }
  @Override
  public void notify(ChangeSet changeSet) {
    if (!systemWorkspaceName.equals(changeSet.getWorkspaceName())) {
      // The change does not affect the 'system' workspace, so skip it ...
      return;
    }
    if (context.getProcessId().equals(changeSet.getProcessKey())) {
      // We generated these changes, so skip them ...
      return;
    }

    // Now process the changes ...
    Set<Name> nodeTypesToRefresh = new HashSet<Name>();
    Set<Name> nodeTypesToDelete = new HashSet<Name>();
    for (Change change : changeSet) {
      if (change instanceof NodeAdded) {
        NodeAdded added = (NodeAdded) change;
        Path addedPath = added.getPath();
        if (nodeTypesPath.isAncestorOf(addedPath)) {
          // Get the name of the node type ...
          Name nodeTypeName = addedPath.getSegment(2).getName();
          nodeTypesToRefresh.add(nodeTypeName);
        }
      } else if (change instanceof NodeRemoved) {
        NodeRemoved removed = (NodeRemoved) change;
        Path removedPath = removed.getPath();
        if (nodeTypesPath.isAncestorOf(removedPath)) {
          // Get the name of the node type ...
          Name nodeTypeName = removedPath.getSegment(2).getName();
          if (removedPath.size() == 3) {
            nodeTypesToDelete.add(nodeTypeName);
          } else {
            // It's a child defn or property defn ...
            if (!nodeTypesToDelete.contains(nodeTypeName)) {
              // The child defn or property defn is being removed but the node type is not ...
              nodeTypesToRefresh.add(nodeTypeName);
            }
          }
        }
      } else if (change instanceof PropertyChanged) {
        PropertyChanged propChanged = (PropertyChanged) change;
        Path changedPath = propChanged.getPathToNode();
        if (nodeTypesPath.isAncestorOf(changedPath)) {
          // Get the name of the node type ...
          Name nodeTypeName = changedPath.getSegment(2).getName();
          nodeTypesToRefresh.add(nodeTypeName);
        }
      } // we don't care about node moves (don't happen) or property added/removed (handled by node
        // add/remove)
    }

    if (nodeTypesToRefresh.isEmpty() && nodeTypesToDelete.isEmpty()) {
      // No changes
      return;
    }

    // There were at least some changes ...
    this.nodeTypesLock.writeLock().lock();
    try {
      // Re-register the node types that were changed or added ...
      SessionCache systemCache = repository.createSystemSession(context, false);
      SystemContent system = new SystemContent(systemCache);
      Collection<NodeTypeDefinition> nodeTypes = system.readNodeTypes(nodeTypesToRefresh);
      registerNodeTypes(nodeTypes, false, false, false);

      // Unregister those that were removed ...
      unregisterNodeType(nodeTypesToDelete, false);
    } catch (Throwable e) {
      logger.error(e, JcrI18n.errorRefreshingNodeTypes, repository.name());
    } finally {
      this.nodeTypesLock.writeLock().unlock();
    }
  }
 private final SystemContent systemContent(boolean readOnly) {
   SessionCache systemCache = repository.createSystemSession(context, readOnly);
   return new SystemContent(systemCache);
 }
  /**
   * 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;
  }