Package com.vercer.engine.persist.standard

Source Code of com.vercer.engine.persist.standard.StrategyObjectDatastore

package com.vercer.engine.persist.standard;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.QueryResultIterator;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.vercer.engine.persist.FindCommand;
import com.vercer.engine.persist.LoadCommand;
import com.vercer.engine.persist.Path;
import com.vercer.engine.persist.Property;
import com.vercer.engine.persist.PropertyTranslator;
import com.vercer.engine.persist.StoreCommand;
import com.vercer.engine.persist.annotation.Id;
import com.vercer.engine.persist.conversion.DefaultTypeConverter;
import com.vercer.engine.persist.conversion.TypeConverter;
import com.vercer.engine.persist.strategy.ActivationStrategy;
import com.vercer.engine.persist.strategy.CacheStrategy;
import com.vercer.engine.persist.strategy.CombinedStrategy;
import com.vercer.engine.persist.strategy.FieldStrategy;
import com.vercer.engine.persist.strategy.RelationshipStrategy;
import com.vercer.engine.persist.strategy.StorageStrategy;
import com.vercer.engine.persist.translator.ChainedTranslator;
import com.vercer.engine.persist.translator.CoreStringTypesTranslator;
import com.vercer.engine.persist.translator.EnumTranslator;
import com.vercer.engine.persist.translator.ListTranslator;
import com.vercer.engine.persist.translator.MapTranslator;
import com.vercer.engine.persist.translator.NativeDirectTranslator;
import com.vercer.engine.persist.translator.ObjectFieldTranslator;
import com.vercer.engine.persist.translator.PolymorphicTranslator;
import com.vercer.engine.persist.util.Entities;
import com.vercer.engine.persist.util.PropertySets;
import com.vercer.util.Reflection;
import com.vercer.util.reference.ObjectReference;

/**
* Stateful layer responsible for caching key-object references and
* creating a PropertyTranslator that can be configured using Strategy
* instances.
*
* @author John Patterson <john@vercer.com>
*/
public class StrategyObjectDatastore extends BaseObjectDatastore
{
  KeySpecification encodeKeySpec;
  Key decodeKey;
 
  // activation depth cannot be in decode context because it is defined per field
  private Deque<Integer> activationDepthDeque= new ArrayDeque<Integer>();
  private boolean indexed;

  private final PropertyTranslator objectFieldTranslator;
  private final PropertyTranslator embedTranslator;
  private final PropertyTranslator polyMorphicComponentTranslator;
  private final PropertyTranslator parentTranslator;
  private final PropertyTranslator independantTranslator;
  private final PropertyTranslator keyFieldTranslator;
  private final PropertyTranslator childTranslator;
  private final ChainedTranslator valueTranslatorChain;
  private final PropertyTranslator defaultTranslator;

  // TODO refactor this into an InstanceStrategy
  private final KeyCache keyCache;

  /**
   * Flag that indicates we are associating instances with this session so do not store them
   */
  // TODO store key field to do this - remove this flag
  private boolean associating;
  private Object refresh;
 
  private Map<Object, Entity> batched;

  private TypeConverter converter;

  // TODO make all these private when commands have no logic
  protected final RelationshipStrategy relationshipStrategy;
  protected final FieldStrategy fieldStrategy;
  protected final ActivationStrategy activationStrategy;
  protected final StorageStrategy storageStrategy;
  protected final CacheStrategy cacheStrategy;

  public StrategyObjectDatastore(CombinedStrategy strategy)
  {
    this(strategy, strategy, strategy, strategy, strategy);
  }

  public StrategyObjectDatastore(
      RelationshipStrategy relationshipStrategy,
      StorageStrategy storageStrategy,
      CacheStrategy cacheStrategy,
      ActivationStrategy activationStrategy,
      FieldStrategy fieldStrategy)
  {
    // push the default depth onto the stack
    activationDepthDeque.push(Integer.MAX_VALUE);
   
    this.activationStrategy = activationStrategy;
    this.cacheStrategy = cacheStrategy;
    this.fieldStrategy = fieldStrategy;
    this.relationshipStrategy = relationshipStrategy;
    this.storageStrategy = storageStrategy;

    converter = createTypeConverter();

    // the main translator which converts to and from objects
    objectFieldTranslator = new StrategyObjectFieldTranslator(converter);

    valueTranslatorChain = createValueTranslatorChain();

    parentTranslator = new ParentEntityTranslator(this);
    independantTranslator = new EntityTranslator(this);
    keyFieldTranslator = new KeyFieldTranslator(this, valueTranslatorChain, converter);
    childTranslator = new ChildEntityTranslator(this);
    embedTranslator = new ListTranslator(objectFieldTranslator);
    polyMorphicComponentTranslator = new ListTranslator(new MapTranslator(new PolymorphicTranslator(objectFieldTranslator, fieldStrategy), converter));
    defaultTranslator = new ListTranslator(new MapTranslator(new ChainedTranslator(valueTranslatorChain, getFallbackTranslator()), converter));

    keyCache = createKeyCache();
  }

