PageRenderTime 67ms CodeModel.GetById 36ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

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