package com.googlecode.objectify.impl; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.PropertyContainer; import com.googlecode.objectify.Key; import com.googlecode.objectify.LoadException; import com.googlecode.objectify.ObjectifyFactory; import com.googlecode.objectify.SaveException; import com.googlecode.objectify.annotation.Cache; import com.googlecode.objectify.impl.translate.ClassTranslator; import com.googlecode.objectify.impl.translate.EntityCreator; import com.googlecode.objectify.impl.translate.LoadContext; import com.googlecode.objectify.impl.translate.SaveContext; import com.googlecode.objectify.impl.translate.Translator; import java.util.ArrayList; import java.util.Collection; import java.util.Map; /** * Holds basic information about POJO entities, and can translate back and forth to the * datastore representation. * * @author Jeff Schnitzer <jeff@infohazard.org> */ public class EntityMetadata<P> { /** The base entity class type, ie the class with the @Entity annotation */ private Class<P> entityClass; /** The cached annotation, or null if entity should not be cached */ private Cache cached; /** */ private ClassTranslator<P> translator; /** */ private KeyMetadata<P> keyMetadata; /** * @param clazz must have @Entity in its hierarchy */ public EntityMetadata(ObjectifyFactory fact, Class<P> clazz) { assert clazz.isAnnotationPresent(com.googlecode.objectify.annotation.Entity.class); this.entityClass = clazz; this.cached = clazz.getAnnotation(Cache.class); this.translator = (ClassTranslator<P>)fact.getTranslators().getRoot(clazz); this.keyMetadata = ((EntityCreator<P>)translator.getCreator()).getKeyMetadata(); } /** * Get the expiry associated with this kind, defined by the @Cached annotation. * For polymorphic types, this is always the instruction on the root @Entity - you * cannot provide per-type caching. * * @return null means DO NOT CACHE, 0 means "no limit", otherwise # of seconds */ public Integer getCacheExpirySeconds() { return this.cached == null ? null : this.cached.expirationSeconds(); } /** * Converts an entity to an object of the appropriate type for this metadata structure. * Does not check that the entity is appropriate; that should be done when choosing * which EntityMetadata to call. */ public P load(Entity ent, LoadContext ctx) { try { // The context needs to know the root entity for any given point ctx.setCurrentRoot(Key.create(ent.getKey())); return translator.load(ent, ctx, Path.root()); } catch (LoadException ex) { throw ex; } catch (Exception ex) { throw new LoadException(ent, ex.getMessage(), ex); } } /** * Converts an object to a datastore Entity with the appropriate Key type. */ public Entity save(P pojo, SaveContext ctx) { try { ctx.startOneEntity(); Entity ent = (Entity) translator.save(pojo, false, ctx, Path.root()); createSyntheticIndexes(ent, ctx); return ent; } catch (SaveException ex) { throw ex; } catch (Exception ex) { throw new SaveException(pojo, ex.getMessage(), ex); } } /** * Gets the class associated with this entity. */ public Class<P> getEntityClass() { return this.entityClass; } /** * Get specific metadata about the key for this type. */ public KeyMetadata<P> getKeyMetadata() { return keyMetadata; } /** = * @return the translator that will convert between native datastore representation and pojo for this type. */ public Translator<P, PropertyContainer> getTranslator() { return translator; } /** * Establish any synthetic dot-separate indexes for embedded things that are indexed. */ private void createSyntheticIndexes(Entity entity, SaveContext ctx) { // Look for anything which is embedded and therefore won't be automatically indexed for (Map.Entry<Path, Collection<Object>> index: ctx.getIndexes().entrySet()) { Path path = index.getKey(); Collection<Object> values = index.getValue(); if (path.isEmbedded()) { // Need to copy the values list otherwise it will clear when we reset the context indexes entity.setProperty(path.toPathString(), new ArrayList<>(values)); } } } }