  protected KeyCache createKeyCache()
  {
    return new KeyCache();
  }
 
  protected PropertyTranslator decoder(Entity entity)
  {
    return objectFieldTranslator;
  }

  protected PropertyTranslator encoder(Object instance)
  {
    return objectFieldTranslator;
  }
 
  protected PropertyTranslator decoder(Field field, Set<Property> properties)
  {
    return translator(field);
  }

  protected PropertyTranslator encoder(Field field, Object instance)
  {
    return translator(field);
  }

  protected PropertyTranslator translator(Field field)
  {
    if (storageStrategy.entity(field))
    {
      PropertyTranslator translator;
      if (relationshipStrategy.parent(field))
      {
        translator = parentTranslator;
      }
      else if (relationshipStrategy.child(field))
      {
        translator = childTranslator;
      }
      else
      {
        translator = independantTranslator;
      }
     
//      if (cacheStrategy.cache(field))
//      {
//       
//      }
     
      return translator;
    }
    else if (relationshipStrategy.key(field))
    {
      return keyFieldTranslator;
    }
    else if (storageStrategy.embed(field))
    {
      if (storageStrategy.polymorphic(field))
      {
        return polyMorphicComponentTranslator;
      }
      else
      {
        return embedTranslator;
      }
    }
    else
    {
      return defaultTranslator;
    }
  }

  /**
   * @return The translator which is used if no others are configured
   */
  protected PropertyTranslator getFallbackTranslator()
  {
    return getIndependantTranslator();
  }

  protected TypeConverter createTypeConverter()
  {
    return new DefaultTypeConverter();
  }

  /**
   * @return The translator that is used for single items by default
   */
  protected ChainedTranslator createValueTranslatorChain()
  {
    ChainedTranslator result = new ChainedTranslator();
    result.append(new NativeDirectTranslator());
    result.append(new CoreStringTypesTranslator());
    result.append(new EnumTranslator());
    return result;
  }

  // TODO put this in a class meta data object
  private static final Map<Class<?>, Field> keyFields = new ConcurrentHashMap<Class<?>, Field>();
 
  // null values are not permitted in a concurrent hash map so need a "missing" value
  private static final Field NO_KEY_FIELD;
  static
  {
    try
    {
      NO_KEY_FIELD = StrategyObjectDatastore.class.getDeclaredField("NO_KEY_FIELD");
    }
    catch (Exception e)
    {
      throw new IllegalStateException(e);
    }
  }
 
  /**
   * Potentially store an entity in the datastore.
   */
  protected Key entityToKey(Entity entity)
  {
    // we could be just pretending to store to process the instance to get its key
    if (associating)
    {
      // do not save the entity because we just want the key
      Key key = entity.getKey();
      if (!key.isComplete())
      {
        // incomplete keys are no good to us
        throw new IllegalArgumentException("Associating entity does not have complete key: " + entity);
      }
      return key;
    }
    else if (batched != null)
    {
      // don't store anything yet if we are batching writes
      Key key = entity.getKey();
     
      // referenced entities must have a full key without being stored
      if (!encodeKeySpec.isComplete())
      {
        // incomplete keys are no good to us - we need a key now
        throw new IllegalArgumentException("Must have complete key in batched mode for entity " + entity);
      }

      // we will get the key after put
      return key;
    }
    else
    {
      // actually put the entity in the datastore
      return servicePut(entity);
    }
  }

  protected List<Key> entitiesToKeys(Iterable<Entity> entities)
  {
    // TODO do some of the same stuff as above
    return servicePut(entities);
  }
 
