Java Code Examples for com.googlecode.objectify.cmd.Query#filter()

The following examples show how to use com.googlecode.objectify.cmd.Query#filter() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: RdapDomainSearchAction.java    From nomulus with Apache License 2.0 6 votes vote down vote up
/** Searches for domains by domain name with a TLD suffix. */
private DomainSearchResponse searchByDomainNameByTld(String tld) {
  // Even though we are not searching on fullyQualifiedDomainName, we want the results to come
  // back ordered by name, so we are still in the same boat as
  // searchByDomainNameWithInitialString, unable to perform an inequality query on deletion time.
  // Don't use queryItems, because it doesn't handle pending deletes.
  int querySizeLimit = RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize;
  Query<DomainBase> query =
      ofy()
          .load()
          .type(DomainBase.class)
          .filter("tld", tld);
  if (cursorString.isPresent()) {
    query = query.filter("fullyQualifiedDomainName >", cursorString.get());
  }
  query = query.order("fullyQualifiedDomainName").limit(querySizeLimit);
  return makeSearchResults(getMatchingResources(query, true, querySizeLimit));
}
 
Example 2
Source File: RdapDomainSearchAction.java    From nomulus with Apache License 2.0 6 votes vote down vote up
/**
 * Searches for domains by nameserver address, returning a JSON array of domain info maps.
 *
 * <p>This is a two-step process: get a list of host references by IP address, and then look up
 * domains by host reference.
 *
 * <p>In theory, we could have any number of hosts using the same IP address. To make sure we get
 * all the associated domains, we have to retrieve all of them, and use them to look up domains.
 * This could open us up to a kind of DoS attack if huge number of hosts are defined on a single
 * IP. To avoid this, fetch only the first {@link #maxNameserversInFirstStage} nameservers. In all
 * normal circumstances, this should be orders of magnitude more than there actually are. But it
 * could result in us missing some domains.
 *
 * <p>The includeDeleted parameter does NOT cause deleted nameservers to be searched, only deleted
 * domains which used to be connected to an undeleted nameserver.
 */
private DomainSearchResponse searchByNameserverIp(final InetAddress inetAddress) {
  Query<HostResource> query =
      queryItems(
          HostResource.class,
          "inetAddresses",
          inetAddress.getHostAddress(),
          Optional.empty(),
          Optional.empty(),
          DeletedItemHandling.EXCLUDE,
          maxNameserversInFirstStage);
  Optional<String> desiredRegistrar = getDesiredRegistrar();
  if (desiredRegistrar.isPresent()) {
    query = query.filter("currentSponsorClientId", desiredRegistrar.get());
  }
  return searchByNameserverRefs(
      StreamSupport.stream(query.keys().spliterator(), false)
          .map(key -> VKey.from(key))
          .collect(toImmutableSet()));
}
 
Example 3
Source File: FeedbackResponsesDb.java    From teammates with GNU General Public License v2.0 6 votes vote down vote up
/**
 * Deletes responses using {@link AttributesDeletionQuery}.
 */
public void deleteFeedbackResponses(AttributesDeletionQuery query) {
    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, query);

    Query<FeedbackResponse> entitiesToDelete = load().project();
    if (query.isCourseIdPresent()) {
        entitiesToDelete = entitiesToDelete.filter("courseId =", query.getCourseId());
    }
    if (query.isFeedbackSessionNamePresent()) {
        entitiesToDelete = entitiesToDelete.filter("feedbackSessionName =", query.getFeedbackSessionName());
    }
    if (query.isQuestionIdPresent()) {
        entitiesToDelete = entitiesToDelete.filter("feedbackQuestionId =", query.getQuestionId());
    }

    deleteEntity(entitiesToDelete.keys().list().toArray(new Key<?>[0]));
}
 
Example 4
Source File: FeedbackResponseCommentsDb.java    From teammates with GNU General Public License v2.0 6 votes vote down vote up
/**
 * Deletes comments using {@link AttributesDeletionQuery}.
 */
