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