  @SuppressWarnings("unchecked")
  final <T> T entityToInstance(Entity entity, Predicate<Property> filter)
  {
    T instance = (T) keyCache.getInstance(entity.getKey());
    if (instance == null)
    {
      // push a new context
      Key existingDecodeKey = decodeKey;
      decodeKey = entity.getKey();

      Type type = fieldStrategy.kindToType(entity.getKind());

      Set<Property> properties = PropertySets.create(entity.getProperties(), indexed);
     
      // filter out unwanted properties at the lowest level
      if (filter != null)
      {
        properties = Sets.filter(properties, filter);
      }

      // order the properties for efficient separation by field
      properties = new TreeSet<Property>(properties);

      instance = (T) decoder(entity).propertiesToTypesafe(properties, Path.EMPTY_PATH, type);
      if (instance == null)
      {
        throw new IllegalStateException("Could not translate entity " + entity);
      }

      // pop the context
      decodeKey = existingDecodeKey;
    }

    return instance;
  }
 

  final <T> Iterator<T> entitiesToInstances(final Iterator<Entity> entities, final Predicate<Property> filter)
  {
    return new Iterator<T>()
    {
      @Override
      public boolean hasNext()
      {
        return entities.hasNext();
      }

      @SuppressWarnings("unchecked")
      @Override
      public T next()
      {
        return (T) entityToInstance(entities.next(), filter);
      }

      @Override
      public void remove()
      {
        entities.remove();
      }
    };
  }


  @SuppressWarnings("unchecked")
  <T> T keyToInstance(Key key, Predicate<Property> filter)
  {
    T instance = (T) keyCache.getInstance(key);
    if (instance == null)
    {
      Entity entity = keyToEntity(key);
      if (entity == null)
      {
        instance = null;
      }
      else
      {
        instance = (T) entityToInstance(entity, filter);
      }
    }

    return instance;
  }
 
  @SuppressWarnings("unchecked")
  final <T> Map<Key, T> keysToInstances(List<Key> keys, Predicate<Property> filter)
  {
    Map<Key, T> result = new HashMap<Key, T>(keys.size());
    List<Key> missing = null;
    for (Key key : keys)
    {
      T instance = (T) keyCache.getInstance(key);
      if (instance != null)
      {
        result.put(key, instance);
      }
      else
      {
        if (missing == null)
        {
          missing = new ArrayList<Key>(keys.size());
        }
        missing.add(key);
      }
    }
   
    if (!missing.isEmpty())
    {
      Map<Key, Entity> entities = keysToEntities(missing);
      if (!entities.isEmpty())
      {
        Set<Entry<Key, Entity>> entries = entities.entrySet();
        for (Entry<Key, Entity> entry : entries)
        {
          T instance = (T) entityToInstance(entry.getValue(), filter);
          result.put(entry.getKey(), instance);
        }
      }
    }

    return result;
  }

  // TODO make private once cache strategy working
  protected Entity keyToEntity(Key key)
  {
    if (getActivationDepth() > 0)
    {
      try
      {
        return serviceGet(key);
      }
      catch (EntityNotFoundException e)
      {
        return null;
      }
    }
    else
    {
      // don't load entity if it will not be activated - but need one for key
      return new Entity(key);
    }
  }
 
  protected Map<Key, Entity> keysToEntities(Collection<Key> keys)
  {
    // only load entity if we will activate instance
    if (getActivationDepth() > 0)
    {
      return serviceGet(keys);
    }
    else
    {
      // we must return empty entities with the correct kind to instantiate
      HashMap<Key, Entity> result = new HashMap<Key, Entity>();
      for (Key key : keys)
      {
        result.put(key, new Entity(key));
      }
      return result;
    }
  }
 
  // TODO make almost every method private once commands contain no logic
  final Entity createEntity()
  {
    if (encodeKeySpec.isComplete())
    {
      // we have a complete key with id specified
      return new Entity(encodeKeySpec.toKey());
    }
    else
    {
      // we have no id specified so must create entity for auto-generated id
      ObjectReference<Key> parentKeyReference = encodeKeySpec.getParentKeyReference();
      Key parentKey = parentKeyReference == null ? null : parentKeyReference.get();
      return Entities.createEntity(encodeKeySpec.getKind(), null, parentKey);
    }
  }

  protected boolean propertiesIndexedByDefault()
  {
    return true;
  }

  @Override
  public final void disassociate(Object reference)
  {
    keyCache.evictInstance(reference);
  }

  @Override
  public final void disassociateAll()
  {
    keyCache.clear();
  }

