/testing/library/src/com/google/appengine/library/DataStoreBookDataService.java
Java | 225 lines | 176 code | 36 blank | 13 comment | 24 complexity | 29b056b1322c2a2b39de1b9eb5984bc5 MD5 | raw file
1// Copyright 2008 Google Inc. All Rights Reserved. 2 3package com.google.appengine.library; 4 5import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit; 6 7import com.google.appengine.api.datastore.DatastoreService; 8import com.google.appengine.api.datastore.DatastoreServiceFactory; 9import com.google.appengine.api.datastore.Entity; 10import com.google.appengine.api.datastore.FetchOptions; 11import com.google.appengine.api.datastore.Query; 12import com.google.appengine.api.datastore.Query.FilterOperator; 13import com.google.appengine.api.datastore.Query.SortDirection; 14 15import java.util.Date; 16import java.util.HashMap; 17import java.util.Iterator; 18import java.util.Map; 19import java.util.StringTokenizer; 20 21/** 22 * @author kjin@google.com (Kevin Jin) 23 */ 24final class DataStoreBookDataService implements BookDataService { 25 26 private final DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService(); 27 28 public Iterable<Book> asIterable(String jpqlQuery) { 29 return new BookIterable(datastoreService.prepare(new QueryConverter().convertQuery(jpqlQuery)) 30 .asIterable()); 31 } 32 33 public Iterable<Book> asIterable(String jpqlQuery, int limit, int offset) { 34 FetchOptions fo = withLimit(limit).offset(offset); 35 return new BookIterable(datastoreService.prepare(new QueryConverter().convertQuery(jpqlQuery)) 36 .asIterable(fo)); 37 } 38 39 public int countEntities(String jpqlQuery) { 40 return datastoreService.prepare(new QueryConverter().convertQuery(jpqlQuery)).countEntities(); 41 } 42 43 public void delete(Book book) { 44 datastoreService.delete(((BookWithEntity) book).e.getKey()); 45 } 46 47 public void put(Book book) { 48 final Entity e; 49 if (book instanceof BookWithEntity) { 50 e = ((BookWithEntity) book).e; 51 } else { 52 e = new Entity("Book"); 53 } 54 e.setProperty("category", book.getCategory()); 55 e.setProperty("lastname", book.getLastname()); 56 e.setProperty("firstname", book.getFirstname()); 57 e.setProperty("title", book.getTitle()); 58 e.setProperty("created", book.getCreated()); 59 e.setProperty("year", book.getYear()); 60 datastoreService.put(e); 61 } 62 63 private static final class BookWithEntity extends Book { 64 private final Entity e; 65 66 private BookWithEntity(Entity e) { 67 super((String) e.getProperty("category"), (Date) e.getProperty("created"), 68 (String) e.getProperty("firstname"), (String) e.getProperty("lastname"), 69 (String) e.getProperty("title"), ((Long) e.getProperty("year")).intValue()); 70 this.e = e; 71 } 72 } 73 74 private static final class BookIterable implements Iterable<Book> { 75 76 public Iterator<Book> iterator() { 77 return new BookIterator(it.iterator()); 78 } 79 80 private BookIterable(Iterable<Entity> it) { 81 super(); 82 this.it = it; 83 } 84 85 private final Iterable<Entity> it; 86 } 87 88 private static final class BookIterator implements Iterator<Book> { 89 90 public boolean hasNext() { 91 return it.hasNext(); 92 } 93 94 public Book next() { 95 Book book = null; 96 final Entity e = it.next(); 97 if (e != null) { 98 book = 99 new BookWithEntity(e); 100 } 101 return book; 102 } 103 104 public void remove() { 105 it.remove(); 106 } 107 108 private BookIterator(Iterator<Entity> it) { 109 super(); 110 this.it = it; 111 } 112 113 private final Iterator<Entity> it; 114 } 115 116 private static final class QueryConverter { 117 /** 118 * Converts a JPQL string into {@code Query}. Only parses the format output 119 * by {@code Library}, e.g. WHERE category='test' AND year>1800 ORDER BY 120 * year. 121 */ 122 private Query convertQuery(String jpqlQuery) { 123 query = new Query("Book"); 124 125 tokenizer = new StringTokenizer(jpqlQuery); 126 if (tokenizer.hasMoreTokens()) { 127 String lastToken = tokenizer.nextToken(); 128 if (lastToken.equalsIgnoreCase("WHERE")) { 129 do { 130 parseFilter(); 131 if (!tokenizer.hasMoreTokens()) { 132 break; 133 } 134 lastToken = tokenizer.nextToken(); 135 } while (lastToken.equals("AND")); 136 } 137 if (lastToken.equals("ORDER")) { 138 lastToken = tokenizer.nextToken(); // skip "BY" 139 assert lastToken.equals("BY"); 140 while (parseSort()) { 141 } 142 } 143 } 144 145 return query; 146 } 147 148 private boolean parseSort() { 149 String propName = tokenizer.nextToken(); 150 String direction = tokenizer.nextToken(); 151 boolean moreSort = direction.endsWith(","); 152 if (moreSort) { 153 direction = direction.substring(0, direction.length() - 1); 154 } 155 156 SortDirection sd = null; 157 if (direction.equals("ASC")) { 158 sd = SortDirection.ASCENDING; 159 } else if (direction.equals("DESC")) { 160 sd = SortDirection.DESCENDING; 161 } else { 162 assert false : direction + " is not ASC or DESC"; 163 } 164 query.addSort(propName, sd); 165 return moreSort; 166 } 167 168 private void parseFilter() { 169 String filter = tokenizer.nextToken(); 170 int opIndex = -1; 171 for (String op : OPERATORS) { 172 opIndex = filter.indexOf(op); 173 if (opIndex != -1) { 174 String propName = filter.substring(0, opIndex); 175 FilterOperator operator = OPERATOR_MAP.get(op); 176 String propValue = filter.substring(opIndex + op.length()); 177 propValue = parseValue(propValue); 178 179 // special case: year has int value 180 if (propName.equals("year")) { 181 query.addFilter(propName, operator, Integer.parseInt(propValue)); 182 } else { 183 query.addFilter(propName, operator, propValue); 184 } 185 break; 186 } 187 } 188 assert opIndex != -1 : filter + "missing comparison operator"; 189 } 190 191 private String parseValue(String propValue) { 192 // not quoted 193 if (!propValue.startsWith("'")) { 194 return propValue; 195 } 196 197 while (!propValue.endsWith("'")) { 198 // an incomplete quoted literal 199 propValue += " " + tokenizer.nextToken(); 200 } 201 propValue = propValue.substring(1, propValue.length() - 1); 202 return propValue; 203 } 204 205 private StringTokenizer tokenizer; 206 private Query query; 207 208 private static final String[] OPERATORS = {">=", ">", "<=", "<", "="}; 209 private static final FilterOperator[] OPERATOR_ENUMS = 210 {FilterOperator.GREATER_THAN_OR_EQUAL, FilterOperator.GREATER_THAN, 211 FilterOperator.LESS_THAN_OR_EQUAL, FilterOperator.LESS_THAN, FilterOperator.EQUAL}; 212 213 private static final Map<String, FilterOperator> OPERATOR_MAP = 214 new HashMap<String, FilterOperator>(); 215 static { 216 for (int ii = 0; ii < OPERATORS.length; ii++) { 217 OPERATOR_MAP.put(OPERATORS[ii], OPERATOR_ENUMS[ii]); 218 } 219 } 220 } 221 222 public void close() { 223 // no clean-up needed for DataStore. 224 } 225}