Package siena.gae

Source Code of siena.gae.GaeQueryUtils

package siena.gae;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import siena.ClassInfo;
import siena.Id;
import siena.QueryData;
import siena.QueryFilter;
import siena.QueryFilterSearch;
import siena.QueryFilterSimple;
import siena.QueryJoin;
import siena.QueryOrder;
import siena.QueryOwned;
import siena.SienaException;
import siena.SienaRestrictedApiException;
import siena.Util;
import siena.core.QueryFilterEmbedded;
import siena.core.options.QueryOptionOffset;
import siena.core.options.QueryOptionPage;
import siena.core.options.QueryOptionState;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.SortDirection;

public class GaeQueryUtils {
  public static final Map<String, FilterOperator> operators = new HashMap<String, FilterOperator>() {
    private static final long serialVersionUID = 1L;
    {
      put("=", FilterOperator.EQUAL);
      put("!=", FilterOperator.NOT_EQUAL);
      put("<", FilterOperator.LESS_THAN);
      put(">", FilterOperator.GREATER_THAN);
      put("<=", FilterOperator.LESS_THAN_OR_EQUAL);
      put(">=", FilterOperator.GREATER_THAN_OR_EQUAL);
      put(" IN", FilterOperator.IN);
    }
  };

  public static <T> com.google.appengine.api.datastore.Query
  addFiltersOrders(
      QueryData<T> query,
      com.google.appengine.api.datastore.Query q)
  {
    return addFiltersOrders(query, q, null);
  }
 
