private String search(HttpServletRequest req) {
    String queryStr = req.getParameter("query");
    if (queryStr == null) {
      queryStr = "";
    }
    String limitStr = req.getParameter("limit");
    int limit = 10;
    if (limitStr != null) {
      try {
        limit = Integer.parseInt(limitStr);
      } catch (NumberFormatException e) {
        LOG.severe("Failed to parse " + limitStr);
      }
    }
    List<Document> found = new ArrayList<Document>();
    String outcome = null;
    try {
      // Rather than just using a query we build a search request.
      // This allows us to specify other attributes, such as the
      // number of documents to be returned by search.
      Query query = Query.newBuilder()
          .setOptions(QueryOptions.newBuilder()
              .setLimit(limit).
              // for deployed apps, uncomment the line below to demo snippeting.
              // This will not work on the dev_appserver.
              // setFieldsToSnippet("content").
              build())
          .build(queryStr);
      LOG.info("Sending query " + query);
      Results<ScoredDocument> results = INDEX.search(query);
      for (ScoredDocument scoredDoc : results) {
        User author = new User(
            getOnlyField(scoredDoc, "email", "user"),
            getOnlyField(scoredDoc, "domain", "example.com"));
        // Rather than presenting the original document to the
        // user, we build a derived one that holds author's nickname.
        List<Field> expressions = scoredDoc.getExpressions();
        String content = null;
        if (expressions != null) {
          for (Field field : expressions) {
            if ("content".equals(field.getName())) {
              content = field.getHTML();
              break;
            }
          }
        }
        if (content == null) {
          content = getOnlyField(scoredDoc, "content", "");
        }
        Document derived = Document.newBuilder()
            .setId(scoredDoc.getId())
            .addField(Field.newBuilder().setName("content").setText(content))
            .addField(Field.newBuilder().setName("nickname").setText(
                author.getNickname()))
            .addField(Field.newBuilder().setName("published").setDate(
                scoredDoc.getOnlyField("published").getDate()))
            .build();
        found.add(derived);
      }
    } catch (RuntimeException e) {
      LOG.log(Level.SEVERE, "Search with query '" + queryStr + "' failed", e);
      outcome = "Search failed due to an error: " + e.getMessage();
    }
    req.setAttribute("found", found);
    return outcome;
  }