  @Override
  public final void associate(Object instance, Key key)
  {
    keyCache.cache(key, instance);
  }

  @Override
  public final void associate(Object instance)
  {
    // convert the instance so we can get its key and children
    associating = true;
    store(instance);
    associating = false;
  }

  @Override
  public final Key associatedKey(Object instance)
  {
    return keyCache.getKey(instance);
  }

  @Override
  public final <T> T load(Class<T> type, Object id, Object parent)
  {
    Object converted;
    if (Number.class.isAssignableFrom(id.getClass()))
    {
      converted = converter.convert(id, Long.class);
    }
    else
    {
      converted = converter.convert(id, String.class);
    }

    Key parentKey = null;
    if (parent != null)
    {
      parentKey = keyCache.getKey(parent);
    }
    return internalLoad(type, converted, parentKey);
  }

  @Override
  public final <I, T> Map<I, T>  loadAll(Class<? extends T> type, Collection<I> ids)
  {
    Map<I, T> result = new HashMap<I, T>(ids.size());
    for (I id : ids)
    {
      // TODO optimise this
      T loaded = load(type, id);
      result.put(id, loaded);
    }

    return result;
  }

  @Override
  public final void update(Object instance)
  {
    Key key = keyCache.getKey(instance);
    if (key == null)
    {
      throw new IllegalArgumentException("Can only update instances loaded from this session");
    }
    internalUpdate(instance, key);
  }

  @Override
  public final void storeOrUpdate(Object instance)
  {
    if (associatedKey(instance) != null)
    {
      update(instance);
    }
    else
    {
      store(instance);
    }
  }

  @Override
  public final void storeOrUpdate(Object instance, Object parent)
  {
    if (associatedKey(instance) != null)
    {
      update(instance);
    }
    else
    {
      store(instance, parent);
    }
  }

  @Override
  public final void delete(Object instance)
  {
    Key key= keyCache.getKey(instance);
    if (key == null)
    {
      throw new IllegalArgumentException("Instance " + instance + " is not associated");
    }
    deleteKeys(Collections.singleton(key));
  }

  @Override
  public final void deleteAll(Collection<?> instances)
  {
    deleteKeys(Collections2.transform(instances, cachedInstanceToKeyFunction));
  }

  /**
   * Either gets exiting key from cache or first stores the instance then returns the key
   */
  Key instanceToKey(Object instance, Key parentKey)
  {
    Key key = keyCache.getKey(instance);
    if (key == null)
    {
      key = internalStore(instance, parentKey);
    }
    return key;
  }
 
  <T> Map<T, Key> instancesToKeys(Collection<T> instances, Object parent)
  {
    Map<T, Key> result = new HashMap<T, Key>(instances.size());
    List<T> missed = new ArrayList<T>(instances.size());
    for (T instance : instances)
    {
      Key key = keyCache.getKey(instance);
      if (key == null)
      {
        missed.add(instance);
      }
      else
      {
        result.put(instance, key);
      }
    }
   
    if (!missed.isEmpty())
    {
      result.putAll(storeAll(missed, parent));
    }
   
    return result;
  }

  @Override
  public final Key store(Object instance, Object parent)
  {
    Key parentKey = null;
    if (parent != null)
    {
      parentKey = keyCache.getKey(parent);
    }
    return internalStore(instance, parentKey);
  }

  final Key internalStore(Object instance, Key parentKey)
  {
    // cache the empty key details now in case a child references back to us
    if (keyCache.getKey(instance) != null)
    {
      throw new IllegalStateException("Cannot store same instance twice: " + instance);
    }
    Entity entity = instanceToEntity(instance, parentKey);
    Key key = entityToKey(entity);
   
    // replace the temp key ObjRef with the full key for this instance
    keyCache.cache(key, instance);
   
    setInstanceId(instance, key);
   
    return key;
  }

