package siena.sdb;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import siena.ClassInfo;
import siena.Id;
import siena.Json;
import siena.Query;
import siena.QueryData;
import siena.QueryFilter;
import siena.QueryFilterSearch;
import siena.QueryFilterSimple;
import siena.QueryOrder;
import siena.SienaException;
import siena.SienaRestrictedApiException;
import siena.Util;
import siena.core.Base64;
import siena.core.DecimalPrecision;
import siena.core.options.QueryOptionFetchType;
import siena.core.options.QueryOptionOffset;
import siena.core.options.QueryOptionPage;
import siena.core.options.QueryOptionState;
import siena.embed.Embedded;
import siena.embed.JavaSerializer;
import siena.embed.JsonSerializer;
import siena.gae.GaeNativeSerializer;
import siena.gae.QueryOptionGaeContext;
import siena.gae.Unindexed;
import siena.sdb.ws.SimpleDB;
import com.amazonaws.services.simpledb.model.Attribute;
import com.amazonaws.services.simpledb.model.DeletableItem;
import com.amazonaws.services.simpledb.model.DeleteAttributesRequest;
import com.amazonaws.services.simpledb.model.GetAttributesRequest;
import com.amazonaws.services.simpledb.model.GetAttributesResult;
import com.amazonaws.services.simpledb.model.Item;
import com.amazonaws.services.simpledb.model.PutAttributesRequest;
import com.amazonaws.services.simpledb.model.ReplaceableAttribute;
import com.amazonaws.services.simpledb.model.ReplaceableItem;
import com.amazonaws.services.simpledb.model.SelectRequest;
import com.amazonaws.services.simpledb.model.SelectResult;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Text;
public class SdbMappingUtils {
private static long ioffset = Math.abs(0L+Integer.MIN_VALUE);
public static String getDomainName(Class<?> clazz, String prefix) {
ClassInfo ci = ClassInfo.getClassInfo(clazz);
if(ClassInfo.isAutoIncrement(ci.getIdField())){
throw new SienaRestrictedApiException("DB", "getItemName", "@Id AUTO_INCREMENT not supported by SDB");
}
String domain = prefix + ci.tableName;
return domain;
}
public static void getDomainName(StringBuffer str, Class<?> clazz, String prefix) {
str.append(prefix + ClassInfo.getClassInfo(clazz).tableName);
}
public static String getAttributeName(Field field) {
return ClassInfo.getColumnNames(field)[0];
}
public static String getItemName(Class<?> clazz, Object obj){
Field idField = ClassInfo.getIdField(clazz);
Id id = idField.getAnnotation(Id.class);
String keyVal = null;
if(id != null){
switch(id.value()) {
case NONE:
{
Object idVal = Util.readField(obj, idField);
if(idVal == null)
throw new SienaException("Id Field " + idField.getName() + " value null");
keyVal = toString(idField, idVal);
break;
}
case AUTO_INCREMENT:
// manages String ID as not long!!!
throw new SienaRestrictedApiException("DB", "getItemName", "@Id AUTO_INCREMENT not supported by SDB");
case UUID:
{
Object idVal = Util.readField(obj, idField);
if(idVal == null){
UUID uuid = UUID.randomUUID();
keyVal = uuid.toString();
if(idField.getType() == UUID.class){
Util.setField(obj, idField, uuid);
}
else if(idField.getType() == String.class){
Util.setField(obj, idField, uuid.toString());
}
else {
throw new SienaRestrictedApiException("DB", "getItemName", "@Id UUID must be of type String or UUID");
}
}else {
keyVal = toString(idField, idVal);
}
break;
}
default:
throw new SienaRestrictedApiException("DB", "createEntityInstance", "Id Generator "+id.value()+ " not supported");
}
}
else throw new SienaException("Field " + idField.getName() + " is not an @Id field");
return keyVal;
}
public static String getItemNameFromKey(Class<?> clazz, Object key){
Field idField = ClassInfo.getIdField(clazz);
Id id = idField.getAnnotation(Id.class);
String keyVal = null;
if(id != null){
switch(id.value()) {
case NONE:
{
keyVal = toString(idField, key);
break;
}
case AUTO_INCREMENT:
// manages String ID as not long!!!
throw new SienaRestrictedApiException("DB", "getItemName", "@Id AUTO_INCREMENT not supported by SDB");
case UUID:
{
keyVal = toString(idField, key);
break;
}
default:
throw new SienaRestrictedApiException("DB", "createEntityInstance", "Id Generator "+id.value()+ " not supported");
}
}
else throw new SienaException("Field " + idField.getName() + " is not an @Id field");
return keyVal;
}
public static PutAttributesRequest createPutRequest(String domain, Class<?> clazz, ClassInfo info, Object obj) {
PutAttributesRequest req = new PutAttributesRequest().withDomainName(domain);
req.withItemName(getItemName(clazz, obj));
for (Field field : ClassInfo.getClassInfo(clazz).updateFields) {
try {
String value = objectFieldToString(obj, field);
if(value != null){
ReplaceableAttribute attr = new ReplaceableAttribute(getAttributeName(field), value, true);
req.withAttributes(attr);
}else {
if (ClassInfo.isEmbeddedNative(field)){
SdbNativeSerializer.embed(req, ClassInfo.getSingleColumnName(field), value);
}
}
} catch (Exception e) {
throw new SienaException(e);
}
}
return req;
}
public static ReplaceableItem createItem(Object obj) {
Class<?> clazz = obj.getClass();
ReplaceableItem item = new ReplaceableItem(getItemName(clazz, obj));
for (Field field : ClassInfo.getClassInfo(clazz).updateFields) {
try {
String value = objectFieldToString(obj, field);
if(value != null){
ReplaceableAttribute attr = new ReplaceableAttribute(getAttributeName(field), value, true);
item.withAttributes(attr);
}else {
if (ClassInfo.isEmbeddedNative(field)){
SdbNativeSerializer.embed(item, ClassInfo.getSingleColumnName(field), value);
}
}
} catch (Exception e) {
throw new SienaException(e);
}
}
return item;
}
public static DeletableItem createDeletableItem(Object obj) {
Class<?> clazz = obj.getClass();
return new DeletableItem().withName(getItemName(clazz, obj));
}
public static <T> DeletableItem createDeletableItemFromKey(Class<T> clazz, Object key) {
return new DeletableItem().withName(getItemNameFromKey(clazz, key));
}
public static GetAttributesRequest createGetRequest(String domain, Class<?> clazz, Object obj) {
GetAttributesRequest req =
new GetAttributesRequest().withDomainName(domain).withItemName(getItemName(clazz, obj));
return req;
}
public static GetAttributesRequest createGetRequestFromKey(String domain, Class<?> clazz, Object key) {
GetAttributesRequest req =
new GetAttributesRequest().withDomainName(domain).withItemName(key.toString());
return req;
}
public static DeleteAttributesRequest createDeleteRequest(String domain, Class<?> clazz, Object obj) {
DeleteAttributesRequest req =
new DeleteAttributesRequest().withDomainName(domain).withItemName(getItemName(clazz, obj));
return req;
}
public static String objectFieldToString(Object obj, Field field) {
Object val = Util.readField(obj, field);
if(val == null) return null;
return toString(field, val);
}
public static String toString(Object val){
Class<?> type = val.getClass();
if(type == Integer.class || type == int.class) {
return toString((Integer)val);
}
if(ClassInfo.isModel(type)) {
try {
return objectFieldToString(val, ClassInfo.getIdField(type));
} catch (Exception e) {
throw new SienaException(e);
}
} else {
if (type == Json.class) {
return val.toString();
} else if (type == byte[].class) {
return Base64.encodeBytes((byte[]) val);
}
else if (type == BigDecimal.class){
return ((BigDecimal)val).toPlainString();
}
// enum is after embedded because an enum can be embedded
// don't know if anyone will use it but it will work :)
else if (Enum.class.isAssignableFrom(type)) {
return val.toString();
}
}
return val.toString();
}
public static String toString(Field field, Object val) {
if(val == null) return null;
Class<?> type = field.getType();
if(type == Integer.class || type == int.class) {
return intToString((Integer)val);
}
if(ClassInfo.isModel(type) && !ClassInfo.isEmbedded(field)) {
try {
return objectFieldToString(val, ClassInfo.getIdField(type));
} catch (Exception e) {
throw new SienaException(e);
}
} else {
if (type == Json.class) {
return val.toString();
} else if (type == byte[].class) {
return Base64.encodeBytes((byte[]) val);
}
else if (ClassInfo.isEmbedded(field)) {
Embedded embed = field.getAnnotation(Embedded.class);
switch(embed.mode()){
case SERIALIZE_JSON:
return JsonSerializer.serialize(val).toString();
case SERIALIZE_JAVA:
// this embedding mode doesn't manage @EmbedIgnores
try {
return Base64.encodeBytes(JavaSerializer.serialize(val));
}
catch(IOException ex) {
throw new SienaException(ex);
}
case NATIVE:
// returns null because here we need to manage all fields of the embedded entity
return null;
}
}
else if (type == BigDecimal.class){
DecimalPrecision ann = field.getAnnotation(DecimalPrecision.class);
if(ann == null) {
return ((BigDecimal)val).toPlainString();
}else {
switch(ann.storageType()){
case DOUBLE:
return ((Double)((BigDecimal)val).doubleValue()).toString();
case STRING:
case NATIVE:
return ((BigDecimal)val).toPlainString();
}
}
}
// enum is after embedded because an enum can be embedded
// don't know if anyone will use it but it will work :)
else if (Enum.class.isAssignableFrom(field.getType())) {
return val.toString();
}
}
return Util.toString(field, val);
}
public static String intToString(int i) {
return String.format("%010d", i+ioffset);
}
public static int intFromString(String s) {
long l = Long.parseLong(s);
return (int) (l-ioffset);
}
public static void setFromString(Object obj, Field field, String val) {
if(val == null) return;
Class<?> fieldClass = field.getType();
if(fieldClass == Integer.class || fieldClass == int.class) {
Util.setField(obj, field, intFromString(val));
return;
}
if(ClassInfo.isModel(fieldClass) && !ClassInfo.isEmbedded(field)) {
try {
Object relObj = Util.createObjectInstance(fieldClass);
Field relIdField = ClassInfo.getIdField(fieldClass);
setFromString(relObj, relIdField, val);
Util.setField(obj, field, relObj);
return;
} catch (Exception e) {
throw new SienaException(e);
}
} else {
if (fieldClass == byte[].class) {
try {
Util.setField(obj, field, Base64.decode(val));
return;
}catch(Exception ex){
throw new SienaException(ex);
}
}
else if (ClassInfo.isEmbeddedNative(field)) {
return;
}
else if (fieldClass == BigDecimal.class){
DecimalPrecision ann = field.getAnnotation(DecimalPrecision.class);
if(ann == null) {
Util.setField(obj, field, new BigDecimal((String)val));
return;
}else {
switch(ann.storageType()){
case DOUBLE:
// TODO add bigdecimal double lexicographic storage
Util.setField(obj, field, new BigDecimal(val));
return;
case STRING:
case NATIVE:
Util.setField(obj, field, new BigDecimal(val));
return;
}
}
}
}
Util.setFromObject(obj, field, val);
}
public static void fillModelKeysOnly(String itemName, Class<?> clazz, Object obj) {
Field idField = ClassInfo.getIdField(clazz);
setFromString(obj, idField, itemName);
}
public static void fillModel(String itemName, List<Attribute> attrs, Class<?> clazz, Object obj) {
fillModelKeysOnly(itemName, clazz, obj);
Attribute theAttr;
for (Field field : ClassInfo.getClassInfo(clazz).updateFields) {
if(!ClassInfo.isEmbeddedNative(field)){
theAttr = null;
String attrName = getAttributeName(field);
// searches attribute and if found, removes it from the list to reduce number of attributes
for(Attribute attr: attrs){
if(attrName.equals(attr.getName())){
theAttr = attr;
attrs.remove(attr);
break;
}
}
if(theAttr != null){
setFromString(obj, field, theAttr.getValue());
}
}else {
Object value = SdbNativeSerializer.unembed(
field.getType(), ClassInfo.getSingleColumnName(field), attrs);
Util.setField(obj, field, value);
}
}
}
public static void fillModel(String itemName, GetAttributesResult res, Class<?> clazz, Object obj) {
fillModel(itemName, res.getAttributes(), clazz, obj);
}
public static void fillModel(Item item, Class<?> clazz, ClassInfo info, Object obj) {
fillModel(item.getName(), item.getAttributes(), clazz, obj);
}
public static void fillModelKeysOnly(Item item, Class<?> clazz, ClassInfo info, Object obj) {
fillModelKeysOnly(item.getName(), clazz, obj);
}
public static <T> int mapSelectResult(SelectResult res, Iterable<T> objects) {
List<Item> items = res.getItems();
Class<?> clazz = null;
ClassInfo info = null;
int nb = 0;
for(T obj: objects){
if(clazz == null){
clazz = obj.getClass();
info = ClassInfo.getClassInfo(clazz);
}
String itemName = getItemName(clazz, obj);
Item theItem = null;
for(Item item:items){
if(item.getName().equals(itemName)){
theItem = item;
items.remove(item);
break;
}
}
if(theItem != null){
fillModel(theItem, clazz, info, obj);
nb++;
}
}
return nb;
}
public static <T> void mapSelectResultToList(SelectResult res, List<T> resList, Class<T> clazz) {
List<Item> items = res.getItems();
ClassInfo info = ClassInfo.getClassInfo(clazz);
for(Item item: items){
T obj = Util.createObjectInstance(clazz);
fillModel(item, clazz, info, obj);
resList.add(obj);
}
}
public static <T> void mapSelectResultToList(SelectResult res, List<T> resList, Class<T> clazz, int offset) {
List<Item> items = res.getItems();
ClassInfo info = ClassInfo.getClassInfo(clazz);
for(int i=offset; i<items.size(); i++){
Item item = items.get(i);
T obj = Util.createObjectInstance(clazz);
fillModel(item, clazz, info, obj);
resList.add(obj);
}
}
public static <T> void mapSelectResultToListKeysOnly(SelectResult res, List<T> resList, Class<T> clazz) {
List<Item> items = res.getItems();
ClassInfo info = ClassInfo.getClassInfo(clazz);
for(Item item: items){
T obj = Util.createObjectInstance(clazz);
fillModelKeysOnly(item, clazz, info, obj);
resList.add(obj);
}
}
public static <T> void mapSelectResultToListKeysOnly(SelectResult res, List<T> resList, Class<T> clazz, int offset) {
List<Item> items = res.getItems();
ClassInfo info = ClassInfo.getClassInfo(clazz);
for(int i=offset; i<items.size(); i++){
Item item = items.get(i);
T obj = Util.createObjectInstance(clazz);
fillModelKeysOnly(item, clazz, info, obj);
resList.add(obj);
}
}
public static <T> void mapSelectResultToListOrderedFromKeys(SelectResult res, List<T> resList, Class<T> clazz, Iterable<?> keys) {
List<Item> items = res.getItems();
ClassInfo info = ClassInfo.getClassInfo(clazz);
boolean found;
for(Object key: keys){
found = false;
for(Item item: items){
if(item.getName().equals(getItemNameFromKey(clazz, key))){
T obj = Util.createObjectInstance(clazz);
fillModel(item, clazz, info, obj);
resList.add(obj);
items.remove(item);
found = true;
break;
}
}
if(!found){
// if not found, puts NULL in the list
resList.add(null);
}
}
}
public static int mapSelectResultToCount(SelectResult res) {
Item item = res.getItems().get(0);
if(item != null){
Attribute attr = item.getAttributes().get(0);
if("Count".equals(attr.getName())){
return Integer.parseInt(attr.getValue());
}
}
return -1;
}
public static String quote(String s) {
return "\""+s.replace("'", "''")+"\"";
}
public static final String WHERE = " where ";
public static final String AND = " and ";
public static final String OR = " or ";
public static final String IS_NULL = " is null ";
public static final String IS_NOT_NULL = " is not null ";
public static final String ITEM_NAME = "itemName()";
public static final String ALL_COLS = "*";
public static final String SELECT = "select ";
public static final String FROM = " from ";
public static final String ORDER_BY = " order by ";
public static final String DESC = " desc";
public static final String IN_BEGIN = " in(";
public static final String IN_END = ")";
public static final String COUNT_BEGIN = " count(";
public static final String COUNT_END = ")";
public static final String LIMIT = " limit ";
public static final String LIKE = " like ";
public static final String EQ = " = ";
public static <T> SelectRequest buildBatchGetQuery(Iterable<T> objects, String prefix, StringBuffer domainBuf) {
Class<?> clazz = null;
StringBuilder q = new StringBuilder();
String domain = null;
boolean first = true;
for(T obj: objects){
if(clazz == null){
clazz = obj.getClass();
domain = getDomainName(clazz, prefix);
domainBuf.append(domain);
q.append(SELECT + "*" + FROM + domain + WHERE + ITEM_NAME + IN_BEGIN);
}
String itemName = getItemName(clazz, obj);
if(!first){
q.append(",");
} else {
first = false;
}
q.append(quote(itemName));
}
q.append(IN_END);
return new SelectRequest(q.toString());
}
public static <T> SelectRequest buildBatchGetQueryByKeys(Class<T> clazz, Iterable<?> keys, String prefix, StringBuffer domainBuf) {
String domain = getDomainName(clazz, prefix);;
domainBuf.append(domain);
StringBuilder q = new StringBuilder();
q.append(SELECT + "*" + FROM + domain + WHERE + ITEM_NAME + IN_BEGIN);
boolean first = true;
for(Object key: keys){
String itemName = toString(key);
if(!first){
q.append(",");
} else {
first = false;
}
q.append(quote(itemName));
}
q.append(IN_END);
return new SelectRequest(q.toString());
}
public static <T> SelectRequest buildCountQuery(Query<T> query, String prefix, StringBuffer domainBuf) {
String domain = getDomainName(query.getQueriedClass(), prefix);;
domainBuf.append(domain);
StringBuilder q = new StringBuilder();
q.append(SELECT + COUNT_BEGIN + "*" + COUNT_END + FROM + domain);
return new SelectRequest(buildFilterOrder(query, q).toString());
}
public static <T> SelectRequest buildQuery(Query<T> query, String prefix, StringBuffer domainBuf) {
Class<?> clazz = query.getQueriedClass();
String domain = getDomainName(clazz, prefix);
domainBuf.append(domain);
QueryOptionFetchType fetchType = (QueryOptionFetchType)query.option(QueryOptionFetchType.ID);
StringBuilder q = new StringBuilder();
switch(fetchType.fetchType){
case KEYS_ONLY:
q.append(SELECT + ITEM_NAME + FROM + domain);
break;
case NORMAL:
default:
q.append(SELECT + ALL_COLS + FROM + domain);
break;
}
return new SelectRequest(buildFilterOrder(query, q).toString());
}
public static <T> StringBuilder buildFilterOrder(Query<T> query, StringBuilder q){
List<QueryFilter> filters = query.getFilters();
Set<Field> filteredFields = new HashSet<Field>();
boolean first = true;
if(!filters.isEmpty()) {
q.append(WHERE);
for (QueryFilter filter : filters) {
if(QueryFilterSimple.class.isAssignableFrom(filter.getClass())){
QueryFilterSimple qf = (QueryFilterSimple)filter;
Field f = qf.field;
Object value = qf.value;
String op = qf.operator;
// for order verification in case the order is not on a filtered field
filteredFields.add(f);
if(!first) {
q.append(AND);
}
first = false;
String[] columns = ClassInfo.getColumnNames(f);
if("IN".equals(op)) {
if(!Collection.class.isAssignableFrom(value.getClass()))
throw new SienaException("Collection needed when using IN operator in filter() query");
StringBuilder s = new StringBuilder();
Collection<?> col = (Collection<?>) value;
for (Object object : col) {
// TODO manages model collection
// TO BE VERIFIED: SHOULD BE MANAGED by toString!!!
if(object != null){
s.append(","+SimpleDB.quote(toString(f, object)));
}else{
throw new SienaException("Can't use NULL in collection for IN operator");
}
}
String column = null;
if(ClassInfo.isId(f)) {
column = ITEM_NAME;
} else {
column = ClassInfo.getColumnNames(f)[0];
}
q.append(column+" in("+s.toString().substring(1)+")");
} else if(ClassInfo.isModel(f.getType())) {
// TODO could manage other ops here
if(!op.equals("=")) {
throw new SienaException("Unsupported operator for relationship: "+op);
}
ClassInfo relInfo = ClassInfo.getClassInfo(f.getType());
int i = 0;
for (Field key : relInfo.keys) {
if(value == null) {
q.append(columns[i++] + IS_NULL);
} else {
q.append(columns[i++] + op + SimpleDB.quote(objectFieldToString(value, key)));
}
}
} else {
String column = null;
if(ClassInfo.isId(f)) {
column = "itemName()";
if(value == null && op.equals("=")) {
throw new SienaException("SDB filter on @Id field with 'IS NULL' is not possible");
}
} else {
column = ClassInfo.getColumnNames(f)[0];
}
if(value == null && op.equals("=")) {
q.append(column + IS_NULL);
} else if(value == null && op.equals("!=")) {
q.append(column + IS_NOT_NULL);
} else {
q.append(column + op + SimpleDB.quote(toString(f, value)));
}
}
}else if(QueryFilterSearch.class.isAssignableFrom(filter.getClass())){
Class<T> clazz = query.getQueriedClass();
QueryFilterSearch qf = (QueryFilterSearch)filter;
//if(qf.fields.length>1)
// throw new SienaException("Search not possible for several fields in SDB: only one field");
try {
//Field field = Util.getField(clazz, qf.fields[0]);
//if(field.isAnnotationPresent(Unindexed.class)){
// throw new SienaException("Cannot search the @Unindexed field "+field.getName());
//}
// cuts match into words
String[] words = qf.match.split("\\s");
// if several words, then only OR operator represented by IN GAE
Pattern pNormal = Pattern.compile("[\\%]*(\\w+)[\\%]*");
if(!first) {
q.append(AND);
}
// forces true
first = true;
q.append(" ( ");
for(String f: qf.fields){
Field field = Util.getField(clazz, f);
if(!first) {
q.append(OR);
}
first = false;
q.append(" ( ");
String column = null;
if(ClassInfo.isId(field)) {
column = "itemName()";
} else {
column = ClassInfo.getColumnNames(field)[0];
}
first = true;
for(String word:words){
if(!first) {
q.append(OR);
}
first = false;
if(!pNormal.matcher(word).matches()){
throw new SienaException("'"+word+"' doesn't match pattern [\\%]*(\\w+)[\\%]*");
}
if(word.contains("%")){
q.append(column + LIKE + SimpleDB.quote(word));
}else {
q.append(column + EQ + SimpleDB.quote(word));
}
}
q.append(" ) ");
}
q.append(" ) ");
}catch(Exception e){
throw new SienaException(e);
}
}
}
}
List<QueryOrder> orders = query.getOrders();
if(!orders.isEmpty()) {
QueryOrder last = orders.get(orders.size()-1);
Field field = last.field;
if(ClassInfo.isId(field)) {
if(!filteredFields.contains(field)){
if(filters.isEmpty()) {
q.append(WHERE);
}else {
q.append(AND);
}
q.append(ITEM_NAME + IS_NOT_NULL);
}
q.append(ORDER_BY + ITEM_NAME);
} else {
String column = ClassInfo.getColumnNames(field)[0];
if(!filteredFields.contains(field)){
if(filters.isEmpty()) {
q.append(WHERE);
}else {
q.append(AND);
}
q.append(column + IS_NOT_NULL);
}
q.append(ORDER_BY + column);
}
if(!last.ascending)
q.append(DESC);
}
QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID);
QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
if(sdbCtx != null && sdbCtx.realPageSize != 0){
if(off!=null && off.isActive()){
// if offset is active, adds it to the page size to be sure to retrieve enough elements
q.append(LIMIT + (sdbCtx.realPageSize + off.offset));
}else {
q.append(LIMIT + sdbCtx.realPageSize);
}
}
return q;
}
public static <T> void nextPage(QueryData<T> query) {
QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID);
QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
if(sdbCtx==null){
sdbCtx = new QueryOptionSdbContext();
query.options().put(sdbCtx.type, sdbCtx);
}
// if no more data after, doesn't try to go after
if(sdbCtx.noMoreDataAfter){
return;
}
// if no more data before, removes flag to be able and stay there
if(sdbCtx.noMoreDataBefore){
sdbCtx.noMoreDataBefore = false;
return;
}
if(pag.isPaginating()){
if(sdbCtx.hasToken()){
if(sdbCtx.nextToken() == null) {
// in this case, doesn't advance to the next page
// and stays at the offset of the beginning of the
// last page
sdbCtx.noMoreDataAfter = true;
}else{
// follows the real offset and doesn't forget to add the off.offset
if(off.isActive())
sdbCtx.realOffset += pag.pageSize + off.offset;
else sdbCtx.realOffset += pag.pageSize;
// if currentokenoffset is less than next page realoffset
// uses offset
if(sdbCtx.currentTokenOffset() <= sdbCtx.realOffset){
off.activate();
off.offset = sdbCtx.realOffset - sdbCtx.currentTokenOffset();
}
// if currentokenoffset is greater than previous page realoffset
// go to previous page again
else {
nextPage(query);
}
}
}else {
// no token yet, so uses the offset to go to next page
QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
offset.activate();
offset.offset += pag.pageSize;
// follows the real offset
sdbCtx.realOffset += pag.pageSize;
}
}else {
// throws exception because it's impossible to reuse nextPage when paginating has been interrupted, the cases are too many
throw new SienaException("Can't use nextPage after pagination has been interrupted...");
}
}
public static <T> void previousPage(QueryData<T> query) {
QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID);
if(sdbCtx==null){
sdbCtx = new QueryOptionSdbContext();
query.options().put(sdbCtx.type, sdbCtx);
}
// if no more data before, doesn't try to go before
if(sdbCtx.noMoreDataBefore){
return;
}
// if no more data after, removes flag to be able to go before
if(sdbCtx.noMoreDataAfter){
// here the realoffset is not at the end of current pages
// but at the beginning of the last page
// so need to fake that we are at the end of the last page
sdbCtx.realOffset += pag.pageSize;
sdbCtx.noMoreDataAfter = false;
}
if(pag.isPaginating()){
if(sdbCtx.hasToken()) {
// if tokenIdx is 0, it means at first page after beginning
if(sdbCtx.tokenIdx == 0){
sdbCtx.previousToken();
// follows the real offset
sdbCtx.realOffset -= pag.pageSize;
// if currentokenoffset is less than previous page realoffset
// uses offset
if(sdbCtx.currentTokenOffset() <= sdbCtx.realOffset){
QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
offset.activate();
offset.offset = sdbCtx.realOffset - sdbCtx.currentTokenOffset();
}
// if currentokenoffset is greater than previous page realoffset
// go to previous page again
else {
previousPage(query);
}
}else {
if(sdbCtx.previousToken() == null) {
sdbCtx.realOffset -= pag.pageSize;
// if the realOffset is not null, it means we are not at the index 0 of the table
// so now uses realOffset
if(sdbCtx.realOffset >= 0){
QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
offset.activate();
offset.offset = sdbCtx.realOffset;
}else {
// resets realOffset to 0 because it was negative
sdbCtx.realOffset = 0;
sdbCtx.noMoreDataBefore = true;
}
}else {
// follows the real offset
sdbCtx.realOffset -= pag.pageSize;
// if currentokenoffset is less than previous page realoffset
// uses offset
if(sdbCtx.currentTokenOffset() <= sdbCtx.realOffset){
QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
offset.activate();
offset.offset = sdbCtx.realOffset - sdbCtx.currentTokenOffset();
}
// if currentokenoffset is greater than previous page realoffset
// go to previous page again
else {
previousPage(query);
}
}
}
}else {
QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
// means there has been a nextPage performed first and the offset has been used
// to simulate the nextPage as there was no token yet
if(sdbCtx.realOffset != 0){
// follows the real offset
sdbCtx.realOffset -= pag.pageSize;
offset.activate();
offset.offset = sdbCtx.realOffset;
}else {
sdbCtx.noMoreDataBefore = true;
}
/*if(offset.offset != 0){
offset.offset -= pag.pageSize;
offset.activate();
// follows the real offset
sdbCtx.realOffset -= pag.pageSize;
}else {
// if the realOffset is not null, it means we are not at the index 0 of the table
// so now uses realOffset
if(sdbCtx.realOffset != 0){
offset.activate();
offset.offset = sdbCtx.realOffset;
}
sdbCtx.noMoreDataBefore = true;
}*/
}
} else {
// throws exception because it's impossible to reuse nextPage when paginating has been interrupted, the cases are too many
throw new SienaException("Can't use nextPage after pagination has been interrupted...");
}
}
}