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

http://datanucleus-appengine.googlecode.com/ · Java · 448 lines · 296 code · 48 blank · 104 comment · 78 complexity · 477c3dd8bb06ddbafb46c7bf586add68 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 com.google.appengine.api.datastore.DatastoreService;
  15. import com.google.appengine.api.datastore.DatastoreServiceConfig;
  16. import com.google.appengine.api.datastore.Entity;
  17. import com.google.appengine.api.datastore.Key;
  18. import com.google.appengine.api.datastore.PreparedQuery;
  19. import com.google.appengine.api.datastore.Query;
  20. import com.google.appengine.api.datastore.Query.FilterPredicate;
  21. import com.google.appengine.api.datastore.Query.SortPredicate;
  22. import com.google.appengine.datanucleus.DatastoreManager;
  23. import com.google.appengine.datanucleus.DatastoreServiceFactoryInternal;
  24. import com.google.appengine.datanucleus.EntityUtils;
  25. import com.google.appengine.datanucleus.MetaDataUtils;
  26. import com.google.appengine.datanucleus.Utils;
  27. import org.datanucleus.ClassLoaderResolver;
  28. import org.datanucleus.exceptions.NucleusUserException;
  29. import org.datanucleus.metadata.AbstractClassMetaData;
  30. import org.datanucleus.metadata.AbstractMemberMetaData;
  31. import org.datanucleus.metadata.CollectionMetaData;
  32. import org.datanucleus.metadata.RelationType;
  33. import org.datanucleus.ExecutionContext;
  34. import org.datanucleus.store.FieldValues;
  35. import org.datanucleus.state.ObjectProvider;
  36. import org.datanucleus.store.mapped.DatastoreClass;
  37. import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
  38. import org.datanucleus.store.mapped.mapping.MappingConsumer;
  39. import org.datanucleus.store.types.SCOUtils;
  40. import org.datanucleus.util.ClassUtils;
  41. import org.datanucleus.util.Localiser;
  42. import org.datanucleus.util.NucleusLogger;
  43. import java.util.ArrayList;
  44. import java.util.Collection;
  45. import java.util.Collections;
  46. import java.util.List;
  47. import java.util.Map;
  48. /**
  49. * Abstract base class for backing stores using a "FK" in the element.
  50. */
  51. public abstract class AbstractFKStore {
  52. /** Localiser for messages. */
  53. protected static final Localiser LOCALISER = Localiser.getInstance(
  54. "org.datanucleus.Localisation", org.datanucleus.ClassConstants.NUCLEUS_CONTEXT_LOADER);
  55. /** Manager for the GAE datastore. */
  56. protected DatastoreManager storeMgr;
  57. /** MetaData for the field/property in the owner with this container. */
  58. protected AbstractMemberMetaData ownerMemberMetaData;
  59. protected String elementType;
  60. /** Metadata for the class of the element. */
  61. protected AbstractClassMetaData elementCmd;
  62. /** Metadata for the member of the element (when bidirectional). */
  63. protected AbstractMemberMetaData elementMemberMetaData;
  64. protected ClassLoaderResolver clr;
  65. protected RelationType relationType;
  66. /** Primary table for the element(s). */
  67. protected DatastoreClass elementTable;
  68. /** Mapping for the owner FK column in the element table. */
  69. protected JavaTypeMapping ownerMapping;
  70. public AbstractFKStore(AbstractMemberMetaData ownerMmd, DatastoreManager storeMgr, ClassLoaderResolver clr) {
  71. this.storeMgr = storeMgr;
  72. this.ownerMemberMetaData = ownerMmd;
  73. this.clr = clr;
  74. this.relationType = ownerMemberMetaData.getRelationType(clr);
  75. CollectionMetaData colmd = ownerMemberMetaData.getCollection();
  76. if (colmd == null) {
  77. throw new NucleusUserException(LOCALISER.msg("056001", ownerMemberMetaData.getFullFieldName()));
  78. }
  79. // Load the element class
  80. elementType = colmd.getElementType();
  81. Class element_class = clr.classForName(elementType);
  82. if (ClassUtils.isReferenceType(element_class)) {
  83. if (storeMgr.getNucleusContext().getMetaDataManager().isPersistentInterface(elementType)) {
  84. elementCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForInterface(element_class,clr);
  85. }
  86. else {
  87. // Take the metadata for the first implementation of the reference type
  88. elementCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForImplementationOfReference(element_class,null,clr);
  89. if (elementCmd != null)
  90. {
  91. // Pretend we have a relationship with this one implementation
  92. elementType = elementCmd.getFullClassName();
  93. }
  94. }
  95. }
  96. else {
  97. // Check that the element class has MetaData
  98. elementCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(element_class, clr);
  99. }
  100. if (elementCmd == null) {
  101. throw new NucleusUserException(LOCALISER.msg("056003", element_class.getName(), ownerMemberMetaData.getFullFieldName()));
  102. }
  103. // Set table of element
  104. elementTable = this.storeMgr.getDatastoreClass(elementCmd.getFullClassName(), clr);
  105. if (elementTable == null) {
  106. // Special case : single subclass with table
  107. String[] subclassNames = storeMgr.getNucleusContext().getMetaDataManager().getSubclassesForClass(element_class.getName(), true);
  108. if (subclassNames.length == 1) {
  109. elementTable = this.storeMgr.getDatastoreClass(subclassNames[0], clr);
  110. }
  111. if (elementTable == null) {
  112. throw new UnsupportedOperationException("Field " + ownerMemberMetaData.getFullFieldName() + " is collection of elements of type " +
  113. elementCmd.getFullClassName() + " but this has no table of its own!");
  114. }
  115. }
  116. // Get the field in the element table (if any)
  117. String mappedByFieldName = ownerMemberMetaData.getMappedBy();
  118. if (mappedByFieldName != null) {
  119. // bidirectional - the element class has a field for the owner.
  120. elementMemberMetaData = elementCmd.getMetaDataForMember(mappedByFieldName);
  121. if (elementMemberMetaData == null) {
  122. throw new NucleusUserException(LOCALISER.msg("056024", ownerMemberMetaData.getFullFieldName(),
  123. mappedByFieldName, element_class.getName()));
  124. }
  125. // Check that the type of the element "mapped-by" field is consistent with the owner type when 1-N
  126. if ((relationType == RelationType.ONE_TO_MANY_BI || relationType == RelationType.ONE_TO_MANY_UNI) &&
  127. !clr.isAssignableFrom(elementMemberMetaData.getType(), ownerMmd.getAbstractClassMetaData().getFullClassName())) {
  128. throw new NucleusUserException(LOCALISER.msg("056025", ownerMmd.getFullFieldName(),
  129. elementMemberMetaData.getFullFieldName(), elementMemberMetaData.getTypeName(), ownerMmd.getAbstractClassMetaData().getFullClassName()));
  130. }
  131. ownerMapping = elementTable.getMemberMapping(elementMemberMetaData);
  132. }
  133. else {
  134. ownerMapping = elementTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
  135. }
  136. }
  137. public DatastoreManager getStoreManager() {
  138. return storeMgr;
  139. }
  140. public AbstractMemberMetaData getOwnerMemberMetaData() {
  141. return ownerMemberMetaData;
  142. }
  143. protected Entity getOwnerEntity(ObjectProvider op) {
  144. Entity entity = (Entity) op.getAssociatedValue(storeMgr.getDatastoreTransaction(op.getExecutionContext()));
  145. if (entity == null) {
  146. storeMgr.validateMetaDataForClass(op.getClassMetaData());
  147. EntityUtils.getEntityFromDatastore(storeMgr.getDatastoreServiceForReads(op.getExecutionContext()), op,
  148. EntityUtils.getPkAsKey(op));
  149. return (Entity) op.getAssociatedValue(storeMgr.getDatastoreTransaction(op.getExecutionContext()));
  150. }
  151. return entity;
  152. }
  153. /* (non-Javadoc)
  154. * @see org.datanucleus.store.scostore.CollectionStore#updateEmbeddedElement(org.datanucleus.store.ObjectProvider, java.lang.Object, int, java.lang.Object)
  155. */
  156. public boolean updateEmbeddedElement(ObjectProvider ownerOP, Object elem, int fieldNum, Object value) {
  157. // This is only used by join table stores where the element is embedded in the join table
  158. return false;
  159. }
  160. /* (non-Javadoc)
  161. * @see org.datanucleus.store.scostore.CollectionStore#size(org.datanucleus.store.ObjectProvider)
  162. */
  163. public int size(ObjectProvider op) {
  164. if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
  165. // Child keys are stored in field in owner Entity
  166. return getSizeUsingChildKeysInParent(op);
  167. } else {
  168. // Get size from child keys by doing a query with the owner as the parent Entity
  169. return getSizeUsingParentKeyInChildren(op);
  170. }
  171. }
  172. protected int getSizeUsingChildKeysInParent(ObjectProvider op) {
  173. Entity ownerEntity = getOwnerEntity(op);
  174. // Child keys are stored in field in owner Entity
  175. String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
  176. if (ownerEntity.hasProperty(propName)) {
  177. Object value = ownerEntity.getProperty(propName);
  178. if (value == null) {
  179. return 0;
  180. }
  181. List<Key> keys = (List<Key>) value;
  182. return keys.size();
  183. }
  184. return 0;
  185. }
  186. protected int getSizeUsingParentKeyInChildren(ObjectProvider op) {
  187. Entity ownerEntity = getOwnerEntity(op);
  188. // Get size from child keys by doing a query with the owner as the parent Entity
  189. String kindName = elementTable.getIdentifier().getIdentifierName();
  190. Iterable<Entity> children = prepareChildrenQuery(ownerEntity.getKey(),
  191. Collections.<FilterPredicate>emptyList(),
  192. Collections.<SortPredicate>emptyList(), // Sort not important when counting
  193. true, kindName).asIterable();
  194. int count = 0;
  195. for (Entity e : children) {
  196. if (ownerEntity.getKey().equals(e.getKey().getParent())) {
  197. count++;
  198. }
  199. }
  200. return count;
  201. }
  202. /* (non-Javadoc)
  203. * @see org.datanucleus.store.scostore.CollectionStore#contains(org.datanucleus.store.ObjectProvider, java.lang.Object)
  204. */
  205. public boolean contains(ObjectProvider op, Object element) {
  206. ExecutionContext ec = op.getExecutionContext();
  207. if (!validateElementForReading(ec, element)) {
  208. return false;
  209. }
  210. Key childKey = EntityUtils.getKeyForObject(element, ec);
  211. if (childKey == null) {
  212. // Not yet persistent
  213. return false;
  214. }
  215. if (MetaDataUtils.readRelatedKeysFromParent(storeMgr, ownerMemberMetaData)) {
  216. // Check containment using field in parent containing "List<Key>"
  217. Entity datastoreEntity = getOwnerEntity(op);
  218. String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
  219. if (datastoreEntity.hasProperty(propName)) {
  220. Object value = datastoreEntity.getProperty(propName);
  221. if (value == null) {
  222. return false;
  223. } else {
  224. List<Key> keys = (List<Key>)value;
  225. return keys.contains(childKey);
  226. }
  227. } else {
  228. return false;
  229. }
  230. } else {
  231. // Check containment using parent key of the element key
  232. // Child key can be null if element has not yet been persisted
  233. if (childKey.getParent() == null) {
  234. return false;
  235. }
  236. Key parentKey = EntityUtils.getPrimaryKeyAsKey(ec.getApiAdapter(), op);
  237. return childKey.getParent().equals(parentKey);
  238. }
  239. }
  240. /**
  241. * Method to run a query with the supplied filter and sort predicates, to get the child objects for the
  242. * specified parent.
  243. * @param parentKey Key of the parent
  244. * @param filterPredicates Filtering required
  245. * @param sortPredicates Ordering required
  246. * @param ec ExecutionContext
  247. * @return The child objects list
  248. */
  249. List<?> getChildrenUsingParentQuery(Key parentKey, Iterable<FilterPredicate> filterPredicates,
  250. Iterable<SortPredicate> sortPredicates, ExecutionContext ec) {
  251. List<Object> result = new ArrayList<Object>();
  252. int numChildren = 0;
  253. String kindName = elementTable.getIdentifier().getIdentifierName();
  254. for (Entity e : prepareChildrenQuery(parentKey, filterPredicates, sortPredicates, false, kindName).asIterable()) {
  255. // We only want direct children
  256. if (parentKey.equals(e.getKey().getParent())) {
  257. numChildren++;
  258. result.add(EntityUtils.entityToPojo(e, elementCmd, clr, ec, false, ec.getFetchPlan()));
  259. if (NucleusLogger.PERSISTENCE.isDebugEnabled()) {
  260. NucleusLogger.PERSISTENCE.debug("Retrieved entity with key " + e.getKey());
  261. }
  262. }
  263. }
  264. NucleusLogger.PERSISTENCE.debug(String.format("Query had %d result%s.", numChildren, numChildren == 1 ? "" : "s"));
  265. return result;
  266. }
  267. /**
  268. * Method to return the List of children for this collection using the "List<Key>" stored in the owner field.
  269. * @param op ObjectProvider for the owner
  270. * @param ec ExecutionContext
  271. * @param startIdx Start index of range (or -1 if not needed)
  272. * @param endIdx End index of range (or -1 if not needed)
  273. * @return The child objects list
  274. */
  275. List<?> getChildrenFromParentField(ObjectProvider op, ExecutionContext ec, int startIdx, int endIdx) {
  276. Entity datastoreEntity = getOwnerEntity(op);
  277. String propName = EntityUtils.getPropertyName(storeMgr.getIdentifierFactory(), ownerMemberMetaData);
  278. if (datastoreEntity.hasProperty(propName)) {
  279. Object value = datastoreEntity.getProperty(propName);
  280. if (value == null || (value instanceof Collection && ((Collection)value).isEmpty())) {
  281. // No elements so just return
  282. return Utils.newArrayList();
  283. }
  284. List children = new ArrayList();
  285. List<Key> keys = (List<Key>)value;
  286. DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
  287. DatastoreService ds = DatastoreServiceFactoryInternal.getDatastoreService(config);
  288. Map<Key, Entity> entitiesByKey = ds.get(keys);
  289. int i = 0;
  290. for (Key key : keys) {
  291. if (i < startIdx) {
  292. continue;
  293. } else if (endIdx > 0 && i >= endIdx) {
  294. continue;
  295. }
  296. Entity entity = entitiesByKey.get(key);
  297. if (entity == null) {
  298. // User must have deleted it? Ignore the entry
  299. NucleusLogger.DATASTORE_RETRIEVE.info("Field " + ownerMemberMetaData.getFullFieldName() + " of " + datastoreEntity.getKey() +
  300. " was marked as having child " + key + " but doesn't exist, so must have been deleted. Ignoring");
  301. continue;
  302. }
  303. Object pojo = EntityUtils.entityToPojo(entity, elementCmd, clr, ec, false, ec.getFetchPlan());
  304. children.add(pojo);
  305. i++;
  306. }
  307. return children;
  308. }
  309. return Utils.newArrayList();
  310. }
  311. /**
  312. * Method to create a PreparedQuery, for the specified filter and ordering, to get the child objects of a parent.
  313. * @param parentKey Key of the parent
  314. * @param filterPredicates Filtering required
  315. * @param sortPredicates Ordering required
  316. * @param keysOnly Whether to just returns the keys of the children
  317. * @param kindName Name of the kind that we are querying
  318. * @return The PreparedQuery
  319. */
  320. PreparedQuery prepareChildrenQuery(Key parentKey, Iterable<FilterPredicate> filterPredicates,
  321. Iterable<SortPredicate> sortPredicates, boolean keysOnly, String kindName) {
  322. Query q = new Query(kindName, parentKey);
  323. if (keysOnly) {
  324. q.setKeysOnly();
  325. }
  326. NucleusLogger.PERSISTENCE.debug("Preparing to query for all children of " + parentKey + " of kind " + kindName);
  327. for (FilterPredicate fp : filterPredicates) {
  328. q.addFilter(fp.getPropertyName(), fp.getOperator(), fp.getValue());
  329. NucleusLogger.PERSISTENCE.debug(" Added filter: " + fp.getPropertyName() + " " + fp.getOperator() + " " + fp.getValue());
  330. }
  331. for (SortPredicate sp : sortPredicates) {
  332. q.addSort(sp.getPropertyName(), sp.getDirection());
  333. NucleusLogger.PERSISTENCE.debug(" Added sort: " + sp.getPropertyName() + " " + sp.getDirection());
  334. }
  335. DatastoreServiceConfig config = storeMgr.getDefaultDatastoreServiceConfigForReads();
  336. DatastoreService ds = DatastoreServiceFactoryInternal.getDatastoreService(config);
  337. return ds.prepare(q);
  338. }
  339. /**
  340. * Method to check if an element is already persistent, or is managed by a different
  341. * persistence manager. If not persistent, this will persist it.
  342. * @param ec ExecutionContext
  343. * @param element The element
  344. * @param fieldValues any initial field values to use if persisting the element
  345. * @return Whether the element was persisted during this call
  346. */
  347. protected boolean validateElementForWriting(ExecutionContext ec, Object element, FieldValues fieldValues) {
  348. // Check the element type for this collection
  349. if (!storeMgr.getNucleusContext().getMetaDataManager().isPersistentInterface(elementType) &&
  350. !validateElementType(ec.getClassLoaderResolver(), element)) {
  351. throw new ClassCastException(LOCALISER.msg("056033", element.getClass().getName(),
  352. ownerMemberMetaData.getFullFieldName(), elementType));
  353. }
  354. return SCOUtils.validateObjectForWriting(ec, element, fieldValues);
  355. }
  356. /**
  357. * Method to check if an element is already persistent or is persistent but managed by
  358. * a different persistence manager.
  359. * @param ec ExecutionContext
  360. * @param element The element
  361. * @return Whether it is valid for reading.
  362. */
  363. protected boolean validateElementForReading(ExecutionContext ec, Object element) {
  364. if (!validateElementType(clr, element)) {
  365. return false;
  366. }
  367. if (element != null) {
  368. if ((!ec.getApiAdapter().isPersistent(element) || ec != ec.getApiAdapter().getExecutionContext(element)) &&
  369. !ec.getApiAdapter().isDetached(element)) {
  370. return false;
  371. }
  372. }
  373. return true;
  374. }
  375. /**
  376. * Method to validate an element against the accepted type.
  377. * @param clr The ClassLoaderResolver
  378. * @param element The element to validate
  379. * @return Whether it is valid.
  380. */
  381. protected boolean validateElementType(ClassLoaderResolver clr, Object element) {
  382. if (element == null) {
  383. return true;
  384. }
  385. Class primitiveElementClass = ClassUtils.getPrimitiveTypeForType(element.getClass());
  386. if (primitiveElementClass != null) {
  387. // Allow for the element type being primitive, and the user wanting to store its wrapper
  388. String elementTypeWrapper = elementType;
  389. Class elementTypeClass = clr.classForName(elementType);
  390. if (elementTypeClass.isPrimitive()) {
  391. elementTypeWrapper = ClassUtils.getWrapperTypeForPrimitiveType(elementTypeClass).getName();
  392. }
  393. return clr.isAssignableFrom(elementTypeWrapper, element.getClass());
  394. }
  395. return clr.isAssignableFrom(elementType, element.getClass());
  396. }
  397. }