PageRenderTime 66ms CodeModel.GetById 2ms app.highlight 55ms RepoModel.GetById 1ms app.codeStats 0ms

/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
   1/**********************************************************************
   2Copyright (c) 2011 Google Inc.
   3
   4Licensed under the Apache License, Version 2.0 (the "License");
   5you may not use this file except in compliance with the License.
   6You may obtain a copy of the License at
   7
   8http://www.apache.org/licenses/LICENSE-2.0
   9
  10Unless required by applicable law or agreed to in writing, software
  11distributed under the License is distributed on an "AS IS" BASIS,
  12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13See the License for the specific language governing permissions and
  14limitations under the License.
  15**********************************************************************/
  16package com.google.appengine.datanucleus.scostore;
  17
  18import java.util.ArrayList;
  19import java.util.Collection;
  20import java.util.Collections;
  21import java.util.Iterator;
  22import java.util.List;
  23import java.util.ListIterator;
  24import java.util.Set;
  25
  26import org.datanucleus.ClassLoaderResolver;
  27import org.datanucleus.ClassNameConstants;
  28import org.datanucleus.FetchPlan;
  29import org.datanucleus.api.ApiAdapter;
  30import org.datanucleus.exceptions.NucleusDataStoreException;
  31import org.datanucleus.exceptions.NucleusFatalUserException;
  32import org.datanucleus.exceptions.NucleusUserException;
  33import org.datanucleus.metadata.AbstractClassMetaData;
  34import org.datanucleus.metadata.AbstractMemberMetaData;
  35import org.datanucleus.metadata.OrderMetaData;
  36import org.datanucleus.metadata.RelationType;
  37import org.datanucleus.ExecutionContext;
  38import org.datanucleus.store.FieldValues;
  39import org.datanucleus.state.ObjectProvider;
  40import org.datanucleus.store.mapped.DatastoreClass;
  41import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
  42import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
  43import org.datanucleus.store.mapped.mapping.MappingConsumer;
  44import org.datanucleus.store.scostore.ListStore;
  45import org.datanucleus.util.NucleusLogger;
  46import org.datanucleus.util.StringUtils;
  47
  48import com.google.appengine.api.datastore.DatastoreService;
  49import com.google.appengine.api.datastore.DatastoreServiceConfig;
  50import com.google.appengine.api.datastore.Entity;
  51import com.google.appengine.api.datastore.EntityNotFoundException;
  52import com.google.appengine.api.datastore.Key;
  53import com.google.appengine.api.datastore.KeyFactory;
  54import com.google.appengine.api.datastore.Query;
  55import com.google.appengine.datanucleus.DatastoreManager;
  56import com.google.appengine.datanucleus.DatastoreServiceFactoryInternal;
  57import com.google.appengine.datanucleus.EntityUtils;
  58import com.google.appengine.datanucleus.KeyRegistry;
  59import com.google.appengine.datanucleus.MetaDataUtils;
  60import com.google.appengine.datanucleus.StorageVersion;
  61import com.google.appengine.datanucleus.Utils;
  62import com.google.appengine.datanucleus.query.LazyResult;
  63
  64/**
  65 * Backing store for lists stored with a "FK" in the element.
  66 */
  67public class FKListStore extends AbstractFKStore implements ListStore {
  68  /** Mapping for the ordering column in the element table. */
  69  protected JavaTypeMapping orderMapping;
  70
  71  /** Whether the list is indexed (like with JDO). If false then it will have no orderMapping (like with JPA). */
  72  protected boolean indexedList = true;
  73
  74  private final ThreadLocal<Boolean> removing = new ThreadLocal<Boolean>() {
  75    @Override
  76    protected Boolean initialValue() {
  77      return false;
  78    }
  79  };
  80
  81  public FKListStore(AbstractMemberMetaData ownerMmd, DatastoreManager storeMgr, ClassLoaderResolver clr) {
  82    super(ownerMmd, storeMgr, clr);
  83
  84    orderMapping = elementTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_INDEX);
  85    if (ownerMemberMetaData.getOrderMetaData() != null && !ownerMemberMetaData.getOrderMetaData().isIndexedList()) {
  86        indexedList = false;
  87    }
  88    if (!storeMgr.storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS) && 
  89        orderMapping == null && indexedList) {
  90      // Early storage version requires that indexedList has an order mapping in the element
  91      throw new NucleusUserException(LOCALISER.msg("056041", 
  92          ownerMemberMetaData.getAbstractClassMetaData().getFullClassName(), ownerMemberMetaData.getName(), elementType));
  93    }
  94  }
  95
  96  /* (non-Javadoc)
  97   * @see org.datanucleus.store.scostore.CollectionStore#hasOrderMapping()
  98   */
  99  public boolean hasOrderMapping() {
 100    return (orderMapping != null);
 101  }
 102
 103  /* (non-Javadoc)
 104   * @see org.datanucleus.store.scostore.CollectionStore#add(org.datanucleus.store.ObjectProvider, java.lang.Object, int)
 105   */
 106  public boolean add(ObjectProvider ownerOP, Object element, int currentSize) {
 107    return internalAdd(ownerOP, 0, true, Collections.singleton(element), currentSize);
 108  }
 109
 110  /* (non-Javadoc)
 111   * @see org.datanucleus.store.scostore.CollectionStore#addAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int)
 112   */
 113  public boolean addAll(ObjectProvider ownerOP, Collection elements, int currentSize) {
 114    return internalAdd(ownerOP, 0, true, elements, currentSize);
 115  }
 116
 117  /* (non-Javadoc)
 118   * @see org.datanucleus.store.scostore.ListStore#add(org.datanucleus.store.ObjectProvider, java.lang.Object, int, int)
 119   */
 120  public void add(ObjectProvider ownerOP, Object element, int index, int currentSize) {
 121    internalAdd(ownerOP, index, false, Collections.singleton(element), currentSize);
 122  }
 123
 124  /* (non-Javadoc)
 125   * @see org.datanucleus.store.scostore.ListStore#addAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int, int)
 126   */
 127  public boolean addAll(ObjectProvider ownerOP, Collection elements, int index, int currentSize) {
 128    return internalAdd(ownerOP, index, false, elements, currentSize);
 129  }
 130
 131  /**
 132   * Internal method for adding an item to the List.
 133   * @param ownerOP Object Provider of the owner of the list
 134   * @param startAt The start position
 135   * @param atEnd Whether to add at the end
 136   * @param elements The Collection of elements to add.
 137   * @param currentSize Current size of List (if known). -1 if not known
 138   * @return Whether it was successful
 139   */
 140  protected boolean internalAdd(ObjectProvider ownerOP, int startAt, boolean atEnd, Collection elements, int currentSize) {
 141    boolean success = false;
 142    if (elements == null || elements.size() == 0) {
 143      success = true;
 144    }
 145    else {
 146      if (!storeMgr.storageVersionAtLeast(StorageVersion.WRITE_OWNED_CHILD_KEYS_TO_PARENTS)) {
 147        // Check what we have persistent already
 148        int currentListSize = 0;
 149        if (currentSize < 0) {
 150          // Get the current size from the datastore
 151          currentListSize = size(ownerOP);
 152        }
 153        else {
 154          currentListSize = currentSize;
 155        }
 156
 157        boolean shiftingElements = true;
 158        if (atEnd || startAt == currentListSize) {
 159          shiftingElements = false;
 160          startAt = currentListSize; // Not shifting so we insert from the end
 161        }
 162
 163        if (shiftingElements)
 164        {
 165          // We need to shift existing elements before positioning the new ones
 166          try {
 167            // Calculate the amount we need to shift any existing elements by
 168            // This is used where inserting between existing elements and have to shift down all elements after the start point
 169            int shift = elements.size();
 170            // shift up existing elements after start position by "shift"
 171            for (int i=currentListSize-1; i>=startAt; i--) {
 172              internalShift(ownerOP, false, i, shift);
 173            }
 174          }
 175          catch (MappedDatastoreException e) {
 176            // An error was encountered during the shift process so abort here
 177            throw new NucleusDataStoreException(LOCALISER.msg("056009", e.getMessage()), e.getCause());
 178          }
 179        }
 180      }
 181
 182      boolean elementsNeedPositioning = false;
 183      int position = startAt;
 184      Iterator elementIter = elements.iterator();
 185      while (elementIter.hasNext()) {
 186        Object element = elementIter.next();
 187
 188        if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
 189          // Register the parent key for the element when owned
 190          Key parentKey = EntityUtils.getKeyForObject(ownerOP.getObject(), ownerOP.getExecutionContext());
 191          KeyRegistry.getKeyRegistry(ownerOP.getExecutionContext()).registerParentKeyForOwnedObject(element, parentKey);
 192        }
 193
 194        // Persist any non-persistent objects at their final list position (persistence-by-reachability)
 195        boolean inserted = validateElementForWriting(ownerOP, element, position);
 196        if (!inserted) {
 197          if (!storeMgr.storageVersionAtLeast(StorageVersion.WRITE_OWNED_CHILD_KEYS_TO_PARENTS)) {
 198            // This element wasn't positioned in the validate so we need to set the positions later
 199            elementsNeedPositioning = true;
 200          }
 201        }
 202        position++;
 203      }
 204
 205      if (elementsNeedPositioning) {
 206        // Some elements have been shifted so the new elements need positioning now, or we already had some
 207        // of the new elements persistent and so they need their positions setting now
 208        elementIter = elements.iterator();
 209        while (elementIter.hasNext()) {
 210          Object element = elementIter.next();
 211          updateElementFk(ownerOP, element, ownerOP.getObject(), startAt);
 212          startAt++;
 213        }
 214      }
 215
 216      success = true;
 217    }
 218
 219    return success;
 220  }
 221
 222  protected int[] internalShift(ObjectProvider op, boolean batched, int oldIndex, int amount) 
 223  throws MappedDatastoreException {
 224    if (orderMapping == null) {
 225      return null;
 226    }
 227
 228    DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
 229    DatastoreService service = DatastoreServiceFactoryInternal.getDatastoreService(config);
 230    AbstractClassMetaData acmd = elementCmd;
 231    String kind =
 232        storeMgr.getIdentifierFactory().newDatastoreContainerIdentifier(acmd).getIdentifierName();
 233    Query q = new Query(kind);
 234    ExecutionContext ec = op.getExecutionContext();
 235    Object id = ec.getApiAdapter().getTargetKeyForSingleFieldIdentity(op.getInternalObjectId());
 236    Key key = id instanceof Key ? (Key) id : KeyFactory.stringToKey((String) id);
 237    q.setAncestor(key);
 238
 239    // create an entity just to capture the name of the index property
 240    Entity entity = new Entity(kind);
 241    orderMapping.setObject(ec, entity, new int[] {1}, oldIndex);
 242    String indexProp = entity.getProperties().keySet().iterator().next();
 243    q.addFilter(indexProp, Query.FilterOperator.GREATER_THAN_OR_EQUAL, oldIndex);
 244    for (Entity shiftMe : service.prepare(service.getCurrentTransaction(null), q).asIterable()) {
 245      Long pos = (Long) shiftMe.getProperty(indexProp);
 246      shiftMe.setProperty(indexProp, pos + amount);
 247      EntityUtils.putEntityIntoDatastore(ec, shiftMe);
 248    }
 249    return null;
 250  }
 251
 252  /**
 253   * Utility to update a foreign-key in the element in the case of a unidirectional 1-N relationship.
 254   * @param op ObjectProvider for the owner
 255   * @param element The element to update
 256   * @param owner The owner object to set in the FK
 257   * @param index The index position (or -1 if not known)
 258   * @return Whether it was performed successfully
 259   */
 260  protected boolean updateElementFk(ObjectProvider op, Object element, Object owner, int index) {
 261    if (element == null) {
 262      return false;
 263    }
 264
 265    // Keys (and therefore parents) are immutable so we don't need to ever
 266    // actually update the parent FK, but we do need to check to make sure
 267    // someone isn't trying to modify the parent FK
 268    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
 269      EntityUtils.checkParentage(element, op);
 270    }
 271
 272    if (orderMapping == null) {
 273      return false;
 274    }
 275
 276    return true;
 277  }
 278
 279  /**
 280   * Convenience method for whether we should delete elements when clear()/remove() is called.
 281   * @return Whether to delete an element on call of clear()/remove()
 282   */
 283  protected boolean deleteElementsOnRemoveOrClear() {
 284    boolean deleteElements = false;
 285
 286    boolean dependent = ownerMemberMetaData.getCollection().isDependentElement();
 287    if (ownerMemberMetaData.isCascadeRemoveOrphans()) {
 288      dependent = true;
 289    }
 290
 291    if (dependent) {
 292      // Elements are dependent and can't exist on their own, so delete them all
 293      NucleusLogger.DATASTORE.debug(LOCALISER.msg("056034"));
 294      deleteElements = true;
 295    } else {
 296      if ((ownerMapping.isNullable() && orderMapping == null) ||
 297          (ownerMapping.isNullable() && orderMapping != null && orderMapping.isNullable())) {
 298        // Field isn't dependent, and is nullable, so we'll null it
 299        NucleusLogger.DATASTORE.debug(LOCALISER.msg("056036"));
 300        deleteElements = false;
 301      } else {
 302        if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
 303          // Field is not dependent, and not nullable so we just delete the elements
 304          NucleusLogger.DATASTORE.debug(LOCALISER.msg("056035"));
 305          deleteElements = true;
 306        } else {
 307          // Unowned relation doesn't care since FK is not stored
 308        }
 309      }
 310    }
 311
 312    return deleteElements;
 313  }
 314
 315  /* (non-Javadoc)
 316   * @see org.datanucleus.store.scostore.CollectionStore#clear(org.datanucleus.store.ObjectProvider)
 317   */
 318  public void clear(ObjectProvider op) {
 319    boolean deleteElements = deleteElementsOnRemoveOrClear();
 320    ExecutionContext ec = op.getExecutionContext();
 321    Iterator elementsIter = iterator(op);
 322    if (elementsIter != null) {
 323      while (elementsIter.hasNext()) {
 324        Object element = elementsIter.next();
 325        if (ec.getApiAdapter().isPersistable(element) && ec.getApiAdapter().isDeleted(element)) {
 326          // Element is waiting to be deleted so flush it (it has the FK)
 327          ObjectProvider objSM = ec.findObjectProvider(element);
 328          objSM.flush();
 329        } else {
 330          if (deleteElements) {
 331            ec.deleteObjectInternal(element);
 332          }
 333        }
 334      }
 335    }
 336  }
 337
 338  /* (non-Javadoc)
 339   * @see org.datanucleus.store.scostore.CollectionStore#iterator(org.datanucleus.store.ObjectProvider)
 340   */
 341  public Iterator iterator(ObjectProvider op) {
 342    return listIterator(op);
 343  }
 344
 345  /* (non-Javadoc)
 346   * @see org.datanucleus.store.scostore.ListStore#listIterator(org.datanucleus.store.ObjectProvider)
 347   */
 348  public ListIterator listIterator(ObjectProvider op) {
 349    return listIterator(op, -1, -1);
 350  }
 351
 352  protected ListIterator listIterator(ObjectProvider op, int startIdx, int endIdx) {
 353    ExecutionContext ec = op.getExecutionContext();
 354    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
 355      // Get child keys from property in owner Entity if the property exists
 356      Entity datastoreEntity = getOwnerEntity(op);
 357      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
 358      if (datastoreEntity.hasProperty(propName)) {
 359        if (indexedList) {
 360          return getChildrenFromParentField(op, ec, startIdx, endIdx).listIterator();
 361        } else if (!MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
 362          Object value = datastoreEntity.getProperty(propName);
 363          if (value == null || (value instanceof Collection && ((Collection)value).isEmpty())) {
 364            // No elements so just return
 365            return Utils.newArrayList().listIterator();
 366          }
 367
 368          return getChildrenByKeys((List<Key>) value, ec); // TODO Use startIdx,endIdx
 369        } else {
 370          // TODO Get the objects and then order them in-memory using the order criteria
 371        }
 372      } else {
 373        if (op.getLifecycleState().isDeleted()) {
 374          // Object has been deleted so just return empty list
 375          return Utils.newArrayList().listIterator();
 376        }
 377      }
 378    }
 379
 380    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
 381      // Get child keys by doing a query with the owner as the parent Entity
 382      Key parentKey = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), op);
 383      return getChildrenUsingParentQuery(parentKey, getFilterPredicates(startIdx, endIdx), getSortPredicates(), ec).listIterator();
 384    } else {
 385      return Utils.newArrayList().listIterator();
 386    }
 387  }
 388
 389  ListIterator<?> getChildrenByKeys(List<Key> childKeys, final ExecutionContext ec) {
 390    String kindName = elementTable.getIdentifier().getIdentifierName();
 391    Query q = new Query(kindName);
 392
 393    NucleusLogger.PERSISTENCE.debug("Preparing to query for " + childKeys);
 394    q.addFilter(Entity.KEY_RESERVED_PROPERTY, Query.FilterOperator.IN, childKeys);
 395    for (Query.SortPredicate sp : getSortPredicates()) {
 396      q.addSort(sp.getPropertyName(), sp.getDirection());
 397    }
 398
 399    DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
 400    DatastoreService ds = DatastoreServiceFactoryInternal.getDatastoreService(config);
 401
 402    Utils.Function<Entity, Object> func = new Utils.Function<Entity, java.lang.Object>() {
 403      @Override
 404      public Object apply(Entity from) {
 405        return EntityUtils.entityToPojo(from, elementCmd, clr, ec, false, ec.getFetchPlan());
 406      }
 407    };
 408    return new LazyResult(ds.prepare(q).asIterable(), func, true).listIterator();
 409  }
 410
 411  @Override
 412  public int size(ObjectProvider op) {
 413    if (storeMgr.storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS) && !indexedList) {
 414      if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
 415        // Ordered list can only be done via parent key currently
 416        return getSizeUsingParentKeyInChildren(op);
 417      } else {
 418        throw new NucleusFatalUserException("Dont currently support ordered lists that are unowned");
 419      }
 420    }
 421    return super.size(op);
 422  }
 423
 424  /* (non-Javadoc)
 425   * @see org.datanucleus.store.scostore.CollectionStore#remove(org.datanucleus.store.ObjectProvider, java.lang.Object, int, boolean)
 426   */
 427  public boolean remove(ObjectProvider op, Object element, int currentSize, boolean allowCascadeDelete) {
 428    ExecutionContext ec = op.getExecutionContext();
 429    if (!validateElementForReading(ec, element)) {
 430      return false;
 431    }
 432
 433    Object elementToRemove = element;
 434    if (ec.getApiAdapter().isDetached(element)) {
 435      // Element passed in is detached so find attached version (DON'T attach this object)
 436      elementToRemove = ec.findObject(ec.getApiAdapter().getIdForObject(element), true, false,
 437          element.getClass().getName());
 438    }
 439
 440    return internalRemove(op, elementToRemove, currentSize);
 441  }
 442
 443  /* (non-Javadoc)
 444   * @see org.datanucleus.store.scostore.CollectionStore#removeAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int)
 445   */
 446  public boolean removeAll(ObjectProvider ownerOP, Collection elements, int currentSize) {
 447    if (elements == null || elements.size() == 0) {
 448      return false;
 449    }
 450
 451    boolean modified = false;
 452    if (indexedList) {
 453      // Get the indices of the elements to remove in reverse order (highest first)
 454      int[] indices = getIndicesOf(ownerOP, elements);
 455
 456      // Remove each element in turn, doing the shifting of indexes each time
 457      // TODO : Change this to remove all in one go and then shift once
 458      for (int i=0;i<indices.length;i++) {
 459        removeAt(ownerOP, indices[i], -1);
 460        modified = true;
 461      }
 462    }
 463    else {
 464      // Ordered List, so remove the elements
 465      Iterator iter = elements.iterator();
 466      while (iter.hasNext()) {
 467        Object element = iter.next();
 468        boolean mod = internalRemove(ownerOP, element, -1);
 469        if (mod) {
 470          modified = true;
 471        }
 472      }
 473    }
 474
 475    return modified;
 476  }
 477
 478  /* (non-Javadoc)
 479   * @see org.datanucleus.store.scostore.ListStore#remove(org.datanucleus.store.ObjectProvider, int, int)
 480   */
 481  public Object remove(ObjectProvider ownerOP, int index, int currentSize) {
 482    Object element = get(ownerOP, index);
 483    if (indexedList) {
 484      // Remove the element at this position
 485      removeAt(ownerOP, index, currentSize);
 486    }
 487    else {
 488      // Ordered list doesn't allow indexed removal so just remove the element
 489      internalRemove(ownerOP, element, currentSize);
 490    }
 491
 492    // TODO This does delete of element, yet internalRemove/removeAt also do
 493    boolean dependent = ownerMemberMetaData.getCollection().isDependentElement();
 494    if (ownerMemberMetaData.isCascadeRemoveOrphans()) {
 495      dependent = true;
 496    }
 497    if (dependent && !ownerMemberMetaData.getCollection().isEmbeddedElement()) {
 498      if (!contains(ownerOP, element)) {
 499        // Delete the element if it is dependent and doesn't have a duplicate entry in the list
 500        ownerOP.getExecutionContext().deleteObjectInternal(element);
 501      }
 502    }
 503
 504    return element;
 505  }
 506
 507  /**
 508   * Convenience method to remove the specified element from the List.
 509   * @param ownerOP ObjectProvider of the owner
 510   * @param element The element
 511   * @return Whether the List was modified
 512   */
 513  protected boolean internalRemove(ObjectProvider ownerOP, Object element, int size)
 514  {
 515    if (indexedList) {
 516      // Indexed List - The element can be at one position only (no duplicates allowed in FK list)
 517      int index = indexOf(ownerOP, element);
 518      if (index == -1) {
 519        return false;
 520      }
 521      removeAt(ownerOP, index, size);
 522    }
 523    else {
 524      // Ordered List - no index so null the FK (if nullable) or delete the element
 525      ExecutionContext ec = ownerOP.getExecutionContext();
 526      if (ownerMapping.isNullable()) {
 527        // Nullify the FK
 528        ObjectProvider elementSM = ec.findObjectProvider(element);
 529        if (relationType == RelationType.ONE_TO_MANY_BI) {
 530          // TODO This is ManagedRelations - move into RelationshipManager
 531          elementSM.replaceFieldMakeDirty(ownerMemberMetaData.getRelatedMemberMetaData(clr)[0].getAbsoluteFieldNumber(), 
 532              null);
 533          if (ec.isFlushing()) {
 534            elementSM.flush();
 535          }
 536        } else {
 537          updateElementFk(ownerOP, element, null, -1);
 538          if (deleteElementsOnRemoveOrClear()) {
 539            // TODO If present elsewhere in List then don't delete the element from persistence
 540            ec.deleteObjectInternal(element);
 541          }
 542        }
 543      }
 544      else {
 545        // Delete the element
 546        ec.deleteObjectInternal(element);
 547      }
 548    }
 549
 550    return true;
 551  }
 552
 553  /**
 554   * Internal method to remove an object at a location in the List.
 555   * @param ownerOP ObjectProvider for the owner of the list.
 556   * @param index The location
 557   * @param size Current size of list (if known). -1 if not known
 558   */
 559  protected void removeAt(ObjectProvider ownerOP, int index, int size)
 560  {
 561    if (!indexedList) {
 562      throw new NucleusUserException("Cannot remove an element from a particular position with an ordered list since no indexes exist");
 563    }
 564
 565    // Handle delete/nulling of the element - Use thread-local to prevent recurse
 566    if (removing.get()) {
 567      return;
 568    }
 569    boolean deleteElement = deleteElementsOnRemoveOrClear();
 570    ExecutionContext ec = ownerOP.getExecutionContext();
 571    Object element = get(ownerOP, index);
 572    try {
 573      removing.set(true);
 574
 575      if (!deleteElement) {
 576        // Nullify the index of the element
 577        ObjectProvider elementOP = ec.findObjectProvider(element);
 578        if (elementOP != null && !ec.getApiAdapter().isDeleted(element)) {
 579          Entity elementEntity = getOwnerEntity(elementOP);
 580          if (!storeMgr.storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS)) {
 581            // Remove the external index property from the element
 582            elementEntity.removeProperty(getIndexPropertyName());
 583          }
 584          EntityUtils.putEntityIntoDatastore(ec, elementEntity);
 585        }
 586      } else {
 587        // Delete the element
 588        ec.deleteObjectInternal(element);
 589      }
 590    } finally {
 591      removing.set(false);
 592    }
 593
 594    // TODO Don't bother with this if using latest storage version (but update tests too)
 595    // Not storing element keys in owner, so need to update the index property of following objects
 596    if (orderMapping != null) {
 597      // need to shift indexes of following elements down
 598      DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
 599      DatastoreService service = DatastoreServiceFactoryInternal.getDatastoreService(config);
 600      AbstractClassMetaData acmd = elementCmd;
 601      String kind =
 602        storeMgr.getIdentifierFactory().newDatastoreContainerIdentifier(acmd).getIdentifierName();
 603      Query q = new Query(kind);
 604      Key key = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), ownerOP);
 605      q.setAncestor(key);
 606
 607      // create an entity just to capture the name of the index property
 608      Entity entity = new Entity(kind);
 609      orderMapping.setObject(ec, entity, new int[] {1}, index);
 610      String indexProp = entity.getProperties().keySet().iterator().next();
 611      q.addFilter(indexProp, Query.FilterOperator.GREATER_THAN, index);
 612      for (Entity shiftMe : service.prepare(service.getCurrentTransaction(null), q).asIterable()) {
 613        Long pos = (Long) shiftMe.getProperty(indexProp);
 614        shiftMe.setProperty(indexProp, pos - 1);
 615        EntityUtils.putEntityIntoDatastore(ec, shiftMe);
 616      }
 617    }
 618  }
 619
 620  /* (non-Javadoc)
 621   * @see org.datanucleus.store.scostore.CollectionStore#update(org.datanucleus.store.ObjectProvider, java.util.Collection)
 622   */
 623  public void update(ObjectProvider ownerOP, Collection coll) {
 624    if (coll == null || coll.isEmpty()) {
 625      clear(ownerOP);
 626      return;
 627    }
 628
 629    // Find existing elements, and remove any that are no longer present
 630    Collection existing = new ArrayList();
 631    Iterator elemIter = iterator(ownerOP);
 632    while (elemIter.hasNext()) {
 633      Object elem = elemIter.next();
 634      if (!coll.contains(elem)) {
 635        remove(ownerOP, elem, -1, true);
 636      }
 637      else {
 638        existing.add(elem);
 639      }
 640    }
 641
 642    if (existing.equals(coll)) {
 643      // Existing (after any removals) is same as the specified so job done
 644      return;
 645    }
 646
 647    // TODO Improve this - need to allow for list element position changes etc
 648    clear(ownerOP);
 649    addAll(ownerOP, coll, 0);
 650  }
 651
 652  /* (non-Javadoc)
 653   * @see org.datanucleus.store.scostore.ListStore#get(org.datanucleus.store.ObjectProvider, int)
 654   */
 655  public Object get(ObjectProvider op, int index) {
 656    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
 657      // Get child keys from field in owner Entity
 658      ExecutionContext ec = op.getExecutionContext();
 659      Entity datastoreEntity = getOwnerEntity(op);
 660      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
 661      if (datastoreEntity.hasProperty(propName)) {
 662        Object value = datastoreEntity.getProperty(propName);
 663        if (value == null) {
 664          return null;
 665        }
 666
 667        List<Key> keys = (List<Key>)value;
 668        Key indexKey = keys.get(index);
 669        DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
 670        DatastoreService ds = DatastoreServiceFactoryInternal.getDatastoreService(config);
 671        try {
 672          return EntityUtils.entityToPojo(ds.get(indexKey), elementCmd, clr, ec, false, ec.getFetchPlan());
 673        } catch (EntityNotFoundException enfe) {
 674          throw new NucleusDataStoreException("Could not determine entity for index=" + index + " with key=" + indexKey, enfe);
 675        }
 676      }
 677    } else {
 678      // Earlier storage version, for owned relation, so use parentKey for membership of List
 679      ListIterator iter = listIterator(op, index, index);
 680      if (iter == null || !iter.hasNext()) {
 681        return null;
 682      }
 683
 684      if (!indexedList) {
 685        // Restrict to the actual element since can't be done in the query
 686        Object obj = null;
 687        int position = 0;
 688        while (iter.hasNext()) {
 689          obj = iter.next();
 690          if (position == index) {
 691            return obj;
 692          }
 693          position++;
 694        }
 695      }
 696
 697      return iter.next();
 698    }
 699    return null;
 700  }
 701
 702  /**
 703   * Utility to find the indices of a collection of elements.
 704   * The returned list are in reverse order (highest index first).
 705   * @param op ObjectProvider for the owner of the list
 706   * @param elements The elements
 707   * @return The indices of the elements in the List.
 708   */
 709  protected int[] getIndicesOf(ObjectProvider op, Collection elements)
 710  {
 711    if (elements == null || elements.size() == 0) {
 712      return null;
 713    }
 714
 715    ExecutionContext ec = op.getExecutionContext();
 716    Iterator iter = elements.iterator();
 717    while (iter.hasNext()) {
 718      validateElementForReading(ec, iter.next());
 719    }
 720
 721    // Since the datastore doesn't support 'or', we're going to sort the keys in memory.
 722    // issue an ancestor query that fetches all children between the first key and the last, 
 723    // and then build the array of indices from there. The query may return entities that are 
 724    // not in the elements so we have to be careful.
 725    if (elements.isEmpty()) {
 726      return new int[0];
 727    }
 728
 729    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
 730      // Obtain via field of List<Key> in parent
 731      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
 732      Entity ownerEntity = getOwnerEntity(op);
 733      if (ownerEntity.hasProperty(propName)) {
 734        Object value = ownerEntity.getProperty(propName);
 735        if (value == null) {
 736          return new int[0];
 737        }
 738
 739        // Convert elements into list of keys to search for
 740        List<Key> keys = (List<Key>) value;
 741        Set<Key> elementKeys = Utils.newHashSet();
 742        for (Object element : elements) {
 743          Key key = EntityUtils.getKeyForObject(element, ec);
 744          if (key != null) {
 745            elementKeys.add(key);
 746          }
 747        }
 748
 749        // Generate indices list for these elements
 750        int i = 0;
 751        List<Integer> indicesList = new ArrayList<Integer>();
 752        for (Key key : keys) {
 753          if (elementKeys.contains(key)) {
 754            indicesList.add(i);
 755          }
 756          i++;
 757        }
 758        int[] indices = new int[indicesList.size()];
 759        i = 0;
 760        for (Integer index : indicesList) {
 761          indices[i++] = index;
 762        }
 763
 764        return indices;
 765      }
 766    } else {
 767      // Owned relation in earlier storage version so use parentKey to determine membership of list
 768      List<Key> keys = Utils.newArrayList();
 769      Set<Key> keySet = Utils.newHashSet();
 770      for (Object ele : elements) {
 771        ApiAdapter apiAdapter = ec.getApiAdapter();
 772        Object keyOrString =
 773          apiAdapter.getTargetKeyForSingleFieldIdentity(apiAdapter.getIdForObject(ele));
 774        Key key = keyOrString instanceof Key ? (Key) keyOrString : KeyFactory.stringToKey((String) keyOrString);
 775        if (key == null) {
 776          throw new NucleusUserException("Collection element does not have a primary key.");
 777        } else if (key.getParent() == null) {
 778          throw new NucleusUserException("Collection element primary key does not have a parent.");
 779        }
 780        keys.add(key);
 781        keySet.add(key);
 782      }
 783      Collections.sort(keys);
 784      AbstractClassMetaData emd = elementCmd;
 785      String kind =
 786        storeMgr.getIdentifierFactory().newDatastoreContainerIdentifier(emd).getIdentifierName();
 787      Query q = new Query(kind);
 788      // This is safe because we know we have at least one element and therefore
 789      // at least one key.
 790      q.setAncestor(keys.get(0).getParent());
 791      q.addFilter(
 792          Entity.KEY_RESERVED_PROPERTY, Query.FilterOperator.GREATER_THAN_OR_EQUAL, keys.get(0));
 793      q.addFilter(
 794          Entity.KEY_RESERVED_PROPERTY, Query.FilterOperator.LESS_THAN_OR_EQUAL, keys.get(keys.size() - 1));
 795      q.addSort(Entity.KEY_RESERVED_PROPERTY, Query.SortDirection.DESCENDING);
 796      DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
 797      DatastoreService service = DatastoreServiceFactoryInternal.getDatastoreService(config);
 798      int[] indices = new int[keys.size()];
 799      int index = 0;
 800      for (Entity e : service.prepare(service.getCurrentTransaction(null), q).asIterable()) {
 801        if (keySet.contains(e.getKey())) {
 802          Long indexVal = (Long) orderMapping.getObject(ec, e, new int[1]);
 803          if (indexVal == null) {
 804            throw new NucleusDataStoreException("Null index value");
 805          }
 806          indices[index++] = indexVal.intValue();
 807        }
 808      }
 809      if (index != indices.length) {
 810        // something was missing in the result set
 811        throw new NucleusDataStoreException("Too few keys returned.");
 812      }
 813      return indices;
 814    }
 815    return new int[0];
 816  }
 817
 818  /* (non-Javadoc)
 819   * @see org.datanucleus.store.scostore.ListStore#indexOf(org.datanucleus.store.ObjectProvider, java.lang.Object)
 820   */
 821  public int indexOf(ObjectProvider op, Object element) {
 822    ExecutionContext ec = op.getExecutionContext();
 823    validateElementForReading(ec, element);
 824
 825    ObjectProvider elementOP = ec.findObjectProvider(element);
 826    Key elementKey = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), elementOP);
 827    if (elementKey == null) {
 828      // Not persistent
 829      return -1;
 830    }
 831
 832    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
 833      // Return the position using the field of List<Key> in the owner
 834      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
 835      Entity ownerEntity = getOwnerEntity(op);
 836      if (ownerEntity.hasProperty(propName)) {
 837        Object value = ownerEntity.getProperty(propName);
 838        if (value == null) {
 839          return -1;
 840        }
 841        List<Key> keys = (List<Key>) value;
 842        return keys.indexOf(elementKey);
 843      }
 844    } else {
 845      // Owned relation in earlier storage version so use parentKey to determine membership of list (only present once)
 846      if (elementKey.getParent() == null) {
 847        throw new NucleusUserException("Element primary-key does not have a parent.");
 848      }
 849
 850      DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
 851      DatastoreService service = DatastoreServiceFactoryInternal.getDatastoreService(config);
 852      try {
 853        Entity e = service.get(elementKey);
 854        Long indexVal = (Long) orderMapping.getObject(ec, e, new int[1]);
 855        if (indexVal == null) {
 856          throw new NucleusDataStoreException("Null index value");
 857        }
 858        return indexVal.intValue();
 859      } catch (EntityNotFoundException enfe) {
 860        throw new NucleusDataStoreException("Could not determine index of entity.", enfe);
 861      }
 862    }
 863    return -1;
 864  }
 865
 866  /* (non-Javadoc)
 867   * @see org.datanucleus.store.scostore.ListStore#lastIndexOf(org.datanucleus.store.ObjectProvider, java.lang.Object)
 868   */
 869  public int lastIndexOf(ObjectProvider op, Object element) {
 870    ExecutionContext ec = op.getExecutionContext();
 871    validateElementForReading(ec, element);
 872    ObjectProvider elementOP = ec.findObjectProvider(element);
 873    Key elementKey = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), elementOP);
 874    if (elementKey == null) {
 875      // Not persistent
 876      return -1;
 877    }
 878
 879    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
 880      // Return the position using the field of List<Key> in the owner
 881      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
 882      Entity ownerEntity = getOwnerEntity(op);
 883      if (ownerEntity.hasProperty(propName)) {
 884        Object value = ownerEntity.getProperty(propName);
 885        if (value == null) {
 886          return -1;
 887        }
 888        List<Key> keys = (List<Key>) value;
 889        return keys.lastIndexOf(elementKey);
 890      }
 891    }
 892    // Owned relation in earlier storage version so use parentKey to determine membership of list (only present once)
 893    return indexOf(op, element);
 894  }
 895
 896  /* (non-Javadoc)
 897   * @see org.datanucleus.store.scostore.ListStore#set(org.datanucleus.store.ObjectProvider, int, java.lang.Object, boolean)
 898   */
 899  public Object set(ObjectProvider op, int index, Object element, boolean allowCascadeDelete) {
 900    // Get current element at this position
 901    Object obj = get(op, index);
 902
 903    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
 904      // Register the parent key for the element when owned
 905      Key parentKey = EntityUtils.getKeyForObject(op.getObject(), op.getExecutionContext());
 906      KeyRegistry.getKeyRegistry(op.getExecutionContext()).registerParentKeyForOwnedObject(element, parentKey);
 907    }
 908
 909    // Make sure the element going to this position is persisted (and give it its index)
 910    validateElementForWriting(op, element, index);
 911
 912    // TODO Allow for a user setting position x as element1 and then setting element2 (that used to be there) to position y
 913    // At the moment we just delete the previous element
 914    if (ownerMemberMetaData.getCollection().isDependentElement() && allowCascadeDelete && obj != null) {
 915      op.getExecutionContext().deleteObjectInternal(obj);
 916    }
 917
 918    return obj;
 919  }
 920
 921  /* (non-Javadoc)
 922   * @see org.datanucleus.store.scostore.ListStore#subList(org.datanucleus.store.ObjectProvider, int, int)
 923   */
 924  public List subList(ObjectProvider op, int startIdx, int endIdx) {
 925    ListIterator iter = listIterator(op, startIdx, endIdx);
 926    java.util.List list = new ArrayList();
 927    while (iter.hasNext()) {
 928      list.add(iter.next());
 929    }
 930    if (!indexedList) {
 931      if (list.size() > (endIdx-startIdx)) {
 932        // Iterator hasn't restricted what is returned so do the index range restriction here
 933        return list.subList(startIdx, endIdx);
 934      }
 935    }
 936    return list;
 937  }
 938
 939  private List<Query.FilterPredicate> getFilterPredicates(int startIdx, int endIdx) {
 940    List<Query.FilterPredicate> filterPredicates = Utils.newArrayList();
 941    if (indexedList) {
 942      String indexProperty = getIndexPropertyName();
 943      if (startIdx >= 0 && endIdx == startIdx) {
 944        // Particular index required so add restriction
 945        Query.FilterPredicate filterPred =
 946            new Query.FilterPredicate(indexProperty, Query.FilterOperator.EQUAL, startIdx);
 947        filterPredicates.add(filterPred);
 948      } else if (startIdx != -1 || endIdx != -1) {
 949        // Add restrictions on start/end indices as required
 950        if (startIdx >= 0) {
 951          Query.FilterPredicate filterPred =
 952              new Query.FilterPredicate(indexProperty, Query.FilterOperator.GREATER_THAN_OR_EQUAL, startIdx);
 953          filterPredicates.add(filterPred);
 954        }
 955        if (endIdx >= 0) {
 956          Query.FilterPredicate filterPred =
 957              new Query.FilterPredicate(indexProperty, Query.FilterOperator.LESS_THAN, endIdx);
 958          filterPredicates.add(filterPred);
 959        }
 960      }
 961    }
 962    return filterPredicates;
 963  }
 964
 965  private String getIndexPropertyName() {
 966    String propertyName;
 967    if (orderMapping.getMemberMetaData() == null) {
 968      // I'm not sure what we should do if this mapping doesn't exist so for now we'll just blow up.
 969      propertyName =
 970          orderMapping.getDatastoreMappings()[0].getDatastoreField().getIdentifier().getIdentifierName();
 971    } else {
 972      propertyName = orderMapping.getMemberMetaData().getName();
 973      AbstractMemberMetaData ammd = orderMapping.getMemberMetaData();
 974
 975      if (ammd.getColumn() != null) {
 976        propertyName = ammd.getColumn();
 977      } else if (ammd.getColumnMetaData() != null && ammd.getColumnMetaData().length == 1) {
 978        propertyName = ammd.getColumnMetaData()[0].getName();
 979      }
 980    }
 981    return propertyName;
 982  }
 983
 984  private List<Query.SortPredicate> getSortPredicates() {
 985    // TODO(maxr) Correctly translate field names to datastore property names
 986    // (embedded fields, overridden column names, etc.)
 987    List<Query.SortPredicate> sortPredicates = Utils.newArrayList();
 988    if (indexedList) {
 989      // Order by the index column
 990      String propertyName = getIndexPropertyName();
 991      Query.SortPredicate sortPredicate =
 992          new Query.SortPredicate(propertyName, Query.SortDirection.ASCENDING);
 993      sortPredicates.add(sortPredicate);
 994    } else {
 995      for (OrderMetaData.FieldOrder fieldOrder : ownerMemberMetaData.getOrderMetaData().getFieldOrders()) {
 996        AbstractMemberMetaData orderMmd = elementCmd.getMetaDataForMember(fieldOrder.getFieldName());
 997        String orderPropName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), orderMmd);
 998        boolean isPrimaryKey = orderMmd.isPrimaryKey();
 999        if (isPrimaryKey) {
1000          if (fieldOrder.isForward() && sortPredicates.isEmpty()) {
1001            // Don't even bother adding if the first sort is id ASC (this is the
1002            // default sort so there's no point in making the datastore figure this out).
1003            break;
1004          }
1005          // sorting by id requires us to use a reserved property name
1006          orderPropName = Entity.KEY_RESERVED_PROPERTY;
1007        }
1008        Query.SortPredicate sortPredicate = new Query.SortPredicate(
1009            orderPropName, fieldOrder.isForward() ? Query.SortDirection.ASCENDING : Query.SortDirection.DESCENDING);
1010        sortPredicates.add(sortPredicate);
1011        if (isPrimaryKey) {
1012          // User wants to sort by pk.  Since pk is guaranteed to be unique, break
1013          // because we know there's no point in adding any more sort predicates
1014          break;
1015        }
1016      }
1017    }
1018    return sortPredicates;
1019  }
1020
1021  boolean isPrimaryKey(String propertyName) {
1022    return elementCmd.getMetaDataForMember(propertyName).isPrimaryKey();
1023  }
1024
1025  /**
1026   * Method to validate that an element is valid for writing to the datastore.
1027   * TODO Minimise differences to super.validateElementForWriting()
1028   * @param op ObjectProvider for the owner of the List
1029   * @param element The element to validate
1030   * @param index The position that the element is being stored at in the list
1031   * @return Whether the element was inserted
1032   */
1033  protected boolean validateElementForWriting(final ObjectProvider op, Object element, final int index)
1034  {
1035    final Object newOwner = op.getObject();
1036
1037    // Check if element is ok for use in the datastore, specifying any external mappings that may be required
1038    boolean inserted = super.validateElementForWriting(op.getExecutionContext(), element, new FieldValues()
1039    {
1040      public void fetchFields(ObjectProvider elementOP)
1041      {
1042        // Find the (element) table storing the FK back to the owner
1043        boolean isPersistentInterface = storeMgr.getNucleusContext().getMetaDataManager().isPersistentInterface(elementType);
1044        DatastoreClass elementTable = null;
1045        if (isPersistentInterface) {
1046          elementTable = storeMgr.getDatastoreClass(
1047              storeMgr.getNucleusContext().getMetaDataManager().getImplementationNameForPersistentInterface(elementType), clr);
1048        }
1049        else {
1050          elementTable = storeMgr.getDatastoreClass(elementType, clr);
1051        }
1052        if (elementTable == null) {
1053          // "subclass-table", persisted into table of other class
1054          AbstractClassMetaData[] managingCmds = storeMgr.getClassesManagingTableForClass(elementCmd, clr);
1055          if (managingCmds != null && managingCmds.length > 0) {
1056            // Find which of these subclasses is appropriate for this element
1057            for (int i=0;i<managingCmds.length;i++) {
1058              Class tblCls = clr.classForName(managingCmds[i].getFullClassName());
1059              if (tblCls.isAssignableFrom(elementOP.getObject().getClass())) {
1060                elementTable = storeMgr.getDatastoreClass(managingCmds[i].getFullClassName(), clr);
1061                break;
1062              }
1063            }
1064          }
1065        }
1066
1067        if (elementTable != null) {
1068          JavaTypeMapping externalFKMapping = elementTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
1069          if (externalFKMapping != null) {
1070            // The element has an external FK mapping so set the value it needs to use in the INSERT
1071            elementOP.setAssociatedValue(externalFKMapping, op.getObject());
1072          }
1073
1074          if (orderMapping != null && index >= 0) {
1075            if (ownerMemberMetaData.getOrderMetaData() != null && ownerMemberMetaData.getOrderMetaData().getMappedBy() != null) {
1076              // Order is stored in a field in the element so update it
1077              // We support mapped-by fields of types int/long/Integer/Long currently
1078              Object indexValue = null;
1079              if (orderMapping.getMemberMetaData().getTypeName().equals(ClassNameConstants.JAVA_LANG_LONG) ||
1080                  orderMapping.getMemberMetaData().getTypeName().equals(ClassNameConstants.LONG)) {
1081                indexValue = Long.valueOf(index);
1082              } else {
1083                indexValue = Integer.valueOf(index);
1084              }
1085              elementOP.replaceFieldMakeDirty(orderMapping.getMemberMetaData().getAbsoluteFieldNumber(), indexValue);
1086            } else {
1087              // Order is stored in a surrogate column so save its vaue for the element to use later
1088              elementOP.setAssociatedValue(orderMapping, Integer.valueOf(index));
1089            }
1090          }
1091        }
1092        if (relationType == RelationType.ONE_TO_MANY_BI) {
1093          // TODO This is ManagedRelations - move into RelationshipManager
1094          // Managed Relations : 1-N bidir, so make sure owner is correct at persist
1095          Object currentOwner = elementOP.provideField(elementMemberMetaData.getAbsoluteFieldNumber());
1096          if (currentOwner == null) {
1097            // No owner, so correct it
1098            NucleusLogger.PERSISTENCE.info(LOCALISER.msg("056037",
1099                StringUtils.toJVMIDString(op.getObject()), ownerMemberMetaData.getFullFieldName(), 
1100                StringUtils.toJVMIDString(elementOP.getObject())));
1101            elementOP.replaceFieldMakeDirty(elementMemberMetaData.getAbsoluteFieldNumber(), newOwner);
1102          }
1103          else if (currentOwner != newOwner && op.getReferencedPC() == null) {
1104            // Owner of the element is neither this container nor is it being attached
1105            // Inconsistent owner, so throw exception
1106            throw new NucleusUserException(LOCALISER.msg("056038",
1107                StringUtils.toJVMIDString(op.getObject()), ownerMemberMetaData.getFullFieldName(), 
1108                StringUtils.toJVMIDString(elementOP.getObject()),
1109                StringUtils.toJVMIDString(currentOwner)));
1110          }
1111        }
1112      }
1113      public void fetchNonLoadedFields(ObjectProvider elementOP) {}
1114      public FetchPlan getFetchPlanForLoading() { return null; }
1115    });
1116
1117    return inserted;
1118  }
1119}