/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
- /**********************************************************************
- 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.Collection;
- import java.util.Collections;
- import java.util.HashSet;
- import java.util.Iterator;
- import org.datanucleus.ClassLoaderResolver;
- import org.datanucleus.FetchPlan;
- import org.datanucleus.api.ApiAdapter;
- import org.datanucleus.exceptions.NucleusUserException;
- import org.datanucleus.metadata.AbstractMemberMetaData;
- import org.datanucleus.metadata.RelationType;
- import org.datanucleus.ExecutionContext;
- import org.datanucleus.store.FieldValues;
- import org.datanucleus.state.ObjectProvider;
- import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
- import org.datanucleus.store.mapped.mapping.MappingConsumer;
- import org.datanucleus.store.scostore.SetStore;
- import org.datanucleus.util.NucleusLogger;
- import org.datanucleus.util.StringUtils;
- import com.google.appengine.api.datastore.Entity;
- import com.google.appengine.api.datastore.Key;
- import com.google.appengine.api.datastore.Query;
- import com.google.appengine.datanucleus.DatastoreManager;
- import com.google.appengine.datanucleus.EntityUtils;
- import com.google.appengine.datanucleus.KeyRegistry;
- import com.google.appengine.datanucleus.MetaDataUtils;
- import com.google.appengine.datanucleus.Utils;
- /**
- * Backing store for sets stored with a "FK" in the element.
- */
- public class FKSetStore extends AbstractFKStore implements SetStore {
- public FKSetStore(AbstractMemberMetaData ownerMmd, DatastoreManager storeMgr, ClassLoaderResolver clr) {
- super(ownerMmd, storeMgr, clr);
- }
- /* (non-Javadoc)
- * @see org.datanucleus.store.scostore.CollectionStore#hasOrderMapping()
- */
- public boolean hasOrderMapping() {
- return false;
- }
- /* (non-Javadoc)
- * @see org.datanucleus.store.scostore.CollectionStore#add(org.datanucleus.store.ObjectProvider, java.lang.Object, int)
- */
- public boolean add(final ObjectProvider op, Object element, int currentSize) {
- if (element == null) {
- // FK sets allow no nulls (since can't have a FK on a null element!)
- throw new NucleusUserException(LOCALISER.msg("056039"));
- }
- 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 that the element is persisted in the datastore (reachability)
- final Object newOwner = op.getObject();
- ExecutionContext ec = op.getExecutionContext();
- boolean inserted = validateElementForWriting(ec, element, new FieldValues() {
- public void fetchFields(ObjectProvider esm) {
- // Find the (element) table storing the FK back to the owner
- 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
- esm.setAssociatedValue(externalFKMapping, op.getObject());
- }
- if (relationType == RelationType.ONE_TO_MANY_BI) {
- // TODO Move this into RelationshipManager
- // Managed Relations : 1-N bidir, so make sure owner is correct at persist
- Object currentOwner = esm.provideField(getFieldNumberInElementForBidirectional(esm));
- if (currentOwner == null) {
- // No owner, so correct it
- NucleusLogger.PERSISTENCE.info(LOCALISER.msg("056037",
- StringUtils.toJVMIDString(op.getObject()), ownerMemberMetaData.getFullFieldName(),
- StringUtils.toJVMIDString(esm.getObject())));
- esm.replaceFieldMakeDirty(getFieldNumberInElementForBidirectional(esm), newOwner);
- }
- else if (currentOwner != newOwner && op.getReferencedPC() == null) {
- // Owner of the element is neither this container and not being attached
- // Inconsistent owner, so throw exception
- throw new NucleusUserException(LOCALISER.msg("056038",
- StringUtils.toJVMIDString(op.getObject()), ownerMemberMetaData.getFullFieldName(),
- StringUtils.toJVMIDString(esm.getObject()),
- StringUtils.toJVMIDString(currentOwner)));
- }
- }
- }
- public void fetchNonLoadedFields(ObjectProvider sm) {}
- public FetchPlan getFetchPlanForLoading() {return null;}
- });
- if (!inserted) {
- // Element was already persistent so make sure the FK is in place
- // TODO This is really "ManagedRelationships" so needs to go in RelationshipManager
- ObjectProvider elementOP = ec.findObjectProvider(element);
- if (relationType == RelationType.ONE_TO_MANY_BI) {
- // Managed Relations : 1-N bidir, so update the owner of the element
- elementOP.isLoaded(getFieldNumberInElementForBidirectional(elementOP)); // Ensure is loaded
- Object oldOwner = elementOP.provideField(getFieldNumberInElementForBidirectional(elementOP));
- if (oldOwner != newOwner) {
- if (NucleusLogger.PERSISTENCE.isDebugEnabled()) {
- NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("055009", StringUtils.toJVMIDString(op.getObject()),
- ownerMemberMetaData.getFullFieldName(), StringUtils.toJVMIDString(element)));
- }
- int relatedFieldNumber = getFieldNumberInElementForBidirectional(elementOP);
- elementOP.replaceFieldMakeDirty(relatedFieldNumber, newOwner);
- if (ec.getManageRelations()) {
- // Managed Relationships - add the change we've made here to be analysed at flush
- ec.getRelationshipManager(elementOP).relationChange(relatedFieldNumber, oldOwner, newOwner);
- }
- if (ec.isFlushing()) {
- elementOP.flush();
- }
- }
- return oldOwner != newOwner;
- }
- else {
- // 1-N unidir so update the FK if not set to be contained in the set
- if (contains(op, element)) {
- return false;
- }
- else {
- if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
- // fk is already set and sets are unindexed so there's nothing else to do
- // 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
- EntityUtils.checkParentage(element, op);
- return true;
- }
- }
- }
- }
- return true;
- }
- /* (non-Javadoc)
- * @see org.datanucleus.store.scostore.CollectionStore#addAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int)
- */
- public boolean addAll(ObjectProvider op, Collection coll, int currentSize) {
- if (coll == null || coll.size() == 0) {
- return false;
- }
- // TODO Investigate if we can do a batch put
- boolean success = false;
- Iterator iter = coll.iterator();
- while (iter.hasNext()) {
- if (add(op, iter.next(), -1)) {
- success = true;
- }
- }
- return success;
- }
- /**
- * Convenience method for whether we should delete elements when clear()/remove() is called.
- * Owned relations will be deleted, whereas unowned will follow the "dependent-element" setting.
- * @return Whether to delete an element on call of clear()/remove()
- */
- protected boolean deleteElementsOnRemoveOrClear() {
- // Removal of child should always delete the child with GAE since cannot null the parent in owned relations
- boolean deleteElements = false;
- boolean dependent = ownerMemberMetaData.getCollection().isDependentElement();
- if (ownerMemberMetaData.isCascadeRemoveOrphans()) {
- dependent = true;
- }
- 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 {
- if (dependent) {
- deleteElements = true;
- }
- // TODO Allow for non-nullable FK
- }
- return deleteElements;
- }
- /* (non-Javadoc)
- * @see org.datanucleus.store.scostore.CollectionStore#clear(org.datanucleus.store.ObjectProvider)
- */
- public void clear(ObjectProvider op) {
- // Find elements present in the datastore and process them one-by-one
- 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 elementSM = ec.findObjectProvider(element);
- elementSM.flush();
- } else {
- if (deleteElements) {
- ec.deleteObjectInternal(element);
- } else {
- // TODO Null this out (in parent)
- }
- }
- }
- }
- }
- /* (non-Javadoc)
- * @see org.datanucleus.store.scostore.CollectionStore#iterator(org.datanucleus.store.ObjectProvider)
- */
- public Iterator iterator(ObjectProvider op) {
- 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)) {
- return getChildrenFromParentField(op, ec, -1, -1).listIterator();
- } else {
- if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
- // Not yet got the property in the parent, so this entity has not yet been migrated to latest storage version
- NucleusLogger.PERSISTENCE.info("Collection at field " + ownerMemberMetaData.getFullFieldName() + " of " + op +
- " not yet migrated to latest storage version, so reading elements via the parent key");
- }
- }
- }
- if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
- // Get child keys by doing a query with the owner as the parent Entity
- ApiAdapter apiAdapter = ec.getApiAdapter();
- Key parentKey = EntityUtils.getPrimaryKeyAsKey(apiAdapter, op);
- return getChildrenUsingParentQuery(parentKey, Collections.<Query.FilterPredicate>emptyList(),
- Collections.<Query.SortPredicate>emptyList(), ec).iterator();
- } else {
- return Utils.newArrayList().listIterator();
- }
- }
- /* (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) {
- if (element == null) {
- return false;
- }
- if (!validateElementForReading(op.getExecutionContext(), element)) {
- return false;
- }
- // Find the ObjectProvider for the element
- Object elementToRemove = element;
- ExecutionContext ec = op.getExecutionContext();
- if (ec.getApiAdapter().isDetached(element)) {// User passed in detached object to collection.remove()! {
- // Find an attached equivalent of this detached object (DON'T attach the object itself)
- elementToRemove = ec.findObject(ec.getApiAdapter().getIdForObject(element), true, false, element.getClass().getName());
- }
- ObjectProvider elementOP = ec.findObjectProvider(elementToRemove);
- // Check for change of owner of the element (removed from this but added to another one maybe?)
- if (MetaDataUtils.isOwnedRelation(ownerMemberMetaData, storeMgr)) {
- // Check for ownership change when owned relation, and prevent changes
- Object oldOwner = null;
- if (relationType == RelationType.ONE_TO_MANY_BI) {
- if (!ec.getApiAdapter().isDeleted(elementToRemove)) {
- // Find the existing owner if the record hasn't already been deleted
- int elemOwnerFieldNumber = getFieldNumberInElementForBidirectional(elementOP);
- elementOP.isLoaded(elemOwnerFieldNumber);
- oldOwner = elementOP.provideField(elemOwnerFieldNumber);
- }
- }
- if (RelationType.isBidirectional(relationType) && oldOwner != op.getObject() && oldOwner != null) {
- // Owner of the element has been changed, so reject it
- return false;
- }
- }
- boolean deleteElements = deleteElementsOnRemoveOrClear();
- if (ec.getApiAdapter().isPersistable(elementToRemove) && ec.getApiAdapter().isDeleted(elementToRemove)) {
- // Element is waiting to be deleted so flush it (it has the FK)
- elementOP.flush();
- } else {
- if (deleteElements) {
- ec.deleteObjectInternal(elementToRemove);
- } else {
- // TODO Null it out
- }
- }
- return true;
- }
- /* (non-Javadoc)
- * @see org.datanucleus.store.scostore.CollectionStore#removeAll(org.datanucleus.store.ObjectProvider, java.util.Collection, int)
- */
- public boolean removeAll(ObjectProvider op, Collection coll, int currentSize) {
- if (coll == null || coll.size() == 0) {
- return false;
- }
- // Check the first element for whether we can null the column or whether we have to delete
- // TODO Investigate if we can do a batch delete
- boolean success = true;
- Iterator iter = coll.iterator();
- while (iter.hasNext()) {
- if (remove(op, iter.next(), -1, true)) {
- success = false;
- }
- }
- return success;
- }
- /* (non-Javadoc)
- * @see org.datanucleus.store.scostore.CollectionStore#update(org.datanucleus.store.ObjectProvider, java.util.Collection)
- */
- public void update(ObjectProvider op, Collection coll) {
- if (coll == null || coll.isEmpty()) {
- clear(op);
- return;
- }
- // Find existing elements, and remove any that are no longer present
- Iterator elemIter = iterator(op);
- Collection existing = new HashSet();
- while (elemIter.hasNext()) {
- Object elem = elemIter.next();
- if (!coll.contains(elem)) {
- remove(op, elem, -1, true);
- } else {
- existing.add(elem);
- }
- }
- if (existing.size() != coll.size()) {
- // Add any elements that aren't already present
- Iterator iter = coll.iterator();
- while (iter.hasNext()) {
- Object elem = iter.next();
- if (!existing.contains(elem)) {
- add(op, elem, 0);
- }
- }
- }
- }
- /**
- * This seems to return the field number in the element of the relation when it is a bidirectional relation.
- * @param elementOP ObjectProvider of the element
- * @return The field number in the element for this relation
- */
- protected int getFieldNumberInElementForBidirectional(ObjectProvider elementOP) {
- if (RelationType.isBidirectional(relationType)) {
- AbstractMemberMetaData[] relMmds = ownerMemberMetaData.getRelatedMemberMetaData(clr);
- if (relMmds != null) {
- return relMmds[0].getAbsoluteFieldNumber();
- }
- }
- return -1;
- }
- }