  @SuppressWarnings("deprecation")
  private void setInstanceId(Object instance, Key key)
  {
    // TODO share fields with ObjectFieldTranslator
    Field idField = null;
    if (keyFields.containsKey(instance.getClass()))
    {
      idField = keyFields.get(instance.getClass());
    }
    else
    {
      List<Field> fields = Reflection.getAccessibleFields(instance.getClass());
      for (Field field : fields)
      {
        if (field.isAnnotationPresent(com.vercer.engine.persist.annotation.Key.class) ||
          field.isAnnotationPresent(Id.class))
        {
          idField = field;
          break;
        }
      }
     
      if (idField == null)
      {
        idField = NO_KEY_FIELD;
      }
      keyFields.put(instance.getClass(), idField);
    }
   
    try
    {
      // if there is a key field
      if (idField != NO_KEY_FIELD)
      {
        // see if its current value is null or 0
        Object current = idField.get(instance);
        if (current == null || current instanceof Number && ((Number) current).longValue() == 0)
        {
          Class<?> type = idField.getType();
          Object idOrName = key.getId();
         
          // the key name could have been set explicitly when storing
          if (idOrName == null)
          {
            idOrName = key.getName();
          }
         
          // convert the long or String to the declared key type
          Object converted = converter.convert(idOrName, type);
          idField.set(instance, converted);
        }
      }
    }
    catch (Exception e)
    {
      throw new IllegalStateException(e);
    }
  }
 
  @Override
  public final <T> Map<T, Key> storeAll(Collection<? extends T> instances)
  {
    return storeAll(instances, (Key) null);
  }

  @Override
  public final <T> Map<T, Key> storeAll(Collection<? extends T> instances, Object parent)
  {
    // encode the instances to entities
    final Map<T, Entity> entities = instancesToEntities(instances, parent, false);
   
    // actually put them in the datastore and get their keys
    final List<Key> keys = entitiesToKeys(entities.values());
   
    LinkedHashMap<T, Key> result = Maps.newLinkedHashMap();
    Iterator<T> instanceItr = entities.keySet().iterator();
    Iterator<Key> keyItr = keys.iterator();
    while (instanceItr.hasNext())
    {
      // iterate instances and keys in parallel
      T instance = instanceItr.next();
      Key key = keyItr.next();
     
      // replace the temp key ObjRef with the full key for this instance
      keyCache.cache(key, instance);
     
      result.put(instance, key);
    }
    return result;
  }

  @Override
  public final void refresh(Object instance)
  {
    Key key = associatedKey(instance);

    if (key == null)
    {
      throw new IllegalStateException("Instance not associated with session");
    }

    // so it is not loaded from the cache
    disassociate(instance);

    // load will use this instance instead of creating new
    refresh = instance;

    // load from datastore into the refresh instance
    Object loaded = load(key);

    if (loaded == null)
    {
      throw new IllegalStateException("Instance to be refreshed could not be found");
    }
  }
 
  @Override
  public void refreshAll(Collection<?> instances)
  {
    // TODO optimise!
    for (Object instance : instances)
    {
      refresh(instance);
    }
  }

  @Override
  public final int getActivationDepth()
  {
    return activationDepthDeque.peek();
  }

  @Override
  public final void setActivationDepth(int depth)
  {
    activationDepthDeque.pop();
    activationDepthDeque.push(depth);
  }

  protected final PropertyTranslator getIndependantTranslator()
  {
    return independantTranslator;
  }

  protected final PropertyTranslator getChildTranslator()
  {
    return childTranslator;
  }

  protected final PropertyTranslator getParentTranslator()
  {
    return parentTranslator;
  }

  protected final PropertyTranslator getPolyMorphicComponentTranslator()
  {
    return polyMorphicComponentTranslator;
  }

  protected final PropertyTranslator getEmbedTranslator()
  {
    return embedTranslator;
  }

  protected final PropertyTranslator getKeyFieldTranslator()
  {
    return keyFieldTranslator;
  }

  protected final PropertyTranslator getDefaultTranslator()
  {
    return defaultTranslator;
  }
 
  protected final KeyCache getKeyCache()
  {
    return keyCache;
  }

  private final Function<Object, Key> cachedInstanceToKeyFunction = new Function<Object, Key>()
  {
    public Key apply(Object instance)
    {
      return keyCache.getKey(instance);
    }
  };

  @Override
  public final FindCommand find()
  {
    return new StandardFindCommand(this);
  }

  @Override
  public final StoreCommand store()
  {
    return new StandardStoreCommand(this);
  }
 
  @Override
  public void activate(Object... instances)
  {
    activateAll(Arrays.asList(instances));
  }

  @Override
  public void activateAll(Collection<?> instances)
  {
    // TODO optimise this
    for (Object instance : instances)
    {
      refresh(instance);
    }
  }

  protected final void setIndexed(boolean indexed)
  {
    this.indexed = indexed;
  }