public void deleteFeedbackResponseComments(AttributesDeletionQuery query) {
    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, query);

    Query<FeedbackResponseComment> entitiesToDelete = load().project();
    if (query.isCourseIdPresent()) {
        entitiesToDelete = entitiesToDelete.filter("courseId =", query.getCourseId());
    }
    if (query.isFeedbackSessionNamePresent()) {
        entitiesToDelete = entitiesToDelete.filter("feedbackSessionName =", query.getFeedbackSessionName());
    }
    if (query.isQuestionIdPresent()) {
        entitiesToDelete = entitiesToDelete.filter("feedbackQuestionId =", query.getQuestionId());
    }
    if (query.isResponseIdPresent()) {
        entitiesToDelete = entitiesToDelete.filter("feedbackResponseId =", query.getResponseId());
    }

    List<Key<FeedbackResponseComment>> keysToDelete = entitiesToDelete.keys().list();

    deleteDocument(Const.SearchIndex.FEEDBACK_RESPONSE_COMMENT,
            keysToDelete.stream().map(key -> String.valueOf(key.getId())).toArray(String[]::new));
    deleteEntity(keysToDelete.toArray(new Key<?>[0]));
}
 
Example 5
Source File: RdapSearchActionBase.java    From nomulus with Apache License 2.0 6 votes vote down vote up
/**
 * Handles searches using a simple string rather than an {@link RdapSearchPattern}.
 *
 * <p>Since the filter is not an inequality, we can support also checking a cursor string against
 * a different field (which involves an inequality on that field).
 *
 * @param clazz the type of resource to be queried
 * @param filterField the database field of interest
 * @param queryString the search string
 * @param cursorField the field which should be compared to the cursor string, or empty() if the
 *        key should be compared to a key created from the cursor string
 * @param cursorString if a cursor is present, this parameter should specify the cursor string, to
 *        skip any results up to and including the string; empty() if there is no cursor
 * @param deletedItemHandling whether to include or exclude deleted items
 * @param resultSetMaxSize the maximum number of results to return
 * @return the query object
 */
