PageRenderTime 48ms CodeModel.GetById 12ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 0ms

/testing/library/src/com/google/appengine/library/Library.java

http://datanucleus-appengine.googlecode.com/
Java | 472 lines | 363 code | 35 blank | 74 comment | 52 complexity | 8ad6b9fee26a193cd6697c535c0d8cf7 MD5 | raw file
  1package com.google.appengine.library;
  2
  3import com.google.appengine.library.Util.NullToEmptyMapWrapper;
  4
  5import java.io.IOException;
  6import java.io.PrintWriter;
  7import java.util.Date;
  8import java.util.logging.Level;
  9import java.util.logging.Logger;
 10
 11import javax.servlet.ServletException;
 12import javax.servlet.http.HttpServlet;
 13import javax.servlet.http.HttpServletRequest;
 14import javax.servlet.http.HttpServletResponse;
 15
 16/**
 17 * This is a test servlet ported from //apphosting/testing/testshop/library.py.
 18 * I try to maintain the same user interface so the same Selenium tests can run
 19 * for this Java port. The implementation can be different. For example, ORM API
 20 * can be used instead of the Python datastore API.
 21 * 
 22 * There are four action buttons (and a clear form button which is implemented
 23 * in javascript) on the Prometheus Library.
 24 * 
 25 * Import - Updates the working 'Book' entities with copies from the Classics or
 26 * TechBooks catalog as specified by the Catalog form entry.
 27 * 
 28 * Query - Performs a query on 'Book' entities depending on the field values and
 29 * checkbox values.
 30 * 
 31 * Add - Adds a 'Book' entity with values from the lastname, firstname, year and
 32 * title fields.
 33 * 
 34 * Delete - Similar to query, but instead of returning the results of the query,
 35 * it deletes those entities.
 36 * 
 37 * TODO(kjin): use JSP for HTML content.
 38 * 
 39 * @author kjin@google.com (Kevin Jin)
 40 */
 41public class Library extends HttpServlet {
 42  private static final Logger log = Logger.getLogger(Library.class.getName());
 43  static {
 44    log.setLevel(Level.FINE);
 45  }
 46
 47  @Override
 48  public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException,
 49      ServletException {
 50    try {
 51      super.service(req, resp);
 52    } finally {
 53      if (bds != null) {
 54        bds.close();
 55        bds = null;
 56      }
 57    }
 58  }
 59
 60  @Override
 61  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
 62      ServletException {
 63    if (bds == null) {
 64      // doGet may be called by doPost. Two EntityManagers don't sync when using
 65      // JPA.
 66      bds = BookDataServiceFactory.getBookDataService();
 67    }
 68    formFields = Util.wrapFormFields(req);
 69    String query = " WHERE category='test'";
 70    query = handleQueryOrder(query);
 71    render(query, resp);
 72  }
 73
 74  /**
 75   * Renders Each book entity in the query which has been built unless
 76   * displaying count-only has been specified (checkmarked). Also uses getNumber
 77   * if specified to expand test coverage.
 78   * 
 79   * @throws IOException
 80   */
 81  private void render(String query, HttpServletResponse resp)
 82      throws IOException {
 83    PrintWriter out = resp.getWriter();
 84
 85    out.println(HEADER);
 86    if (formFields.get("DispCountOnly").equals("countonly")) {
 87      int total = bds.countEntities(query);
 88      out.println(" <p class=\"entry\"> Found " + total + " books </p> ");
 89    } else {
 90      int limit = Integer.MAX_VALUE;
 91      if (formFields.get("UseGetQuery").equals("UseGet")) {
 92        limit = Integer.parseInt(formFields.get("getNumber"));
 93      }
 94
 95      for (Book book : bds.asIterable(query, limit, 0)) {
 96        out.println(getBookHtml(book));
 97      }
 98    }
 99    out.println(FOOTER);
100  }
101
102  @Override
103  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException,
104      ServletException {
105    bds = BookDataServiceFactory.getBookDataService();
106    formFields = Util.wrapFormFields(req);
107    String action_type = formFields.get("action_type");
108
109    // Handle any buttons that got pushed on the form
110    if (action_type.equals("Add")) {
111      String category = formFields.get("entity");
112      if (category.equals("")) {
113        category = "test";
114      }
115      String title = formFields.get("title");
116      addBook(category, formFields.get("lastname"), formFields.get("firstname"), title,
117          Integer.parseInt(formFields.get("year")),
118          !title.contains("'"));        //hack for ' not supported in JPQL
119      doGet(req, resp);
120    } else if (action_type.equals("Query")) {
121      handleQuery(req, resp, false);
122    } else if (action_type.equals("Delete")) {
123      handleQuery(req, resp, true);
124    } else if (action_type.equals("Import")) {
125      String catalogtype = formFields.get("catalogtype");
126      if (catalogtype.equals("SyntheticSmall")) {
127        createSyntheticCatalog(20);
128      } else if (catalogtype.equals("TechBooks") || catalogtype.equals("Classics")) {
129        copyCatalog(catalogtype, "test");
130      }
131      doGet(req, resp);
132    }
133  }
134
135  /*
136   * We will use a script that calls wget (or curl) to initially populate two
137   * sets of entities called 'Classics', and 'TechBooks'. Pushing the import
138   * button will copy the appropriate entities into Book entities by using this
139   * method to retrieve them from Classics or TechBooks and calling addBook to
140   * put them in Book.
141   */
142  private void copyCatalog(String src, String dest) {
143    long start = System.currentTimeMillis();
144    String query = " WHERE category='" + src + "'";
145    query += " ORDER BY title DESC";
146    long endBuildQuery = System.currentTimeMillis();
147    log.fine("build Query time = " + (endBuildQuery - start));
148    Iterable<Book> it = bds.asIterable(query);
149    long endQuery = System.currentTimeMillis();
150    log.fine("asIterable time = " + (endQuery - endBuildQuery));
151
152    int ii = 0;
153    long startii = System.currentTimeMillis();
154    for (Book book : it) {
155      long endii = System.currentTimeMillis();
156      log.fine("iterator " + (ii++) + " time = " + (endii - startii));
157      startii = endii;
158      addBook(dest, book.getLastname(), book.getFirstname(), book.getTitle(), book.getYear(), false);
159    }
160    long end = System.currentTimeMillis();
161    log.fine("copyCatalog time = " + (end - start));
162  }
163
164  private void createSyntheticCatalog(int count) {
165    for (int i = 0; i < count; i++) {
166      addBook("test", "Last " + i, "First " + i, "Title " + i, 1800 + i % 200, false);
167    }
168  }
169
170  /*
171   * Handle various types of user queries using all of or selected properties.
172   * The form will allow the user to type arithmetic queries in the year field
173   * such as >, >=, <, <= or ==, = No relational operator defaults to =. If the
174   * delete button was pushed instead of query, then delete the specified
175   * entities.
176   */
177  private void handleQuery(HttpServletRequest req, HttpServletResponse resp, boolean delete)
178      throws IOException, ServletException {
179    String query = " WHERE category='test'";
180
181    query = handleInList("lastname", query);
182    query = handleInList("firstname", query);
183
184    query = addFilter("title", query);
185    query = addFilter("year", query);
186
187//    String queryHint = formFields.get("queryHint");
188//    if (queryHint.equals("order_first")) {
189//      query.setHint(Query.Hint.ORDER_FIRST);
190//    } else if (queryHint.equals("ancestor_first")) {
191//      query.setHint(Query.Hint.ANCESTOR_FIRST);
192//    } else if (queryHint.equals("filter_first")) {
193//      query.setHint(Query.Hint.FILTER_FIRST);
194//    }
195
196    if (delete) {
197      // sometimes NPE is thrown at
198      // com.google.apphosting.api.datastore.dev.LocalDatastoreService
199      // .next(LocalDatastoreService.java:659) when using JPA.
200      for (Book book : bds.asIterable(query)) {
201        bds.delete(book);
202      }
203      doGet(req, resp);
204    } else {
205      query = handleQueryOrder(query);
206      render(query, resp);
207    }
208  }
209
210  private String handleQueryOrder(String query) {
211    String selType = formFields.get("selType");
212    if (selType.length() != 0 && !selType.equals("none")) {
213      query +=
214          " ORDER BY " + selType + " " + (formFields.get("selOrder").equals("ascending") ? "ASC" : "DESC");
215
216      String secType = formFields.get("secType");
217      if (secType.length() != 0 && !secType.equals("none")) {
218        query +=
219            ", " + secType + " "
220                + (formFields.get("secOrder").equals("ascending") ? "ASC" : "DESC");
221      }
222    }
223    return query;
224  }
225
226  /*
227   * in operator is not supported, but anyway I ported this function for future
228   * when in operator is supported. Form fields such as lastname or firstname
229   * can contain multiple words which can be used to create queries with the in
230   * operator.
231   * 
232   * query.update is not supported in Java.
233   */
234  private String handleInList(String propertyName, String query) {
235    String UseQueryUpdate = formFields.get("UseQueryUpdate");
236    if (UseQueryUpdate.equals("UseUpdate")) {
237      log.warning("Query.update not supported");
238    }
239    String propertyValue = formFields.get(propertyName);
240    if (propertyValue != null && propertyValue.length() != 0) {
241      query += " AND " + propertyName + "='" + propertyValue + "'";
242    }
243    return query;
244  }
245
246  private String addFilter(String propertyName, String query) {
247    String op = "=";
248    String propertyValue = formFields.get(propertyName);
249    if (propertyValue != null && propertyValue.length() != 0) {
250      if (propertyName.equals("year")) {
251        final String[] operators = {">=", ">", "<=", "<", "==", "="};
252        for (final String operator : operators) {
253          int index = propertyValue.indexOf(operator);
254          if (index != -1) {
255            propertyValue = propertyValue.substring(index + operator.length()).trim();
256            if (!operator.equals("==")) {
257              op = operator;
258            }
259            // hack for ApplicationError: 1: The first sort property must be the
260            // same as the property to which the inequality filter is applied.
261            formFields.put("selType", "year");
262            break;
263          }
264        }
265      } else { // treat as String literal in JPQL
266        propertyValue = "'" + propertyValue + "'";
267      }
268      query += " AND " + propertyName + op + propertyValue;
269    }
270    return query;
271  }
272
273  /**
274   * Puts the Book entity into datastore and does a check for duplicates if
275   * {@code check == true}.
276   */
277  private void addBook(String category, String lastname, String firstname, String title, int year,
278      boolean check) {
279    if (title == null || title.length() == 0) {
280      return;
281    }
282
283    if (check) {
284      String query = " WHERE category='" + category + "' AND title='" + title + "'";
285      if (bds.countEntities(query) > 0) {
286        return;
287      }
288    }
289    
290    long start = System.currentTimeMillis();
291    Book book = new Book(category, new Date(),firstname, lastname, title, year);
292    bds.put(book);
293    log.fine("put time = " + (System.currentTimeMillis()-start));
294}
295  
296  private String getBookHtml(Book book) {
297    return String.format(
298      "<p class=\"entry\"> %s \n" +
299      "<br />\n" +
300      "&nbsp;&nbsp;%s,<i> %s</i>  &copy;%s\n" +
301      "<br /> \n" +
302      "&nbsp;&nbsp;Created on %s\n" +
303      "</p>",
304      book.getLastname(), book.getFirstname(), book.getTitle(), book.getYear(), book
305        .getCreated());
306  }
307
308  private static final String HEADER = ""+
309  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n" +
310  "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" +
311  "<html>\n" +
312  "<head>\n" +
313  "<script type=\"text/javascript\">\n" +
314  "function disable()\n" +
315    "{\n" +
316    "document.getElementById(\"getNumber\").disabled=true\n" +
317    "}\n" +
318  "function enable()\n" +
319    "{\n" +
320    "document.getElementById(\"getNumber\").disabled=false\n" +
321    "}\n" +
322  "</script>\n" +
323  "<title> Prometheus Library </title>\n" +
324  "<style type=\"text/css\">\n" +
325  "body {\n" +
326    "width: 900px;\n" +
327    "margin: 25px;\n" +
328  "}\n" +
329
330  "p.entry, div.form {\n" +
331    "padding: 10px;\n" +
332    "border: 1px solid Navy;\n" +
333    "width: auto;\n" +
334  "}\n" +
335
336  "p.entry { background-color: Ivory; }\n" +
337  "div.form { background-color: LightSteelBlue; }\n" +
338  "</style>\n" +
339  "</head>\n" +
340
341  "<body onload=\"disable()\">\n" +
342  "<div id=\"main\">\n" +
343  "<div id=\"body\">\n" +
344
345  "<h1> Prometheus Library </h1>\n" +
346
347  "<p style=\"font-style: italic\">\n" +
348  "This is an example app for\n" +
349  "<a href=\"http://wiki/Main/Prometheus\"> Prometheus</a>.\n" +
350  "It is based on the sample guestbook app by Ryan Barrett.\n" +
351  "It attempts to use all of datastore types supported by\n" +
352  "the\n" +
353  "<a href=\"http://wiki/Main/PrometheusDatastoreAPI\">\n" +
354  "datastore api.\n" +
355  "</a>\n" +
356  "You can see the items in the datastore by clicking on\n" +
357  "<a href=\"/admin/\">\n" +
358  "the datastore viewer.\n" +
359  "</a>\n" +
360  "<br />\n" +
361  "To run the automated test suite under Selenium Core\n" +
362  "which is also being hosted by Prometheus\n" +
363  "<a href=\"/core/TestRunner.html\"> click here</a>.\n" +
364  "</p>\n" +
365  "<p>\n" +
366  "</p>\n" +
367  "<hr />\n" +
368  "<div class=\"form\">\n" +
369  "<B>Admin or Query the Library</B>\n" +
370  "<p></p>\n" +
371  "<form action=\"/library\" method=\"post\">\n" +
372  "<table>\n" +
373  "<tr><td> Catalog </td>\n" +
374  "<td> <select name=\"catalogtype\">\n" +
375  "<option selected=\"selected\" value=\"Classics\">Select Catalog</option>\n" +
376  "<option value=\"Classics\">Classics</option>\n" +
377  "<option value=\"TechBooks\">Technical Books</option>\n" +
378  "<option value=\"SyntheticSmall\">Synthetically Generated (Small ~20)</option>\n" +
379  "</select> </td>\n" +
380  "<tr></tr>\n" +
381  "<tr><td> Author (Last): </td><td><input type=\"text\" name=\"lastname\" size=\"20\"</td>\n" +
382  "<tr><td> Author (First): </td><td><input type=\"text\" name=\"firstname\" size=\"20\"</td>\n" +
383  "<tr><td> Year: </td><td><input type=\"text\" name=\"year\" size=\"10\"</td>\n" +
384  "<tr><td> ISBN: </td><td><input type=\"text\" name=\"isbn\" value=\"NOT USED\" size=\"10\"" +
385  "</td>\n" +
386  "<tr><td> Title:</td><td><input type=\"text\" name=\"title\" size=\"50\"</td></tr>\n" +
387  "<tr><td> Description: </td><td><textarea name=\"message\" rows=\"10\" cols=\"50\">NOT USED " +
388  "</textarea> </td></tr>\n" +
389  "<tr><td></td>\n" +
390  "<td><input type=\"Reset\" value=\"Reset Form\">\n" +
391  "<input type=\"submit\" value=\"Import\"   name=\"action_type\">\n" +
392  "<input type=\"submit\" value=\"Query\" name=\"action_type\">\n" +
393  "<input type=\"submit\" value=\"Add\"   name=\"action_type\">\n" +
394  "<input type=\"submit\" value=\"Delete\"   name=\"action_type\"></td></tr>\n" +
395  "<tr><td></td>\n" +
396  "<td><select name=\"selType\">\n" +
397  "<option selected=\"selected\" value=\"lastname\">Select Order Key</option>\n" +
398  "<option value=\"lastname\">by Last Name</option>\n" +
399  "<option value=\"firstname\">by First Name</option>\n" +
400  "<option value=\"year\">by Year</option>\n" +
401  "<option value=\"title\">by Title</option>\n" +
402  "<option value=\"created\">by creation date</option>\n" +
403  "</select>\n" +
404  "<select name=\"selOrder\">\n" +
405  "<option selected=\"selected\" value=\"Descending\">Select Query Order</option>\n" +
406  "<option value=\"descending\">Descending</option>\n" +
407  "<option value=\"ascending\">Ascending</option>\n" +
408  "</select>\n" +
409  "</td>\n" +
410  "</tr>\n" +
411  "<tr><td></td>\n" +
412  "</select>\n" +
413  "<td><select name=\"secType\">\n" +
414  "<option selected=\"selected\" value=\"none\">Secondary Order Key</option>\n" +
415  "<option value=\"none\">NONE</option>\n" +
416  "<option value=\"lastname\">by Last Name</option>\n" +
417  "<option value=\"firstname\">by First Name</option>\n" +
418  "<option value=\"year\">by Year</option>\n" +
419  "<option value=\"title\">by Title</option>\n" +
420  "</select>\n" +
421  "<select name=\"secOrder\">\n" +
422  "<option selected=\"selected\" value=\"none\">Secondary Query Order</option>\n" +
423  "<option value=\"none\">NONE</option>\n" +
424  "<option value=\"descending\">Descending</option>\n" +
425  "<option value=\"ascending\">Ascending</option>\n" +
426  "</select>\n" +
427  "<select name=\"queryHint\">\n" +
428  "<option selected=\"selected\" value=\"none\">Query Hint</option>\n" +
429  "<option value=\"none\">NONE</option>\n" +
430  "<option value=\"order_first\">ORDER_FIRST</option>\n" +
431  "<option value=\"ancestor_first\">ANCESTOR_FIRST</option>\n" +
432  "<option value=\"filter_first\">FILTER_FIRST</option>\n" +
433  "</select>\n" +
434  "</td>\n" +
435  "</tr>\n" +
436  "<tr><td></td>\n" +
437  "<td>\n" +
438  "<input type=\"checkbox\" name=\"DispCountOnly\" value=\"countonly\">\n" +
439  "Display Count Only\n" +
440  "</td>\n" +
441  "</tr>\n" +
442  "<tr><td></td>\n" +
443  "<td>\n" +
444  "<input type=\"checkbox\" name=\"UseQueryUpdate\" value=\"UseUpdate\">\n" +
445  "Use Query update method to set property filters\n" +
446  "</td>\n" +
447  "</tr>\n" +
448  "<tr><td></td>\n" +
449  "<td>\n" +
450  "<input type=\"checkBox\" onclick=\"if (this.checked) (enable()); else (disable())\" " +
451  "name=\"UseGetQuery\" value=\"UseGet\" >\n" +
452  "Use Get() instead of Run() for up to\n" +
453  "<select name=\"getNumber\" id=\"getNumber\">\n" +
454  "<option value=\"1\">1</option>\n" +
455  "<option value=\"5\">5</option>\n" +
456  "<option value=\"10\">10</option>\n" +
457  "<option value=\"20\">20</option> \n" +
458  "</select> results\n" +
459  "</td>\n" +
460  "</tr>\n" +
461  "</table>\n" +
462  "</form>\n" +
463  "</div>\n";
464
465  private static final String FOOTER =
466  "</div></div>\n" +
467  "</body>\n" +
468  "</html>\n";
469
470  private NullToEmptyMapWrapper formFields;
471  private BookDataService bds;
472}