  final Entity instanceToEntity(Object instance, Key parentKey)
  {
    String kind = fieldStrategy.typeToKind(instance.getClass());
   
    // push a new encode context
    KeySpecification existingEncodeKeySpec = encodeKeySpec;
    encodeKeySpec = new KeySpecification(kind, parentKey, null);

    keyCache.cacheKeyReferenceForInstance(instance, encodeKeySpec.toObjectReference());
     
    // translate fields to properties - sets parent and id on key
    PropertyTranslator encoder = encoder(instance);
    Set<Property> properties = encoder.typesafeToProperties(instance, Path.EMPTY_PATH, indexed);
    if (properties == null)
    {
      throw new IllegalStateException("Could not translate instance: " + instance);
    }

    // the key will now be set with id and parent
    Entity entity = createEntity();

    transferProperties(entity, properties);
   
    // we can store all entities for a single batch put
    if (batched != null)
    {
      batched.put(instance, entity);
    }
    // pop the encode context
    encodeKeySpec = existingEncodeKeySpec;
   
    return entity;
  }

  @SuppressWarnings("unchecked")
  final <T> Map<T, Entity> instancesToEntities(Collection<? extends T> instances, Object parent, boolean batch)
  {
    Key parentKey = null;
    if (parent != null)
    {
      parentKey= keyCache.getKey(parent);
    }
   
    Map<T, Entity> entities = new LinkedHashMap<T, Entity>(instances.size());
    if (batch)
    {
      batched = (Map<Object, Entity>) entities;
    }

    // TODO optimise
    for (T instance : instances)
    {
      // cannot define a key name
      Entity entity = instanceToEntity(instance, parentKey);
      entities.put(instance, entity);
    }
   
    if (batch)
    {
      batched = null;
    }
   
    return entities;
  }
 
  private void transferProperties(Entity entity, Collection<Property> properties)
  {
    for (Property property : properties)
    {
      // dereference object references
      Object value = property.getValue();
      value = dereferencePropertyValue(value);

      if (property.isIndexed())
      {
        entity.setProperty(property.getPath().toString(), value);
      }
      else
      {
        entity.setUnindexedProperty(property.getPath().toString(), value);
      }
    }
  }

  public static Object dereferencePropertyValue(Object value)
  {
    if (value instanceof ObjectReference<?>)
    {
      value = ((ObjectReference<?>)value).get();
    }
    else if (value instanceof List<?>)
    {
      // we know the value is a mutable list from ListTranslator
      @SuppressWarnings("unchecked")
      List<Object> values = (List<Object>) value;
      for (int i = 0; i < values.size(); i++)
      {
        Object item = values.get(i);
        if (item instanceof ObjectReference<?>)
        {
          // dereference the value and set it in-place
          Object dereferenced = ((ObjectReference<?>) item).get();
          values.set(i, dereferenced)// replace the reference
        }
      }
    }
    return value;
  }
 
  @Override
  public final Key store(Object instance)
  {
    return store(instance, null);
  }

  @Override
  public final <T> T load(Class<T> type, Object key)
  {
    return load(type, key, null);
  }

  protected final <T> T internalLoad(Class<T> type, Object converted, Key parent)
  {
    assert activationDepthDeque.size() == 1;
   
    String kind = fieldStrategy.typeToKind(type);

    Key key;
    if (parent == null)
    {
      if (converted instanceof Long)
      {
        key = KeyFactory.createKey(kind, (Long) converted);
      }
      else
      {
        key = KeyFactory.createKey(kind, (String) converted);
      }
    }
    else
    {
      if (converted instanceof Long)
      {
        key = KeyFactory.createKey(parent, kind, (Long) converted);
      }
      else
      {
        key = KeyFactory.createKey(parent, kind, (String) converted);
      }
    }

    // needed to avoid sun generics bug "no unique maximal instance exists..."
    @SuppressWarnings("unchecked")
    T result = (T) keyToInstance(key, null);
    return result;
  }

  public final <T> QueryResultIterator<T> find(Class<T> type)
  {
    return find().type(type).returnResultsNow();
  }

  public final <T> QueryResultIterator<T> find(Class<T> type, Object ancestor)
  {
    return find().type(type).ancestor(ancestor).returnResultsNow();
  }

  final Query createQuery(Type type)
  {
    return new Query(fieldStrategy.typeToKind(type));
  }

