PageRenderTime 62ms CodeModel.GetById 16ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 1ms

/src/com/google/appengine/datanucleus/scostore/FKSetStore.java

http://datanucleus-appengine.googlecode.com/
Java | 387 lines | 261 code | 33 blank | 93 comment | 88 complexity | 78248181e1e7ef1c0168e376061d1c46 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.Collection;
 19import java.util.Collections;
 20import java.util.HashSet;
 21import java.util.Iterator;
 22
 23import org.datanucleus.ClassLoaderResolver;
 24import org.datanucleus.FetchPlan;
 25import org.datanucleus.api.ApiAdapter;
 26import org.datanucleus.exceptions.NucleusUserException;
 27import org.datanucleus.metadata.AbstractMemberMetaData;
 28import org.datanucleus.metadata.RelationType;
 29import org.datanucleus.ExecutionContext;
 30import org.datanucleus.store.FieldValues;
 31import org.datanucleus.state.ObjectProvider;
 32import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
 33import org.datanucleus.store.mapped.mapping.MappingConsumer;
 34import org.datanucleus.store.scostore.SetStore;
 35import org.datanucleus.util.NucleusLogger;
 36import org.datanucleus.util.StringUtils;
 37
 38import com.google.appengine.api.datastore.Entity;
 39import com.google.appengine.api.datastore.Key;
 40import com.google.appengine.api.datastore.Query;
 41import com.google.appengine.datanucleus.DatastoreManager;
 42import com.google.appengine.datanucleus.EntityUtils;
 43import com.google.appengine.datanucleus.KeyRegistry;
 44import com.google.appengine.datanucleus.MetaDataUtils;
 45import com.google.appengine.datanucleus.Utils;
 46
 47/**
 48 * Backing store for sets stored with a "FK" in the element.
 49 */
 50public class FKSetStore extends AbstractFKStore implements SetStore {
 51  public FKSetStore(AbstractMemberMetaData ownerMmd, DatastoreManager storeMgr, ClassLoaderResolver clr) {
 52    super(ownerMmd, storeMgr, clr);
 53  }
 54
 55  /* (non-Javadoc)
 56   * @see org.datanucleus.store.scostore.CollectionStore#hasOrderMapping()
 57   */
 58  public boolean hasOrderMapping() {
 59    return false;
 60  }
 61
 62  /* (non-Javadoc)
 63   * @see org.datanucleus.store.scostore.CollectionStore#add(org.datanucleus.store.ObjectProvider, java.lang.Object, int)
 64   */
 65  public boolean add(final ObjectProvider op, Object element, int currentSize) {
 66    if (element == null) {
 67      // FK sets allow no nulls (since can't have a FK on a null element!)
 68      throw new NucleusUserException(LOCALISER.msg("056039"));
 69    }
 70
 71    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
 72      // Register the parent key for the element when owned
 73      Key parentKey = EntityUtils.getKeyForObject(op.getObject(), op.getExecutionContext());
 74      KeyRegistry.getKeyRegistry(op.getExecutionContext()).registerParentKeyForOwnedObject(element, parentKey);
 75    }
 76
 77    // Make sure that the element is persisted in the datastore (reachability)
 78    final Object newOwner = op.getObject();
 79    ExecutionContext ec = op.getExecutionContext();
 80    boolean inserted = validateElementForWriting(ec, element, new FieldValues() {
 81      public void fetchFields(ObjectProvider esm) {
 82        // Find the (element) table storing the FK back to the owner
 83        JavaTypeMapping externalFKMapping = elementTable.getExternalMapping(ownerMemberMetaData, 
 84            MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
 85        if (externalFKMapping != null) {
 86          // The element has an external FK mapping so set the value it needs to use in the INSERT
 87          esm.setAssociatedValue(externalFKMapping, op.getObject());
 88        }
 89
 90        if (relationType == RelationType.ONE_TO_MANY_BI) {
 91          // TODO Move this into RelationshipManager
 92          // Managed Relations : 1-N bidir, so make sure owner is correct at persist
 93          Object currentOwner = esm.provideField(getFieldNumberInElementForBidirectional(esm));
 94          if (currentOwner == null) {
 95            // No owner, so correct it
 96            NucleusLogger.PERSISTENCE.info(LOCALISER.msg("056037",
 97                StringUtils.toJVMIDString(op.getObject()), ownerMemberMetaData.getFullFieldName(), 
 98                StringUtils.toJVMIDString(esm.getObject())));
 99            esm.replaceFieldMakeDirty(getFieldNumberInElementForBidirectional(esm), newOwner);
100          }
101          else if (currentOwner != newOwner && op.getReferencedPC() == null) {
102            // Owner of the element is neither this container and not being attached
103            // Inconsistent owner, so throw exception
104            throw new NucleusUserException(LOCALISER.msg("056038",
105                StringUtils.toJVMIDString(op.getObject()), ownerMemberMetaData.getFullFieldName(), 
106                StringUtils.toJVMIDString(esm.getObject()),
107                StringUtils.toJVMIDString(currentOwner)));
108          }
109        }
110      }
111
112      public void fetchNonLoadedFields(ObjectProvider sm) {}
113      public FetchPlan getFetchPlanForLoading() {return null;}
114    });
115
116    if (!inserted) {
117      // Element was already persistent so make sure the FK is in place
118      // TODO This is really "ManagedRelationships" so needs to go in RelationshipManager
119      ObjectProvider elementOP = ec.findObjectProvider(element);
120      if (relationType == RelationType.ONE_TO_MANY_BI) {
121        // Managed Relations : 1-N bidir, so update the owner of the element
122        elementOP.isLoaded(getFieldNumberInElementForBidirectional(elementOP)); // Ensure is loaded
123        Object oldOwner = elementOP.provideField(getFieldNumberInElementForBidirectional(elementOP));
124        if (oldOwner != newOwner) {
125          if (NucleusLogger.PERSISTENCE.isDebugEnabled()) {
126            NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("055009", StringUtils.toJVMIDString(op.getObject()),
127                ownerMemberMetaData.getFullFieldName(), StringUtils.toJVMIDString(element)));
128          }
129
130          int relatedFieldNumber = getFieldNumberInElementForBidirectional(elementOP);
131          elementOP.replaceFieldMakeDirty(relatedFieldNumber, newOwner);
132          if (ec.getManageRelations()) {
133            // Managed Relationships - add the change we've made here to be analysed at flush
134            ec.getRelationshipManager(elementOP).relationChange(relatedFieldNumber, oldOwner, newOwner);
135          }
136
137          if (ec.isFlushing()) {
138            elementOP.flush();
139          }
140        }
141        return oldOwner != newOwner;
142      }
143      else {
144        // 1-N unidir so update the FK if not set to be contained in the set
145        if (contains(op, element)) {
146          return false;
147        }
148        else {
149          if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
150            // fk is already set and sets are unindexed so there's nothing else to do
151            // Keys (and therefore parents) are immutable so we don't need to ever
152            // actually update the parent FK, but we do need to check to make sure
153            // someone isn't trying to modify the parent FK
154            EntityUtils.checkParentage(element, op);
155            return true;
156          }
157        }
158      }
159    }
160    return true;
161  }
162
163  /* (non-Javadoc)
164   * @see org.datanucleus.store.scostore.CollectionStore#addAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int)
165   */
166  public boolean addAll(ObjectProvider op, Collection coll, int currentSize) {
167    if (coll == null || coll.size() == 0) {
168      return false;
169    }
170
171    // TODO Investigate if we can do a batch put
172    boolean success = false;
173    Iterator iter = coll.iterator();
174    while (iter.hasNext()) {
175      if (add(op, iter.next(), -1)) {
176        success = true;
177      }
178    }
179
180    return success;
181  }
182
183  /**
184   * Convenience method for whether we should delete elements when clear()/remove() is called.
185   * Owned relations will be deleted, whereas unowned will follow the "dependent-element" setting.
186   * @return Whether to delete an element on call of clear()/remove()
187   */
188  protected boolean deleteElementsOnRemoveOrClear() {
189    // Removal of child should always delete the child with GAE since cannot null the parent in owned relations
190    boolean deleteElements = false;
191    boolean dependent = ownerMemberMetaData.getCollection().isDependentElement();
192    if (ownerMemberMetaData.isCascadeRemoveOrphans()) {
193      dependent = true;
194    }
195
196    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
197      // Field is not dependent, and not nullable so we just delete the elements
198      NucleusLogger.DATASTORE.debug(LOCALISER.msg("056035"));
199      deleteElements = true;
200    } else {
201      if (dependent) {
202        deleteElements = true;
203      }
204      // TODO Allow for non-nullable FK
205    }
206    return deleteElements;
207  }
208
209  /* (non-Javadoc)
210   * @see org.datanucleus.store.scostore.CollectionStore#clear(org.datanucleus.store.ObjectProvider)
211   */
212  public void clear(ObjectProvider op) {
213    // Find elements present in the datastore and process them one-by-one
214    boolean deleteElements = deleteElementsOnRemoveOrClear();
215    ExecutionContext ec = op.getExecutionContext();
216    Iterator elementsIter = iterator(op);
217    if (elementsIter != null) {
218      while (elementsIter.hasNext()) {
219        Object element = elementsIter.next();
220        if (ec.getApiAdapter().isPersistable(element) && ec.getApiAdapter().isDeleted(element)) {
221          // Element is waiting to be deleted so flush it (it has the FK)
222          ObjectProvider elementSM = ec.findObjectProvider(element);
223          elementSM.flush();
224        } else {
225          if (deleteElements) {
226            ec.deleteObjectInternal(element);
227          } else {
228            // TODO Null this out (in parent)
229          }
230        }
231      }
232    }
233  }
234
235  /* (non-Javadoc)
236   * @see org.datanucleus.store.scostore.CollectionStore#iterator(org.datanucleus.store.ObjectProvider)
237   */
238  public Iterator iterator(ObjectProvider op) {
239    ExecutionContext ec = op.getExecutionContext();
240    if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
241      // Get child keys from property in owner Entity if the property exists
242      Entity datastoreEntity = getOwnerEntity(op);
243      String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
244      if (datastoreEntity.hasProperty(propName)) {
245        return getChildrenFromParentField(op, ec, -1, -1).listIterator();
246      } else {
247        if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
248          // Not yet got the property in the parent, so this entity has not yet been migrated to latest storage version
249          NucleusLogger.PERSISTENCE.info("Collection at field " + ownerMemberMetaData.getFullFieldName() + " of " + op +
250              " not yet migrated to latest storage version, so reading elements via the parent key");
251        }
252      }
253    }
254
255    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
256      // Get child keys by doing a query with the owner as the parent Entity
257      ApiAdapter apiAdapter = ec.getApiAdapter();
258      Key parentKey = EntityUtils.getPrimaryKeyAsKey(apiAdapter, op);
259      return getChildrenUsingParentQuery(parentKey, Collections.<Query.FilterPredicate>emptyList(),
260          Collections.<Query.SortPredicate>emptyList(), ec).iterator();
261    } else {
262      return Utils.newArrayList().listIterator();
263    }
264  }
265
266  /* (non-Javadoc)
267   * @see org.datanucleus.store.scostore.CollectionStore#remove(org.datanucleus.store.ObjectProvider, java.lang.Object, int, boolean)
268   */
269  public boolean remove(ObjectProvider op, Object element, int currentSize, boolean allowCascadeDelete) {
270    if (element == null) {
271        return false;
272    }
273    if (!validateElementForReading(op.getExecutionContext(), element)) {
274        return false;
275    }
276
277    // Find the ObjectProvider for the element
278    Object elementToRemove = element;
279    ExecutionContext ec = op.getExecutionContext();
280    if (ec.getApiAdapter().isDetached(element)) {// User passed in detached object to collection.remove()! {
281      // Find an attached equivalent of this detached object (DON'T attach the object itself)
282      elementToRemove = ec.findObject(ec.getApiAdapter().getIdForObject(element), true, false, element.getClass().getName());
283    }
284    ObjectProvider elementOP = ec.findObjectProvider(elementToRemove);
285
286    // Check for change of owner of the element (removed from this but added to another one maybe?)
287    if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
288      // Check for ownership change when owned relation, and prevent changes
289      Object oldOwner = null;
290      if (relationType == RelationType.ONE_TO_MANY_BI) {
291        if (!ec.getApiAdapter().isDeleted(elementToRemove)) {
292          // Find the existing owner if the record hasn't already been deleted
293          int elemOwnerFieldNumber = getFieldNumberInElementForBidirectional(elementOP);
294          elementOP.isLoaded(elemOwnerFieldNumber);
295          oldOwner = elementOP.provideField(elemOwnerFieldNumber);
296        }
297      }
298      if (RelationType.isBidirectional(relationType) && oldOwner != op.getObject() && oldOwner != null) {
299        // Owner of the element has been changed, so reject it
300        return false;
301      }
302    }
303
304    boolean deleteElements = deleteElementsOnRemoveOrClear();
305    if (ec.getApiAdapter().isPersistable(elementToRemove) && ec.getApiAdapter().isDeleted(elementToRemove)) {
306      // Element is waiting to be deleted so flush it (it has the FK)
307      elementOP.flush();
308    } else {
309      if (deleteElements) {
310        ec.deleteObjectInternal(elementToRemove);
311      } else {
312        // TODO Null it out
313      }
314    }
315
316    return true;
317  }
318
319  /* (non-Javadoc)
320   * @see org.datanucleus.store.scostore.CollectionStore#removeAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int)
321   */
322  public boolean removeAll(ObjectProvider op, Collection coll, int currentSize) {
323    if (coll == null || coll.size() == 0) {
324      return false;
325    }
326
327    // Check the first element for whether we can null the column or whether we have to delete
328    // TODO Investigate if we can do a batch delete
329    boolean success = true;
330    Iterator iter = coll.iterator();
331    while (iter.hasNext()) {
332      if (remove(op, iter.next(), -1, true)) {
333        success = false;
334      }
335    }
336
337    return success;
338  }
339
340  /* (non-Javadoc)
341   * @see org.datanucleus.store.scostore.CollectionStore#update(org.datanucleus.store.ObjectProvider, java.util.Collection)
342   */
343  public void update(ObjectProvider op, Collection coll) {
344    if (coll == null || coll.isEmpty()) {
345      clear(op);
346      return;
347    }
348
349    // Find existing elements, and remove any that are no longer present
350    Iterator elemIter = iterator(op);
351    Collection existing = new HashSet();
352    while (elemIter.hasNext()) {
353      Object elem = elemIter.next();
354      if (!coll.contains(elem)) {
355        remove(op, elem, -1, true);
356      } else {
357        existing.add(elem);
358      }
359    }
360
361    if (existing.size() != coll.size()) {
362      // Add any elements that aren't already present
363      Iterator iter = coll.iterator();
364      while (iter.hasNext()) {
365        Object elem = iter.next();
366        if (!existing.contains(elem)) {
367          add(op, elem, 0);
368        }
369      }
370    }
371  }
372
373  /**
374   * This seems to return the field number in the element of the relation when it is a bidirectional relation.
375   * @param elementOP ObjectProvider of the element
376   * @return The field number in the element for this relation
377   */
378  protected int getFieldNumberInElementForBidirectional(ObjectProvider elementOP) {
379    if (RelationType.isBidirectional(relationType)) {
380      AbstractMemberMetaData[] relMmds = ownerMemberMetaData.getRelatedMemberMetaData(clr);
381      if (relMmds != null) {
382        return relMmds[0].getAbsoluteFieldNumber();
383      }
384    }
385    return -1;
386  }
387}