Change @DoNotHydrate to work on fields, not types.

There was a circular reference when hydrating a domain with a
subordinate host, since the host references the domain. To fix
this, I redid @DoNotHydrate to be the way it should have been,
rather than the hack I had originally submitted. I also beefed
up the unit tests of the epp resource types to check for cycles.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=135792416
This commit is contained in:
cgoldfeder 2016-10-11 07:05:26 -07:00 committed by Ben McIlwain
parent 27ec47051e
commit cb8320ff40
12 changed files with 151 additions and 118 deletions

View file

@ -14,11 +14,10 @@
package google.registry.model;
import static com.google.common.base.Functions.identity;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Maps.transformValues;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.base.Function;
@ -31,10 +30,16 @@ import google.registry.model.domain.ReferenceUnion;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.annotation.concurrent.Immutable;
import javax.xml.bind.annotation.XmlTransient;
@ -43,10 +48,10 @@ import javax.xml.bind.annotation.XmlTransient;
@XmlTransient
public abstract class ImmutableObject implements Cloneable {
/** Marker to indicate that {@link #toHydratedString} should not hydrate a field of this type. */
/** Marker to indicate that {@link #toHydratedString} should not hydrate a field. */
@Documented
@Retention(RUNTIME)
@Target(TYPE)
@Target(FIELD)
public static @interface DoNotHydrate {}
@Ignore
@ -105,47 +110,55 @@ public abstract class ImmutableObject implements Cloneable {
*/
@Override
public String toString() {
return toStringHelper(identity());
NavigableMap<String, Object> sortedFields = new TreeMap<>();
for (Entry<Field, Object> entry : ModelUtils.getFieldValues(this).entrySet()) {
sortedFields.put(entry.getKey().getName(), entry.getValue());
}
return toStringHelper(sortedFields);
}
/**
* Similar to toString(), with a full expansion of embedded ImmutableObjects,
* collections, and referenced keys.
*/
/** Similar to toString(), with a full expansion of referenced keys, including in collections. */
public String toHydratedString() {
return toStringHelper(new Function<Object, Object>() {
@Override
public Object apply(Object input) {
if (input instanceof ReferenceUnion) {
return apply(((ReferenceUnion<?>) input).getLinked());
} else if (input instanceof Key) {
Object target = ofy().load().key((Key<?>) input).now();
return target != null && target.getClass().isAnnotationPresent(DoNotHydrate.class)
? input
: apply(target);
} else if (input instanceof Map) {
return transformValues((Map<?, ?>) input, this);
} else if (input instanceof Collection) {
return transform((Collection<?>) input, this);
} else if (input instanceof ImmutableObject) {
return ((ImmutableObject) input).toHydratedString();
}
return input;
}});
// We can't use ImmutableSortedMap because we need to allow null values.
NavigableMap<String, Object> sortedFields = new TreeMap<>();
for (Entry<Field, Object> entry : ModelUtils.getFieldValues(this).entrySet()) {
Field field = entry.getKey();
Object value = entry.getValue();
sortedFields.put(
field.getName(),
field.isAnnotationPresent(DoNotHydrate.class) ? value : HYDRATOR.apply(value));
}
return toStringHelper(sortedFields);
}
public String toStringHelper(Function<Object, Object> transformation) {
Map<String, Object> sortedFields = Maps.newTreeMap();
sortedFields.putAll(
transformValues(ModelUtils.getFieldValues(this), transformation));
public String toStringHelper(SortedMap<String, Object> fields) {
return String.format(
"%s (@%s): {\n%s",
getClass().getSimpleName(),
System.identityHashCode(this),
Joiner.on('\n').join(sortedFields.entrySet()))
Joiner.on('\n').join(fields.entrySet()))
.replaceAll("\n", "\n ") + "\n}";
}
/** Helper function to recursively hydrate an ImmutableObject. */
private static final Function<Object, Object> HYDRATOR =
new Function<Object, Object>() {
@Override
public Object apply(Object value) {
if (value instanceof ReferenceUnion) {
return apply(((ReferenceUnion<?>) value).getLinked());
} else if (value instanceof Key) {
return apply(ofy().load().key((Key<?>) value).now());
} else if (value instanceof Map) {
return transformValues((Map<?, ?>) value, this);
} else if (value instanceof Collection) {
return transform((Collection<?>) value, this);
} else if (value instanceof ImmutableObject) {
return ((ImmutableObject) value).toHydratedString();
}
return value;
}};
/** Helper function to recursively convert a ImmutableObject to a Map of generic objects. */
private static final Function<Object, Object> TO_MAP_HELPER = new Function<Object, Object>() {
@Override
@ -153,8 +166,11 @@ public abstract class ImmutableObject implements Cloneable {
if (o == null) {
return null;
} else if (o instanceof ImmutableObject) {
Map<String, Object> result =
Maps.transformValues(ModelUtils.getFieldValues(o), this);
// LinkedHashMap to preserve field ordering and because ImmutableMap forbids null values.
Map<String, Object> result = new LinkedHashMap<>();
for (Entry<Field, Object> entry : ModelUtils.getFieldValues(o).entrySet()) {
result.put(entry.getKey().getName(), apply(entry.getValue()));
}
return result;
} else if (o instanceof Map) {
return Maps.transformValues((Map<?, ?>) o, this);