/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

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