static <T extends EppResource> Query<T> queryItems(
    Class<T> clazz,
    String filterField,
    String queryString,
    Optional<String> cursorField,
    Optional<String> cursorString,
    DeletedItemHandling deletedItemHandling,
    int resultSetMaxSize) {
  if (queryString.length() < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
    throw new UnprocessableEntityException(
        String.format(
            "Initial search string must be at least %d characters",
            RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
  }
  Query<T> query = ofy().load().type(clazz).filter(filterField, queryString);
  if (cursorString.isPresent()) {
    if (cursorField.isPresent()) {
      query = query.filter(cursorField.get() + " >", cursorString.get());
    } else {
      query = query.filterKey(">", Key.create(clazz, cursorString.get()));
    }
  }
  return setOtherQueryAttributes(query, deletedItemHandling, resultSetMaxSize);
}
 
Example 6
Source File: FeedbackSessionsDb.java    From teammates with GNU General Public License v2.0 5 votes vote down vote up
/**
 * Deletes sessions using {@link AttributesDeletionQuery}.
 */
public void deleteFeedbackSessions(AttributesDeletionQuery query) {
    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, query);

    Query<FeedbackSession> entitiesToDelete = load().project();
    if (query.isCourseIdPresent()) {
        entitiesToDelete = entitiesToDelete.filter("courseId =", query.getCourseId());
    }

    deleteEntity(entitiesToDelete.keys().list().toArray(new Key<?>[0]));
}
 
Example 7
Source File: RdapDomainSearchAction.java    From nomulus with Apache License 2.0 5 votes vote down vote up
/** Searches for domains by domain name with an initial string, wildcard and possible suffix. */
private DomainSearchResponse searchByDomainNameWithInitialString(
    final RdapSearchPattern partialStringQuery) {
  // We can't query for undeleted domains as part of the query itself; that would require an
  // inequality query on deletion time, and we are already using inequality queries on
  // fullyQualifiedDomainName. So we instead pick an arbitrary limit of
  // RESULT_SET_SIZE_SCALING_FACTOR times the result set size limit, fetch up to that many, and
  // weed out all deleted domains. If there still isn't a full result set's worth of domains, we
  // give up and return just the ones we found. Don't use queryItems, because it checks that the
  // initial string is at least a certain length, which we don't need in this case. Query the
  // domains directly, rather than the foreign keys, because then we have an index on TLD if we
  // need it.
  int querySizeLimit = RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize;
  Query<DomainBase> query =
      ofy()
          .load()
          .type(DomainBase.class)
          .filter("fullyQualifiedDomainName <", partialStringQuery.getNextInitialString())
          .filter("fullyQualifiedDomainName >=", partialStringQuery.getInitialString());
  if (cursorString.isPresent()) {
    query = query.filter("fullyQualifiedDomainName >", cursorString.get());
  }
  if (partialStringQuery.getSuffix() != null) {
    query = query.filter("tld", partialStringQuery.getSuffix());
  }
  query = query.limit(querySizeLimit);
  // Always check for visibility, because we couldn't look at the deletionTime in the query.
  return makeSearchResults(getMatchingResources(query, true, querySizeLimit));
}
 
Example 8
Source File: FeedbackQuestionsDb.java    From teammates with GNU General Public License v2.0 5 votes vote down vote up
/**
 * Deletes questions using {@link AttributesDeletionQuery}.
 */
public void deleteFeedbackQuestions(AttributesDeletionQuery query) {
    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, query);

    Query<FeedbackQuestion> entitiesToDelete = load().project();
    if (query.isCourseIdPresent()) {
        entitiesToDelete = entitiesToDelete.filter("courseId =", query.getCourseId());
    }
    if (query.isFeedbackSessionNamePresent()) {
        entitiesToDelete = entitiesToDelete.filter("feedbackSessionName =", query.getFeedbackSessionName());
    }

    deleteEntity(entitiesToDelete.keys().list().toArray(new Key<?>[0]));
}
 
Example 9
Source File: ConferenceQueryForm.java    From ud859 with GNU General Public License v3.0 5 votes vote down vote up
/**
 * Returns an Objectify Query object for the specified filters.
 *
 * @return an Objectify Query.
 */
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Query<Conference> getQuery() {
    // First check the feasibility of inequality filters.
    checkFilters();
    Query<Conference> query = ofy().load().type(Conference.class);
    if (inequalityFilter == null) {
        // Order by name.
        query = query.order("name");
    } else {
        // If we have any inequality filters, order by the field first.
        query = query.order(inequalityFilter.field.getFieldName());
        query = query.order("name");
    }
    for (Filter filter : this.filters) {
        // Applies filters in order.
        if (filter.field.fieldType == FieldType.STRING) {
            query = query.filter(String.format("%s %s", filter.field.getFieldName(),
                    filter.operator.getQueryOperator()), filter.value);
        } else if (filter.field.fieldType == FieldType.INTEGER) {
            query = query.filter(String.format("%s %s", filter.field.getFieldName(),
                    filter.operator.getQueryOperator()), Integer.parseInt(filter.value));
        }
    }
    LOG.info(query.toString());
    return query;
}
 
