protected Indexes(
        ExecutionContext context, Collection<IndexDefinition> defns, NodeTypes nodeTypes) {
      // Identify the subtypes for each node type, and do this before we build any views ...
      if (!defns.isEmpty()) {
        Map<Name, Collection<String>> subtypesByName = new HashMap<>();
        for (JcrNodeType nodeType : nodeTypes.getAllNodeTypes()) {
          // For each of the supertypes ...
          for (JcrNodeType supertype : nodeType.getTypeAndSupertypes()) {
            Collection<String> types = subtypesByName.get(supertype.getInternalName());
            if (types == null) {
              types = new LinkedList<>();
              subtypesByName.put(supertype.getInternalName(), types);
            }
            types.add(nodeType.getName());
          }
        }

        // Now process all of the indexes ...
        NameFactory names = context.getValueFactories().getNameFactory();
        Set<Name> nodeTypeNames = new HashSet<>();
        for (IndexDefinition defn : defns) {

          // Determine all of the node types that are subtypes of any columns
          nodeTypeNames.clear();
          Name nodeTypeName = names.create(defn.getNodeTypeName());

          if (!subtypesByName.containsKey(nodeTypeName)) {
            Logger.getLogger(getClass())
                .warn(
                    JcrI18n.errorIndexing,
                    "not creating index "
                        + defn.getName()
                        + " because of unknown nodeType "
                        + nodeTypeName.getString());
            continue;
          }

          indexByName.put(defn.getName(), defn);

          // Now find out all of the node types that are or subtype the named node types ...
          for (String typeAndSubtype : subtypesByName.get(nodeTypeName)) {
            Map<String, Collection<IndexDefinition>> byProvider =
                indexesByProviderByNodeTypeName.get(typeAndSubtype);
            if (byProvider == null) {
              byProvider = new HashMap<>();
              indexesByProviderByNodeTypeName.put(typeAndSubtype, byProvider);
            }
            Collection<IndexDefinition> indexes = byProvider.get(defn.getProviderName());
            if (indexes == null) {
              indexes = new LinkedList<>();
              byProvider.put(typeAndSubtype, indexes);
            }
            indexes.add(defn);
          }
        }
      }
    }
  /**
   * 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;
    }
  }
  /**
   * 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();
    }
  }