/src/com/google/appengine/datanucleus/scostore/FKListStore.java
http://datanucleus-appengine.googlecode.com/ · Java · 1119 lines · 798 code · 98 blank · 223 comment · 245 complexity · 6e82f5f100d6c05b9f7a6d8f67942268 MD5 · raw file
- /**********************************************************************
- 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;
- }
- }