package com.googlecode.objectify.impl; import com.google.appengine.api.datastore.PropertyContainer; import com.googlecode.objectify.annotation.Load; import com.googlecode.objectify.annotation.Parent; import com.googlecode.objectify.impl.translate.LoadContext; import com.googlecode.objectify.impl.translate.Populator; import com.googlecode.objectify.impl.translate.Recycles; import com.googlecode.objectify.impl.translate.SaveContext; import com.googlecode.objectify.impl.translate.SkipException; import com.googlecode.objectify.impl.translate.Synthetic; import com.googlecode.objectify.impl.translate.Translator; import com.googlecode.objectify.util.DatastoreUtils; import com.googlecode.objectify.util.LogUtils; import java.util.logging.Level; import java.util.logging.Logger; /** * Associates a Property with a Translator and provides a more convenient interface. */ public class PropertyPopulator<P, D> implements Populator<P> { /** */ private static final Logger log = Logger.getLogger(PropertyPopulator.class.getName()); /** */ protected Property property; protected Translator<P, D> translator; /** */ public PropertyPopulator(Property prop, Translator<P, D> trans) { this.property = prop; this.translator = trans; } /** */ public Property getProperty() { return this.property; } /** */ public LoadConditions getLoadConditions() { return new LoadConditions(property.getAnnotation(Load.class), property.getAnnotation(Parent.class)); } /** This is easier to debug if we have a string value */ @Override public String toString() { return this.getClass().getSimpleName() + "(" + property.getName() + ")"; } /** * Gets the appropriate value from the container and sets it on the appropriate field of the pojo. */ @Override public void load(PropertyContainer container, LoadContext ctx, Path containerPath, Object intoPojo) { try { if (translator instanceof Recycles) ctx.recycle(property.get(intoPojo)); D value = (translator instanceof Synthetic) ? null : getPropertyFromContainer(container, containerPath); // will throw SkipException if property not present setValue(intoPojo, value, ctx, containerPath); } catch (SkipException ex) { // Irrelevant } } /** * Gets the relevant property from the container, detecting alsoload collisions. * * @return the value obtained from the container * @throws IllegalStateException if there are multiple alsoload name matches */ private D getPropertyFromContainer(PropertyContainer container, Path containerPath) { String foundName = null; D value = null; for (String name: property.getLoadNames()) { if (container.hasProperty(name)) { if (foundName != null) throw new IllegalStateException("Collision trying to load field; multiple name matches for '" + property.getName() + "' at '" + containerPath.extend(foundName) + "' and '" + containerPath.extend(name) + "'"); //noinspection unchecked value = (D)container.getProperty(name); foundName = name; } } if (foundName == null) throw new SkipException(); else return value; } /** * Set this raw datastore value on the relevant property of the pojo, doing whatever translations are necessary. */ public void setValue(Object pojo, D value, LoadContext ctx, Path containerPath) throws SkipException { Path propertyPath = containerPath.extend(property.getName()); P loaded = translator.load(value, ctx, propertyPath); setOnPojo(pojo, loaded, ctx, propertyPath); } /** * Sets the property on the pojo to the value. The value should already be translated. * TODO: Sensitive to the value possibly being a Result<?> wrapper, in which case it enqueues the set operation until the loadcontext is done. */ private void setOnPojo(Object pojo, P value, LoadContext ctx, Path path) { if (log.isLoggable(Level.FINEST)) log.finest(LogUtils.msg(path, "Setting property " + property.getName() + " to " + value)); property.set(pojo, value); } /** * Gets the appropriate field value from the pojo and puts it in the container at the appropriate prop name * and with the appropriate indexing. * @param onPojo is the parent pojo which holds the property we represent * @param index is the default state of indexing up to this point * @param containerPath is the path to the container; each property will extend this path. */ @Override public void save(Object onPojo, boolean index, SaveContext ctx, Path containerPath, PropertyContainer into) { if (property.isSaved(onPojo)) { // Look for an override on indexing Boolean propertyIndexInstruction = property.getIndexInstruction(onPojo); if (propertyIndexInstruction != null) index = propertyIndexInstruction; @SuppressWarnings("unchecked") P value = (P)property.get(onPojo); try { Path propPath = containerPath.extend(property.getName()); Object propValue = translator.save(value, index, ctx, propPath); DatastoreUtils.setContainerProperty(into, property.getName(), propValue, index, ctx, propPath); } catch (SkipException ex) { // No problem, do nothing } } } /** * Get the value for the property and translate it into datastore format. */ public D getValue(Object pojo, SaveContext ctx, Path containerPath) { @SuppressWarnings("unchecked") P value = (P)property.get(pojo); return translator.save(value, false, ctx, containerPath.extend(property.getName())); } }