Package com.google.appengine.demos.search

Source Code of com.google.appengine.demos.search.TextSearchServlet

// Copyright 2011 Google Inc. All Rights Reserved.

package com.google.appengine.demos.search;

import com.google.appengine.api.search.Document;
import com.google.appengine.api.search.Field;
import com.google.appengine.api.search.Index;
import com.google.appengine.api.search.IndexSpec;
import com.google.appengine.api.search.OperationResult;
import com.google.appengine.api.search.Query;
import com.google.appengine.api.search.QueryOptions;
import com.google.appengine.api.search.Results;
import com.google.appengine.api.search.DeleteException;
import com.google.appengine.api.search.ScoredDocument;
import com.google.appengine.api.search.SearchServiceFactory;
import com.google.appengine.api.search.StatusCode;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* A demo servlet showing basic text search capabilities. This servlet
* has a single index shared between all users. It illustrates how to
* add, search for and remove documents from the shared index.
*
*/
public class TextSearchServlet extends HttpServlet {

  private static final long serialVersionUID = 1L;

  private static final String VOID_REMOVE =
      "Remove failed due to a null doc ID";

  private static final String VOID_ADD =
      "Document not added due to empty content";

  /**
   * The index used by this application. Since we only have one index
   * we create one instance only. We build an index with the default
   * consistency, which is Consistency.PER_DOCUMENT. These types of
   * indexes are most suitable for streams and feeds, and can cope with
   * a high rate of updates.
   */
  private static final Index INDEX = SearchServiceFactory.getSearchService()
      .getIndex(IndexSpec.newBuilder().setName("shared_index"));

  enum Action {
    ADD, REMOVE, DEFAULT;
  }

  private static final Logger LOG = Logger.getLogger(
      TextSearchServlet.class.getName());

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {
    User currentUser = setupUser(req);
    String outcome = null;
    switch (getAction(req)) {
      case ADD:
        outcome = add(req, currentUser);
        break;
      case REMOVE:
        outcome = remove(req);
        break;
      // On DEFAULT we fall through and just execute search below.
    }
    String searchOutcome = search(req);
    if (outcome == null) {
      outcome = searchOutcome;
    }
    req.setAttribute("outcome", outcome);
    req.getRequestDispatcher("index.jsp").forward(req, resp);
  }

  private User setupUser(HttpServletRequest req) {
    UserService userService = UserServiceFactory.getUserService();
    User currentUser = userService.getCurrentUser();
    if (currentUser != null) {
      req.setAttribute("authAction", "sign out");
      req.setAttribute("authUrl",
          userService.createLogoutURL(req.getRequestURI()));
    } else {
      currentUser = new User("nobody@example.com", "example.com");
      req.setAttribute("authAction", "sign in");
      req.setAttribute("authUrl",
          userService.createLoginURL(req.getRequestURI()));
    }
    req.setAttribute("nickname", currentUser.getNickname());
    return currentUser;
  }

  /**
   * Indexes a document built from the current request on behalf of the
   * specified user. Each document has three fields in it. The content
   * field stores used entered text. The email, and domain are extracted
   * from the current user.
   */
  private String add(HttpServletRequest req, User currentUser) {
    String content = req.getParameter("doc");
    if (content == null || content.isEmpty()) {
      LOG.warning(VOID_ADD);
      return VOID_ADD;
    }
    String ratingStr = req.getParameter("rating");
    int rating = 1;
    if (ratingStr != null) {
      rating = Integer.parseInt(ratingStr);
    }
    Document.Builder docBuilder = Document.newBuilder()
        .addField(Field.newBuilder().setName("content").setText(content))
        .addField(Field.newBuilder().setName("email")
            .setText(currentUser.getEmail()))
        .addField(Field.newBuilder().setName("domain")
            .setText(currentUser.getAuthDomain()))
        .addField(Field.newBuilder().setName("published").setDate(
            Field.date(new Date())))
        .addField(Field.newBuilder().setName("rating")
            .setNumber(rating));
    String tagStr = req.getParameter("tags");
    if (tagStr != null) {
      StringTokenizer tokenizer = new StringTokenizer(tagStr, ",");
      while (tokenizer.hasMoreTokens()) {
        docBuilder.addField(Field.newBuilder().setName("tag")
            .setAtom(tokenizer.nextToken()));
      }
    }
    Document doc = docBuilder.build();
    LOG.info("Adding document:\n" + doc.toString());
    try {
      INDEX.put(doc);
      return "Document added";
    } catch (RuntimeException e) {
      LOG.log(Level.SEVERE, "Failed to add " + doc, e);
      return "Document not added due to an error " + e.getMessage();
    }
  }

  private String getOnlyField(Document doc, String fieldName, String defaultValue) {
    if (doc.getFieldCount(fieldName) == 1) {
      return doc.getOnlyField(fieldName).getText();
    }
    LOG.severe("Field " + fieldName + " present " + doc.getFieldCount(fieldName));
    return defaultValue;
  }

  /**
   * Searches the index for matching documents. If the query is not specified
   * in the request, we search for any documents.
   */
  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;
  }

  /**
   * Removes documents with IDs specified in the given request. In the demo
   * application we do not perform any authorization checks, thus no user
   * information is necessary.
   */
  private String remove(HttpServletRequest req) {
    String[] docIds = req.getParameterValues("docid");
    if (docIds == null) {
      LOG.warning(VOID_REMOVE);
      return VOID_REMOVE;
    }
    List<String> docIdList = Arrays.asList(docIds);
    try {
      INDEX.delete(docIdList);
      return "Documents " + docIdList + " removed";
    } catch (DeleteException e) {
      List<String> failedIds = findFailedIds(docIdList, e.getResults());
      LOG.log(Level.SEVERE, "Failed to remove documents " + failedIds, e);
      return "Remove failed for " + failedIds;
    }
  }

  /**
   * A convenience method that correlates document status to the document ID.
   */
  private List<String> findFailedIds(List<String> docIdList,
      List<OperationResult> results) {
    List<String> failedIds = new ArrayList<String>();
    Iterator<OperationResult> opIter = results.iterator();
    Iterator<String> idIter = docIdList.iterator();
    while (opIter.hasNext() && idIter.hasNext()) {
      OperationResult result = opIter.next();
      String docId = idIter.next();
      if (!StatusCode.OK.equals(result.getCode())) {
        failedIds.add(docId);
      }
    }
    return failedIds;
  }

  /**
   * Extracts the type of action stored in the request. We have only three
   * types of actions: ADD, REMOVE and DEFAULT. The DEFAULT is included
   * to indicate action other than ADD or REMOVE. We do not have a special
   * acton for search, as we always execute search. This way we show documents
   * that match terms entered in the search box, regardless of the operation.
   *
   * @param HTTP request received by the servlet
   * @return the requested user action, as inferred from the request
   */
  private Action getAction(HttpServletRequest req) {
    if (req.getParameter("index") != null) {
      return Action.ADD;
    }
    if (req.getParameter("delete") != null) {
      return Action.REMOVE;
    }
    return Action.DEFAULT;
  }
}
TOP

Related Classes of com.google.appengine.demos.search.TextSearchServlet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.