Package com.google.appengine.datanucleus.scostore

Source Code of com.google.appengine.datanucleus.scostore.FKListStore

/**********************************************************************
Copyright (c) 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
**********************************************************************/
package com.google.appengine.datanucleus.scostore;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.ClassNameConstants;
import org.datanucleus.FetchPlan;
import org.datanucleus.api.ApiAdapter;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusFatalUserException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.OrderMetaData;
import org.datanucleus.metadata.RelationType;
import org.datanucleus.ExecutionContext;
import org.datanucleus.store.FieldValues;
import org.datanucleus.state.ObjectProvider;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.mapped.mapping.MappingConsumer;
import org.datanucleus.store.scostore.ListStore;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceConfig;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
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.datanucleus.DatastoreManager;
import com.google.appengine.datanucleus.DatastoreServiceFactoryInternal;
import com.google.appengine.datanucleus.EntityUtils;
import com.google.appengine.datanucleus.KeyRegistry;
import com.google.appengine.datanucleus.MetaDataUtils;
import com.google.appengine.datanucleus.StorageVersion;
import com.google.appengine.datanucleus.Utils;
import com.google.appengine.datanucleus.query.LazyResult;

/**
* Backing store for lists stored with a "FK" in the element.
*/
public class FKListStore extends AbstractFKStore implements ListStore {
  /** Mapping for the ordering column in the element table. */
  protected JavaTypeMapping orderMapping;

  /** Whether the list is indexed (like with JDO). If false then it will have no orderMapping (like with JPA). */
  protected boolean indexedList = true;

  private final ThreadLocal<Boolean> removing = new ThreadLocal<Boolean>() {
    @Override
    protected Boolean initialValue() {
      return false;
    }
  };