Example 10
Source File: ConferenceQueryForm.java    From ud859 with GNU General Public License v3.0 5 votes vote down vote up
/**
 * Returns an Objectify Query object for the specified filters.
 *
 * @return an Objectify Query.
 */
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Query<Conference> getQuery() {
    // First check the feasibility of inequality filters.
    checkFilters();
    Query<Conference> query = ofy().load().type(Conference.class);
    if (inequalityFilter == null) {
        // Order by name.
        query = query.order("name");
    } else {
        // If we have any inequality filters, order by the field first.
        query = query.order(inequalityFilter.field.getFieldName());
        query = query.order("name");
    }
    for (Filter filter : this.filters) {
        // Applies filters in order.
        if (filter.field.fieldType == FieldType.STRING) {
            query = query.filter(String.format("%s %s", filter.field.getFieldName(),
                    filter.operator.getQueryOperator()), filter.value);
        } else if (filter.field.fieldType == FieldType.INTEGER) {
            query = query.filter(String.format("%s %s", filter.field.getFieldName(),
                    filter.operator.getQueryOperator()), Integer.parseInt(filter.value));
        }
    }
    LOG.info(query.toString());
    return query;
}
 
Example 11
Source File: ConferenceQueryForm.java    From ud859 with GNU General Public License v3.0 5 votes vote down vote up
/**
 * Returns an Objectify Query object for the specified filters.
 *
 * @return an Objectify Query.
 */
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Query<Conference> getQuery() {
    // First check the feasibility of inequality filters.
    checkFilters();
    Query<Conference> query = ofy().load().type(Conference.class);
    if (inequalityFilter == null) {
        // Order by name.
        query = query.order("name");
    } else {
        // If we have any inequality filters, order by the field first.
        query = query.order(inequalityFilter.field.getFieldName());
        query = query.order("name");
    }
    for (Filter filter : this.filters) {
        // Applies filters in order.
        if (filter.field.fieldType == FieldType.STRING) {
            query = query.filter(String.format("%s %s", filter.field.getFieldName(),
                    filter.operator.getQueryOperator()), filter.value);
        } else if (filter.field.fieldType == FieldType.INTEGER) {
            query = query.filter(String.format("%s %s", filter.field.getFieldName(),
                    filter.operator.getQueryOperator()), Integer.parseInt(filter.value));
        }
    }
    LOG.info(query.toString());
    return query;
}
 
Example 12
Source File: ConferenceQueryForm.java    From ud859 with GNU General Public License v3.0 5 votes vote down vote up
/**
 * Returns an Objectify Query object for the specified filters.
 *
 * @return an Objectify Query.
 */
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Query<Conference> getQuery() {
    // First check the feasibility of inequality filters.
    checkFilters();
    Query<Conference> query = ofy().load().type(Conference.class);
    if (inequalityFilter == null) {
        // Order by name.
        query = query.order("name");
    } else {
        // If we have any inequality filters, order by the field first.
        query = query.order(inequalityFilter.field.getFieldName());
        query = query.order("name");
    }
    for (Filter filter : this.filters) {
        // Applies filters in order.
        if (filter.field.fieldType == FieldType.STRING) {
            query = query.filter(String.format("%s %s", filter.field.getFieldName(),
                    filter.operator.getQueryOperator()), filter.value);
        } else if (filter.field.fieldType == FieldType.INTEGER) {
            query = query.filter(String.format("%s %s", filter.field.getFieldName(),
                    filter.operator.getQueryOperator()), Integer.parseInt(filter.value));
        }
    }
    LOG.info(query.toString());
    return query;
}
 
Example 13
Source File: RdapSearchActionBase.java    From nomulus with Apache License 2.0 5 votes vote down vote up
static <T extends EppResource> Query<T> setOtherQueryAttributes(
    Query<T> query, DeletedItemHandling deletedItemHandling, int resultSetMaxSize) {
  if (deletedItemHandling != DeletedItemHandling.INCLUDE) {
    query = query.filter("deletionTime", END_OF_TIME);
  }
  return query.limit(resultSetMaxSize);
}
 
