// Copyright 2008 Google Inc. All Rights Reserved.
package com.google.appengine.library;
import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.SortDirection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
/**
* @author kjin@google.com (Kevin Jin)
*/
final class DataStoreBookDataService implements BookDataService {
private final DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
public Iterable<Book> asIterable(String jpqlQuery) {
return new BookIterable(datastoreService.prepare(new QueryConverter().convertQuery(jpqlQuery))
.asIterable());
}
public Iterable<Book> asIterable(String jpqlQuery, int limit, int offset) {
FetchOptions fo = withLimit(limit).offset(offset);
return new BookIterable(datastoreService.prepare(new QueryConverter().convertQuery(jpqlQuery))
.asIterable(fo));
}
public int countEntities(String jpqlQuery) {
return datastoreService.prepare(new QueryConverter().convertQuery(jpqlQuery)).countEntities();
}
public void delete(Book book) {
datastoreService.delete(((BookWithEntity) book).e.getKey());
}
public void put(Book book) {
final Entity e;
if (book instanceof BookWithEntity) {
e = ((BookWithEntity) book).e;
} else {
e = new Entity("Book");
}
e.setProperty("category", book.getCategory());
e.setProperty("lastname", book.getLastname());
e.setProperty("firstname", book.getFirstname());
e.setProperty("title", book.getTitle());
e.setProperty("created", book.getCreated());
e.setProperty("year", book.getYear());
datastoreService.put(e);
}
private static final class BookWithEntity extends Book {
private final Entity e;
private BookWithEntity(Entity e) {
super((String) e.getProperty("category"), (Date) e.getProperty("created"),
(String) e.getProperty("firstname"), (String) e.getProperty("lastname"),
(String) e.getProperty("title"), ((Long) e.getProperty("year")).intValue());
this.e = e;
}
}
private static final class BookIterable implements Iterable<Book> {
public Iterator<Book> iterator() {
return new BookIterator(it.iterator());
}
private BookIterable(Iterable<Entity> it) {
super();
this.it = it;
}
private final Iterable<Entity> it;
}
private static final class BookIterator implements Iterator<Book> {
public boolean hasNext() {
return it.hasNext();
}
public Book next() {
Book book = null;
final Entity e = it.next();
if (e != null) {
book =
new BookWithEntity(e);
}
return book;
}
public void remove() {
it.remove();
}
private BookIterator(Iterator<Entity> it) {
super();
this.it = it;
}
private final Iterator<Entity> it;
}
private static final class QueryConverter {
/**
* Converts a JPQL string into {@code Query}. Only parses the format output
* by {@code Library}, e.g. WHERE category='test' AND year>1800 ORDER BY
* year.
*/
private Query convertQuery(String jpqlQuery) {
query = new Query("Book");
tokenizer = new StringTokenizer(jpqlQuery);
if (tokenizer.hasMoreTokens()) {
String lastToken = tokenizer.nextToken();
if (lastToken.equalsIgnoreCase("WHERE")) {
do {
parseFilter();
if (!tokenizer.hasMoreTokens()) {
break;
}
lastToken = tokenizer.nextToken();
} while (lastToken.equals("AND"));
}
if (lastToken.equals("ORDER")) {
lastToken = tokenizer.nextToken(); // skip "BY"
assert lastToken.equals("BY");
while (parseSort()) {
}
}
}
return query;
}
private boolean parseSort() {
String propName = tokenizer.nextToken();
String direction = tokenizer.nextToken();
boolean moreSort = direction.endsWith(",");
if (moreSort) {
direction = direction.substring(0, direction.length() - 1);
}
SortDirection sd = null;
if (direction.equals("ASC")) {
sd = SortDirection.ASCENDING;
} else if (direction.equals("DESC")) {
sd = SortDirection.DESCENDING;
} else {
assert false : direction + " is not ASC or DESC";
}
query.addSort(propName, sd);
return moreSort;
}
private void parseFilter() {
String filter = tokenizer.nextToken();
int opIndex = -1;
for (String op : OPERATORS) {
opIndex = filter.indexOf(op);
if (opIndex != -1) {
String propName = filter.substring(0, opIndex);
FilterOperator operator = OPERATOR_MAP.get(op);
String propValue = filter.substring(opIndex + op.length());
propValue = parseValue(propValue);
// special case: year has int value
if (propName.equals("year")) {
query.addFilter(propName, operator, Integer.parseInt(propValue));
} else {
query.addFilter(propName, operator, propValue);
}
break;
}
}
assert opIndex != -1 : filter + "missing comparison operator";
}
private String parseValue(String propValue) {
// not quoted
if (!propValue.startsWith("'")) {
return propValue;
}
while (!propValue.endsWith("'")) {
// an incomplete quoted literal
propValue += " " + tokenizer.nextToken();
}
propValue = propValue.substring(1, propValue.length() - 1);
return propValue;
}
private StringTokenizer tokenizer;
private Query query;
private static final String[] OPERATORS = {">=", ">", "<=", "<", "="};
private static final FilterOperator[] OPERATOR_ENUMS =
{FilterOperator.GREATER_THAN_OR_EQUAL, FilterOperator.GREATER_THAN,
FilterOperator.LESS_THAN_OR_EQUAL, FilterOperator.LESS_THAN, FilterOperator.EQUAL};
private static final Map<String, FilterOperator> OPERATOR_MAP =
new HashMap<String, FilterOperator>();
static {
for (int ii = 0; ii < OPERATORS.length; ii++) {
OPERATOR_MAP.put(OPERATORS[ii], OPERATOR_ENUMS[ii]);
}
}
}
public void close() {
// no clean-up needed for DataStore.
}
}