  public FKListStore(AbstractMemberMetaData ownerMmd, DatastoreManager storeMgr, ClassLoaderResolver clr) {
    super(ownerMmd, storeMgr, clr);

    orderMapping = elementTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_INDEX);
    if (ownerMemberMetaData.getOrderMetaData() != null && !ownerMemberMetaData.getOrderMetaData().isIndexedList()) {
        indexedList = false;
    }
    if (!storeMgr.storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS) &&
        orderMapping == null && indexedList) {
      // Early storage version requires that indexedList has an order mapping in the element
      throw new NucleusUserException(LOCALISER.msg("056041",
          ownerMemberMetaData.getAbstractClassMetaData().getFullClassName(), ownerMemberMetaData.getName(), elementType));
    }
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.CollectionStore#hasOrderMapping()
   */
  public boolean hasOrderMapping() {
    return (orderMapping != null);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.CollectionStore#add(org.datanucleus.store.ObjectProvider, java.lang.Object, int)
   */
  public boolean add(ObjectProvider ownerOP, Object element, int currentSize) {
    return internalAdd(ownerOP, 0, true, Collections.singleton(element), currentSize);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.CollectionStore#addAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int)
   */
  public boolean addAll(ObjectProvider ownerOP, Collection elements, int currentSize) {
    return internalAdd(ownerOP, 0, true, elements, currentSize);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#add(org.datanucleus.store.ObjectProvider, java.lang.Object, int, int)
   */
  public void add(ObjectProvider ownerOP, Object element, int index, int currentSize) {
    internalAdd(ownerOP, index, false, Collections.singleton(element), currentSize);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#addAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int, int)
   */
  public boolean addAll(ObjectProvider ownerOP, Collection elements, int index, int currentSize) {
    return internalAdd(ownerOP, index, false, elements, currentSize);
  }

  /**
   * Internal method for adding an item to the List.
   * @param ownerOP Object Provider of the owner of the list
   * @param startAt The start position
   * @param atEnd Whether to add at the end
   * @param elements The Collection of elements to add.
   * @param currentSize Current size of List (if known). -1 if not known
   * @return Whether it was successful
   */
  protected boolean internalAdd(ObjectProvider ownerOP, int startAt, boolean atEnd, Collection elements, int currentSize) {
    boolean success = false;
    if (elements == null || elements.size() == 0) {
      success = true;
    }
    else {
      if (!storeMgr.storageVersionAtLeast(StorageVersion.WRITE_OWNED_CHILD_KEYS_TO_PARENTS)) {
        // Check what we have persistent already
        int currentListSize = 0;
        if (currentSize < 0) {
          // Get the current size from the datastore
          currentListSize = size(ownerOP);
        }
        else {
          currentListSize = currentSize;
        }

        boolean shiftingElements = true;
        if (atEnd || startAt == currentListSize) {
          shiftingElements = false;
          startAt = currentListSize; // Not shifting so we insert from the end
        }

        if (shiftingElements)
        {
          // We need to shift existing elements before positioning the new ones
          try {
            // Calculate the amount we need to shift any existing elements by
            // This is used where inserting between existing elements and have to shift down all elements after the start point
            int shift = elements.size();
            // shift up existing elements after start position by "shift"
            for (int i=currentListSize-1; i>=startAt; i--) {
              internalShift(ownerOP, false, i, shift);
            }
          }
          catch (MappedDatastoreException e) {
            // An error was encountered during the shift process so abort here
            throw new NucleusDataStoreException(LOCALISER.msg("056009", e.getMessage()), e.getCause());
          }
        }
      }

      boolean elementsNeedPositioning = false;
      int position = startAt;
      Iterator elementIter = elements.iterator();
      while (elementIter.hasNext()) {
        Object element = elementIter.next();

        if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
          // Register the parent key for the element when owned
          Key parentKey = EntityUtils.getKeyForObject(ownerOP.getObject(), ownerOP.getExecutionContext());
          KeyRegistry.getKeyRegistry(ownerOP.getExecutionContext()).registerParentKeyForOwnedObject(element, parentKey);
        }

        // Persist any non-persistent objects at their final list position (persistence-by-reachability)
        boolean inserted = validateElementForWriting(ownerOP, element, position);
        if (!inserted) {
          if (!storeMgr.storageVersionAtLeast(StorageVersion.WRITE_OWNED_CHILD_KEYS_TO_PARENTS)) {
            // This element wasn't positioned in the validate so we need to set the positions later
            elementsNeedPositioning = true;
          }
        }
        position++;
      }

      if (elementsNeedPositioning) {
        // Some elements have been shifted so the new elements need positioning now, or we already had some
        // of the new elements persistent and so they need their positions setting now
        elementIter = elements.iterator();
        while (elementIter.hasNext()) {
          Object element = elementIter.next();
          updateElementFk(ownerOP, element, ownerOP.getObject(), startAt);
          startAt++;
        }
      }

      success = true;
    }

    return success;
  }

  protected int[] internalShift(ObjectProvider op, boolean batched, int oldIndex, int amount)
  throws MappedDatastoreException {
    if (orderMapping == null) {
      return null;
    }

    DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
    DatastoreService service = DatastoreServiceFactoryInternal.getDatastoreService(config);
    AbstractClassMetaData acmd = elementCmd;
    String kind =
        storeMgr.getIdentifierFactory().newDatastoreContainerIdentifier(acmd).getIdentifierName();
    Query q = new Query(kind);
    ExecutionContext ec = op.getExecutionContext();
    Object id = ec.getApiAdapter().getTargetKeyForSingleFieldIdentity(op.getInternalObjectId());
    Key key = id instanceof Key ? (Key) id : KeyFactory.stringToKey((String) id);
    q.setAncestor(key);

    // create an entity just to capture the name of the index property
    Entity entity = new Entity(kind);
    orderMapping.setObject(ec, entity, new int[] {1}, oldIndex);
    String indexProp = entity.getProperties().keySet().iterator().next();
    q.addFilter(indexProp, Query.FilterOperator.GREATER_THAN_OR_EQUAL, oldIndex);
    for (Entity shiftMe : service.prepare(service.getCurrentTransaction(null), q).asIterable()) {
      Long pos = (Long) shiftMe.getProperty(indexProp);
      shiftMe.setProperty(indexProp, pos + amount);
      EntityUtils.putEntityIntoDatastore(ec, shiftMe);
    }
    return null;
  }

  /**
   * Utility to update a foreign-key in the element in the case of a unidirectional 1-N relationship.
   * @param op ObjectProvider for the owner
   * @param element The element to update
   * @param owner The owner object to set in the FK
   * @param index The index position (or -1 if not known)
   * @return Whether it was performed successfully
   */
  protected boolean updateElementFk(ObjectProvider op, Object element, Object owner, int index) {
    if (element == null) {
      return false;
    }

    // Keys (and therefore parents) are immutable so we don't need to ever
    // actually update the parent FK, but we do need to check to make sure
    // someone isn't trying to modify the parent FK
    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
      EntityUtils.checkParentage(element, op);
    }

    if (orderMapping == null) {
      return false;
    }

    return true;
  }

  /**
   * Convenience method for whether we should delete elements when clear()/remove() is called.
   * @return Whether to delete an element on call of clear()/remove()
   */
  protected boolean deleteElementsOnRemoveOrClear() {
    boolean deleteElements = false;

    boolean dependent = ownerMemberMetaData.getCollection().isDependentElement();
    if (ownerMemberMetaData.isCascadeRemoveOrphans()) {
      dependent = true;
    }

    if (dependent) {
      // Elements are dependent and can't exist on their own, so delete them all
      NucleusLogger.DATASTORE.debug(LOCALISER.msg("056034"));
      deleteElements = true;
    } else {
      if ((ownerMapping.isNullable() && orderMapping == null) ||
          (ownerMapping.isNullable() && orderMapping != null && orderMapping.isNullable())) {
        // Field isn't dependent, and is nullable, so we'll null it
        NucleusLogger.DATASTORE.debug(LOCALISER.msg("056036"));
        deleteElements = false;
      } else {
        if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
          // Field is not dependent, and not nullable so we just delete the elements
          NucleusLogger.DATASTORE.debug(LOCALISER.msg("056035"));
          deleteElements = true;
        } else {
          // Unowned relation doesn't care since FK is not stored
        }
      }
    }

    return deleteElements;
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.CollectionStore#clear(org.datanucleus.store.ObjectProvider)
   */
  public void clear(ObjectProvider op) {
    boolean deleteElements = deleteElementsOnRemoveOrClear();
    ExecutionContext ec = op.getExecutionContext();
    Iterator elementsIter = iterator(op);
    if (elementsIter != null) {
      while (elementsIter.hasNext()) {
        Object element = elementsIter.next();
        if (ec.getApiAdapter().isPersistable(element) && ec.getApiAdapter().isDeleted(element)) {
          // Element is waiting to be deleted so flush it (it has the FK)
          ObjectProvider objSM = ec.findObjectProvider(element);
          objSM.flush();
        } else {
          if (deleteElements) {
            ec.deleteObjectInternal(element);
          }
        }
      }
    }
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.CollectionStore#iterator(org.datanucleus.store.ObjectProvider)
   */
  public Iterator iterator(ObjectProvider op) {
    return listIterator(op);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#listIterator(org.datanucleus.store.ObjectProvider)
   */
  public ListIterator listIterator(ObjectProvider op) {
    return listIterator(op, -1, -1);
  }

  protected ListIterator listIterator(ObjectProvider op, int startIdx, int endIdx) {
    ExecutionContext ec = op.getExecutionContext();
    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
      // Get child keys from property in owner Entity if the property exists
      Entity datastoreEntity = getOwnerEntity(op);
      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
      if (datastoreEntity.hasProperty(propName)) {
        if (indexedList) {
          return getChildrenFromParentField(op, ec, startIdx, endIdx).listIterator();
        } else if (!MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
          Object value = datastoreEntity.getProperty(propName);
          if (value == null || (value instanceof Collection && ((Collection)value).isEmpty())) {
            // No elements so just return
            return Utils.newArrayList().listIterator();
          }

          return getChildrenByKeys((List<Key>) value, ec); // TODO Use startIdx,endIdx
        } else {
          // TODO Get the objects and then order them in-memory using the order criteria
        }
      } else {
        if (op.getLifecycleState().isDeleted()) {
          // Object has been deleted so just return empty list
          return Utils.newArrayList().listIterator();
        }
      }
    }

    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
      // Get child keys by doing a query with the owner as the parent Entity
      Key parentKey = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), op);
      return getChildrenUsingParentQuery(parentKey, getFilterPredicates(startIdx, endIdx), getSortPredicates(), ec).listIterator();
    } else {
      return Utils.newArrayList().listIterator();
    }
  }

  ListIterator<?> getChildrenByKeys(List<Key> childKeys, final ExecutionContext ec) {
    String kindName = elementTable.getIdentifier().getIdentifierName();
    Query q = new Query(kindName);

    NucleusLogger.PERSISTENCE.debug("Preparing to query for " + childKeys);
    q.addFilter(Entity.KEY_RESERVED_PROPERTY, Query.FilterOperator.IN, childKeys);
    for (Query.SortPredicate sp : getSortPredicates()) {
      q.addSort(sp.getPropertyName(), sp.getDirection());
    }

    DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
    DatastoreService ds = DatastoreServiceFactoryInternal.getDatastoreService(config);

    Utils.Function<Entity, Object> func = new Utils.Function<Entity, java.lang.Object>() {
      @Override
      public Object apply(Entity from) {
        return EntityUtils.entityToPojo(from, elementCmd, clr, ec, false, ec.getFetchPlan());
      }
    };
    return new LazyResult(ds.prepare(q).asIterable(), func, true).listIterator();
  }

  @Override
  public int size(ObjectProvider op) {
    if (storeMgr.storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS) && !indexedList) {
      if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
        // Ordered list can only be done via parent key currently
        return getSizeUsingParentKeyInChildren(op);
      } else {
        throw new NucleusFatalUserException("Dont currently support ordered lists that are unowned");
      }
    }
    return super.size(op);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.CollectionStore#remove(org.datanucleus.store.ObjectProvider, java.lang.Object, int, boolean)
   */
  public boolean remove(ObjectProvider op, Object element, int currentSize, boolean allowCascadeDelete) {
    ExecutionContext ec = op.getExecutionContext();
    if (!validateElementForReading(ec, element)) {
      return false;
    }

    Object elementToRemove = element;
    if (ec.getApiAdapter().isDetached(element)) {
      // Element passed in is detached so find attached version (DON'T attach this object)
      elementToRemove = ec.findObject(ec.getApiAdapter().getIdForObject(element), true, false,
          element.getClass().getName());
    }

    return internalRemove(op, elementToRemove, currentSize);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.CollectionStore#removeAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int)
   */
  public boolean removeAll(ObjectProvider ownerOP, Collection elements, int currentSize) {
    if (elements == null || elements.size() == 0) {
      return false;
    }

    boolean modified = false;
    if (indexedList) {
      // Get the indices of the elements to remove in reverse order (highest first)
      int[] indices = getIndicesOf(ownerOP, elements);

      // Remove each element in turn, doing the shifting of indexes each time
      // TODO : Change this to remove all in one go and then shift once
      for (int i=0;i<indices.length;i++) {
        removeAt(ownerOP, indices[i], -1);
        modified = true;
      }
    }
    else {
      // Ordered List, so remove the elements
      Iterator iter = elements.iterator();
      while (iter.hasNext()) {
        Object element = iter.next();
        boolean mod = internalRemove(ownerOP, element, -1);
        if (mod) {
          modified = true;
        }
      }
    }

    return modified;
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#remove(org.datanucleus.store.ObjectProvider, int, int)
   */
  public Object remove(ObjectProvider ownerOP, int index, int currentSize) {
    Object element = get(ownerOP, index);
    if (indexedList) {
      // Remove the element at this position
      removeAt(ownerOP, index, currentSize);
    }
    else {
      // Ordered list doesn't allow indexed removal so just remove the element
      internalRemove(ownerOP, element, currentSize);
    }

    // TODO This does delete of element, yet internalRemove/removeAt also do
    boolean dependent = ownerMemberMetaData.getCollection().isDependentElement();
    if (ownerMemberMetaData.isCascadeRemoveOrphans()) {
      dependent = true;
    }
    if (dependent && !ownerMemberMetaData.getCollection().isEmbeddedElement()) {
      if (!contains(ownerOP, element)) {
        // Delete the element if it is dependent and doesn't have a duplicate entry in the list
        ownerOP.getExecutionContext().deleteObjectInternal(element);
      }
    }

    return element;
  }

  /**
   * Convenience method to remove the specified element from the List.
   * @param ownerOP ObjectProvider of the owner
   * @param element The element
   * @return Whether the List was modified
   */
  protected boolean internalRemove(ObjectProvider ownerOP, Object element, int size)
  {
    if (indexedList) {
      // Indexed List - The element can be at one position only (no duplicates allowed in FK list)
      int index = indexOf(ownerOP, element);
      if (index == -1) {
        return false;
      }
      removeAt(ownerOP, index, size);
    }
    else {
      // Ordered List - no index so null the FK (if nullable) or delete the element
      ExecutionContext ec = ownerOP.getExecutionContext();
      if (ownerMapping.isNullable()) {
        // Nullify the FK
        ObjectProvider elementSM = ec.findObjectProvider(element);
        if (relationType == RelationType.ONE_TO_MANY_BI) {
          // TODO This is ManagedRelations - move into RelationshipManager
          elementSM.replaceFieldMakeDirty(ownerMemberMetaData.getRelatedMemberMetaData(clr)[0].getAbsoluteFieldNumber(),
              null);
          if (ec.isFlushing()) {
            elementSM.flush();
          }
        } else {
          updateElementFk(ownerOP, element, null, -1);
          if (deleteElementsOnRemoveOrClear()) {
            // TODO If present elsewhere in List then don't delete the element from persistence
            ec.deleteObjectInternal(element);
          }
        }
      }
      else {
        // Delete the element
        ec.deleteObjectInternal(element);
      }
    }

    return true;
  }

  /**
   * Internal method to remove an object at a location in the List.
   * @param ownerOP ObjectProvider for the owner of the list.
   * @param index The location
   * @param size Current size of list (if known). -1 if not known
   */
  protected void removeAt(ObjectProvider ownerOP, int index, int size)
  {
    if (!indexedList) {
      throw new NucleusUserException("Cannot remove an element from a particular position with an ordered list since no indexes exist");
    }

    // Handle delete/nulling of the element - Use thread-local to prevent recurse
    if (removing.get()) {
      return;
    }
    boolean deleteElement = deleteElementsOnRemoveOrClear();
    ExecutionContext ec = ownerOP.getExecutionContext();
    Object element = get(ownerOP, index);
    try {
      removing.set(true);

      if (!deleteElement) {
        // Nullify the index of the element
        ObjectProvider elementOP = ec.findObjectProvider(element);
        if (elementOP != null && !ec.getApiAdapter().isDeleted(element)) {
          Entity elementEntity = getOwnerEntity(elementOP);
          if (!storeMgr.storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS)) {
            // Remove the external index property from the element
            elementEntity.removeProperty(getIndexPropertyName());
          }
          EntityUtils.putEntityIntoDatastore(ec, elementEntity);
        }
      } else {
        // Delete the element
        ec.deleteObjectInternal(element);
      }
    } finally {
      removing.set(false);
    }

    // TODO Don't bother with this if using latest storage version (but update tests too)
    // Not storing element keys in owner, so need to update the index property of following objects
    if (orderMapping != null) {
      // need to shift indexes of following elements down
      DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
      DatastoreService service = DatastoreServiceFactoryInternal.getDatastoreService(config);
      AbstractClassMetaData acmd = elementCmd;
      String kind =
        storeMgr.getIdentifierFactory().newDatastoreContainerIdentifier(acmd).getIdentifierName();
      Query q = new Query(kind);
      Key key = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), ownerOP);
      q.setAncestor(key);

      // create an entity just to capture the name of the index property
      Entity entity = new Entity(kind);
      orderMapping.setObject(ec, entity, new int[] {1}, index);
      String indexProp = entity.getProperties().keySet().iterator().next();
      q.addFilter(indexProp, Query.FilterOperator.GREATER_THAN, index);
      for (Entity shiftMe : service.prepare(service.getCurrentTransaction(null), q).asIterable()) {
        Long pos = (Long) shiftMe.getProperty(indexProp);
        shiftMe.setProperty(indexProp, pos - 1);
        EntityUtils.putEntityIntoDatastore(ec, shiftMe);
      }
    }
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.CollectionStore#update(org.datanucleus.store.ObjectProvider, java.util.Collection)
   */
  public void update(ObjectProvider ownerOP, Collection coll) {
    if (coll == null || coll.isEmpty()) {
      clear(ownerOP);
      return;
    }

    // Find existing elements, and remove any that are no longer present
    Collection existing = new ArrayList();
    Iterator elemIter = iterator(ownerOP);
    while (elemIter.hasNext()) {
      Object elem = elemIter.next();
      if (!coll.contains(elem)) {
        remove(ownerOP, elem, -1, true);
      }
      else {
        existing.add(elem);
      }
    }

    if (existing.equals(coll)) {
      // Existing (after any removals) is same as the specified so job done
      return;
    }

    // TODO Improve this - need to allow for list element position changes etc
    clear(ownerOP);
    addAll(ownerOP, coll, 0);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#get(org.datanucleus.store.ObjectProvider, int)
   */
  public Object get(ObjectProvider op, int index) {
    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
      // Get child keys from field in owner Entity
      ExecutionContext ec = op.getExecutionContext();
      Entity datastoreEntity = getOwnerEntity(op);
      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
      if (datastoreEntity.hasProperty(propName)) {
        Object value = datastoreEntity.getProperty(propName);
        if (value == null) {
          return null;
        }

        List<Key> keys = (List<Key>)value;
        Key indexKey = keys.get(index);
        DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
        DatastoreService ds = DatastoreServiceFactoryInternal.getDatastoreService(config);
        try {
          return EntityUtils.entityToPojo(ds.get(indexKey), elementCmd, clr, ec, false, ec.getFetchPlan());
        } catch (EntityNotFoundException enfe) {
          throw new NucleusDataStoreException("Could not determine entity for index=" + index + " with key=" + indexKey, enfe);
        }
      }
    } else {
      // Earlier storage version, for owned relation, so use parentKey for membership of List
      ListIterator iter = listIterator(op, index, index);
      if (iter == null || !iter.hasNext()) {
        return null;
      }

      if (!indexedList) {
        // Restrict to the actual element since can't be done in the query
        Object obj = null;
        int position = 0;
        while (iter.hasNext()) {
          obj = iter.next();
          if (position == index) {
            return obj;
          }
          position++;
        }
      }

      return iter.next();
    }
    return null;
  }

  /**
   * Utility to find the indices of a collection of elements.
   * The returned list are in reverse order (highest index first).
   * @param op ObjectProvider for the owner of the list
   * @param elements The elements
   * @return The indices of the elements in the List.
   */
  protected int[] getIndicesOf(ObjectProvider op, Collection elements)
  {
    if (elements == null || elements.size() == 0) {
      return null;
    }

    ExecutionContext ec = op.getExecutionContext();
    Iterator iter = elements.iterator();
    while (iter.hasNext()) {
      validateElementForReading(ec, iter.next());
    }

    // Since the datastore doesn't support 'or', we're going to sort the keys in memory.
    // issue an ancestor query that fetches all children between the first key and the last,
    // and then build the array of indices from there. The query may return entities that are
    // not in the elements so we have to be careful.
    if (elements.isEmpty()) {
      return new int[0];
    }

    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
      // Obtain via field of List<Key> in parent
      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
      Entity ownerEntity = getOwnerEntity(op);
      if (ownerEntity.hasProperty(propName)) {
        Object value = ownerEntity.getProperty(propName);
        if (value == null) {
          return new int[0];
        }

        // Convert elements into list of keys to search for
        List<Key> keys = (List<Key>) value;
        Set<Key> elementKeys = Utils.newHashSet();
        for (Object element : elements) {
          Key key = EntityUtils.getKeyForObject(element, ec);
          if (key != null) {
            elementKeys.add(key);
          }
        }

        // Generate indices list for these elements
        int i = 0;
        List<Integer> indicesList = new ArrayList<Integer>();
        for (Key key : keys) {
          if (elementKeys.contains(key)) {
            indicesList.add(i);
          }
          i++;
        }
        int[] indices = new int[indicesList.size()];
        i = 0;
        for (Integer index : indicesList) {
          indices[i++] = index;
        }

        return indices;
      }
    } else {
      // Owned relation in earlier storage version so use parentKey to determine membership of list
      List<Key> keys = Utils.newArrayList();
      Set<Key> keySet = Utils.newHashSet();
      for (Object ele : elements) {
        ApiAdapter apiAdapter = ec.getApiAdapter();
        Object keyOrString =
          apiAdapter.getTargetKeyForSingleFieldIdentity(apiAdapter.getIdForObject(ele));
        Key key = keyOrString instanceof Key ? (Key) keyOrString : KeyFactory.stringToKey((String) keyOrString);
        if (key == null) {
          throw new NucleusUserException("Collection element does not have a primary key.");
        } else if (key.getParent() == null) {
          throw new NucleusUserException("Collection element primary key does not have a parent.");
        }
        keys.add(key);
        keySet.add(key);
      }
      Collections.sort(keys);
      AbstractClassMetaData emd = elementCmd;
      String kind =
        storeMgr.getIdentifierFactory().newDatastoreContainerIdentifier(emd).getIdentifierName();
      Query q = new Query(kind);
      // This is safe because we know we have at least one element and therefore
      // at least one key.
      q.setAncestor(keys.get(0).getParent());
      q.addFilter(
          Entity.KEY_RESERVED_PROPERTY, Query.FilterOperator.GREATER_THAN_OR_EQUAL, keys.get(0));
      q.addFilter(
          Entity.KEY_RESERVED_PROPERTY, Query.FilterOperator.LESS_THAN_OR_EQUAL, keys.get(keys.size() - 1));
      q.addSort(Entity.KEY_RESERVED_PROPERTY, Query.SortDirection.DESCENDING);
      DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
      DatastoreService service = DatastoreServiceFactoryInternal.getDatastoreService(config);
      int[] indices = new int[keys.size()];
      int index = 0;
      for (Entity e : service.prepare(service.getCurrentTransaction(null), q).asIterable()) {
        if (keySet.contains(e.getKey())) {
          Long indexVal = (Long) orderMapping.getObject(ec, e, new int[1]);
          if (indexVal == null) {
            throw new NucleusDataStoreException("Null index value");
          }
          indices[index++] = indexVal.intValue();
        }
      }
      if (index != indices.length) {
        // something was missing in the result set
        throw new NucleusDataStoreException("Too few keys returned.");
      }
      return indices;
    }
    return new int[0];
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#indexOf(org.datanucleus.store.ObjectProvider, java.lang.Object)
   */
  public int indexOf(ObjectProvider op, Object element) {
    ExecutionContext ec = op.getExecutionContext();
    validateElementForReading(ec, element);

    ObjectProvider elementOP = ec.findObjectProvider(element);
    Key elementKey = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), elementOP);
    if (elementKey == null) {
      // Not persistent
      return -1;
    }

    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
      // Return the position using the field of List<Key> in the owner
      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
      Entity ownerEntity = getOwnerEntity(op);
      if (ownerEntity.hasProperty(propName)) {
        Object value = ownerEntity.getProperty(propName);
        if (value == null) {
          return -1;
        }
        List<Key> keys = (List<Key>) value;
        return keys.indexOf(elementKey);
      }
    } else {
      // Owned relation in earlier storage version so use parentKey to determine membership of list (only present once)
      if (elementKey.getParent() == null) {
        throw new NucleusUserException("Element primary-key does not have a parent.");
      }

      DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
      DatastoreService service = DatastoreServiceFactoryInternal.getDatastoreService(config);
      try {
        Entity e = service.get(elementKey);
        Long indexVal = (Long) orderMapping.getObject(ec, e, new int[1]);
        if (indexVal == null) {
          throw new NucleusDataStoreException("Null index value");
        }
        return indexVal.intValue();
      } catch (EntityNotFoundException enfe) {
        throw new NucleusDataStoreException("Could not determine index of entity.", enfe);
      }
    }
    return -1;
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#lastIndexOf(org.datanucleus.store.ObjectProvider, java.lang.Object)
   */
  public int lastIndexOf(ObjectProvider op, Object element) {
    ExecutionContext ec = op.getExecutionContext();
    validateElementForReading(ec, element);
    ObjectProvider elementOP = ec.findObjectProvider(element);
    Key elementKey = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), elementOP);
    if (elementKey == null) {
      // Not persistent
      return -1;
    }

    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
      // Return the position using the field of List<Key> in the owner
      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
      Entity ownerEntity = getOwnerEntity(op);
      if (ownerEntity.hasProperty(propName)) {
        Object value = ownerEntity.getProperty(propName);
        if (value == null) {
          return -1;
        }
        List<Key> keys = (List<Key>) value;
        return keys.lastIndexOf(elementKey);
      }
    }
    // Owned relation in earlier storage version so use parentKey to determine membership of list (only present once)
    return indexOf(op, element);
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#set(org.datanucleus.store.ObjectProvider, int, java.lang.Object, boolean)
   */
  public Object set(ObjectProvider op, int index, Object element, boolean allowCascadeDelete) {
    // Get current element at this position
    Object obj = get(op, index);

    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
      // Register the parent key for the element when owned
      Key parentKey = EntityUtils.getKeyForObject(op.getObject(), op.getExecutionContext());
      KeyRegistry.getKeyRegistry(op.getExecutionContext()).registerParentKeyForOwnedObject(element, parentKey);
    }

    // Make sure the element going to this position is persisted (and give it its index)
    validateElementForWriting(op, element, index);

    // TODO Allow for a user setting position x as element1 and then setting element2 (that used to be there) to position y
    // At the moment we just delete the previous element
    if (ownerMemberMetaData.getCollection().isDependentElement() && allowCascadeDelete && obj != null) {
      op.getExecutionContext().deleteObjectInternal(obj);
    }

    return obj;
  }

  /* (non-Javadoc)
   * @see org.datanucleus.store.scostore.ListStore#subList(org.datanucleus.store.ObjectProvider, int, int)
   */
  public List subList(ObjectProvider op, int startIdx, int endIdx) {
    ListIterator iter = listIterator(op, startIdx, endIdx);
    java.util.List list = new ArrayList();
    while (iter.hasNext()) {
      list.add(iter.next());
    }
    if (!indexedList) {
      if (list.size() > (endIdx-startIdx)) {
        // Iterator hasn't restricted what is returned so do the index range restriction here
        return list.subList(startIdx, endIdx);
      }
    }
    return list;
  }

  private List<Query.FilterPredicate> getFilterPredicates(int startIdx, int endIdx) {
    List<Query.FilterPredicate> filterPredicates = Utils.newArrayList();
    if (indexedList) {
      String indexProperty = getIndexPropertyName();
      if (startIdx >= 0 && endIdx == startIdx) {
        // Particular index required so add restriction
        Query.FilterPredicate filterPred =
            new Query.FilterPredicate(indexProperty, Query.FilterOperator.EQUAL, startIdx);
        filterPredicates.add(filterPred);
      } else if (startIdx != -1 || endIdx != -1) {
        // Add restrictions on start/end indices as required
        if (startIdx >= 0) {
          Query.FilterPredicate filterPred =
              new Query.FilterPredicate(indexProperty, Query.FilterOperator.GREATER_THAN_OR_EQUAL, startIdx);
          filterPredicates.add(filterPred);
        }
        if (endIdx >= 0) {
          Query.FilterPredicate filterPred =
              new Query.FilterPredicate(indexProperty, Query.FilterOperator.LESS_THAN, endIdx);
          filterPredicates.add(filterPred);
        }
      }
    }
    return filterPredicates;
  }

  private String getIndexPropertyName() {
    String propertyName;
    if (orderMapping.getMemberMetaData() == null) {
      // I'm not sure what we should do if this mapping doesn't exist so for now we'll just blow up.
      propertyName =
          orderMapping.getDatastoreMappings()[0].getDatastoreField().getIdentifier().getIdentifierName();
    } else {
      propertyName = orderMapping.getMemberMetaData().getName();
      AbstractMemberMetaData ammd = orderMapping.getMemberMetaData();

      if (ammd.getColumn() != null) {
        propertyName = ammd.getColumn();
      } else if (ammd.getColumnMetaData() != null && ammd.getColumnMetaData().length == 1) {
        propertyName = ammd.getColumnMetaData()[0].getName();
      }
    }
    return propertyName;
  }

  private List<Query.SortPredicate> getSortPredicates() {
    // TODO(maxr) Correctly translate field names to datastore property names
    // (embedded fields, overridden column names, etc.)
    List<Query.SortPredicate> sortPredicates = Utils.newArrayList();
    if (indexedList) {
      // Order by the index column
      String propertyName = getIndexPropertyName();
      Query.SortPredicate sortPredicate =
          new Query.SortPredicate(propertyName, Query.SortDirection.ASCENDING);
      sortPredicates.add(sortPredicate);
    } else {
      for (OrderMetaData.FieldOrder fieldOrder : ownerMemberMetaData.getOrderMetaData().getFieldOrders()) {
        AbstractMemberMetaData orderMmd = elementCmd.getMetaDataForMember(fieldOrder.getFieldName());
        String orderPropName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), orderMmd);
        boolean isPrimaryKey = orderMmd.isPrimaryKey();
        if (isPrimaryKey) {
          if (fieldOrder.isForward() && sortPredicates.isEmpty()) {
            // Don't even bother adding if the first sort is id ASC (this is the
            // default sort so there's no point in making the datastore figure this out).
            break;
          }
          // sorting by id requires us to use a reserved property name
          orderPropName = Entity.KEY_RESERVED_PROPERTY;
        }
        Query.SortPredicate sortPredicate = new Query.SortPredicate(
            orderPropName, fieldOrder.isForward() ? Query.SortDirection.ASCENDING : Query.SortDirection.DESCENDING);
        sortPredicates.add(sortPredicate);
        if (isPrimaryKey) {
          // User wants to sort by pk.  Since pk is guaranteed to be unique, break
          // because we know there's no point in adding any more sort predicates
          break;
        }
      }
    }
    return sortPredicates;
  }

  boolean isPrimaryKey(String propertyName) {
    return elementCmd.getMetaDataForMember(propertyName).isPrimaryKey();
  }

  /**
   * Method to validate that an element is valid for writing to the datastore.
   * TODO Minimise differences to super.validateElementForWriting()
   * @param op ObjectProvider for the owner of the List
   * @param element The element to validate
   * @param index The position that the element is being stored at in the list
   * @return Whether the element was inserted
   */
  protected boolean validateElementForWriting(final ObjectProvider op, Object element, final int index)
  {
    final Object newOwner = op.getObject();

    // Check if element is ok for use in the datastore, specifying any external mappings that may be required
    boolean inserted = super.validateElementForWriting(op.getExecutionContext(), element, new FieldValues()
    {
      public void fetchFields(ObjectProvider elementOP)
      {
        // Find the (element) table storing the FK back to the owner
        boolean isPersistentInterface = storeMgr.getNucleusContext().getMetaDataManager().isPersistentInterface(elementType);
        DatastoreClass elementTable = null;
        if (isPersistentInterface) {
          elementTable = storeMgr.getDatastoreClass(
              storeMgr.getNucleusContext().getMetaDataManager().getImplementationNameForPersistentInterface(elementType), clr);
        }
        else {
          elementTable = storeMgr.getDatastoreClass(elementType, clr);
        }
        if (elementTable == null) {
          // "subclass-table", persisted into table of other class
          AbstractClassMetaData[] managingCmds = storeMgr.getClassesManagingTableForClass(elementCmd, clr);
          if (managingCmds != null && managingCmds.length > 0) {
            // Find which of these subclasses is appropriate for this element
            for (int i=0;i<managingCmds.length;i++) {
              Class tblCls = clr.classForName(managingCmds[i].getFullClassName());
              if (tblCls.isAssignableFrom(elementOP.getObject().getClass())) {
                elementTable = storeMgr.getDatastoreClass(managingCmds[i].getFullClassName(), clr);
                break;
              }
            }
          }
        }

        if (elementTable != null) {
          JavaTypeMapping externalFKMapping = elementTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
          if (externalFKMapping != null) {
            // The element has an external FK mapping so set the value it needs to use in the INSERT
            elementOP.setAssociatedValue(externalFKMapping, op.getObject());
          }

          if (orderMapping != null && index >= 0) {
            if (ownerMemberMetaData.getOrderMetaData() != null && ownerMemberMetaData.getOrderMetaData().getMappedBy() != null) {
              // Order is stored in a field in the element so update it
              // We support mapped-by fields of types int/long/Integer/Long currently
              Object indexValue = null;
              if (orderMapping.getMemberMetaData().getTypeName().equals(ClassNameConstants.JAVA_LANG_LONG) ||
                  orderMapping.getMemberMetaData().getTypeName().equals(ClassNameConstants.LONG)) {
                indexValue = Long.valueOf(index);
              } else {
                indexValue = Integer.valueOf(index);
              }
              elementOP.replaceFieldMakeDirty(orderMapping.getMemberMetaData().getAbsoluteFieldNumber(), indexValue);
            } else {
              // Order is stored in a surrogate column so save its vaue for the element to use later
              elementOP.setAssociatedValue(orderMapping, Integer.valueOf(index));
            }
          }
        }
        if (relationType == RelationType.ONE_TO_MANY_BI) {
          // TODO This is ManagedRelations - move into RelationshipManager
          // Managed Relations : 1-N bidir, so make sure owner is correct at persist
          Object currentOwner = elementOP.provideField(elementMemberMetaData.getAbsoluteFieldNumber());
          if (currentOwner == null) {
            // No owner, so correct it
            NucleusLogger.PERSISTENCE.info(LOCALISER.msg("056037",
                StringUtils.toJVMIDString(op.getObject()), ownerMemberMetaData.getFullFieldName(),
                StringUtils.toJVMIDString(elementOP.getObject())));
            elementOP.replaceFieldMakeDirty(elementMemberMetaData.getAbsoluteFieldNumber(), newOwner);
          }
          else if (currentOwner != newOwner && op.getReferencedPC() == null) {
            // Owner of the element is neither this container nor is it being attached
            // Inconsistent owner, so throw exception
            throw new NucleusUserException(LOCALISER.msg("056038",
                StringUtils.toJVMIDString(op.getObject()), ownerMemberMetaData.getFullFieldName(),
                StringUtils.toJVMIDString(elementOP.getObject()),
                StringUtils.toJVMIDString(currentOwner)));
          }
        }
      }
      public void fetchNonLoadedFields(ObjectProvider elementOP) {}
      public FetchPlan getFetchPlanForLoading() { return null; }
    });

    return inserted;
  }
}
TOP

Related Classes of com.google.appengine.datanucleus.scostore.FKListStore

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.