Example 14
Source File: RdapSearchActionBase.java    From nomulus with Apache License 2.0 5 votes vote down vote up
/**
 * Handles prefix searches in cases where, if we need to filter out deleted items, there are no
 * pending deletes.
 *
 * <p>In such cases, it is sufficient to check whether {@code deletionTime} is equal to
 * {@code END_OF_TIME}, because any other value means it has already been deleted. This allows us
 * to use an equality query for the deletion time.
 *
 * @param clazz the type of resource to be queried
 * @param filterField the database field of interest
 * @param partialStringQuery the details of the search string; if there is no wildcard, an
 *        equality query is used; if there is a wildcard, a range query is used instead; the
 *        initial string should not be empty, and any search suffix will be ignored, so the caller
 *        must filter the results if a suffix is specified
 * @param cursorString if a cursor is present, this parameter should specify the cursor string, to
 *        skip any results up to and including the string; empty() if there is no cursor
 * @param deletedItemHandling whether to include or exclude deleted items
 * @param resultSetMaxSize the maximum number of results to return
 * @return the query object
 */
static <T extends EppResource> Query<T> queryItems(
    Class<T> clazz,
    String filterField,
    RdapSearchPattern partialStringQuery,
    Optional<String> cursorString,
    DeletedItemHandling deletedItemHandling,
    int resultSetMaxSize) {
  if (partialStringQuery.getInitialString().length()
      < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
    throw new UnprocessableEntityException(
        String.format(
            "Initial search string must be at least %d characters",
            RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
  }
  Query<T> query = ofy().load().type(clazz);
  if (!partialStringQuery.getHasWildcard()) {
    query = query.filter(filterField, partialStringQuery.getInitialString());
  } else {
    // Ignore the suffix; the caller will need to filter on the suffix, if any.
    query = query
        .filter(filterField + " >=", partialStringQuery.getInitialString())
        .filter(filterField + " <", partialStringQuery.getNextInitialString());
  }
  if (cursorString.isPresent()) {
    query = query.filter(filterField + " >", cursorString.get());
  }
  return setOtherQueryAttributes(query, deletedItemHandling, resultSetMaxSize);
}
 
Example 15
Source File: RdapDomainSearchAction.java    From nomulus with Apache License 2.0 5 votes vote down vote up
/**
 * Assembles a list of {@link HostResource} keys by name.
 *
 * <p>Nameserver query strings with wildcards are allowed to have a suffix after the wildcard,
 * which must be a domain. If the domain is not specified, or is not an existing domain in one of
 * our TLDs, the wildcard must be preceded by at least two characters (e.g. "ns*"), to avoid
 * queries for all nameservers in the system. If the suffix specifies an existing domain, the
 * initial string is not required (e.g. "*.example.tld" is valid), because we can look up the
 * domain and just list all of its subordinate hosts.
 */
private Iterable<VKey<HostResource>> getNameserverRefsByLdhName(
    final RdapSearchPattern partialStringQuery) {
  // Handle queries without a wildcard.
  if (!partialStringQuery.getHasWildcard()) {
    return getNameserverRefsByLdhNameWithoutWildcard(partialStringQuery);
  }
  // Handle queries with a wildcard and suffix (specifying a suprerordinate domain).
  if (partialStringQuery.getSuffix() != null) {
    return getNameserverRefsByLdhNameWithSuffix(partialStringQuery);
  }
  // If there's no suffix, query the host resources. Query the resources themselves, rather than
  // the foreign key indexes, because then we have an index on fully qualified host name and
  // deletion time, so we can check the deletion status in the query itself. The initial string
  // must be present, to avoid querying every host in the system. This restriction is enforced by
  // {@link queryItems}.
  //
  // Only return the first maxNameserversInFirstStage nameservers. This could result in an
  // incomplete result set if a search asks for something like "ns*", but we need to enforce a
  // limit in order to avoid arbitrarily long-running queries.
  Query<HostResource> query =
      queryItems(
          HostResource.class,
          "fullyQualifiedHostName",
          partialStringQuery,
          Optional.empty(),
          DeletedItemHandling.EXCLUDE,
          maxNameserversInFirstStage);
  Optional<String> desiredRegistrar = getDesiredRegistrar();
  if (desiredRegistrar.isPresent()) {
    query = query.filter("currentSponsorClientId", desiredRegistrar.get());
  }
  return StreamSupport.stream(query.keys().spliterator(), false)
      .map(key -> VKey.from(key))
      .collect(toImmutableSet());
}
 
Example 16
Source File: RdapSearchActionBase.java    From nomulus with Apache License 2.0 4 votes vote down vote up
/**
 * Runs the given query, and checks for permissioning if necessary.
 *
 * @param query an already-defined query to be run; a filter on currentSponsorClientId will be
 *     added if appropriate
 * @param checkForVisibility true if the results should be checked to make sure they are visible;
 *     normally this should be equal to the shouldIncludeDeleted setting, but in cases where the
 *     query could not check deletion status (due to Datastore limitations such as the limit of
 *     one field queried for inequality, for instance), it may need to be set to true even when
 *     not including deleted records
 * @param querySizeLimit the maximum number of items the query is expected to return, usually
 *     because the limit has been set
 * @return an {@link RdapResultSet} object containing the list of resources and an incompleteness
 *     warning flag, which is set to MIGHT_BE_INCOMPLETE iff any resources were excluded due to
 *     lack of visibility, and the resulting list of resources is less than the maximum allowable,
 *     and the number of items returned by the query is greater than or equal to the maximum
 *     number we might have expected
 */
<T extends EppResource> RdapResultSet<T> getMatchingResources(
    Query<T> query, boolean checkForVisibility, int querySizeLimit) {
  Optional<String> desiredRegistrar = getDesiredRegistrar();
  if (desiredRegistrar.isPresent()) {
    query = query.filter("currentSponsorClientId", desiredRegistrar.get());
  }
  if (!checkForVisibility) {
    return RdapResultSet.create(query.list());
  }
  // If we are including deleted resources, we need to check that we're authorized for each one.
  List<T> resources = new ArrayList<>();
  int numResourcesQueried = 0;
  boolean someExcluded = false;
  for (T resource : query) {
    if (shouldBeVisible(resource)) {
      resources.add(resource);
    } else {
      someExcluded = true;
    }
    numResourcesQueried++;
    if (resources.size() > rdapResultSetMaxSize) {
      break;
    }
  }
  // The incompleteness problem comes about because we don't know how many items to fetch. We want
  // to return rdapResultSetMaxSize worth of items, but some might be excluded, so we fetch more
  // just in case. But how many more? That's the potential problem, addressed with the three way
  // AND statement:
  // 1. If we didn't exclude any items, then we can't have the incompleteness problem.
  // 2. If have a full result set batch (rdapResultSetMaxSize items), we must by definition be
  //    giving the user a complete result set.
  // 3. If we started with fewer than querySizeLimit items, then there weren't any more items that
  //    we missed. Even if we return fewer than rdapResultSetMaxSize items, it isn't because we
  //    didn't fetch enough to start.
  // Only if all three conditions are true might things be incomplete. In other words, we fetched
  // as many as our limit allowed, but then excluded so many that we wound up with less than a
  // full result set's worth of results.
  return RdapResultSet.create(
      resources,
      (someExcluded
              && (resources.size() < rdapResultSetMaxSize)
              && (numResourcesQueried >= querySizeLimit))
          ? IncompletenessWarningType.MIGHT_BE_INCOMPLETE
          : IncompletenessWarningType.COMPLETE,
      numResourcesQueried);
}
 
Example 17
Source File: RdapEntitySearchAction.java    From nomulus with Apache License 2.0 4 votes vote down vote up
/**
 * Searches for entities by name, returning a JSON array of entity info maps.
 *
 * <p>As per Gustavo Lozano of ICANN, registrar name search should be by registrar name only, not
 * by registrar contact name:
 *
 * <p>The search is by registrar name only. The profile is supporting the functionality defined in
 * the Base Registry Agreement.
 *
 * <p>According to RFC 7482 section 6.1, punycode is only used for domain name labels, so we can
 * assume that entity names are regular unicode.
 *
 * <p>The includeDeleted flag is ignored when searching for contacts, because contact names are
 * set to null when the contact is deleted, so a deleted contact can never have a name.
 *
 * <p>Since we are restricting access to contact names, we don't want name searches to return
 * contacts whose names are not visible. That would allow unscrupulous users to query by name and
 * infer that all returned contacts contain that name string. So we check the authorization level
 * to determine what to do.
 *
 * @see <a
 *     href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-09jan14-en.htm">1.6
 *     of Section 4 of the Base Registry Agreement</a>
 */
private EntitySearchResponse searchByName(
    final RdapSearchPattern partialStringQuery,
    CursorType cursorType,
    Optional<String> cursorQueryString,
    Subtype subtype) {
  // Don't allow wildcard suffixes when searching for entities.
  if (partialStringQuery.getHasWildcard() && (partialStringQuery.getSuffix() != null)) {
    throw new UnprocessableEntityException(
        partialStringQuery.getHasWildcard()
            ? "Suffixes not allowed in wildcard entity name searches"
            : "Suffixes not allowed when searching for deleted entities");
  }
  // For wildcards, make sure the initial string is long enough, except in the special case of
  // searching for all registrars, where we aren't worried about inefficient searches.
  if (partialStringQuery.getHasWildcard()
      && (subtype != Subtype.REGISTRARS)
      && (partialStringQuery.getInitialString().length()
          < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH)) {
    throw new UnprocessableEntityException(
        partialStringQuery.getHasWildcard()
            ? "Initial search string required in wildcard entity name searches"
            : "Initial search string required when searching for deleted entities");
  }
  // Get the registrar matches. If we have a registrar cursor, weed out registrars up to and
  // including the one we ended with last time. We can skip registrars if subtype is CONTACTS.
  ImmutableList<Registrar> registrars;
  if (subtype == Subtype.CONTACTS) {
    registrars = ImmutableList.of();
  } else {
    registrars =
        Streams.stream(Registrar.loadAllCached())
            .sorted(
                Comparator.comparing(Registrar::getRegistrarName, String.CASE_INSENSITIVE_ORDER))
            .filter(
                registrar ->
                    partialStringQuery.matches(registrar.getRegistrarName())
                        && ((cursorType != CursorType.REGISTRAR)
                            || (registrar.getRegistrarName().compareTo(cursorQueryString.get())
                                > 0))
                        && shouldBeVisible(registrar))
            .limit(rdapResultSetMaxSize + 1)
            .collect(toImmutableList());
  }
  // Get the contact matches and return the results, fetching an additional contact to detect
  // truncation. Don't bother searching for contacts by name if the request would not be able to
  // see any names anyway. Also, if a registrar cursor is present, we have already moved past the
  // contacts, and don't need to fetch them this time. We can skip contacts if subtype is
  // REGISTRARS.
  RdapResultSet<ContactResource> resultSet;
  if (subtype == Subtype.REGISTRARS) {
    resultSet = RdapResultSet.create(ImmutableList.of());
  } else {
    if ((rdapAuthorization.role() == RdapAuthorization.Role.PUBLIC)
        || (cursorType == CursorType.REGISTRAR)) {
      resultSet = RdapResultSet.create(ImmutableList.of());
    } else {
      Query<ContactResource> query =
          queryItems(
              ContactResource.class,
              "searchName",
              partialStringQuery,
              cursorQueryString, // if we get this far, and there's a cursor, it must be a contact
              DeletedItemHandling.EXCLUDE,
              rdapResultSetMaxSize + 1);
      if (rdapAuthorization.role() != RdapAuthorization.Role.ADMINISTRATOR) {
        query = query.filter("currentSponsorClientId in", rdapAuthorization.clientIds());
      }
      resultSet = getMatchingResources(query, false, rdapResultSetMaxSize + 1);
    }
  }
  return makeSearchResults(resultSet, registrars, QueryType.FULL_NAME);
}