  public final <T> T load(Key key)
  {
    @SuppressWarnings("unchecked")
    T result = (T) keyToInstance(key, null);
    return result;
  }

  final void internalUpdate(Object instance, Key key)
  {
    Entity entity = new Entity(key);
   
    // push a new encode context just to double check values and stop NPEs
    assert encodeKeySpec == null;
    encodeKeySpec = new KeySpecification();

    // translate fields to properties - sets parent and id on key
    Set<Property> properties = encoder(instance).typesafeToProperties(instance, Path.EMPTY_PATH, indexed);
    if (properties == null)
    {
      throw new IllegalStateException("Could not translate instance: " + instance);
    }

    transferProperties(entity, properties);
   
    // we can store all entities for a single batch put
    if (batched != null)
    {
      batched.put(instance, entity);
    }
   
    // pop the encode context
    encodeKeySpec = null;
   
    Key putKey = entityToKey(entity);
   
    assert putKey.equals(key);
  }

  public final void deleteAll(Type type)
  {
    Query query = createQuery(type);
    query.setKeysOnly();
    FetchOptions options = FetchOptions.Builder.withChunkSize(100);
    Iterator<Entity> entities = servicePrepare(query).asIterator(options);
    Iterator<Key> keys = Iterators.transform(entities, entityToKeyFunction);
    Iterator<List<Key>> partitioned = Iterators.partition(keys, 100);
    while (partitioned.hasNext())
    {
      deleteKeys(partitioned.next());
    }
  }

  protected void deleteKeys(Collection<Key> keys)
  {
    serviceDelete(keys);
    for (Key key : keys)
    {
      if (keyCache.containsKey(key))
      {
        keyCache.evictKey(key);
      }
    }
  }

  private final class StrategyObjectFieldTranslator extends ObjectFieldTranslator
  {
    private StrategyObjectFieldTranslator(TypeConverter converters)
    {
      super(converters);
    }
 
    @Override
    protected boolean indexed(Field field)
    {
      return StrategyObjectDatastore.this.storageStrategy.index(field);
    }
 
    @Override
    protected boolean stored(Field field)
    {
      return StrategyObjectDatastore.this.storageStrategy.store(field);
    }
 
    @Override
    protected Type typeFromField(Field field)
    {
      return StrategyObjectDatastore.this.fieldStrategy.typeOf(field);
    }
 
    @Override
    protected String fieldToPartName(Field field)
    {
      return StrategyObjectDatastore.this.fieldStrategy.name(field);
    }
 
    @Override
    protected PropertyTranslator encoder(Field field, Object instance)
    {
      return StrategyObjectDatastore.this.encoder(field, instance);
    }
 
    @Override
    protected PropertyTranslator decoder(Field field, Set<Property> properties)
    {
      return StrategyObjectDatastore.this.decoder(field, properties);
    }
 
    @Override
    protected Object createInstance(Class<?> clazz)
    {
      // if we are refreshing an instance do not create a new one
      Object instance = refresh;
      if (instance == null)
      {
        instance = super.createInstance(clazz);
      }
      refresh = null;
 
      // need to cache the instance immediately so instances can reference it
      if (keyCache.getInstance(decodeKey) == null)
      {
        // only cache first time - not for embedded components
        keyCache.cache(decodeKey, instance);
      }
 
      return instance;
    }
 
    @Override
    protected void onBeforeTranslate(Field field, Set<Property> childProperties)
    {
        if (activationDepthDeque.peek() > 0)
        {
          activationDepthDeque.push(StrategyObjectDatastore.this.activationStrategy.activationDepth(field, activationDepthDeque.peek() - 1));
        }
    }
 
    @Override
    protected void onAfterTranslate(Field field, Object value)
    {
      activationDepthDeque.pop();
    }
 
    @Override
    protected void activate(Set<Property> properties, Object instance, Path path)
    {
      if (getActivationDepth() > 0)
      {
        super.activate(properties, instance, path);
      }
    }
  }

  private static final Function<Entity, Key> entityToKeyFunction = new Function<Entity, Key>()
  {
    public Key apply(Entity arg0)
    {
      return arg0.getKey();
    }
  };

  @Override
  public LoadCommand load()
  {
    throw new UnsupportedOperationException("Not yet implemented");
  }
}
TOP

Related Classes of com.vercer.engine.persist.standard.StrategyObjectDatastore

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.