  public static <T> com.google.appengine.api.datastore.Query
      addFiltersOrders(
          QueryData<T> query,
          com.google.appengine.api.datastore.Query q, Key parentKey)
  {
    List<QueryFilter> filters = query.getFilters();
    for (QueryFilter filter : filters) {
      if(QueryFilterSimple.class.isAssignableFrom(filter.getClass())){
        QueryFilterSimple qf = (QueryFilterSimple)filter;
        Field f = qf.field;
        String propertyName = ClassInfo.getColumnNames(f)[0];
        Object value = qf.value;
        FilterOperator op = operators.get(qf.operator);
       
        // IN and NOT_EQUAL doesn't allow to use cursors
        if(op == FilterOperator.IN || op == FilterOperator.NOT_EQUAL){
          QueryOptionGaeContext gaeCtx = (QueryOptionGaeContext)query.option(QueryOptionGaeContext.ID);
          if(gaeCtx==null){
            gaeCtx = new QueryOptionGaeContext();
            query.options().put(gaeCtx.type, gaeCtx);
          }
          gaeCtx.useCursor = false;
          query.option(QueryOptionOffset.ID).activate();         
        }
       
        if (value != null && ClassInfo.isModel(value.getClass())) {
          Key key = GaeMappingUtils.getKey(value);
          q.addFilter(propertyName, op, key);
        } else {
          if (ClassInfo.isId(f)) {
            Id id = f.getAnnotation(Id.class);
            switch(id.value()) {
            case NONE:
              if(value != null){
                if(!Collection.class.isAssignableFrom(value.getClass())){
                  // long or string goes toString
                  Key key;
                  if(parentKey == null){
                    key = KeyFactory.createKey(
                      q.getKind(),
                      value.toString());
                  }else {
                    key = KeyFactory.createKey(
                        parentKey,
                        q.getKind(),
                        value.toString());
                  }
                  q.addFilter(Entity.KEY_RESERVED_PROPERTY, op, key);
                }else {
                  List<Key> keys = new ArrayList<Key>();
                  for(Object val: (Collection<?>)value) {
                    if(parentKey == null){
                      keys.add(KeyFactory.createKey(q.getKind(), val.toString()));
                    }else {
                      keys.add(KeyFactory.createKey(parentKey, q.getKind(), val.toString()));
                    }
                  }
                  q.addFilter(Entity.KEY_RESERVED_PROPERTY, op, keys);
                }
              }
              break;
            case AUTO_INCREMENT:
              if(value != null){
                if(!Collection.class.isAssignableFrom(value.getClass())){
                  Key key;
                  Class<?> type = f.getType();
 
                  if(Long.TYPE == type || Long.class.isAssignableFrom(type)){
                    if(parentKey == null){
                      key = KeyFactory.createKey(
                          q.getKind(),
                          (Long)value);
                    }else {
                      key = KeyFactory.createKey(
                          parentKey,
                          q.getKind(),
                          (Long)value);
                    }
                  } else {
                    if(parentKey == null){
                      key = KeyFactory.createKey(
                        q.getKind(),
                        value.toString());
                    }else {
                      key = KeyFactory.createKey(
                          parentKey,
                          q.getKind(),
                          value.toString());
                    }
                  }
                 
                  q.addFilter(Entity.KEY_RESERVED_PROPERTY, op, key);
                }else {
                  List<Key> keys = new ArrayList<Key>();
                  for(Object val: (Collection<?>)value) {
                    if (value instanceof String)
                      val = Long.parseLong((String) val);
                    if(parentKey == null){
                      keys.add(KeyFactory.createKey(q.getKind(), (Long)val));
                    }else {
                      keys.add(KeyFactory.createKey(parentKey, q.getKind(), (Long)val));
                    }
                  }
                  q.addFilter(Entity.KEY_RESERVED_PROPERTY, op, keys);
                }
              }
              break;
            case UUID:
              if(value != null) {
                if(!Collection.class.isAssignableFrom(value.getClass())){
                  // long or string goes toString
                  Key key;
                  if(parentKey == null){
                    key = KeyFactory.createKey(
                        q.getKind(),
                        value.toString());
                  }else {
                    key = KeyFactory.createKey(
                        parentKey,
                        q.getKind(),
                        value.toString());
                  }
                  q.addFilter(Entity.KEY_RESERVED_PROPERTY, op, key);
                }else {
                  List<Key> keys = new ArrayList<Key>();
                  for(Object val: (Collection<?>)value) {
                    keys.add(KeyFactory.createKey(q.getKind(), val.toString()));
                  }
                  q.addFilter(Entity.KEY_RESERVED_PROPERTY, op, keys);
                }
              }
              break;
            default:
              throw new SienaException("Id Generator "+id.value()+ " not supported");
            }
   
          } else if (Enum.class.isAssignableFrom(f.getType())) {
            value = value.toString();
            q.addFilter(propertyName, op, value);
          } else {
            q.addFilter(propertyName, op, 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 GAE: 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(words.length>1){
            for(String word:words){
              if(!pNormal.matcher(word).matches()){
                throw new SienaException("Cannot do a multiwords search with the * operator");
              }
            }
            List<String> wordList = new ArrayList<String>();
            Collections.addAll(wordList, words);
            addSearchFilterIn(q, field, wordList);
          }else {
            // searches for pattern such as "alpha*" or "*alpha" or "alpha"
            Pattern pStart = Pattern.compile("(\\w+)\\*");
   
            String word = words[0];
            Matcher matcher = pStart.matcher(word);
            if(matcher.matches()){
              String realWord = matcher.group(1);
              addSearchFilterBeginsWith(q, field, realWord);
              continue;
            }
           
            matcher = pNormal.matcher(word);
            if(matcher.matches()){
              addSearchFilterEquals(q, field, word);
              continue;
            }
           
            Pattern pEnd = Pattern.compile("\\*(\\w+)");
            matcher = pEnd.matcher(word);
            if(matcher.matches()){
              throw new SienaException("Cannot do a \"*word\" search in GAE");
            }            
          }         
        }catch(Exception e){
          throw new SienaException(e);
        }
        break;
      }else if(QueryFilterEmbedded.class.isAssignableFrom(filter.getClass())){
        QueryFilterEmbedded qf = (QueryFilterEmbedded)filter;
       
        String propName = "";
        int sz = qf.fields.size();
        for(int i=0; i<sz; i++){
          propName += ClassInfo.getSingleColumnName(qf.fields.get(i));
          if(i < sz-1){
            propName += qf.fieldSeparator;
          }
        }
       
        Object value = qf.value;
        FilterOperator op = operators.get(qf.operator);
       
        // IN and NOT_EQUAL doesn't allow to use cursors
        if(op == FilterOperator.IN || op == FilterOperator.NOT_EQUAL){
          QueryOptionGaeContext gaeCtx = (QueryOptionGaeContext)query.option(QueryOptionGaeContext.ID);
          if(gaeCtx==null){
            gaeCtx = new QueryOptionGaeContext();
            query.options().put(gaeCtx.type, gaeCtx);
          }
          gaeCtx.useCursor = false;
          query.option(QueryOptionOffset.ID).activate();         
        }
       
        q.addFilter(propName, op, value);
      }
    }
   
    // adds filter on owners
    List<QueryOwned> ownees = query.getOwnees();
    for (QueryOwned ownee : ownees) {
      String propertyName = ClassInfo.getSimplestColumnName(ownee.field);
      FilterOperator op = operators.get("=");
      Key key = GaeMappingUtils.getKey(ownee.owner);
      q.addFilter(propertyName, op, key);
    }
   
    List<QueryOrder> orders = query.getOrders();
    for (QueryOrder order : orders) {
      Field f = order.field;
      if (ClassInfo.isId(f)) {
        q.addSort(Entity.KEY_RESERVED_PROPERTY,
            order.ascending ? SortDirection.ASCENDING
                : SortDirection.DESCENDING);
      } else {
        q.addSort(ClassInfo.getColumnNames(f)[0],
            order.ascending ? SortDirection.ASCENDING
                : SortDirection.DESCENDING);
      }
    }
   
    return q;
  }

  public static void addSearchFilterBeginsWith(com.google.appengine.api.datastore.Query q, Field field, String match)
  {
    String[] columns = ClassInfo.getColumnNames(field);
    if(columns.length>1)
      throw new SienaException("Search not possible for multi-column fields in GAE: only one field with one column");
    q.addFilter(columns[0], FilterOperator.GREATER_THAN_OR_EQUAL, match);
    q.addFilter(columns[0], FilterOperator.LESS_THAN, match + "\ufffd");
  }

  public static void addSearchFilterEquals(com.google.appengine.api.datastore.Query q, Field field, String match)
  {
    String[] columns = ClassInfo.getColumnNames(field);
    if(columns.length>1)
      throw new SienaException("Search not possible for multi-column fields in GAE: only one field with one column");
    q.addFilter(columns[0], FilterOperator.EQUAL, match);
  }


  public static void addSearchFilterIn(com.google.appengine.api.datastore.Query q, Field field, List<String> matches)
  {
    String[] columns = ClassInfo.getColumnNames(field);
    if(columns.length>1)
      throw new SienaException("Search not possible for multi-column fields in GAE: only one field with one column");
    q.addFilter(columns[0], FilterOperator.IN, matches);
  }

  public static <T> Map<Field, ArrayList<Key>> buildJoinFieldKeysMap(QueryData<T> query){
    List<QueryJoin> joins = query.getJoins();
   
    // join queries
    Map<Field, ArrayList<Key>> fieldMap = new HashMap<Field, ArrayList<Key>>();
    for (QueryJoin join : joins) {
      Field field = join.field;
      if (!ClassInfo.isModel(field.getType())){
        throw new SienaRestrictedApiException(GaePersistenceManager.DB, "join", "Join not possible: Field "+field.getName()+" is not a relation field");
      }
      else if(join.sortFields!=null && join.sortFields.length!=0)
        throw new SienaRestrictedApiException(GaePersistenceManager.DB, "join", "Join not allowed with sort fields");
      fieldMap.put(field, new ArrayList<Key>());
    }
   
    // join annotations
    for(Field field:
      ClassInfo.getClassInfo(query.getQueriedClass()).joinFields)
    {
      fieldMap.put(field, new ArrayList<Key>());
    }
   
    return fieldMap;
  }
 
  public static <T> Map<Field, ArrayList<Key>> buildJoinFieldKeysMap(T model){
    // join queries
    Map<Field, ArrayList<Key>> fieldMap = new HashMap<Field, ArrayList<Key>>();
       
    // join annotations
    for(Field field:
      ClassInfo.getClassInfo(model.getClass()).joinFields)
    {
      fieldMap.put(field, new ArrayList<Key>());
    }
   
    return fieldMap;
  }

  public static <T> void paginate(QueryData<T> query) {
    QueryOptionGaeContext gaeCtx = (QueryOptionGaeContext)query.option(QueryOptionGaeContext.ID);
    QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
    if(gaeCtx==null){
      gaeCtx = new QueryOptionGaeContext();
      query.options().put(gaeCtx.type, gaeCtx);
    }
   
    // resets the realoffset to 0 if stateless
    if(state.isStateless()){
      gaeCtx.realOffset = 0;
    }
  }
 
  public static <T> void nextPage(QueryData<T> query) {
    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
    QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
    QueryOptionGaeContext gaeCtx = (QueryOptionGaeContext)query.option(QueryOptionGaeContext.ID);
    if(gaeCtx==null){
      gaeCtx = new QueryOptionGaeContext();
      query.options().put(gaeCtx.type, gaeCtx);
    }
   
    // if no more data after, doesn't try to go after
    if(gaeCtx.noMoreDataAfter){
      return;
    }
   
    // if no more data before, removes flag to be able and stay there
    if(gaeCtx.noMoreDataBefore){
      gaeCtx.noMoreDataBefore = false;
      return;
    }
   
    if(pag.isPaginating()){
      gaeCtx.realPageSize = pag.pageSize;
      if(state.isStateless()) {
        //QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
        //if(offset.isActive()){
        gaeCtx.realOffset+=pag.pageSize;
        //}
      }     
      else {
        if(!gaeCtx.isActive()){
          QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
          if(!gaeCtx.useCursor){
            // then uses offset (in case of IN or != operators)
            //if(offset.isActive()){
            gaeCtx.realOffset+=pag.pageSize;
            //}
          }
          // if the cursor is used, just passivates the offset
          else {
            offset.passivate();
            // keeps track of the offset anyway
            gaeCtx.realOffset+=pag.pageSize;
          }
        }else {
          QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
          // if there is a next cursor, we prefer using it because it can mean it was a
          // cursor added by a previousPage call which tries to go backward the first page
          if(!gaeCtx.useCursor && !gaeCtx.hasNextCursor()){
            // then uses offset (in case of IN or != operators)
            //if(offset.isActive()){
            gaeCtx.realOffset+=pag.pageSize;
            //}
          }else{
            // forces cursor to be sure it is used
            gaeCtx.useCursor = true;
            String cursor = gaeCtx.nextCursor();
            // if the cursor is null, it means we are back to the first page so we reactivate the offset
            gaeCtx.realOffset+=pag.pageSize;
            if(cursor==null){
              offset.activate();
            }else {
              offset.passivate();
            }
          }
        }
      }
    }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);
    QueryOptionGaeContext gaeCtx = (QueryOptionGaeContext)query.option(QueryOptionGaeContext.ID);
    if(gaeCtx==null){
      gaeCtx = new QueryOptionGaeContext();
      query.options().put(gaeCtx.type, gaeCtx);
    }
   
    // if no more data before, doesn't try to go before
    if(gaeCtx.noMoreDataBefore){
      return;
    }
   
    // if no more data after, removes flag to be able to go before
    if(gaeCtx.noMoreDataAfter){
      gaeCtx.noMoreDataAfter = false;
    }
   
    if(pag.isPaginating()){
      gaeCtx.realPageSize = pag.pageSize;
      if(state.isStateless()) {
        //QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
        //if(offset.isActive()){
          if(gaeCtx.realOffset>=pag.pageSize) {
            gaeCtx.realOffset-=pag.pageSize;
          }
          else {
            gaeCtx.realOffset = 0;
            gaeCtx.noMoreDataBefore = true;
          }
        //}
      }     
      else {
        if(!gaeCtx.isActive()){
          if(!gaeCtx.useCursor){
            // then uses offset (in case of IN or != operators)
            //QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
            //if(offset.isActive()){
              if(gaeCtx.realOffset>=pag.pageSize) {
                gaeCtx.realOffset-=pag.pageSize;
              }
              else {
                gaeCtx.realOffset = 0;
                gaeCtx.noMoreDataBefore = true;
              }
            //}
          }
          // if the cursor is active, verifies this is not the first page
          // with the offset (active or passive) and sets noMoreData in this case
          else {
            //QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
            if(gaeCtx.realOffset==0) {
              gaeCtx.noMoreDataBefore = true;
            }
          }
         
        }else {
          QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
          if(!gaeCtx.useCursor){
            // then uses offset (in case of IN or != operators)
            //if(offset.isActive()){
              if(gaeCtx.realOffset>=pag.pageSize) {
                gaeCtx.realOffset-=pag.pageSize;
              }
              // passivates offset and computes the page before because we are at the first page
              else{
                offset.passivate();
                gaeCtx.noMoreDataBefore = true;               
                previousPage(query);
              }
            //}
          }else{
            String cursor = gaeCtx.previousCursor();
            // if the cursor is null, it means we are back to the first page
            // so we reactivate the offset and deactivate the useCursor
            // and recall the previousPage with the offset mechanism
            if(cursor==null){
              offset.activate();
              gaeCtx.useCursor = false;
              previousPage(query);
            }else {
              offset.passivate();
              gaeCtx.useCursor = true;
              if(gaeCtx.realOffset>=pag.pageSize) {
                gaeCtx.realOffset-=pag.pageSize;
              }
              // passivates offset and computes the page before because we are at the first page
              else{
                gaeCtx.noMoreDataBefore = true;               
                previousPage(query);
              }
              //previousPage(query);
            }
           
          }
        }
      }
    } 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 release(QueryData<T> query) {
    QueryOptionGaeContext gaeCtx = (QueryOptionGaeContext)query.option(QueryOptionGaeContext.ID);

    if(gaeCtx != null){
      gaeCtx.cursors.clear();
      gaeCtx.passivate();
    }
  }
/* 
  public static <T> List<T> mapKeysOnly(QueryData<T> query, Iterable<Entity> entities) {
    Class<?> clazz = query.getQueriedClass();
    @SuppressWarnings("unchecked")
    List<T> result = (List<T>) GaeMappingUtils.mapEntitiesKeysOnly(entities, clazz);
    return result;
  }
 
  public static <T> List<T> mapKeysOnly(QueryData<T> query, QueryResultList<Entity> entities) {
    Class<?> clazz = query.getQueriedClass();
   
    @SuppressWarnings("unchecked")
    List<T> result = (List<T>) GaeMappingUtils.mapEntitiesKeysOnly(entities, clazz);
    return result;
  }
  */
 

TOP

Related Classes of siena.gae.GaeQueryUtils

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.