Package siena.sdb

Source Code of siena.sdb.SdbMappingUtils

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...");
    }
  }


}
TOP

Related Classes of siena.sdb.SdbMappingUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.